From 150873f7e7c15135080c58f59e27d1b688460897 Mon Sep 17 00:00:00 2001 From: XxFran10xX <318299142+XxFran10xX@users.noreply.github.com> Date: Tue, 29 Sep 2026 22:09:24 +0200 Subject: [PATCH] fix: preserve player-kept mounts during chunk cleanup --- .../husbandry/HusbandryLifecycleListener.java | 6 +- .../cooking/husbandry/HusbandryMounts.java | 13 +++ .../HusbandryMountPersistenceTest.java | 108 ++++++++++++++++++ 3 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 src/test/java/net/tfminecraft/cooking/husbandry/HusbandryMountPersistenceTest.java diff --git a/src/main/java/net/tfminecraft/cooking/husbandry/HusbandryLifecycleListener.java b/src/main/java/net/tfminecraft/cooking/husbandry/HusbandryLifecycleListener.java index 10520c0..64bf189 100644 --- a/src/main/java/net/tfminecraft/cooking/husbandry/HusbandryLifecycleListener.java +++ b/src/main/java/net/tfminecraft/cooking/husbandry/HusbandryLifecycleListener.java @@ -125,10 +125,11 @@ static void handleLoad(Entity entity) { } UUID uuid = entity.getUniqueId(); boolean hasRow = repository.exists(uuid); + boolean playerKeptMount = HusbandryMounts.isPlayerKeptMount(entity); if (HusbandryMounts.shouldWipeUnowned( HusbandryConfig.isRemoveUnowned(entity.getType()), - HusbandryOwnershipService.hasAnyOwner(uuid), + HusbandryOwnershipService.hasAnyOwner(uuid) || playerKeptMount, hasRow, HusbandryMounts.hasConfiguredStats(entity))) { if (hasRow) { @@ -139,6 +140,9 @@ static void handleLoad(Entity entity) { return; } if (!hasRow) { + if (playerKeptMount) { + HusbandryEntities.applyPersistFlags(living); + } return; } diff --git a/src/main/java/net/tfminecraft/cooking/husbandry/HusbandryMounts.java b/src/main/java/net/tfminecraft/cooking/husbandry/HusbandryMounts.java index 0a82ea0..c497f10 100644 --- a/src/main/java/net/tfminecraft/cooking/husbandry/HusbandryMounts.java +++ b/src/main/java/net/tfminecraft/cooking/husbandry/HusbandryMounts.java @@ -8,6 +8,7 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.LivingEntity; +import org.bukkit.inventory.ItemStack; public final class HusbandryMounts { @@ -21,6 +22,18 @@ public static boolean hasConfiguredStats(Entity entity) { return entity != null && HusbandryConfig.mountStats(entity.getType()) != null; } + /** Vanilla ownership and equipment survive even when Cooking has no record. */ + public static boolean isPlayerKeptMount(Entity entity) { + if (!(entity instanceof AbstractHorse horse)) { + return false; + } + if (horse.isTamed() || horse.customName() != null) { + return true; + } + ItemStack saddle = horse.getInventory().getSaddle(); + return saddle != null && !saddle.getType().isAir(); + } + public static boolean shouldWipeUnowned( boolean removeUnownedType, boolean hasOwner, boolean hasRow, boolean configuredMount) { return removeUnownedType && !hasOwner && !(hasRow && configuredMount); diff --git a/src/test/java/net/tfminecraft/cooking/husbandry/HusbandryMountPersistenceTest.java b/src/test/java/net/tfminecraft/cooking/husbandry/HusbandryMountPersistenceTest.java new file mode 100644 index 0000000..fdf55b6 --- /dev/null +++ b/src/test/java/net/tfminecraft/cooking/husbandry/HusbandryMountPersistenceTest.java @@ -0,0 +1,108 @@ +package net.tfminecraft.cooking.husbandry; + +import static org.mockito.Mockito.*; + +import java.util.UUID; + +import net.kyori.adventure.text.Component; +import org.bukkit.Material; +import org.bukkit.entity.Horse; +import org.bukkit.entity.Llama; +import org.bukkit.entity.Cow; +import org.bukkit.entity.LivingEntity; +import org.bukkit.inventory.HorseInventory; +import org.bukkit.inventory.ItemStack; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +class HusbandryMountPersistenceTest { + @Test + void namedSaddledHorseWithoutCookingRecordSurvivesLoad() { + Horse horse = horse(); + when(horse.customName()).thenReturn(Component.text("Hazel's horse")); + ItemStack saddle = mock(ItemStack.class); + when(saddle.getType()).thenReturn(Material.SADDLE); + when(horse.getInventory().getSaddle()).thenReturn(saddle); + checkLoad(horse, false, false, true); + } + + @Test + void vanillaTamedHorseWithoutCookingOwnerSurvivesLoad() { + Horse horse = horse(); + when(horse.isTamed()).thenReturn(true); + checkLoad(horse, false, false, true); + } + + @Test + void namedHorseAloneSurvivesLoad() { + Horse horse = horse(); + when(horse.customName()).thenReturn(Component.text("Named")); + checkLoad(horse, false, false, true); + } + + @Test + void saddledHorseAloneSurvivesLoad() { + Horse horse = horse(); + ItemStack saddle = mock(ItemStack.class); + when(saddle.getType()).thenReturn(Material.SADDLE); + when(horse.getInventory().getSaddle()).thenReturn(saddle); + checkLoad(horse, false, false, true); + } + + @Test + void wildHorseWithoutRecordIsStillRemoved() { + checkLoad(horse(), false, false, false); + } + + @Test + void enrolledWildHorseIsStillKept() { + checkLoad(horse(), true, true, true); + } + + @Test + void tamedLlamaKeepsItsRowWithoutConfiguredMountStats() { + Llama llama = mock(Llama.class); + when(llama.isTamed()).thenReturn(true); + checkLoad(llama, true, false, true); + } + + @Test + void namedUnownedLivestockStillUsesExistingCleanup() { + Cow cow = mock(Cow.class); + when(cow.customName()).thenReturn(Component.text("Cow")); + checkLoad(cow, true, false, false); + } + + private static Horse horse() { + Horse horse = mock(Horse.class); + when(horse.getInventory()).thenReturn(mock(HorseInventory.class)); + return horse; + } + + private static void checkLoad(LivingEntity entity, boolean hasRow, boolean configuredMount, boolean kept) { + UUID uuid = UUID.randomUUID(); + when(entity.getUniqueId()).thenReturn(uuid); + HusbandryRepository repository = mock(HusbandryRepository.class); + when(repository.exists(uuid)).thenReturn(hasRow); + try (MockedStatic entities = mockStatic(HusbandryEntities.class); + MockedStatic config = mockStatic(HusbandryConfig.class); + MockedStatic ownership = mockStatic(HusbandryOwnershipService.class)) { + entities.when(HusbandryEntities::repository).thenReturn(repository); + entities.when(() -> HusbandryEntities.applyPersistFlags(entity)).thenCallRealMethod(); + config.when(() -> HusbandryConfig.isRemoveUnowned(entity.getType())).thenReturn(true); + if (configuredMount) { + config.when(() -> HusbandryConfig.mountStats(entity.getType())) + .thenReturn(mock(HusbandryMountStats.class)); + } + + HusbandryLifecycleListener.handleLoad(entity); + + verify(entity, kept ? never() : times(1)).remove(); + verify(repository, !kept && hasRow ? times(1) : never()).deleteAnimal(uuid); + if (kept) { + verify(entity).setPersistent(true); + verify(entity).setRemoveWhenFarAway(false); + } + } + } +}