From 132a449a0cc8154be3b79826a4afd6239ead5360 Mon Sep 17 00:00:00 2001
From: Serenibyss <10861407+serenibyss@users.noreply.github.com>
Date: Tue, 18 Jun 2024 18:13:46 -0500
Subject: [PATCH] Allow recipe logic to set EU/t and speed discounts (#2496)
---
.../capability/impl/AbstractRecipeLogic.java | 71 +++++++++
.../impl/AbstractRecipeLogicTest.java | 137 ++++++++++++------
2 files changed, 163 insertions(+), 45 deletions(-)
diff --git a/src/main/java/gregtech/api/capability/impl/AbstractRecipeLogic.java b/src/main/java/gregtech/api/capability/impl/AbstractRecipeLogic.java
index 6d8877ff1dc..4ff7875d7bb 100644
--- a/src/main/java/gregtech/api/capability/impl/AbstractRecipeLogic.java
+++ b/src/main/java/gregtech/api/capability/impl/AbstractRecipeLogic.java
@@ -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;
@@ -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;
@@ -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,
@@ -508,6 +530,55 @@ public void setParallelLimit(int amount) {
parallelLimit = amount;
}
+ /**
+ * Sets an EU/t discount to apply to a machine when running recipes.
+ * This does NOT affect recipe lookup voltage, even if the discount drops it to a lower voltage tier.
+ * 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.
+ * Be careful as this may not always be possible within the EU/t maximums of the machine!
+ *
+ */
+ 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.
+ * 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
*/
diff --git a/src/test/java/gregtech/api/capability/impl/AbstractRecipeLogicTest.java b/src/test/java/gregtech/api/capability/impl/AbstractRecipeLogicTest.java
index 68ae9384de7..056f025c8e0 100644
--- a/src/test/java/gregtech/api/capability/impl/AbstractRecipeLogicTest.java
+++ b/src/test/java/gregtech/api/capability/impl/AbstractRecipeLogicTest.java
@@ -30,10 +30,93 @@ 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 map = new RecipeMap<>("test_reactor",
+ RecipeMap map = new RecipeMap<>("test_reactor_" + TEST_ID,
2,
2,
3,
@@ -41,9 +124,9 @@ public void trySearchNewRecipe() {
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));
@@ -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
@@ -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));
}
}