diff --git a/design/log.md b/design/log.md index 78fa05e..da511bc 100644 --- a/design/log.md +++ b/design/log.md @@ -6,6 +6,12 @@ description: Chronological record of material decisions affecting the Spigot Tyr # Spigot Tyrant Design Log +## 2026-09-04 — Vigilante arena victory kept private + +- Removed the server-wide Vigilante arena victory announcement that exposed the new Vigilante's identity. +- The winning Vigilante now receives a private confirmation, while the publicly identifiable Tyrant victory announcement remains unchanged. +- Verified private Vigilante and public Tyrant messaging, 121 automated tests, compiler warnings, and packaging with `./gradlew clean check jar`. + ## 2026-09-04 — Arena winners safely extracted - Corrected shared-role arena completion so a successful Tyrant or Vigilante challenger is moved safely outside immediately after assignment and barrier removal. diff --git a/design/user-stories/us-019-claim-vigilante-in-arena.md b/design/user-stories/us-019-claim-vigilante-in-arena.md index c2d56a0..8bfd22e 100644 --- a/design/user-stories/us-019-claim-vigilante-in-arena.md +++ b/design/user-stories/us-019-claim-vigilante-in-arena.md @@ -27,6 +27,7 @@ As an **opted-in participant**, I want a visible one-player boss challenge for a - [x] Block placement and breaking are denied inside the arena during an active fight so players cannot build over or modify the boundary. - [x] Killing the boss assigns the challenger as Vigilante, removes the boss and barriers, and restores the passive particle boundary. - [x] After winning, the new Vigilante is moved safely outside the arena before normal arena enforcement resumes. +- [x] The Vigilante winner receives a private confirmation, and no server-wide victory message names or reveals the new Vigilante. - [x] Pausing, resetting, restarting, reloading, and repeated or concurrent events cannot duplicate the boss, barriers, challenger, or Vigilante assignment. - [x] If the arena is not configured when a challenge should open, the Vigilante remains vacant and administrators receive a clear warning. diff --git a/src/main/java/games/dmg/spigottyrant/RoleArenaController.java b/src/main/java/games/dmg/spigottyrant/RoleArenaController.java index 10e4c88..7184cd7 100644 --- a/src/main/java/games/dmg/spigottyrant/RoleArenaController.java +++ b/src/main/java/games/dmg/spigottyrant/RoleArenaController.java @@ -416,6 +416,13 @@ public final class RoleArenaController implements Listener, Runnable { barrier.clear(); Player winner = server.getPlayer(playerId); moveWinnerOutside(winner); + if (role == ArenaRole.VIGILANTE) { + if (winner != null) { + winner.sendMessage(ChatColor.YELLOW + + "You defeated the arena boss and became the Vigilante!"); + } + return; + } String name = Optional.ofNullable(winner) .map(Player::getName).orElse(playerId.toString()); String message = ChatColor.YELLOW + name + " defeated the arena boss and is the " diff --git a/src/test/java/games/dmg/spigottyrant/RoleArenaControllerTest.java b/src/test/java/games/dmg/spigottyrant/RoleArenaControllerTest.java index 2ce39d1..8708795 100644 --- a/src/test/java/games/dmg/spigottyrant/RoleArenaControllerTest.java +++ b/src/test/java/games/dmg/spigottyrant/RoleArenaControllerTest.java @@ -30,6 +30,7 @@ import org.junit.jupiter.api.Test; final class RoleArenaControllerTest { private static final UUID TYRANT = UUID.fromString("11111111-1111-1111-1111-111111111111"); private static final UUID PLAYER = UUID.fromString("22222222-2222-2222-2222-222222222222"); + private static final UUID OTHER = UUID.fromString("33333333-3333-3333-3333-333333333333"); @Test void boundaryColorsIdentifyTheOpenRole() { @@ -133,13 +134,15 @@ final class RoleArenaControllerTest { when(player.getUniqueId()).thenReturn(PLAYER); when(player.getName()).thenReturn("Player"); when(player.getLocation()).thenReturn(new Location(world, arena.x(), arena.y(), arena.z())); + Player other = mock(Player.class); + when(other.getUniqueId()).thenReturn(OTHER); PlayerState playerState = PlayerState.newPlayer(PLAYER, "Player"); TyrantStateManager manager = mock(TyrantStateManager.class); when(manager.snapshot()).thenReturn(new PersistentState(game, Map.of(PLAYER, playerState))); Server server = mock(Server.class); when(server.getPlayer(PLAYER)).thenReturn(player); when(server.getWorld("world")).thenReturn(world); - doReturn(Set.of(player)).when(server).getOnlinePlayers(); + doReturn(Set.of(player, other)).when(server).getOnlinePlayers(); ArenaLocationStore locations = mock(ArenaLocationStore.class); when(locations.location()).thenReturn(Optional.of(arena)); RoleArenaController controller = new RoleArenaController( @@ -153,6 +156,14 @@ final class RoleArenaControllerTest { !ArenaGeometry.contains(arena, 10.0, "world", destination.getX(), destination.getY(), destination.getZ()) ), eq(org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.PLUGIN)); + if (role == ArenaRole.VIGILANTE) { + verify(player).sendMessage(contains( + "You defeated the arena boss and became the Vigilante" + )); + verify(other, never()).sendMessage(contains("Vigilante")); + } else { + verify(other).sendMessage(contains("Player defeated the arena boss")); + } } private static PlayerJoinEvent mockJoin(Player player) {