Skip to content

Culinary energy generation issue + JEI display issue #24

@tatoyoda600

Description

@tatoyoda600

Issue:

While messing around with the Culinary Generator in a modpack (All results were able to be replicated in an instance with just this mod and JEI), I noticed that the JEI rates were wildly different from the real rates I was experiencing, so I started tracking the rates manually, which is how I came across the fact that when the Culinary Generator processes 2 different foods, the order in which they're processed can greatly affect the rates.
This is especially notable as the processing order is not per generator, it is per world, therefore the energy produced by a Culinary Generator in a world that contains more than 1 is essentially unpredictable (unless they're all processing food of the same nutritional value).

(All numbers will be from a x64 Culinary Generator, with a JSON generationRate of 8, and increasedConsumption enabled)

While a standard Steak generates 819200FE, and a Potato generates 5120FE, processing a Steak and a Potato does not produce 824320FE, instead it produces either 6556160FE or 821760FE, depending on the order they're processed in.

Prior Input > Current Input => Energy Produced by Current Input (Difference from expected result)

Steak > Steak => 819200FE (+0%)
Potato > Potato => 5120FE (+0%)
Steak > Potato => 2560FE (-50%)
Potato > Steak => 6553600FE (+700%)

Since the boost to more nutritional foods is so substantial, and less nutritional foods have less to lose out on, this is overall a net-positive, as it lets you squeeze more energy out of nutritional food by processing cheap food beforehand, and the boost to the nutritional food should be more than enough to cover it in nearly all cases.


Cause:

The formulas for calculating the rates are as follows

  • baseGenRate: 8 (Default config value)
  • tickRate: 5 (Default config value)
  • modifier: 64 (I used x64 generator for my testing, but would be 8 or 1 if you use x8 or x1 generators)
  • generationRate: nutrition * baseGenRate (If result is <= 0, defaults to baseGenRate)
  • uiRate: (generationRate) * modifier
  • litDuration: (nutrition * saturation * 8000 / generationRate) / 64 (Gets rounded down to whole value)
  • total: litDuration * uiRate
    • For the math to work you need to actually round 'litDuration' up to the nearest multiple of 'tickRate' beforehand
      • Something like: ceil(litDuration / tickRate) * tickRate
      • The generators process in steps of 'tickRate' ticks and don't check how much was really left in 'litDuration'

This results in values like these:

Food (nutrition | saturation) -> generationRate: value, uiRate: value, litDuration: roundedValue (value), total: value

Weekend Picnic (43 | 103.2) -> generationRate: 344, uiRate: 22016, litDuration: 1612t (1612.5), total: 35555840FE
Allthemodium Carrot (40 | 320.0) -> generationRate: 320, uiRate: 20480, litDuration: 5000t (5000.0), total: 102400000FE
Steak (8 | 12.8) -> generationRate: 64, uiRate: 4096, litDuration: 200t (200.0), total: 819200FE
Apple (4 | 2.4) -> generationRate: 32, uiRate: 2048, litDuration: 37t (37.5), total: 81920FE
Carrot (3 | 3.6) -> generationRate: 24, uiRate: 1536, litDuration: 56t (56.25), total: 92160FE
Berry (2 | 0.4) -> generationRate: 16, uiRate: 1024, litDuration: 6t (6.25), total: 10240FE
Beetroot (1 | 1.2) -> generationRate: 8, uiRate: 512, litDuration: 18t (18.75), total: 10240FE
Potato (1 | 0.6) -> generationRate: 8, uiRate: 512, litDuration: 9t (9.37), total: 5120FE
Soap (0 | 0.0) -> generationRate: 8, uiRate: 512, litDuration: 0t (0.0), total: 0FE


However, the 'generationRate' value used in the 'litDuration' formula up above isn't actually the 'generationRate' value of the food you inserted.
The 'litDuration' formula reads the value from the last food that was processed (server-wide), meaning the previous input can affect the energy production of your next input.

For example, if you insert a Potato (generationRate: 8), and then follow it up with the items from the table, the results look quite different.

Food (nutrition | saturation) -> generationRate: value, uiRate: value, litDuration: roundedValue (value), total: value (difference)

Weekend Picnic (43 | 103.2) -> generationRate: 344, uiRate: 22016, litDuration: 69337t (69337.5), total: 1526589440FE (+4193%)
Allthemodium Carrot (40 | 320.0) -> generationRate: 320, uiRate: 20480, litDuration: 200000t (200000.0), total: 4096000000FE (+3900%)
Steak (8 | 12.8) -> generationRate: 64, uiRate: 4096, litDuration: 1600t (1600.0), total: 6553600FE (+700%)
Apple (4 | 2.4) -> generationRate: 32, uiRate: 2048, litDuration: 150t (150.0), total: 307200FE (+275%)
Carrot (3 | 3.6) -> generationRate: 24, uiRate: 1536, litDuration: 168t (168.75), total: 261120FE (+183%)
Berry (2 | 0.4) -> generationRate: 16, uiRate: 1024, litDuration: 12t (12.5), total: 15360FE (+50%)
Beetroot (1 | 1.2) -> generationRate: 8, uiRate: 512, litDuration: 18t (18.75), total: 10240FE (+0%)
Potato (1 | 0.6) -> generationRate: 8, uiRate: 512, litDuration: 9t (9.37), total: 5120FE (+0%)
Soap (0 | 0.0) -> generationRate: 8, uiRate: 512, litDuration: 0t (0.0), total: 0FE (+0%)


On the other hand, if you were to insert a Weekend Picnic (generationRate: 344), and then follow it up with the items from the table, the results wouldn't look as positive.

Food (nutrition | saturation) -> generationRate: value, uiRate: value, litDuration: roundedValue (value), total: value (difference)

Weekend Picnic (43 | 103.2) -> generationRate: 344, uiRate: 22016, litDuration: 1612t (1612.5), total: 35555840FE (-0%)
Allthemodium Carrot (40 | 320.0) -> generationRate: 320, uiRate: 20480, litDuration: 4651t (4651.15), total: 95334400FE (-6%)
Steak (8 | 12.8) -> generationRate: 64, uiRate: 4096, litDuration: 37t (37.20), total: 163840FE (-80%)
Apple (4 | 2.4) -> generationRate: 32, uiRate: 2048, litDuration: 3t (3.48), total: 10240FE (-87%)
Carrot (3 | 3.6) -> generationRate: 24, uiRate: 1536, litDuration: 3t (3.92), total: 7680FE (-91%)
Berry (2 | 0.4) -> generationRate: 16, uiRate: 1024, litDuration: 0t (0.28), total: 0FE (-100%)
Beetroot (1 | 1.2) -> generationRate: 8, uiRate: 512, litDuration: 0t (0.42), total: 0FE (-100%)
Potato (1 | 0.6) -> generationRate: 8, uiRate: 512, litDuration: 0t (0.20), total: 0FE (-100%)
Soap (0 | 0.0) -> generationRate: 8, uiRate: 512, litDuration: 0t (0.0), total: 0FE (-0%)


As for the JEI rates, they seem to basically pick a food (Presumably the 1st that gets its recipe calculated) and then calculates the rest as if that food came right before it, which bloats the values of the more nutritional foods and lowers those of less nutritional foods. Also the calculations don't take rounding or the mod's tick rate configuration into account, which further distorts the values from the real ones.

  • By not rounding down 'litDuration' and then rounding it up to a multiple of 'tickRate', the calculations don't match the process used in-game

Potential Solution:

The root of the issue of both these issues appears to be GeneratorUtil.calculateFoodGenerationRate, specifically the following line (line 116 at this time):

return Pair.of((float) (value * generator.getOriginalGenerationRate()), (int) (totalRF / generator.getGenerationRate()));

It assumes that generator.getGenerationRate() already has the generation rate for the current food, despite the generation rate being the first value that's being calculated, so it ends up using the last processed generation rate instead.
The line should probably instead be:

float generationRate = (float) (value * generator.getOriginalGenerationRate())
return Pair.of(generationRate, (int) (totalRF / generationRate));

This way it calculates the generation rate for the food and immediately uses it, containing all the calculations in the function and avoiding any dependency on the generator's state.


Finally, for the inaccuracies in the JEI calculations, SolidFuelRecipe needs to use an int for 'consumptionRate' in order to match GeneratorBlockEntity.litTime, and SolidFuelRecipeCategory.draw should take the server's 'tickRate' into consideration when calculating the total result.
Something like:

int tickRate = Config.SERVER.tickRate.get();
int consumptionRate = Math.ceil(recipe.consumptionRate() / ((double) tickRate)) * tickRate; // Rounds up to multiple of 'tickRate'
poseStack.drawString(minecraft.font, "Total: " + (int) (recipe.rate() * consumptionRate) + "FE", 37F, 50F, 4210752, false);

I haven't looked into any of the other generators at this time, but I'd assume some probably have similar issues.
(Like I can see that GeneratorUtil.calculateEnchantmentGenerationRate also does (totalRF / generator.getGenerationRate()), so I assume it probably also has order-related issues)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions