Skip to content

Commit 99d9bc9

Browse files
committed
Fix ULV recipe overclocking bug
ULV recipes erroneously overclocked to the highest possible energy tier, needlessly increasing energy usage for recipes that reached maximum speed at a lower tier of overclocking. Duration is now properly considered to prevent this from happening. Added a test case to AbstractRecipeLogicTest to verify the fix. Also refactored AbstractRecipeLogic::calculateOverclock to accept a Recipe instead of extracted values from a Recipe, and to calculate the number of overclocks for non-ULV recipes without a loop. Add some more documentation to related methods.
1 parent 0d3437e commit 99d9bc9

File tree

3 files changed

+125
-19
lines changed

3 files changed

+125
-19
lines changed

src/main/java/gregtech/api/capability/impl/AbstractRecipeLogic.java

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -279,8 +279,15 @@ protected static boolean areItemStacksEqual(ItemStack stackA, ItemStack stackB)
279279
ItemStack.areItemStackTagsEqual(stackA, stackB));
280280
}
281281

282+
/**
283+
* Attempts to start the specified recipe. A recipe will fail to start if there is insufficient energy,
284+
* the output inventories are full, or the required ingredients are not present.
285+
*
286+
* @param recipe the recipe to start
287+
* @return {@code true} if the recipe was started and inputs consumed, {@code false} otherwise.
288+
*/
282289
protected boolean setupAndConsumeRecipeInputs(Recipe recipe) {
283-
int[] resultOverclock = calculateOverclock(recipe.getEUt(), recipe.getDuration());
290+
int[] resultOverclock = calculateOverclock(recipe);
284291
int totalEUt = resultOverclock[0] * resultOverclock[1];
285292
IItemHandlerModifiable importInventory = getInputInventory();
286293
IItemHandlerModifiable exportInventory = getOutputInventory();
@@ -306,11 +313,32 @@ protected boolean setupAndConsumeRecipeInputs(Recipe recipe) {
306313
return recipe.matches(true, importInventory, importFluids);
307314
}
308315

309-
protected int[] calculateOverclock(int EUt, int duration) {
310-
return calculateOverclock(EUt, this.overclockPolicy.getAsLong(), duration);
316+
/**
317+
* Performs overclocking with voltage using {@link #overclockPolicy} for the voltage.
318+
* @see #calculateOverclock(Recipe, long)
319+
*/
320+
protected int[] calculateOverclock(@NotNull Recipe recipe) {
321+
return calculateOverclock(recipe, this.overclockPolicy.getAsLong());
311322
}
312323

313-
protected int[] calculateOverclock(int EUt, long voltage, int duration) {
324+
/**
325+
* Attempts to overclock a given recipe.
326+
* <ul>
327+
* <li>Recipes at or below 16 EU/t overclock by halving duration and quadrupling energy consumption, until the duration
328+
* reaches a single game tick or the EU/t can no longer be increased.</li>
329+
* <li>Recipes above 16 EU/t overclock by dividing duration by 2.8 and quadrupling energy consumption, until the duration
330+
* reaches fewer than 3 game ticks or the EU/t can no longer be increased.</li>
331+
* </ul>
332+
* @param recipe the Recipe to overclock
333+
* @param voltage the maximum EU/t to use for overclocking. This value must be positive.
334+
* @return an {@code int[]} of length 2, where [0] is the computed EU/t and [1] the duration.
335+
*/
336+
protected int[] calculateOverclock(@NotNull final Recipe recipe, final long voltage) {
337+
assert(voltage >= 0);
338+
339+
int EUt = recipe.getEUt();
340+
int duration = recipe.getDuration();
341+
314342
if (!allowOverclocking) {
315343
return new int[] {EUt, duration};
316344
}
@@ -322,19 +350,22 @@ protected int[] calculateOverclock(int EUt, long voltage, int duration) {
322350
EUt = -EUt;
323351
if (EUt <= 16) {
324352
int multiplier = EUt <= 8 ? tier : tier - 1;
325-
int resultEUt = EUt * (1 << multiplier) * (1 << multiplier);
326-
int resultDuration = duration / (1 << multiplier);
327-
return new int[]{negativeEU ? -resultEUt : resultEUt, resultDuration};
353+
// Restrict the maximum number of overclocks to how many times the duration can be halved
354+
int speedCap = (31 - Integer.numberOfLeadingZeros(duration));
355+
if(multiplier > speedCap) multiplier = speedCap;
356+
EUt *= (1 << 2 * multiplier);
357+
duration /= (1 << multiplier);
328358
} else {
329-
int resultEUt = EUt;
330-
double resultDuration = duration;
331-
//do not overclock further if duration is already too small
332-
while (resultDuration >= 3 && resultEUt <= GTValues.V[tier - 1]) {
333-
resultEUt *= 4;
334-
resultDuration /= 2.8;
359+
// Restrict the maximum number of overclocks to how many times the duration is divisible by 2.8
360+
int speedCap = (int) (Math.log(duration) / Math.log(2.8));
361+
int dt = tier - recipe.getBaseTier();
362+
if(dt > speedCap) dt = speedCap;
363+
if(dt > 0) {
364+
EUt *= Math.pow(4, dt);
365+
duration /= Math.pow(2.8, dt);
335366
}
336-
return new int[]{negativeEU ? -resultEUt : resultEUt, (int) Math.ceil(resultDuration)};
337367
}
368+
return new int[]{negativeEU ? -EUt : EUt, duration};
338369
}
339370

340371
protected int getOverclockingTier(long voltage) {
@@ -356,7 +387,7 @@ public String[] getAvailableOverclockingTiers() {
356387
}
357388

358389
protected void setupRecipe(Recipe recipe) {
359-
int[] resultOverclock = calculateOverclock(recipe.getEUt(), recipe.getDuration());
390+
int[] resultOverclock = calculateOverclock(recipe);
360391
this.progressTime = 1;
361392
setMaxProgress(resultOverclock[1]);
362393
this.recipeEUt = resultOverclock[0];
@@ -399,8 +430,13 @@ public List<Pair<ItemStack, Integer>> getChancedItemOutputs() {
399430
return chancedItemOutputs;
400431
}
401432

433+
/**
434+
* <b>This is NOT for energy calculations. Use {@link #getOverclockingTier(long)} for energy.</b><br />
435+
* Used to override the machine's tier for the purposes of determining chanced outputs.
436+
* The default implementation simply returns the overclocking tier of the maximum voltage of the machine.
437+
*/
402438
protected int getMachineTierForRecipe(Recipe recipe) {
403-
return GTUtility.getTierByVoltage(getMaxVoltage());
439+
return getOverclockingTier(getMaxVoltage());
404440
}
405441

406442
protected void completeRecipe() {

src/main/java/gregtech/api/capability/impl/RecipeLogicSteam.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import net.minecraft.util.math.BlockPos;
2020
import net.minecraft.world.WorldServer;
2121
import net.minecraftforge.fluids.IFluidTank;
22+
import org.jetbrains.annotations.NotNull;
2223

2324
public class RecipeLogicSteam extends AbstractRecipeLogic {
2425

@@ -159,12 +160,12 @@ protected void completeRecipe() {
159160
}
160161

161162
@Override
162-
protected int[] calculateOverclock(int EUt, long voltage, int duration) {
163+
protected int[] calculateOverclock(@NotNull final Recipe recipe) {
163164
if (!isHighPressure) {
164165
//disallow overclocking for low pressure bronze machines
165-
return new int[]{EUt, duration};
166+
return new int[]{recipe.getEUt(), recipe.getDuration()};
166167
}
167-
return super.calculateOverclock(EUt, voltage, duration);
168+
return super.calculateOverclock(recipe);
168169
}
169170

170171
@Override

src/test/java/gregtech/api/capability/impl/AbstractRecipeLogicTest.java

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,4 +129,73 @@ protected long getMaxVoltage() {
129129
assertTrue(AbstractRecipeLogic.areItemStacksEqual(arl.getOutputInventory().getStackInSlot(0),
130130
new ItemStack(Blocks.STONE, 1)));
131131
}
132+
133+
@Test
134+
public void ulv_recipe_overclock_test() {
135+
136+
World world = DummyWorld.INSTANCE;
137+
138+
// Create an empty recipe map to work with
139+
RecipeMap<SimpleRecipeBuilder> map = new RecipeMap<>("chemical_reactor_2",
140+
0,
141+
2,
142+
0,
143+
2,
144+
0,
145+
3,
146+
0,
147+
2,
148+
new SimpleRecipeBuilder().EUt(30),
149+
false);
150+
151+
MetaTileEntity at =
152+
GregTechAPI.registerMetaTileEntity(198,
153+
new SimpleMachineMetaTileEntity(
154+
new ResourceLocation(GTValues.MODID, "chemical_reactor.uv"),
155+
map,
156+
Textures.CHEMICAL_REACTOR_OVERLAY,
157+
1));
158+
159+
MetaTileEntity atte = new MetaTileEntityHolder().setMetaTileEntity(at);
160+
atte.getHolder().setWorld(world);
161+
162+
// Recipe that will reach maximum speed (1t) at ZPM
163+
map.recipeBuilder()
164+
.inputs(new ItemStack(Blocks.COBBLESTONE))
165+
.outputs(new ItemStack(Blocks.STONE))
166+
.EUt(4).duration(128)
167+
.buildAndRegister();
168+
169+
// UV-tier machine
170+
AbstractRecipeLogic arl = new AbstractRecipeLogic(atte, map) {
171+
@Override
172+
protected long getEnergyStored() {
173+
return Long.MAX_VALUE;
174+
}
175+
176+
@Override
177+
protected long getEnergyCapacity() {
178+
return Long.MAX_VALUE;
179+
}
180+
181+
@Override
182+
protected boolean drawEnergy(int recipeEUt) {
183+
return true;
184+
}
185+
186+
@Override
187+
protected long getMaxVoltage() {
188+
return GTValues.V[GTValues.UV];
189+
}
190+
};
191+
192+
arl.getInputInventory().insertItem(0, new ItemStack(Blocks.COBBLESTONE, 16), false);
193+
Recipe recipe = arl.findRecipe(arl.getMaxVoltage(), arl.getInputInventory(), arl.getInputTank());
194+
int[] oc = arl.calculateOverclock(recipe);
195+
196+
// Expect seven overclocks to a half-amp of ZPM and capped 1t duration, instead of previous behavior where
197+
// the energy consumption increased as though an eighth overclock was performed
198+
assertEquals(GTValues.V[GTValues.ZPM] / 2, oc[0]);
199+
assertEquals(1, oc[1]);
200+
}
132201
}

0 commit comments

Comments
 (0)