Base replacement for FoodData
This commit is contained in:
parent
c3b2681e89
commit
a34b485e68
@ -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
|
||||
// а так же не регенерируем
|
||||
// ну и не получаем урон от "голодания"
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
119
src/main/kotlin/ru/dbotthepony/mc/otm/player/MatteryFoodData.kt
Normal file
119
src/main/kotlin/ru/dbotthepony/mc/otm/player/MatteryFoodData.kt
Normal 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
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -7,7 +7,6 @@
|
||||
"refmap": "overdrive_that_matters.refmap.json",
|
||||
"mixins": [
|
||||
"BlockEntityMixin",
|
||||
"FoodDataMixin",
|
||||
"MixinLivingEntity",
|
||||
"MixinAnvilBlock",
|
||||
"MixinInventory",
|
||||
|
Loading…
Reference in New Issue
Block a user