Skip to content

Fix invulnerability damage and armour#12190

Merged
lynxplay merged 4 commits into
PaperMC:mainfrom
okx-code:fix-invulnerability
Feb 26, 2025
Merged

Fix invulnerability damage and armour#12190
lynxplay merged 4 commits into
PaperMC:mainfrom
okx-code:fix-invulnerability

Conversation

@okx-code
Copy link
Copy Markdown
Contributor

@okx-code okx-code commented Feb 26, 2025

If a plugin changes the damage in EntityDamageEvent, it can cause the INVULNERABILITY_REDUCTION damage modifier to be set to 0 and render invulnerability damage reduction, added in #11599, ineffective.

This happens because a float (the lastHurt value in a Player) is being compared to a double (stored in the modifiers of EntityDamageEvent), and a float can be greater than a double, even if they are the same noiminal value:

jshell> 3.2 < (float) 3.2
$2 ==> true

Due to lossy floating point precision, 3.2 becomes 3.200000047683716, which indeed would be greater than 3.2.

float lastHurt is the result of casting the double from EntityDamageEvent#getFinalDamage, which causes this imprecision. The fix is to cast the doubles from EntityDamageEvent to floats when comparing them here. This ensures that if they are the same value, then one will not be less than the other, because they have both been cast from the same double value. The opposite approach, casting a double to a float and back to a double again, does not work, because is lossy. We cannot really convert lastHurt to a double without touching a lot of the damage processing code.

I have also taken the liberty of fixing how armour damage is calculated. In the #11599 PR, armour would still take damage even if the damage should have been blocked due to invulnerability. This change will now take that into account. It's also necessary to individual cast each component to a float, rather than adding the doubles like before, otherwise the armourDamage may be marginally greater than 0. I've done this in a similar way to the LivingEntity#computeAmountFromEntityDamageEvent function.

The BASE damage and INVULNERABILITY_DAMAGE modifers may be slightly different doubles, but represent the same float. The INVULNERABILITY_DAMAGE represents the BASE modifier, but casted to a float and back to a double. Therefore we must cast BASE and INVULNERABILITY_DAMAGE to floats before adding them.

To test this PR, you can use the following code and run the /test command (plugin.yml not included)

public class TestPlugin extends JavaPlugin implements Listener, CommandExecutor {

    @Override
    public void onEnable() {
        getCommand("test").setExecutor(this);
        getServer().getPluginManager().registerEvents(this, this);
    }

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        ServerPlayer handle = ((CraftPlayer) sender).getHandle();
        handle.hurtServer(handle.serverLevel(), handle.level().damageSources().cactus(), 4);
        handle.hurtServer(handle.serverLevel(), handle.level().damageSources().cactus(), 4);
        return true;
    }

    @EventHandler
    public void on(EntityDamageEvent event) {
        getLogger().info("Invulnerability reduction before: " + event.getDamage(EntityDamageEvent.DamageModifier.INVULNERABILITY_REDUCTION));
        event.setDamage(3.2);
        getLogger().info("Invulnerability reduction after: " + event.getDamage(EntityDamageEvent.DamageModifier.INVULNERABILITY_REDUCTION));
    }
}

Without this fix:

[TestPlugin] Invulnerability reduction before: 0.0
[TestPlugin] Invulnerability reduction after: 0.0
[TestPlugin] Invulnerability reduction before: -3.200000047683716
[TestPlugin] Invulnerability reduction after: 0.0

With this fix:

[TestPlugin] Invulnerability reduction before: 0.0
[TestPlugin] Invulnerability reduction after: 0.0
[TestPlugin] Invulnerability reduction before: -3.200000047683716
[TestPlugin] Invulnerability reduction after: -3.200000047683716

cc @lynxplay

@okx-code okx-code requested a review from a team as a code owner February 26, 2025 01:17
@github-project-automation github-project-automation Bot moved this to Awaiting review in Paper PR Queue Feb 26, 2025
@okx-code okx-code force-pushed the fix-invulnerability branch from 15bce09 to 8bf47fc Compare February 26, 2025 03:22
@okx-code okx-code force-pushed the fix-invulnerability branch from f264c31 to cdf07ab Compare February 26, 2025 03:57
@okx-code okx-code changed the title Fix invulnerability timer not working Fix invulnerability damage and armour Feb 26, 2025
@github-project-automation github-project-automation Bot moved this from Awaiting review to Awaiting final testing in Paper PR Queue Feb 26, 2025
Comment thread paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch Outdated
…Entity.java.patch

Co-authored-by: Nassim Jahnke <jahnke.nassim@gmail.com>
@lynxplay lynxplay merged commit 0a6e743 into PaperMC:main Feb 26, 2025
@github-project-automation github-project-automation Bot moved this from Awaiting final testing to Merged in Paper PR Queue Feb 26, 2025
+ // Apply damage to armor
+ if (!damageSource.is(DamageTypeTags.BYPASSES_ARMOR)) {
+ float armorDamage = (float) (event.getDamage() + event.getDamage(DamageModifier.BLOCKING) + event.getDamage(DamageModifier.HARD_HAT));
+ float armorDamage = (float) event.getDamage();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This computation is flawed anyway since it doesn't take in account the recent damage modifier added by Spigot (freezing) accounted in Vanilla. So the armor generally take less damage in some environment.

For example with a datapack that clear the #bypasses_armor damage type tag and add the player to the #freeze_hurts_extra_types entity type tag.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Merged

Development

Successfully merging this pull request may close these issues.

4 participants