From 938559b3e0357053ef0beb5b25d6e4bb67725750 Mon Sep 17 00:00:00 2001 From: Bjarne Koll <LynxPlay101@gmail.com> Date: Tue, 19 Nov 2024 11:54:58 +0100 Subject: [PATCH] Fix incorrect invulnerability damage reduction (#11599) Fixes incorrect spigot handling of the invulnerability damage reduction applied when an already invulnerable entity is damaged with a larger damage amount than the initial damage. Vanilla still damages entities even if invulnerable if the damage to be applied is larger than the previous damage taken. In that case, vanilla applies the difference between the previous damage taken and the proposed damage. Spigot's damage modifier API takes over the computation of damage reducing effects, however spigot invokes this handling with the initial damage before computing the difference to the previous damage amount. This leads to the reduction values to generally be larger than expected, as they are computed on the not-yet-reduced value. Spigot applies these reductions after calling the EntityDamageEvent and *then* subtracts the previous damage point, leading to the final damage amount being smaller than expected. This patch cannot simply call the EntityDamageEvent with the reduced damage, as that would lead to EntityDamageEvent#getDamage() returning the already reduced damage, which breaks its method contract. Instead, this patch makes use of the DamageModifier API, implementing the last-damage-reduction as a DamageModifier. --- ...ect-invulnerability-damage-reduction.patch | 40 ++++++ ...ect-invulnerability-damage-reduction.patch | 115 ++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 patches/api/Fix-incorrect-invulnerability-damage-reduction.patch create mode 100644 patches/server/Fix-incorrect-invulnerability-damage-reduction.patch diff --git a/patches/api/Fix-incorrect-invulnerability-damage-reduction.patch b/patches/api/Fix-incorrect-invulnerability-damage-reduction.patch new file mode 100644 index 0000000000..7e933bcda7 --- /dev/null +++ b/patches/api/Fix-incorrect-invulnerability-damage-reduction.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Bjarne Koll <git@lynxplay.dev> +Date: Mon, 11 Nov 2024 21:35:22 +0100 +Subject: [PATCH] Fix incorrect invulnerability damage reduction + +Fixes incorrect spigot handling of the invulnerability damage +reduction applied when an already invulnerable entity is damaged with a +larger damage amount than the initial damage. +Vanilla still damages entities even if invulnerable if the damage to be +applied is larger than the previous damage taken. In that case, vanilla +applies the difference between the previous damage taken and the +proposed damage. + +Spigot's damage modifier API takes over the computation of damage +reducing effects, however spigot invokes this handling with the initial +damage before computing the difference to the previous damage amount. +This leads to the reduction values to generally be larger than expected, +as they are computed on the not-yet-reduced value. +Spigot applies these reductions after calling the EntityDamageEvent and +*then* subtracts the previous damage point, leading to the final damage +amount being smaller than expected. + +This patch cannot simply call the EntityDamageEvent with the reduced +damage, as that would lead to EntityDamageEvent#getDamage() returning +the already reduced damage, which breaks its method contract. +Instead, this patch makes use of the DamageModifier API, implementing +the last-damage-reduction as a DamageModifier. + +diff --git a/src/main/java/org/bukkit/event/entity/EntityDamageEvent.java b/src/main/java/org/bukkit/event/entity/EntityDamageEvent.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/event/entity/EntityDamageEvent.java ++++ b/src/main/java/org/bukkit/event/entity/EntityDamageEvent.java +@@ -0,0 +0,0 @@ public class EntityDamageEvent extends EntityEvent implements Cancellable { + * raw {@link EntityDamageEvent#getDamage()}. + */ + BASE, ++ INVULNERABILITY_REDUCTION, // Paper - fix invulnerability reduction in EntityDamageEvent - needs to be right under BASE as its the first reduction all others are based on + /** + * This represents the damage increased by freezing status. + */ diff --git a/patches/server/Fix-incorrect-invulnerability-damage-reduction.patch b/patches/server/Fix-incorrect-invulnerability-damage-reduction.patch new file mode 100644 index 0000000000..950328ece8 --- /dev/null +++ b/patches/server/Fix-incorrect-invulnerability-damage-reduction.patch @@ -0,0 +1,115 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Bjarne Koll <git@lynxplay.dev> +Date: Mon, 11 Nov 2024 21:35:27 +0100 +Subject: [PATCH] Fix incorrect invulnerability damage reduction + +Fixes incorrect spigot handling of the invulnerability damage +reduction applied when an already invulnerable entity is damaged with a +larger damage amount than the initial damage. +Vanilla still damages entities even if invulnerable if the damage to be +applied is larger than the previous damage taken. In that case, vanilla +applies the difference between the previous damage taken and the +proposed damage. + +Spigot's damage modifier API takes over the computation of damage +reducing effects, however spigot invokes this handling with the initial +damage before computing the difference to the previous damage amount. +This leads to the reduction values to generally be larger than expected, +as they are computed on the not-yet-reduced value. +Spigot applies these reductions after calling the EntityDamageEvent and +*then* subtracts the previous damage point, leading to the final damage +amount being smaller than expected. + +This patch cannot simply call the EntityDamageEvent with the reduced +damage, as that would lead to EntityDamageEvent#getDamage() returning +the already reduced damage, which breaks its method contract. +Instead, this patch makes use of the DamageModifier API, implementing +the last-damage-reduction as a DamageModifier. + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -0,0 +0,0 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + // Paper start - only call damage event when actuallyHurt will be called - move call logic down +- event = this.handleEntityDamage(source, amount); ++ event = this.handleEntityDamage(source, amount, this.lastHurt); // Paper - fix invulnerability reduction in EntityDamageEvent - pass lastDamage reduction + amount = computeAmountFromEntityDamageEvent(event); + // Paper end - only call damage event when actuallyHurt will be called - move call logic down + + // CraftBukkit start +- if (!this.actuallyHurt(world, source, (float) event.getFinalDamage() - this.lastHurt, event)) { ++ if (!this.actuallyHurt(world, source, (float) event.getFinalDamage(), event)) { // Paper - fix invulnerability reduction in EntityDamageEvent - no longer subtract lastHurt, that is part of the damage event calc now + return false; + } + if (this instanceof ServerPlayer && event.getDamage() == 0 && originalAmount == 0) return false; // Paper - revert to vanilla damage - players are not affected by damage that is 0 - skip damage if the vanilla damage is 0 and was not modified by plugins in the event. +@@ -0,0 +0,0 @@ public abstract class LivingEntity extends Entity implements Attackable { + flag1 = false; + } else { + // Paper start - only call damage event when actuallyHurt will be called - move call logic down +- event = this.handleEntityDamage(source, amount); ++ event = this.handleEntityDamage(source, amount, 0); // Paper - fix invulnerability reduction in EntityDamageEvent - pass lastDamage reduction (none in this branch) + amount = computeAmountFromEntityDamageEvent(event); + // Paper end - only call damage event when actuallyHurt will be called - move call logic down + // CraftBukkit start +@@ -0,0 +0,0 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + // CraftBukkit start +- private EntityDamageEvent handleEntityDamage(final DamageSource damagesource, float f) { ++ private EntityDamageEvent handleEntityDamage(final DamageSource damagesource, float f, final float invulnerabilityRelatedLastDamage) { // Paper - fix invulnerability reduction in EntityDamageEvent + float originalDamage = f; ++ // Paper start - fix invulnerability reduction in EntityDamageEvent ++ final com.google.common.base.Function<Double, Double> invulnerabilityReductionEquation = d -> { ++ if (invulnerabilityRelatedLastDamage == 0) return 0D; // no last damage, no reduction ++ // last damage existed, this means the reduction *technically* is (new damage - last damage). ++ // If the event damage was changed to something less than invul damage, hard lock it at 0. ++ if (d < invulnerabilityRelatedLastDamage) return 0D; ++ return (double) -invulnerabilityRelatedLastDamage; ++ }; ++ final float originalInvulnerabilityReduction = invulnerabilityReductionEquation.apply((double) f).floatValue(); ++ f += originalInvulnerabilityReduction; ++ // Paper end - fix invulnerability reduction in EntityDamageEvent + + com.google.common.base.Function<Double, Double> freezing = new com.google.common.base.Function<Double, Double>() { + @Override +@@ -0,0 +0,0 @@ public abstract class LivingEntity extends Entity implements Attackable { + }; + float absorptionModifier = absorption.apply((double) f).floatValue(); + +- return CraftEventFactory.handleLivingEntityDamageEvent(this, damagesource, originalDamage, freezingModifier, hardHatModifier, blockingModifier, armorModifier, resistanceModifier, magicModifier, absorptionModifier, freezing, hardHat, blocking, armor, resistance, magic, absorption); ++ // Paper start - fix invulnerability reduction in EntityDamageEvent ++ return CraftEventFactory.handleLivingEntityDamageEvent(this, damagesource, originalDamage, freezingModifier, hardHatModifier, blockingModifier, armorModifier, resistanceModifier, magicModifier, absorptionModifier, freezing, hardHat, blocking, armor, resistance, magic, absorption, (damageModifierDoubleMap, damageModifierFunctionMap) -> { ++ damageModifierFunctionMap.put(DamageModifier.INVULNERABILITY_REDUCTION, invulnerabilityReductionEquation); ++ damageModifierDoubleMap.put(DamageModifier.INVULNERABILITY_REDUCTION, (double) originalInvulnerabilityReduction); ++ }); ++ // Paper end - fix invulnerability reduction in EntityDamageEvent + } + + protected boolean actuallyHurt(ServerLevel worldserver, final DamageSource damagesource, float f, final EntityDamageEvent event) { // void -> boolean, add final +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -0,0 +0,0 @@ public class CraftEventFactory { + private static final Function<? super Double, Double> ZERO = Functions.constant(-0.0); + + public static EntityDamageEvent handleLivingEntityDamageEvent(Entity damagee, DamageSource source, double rawDamage, double freezingModifier, double hardHatModifier, double blockingModifier, double armorModifier, double resistanceModifier, double magicModifier, double absorptionModifier, Function<Double, Double> freezing, Function<Double, Double> hardHat, Function<Double, Double> blocking, Function<Double, Double> armor, Function<Double, Double> resistance, Function<Double, Double> magic, Function<Double, Double> absorption) { ++ // Paper start - fix invulnerability reduction in EntityDamageEvent ++ return handleLivingEntityDamageEvent(damagee, source, rawDamage, freezingModifier, hardHatModifier, blockingModifier, armorModifier, resistanceModifier, magicModifier, absorptionModifier, freezing, hardHat, blocking, armor, resistance, magic, absorption, null); ++ } ++ public static EntityDamageEvent handleLivingEntityDamageEvent(Entity damagee, DamageSource source, double rawDamage, double freezingModifier, double hardHatModifier, double blockingModifier, double armorModifier, double resistanceModifier, double magicModifier, double absorptionModifier, Function<Double, Double> freezing, Function<Double, Double> hardHat, Function<Double, Double> blocking, Function<Double, Double> armor, Function<Double, Double> resistance, Function<Double, Double> magic, Function<Double, Double> absorption, java.util.function.BiConsumer<Map<DamageModifier, Double>, Map<DamageModifier, Function<? super Double, Double>>> callback) { ++ // Paper end - fix invulnerability reduction in EntityDamageEvent + Map<DamageModifier, Double> modifiers = new EnumMap<>(DamageModifier.class); + Map<DamageModifier, Function<? super Double, Double>> modifierFunctions = new EnumMap<>(DamageModifier.class); + modifiers.put(DamageModifier.BASE, rawDamage); +@@ -0,0 +0,0 @@ public class CraftEventFactory { + modifierFunctions.put(DamageModifier.MAGIC, magic); + modifiers.put(DamageModifier.ABSORPTION, absorptionModifier); + modifierFunctions.put(DamageModifier.ABSORPTION, absorption); ++ if (callback != null) callback.accept(modifiers, modifierFunctions); // Paper - fix invulnerability reduction in EntityDamageEvent + return CraftEventFactory.handleEntityDamageEvent(damagee, source, modifiers, modifierFunctions); + } +