Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
Expand All @@ -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);
}
}
Expand All @@ -69,10 +78,18 @@ public static void forget(UUID playerUuid) {
}

static List<String> 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.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ void wildHorseWithoutRecordIsRemoved() {

@Test
void hintExplainsHowToClaimAndTheLimit() {
List<String> lines = HusbandryClaimHint.lines("Horse", true, true, 3, 15, false);
List<String> 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"));
Expand All @@ -63,15 +63,15 @@ void hintExplainsHowToClaimAndTheLimit() {

@Test
void hintAtCapSaysItCannotBeClaimed() {
List<String> lines = HusbandryClaimHint.lines("Donkey", true, false, 15, 15, false);
List<String> 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"));
}

@Test
void unclaimableAnimalsAreNotSentToTheToken() {
List<String> lines = HusbandryClaimHint.lines("Wolf", false, false, 0, 15, false);
List<String> 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"));
Expand Down Expand Up @@ -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<HusbandryEntities> entities = mockStatic(HusbandryEntities.class);
MockedStatic<HusbandryConfig> config = mockStatic(HusbandryConfig.class);
MockedStatic<HusbandryOwnershipService> 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<String> 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<HusbandryConfig> config = mockStatic(HusbandryConfig.class);
MockedStatic<HusbandryOwnershipService> 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<String> lines = HusbandryClaimHint.lines("Horse", true, false, 0, 15, false);
List<String> 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<String> lines = HusbandryClaimHint.lines("Horse", true, false, 40, 15, true);
List<String> lines = HusbandryClaimHint.lines("Horse", true, false, false, 40, 15, true);
assertEquals(2, lines.size());
assertTrue(lines.get(1).contains("no animal limit"));
}
Expand Down