From 8ea2f0455213cc4258a5e7f1b94300af81240dbc Mon Sep 17 00:00:00 2001 From: Ryan Barlow <7389646+ryanbarlow97@users.noreply.github.com> Date: Mon, 12 Oct 2026 09:07:29 +0000 Subject: [PATCH] fix: preserve successor food after administrative permakill --- .../nutrition/NutritionLifecycleListener.java | 6 +++ .../NutritionListenersCoverageTest.java | 40 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/main/java/net/tfminecraft/cooking/nutrition/NutritionLifecycleListener.java b/src/main/java/net/tfminecraft/cooking/nutrition/NutritionLifecycleListener.java index a4a296f..c6972c1 100644 --- a/src/main/java/net/tfminecraft/cooking/nutrition/NutritionLifecycleListener.java +++ b/src/main/java/net/tfminecraft/cooking/nutrition/NutritionLifecycleListener.java @@ -11,6 +11,7 @@ import org.bukkit.event.player.PlayerRespawnEvent; import net.tfminecraft.rpcharacters.objects.RPCharacter; +import net.tfminecraft.rpcharacters.permadeath.PermadeathService; import net.tfminecraft.tlibs.character.CharacterSessionStartEvent; public final class NutritionLifecycleListener implements Listener { @@ -67,6 +68,11 @@ public void onPlayerDeath(PlayerDeathEvent event) { return; } RPCharacter character = context.character(); + // Administrative permakill activates the successor before the delayed body death. + if (PermadeathService.isAwaitingPermakillRespawn(player)) { + NutritionLog.append("DEATH", player, character, "action=skip cause=permakill-respawn"); + return; + } boolean inBattle = BattleFoodGate.inStartedBattle(player); if (!context.isCurrent()) { NutritionLog.append("DEATH", player, character, "action=skip cause=character-ended"); diff --git a/src/test/java/net/tfminecraft/cooking/nutrition/NutritionListenersCoverageTest.java b/src/test/java/net/tfminecraft/cooking/nutrition/NutritionListenersCoverageTest.java index 9f5a782..dea9e45 100644 --- a/src/test/java/net/tfminecraft/cooking/nutrition/NutritionListenersCoverageTest.java +++ b/src/test/java/net/tfminecraft/cooking/nutrition/NutritionListenersCoverageTest.java @@ -34,6 +34,7 @@ import net.tfminecraft.rpcharacters.RPCharacters; import net.tfminecraft.rpcharacters.managers.PlayerManager; import net.tfminecraft.rpcharacters.objects.RPCharacter; +import net.tfminecraft.rpcharacters.permadeath.PermadeathService; import net.tfminecraft.simplefactions.war.battle.engine.core.Battle; import net.tfminecraft.simplefactions.war.battle.engine.core.BattleManager; import net.tfminecraft.simplefactions.war.battle.engine.core.BattleSide; @@ -405,6 +406,45 @@ class NutritionListenersCoverageTest { assertEquals(15, character.getFoodValue()); verifyNoInteractions(players); } + @ParameterizedTest + @ValueSource(ints = {199, 200}) + void commandPermakillDelayedDeathPreservesSuccessorFood(int successorFood) { + Plugin plugin = MockBukkit.createMockPlugin("RPCharacters"); + character.setFoodValue(119); + RPCharacter successor = new RPCharacter(player); + successor.setFoodValue(successorFood); + NutritionLifecycleListener listener = new NutritionLifecycleListener(); + PlayerDeathEvent death = mock(PlayerDeathEvent.class); + when(death.getEntity()).thenReturn(player); + + // Administrative permakill activates the successor before scheduling the body's death. + rp.when(() -> RPCharacters.getActiveCharacter(player)).thenReturn(successor); + characters.activate(player, successor.getId()); + PermadeathService.markPendingPermakillSounds(player); + try { + server.getScheduler().runTask(plugin, () -> die(listener, death)); + server.getScheduler().performOneTick(); + + assertEquals(119, character.getFoodValue()); + assertEquals(successorFood, successor.getFoodValue()); + verifyNoInteractions(players); + + // RPCharacters consumes this flag on respawn; later ordinary deaths still reset food. + assertTrue(PermadeathService.consumePendingPermakillSounds(player)); + PlayerRespawnEvent respawn = mock(PlayerRespawnEvent.class); + when(respawn.getPlayer()).thenReturn(player); + player.setFoodLevel(8); + listener.onPlayerRespawn(respawn); + assertEquals(successorFood, successor.getFoodValue()); + assertEquals(20, player.getFoodLevel()); + die(listener, death); + assertEquals(80, successor.getFoodValue()); + verify(players).savePlayer(player); + } finally { + PermadeathService.clearPendingPermadeathRespawn(player); + } + } + private static void die(NutritionLifecycleListener listener, PlayerDeathEvent death) { listener.captureDeath(death); listener.onPlayerDeath(death); }