diff --git a/gradle.properties b/gradle.properties index f12d366..a2ca799 100644 --- a/gradle.properties +++ b/gradle.properties @@ -36,4 +36,4 @@ mapping_version=2023.08.20-1.20.1 # Must match the String constant located in the main mod class annotated with @Mod. mod_id=firstaid -mod_version=1.20.1-1.1 \ No newline at end of file +mod_version=1.20.1-1.2.1 diff --git a/src/main/java/ichttt/mods/firstaid/common/damagesystem/DamageablePart.java b/src/main/java/ichttt/mods/firstaid/common/damagesystem/DamageablePart.java index ffa8911..427bc1e 100644 --- a/src/main/java/ichttt/mods/firstaid/common/damagesystem/DamageablePart.java +++ b/src/main/java/ichttt/mods/firstaid/common/damagesystem/DamageablePart.java @@ -191,6 +191,21 @@ public void setMaxHealth(int maxHealth) { maxHealth = 128; this.maxHealth = Math.max(2, maxHealth); //set 2 as a minimum this.currentHealth = Math.min(currentHealth, this.maxHealth); + + int requestedMax = maxHealth; + float oldCurrent = currentHealth; + int oldMax = this.maxHealth; + + FirstAid.LOGGER.info( + "[FirstAid part clamp] part={} requestedMax={} oldMax={} newMax={} oldCurrent={} newCurrent={} capMaxHealth={}", + part, + requestedMax, + oldMax, + this.maxHealth, + oldCurrent, + this.currentHealth, + FirstAidConfig.SERVER.capMaxHealth.get() + ); } @Override diff --git a/src/main/java/ichttt/mods/firstaid/common/damagesystem/PlayerDamageModel.java b/src/main/java/ichttt/mods/firstaid/common/damagesystem/PlayerDamageModel.java index ad59dc6..35ba32b 100644 --- a/src/main/java/ichttt/mods/firstaid/common/damagesystem/PlayerDamageModel.java +++ b/src/main/java/ichttt/mods/firstaid/common/damagesystem/PlayerDamageModel.java @@ -135,6 +135,10 @@ public void tick(Level world, Player player) { else if (sleepBlockTicks < 0) throw new RuntimeException("Negative sleepBlockTicks " + sleepBlockTicks); + if (!world.isClientSide) { + runScaleLogic(player); + } + float newCurrentHealth = calculateNewCurrentHealth(player); if (Float.isNaN(newCurrentHealth)) { FirstAid.LOGGER.warn("New current health is not a number, setting it to 0!"); @@ -164,8 +168,6 @@ else if (sleepBlockTicks < 0) if (!this.hasTutorial) this.hasTutorial = CapProvider.tutorialDone.contains(player.getName().getString()); - runScaleLogic(player); - MobEffect morphineEffect = RegistryObjects.MORPHINE_EFFECT.get(); //morphine update if (this.needsMorphineUpdate) { @@ -379,78 +381,134 @@ public void revivePlayer(Player player) { FirstAid.NETWORKING.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) player), new MessageSyncDamageModel(this, true)); //Upload changes to the client } + private static int sanitizeMaxHealth(int maxHealth) { + if (maxHealth > 12 && FirstAidConfig.SERVER.capMaxHealth.get()) { + maxHealth = 12; + } + + if (maxHealth > 128) { + maxHealth = 128; + } + + return Math.max(2, maxHealth); + } + @Override public void runScaleLogic(Player player) { - if (FirstAidConfig.SERVER.scaleMaxHealth.get()) { //Attempt to calculate the max health of the body parts based on the maxHealth attribute - player.level().getProfiler().push("healthscaling"); - float globalFactor = player.getMaxHealth() / 20F; - if (prevScaleFactor != globalFactor) { + if (!FirstAidConfig.SERVER.scaleMaxHealth.get()) { + return; + } + if (player.level().isClientSide) { + return; + } + if (player.isRemoved() || !player.isAlive() || player.isDeadOrDying() || player.getHealth() <= 0F || isDead(player)) { + return; + } + player.level().getProfiler().push("healthscaling"); + if (FirstAidConfig.GENERAL.debug.get()) { + FirstAid.LOGGER.info("[FirstAid scale] player={} tick={} vanillaMax={} prevScaleFactor={} limbMaxTotal={}", player.getName().getString(), player.tickCount, player.getMaxHealth(), prevScaleFactor, getCurrentMaxHealth()); + } + float globalFactor = player.getMaxHealth() / 20F; + if (Math.abs(prevScaleFactor - globalFactor) > 0.0001F) { + if (FirstAidConfig.GENERAL.debug.get()) { + FirstAid.LOGGER.info( "Starting health scaling factor {} -> {} (max health {})", prevScaleFactor, globalFactor, player.getMaxHealth()); + } + player.level().getProfiler().push("distribution"); + class Target { + final AbstractDamageablePart part; + final float desired; + final float remainder; + int target; + Target(AbstractDamageablePart part, float desired, int target) { + this.part = part; + this.desired = desired; + this.target = target; + this.remainder = desired - target; + } + } + List targets = new ArrayList<>(); + float expectedNewMaxHealth = 0F; + int newMaxHealth = 0; + for (AbstractDamageablePart part : this) { + float desired = ((float) part.initialMaxHealth) * globalFactor; + expectedNewMaxHealth += desired; + int lowerEven = ((int) Math.floor(desired / 2F)) * 2; + lowerEven = sanitizeMaxHealth(lowerEven); + Target target = new Target(part, desired, lowerEven); + targets.add(target); + newMaxHealth += lowerEven; if (FirstAidConfig.GENERAL.debug.get()) { - FirstAid.LOGGER.info("Starting health scaling factor {} -> {} (max health {})", prevScaleFactor, globalFactor, player.getMaxHealth()); + FirstAid.LOGGER.info( "Part {} max health base target: {} initial; {} old; desired {}; {} base", part.part.name(), part.initialMaxHealth, part.getMaxHealth(), desired, lowerEven); } - player.level().getProfiler().push("distribution"); - int reduced = 0; - int added = 0; - float expectedNewMaxHealth = 0F; - int newMaxHealth = 0; - for (AbstractDamageablePart part : this) { - float floatResult = ((float) part.initialMaxHealth) * globalFactor; - expectedNewMaxHealth += floatResult; - int result = (int) floatResult; - if (result % 2 == 1) { - int partMaxHealth = part.getMaxHealth(); - if (part.currentHealth < partMaxHealth && reduced < 4) { - result--; - reduced++; - } else if (part.currentHealth > partMaxHealth && added < 4) { - result++; - added++; - } else if (reduced > added) { - result++; - added++; - } else { - result--; - reduced++; + } + player.level().getProfiler().popPush("correcting"); + int wantedTotal = Math.round(expectedNewMaxHealth / 2F) * 2; + int minPossibleTotal = 0; + int maxPossibleTotal = 0; + for (Target target : targets) { + minPossibleTotal += sanitizeMaxHealth(2); + maxPossibleTotal += sanitizeMaxHealth(128); + } + wantedTotal = Math.max(minPossibleTotal, wantedTotal); + wantedTotal = Math.min(maxPossibleTotal, wantedTotal); + int healthToAdd = wantedTotal - newMaxHealth; + if (healthToAdd > 0) { + targets.sort( + Comparator.comparingDouble(target -> target.remainder) + .reversed() + .thenComparingInt(target -> target.part.part.ordinal()) + ); + for (Target target : targets) { + if (healthToAdd < 2) break; + int oldTarget = target.target; + int newTarget = sanitizeMaxHealth(oldTarget + 2); + if (newTarget != oldTarget) { + target.target = newTarget; + newMaxHealth += newTarget - oldTarget; + healthToAdd -= newTarget - oldTarget; + if (FirstAidConfig.GENERAL.debug.get()) { + FirstAid.LOGGER.info("Part {} corrected target {} -> {}; total now {}; wanted {}; expected {}", target.part.part.name(), oldTarget, newTarget, newMaxHealth, wantedTotal, expectedNewMaxHealth); } } - newMaxHealth += result; - if (FirstAidConfig.GENERAL.debug.get()) { - FirstAid.LOGGER.info("Part {} max health: {} initial; {} old; {} new", part.part.name(), part.initialMaxHealth, part.getMaxHealth(), result); - } - part.setMaxHealth(result); } - player.level().getProfiler().popPush("correcting"); - if (Math.abs(expectedNewMaxHealth - newMaxHealth) >= 2F) { - if (FirstAidConfig.GENERAL.debug.get()) { - FirstAid.LOGGER.info("Entering second stage - diff {}", Math.abs(expectedNewMaxHealth - newMaxHealth)); - } - List prioList = new ArrayList<>(); - for (AbstractDamageablePart part : this) { - prioList.add(part); - } - prioList.sort(Comparator.comparingInt(AbstractDamageablePart::getMaxHealth)); - for (AbstractDamageablePart part : prioList) { - int maxHealth = part.getMaxHealth(); + } else if (healthToAdd < 0) { + targets.sort( + Comparator.comparingDouble(target -> target.remainder) + .thenComparingInt(target -> target.part.part.ordinal()) + ); + for (Target target : targets) { + if (healthToAdd > -2) break; + int oldTarget = target.target; + int newTarget = sanitizeMaxHealth(oldTarget - 2); + if (newTarget != oldTarget) { + target.target = newTarget; + newMaxHealth += newTarget - oldTarget; + healthToAdd -= newTarget - oldTarget; if (FirstAidConfig.GENERAL.debug.get()) { - FirstAid.LOGGER.info("Part {}: Second stage with total diff {}", part.part.name(), Math.abs(expectedNewMaxHealth - newMaxHealth)); - } - if (expectedNewMaxHealth > newMaxHealth) { - part.setMaxHealth(maxHealth + 2); - newMaxHealth += (part.getMaxHealth() - maxHealth); - } else if (expectedNewMaxHealth < newMaxHealth) { - part.setMaxHealth(maxHealth - 2); - newMaxHealth -= (maxHealth - part.getMaxHealth()); - } - if (Math.abs(expectedNewMaxHealth - newMaxHealth) < 2F) { - break; + FirstAid.LOGGER.info("Part {} corrected target {} -> {}; total now {}; wanted {}; expected {}", target.part.part.name(), oldTarget, newTarget, newMaxHealth, wantedTotal, expectedNewMaxHealth); } } } - player.level().getProfiler().pop(); } - prevScaleFactor = globalFactor; + player.level().getProfiler().popPush("apply"); + boolean changed = false; + for (Target target : targets) { + AbstractDamageablePart part = target.part; + if (FirstAidConfig.GENERAL.debug.get()) { + FirstAid.LOGGER.info( "Part {} applying final max health: {} old; {} final", part.part.name(), part.getMaxHealth(), target.target); + } + if (part.getMaxHealth() != target.target) { + changed = true; + } + part.setMaxHealth(target.target); + } + if (changed) { + scheduleResync(); + } player.level().getProfiler().pop(); } + prevScaleFactor = globalFactor; + player.level().getProfiler().pop(); } @Override