yeelp / Scaling-Feast

A simple, balanced way to increase your maximum hunger in Minecraft over the course of a world.
MIT License
0 stars 1 forks source link

Inconsistent behavior for scalingfeast.foodEfficiency for values less than 1 #81

Closed James103 closed 4 years ago

James103 commented 4 years ago

Mod Version: scalingfeast-1.5.0 Minecraft Version: 1.12.2 Loaded Mods: (Reproducible with just Scaling Feast)

Describe the bug When food efficiency is greater than 1, divide exhaustion gained by the food efficiency (this is normal behavior). When food efficiency is less than 1, however, take 2 minus the food efficiency value and multiply exhaustion gained by the result. This is inconsistent behavior compared to food efficiency greater than 1.

Food efficiency Expected exhaustion 1 Actual exhaustion
-1 N/A ~3000
0 Infinity ~2000
0.1 ~10,000 ~1900
1 ~1000 ~1000
2 ~500 ~500
3 ~300 ~300
10 ~100 ~100

Steps to Reproduce

  1. Set food cap -> Hunger Damage Multiplier in the Scaling Feast configs to 0.1.
  2. /gamerule mobGriefing false (recommended)
  3. /give @p minecraft:diamond_chestplate 1 0 {AttributeModifiers:[{AttributeName:"scalingfeast.foodEfficiency",Name:"test",Amount:-2,Operation:0,UUIDLeast:629237,UUIDMost:992869,Slot:"chest"}]} (replace the -2 with other values to test them)
  4. /summon creeper ~ ~ ~ {ExplosionRadius:127} and let it explode directly on you.
  5. You will die, but also gain a number of exhaustion points as shown in the above table.

Expected behavior When food efficiency is less than 1, keep dividing exhaustion gained by the food efficiency. Cap the food efficiency value such that it is always positive to avoid division by zero errors.

Additional context The following graph shows the actual multiplier on exhaustion gained (y-axis) with respect to the food efficiency value (x-axis). Green is intended behavior, red is inconsistent behavior. The purple is the actual food efficiency value, which leads to the inconsistent behavior. image

See following code: https://github.com/yeelp/Scaling-Feast/blob/a4560906fd6f9b9121fb7760b6a9fb9daaeee1f8/src/main/java/yeelp/scalingfeast/handlers/FoodHandler.java#L132-L137

yeelp commented 4 years ago

TL;DR: Unless I've misinterpreted the exact behaviour you were looking for, this is a mathematically sound way to approach this.

So, if you want an attribute that, for example, gives the player 25% food efficiency, you currently set the Amount to -0.75 which makes the attribute show up red on the tooltip as it should (It will say -75% Food Efficiency)

My intention was to have a linear increase/decrease as opposed to some exponential behaviour (I spent a fair amount of time tweaking the function in Desmos to get it just how I wanted it, which you've reported in purple). Also keep in mind that the food efficiency values need to be negative for modifiers that make exhaustion rate worse (an therefore draw in red) and positive for ones that make it better (and therefore draw in blue). So, the value we divide the exhaustion by needs to be a map f: R -> R, that takes the attribute amount and maps it to the value we should divide exhaustion by, where I'm calling R the positive real line (since we should only divide by positive numbers here).

The way I've interpreted this attribute is like so, which is best described with an example that explains the reasoning and math behind my decision. Say you have a -300% Food Efficiency modifier. Then, the effective duration of foodstuffs should only 'last' a quarter of their original duration (assuming actions taken are the same. For example. the food values provided by a Golden Carrot should only last a quarter of the duration they would normally if you were sprinting). -300% Food Efficiency is -3.0 for the attribute Amount. Let x be this attribute amount and let a(x) be the final attribute value. Then, let f(x) be the value we divide exhaustion by at the end. That is, evt.deltaExhaustion /= f(x). Then, f(x) is

f(x) = a(x),

For a(x) >= 1 and

f(x) = -1/(a(x) - 2)),

for a(x) < 1, according to the source code. Minecraft calculates the final attribute value, a(x), as

a(x) = b*(1+x),

when only a single modifier is applied using operation 2, where b is the base value (one in this case). For n many attribute modifiers, Minecraft extends this like so. Let a_n(x_n) be the attribute value calculated for the nth modifier. The final attribute value is (and I've used juxtaposition for multiplication to prevent Markdown from messing things up)

a(x_1, x_2, ..., x_n) = b*a_1(x_1)a_2(x_2)...a_n(x_n).

Here, b is still the base value (one). We only have one attribute modifier for our scenario with a value of -3.0. So,

a(x) = (1 + -3.0) = -2.0.

a(x) < 1, so we fall into case two. So, f(x) = -1/(a(x)-2) = -1/(-2-2) = 1/4. Then, final exhaustion values are divided by 1/4 which multiplies them by 4, the intended result we were looking for. Keep in mind your graph plots a(x) on the horizontal axis and the change in exhaustion on the vertical axis.

Is something missing here? Seems solid to me.

yeelp commented 4 years ago

I should've closed this earlier, as everything is working as intended, at least from what I understand. Feel free to explain what you expected to have happen here, if I've misunderstood.

James103 commented 4 years ago

I get what you mean by how you set up the function for the scalingfeast.foodEfficiency attribute modifer. However, it would be simpler (and more symmetric) to center the attribute at 0 instead of 1, since when the attribute is centered at 0, the function basically simplifies into this:

If the scalingfeast.foodEfficiency attribute is positive, divide exhaustion gained by (1 + X). If the scalingfeast.foodEfficiency attribute is negative, multiply exhaustion gained by (1 - X). *Else (if the scalingfeast.foodEfficiency attribute is 0), leave exhaustion gained unchanged.

By the way, why did you opt for a more linear behavior to scalingfeast.foodEfficiency instead of the reciprocal behavior that I was looking for? If it was reciprocal, then "-99% Food Efficiency" would actually multiply exhaustion gained by 100, since each food point is only worth 1% of what it was before (since 100% - 99% = 1%).

yeelp commented 4 years ago

However, it would be simpler (and more symmetric) to center the attribute at 0 instead of 1, since when the attribute is centered at 0, the function basically simplifies into this: If the scalingfeast.foodEfficiency attribute is positive, divide exhaustion gained by (1 + X). If the scalingfeast.foodEfficiency attribute is negative, multiply exhaustion gained by (1 - X). *Else (if the scalingfeast.foodEfficiency attribute is 0), leave exhaustion gained unchanged.

The problem I found with this approach is that now the attribute modifier values are off. A value of -1.0 for an attribute modifier (i.e. - 100% Food Efficiency) results in a value of 0 for the attribute itself (a(-1.0) = 0), which by your definition leaves exhaustion unchanged. The fact that Minecraft adds one to the amount for the modifier is because it's dealing with percentages and 1+x would represent a net overall change. This system also requires the base value of the attribute to be zero. If the base value of the modifier is zero, all modifiers are multiplied by zero and nothing changes. It's a little messy internally, but to the end user, everything makes sense in terms of values being changed.

By the way, why did you opt for a more linear behavior to scalingfeast.foodEfficiency instead of the reciprocal behavior that I was looking for? If it was reciprocal, then "-99% Food Efficiency" would actually multiply exhaustion gained by 100, since each food point is only worth 1% of what it was before (since 100% - 99% = 1%).

As for this, this was just how I decided to interpret the meaning of Food Efficiency. I wasn't thinking about the values of food points, but the duration of food values as a whole. The benefit to the linear approach is that it does give a more granular approach to modifying exhaustion rate. I'll experiment with the reciprocal approach and see which I like better.