Base replacement for FoodData

This commit is contained in:
DBotThePony 2025-03-14 08:52:36 +07:00
parent c3b2681e89
commit a34b485e68
Signed by: DBot
GPG Key ID: DCC23B5715498507
7 changed files with 249 additions and 52 deletions

View File

@ -1,50 +0,0 @@
package ru.dbotthepony.mc.otm.mixin;
import net.minecraft.world.Difficulty;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.food.FoodData;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import ru.dbotthepony.mc.otm.player.IMatteryPlayer;
@Mixin(FoodData.class)
public class FoodDataMixin {
@Shadow(remap = false)
private int lastFoodLevel;
@Shadow(remap = false)
private int tickTimer;
@Shadow(remap = false)
private int foodLevel;
@Shadow(remap = false)
private float exhaustionLevel;
@Inject(
method = "tick(Lnet/minecraft/world/entity/player/Player;)V",
at = @At("HEAD"),
remap = false,
cancellable = true
)
private void tick(Player player, CallbackInfo info) {
var it = ((IMatteryPlayer) player).getOtmPlayer();
if (it.isAndroid()) {
info.cancel();
// полностью подменяем логику если андроид
lastFoodLevel = foodLevel;
if (player.level().getDifficulty() == Difficulty.PEACEFUL) {
exhaustionLevel = 0f;
} else {
tickTimer = 0;
}
// не обновляем уровень истощения ибо он обнуляется логикой внутри MatteryPlayerCapability
// а так же не регенерируем
// ну и не получаем урон от "голодания"
}
}
}

View File

