feat(access): manage spawn bans
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
package games.dmg.triggerspawn;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
record BanEntry(UUID playerId, String playerName) {
|
||||
}
|
||||
@@ -20,16 +20,19 @@ final class SpawnAdminCommand implements CommandExecutor, TabCompleter {
|
||||
|
||||
private final SpawnAreaService spawnAreas;
|
||||
private final AccessService access;
|
||||
private final SpawnBanService bans;
|
||||
private final PlayerResolver players;
|
||||
private final Clock clock;
|
||||
|
||||
SpawnAdminCommand(
|
||||
SpawnAreaService spawnAreas,
|
||||
AccessService access,
|
||||
SpawnBanService bans,
|
||||
PlayerResolver players,
|
||||
Clock clock) {
|
||||
this.spawnAreas = spawnAreas;
|
||||
this.access = access;
|
||||
this.bans = bans;
|
||||
this.players = players;
|
||||
this.clock = clock;
|
||||
}
|
||||
@@ -52,6 +55,9 @@ final class SpawnAdminCommand implements CommandExecutor, TabCompleter {
|
||||
case "grant" -> grant(player, arguments);
|
||||
case "remove" -> remove(player, arguments);
|
||||
case "access" -> listAccess(player, arguments);
|
||||
case "ban" -> ban(player, arguments);
|
||||
case "unban" -> unban(player, arguments);
|
||||
case "bans" -> listBans(player, arguments);
|
||||
default -> {
|
||||
sendUsage(player, label);
|
||||
yield true;
|
||||
@@ -66,7 +72,7 @@ final class SpawnAdminCommand implements CommandExecutor, TabCompleter {
|
||||
return List.of();
|
||||
}
|
||||
String prefix = arguments[0].toLowerCase(Locale.ROOT);
|
||||
return List.of("set", "distance", "info", "grant", "remove", "access").stream()
|
||||
return List.of("set", "distance", "info", "grant", "remove", "access", "ban", "unban", "bans").stream()
|
||||
.filter(value -> value.startsWith(prefix))
|
||||
.toList();
|
||||
}
|
||||
@@ -224,6 +230,69 @@ final class SpawnAdminCommand implements CommandExecutor, TabCompleter {
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean ban(Player player, String[] arguments) {
|
||||
return changeBan(player, arguments, true);
|
||||
}
|
||||
|
||||
private boolean unban(Player player, String[] arguments) {
|
||||
return changeBan(player, arguments, false);
|
||||
}
|
||||
|
||||
private boolean changeBan(Player player, String[] arguments, boolean banned) {
|
||||
String operation = banned ? "ban" : "unban";
|
||||
if (arguments.length != 2) {
|
||||
player.sendMessage(ChatColor.RED + "Usage: /spawnadmin " + operation + " <player>");
|
||||
return true;
|
||||
}
|
||||
PlayerIdentity identity = players.resolve(arguments[1]).orElse(null);
|
||||
if (identity == null) {
|
||||
player.sendMessage(ChatColor.RED + "No online or previously known player matches '"
|
||||
+ arguments[1] + "'.");
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
if (banned) {
|
||||
bans.ban(identity);
|
||||
player.sendMessage(ChatColor.GREEN + "Banned " + identity.latestName()
|
||||
+ " from /spawn and reset their progress.");
|
||||
} else {
|
||||
bans.unban(identity);
|
||||
player.sendMessage(ChatColor.GREEN + "Unbanned " + identity.latestName()
|
||||
+ "; they begin with no /spawn progress.");
|
||||
}
|
||||
} catch (IOException exception) {
|
||||
player.sendMessage(ChatColor.RED + "Could not save the " + operation + ".");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean listBans(Player player, String[] arguments) {
|
||||
if (arguments.length > 2) {
|
||||
player.sendMessage(ChatColor.RED + "Usage: /spawnadmin bans [page]");
|
||||
return true;
|
||||
}
|
||||
int page = parsePage(arguments, player);
|
||||
if (page < 1) {
|
||||
return true;
|
||||
}
|
||||
List<BanEntry> entries = bans.listBans();
|
||||
int pages = Math.max(1, (entries.size() + PAGE_SIZE - 1) / PAGE_SIZE);
|
||||
if (page > pages) {
|
||||
player.sendMessage(ChatColor.RED + "Page must be between 1 and " + pages + ".");
|
||||
return true;
|
||||
}
|
||||
player.sendMessage(ChatColor.GOLD + "/spawn bans — page " + page + "/" + pages + ":");
|
||||
int start = (page - 1) * PAGE_SIZE;
|
||||
for (BanEntry entry : entries.subList(start, Math.min(start + PAGE_SIZE, entries.size()))) {
|
||||
player.sendMessage(ChatColor.RED + entry.playerName() + ChatColor.GRAY
|
||||
+ " (" + entry.playerId() + ")");
|
||||
}
|
||||
if (entries.isEmpty()) {
|
||||
player.sendMessage(ChatColor.GRAY + "No players are banned from /spawn.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int parsePage(String[] arguments, CommandSender sender) {
|
||||
if (arguments.length == 1) {
|
||||
return 1;
|
||||
@@ -246,6 +315,6 @@ final class SpawnAdminCommand implements CommandExecutor, TabCompleter {
|
||||
|
||||
private static void sendUsage(CommandSender sender, String label) {
|
||||
sender.sendMessage(ChatColor.YELLOW + "Usage: /" + label
|
||||
+ " <set|distance|info|grant|remove|access>");
|
||||
+ " <set|distance|info|grant|remove|access|ban|unban|bans>");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package games.dmg.triggerspawn;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
final class SpawnBanService {
|
||||
private final SpawnStateManager stateManager;
|
||||
|
||||
SpawnBanService(SpawnStateManager stateManager) {
|
||||
this.stateManager = stateManager;
|
||||
}
|
||||
|
||||
void ban(PlayerIdentity identity) throws IOException {
|
||||
stateManager.putPlayer(new PlayerState(
|
||||
identity.playerId(),
|
||||
identity.latestName(),
|
||||
Set.of(),
|
||||
Optional.empty(),
|
||||
true,
|
||||
Optional.empty()));
|
||||
}
|
||||
|
||||
void unban(PlayerIdentity identity) throws IOException {
|
||||
stateManager.putPlayer(PlayerState.newPlayer(identity.playerId(), identity.latestName()));
|
||||
}
|
||||
|
||||
boolean isBanned(PlayerIdentity identity) {
|
||||
PlayerState state = stateManager.snapshot().players().get(identity.playerId());
|
||||
return state != null && state.banned();
|
||||
}
|
||||
|
||||
List<BanEntry> listBans() {
|
||||
return stateManager.snapshot().players().values().stream()
|
||||
.filter(PlayerState::banned)
|
||||
.map(player -> new BanEntry(player.playerId(), player.latestName()))
|
||||
.sorted(Comparator.comparing(BanEntry::playerName, String.CASE_INSENSITIVE_ORDER))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ public final class TriggerSpawnPlugin extends JavaPlugin {
|
||||
SpawnAdminCommand executor = new SpawnAdminCommand(
|
||||
new SpawnAreaService(stateManager),
|
||||
new AccessService(stateManager, settings),
|
||||
new SpawnBanService(stateManager),
|
||||
new PlayerResolver(getServer(), stateManager),
|
||||
Clock.systemUTC());
|
||||
PluginCommand adminCommand = getCommand("spawnadmin");
|
||||
|
||||
@@ -34,7 +34,11 @@ final class SpawnAdminCommandTest {
|
||||
AccessService access = new AccessService(
|
||||
manager, PluginSettings.from(new org.bukkit.configuration.MemoryConfiguration()));
|
||||
SpawnAdminCommand command = new SpawnAdminCommand(
|
||||
service, access, mock(PlayerResolver.class), Clock.systemUTC());
|
||||
service,
|
||||
access,
|
||||
new SpawnBanService(manager),
|
||||
mock(PlayerResolver.class),
|
||||
Clock.systemUTC());
|
||||
Command bukkitCommand = mock(Command.class);
|
||||
|
||||
command.onCommand(player, bukkitCommand, "spawnadmin", new String[] {"set"});
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package games.dmg.triggerspawn;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
final class SpawnBanServiceTest {
|
||||
@TempDir
|
||||
Path temporaryDirectory;
|
||||
|
||||
@Test
|
||||
void banClearsAccessAndUnbanRestoresNoProgress() throws Exception {
|
||||
UUID playerId = UUID.randomUUID();
|
||||
Path stateFile = temporaryDirectory.resolve("state.yml");
|
||||
SpawnStateManager manager = SpawnStateManager.load(new YamlSpawnStateRepository(stateFile));
|
||||
manager.putPlayer(new PlayerState(
|
||||
playerId,
|
||||
"Alex",
|
||||
Set.of(BossType.WARDEN),
|
||||
Optional.of(Duration.ofMinutes(10)),
|
||||
false,
|
||||
Optional.of(Instant.now())));
|
||||
SpawnBanService bans = new SpawnBanService(manager);
|
||||
PlayerIdentity identity = new PlayerIdentity(playerId, "Alex");
|
||||
|
||||
bans.ban(identity);
|
||||
|
||||
PlayerState banned = manager.snapshot().players().get(playerId);
|
||||
assertTrue(banned.banned());
|
||||
assertTrue(banned.defeatedBosses().isEmpty());
|
||||
assertTrue(banned.grantedCooldown().isEmpty());
|
||||
assertTrue(banned.lastSpawnUse().isEmpty());
|
||||
assertTrue(bans.listBans().stream().anyMatch(entry -> entry.playerId().equals(playerId)));
|
||||
|
||||
bans.unban(identity);
|
||||
|
||||
PlayerState unbanned = manager.snapshot().players().get(playerId);
|
||||
assertFalse(unbanned.banned());
|
||||
assertTrue(unbanned.defeatedBosses().isEmpty());
|
||||
assertTrue(unbanned.grantedCooldown().isEmpty());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user