feat(tamer): limit custody to one mob
This commit is contained in:
@@ -2,7 +2,8 @@ package games.dmg.spigottyrant;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface CapturedMobCustodyRelease {
|
||||
PlayerState releaseAll(Player player, PlayerState state);
|
||||
|
||||
PlayerState releaseExcess(Player player, PlayerState state, int custodyLimit);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,11 @@ public final class CapturedMobService {
|
||||
if (player.tyrantClass() != TyrantClass.TAMER) {
|
||||
throw new IllegalStateException("only a Tamer can capture mobs");
|
||||
}
|
||||
if (!player.capturedMobs().isEmpty()) {
|
||||
throw new IllegalStateException(
|
||||
"release the currently captured mob before capturing another"
|
||||
);
|
||||
}
|
||||
if ("ENDER_DRAGON".equalsIgnoreCase(entityType)) {
|
||||
throw new IllegalArgumentException("Ender Dragons cannot be captured");
|
||||
}
|
||||
|
||||
@@ -13,13 +13,27 @@ public final class DefaultCapturedMobCustodyRelease implements CapturedMobCustod
|
||||
|
||||
@Override
|
||||
public PlayerState releaseAll(Player player, PlayerState state) {
|
||||
return releaseExcess(player, state, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayerState releaseExcess(
|
||||
Player player,
|
||||
PlayerState state,
|
||||
int custodyLimit
|
||||
) {
|
||||
if (custodyLimit < 0) {
|
||||
throw new IllegalArgumentException("custodyLimit must not be negative");
|
||||
}
|
||||
List<CapturedMob> retained = new ArrayList<>();
|
||||
for (CapturedMob mob : state.capturedMobs()) {
|
||||
if (!spawner.spawn(player, mob)) {
|
||||
List<CapturedMob> custody = state.capturedMobs();
|
||||
for (int index = 0; index < custody.size(); index++) {
|
||||
CapturedMob mob = custody.get(index);
|
||||
if (index < custodyLimit || !spawner.spawn(player, mob)) {
|
||||
retained.add(mob);
|
||||
}
|
||||
}
|
||||
if (retained.size() == state.capturedMobs().size()) {
|
||||
if (retained.equals(custody)) {
|
||||
return state;
|
||||
}
|
||||
return new PlayerState(
|
||||
|
||||
@@ -37,14 +37,18 @@ public final class FormerTamerReleaseTask implements Runnable {
|
||||
Map<UUID, Player> online = server.getOnlinePlayers().stream()
|
||||
.collect(Collectors.toMap(Player::getUniqueId, player -> player));
|
||||
for (PlayerState state : stateManager.players().values()) {
|
||||
if (state.tyrantClass() == TyrantClass.TAMER || state.capturedMobs().isEmpty()) {
|
||||
if (state.capturedMobs().isEmpty()
|
||||
|| state.tyrantClass() == TyrantClass.TAMER
|
||||
&& state.capturedMobs().size() == 1) {
|
||||
continue;
|
||||
}
|
||||
Player player = online.get(state.playerId());
|
||||
if (player == null || player.isDead()) {
|
||||
continue;
|
||||
}
|
||||
PlayerState released = release.releaseAll(player, state);
|
||||
PlayerState released = state.tyrantClass() == TyrantClass.TAMER
|
||||
? release.releaseExcess(player, state, 1)
|
||||
: release.releaseAll(player, state);
|
||||
if (released.equals(state)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -63,6 +63,11 @@ public final class TamerListener implements Listener {
|
||||
return;
|
||||
}
|
||||
PlayerState state = stateManager.player(player.getUniqueId(), player.getName());
|
||||
if (!state.capturedMobs().isEmpty()) {
|
||||
player.sendMessage(ChatColor.RED
|
||||
+ "Release the currently captured mob before capturing another.");
|
||||
return;
|
||||
}
|
||||
Entity target = event.getRightClicked();
|
||||
if (!canCapture(state, target)) {
|
||||
player.sendMessage(ChatColor.RED + "That mob cannot be captured.");
|
||||
|
||||
@@ -28,6 +28,21 @@ final class CapturedMobServiceTest {
|
||||
assertEquals(java.util.List.of(), released.capturedMobs());
|
||||
}
|
||||
|
||||
@Test
|
||||
void tamerMustReleaseHeldMobBeforeCapturingAnother() {
|
||||
PlayerState holding = service.capture(
|
||||
tamer(), UUID.randomUUID(), "COW", "cow-snapshot"
|
||||
);
|
||||
|
||||
IllegalStateException exception = assertThrows(IllegalStateException.class, () ->
|
||||
service.capture(holding, UUID.randomUUID(), "SHEEP", "sheep-snapshot")
|
||||
);
|
||||
|
||||
assertEquals("release the currently captured mob before capturing another",
|
||||
exception.getMessage());
|
||||
assertEquals(1, holding.capturedMobs().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void enderDragonCanNeverBeCapturedAndMissingReleaseDoesNotMutate() {
|
||||
assertThrows(IllegalArgumentException.class, () -> service.capture(
|
||||
|
||||
@@ -12,6 +12,28 @@ import org.bukkit.entity.Player;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
final class DefaultCapturedMobCustodyReleaseTest {
|
||||
@Test
|
||||
void legacyExcessReleaseRetainsOneAndAnyFailedExtra() {
|
||||
UUID playerId = UUID.fromString("33333333-3333-3333-3333-333333333333");
|
||||
CapturedMob retained = mob("COW");
|
||||
CapturedMob releasedExtra = mob("SHEEP");
|
||||
CapturedMob deferredExtra = mob("PIG");
|
||||
PlayerState state = new PlayerState(
|
||||
playerId, "Tamer", Optional.empty(), Optional.empty(), TyrantClass.TAMER,
|
||||
Optional.empty(), Map.of(), java.util.Set.of(),
|
||||
List.of(retained, releasedExtra, deferredExtra)
|
||||
);
|
||||
CapturedMobSpawner spawner = mock(CapturedMobSpawner.class);
|
||||
Player player = mock(Player.class);
|
||||
when(spawner.spawn(player, releasedExtra)).thenReturn(true);
|
||||
when(spawner.spawn(player, deferredExtra)).thenReturn(false);
|
||||
DefaultCapturedMobCustodyRelease service = new DefaultCapturedMobCustodyRelease(spawner);
|
||||
|
||||
PlayerState result = service.releaseExcess(player, state, 1);
|
||||
|
||||
assertEquals(List.of(retained, deferredExtra), result.capturedMobs());
|
||||
}
|
||||
|
||||
@Test
|
||||
void removesOnlyCustodyThatSpawnedSuccessfully() {
|
||||
UUID playerId = UUID.fromString("33333333-3333-3333-3333-333333333333");
|
||||
|
||||
@@ -31,6 +31,28 @@ final class FormerTamerReleaseTaskTest {
|
||||
verify(release, never()).releaseAll(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void safelyReleasesLegacyCustodyAboveOneForActiveTamer() {
|
||||
UUID tamerId = UUID.fromString("44444444-4444-4444-4444-444444444444");
|
||||
PlayerState once = withMob(withClass(
|
||||
PlayerState.newPlayer(tamerId, "Tamer"), TyrantClass.TAMER
|
||||
));
|
||||
PlayerState legacy = withMob(once);
|
||||
TyrantStateManager manager = mock(TyrantStateManager.class);
|
||||
when(manager.players()).thenReturn(Map.of(tamerId, legacy));
|
||||
Player tamer = mock(Player.class);
|
||||
when(tamer.getUniqueId()).thenReturn(tamerId);
|
||||
Server server = mock(Server.class);
|
||||
doReturn(Set.of(tamer)).when(server).getOnlinePlayers();
|
||||
CapturedMobCustodyRelease release = mock(CapturedMobCustodyRelease.class);
|
||||
when(release.releaseExcess(tamer, legacy, 1)).thenReturn(legacy);
|
||||
|
||||
new FormerTamerReleaseTask(manager, server, release).run();
|
||||
|
||||
verify(release).releaseExcess(tamer, legacy, 1);
|
||||
verify(release, never()).releaseAll(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void releasesCustodyOnlyAfterPlayerLosesTamerClass() {
|
||||
UUID formerId = UUID.fromString("33333333-3333-3333-3333-333333333333");
|
||||
@@ -61,10 +83,12 @@ final class FormerTamerReleaseTaskTest {
|
||||
CapturedMob mob = new CapturedMob("COW", Map.of(
|
||||
"capture-id", UUID.randomUUID().toString(), "snapshot", "snapshot"
|
||||
));
|
||||
java.util.List<CapturedMob> custody = new java.util.ArrayList<>(player.capturedMobs());
|
||||
custody.add(mob);
|
||||
return new PlayerState(
|
||||
player.playerId(), player.latestName(), player.lastLogin(), player.optedOutUntil(),
|
||||
player.tyrantClass(), player.followerOf(), player.cooldownEnds(),
|
||||
player.readyAbilityItems(), java.util.List.of(mob)
|
||||
player.readyAbilityItems(), custody
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package games.dmg.spigottyrant;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.player.PlayerInteractEntityEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.PlayerInventory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
final class TamerListenerTest {
|
||||
@Test
|
||||
void heldMobRejectsAnotherCaptureBeforeTargetOrInventoryChanges() {
|
||||
UUID playerId = UUID.fromString("33333333-3333-3333-3333-333333333333");
|
||||
CapturedMob heldMob = new CapturedMob("COW", Map.of(
|
||||
"capture-id", UUID.randomUUID().toString(), "snapshot", "cow"
|
||||
));
|
||||
PlayerState state = new PlayerState(
|
||||
playerId, "Tamer", Optional.empty(), Optional.empty(), TyrantClass.TAMER,
|
||||
Optional.empty(), Map.of(), Set.of(Ability.TAMER_CAPTURE),
|
||||
java.util.List.of(heldMob)
|
||||
);
|
||||
Player player = mock(Player.class);
|
||||
when(player.getUniqueId()).thenReturn(playerId);
|
||||
when(player.getName()).thenReturn("Tamer");
|
||||
PlayerInventory inventory = mock(PlayerInventory.class);
|
||||
when(player.getInventory()).thenReturn(inventory);
|
||||
ItemStack lead = mock(ItemStack.class);
|
||||
when(inventory.getItem(org.bukkit.inventory.EquipmentSlot.HAND)).thenReturn(lead);
|
||||
Entity target = mock(Entity.class);
|
||||
PlayerInteractEntityEvent event = mock(PlayerInteractEntityEvent.class);
|
||||
when(event.getPlayer()).thenReturn(player);
|
||||
when(event.getHand()).thenReturn(org.bukkit.inventory.EquipmentSlot.HAND);
|
||||
when(event.getRightClicked()).thenReturn(target);
|
||||
AbilityItemService abilityItems = mock(AbilityItemService.class);
|
||||
when(abilityItems.ability(lead)).thenReturn(Optional.of(Ability.TAMER_CAPTURE));
|
||||
when(abilityItems.owner(lead)).thenReturn(Optional.of(playerId));
|
||||
TyrantStateManager manager = mock(TyrantStateManager.class);
|
||||
when(manager.player(playerId, "Tamer")).thenReturn(state);
|
||||
CapturedMobItemService capturedItems = mock(CapturedMobItemService.class);
|
||||
TamerListener listener = new TamerListener(
|
||||
manager, abilityItems, capturedItems, new CapturedMobService(),
|
||||
PluginSettings.from(Map.of()), mock(Server.class)
|
||||
);
|
||||
|
||||
listener.onCapture(event);
|
||||
|
||||
verify(player).sendMessage(org.mockito.ArgumentMatchers.contains(
|
||||
"Release the currently captured mob"
|
||||
));
|
||||
verify(target, never()).createSnapshot();
|
||||
verify(capturedItems, never()).create(
|
||||
org.mockito.ArgumentMatchers.any(), org.mockito.ArgumentMatchers.any(),
|
||||
org.mockito.ArgumentMatchers.anyString()
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user