diff --git a/src/main/java/net/tfminecraft/cooking/husbandry/HusbandryClaimHint.java b/src/main/java/net/tfminecraft/cooking/husbandry/HusbandryClaimHint.java index 7d05668..0552add 100644 --- a/src/main/java/net/tfminecraft/cooking/husbandry/HusbandryClaimHint.java +++ b/src/main/java/net/tfminecraft/cooking/husbandry/HusbandryClaimHint.java @@ -10,7 +10,7 @@ import org.bukkit.entity.Player; import org.bukkit.entity.Tameable; -/** Tells players that animals they tame or ride vanish unless claimed in Cooking. */ +/** Tells players who tame or ride an unowned animal how to claim it, and their animal limit. */ public final class HusbandryClaimHint { static final long COOLDOWN_MILLIS = 60_000L; @@ -35,6 +35,13 @@ public static boolean needsClaim(Entity entity) { HusbandryMounts.hasConfiguredStats(entity)); } + /** Any listed animal without a Cooking owner, including ridden mounts the cleanup keeps as wild. */ + public static boolean isUnclaimed(Entity entity) { + return entity != null + && HusbandryConfig.isRemoveUnowned(entity.getType()) + && !HusbandryOwnershipService.hasAnyOwner(entity.getUniqueId()); + } + /** Sends the hint unless this player saw one within the cooldown. */ public static void remind(Player player, Entity entity) { long now = System.currentTimeMillis(); @@ -50,16 +57,18 @@ public static void remind(Player player, Entity entity) { * so reading it from the entity would tell a player who just tamed it to tame it first. */ public static void send(Player player, Entity entity, boolean untamed) { - if (player == null || !needsClaim(entity)) { + if (player == null || !isUnclaimed(entity)) { return; } + // Riding enrolls a mount, and the cleanup keeps enrolled mounts as unowned wild animals. + boolean staysWild = !needsClaim(entity); LAST_SENT.put(player.getUniqueId(), System.currentTimeMillis()); boolean claimable = HusbandryConfig.species(entity.getType()) != null; boolean staff = HusbandryOwnershipService.isStaff(player); HusbandryRepository repository = HusbandryEntities.repository(); int owned = repository == null ? 0 : repository.countForPlayer(player.getUniqueId()); for (String line : lines(HusbandryEntities.displayName(entity.getType()), - claimable, untamed, owned, HusbandryConfig.maxAnimals(), staff)) { + claimable, untamed, staysWild, owned, HusbandryConfig.maxAnimals(), staff)) { player.sendMessage(line); } } @@ -69,10 +78,18 @@ public static void forget(UUID playerUuid) { } static List lines( - String species, boolean claimable, boolean untamed, int owned, int cap, boolean staff) { + String species, + boolean claimable, + boolean untamed, + boolean staysWild, + int owned, + int cap, + boolean staff) { String label = species == null || species.isBlank() ? "animal" : species.toLowerCase(Locale.ROOT); - String warning = "§eThis " + label + " is not claimed. Unclaimed animals disappear the next time" - + " their area loads."; + String warning = staysWild + ? "§eThis " + label + " is not claimed. Anyone can ride it or claim it until someone does." + : "§eThis " + label + " is not claimed. Unclaimed animals disappear the next time" + + " their area loads."; if (!claimable) { return List.of(warning, "§7This kind of animal cannot be claimed, so it will not stay."); } diff --git a/src/test/java/net/tfminecraft/cooking/husbandry/HusbandryOwnedMountTest.java b/src/test/java/net/tfminecraft/cooking/husbandry/HusbandryOwnedMountTest.java index c203f3e..b184233 100644 --- a/src/test/java/net/tfminecraft/cooking/husbandry/HusbandryOwnedMountTest.java +++ b/src/test/java/net/tfminecraft/cooking/husbandry/HusbandryOwnedMountTest.java @@ -54,7 +54,7 @@ void wildHorseWithoutRecordIsRemoved() { @Test void hintExplainsHowToClaimAndTheLimit() { - List lines = HusbandryClaimHint.lines("Horse", true, true, 3, 15, false); + List lines = HusbandryClaimHint.lines("Horse", true, true, false, 3, 15, false); assertEquals(3, lines.size()); assertTrue(lines.get(0).contains("This horse is not claimed")); assertTrue(lines.get(1).contains("Tame it, then right-click it with an Ownership Token")); @@ -63,7 +63,7 @@ void hintExplainsHowToClaimAndTheLimit() { @Test void hintAtCapSaysItCannotBeClaimed() { - List lines = HusbandryClaimHint.lines("Donkey", true, false, 15, 15, false); + List lines = HusbandryClaimHint.lines("Donkey", true, false, false, 15, 15, false); assertTrue(lines.get(1).contains("15/15 animals, so you cannot claim it")); assertTrue(lines.get(2).contains("shift-right-click it with an empty hand, then click Remove ownership")); assertTrue(lines.get(2).contains("/animals")); @@ -71,7 +71,7 @@ void hintAtCapSaysItCannotBeClaimed() { @Test void unclaimableAnimalsAreNotSentToTheToken() { - List lines = HusbandryClaimHint.lines("Wolf", false, false, 0, 15, false); + List lines = HusbandryClaimHint.lines("Wolf", false, false, false, 0, 15, false); assertEquals(2, lines.size()); assertTrue(lines.get(0).contains("the next time their area loads")); assertTrue(lines.get(1).contains("cannot be claimed")); @@ -152,15 +152,68 @@ void tamingHintDoesNotSayToTameAnAnimalThatWasJustTamed() { } } + @Test + void riddenUnownedHorseStillGetsTheClaimHint() { + Player player = mock(Player.class); + UUID playerId = UUID.randomUUID(); + when(player.getUniqueId()).thenReturn(playerId); + Horse horse = horse(); + UUID horseId = UUID.randomUUID(); + when(horse.getUniqueId()).thenReturn(horseId); + when(horse.getType()).thenReturn(EntityType.HORSE); + HusbandryRepository repository = mock(HusbandryRepository.class); + // Mounting already enrolled it, so the cleanup keeps it as a wild horse. + when(repository.exists(horseId)).thenReturn(true); + when(repository.countForPlayer(playerId)).thenReturn(4); + 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.getLoaded(horseId)).thenReturn(Optional.empty()); + entities.when(() -> HusbandryEntities.displayName(EntityType.HORSE)).thenReturn("Horse"); + config.when(() -> HusbandryConfig.isRemoveUnowned(EntityType.HORSE)).thenReturn(true); + config.when(() -> HusbandryConfig.mountStats(EntityType.HORSE)) + .thenReturn(mock(HusbandryMountStats.class)); + config.when(() -> HusbandryConfig.species(EntityType.HORSE)).thenReturn(mock(HusbandrySpecies.class)); + config.when(HusbandryConfig::maxAnimals).thenReturn(15); + + HusbandryClaimHint.send(player, horse, true); + + ArgumentCaptor sent = ArgumentCaptor.forClass(String.class); + verify(player, times(3)).sendMessage(sent.capture()); + assertTrue(sent.getAllValues().get(0).contains("Anyone can ride it or claim it")); + assertTrue(sent.getAllValues().get(1).startsWith("§7Tame it, then right-click it")); + assertTrue(sent.getAllValues().get(2).contains("4/15")); + } + } + + @Test + void ownedHorseGetsNoHint() { + Player player = mock(Player.class); + Horse horse = horse(); + UUID horseId = UUID.randomUUID(); + when(horse.getUniqueId()).thenReturn(horseId); + when(horse.getType()).thenReturn(EntityType.HORSE); + try (MockedStatic config = mockStatic(HusbandryConfig.class); + MockedStatic ownership = mockStatic(HusbandryOwnershipService.class)) { + config.when(() -> HusbandryConfig.isRemoveUnowned(EntityType.HORSE)).thenReturn(true); + ownership.when(() -> HusbandryOwnershipService.hasAnyOwner(horseId)).thenReturn(true); + + HusbandryClaimHint.send(player, horse, false); + + verify(player, never()).sendMessage(anyString()); + } + } + @Test void tamedHintSkipsTheTamingStep() { - List lines = HusbandryClaimHint.lines("Horse", true, false, 0, 15, false); + List lines = HusbandryClaimHint.lines("Horse", true, false, false, 0, 15, false); assertTrue(lines.get(1).startsWith("§7Right-click it with an Ownership Token")); } @Test void staffHintHasNoLimit() { - List lines = HusbandryClaimHint.lines("Horse", true, false, 40, 15, true); + List lines = HusbandryClaimHint.lines("Horse", true, false, false, 40, 15, true); assertEquals(2, lines.size()); assertTrue(lines.get(1).contains("no animal limit")); }