feat(admin): add Tyrant point grant command
Release / release (push) Successful in 2m27s
CI / build (push) Successful in 1m4s

This commit is contained in:
dmg
2026-09-04 22:12:47 -04:00
parent 4b6c709157
commit e5de6b76e7
11 changed files with 175 additions and 5 deletions
@@ -62,6 +62,93 @@ final class TyrantAdminCommandTest {
verify(effects).suppressAll();
}
@Test
void grantPointAddsAndPersistsOneLevelAndChoice() {
UUID tyrant = UUID.fromString("11111111-1111-1111-1111-111111111111");
GameState game = new GameState(
GameLifecycle.RUNNING, Optional.of(tyrant), Optional.empty(),
Optional.empty(), Optional.empty(), Optional.empty(), Duration.ZERO,
2, 1, Set.of(TyrantUnlock.ASSASSIN)
);
TyrantStateManager manager = mock(TyrantStateManager.class);
when(manager.snapshot()).thenReturn(new PersistentState(
game, Map.of(tyrant, PlayerState.newPlayer(tyrant, "Tyrant"))
));
CommandSender sender = mock(CommandSender.class);
when(sender.hasPermission("spigottyrant.admin")).thenReturn(true);
command(manager, mock(ManagedRoleEffects.class)).onCommand(
sender, mock(Command.class), "tyrantadmin", new String[] {"grantpoint"}
);
verify(manager).replaceState(org.mockito.ArgumentMatchers.argThat(state ->
state.game().tyrantLevel() == 3
&& state.game().unspentChoices() == 2
&& state.game().purchases().equals(Set.of(TyrantUnlock.ASSASSIN))
));
verify(manager).saveIfDirty();
verify(sender).sendMessage(contains("level 3 and 2 unspent choices"));
}
@Test
void grantPointRejectsPausedGameWithoutChangingState() {
UUID tyrant = UUID.fromString("11111111-1111-1111-1111-111111111111");
GameState paused = new GameState(
GameLifecycle.PAUSED, Optional.of(tyrant), Optional.empty(),
Optional.empty(), Optional.empty(),
Optional.of(Instant.parse("2026-08-14T12:00:00Z")), Duration.ZERO,
2, 1, Set.of()
);
TyrantStateManager manager = mock(TyrantStateManager.class);
when(manager.snapshot()).thenReturn(new PersistentState(
paused, Map.of(tyrant, PlayerState.newPlayer(tyrant, "Tyrant"))
));
CommandSender sender = mock(CommandSender.class);
when(sender.hasPermission("spigottyrant.admin")).thenReturn(true);
command(manager, mock(ManagedRoleEffects.class)).onCommand(
sender, mock(Command.class), "tyrantadmin", new String[] {"grantpoint"}
);
verify(manager, never()).replaceState(org.mockito.ArgumentMatchers.any());
verify(sender).sendMessage(contains("must be running"));
}
@Test
void grantPointRejectsAdditionalArgumentsWithoutChangingState() {
TyrantStateManager manager = mock(TyrantStateManager.class);
CommandSender sender = mock(CommandSender.class);
when(sender.hasPermission("spigottyrant.admin")).thenReturn(true);
command(manager, mock(ManagedRoleEffects.class)).onCommand(
sender, mock(Command.class), "tyrantadmin",
new String[] {"grantpoint", "unexpected"}
);
verify(manager, never()).replaceState(org.mockito.ArgumentMatchers.any());
verify(sender).sendMessage(contains("Usage: /tyrantadmin grantpoint"));
}
@Test
void grantPointRejectsTyrantVacancyWithoutChangingState() {
GameState vacant = new GameState(
GameLifecycle.RUNNING, Optional.empty(), Optional.empty(),
Optional.empty(), Optional.empty(), Optional.empty(), Duration.ZERO,
0, 0, Set.of()
);
TyrantStateManager manager = mock(TyrantStateManager.class);
when(manager.snapshot()).thenReturn(new PersistentState(vacant, Map.of()));
CommandSender sender = mock(CommandSender.class);
when(sender.hasPermission("spigottyrant.admin")).thenReturn(true);
command(manager, mock(ManagedRoleEffects.class)).onCommand(
sender, mock(Command.class), "tyrantadmin", new String[] {"grantpoint"}
);
verify(manager, never()).replaceState(org.mockito.ArgumentMatchers.any());
verify(sender).sendMessage(contains("no current Tyrant"));
}
@Test
void playerCanPersistArenaAtCurrentLocation() {
TyrantStateManager manager = mock(TyrantStateManager.class);
@@ -35,7 +35,7 @@ final class TyrantAdminTabCompleterTest {
assertTrue(completer.onTabComplete(
admin, command, "tyrantadmin", new String[] {""}
).containsAll(java.util.List.of(
"status", "start", "pause", "resume", "reset", "arena"
"status", "start", "pause", "resume", "reset", "arena", "grantpoint"
)));
assertEquals(java.util.List.of("Alpha", "Beta"), completer.onTabComplete(
admin, command, "tyrantadmin", new String[] {"start", ""}
@@ -49,6 +49,9 @@ final class TyrantAdminTabCompleterTest {
assertEquals(java.util.List.of("set", "status"), completer.onTabComplete(
admin, command, "tyrantadmin", new String[] {"arena", "s"}
));
assertEquals(java.util.List.of(), completer.onTabComplete(
admin, command, "tyrantadmin", new String[] {"grantpoint", ""}
));
}
private static Player player(UUID id, String name) {
@@ -2,6 +2,7 @@ package games.dmg.spigottyrant;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.time.Duration;
@@ -47,6 +48,38 @@ final class TyrantProgressionServiceTest {
assertEquals(paused, service.recordVigilanteDeath(paused, Optional.of(TYRANT)));
}
@Test
void administrativePointGrantAddsOneLevelAndChoicePerInvocation() {
GameState before = runningState(2, 1, Set.of(TyrantUnlock.ASSASSIN));
GameState first = service.grantPoint(before);
GameState second = service.grantPoint(first);
assertEquals(4, second.tyrantLevel());
assertEquals(3, second.unspentChoices());
assertEquals(before.purchases(), second.purchases());
assertEquals(before.tyrantId(), second.tyrantId());
assertEquals(before.vigilanteId(), second.vigilanteId());
}
@Test
void administrativePointGrantRequiresRunningGameAndCurrentTyrant() {
GameState running = runningState(0, 1, Set.of());
GameState paused = new GameState(
GameLifecycle.PAUSED, running.tyrantId(), running.vigilanteId(),
running.pendingTyrant(), running.pendingVigilante(),
Optional.of(java.time.Instant.parse("2026-08-14T12:00:00Z")),
Duration.ZERO, 0, 1, Set.of()
);
GameState vacant = new GameState(
GameLifecycle.RUNNING, Optional.empty(), Optional.empty(), Optional.empty(),
Optional.empty(), Optional.empty(), Duration.ZERO, 0, 0, Set.of()
);
assertThrows(IllegalStateException.class, () -> service.grantPoint(paused));
assertThrows(IllegalStateException.class, () -> service.grantPoint(vacant));
}
@Test
void purchaseConsumesOneChoiceAndCannotBeBoughtTwice() {
GameState before = runningState(1, 2, Set.of());