Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow recipe logic to set EU/t and speed discounts #2496

Merged
merged 7 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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));
}
}
Loading