@ -4,21 +4,25 @@ import com.mojang.authlib.GameProfile;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.food.FoodData;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.NotNull;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import ru.dbotthepony.mc.otm.player.IMatteryPlayer;
import ru.dbotthepony.mc.otm.player.MatteryFoodData;
import ru.dbotthepony.mc.otm.player.MatteryPlayer;
import java.util.Objects;
@Mixin(Player.class)
public class MixinPlayer implements IMatteryPlayer {
public abstract class MixinPlayer implements IMatteryPlayer {
private Player otmSelf() {
return (Player) (Object) this;
}
@ -85,4 +89,15 @@ public class MixinPlayer implements IMatteryPlayer {
private void readAdditionalSaveData(CompoundTag data, CallbackInfo ci) {
otmPlayer.deserializeNBT(data.getCompound("overdrive_that_matters_player"), otmSelf().registryAccess());
}
@Shadow
protected FoodData foodData;
@Inject(
method = "<init>",
at = @At("TAIL")
)
private void ctorMix() {
foodData = new MatteryFoodData(otmSelf());
}
}

View File

@ -0,0 +1,9 @@
package ru.dbotthepony.mc.otm.config
interface IFoodRegenerationValues {
val foodLimit: Int
val requiresSaturation: Boolean
val ticks: Int
val regenerationSlowdown: Boolean
val upperSlowdownBound: Double
}

View File

@ -148,4 +148,103 @@ object PlayerConfig : AbstractConfig("player") {
Magnet
Shockwave
}
object Food {
init {
builder
.comment("Food related tweaks")
.comment("Since OTM overrides FoodData logic (to make room for Android logic)")
.comment("these settings are provided for tweaking FoodData behavior")
.push("Food")
}
val SOFT_FOOD_LIMIT: Int by builder
.comment("Soft food points limit, which dictate upper bound where player is considered 'hungry',")
.comment("e.g. they can eat food which is not marked as 'can always eat'")
.defineInRange("SOFT_FOOD_LIMIT", 20, 0)
val HARD_FOOD_LIMIT: Int by builder
.comment("Hard food points limit, which dictate upper bound of food points,")
.comment("e.g. hunger can not go above this value")
.comment("This is extremely buffed in OTM by default to remove annoyance regarding consuming foodstuffs")
.comment("when you are running low on food, so you can eat high-quality food without fear")
.comment("that food points will go to waste (especially when you need to regenerate lots of HP)")
.defineInRange("HARD_FOOD_LIMIT", 40, 1)
val OVERSATURATION_LIMIT: Double by builder
.comment("Controls how much 'saturation' can be stored above hunger level.")
.comment("Negative values will decrease maximum saturation achievable.")
.defineInRange("OVERSATURATION_LIMIT", 0.0, -Float.MAX_VALUE.toDouble() + 1.0, Float.MAX_VALUE.toDouble() - 1.0)
val EXHAUSTION_LIMIT: Double by builder
.comment("Controls technical aspect how much 'exhaustion' player can accumulate")
.comment("This should not be confused with how much 'exhaustion' is considered as 1 hunger point")
.comment("Usually, this should not be changed, since it is a very technical detail")
.comment("but if you have a mod installed which easily hits this limit (and somehow hunger system can't keep up,)")
.comment("e.g. it pushes exhaustion over its limit in one call), you can increase this value")
.defineInRange("EXHAUSTION_LIMIT", 40.0, 4.0, Float.MAX_VALUE.toDouble() - 1.0)
private fun defineRegeneration(
threshold: Int,
requiresSaturation: Boolean,
ticks: Int,
): IFoodRegenerationValues {
return object : IFoodRegenerationValues {
override val foodLimit: Int by builder
.defineInRange("FOOD_THRESHOLD", threshold, 1)
override val requiresSaturation: Boolean by builder
.define("REQUIRES_SATURATION", requiresSaturation)
override val ticks: Int by builder
.defineInRange("TICKS", ticks, 0)
override val regenerationSlowdown: Boolean by builder
.comment("Slowdown regeneration based on remaining saturation (given REQUIRES_SATURATION is true)")
.comment("This replicates vanilla behavior where fast regeneration speed linearly slowdowns the moment")
.comment("saturation falls below 6 hunger points (configurable through SATURATION_SLOWDOWN_BOUND)")
.comment("This is disabled in OTM by default to buff humans compared to androids when it comes to regeneration")
.define("SATURATION_SLOWDOWN", false)
override val upperSlowdownBound: Double by builder
.defineInRange("SATURATION_SLOWDOWN_BOUND", 6.0, 1.0, Float.MAX_VALUE - 1.0)
}
}
val FAST_REGEN: IFoodRegenerationValues
val SLOW_REGEN: IFoodRegenerationValues
init {
builder.push("FAST_REGENERATION")
FAST_REGEN = defineRegeneration(20, true, 10)
builder.pop()
builder.push("SLOW_REGENERATION")
SLOW_REGEN = defineRegeneration(18, false, 80)
builder.pop()
}
val ENABLE_STARVATION: Boolean by builder
.define("ENABLE_STARVATION", true)
val STARVATION_TICKS: Int by builder
.defineInRange("STARVATION_TICKS", 80, 1)
val STARVATION_HEALTH_LIMIT_EASY: Double by builder
.defineInRange("STARVATION_HEALTH_LIMIT_EASY", 10.0, 0.0)
val STARVATION_HEALTH_LIMIT_NORMAL: Double by builder
.defineInRange("STARVATION_HEALTH_LIMIT_NORMAL", 1.0, 0.0)
val STARVATION_HEALTH_LIMIT_HARD: Double by builder
.defineInRange("STARVATION_HEALTH_LIMIT_HARD", 0.0, 0.0)
init {
builder.pop()
}
}
init {
Food
}
}

View File

@ -0,0 +1,119 @@
package ru.dbotthepony.mc.otm.player
import net.minecraft.nbt.CompoundTag
import net.minecraft.world.Difficulty
import net.minecraft.world.entity.player.Player
import net.minecraft.world.food.FoodConstants
import net.minecraft.world.food.FoodData
import net.minecraft.world.food.FoodProperties
import net.minecraft.world.level.GameRules
import ru.dbotthepony.mc.otm.config.IFoodRegenerationValues
import ru.dbotthepony.mc.otm.config.PlayerConfig
import ru.dbotthepony.mc.otm.core.nbt.set
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
class MatteryFoodData(private var player: Player) : FoodData() {
private fun add(foodLevel: Int, saturation: Float) {
this.foodLevel = min(this.foodLevel + foodLevel, PlayerConfig.Food.HARD_FOOD_LIMIT)
this.saturationLevel = min(this.saturationLevel + saturation, this.foodLevel + PlayerConfig.Food.OVERSATURATION_LIMIT.toFloat()).coerceAtLeast(0f)
}
override fun eat(foodLevelModifier: Int, saturationLevelModifier: Float) {
add(foodLevelModifier, FoodConstants.saturationByModifier(foodLevelModifier, saturationLevelModifier))
}
override fun eat(foodProperties: FoodProperties) {
add(foodProperties.nutrition(), foodProperties.saturation())
}
override fun needsFood(): Boolean {
return foodLevel < PlayerConfig.Food.SOFT_FOOD_LIMIT
}
override fun addExhaustion(exhaustion: Float) {
this.exhaustionLevel = min(this.exhaustionLevel + exhaustion, PlayerConfig.Food.EXHAUSTION_LIMIT.toFloat())
}
override fun readAdditionalSaveData(compoundTag: CompoundTag) {
super.readAdditionalSaveData(compoundTag)
if ("lastFoodLevel" in compoundTag)
lastFoodLevel = compoundTag.getInt("lastFoodLevel")
}
override fun addAdditionalSaveData(compoundTag: CompoundTag) {
super.addAdditionalSaveData(compoundTag)
compoundTag["lastFoodLevel"] = lastFoodLevel
}
private fun tickExhaustion() {
if (exhaustionLevel >= EXHAUSTION_PER_HUNGER_POINT) {
var points = (exhaustionLevel / EXHAUSTION_PER_HUNGER_POINT).toInt()
exhaustionLevel %= EXHAUSTION_PER_HUNGER_POINT
if (saturationLevel > 0f) {
val satisfied = min(saturationLevel.roundToInt(), points)
points -= satisfied
saturationLevel -= satisfied
}
foodLevel = max(0, foodLevel - points)
}
}
private fun tickRegeneration(values: IFoodRegenerationValues): Boolean {
if (!player.isHurt || foodLevel < values.foodLimit || values.requiresSaturation && saturationLevel <= 0f)
return false
while (player.isHurt && foodLevel >= values.foodLimit && (!values.requiresSaturation || saturationLevel > 0f) && ++tickTimer >= values.ticks) {
tickTimer = 0
val healAmount = if (values.requiresSaturation && values.regenerationSlowdown) min(saturationLevel, values.upperSlowdownBound.toFloat()) / values.upperSlowdownBound.toFloat() else 1f
player.heal(healAmount)
addExhaustion(healAmount * 6f)
tickExhaustion()
}
return tickTimer != 0
}
override fun tick(player: Player) {
this.player = player
if (player.matteryPlayer.isAndroid)
return
lastFoodLevel = foodLevel
tickExhaustion()
if (
player.level().gameRules.getBoolean(GameRules.RULE_NATURAL_REGENERATION) &&
(tickRegeneration(PlayerConfig.Food.FAST_REGEN) || tickRegeneration(PlayerConfig.Food.SLOW_REGEN))
) {
// do nothing
} else if (PlayerConfig.Food.ENABLE_STARVATION && foodLevel <= 0) {
if (++tickTimer >= PlayerConfig.Food.STARVATION_TICKS) {
tickTimer = 0
val threshold = when (player.level().difficulty) {
Difficulty.PEACEFUL -> Float.POSITIVE_INFINITY
Difficulty.EASY -> PlayerConfig.Food.STARVATION_HEALTH_LIMIT_EASY.toFloat()
Difficulty.NORMAL -> PlayerConfig.Food.STARVATION_HEALTH_LIMIT_NORMAL.toFloat()
Difficulty.HARD -> PlayerConfig.Food.STARVATION_HEALTH_LIMIT_HARD.toFloat()
}
if (player.health > threshold) {
player.hurt(player.damageSources().starve(), 1.0f)
}
}
} else {
tickTimer = 0
}
}
companion object {
const val EXHAUSTION_PER_HUNGER_POINT = 4f
}
}

View File

@ -176,3 +176,9 @@ public net.minecraft.advancements.critereon.InventoryChangeTrigger$TriggerInstan
#public-f net.minecraft.advancements.critereon.SimpleCriterionTrigger removePlayerListeners(Lnet/minecraft/server/PlayerAdvancements;)V
public net.minecraft.world.entity.npc.VillagerTrades$TreasureMapForEmeralds
protected net.minecraft.world.food.FoodData foodLevel
protected net.minecraft.world.food.FoodData saturationLevel
protected net.minecraft.world.food.FoodData exhaustionLevel
protected net.minecraft.world.food.FoodData tickTimer
protected net.minecraft.world.food.FoodData lastFoodLevel

View File

@ -7,7 +7,6 @@
"refmap": "overdrive_that_matters.refmap.json",
"mixins": [
"BlockEntityMixin",
"FoodDataMixin",
"MixinLivingEntity",
"MixinAnvilBlock",
"MixinInventory",