From 5e7539afbf3f029f8fbffc1a22c01745379676e1 Mon Sep 17 00:00:00 2001 From: "C.Nelhomme" <64921289+D3ller@users.noreply.github.com> Date: Thu, 15 Oct 2026 11:10:47 +0200 Subject: [PATCH] fix(damage): keep vanilla i-frames and share the health actually lost (#42) Setting the damage to 0 made Paper remember a last hit of 0, so every cactus or lava tick passed the i-frame check and was counted again. Vanilla now applies the damage to the hit player. SharedHealthService shares what each member lost compared to the group, lethal hits are handled in EntityResurrectEvent before vanilla kills the player, and deaths bypassing totems (void, /kill) still kill the whole group. --- .../fr/korastrip/paperplugin/SharedStats.java | 1 + .../paperplugin/listener/DamageListener.java | 81 +++------------ .../listener/EntityResurrectListener.java | 47 +++++++++ .../listener/PlayerDeathListener.java | 6 ++ .../listener/RegenerationListener.java | 5 + .../paperplugin/model/SharedGroup.java | 2 +- .../services/SharedHealthService.java | 98 +++++++++++++++++++ 7 files changed, 174 insertions(+), 66 deletions(-) create mode 100644 src/main/java/fr/korastrip/paperplugin/listener/EntityResurrectListener.java create mode 100644 src/main/java/fr/korastrip/paperplugin/services/SharedHealthService.java diff --git a/src/main/java/fr/korastrip/paperplugin/SharedStats.java b/src/main/java/fr/korastrip/paperplugin/SharedStats.java index f1c6bd5..aeffa18 100644 --- a/src/main/java/fr/korastrip/paperplugin/SharedStats.java +++ b/src/main/java/fr/korastrip/paperplugin/SharedStats.java @@ -27,6 +27,7 @@ public void onEnable() { this.getServer().getPluginManager().registerEvents(new PlayerDeathListener(this), this); this.getServer().getPluginManager().registerEvents(new RegenerationListener(this), this); this.getServer().getPluginManager().registerEvents(new PlayerRespawnListener(this), this); + this.getServer().getPluginManager().registerEvents(new EntityResurrectListener(this), this); this.getLifecycleManager().registerEventHandler( LifecycleEvents.COMMANDS, diff --git a/src/main/java/fr/korastrip/paperplugin/listener/DamageListener.java b/src/main/java/fr/korastrip/paperplugin/listener/DamageListener.java index c8539ca..eadc2d4 100644 --- a/src/main/java/fr/korastrip/paperplugin/listener/DamageListener.java +++ b/src/main/java/fr/korastrip/paperplugin/listener/DamageListener.java @@ -2,19 +2,14 @@ import fr.korastrip.paperplugin.SharedStats; import fr.korastrip.paperplugin.model.SharedGroup; -import fr.korastrip.paperplugin.services.TotemService; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; -import net.kyori.adventure.title.Title; +import fr.korastrip.paperplugin.services.SharedHealthService; import org.bukkit.Bukkit; -import org.bukkit.Sound; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.entity.EntityDamageEvent; -import java.util.List; - public class DamageListener implements Listener { private final SharedStats plugin; @@ -23,7 +18,7 @@ public DamageListener(SharedStats plugin) { this.plugin = plugin; } - @EventHandler(ignoreCancelled = true) + @EventHandler public void onDamageReceive(EntityDamageEvent event) { if (event.getEntity() instanceof Player player) { @@ -34,67 +29,23 @@ public void onDamageReceive(EntityDamageEvent event) { if (group.isGroupDying) { event.setCancelled(true); - return; } + } + } - var finalDamage = event.getFinalDamage(); - event.setDamage(0); - - var newHealth = group.getHealth() - finalDamage; - - List players = group.getOnlinePlayers(); - - if (newHealth <= 0) { - - TotemService totemService = new TotemService(plugin); - - if(totemService.check()) { - totemService.applyEffect(); - return; - } - - group.isGroupDying = true; - - Bukkit.getScheduler().runTask( - plugin, - () -> { - if (group.getIsPlaying()) { - players.forEach(Player::kill); - } - } - ); - - final Component mainTitle = player.displayName() - .color(NamedTextColor.RED); - final Component subtitle = Component.text("is dead", NamedTextColor.WHITE); - final Title title = Title.title(mainTitle, subtitle); - - group.addPlayerDeath(player); - - players.forEach(p -> { - p.showTitle(title); - - p.playSound( - p.getLocation(), - Sound.ENTITY_WITHER_SPAWN, - 1.0f, - 1.0f - ); - }); - group.incrementsDeadCount(); - - return; - } + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onDamageApplied(EntityDamageEvent event) { + if (event.getEntity() instanceof Player player) { - group.setHealth(newHealth); - group.syncHealth(); - player.playHurtAnimation(0); + SharedGroup group = this.plugin.getGroup(); + if (!group.getIsPlaying()) return; + if (!group.getPlayersId().contains(player.getUniqueId())) return; - player.playSound( - player.getLocation(), - Sound.ENTITY_PLAYER_HURT, - 1.0f, - 1.0f + Bukkit.getScheduler().runTask(plugin, + () -> { + SharedHealthService sharedHealthService = new SharedHealthService(this.plugin); + sharedHealthService.shareLoss(); + } ); } } diff --git a/src/main/java/fr/korastrip/paperplugin/listener/EntityResurrectListener.java b/src/main/java/fr/korastrip/paperplugin/listener/EntityResurrectListener.java new file mode 100644 index 0000000..4ad934f --- /dev/null +++ b/src/main/java/fr/korastrip/paperplugin/listener/EntityResurrectListener.java @@ -0,0 +1,47 @@ +package fr.korastrip.paperplugin.listener; + +import fr.korastrip.paperplugin.SharedStats; +import fr.korastrip.paperplugin.model.SharedGroup; +import fr.korastrip.paperplugin.services.SharedHealthService; +import fr.korastrip.paperplugin.services.TotemService; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityResurrectEvent; + +public class EntityResurrectListener implements Listener { + + private final SharedStats plugin; + + public EntityResurrectListener(SharedStats plugin) { + this.plugin = plugin; + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onEntityResurrect(EntityResurrectEvent e) { + if (e.getEntity() instanceof Player player) { + + TotemService totemService = new TotemService(this.plugin); + SharedGroup group = this.plugin.getGroup(); + + if (!group.getIsPlaying()) return; + if (!group.getPlayersId().contains(player.getUniqueId())) return; + + if (group.isGroupDying) { + e.setCancelled(true); + return; + } + + if (!e.isCancelled()) { + totemService.applyEffect(); + } else if (totemService.check()) { + e.setCancelled(false); + totemService.applyEffect(); + } else { + SharedHealthService sharedHealthService = new SharedHealthService(this.plugin); + sharedHealthService.groupDeath(player); + } + } + } +} diff --git a/src/main/java/fr/korastrip/paperplugin/listener/PlayerDeathListener.java b/src/main/java/fr/korastrip/paperplugin/listener/PlayerDeathListener.java index 2de43fa..c12b39c 100644 --- a/src/main/java/fr/korastrip/paperplugin/listener/PlayerDeathListener.java +++ b/src/main/java/fr/korastrip/paperplugin/listener/PlayerDeathListener.java @@ -2,6 +2,7 @@ import fr.korastrip.paperplugin.SharedStats; import fr.korastrip.paperplugin.model.SharedGroup; +import fr.korastrip.paperplugin.services.SharedHealthService; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -24,6 +25,11 @@ public void onPlayerDeath(PlayerDeathEvent event) { if (!group.getIsPlaying()) return; if (!group.getPlayersId().contains(player.getUniqueId())) return; + if(!group.isGroupDying) { + SharedHealthService sharedHealthService = new SharedHealthService(this.plugin); + sharedHealthService.groupDeath(player); + } + Bukkit.getScheduler().runTaskLater( plugin, () -> { diff --git a/src/main/java/fr/korastrip/paperplugin/listener/RegenerationListener.java b/src/main/java/fr/korastrip/paperplugin/listener/RegenerationListener.java index 28b44c5..c801ec5 100644 --- a/src/main/java/fr/korastrip/paperplugin/listener/RegenerationListener.java +++ b/src/main/java/fr/korastrip/paperplugin/listener/RegenerationListener.java @@ -2,6 +2,7 @@ import fr.korastrip.paperplugin.SharedStats; import fr.korastrip.paperplugin.model.SharedGroup; +import fr.korastrip.paperplugin.services.SharedHealthService; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -23,6 +24,10 @@ public void onRegeneration(EntityRegainHealthEvent event) { if(group.getPlayersId().contains(player.getUniqueId()) && group.getIsPlaying()) { event.setCancelled(true); + SharedHealthService sharedHealthService = new SharedHealthService(this.plugin); + sharedHealthService.shareLoss(); + + if(group.isGroupDying) return; double healthAmount = event.getAmount(); diff --git a/src/main/java/fr/korastrip/paperplugin/model/SharedGroup.java b/src/main/java/fr/korastrip/paperplugin/model/SharedGroup.java index 04612fa..95b3773 100644 --- a/src/main/java/fr/korastrip/paperplugin/model/SharedGroup.java +++ b/src/main/java/fr/korastrip/paperplugin/model/SharedGroup.java @@ -113,7 +113,7 @@ public List getOnlinePlayers() { } public void syncHealth() { - this.getOnlinePlayers().forEach(p -> p.setHealth(this.getHealth())); + this.getOnlinePlayers().stream().filter(p -> !p.isDead()).forEach(p -> p.setHealth(this.getHealth())); } public void addPlayerDeath(Player player) { diff --git a/src/main/java/fr/korastrip/paperplugin/services/SharedHealthService.java b/src/main/java/fr/korastrip/paperplugin/services/SharedHealthService.java new file mode 100644 index 0000000..336d689 --- /dev/null +++ b/src/main/java/fr/korastrip/paperplugin/services/SharedHealthService.java @@ -0,0 +1,98 @@ +package fr.korastrip.paperplugin.services; + +import fr.korastrip.paperplugin.SharedStats; +import fr.korastrip.paperplugin.model.SharedGroup; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.title.Title; +import org.bukkit.Bukkit; +import org.bukkit.Sound; +import org.bukkit.entity.Damageable; +import org.bukkit.entity.Player; + +public class SharedHealthService { + + private final SharedStats plugin; + + public SharedHealthService(SharedStats plugin) { + this.plugin = plugin; + } + + + public void groupDeath(Player deathPlayer) { + SharedGroup group = this.plugin.getGroup(); + group.isGroupDying = true; + + var players = group.getOnlinePlayers(); + + Bukkit.getScheduler().runTask( + plugin, + () -> { + if (group.getIsPlaying()) { + group.getOnlinePlayers().stream().filter(p -> !p.isDead()).forEach(Damageable::kill); + } + } + ); + + final Component mainTitle = deathPlayer.displayName() + .color(NamedTextColor.RED); + final Component subtitle = Component.text("is dead", NamedTextColor.WHITE); + final Title title = Title.title(mainTitle, subtitle); + + group.addPlayerDeath(deathPlayer); + + players.forEach(p -> { + p.showTitle(title); + + p.playSound( + p.getLocation(), + Sound.ENTITY_WITHER_SPAWN, + 1.0f, + 1.0f + ); + }); + group.incrementsDeadCount(); + } + + public void shareLoss() { + SharedGroup group = this.plugin.getGroup(); + + if (!group.getIsPlaying() || group.isGroupDying) return; + + double totalLost = 0; + Player mostHurtPlayer = null; + double currentDamage = 0; + for (Player member : group.getOnlinePlayers()) { + if (member.isDead()) continue; + + // Players store health as a float: compare at the same precision to avoid fake losses. + double playerLost = (float) group.getHealth() - member.getHealth(); + if (playerLost > 0) { + totalLost += playerLost; + + if(playerLost > currentDamage) { + currentDamage = playerLost; + mostHurtPlayer = member; + } + + } + } + + if (totalLost == 0) return; + + var newHealth = group.getHealth() - totalLost; + + if (newHealth <= 0) { + TotemService totemService = new TotemService(this.plugin); + if (totemService.check()) { + totemService.applyEffect(); + } else { + this.groupDeath(mostHurtPlayer); + } + return; + } + + group.setHealth(newHealth); + group.syncHealth(); + } +}