Skip to content

Commit

Permalink
Allow recipe logic to set EU/t and speed discounts (GregTechCEu#2496)
Browse files Browse the repository at this point in the history
  • Loading branch information
serenibyss authored Jun 18, 2024
1 parent a68d66f commit 132a449
Show file tree
Hide file tree
Showing 2 changed files with 163 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@
import gregtech.api.metatileentity.multiblock.ICleanroomReceiver;
import gregtech.api.metatileentity.multiblock.ParallelLogicType;
import gregtech.api.recipes.Recipe;
import gregtech.api.recipes.RecipeBuilder;
import gregtech.api.recipes.RecipeMap;
import gregtech.api.recipes.logic.IParallelableRecipeLogic;
import gregtech.api.recipes.recipeproperties.CleanroomProperty;
import gregtech.api.recipes.recipeproperties.DimensionProperty;
import gregtech.api.recipes.recipeproperties.IRecipePropertyStorage;
import gregtech.api.util.GTLog;
import gregtech.api.util.GTTransferUtils;
import gregtech.api.util.GTUtility;
import gregtech.common.ConfigHolder;
Expand Down Expand Up @@ -50,6 +52,9 @@ public abstract class AbstractRecipeLogic extends MTETrait implements IWorkable,

private final RecipeMap<?> recipeMap;

private double euDiscount = -1;
private double speedBonus = -1;

protected Recipe previousRecipe;
private boolean allowOverclocking = true;
protected int parallelRecipesPerformed;
Expand Down Expand Up @@ -457,6 +462,23 @@ public boolean prepareRecipe(Recipe recipe, IItemHandlerModifiable inputInventor
recipe = Recipe.trimRecipeOutputs(recipe, getRecipeMap(), metaTileEntity.getItemOutputLimit(),
metaTileEntity.getFluidOutputLimit());

// apply EU/speed discount (if any) before parallel
if (euDiscount > 0 || speedBonus > 0) { // if-statement to avoid unnecessarily creating RecipeBuilder object
RecipeBuilder<?> builder = new RecipeBuilder<>(recipe, recipeMap);
if (euDiscount > 0) {
int newEUt = (int) Math.round(recipe.getEUt() * euDiscount);
if (newEUt <= 0) newEUt = 1;
builder.EUt(newEUt);
}
if (speedBonus > 0) {
int duration = recipe.getDuration();
int newDuration = (int) Math.round(duration * speedBonus);
if (newDuration <= 0) newDuration = 1;
builder.duration(newDuration);
}
recipe = builder.build().getResult();
}

// Pass in the trimmed recipe to the parallel logic
recipe = findParallelRecipe(
recipe,
Expand Down Expand Up @@ -508,6 +530,55 @@ public void setParallelLimit(int amount) {
parallelLimit = amount;
}

/**
* Sets an EU/t discount to apply to a machine when running recipes.<br>
* This does NOT affect recipe lookup voltage, even if the discount drops it to a lower voltage tier.<br>
* This discount is applied pre-parallel/pre-overclock.
*
* @param discount The discount, must be greater than 0 and less than 1.
* If discount == 0.75, then the recipe will only require 75% of the listed power to run.
* If discount is > 1, then the recipe will require more than the listed power to run.
* <strong>Be careful as this may not always be possible within the EU/t maximums of the machine!
* </strong>
*/
public void setEUDiscount(double discount) {
if (discount <= 0) {
GTLog.logger.warn("Cannot set EU discount for recipe logic to {}, discount must be > 0", discount);
return;
}
euDiscount = discount;
}

/**
* @return the EU/t discount, or -1 if no discount.
*/
public double getEUtDiscount() {
return euDiscount;
}

/**
* Sets a speed multiplier to apply to a machine when running recipes.<br>
* This discount is applied pre-parallel/pre-overclock.
*
* @param bonus The bonus, must be greater than 0.
* If bonus == 0.2, then the recipe will be 20% of the normal duration.
* If bonus is > 1, then the recipe will be slower than the normal duration.
*/
public void setSpeedBonus(double bonus) {
if (bonus <= 0) {
GTLog.logger.warn("Cannot set speed bonus for recipe logic to {}, bonus must be > 0", bonus);
return;
}
speedBonus = bonus;
}

/**
* @return the speed bonus, or -1 if no bonus.
*/
public double getSpeedBonus() {
return speedBonus;
}

/**
* @return the parallel logic type to use for recipes
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,103 @@ public static void bootstrap() {

@Test
public void trySearchNewRecipe() {
AbstractRecipeLogic arl = createTestLogic(1, 1);
arl.trySearchNewRecipe();

// no recipe found
MatcherAssert.assertThat(arl.invalidInputsForRecipes, is(true));
MatcherAssert.assertThat(arl.isActive, is(false));
MatcherAssert.assertThat(arl.previousRecipe, nullValue());

queryTestRecipe(arl);
MatcherAssert.assertThat(arl.invalidInputsForRecipes, is(false));
MatcherAssert.assertThat(arl.previousRecipe, notNullValue());
MatcherAssert.assertThat(arl.isActive, is(true));
MatcherAssert.assertThat(arl.getInputInventory().getStackInSlot(0).getCount(), is(15));

// Save a reference to the old recipe so we can make sure it's getting reused
Recipe prev = arl.previousRecipe;

// Finish the recipe, the output should generate, and the next iteration should begin
arl.update();
MatcherAssert.assertThat(arl.previousRecipe, is(prev));
MatcherAssert.assertThat(AbstractRecipeLogic.areItemStacksEqual(arl.getOutputInventory().getStackInSlot(0),
new ItemStack(Blocks.STONE, 1)), is(true));
MatcherAssert.assertThat(arl.isActive, is(true));

// Complete the second iteration, but the machine stops because its output is now full
arl.getOutputInventory().setStackInSlot(0, new ItemStack(Blocks.STONE, 63));
arl.getOutputInventory().setStackInSlot(1, new ItemStack(Blocks.STONE, 64));
arl.update();
MatcherAssert.assertThat(arl.isActive, is(false));
MatcherAssert.assertThat(arl.isOutputsFull, is(true));

// Try to process again and get failed out because of full buffer.
arl.update();
MatcherAssert.assertThat(arl.isActive, is(false));
MatcherAssert.assertThat(arl.isOutputsFull, is(true));

// Some room is freed in the output bus, so we can continue now.
arl.getOutputInventory().setStackInSlot(1, ItemStack.EMPTY);
arl.update();
MatcherAssert.assertThat(arl.isActive, is(true));
MatcherAssert.assertThat(arl.isOutputsFull, is(false));
MatcherAssert.assertThat(AbstractRecipeLogic.areItemStacksEqual(arl.getOutputInventory().getStackInSlot(0),
new ItemStack(Blocks.STONE, 1)), is(true));
}

@Test
public void euAndSpeedBonus() {
final int initialEUt = 30;
final int initialDuration = 100;

AbstractRecipeLogic arl = createTestLogic(initialEUt, initialDuration);
arl.setEUDiscount(0.75); // 75% EU cost required
arl.setSpeedBonus(0.2); // 20% faster than normal

queryTestRecipe(arl);
MatcherAssert.assertThat(arl.recipeEUt, is((int) Math.round(initialEUt * 0.75)));
MatcherAssert.assertThat(arl.maxProgressTime, is((int) Math.round(initialDuration * 0.2)));
}

@Test
public void euAndSpeedBonusParallel() {
final int initialEUt = 30;
final int initialDuration = 100;

AbstractRecipeLogic arl = createTestLogic(initialEUt, initialDuration);
arl.setEUDiscount(0.5); // 50% EU cost required
arl.setSpeedBonus(0.2); // 20% faster than normal
arl.setParallelLimit(4); // Allow parallels

queryTestRecipe(arl);

// The EU discount should drop the EU/t of this recipe to 15 EU/t. As a result, this should now
// be able to parallel 2 times.
MatcherAssert.assertThat(arl.parallelRecipesPerformed, is(2));
// Because of the parallel, now the paralleled recipe EU/t should be back to 30 EU/t.
MatcherAssert.assertThat(arl.recipeEUt, is(30));
// Duration should be static regardless of parallels.
MatcherAssert.assertThat(arl.maxProgressTime, is((int) Math.round(initialDuration * 0.2)));
}

private static int TEST_ID = 190;

private static AbstractRecipeLogic createTestLogic(int testRecipeEUt, int testRecipeDuration) {
World world = DummyWorld.INSTANCE;

// Create an empty recipe map to work with
RecipeMap<SimpleRecipeBuilder> map = new RecipeMap<>("test_reactor",
RecipeMap<SimpleRecipeBuilder> map = new RecipeMap<>("test_reactor_" + TEST_ID,
2,
2,
3,
2,
new SimpleRecipeBuilder().EUt(30),
false);

MetaTileEntity at = MetaTileEntities.registerMetaTileEntity(190,
MetaTileEntity at = MetaTileEntities.registerMetaTileEntity(TEST_ID,
new SimpleMachineMetaTileEntity(
GTUtility.gregtechId("chemical_reactor.lv"),
GTUtility.gregtechId("chemical_reactor.lv_" + TEST_ID),
map,
null,
1, false));
Expand All @@ -52,9 +135,11 @@ public void trySearchNewRecipe() {
map.recipeBuilder()
.inputs(new ItemStack(Blocks.COBBLESTONE))
.outputs(new ItemStack(Blocks.STONE))
.EUt(1).duration(1)
.EUt(testRecipeEUt).duration(testRecipeDuration)
.buildAndRegister();

TEST_ID++;

AbstractRecipeLogic arl = new AbstractRecipeLogic(atte, map) {

@Override
Expand Down Expand Up @@ -85,51 +170,13 @@ public long getMaxVoltage() {

arl.isOutputsFull = false;
arl.invalidInputsForRecipes = false;
arl.trySearchNewRecipe();

// no recipe found
MatcherAssert.assertThat(arl.invalidInputsForRecipes, is(true));
MatcherAssert.assertThat(arl.isActive, is(false));
MatcherAssert.assertThat(arl.previousRecipe, nullValue());
return arl;
}

private static void queryTestRecipe(AbstractRecipeLogic arl) {
// put an item in the inventory that will trigger recipe recheck
arl.getInputInventory().insertItem(0, new ItemStack(Blocks.COBBLESTONE, 16), false);
// Inputs change. did we detect it ?
MatcherAssert.assertThat(arl.hasNotifiedInputs(), is(true));
arl.trySearchNewRecipe();
MatcherAssert.assertThat(arl.invalidInputsForRecipes, is(false));
MatcherAssert.assertThat(arl.previousRecipe, notNullValue());
MatcherAssert.assertThat(arl.isActive, is(true));
MatcherAssert.assertThat(arl.getInputInventory().getStackInSlot(0).getCount(), is(15));

// Save a reference to the old recipe so we can make sure it's getting reused
Recipe prev = arl.previousRecipe;

// Finish the recipe, the output should generate, and the next iteration should begin
arl.update();
MatcherAssert.assertThat(arl.previousRecipe, is(prev));
MatcherAssert.assertThat(AbstractRecipeLogic.areItemStacksEqual(arl.getOutputInventory().getStackInSlot(0),
new ItemStack(Blocks.STONE, 1)), is(true));
MatcherAssert.assertThat(arl.isActive, is(true));

// Complete the second iteration, but the machine stops because its output is now full
arl.getOutputInventory().setStackInSlot(0, new ItemStack(Blocks.STONE, 63));
arl.getOutputInventory().setStackInSlot(1, new ItemStack(Blocks.STONE, 64));
arl.update();
MatcherAssert.assertThat(arl.isActive, is(false));
MatcherAssert.assertThat(arl.isOutputsFull, is(true));

// Try to process again and get failed out because of full buffer.
arl.update();
MatcherAssert.assertThat(arl.isActive, is(false));
MatcherAssert.assertThat(arl.isOutputsFull, is(true));

// Some room is freed in the output bus, so we can continue now.
arl.getOutputInventory().setStackInSlot(1, ItemStack.EMPTY);
arl.update();
MatcherAssert.assertThat(arl.isActive, is(true));
MatcherAssert.assertThat(arl.isOutputsFull, is(false));
MatcherAssert.assertThat(AbstractRecipeLogic.areItemStacksEqual(arl.getOutputInventory().getStackInSlot(0),
new ItemStack(Blocks.STONE, 1)), is(true));
}
}

0 comments on commit 132a449

Please sign in to comment.