From f8e8806bc7bc08cc7dfe325ee305445e9fd0d213 Mon Sep 17 00:00:00 2001 From: Ryan Barlow <7389646+ryanbarlow97@users.noreply.github.com> Date: Sun, 27 Sep 2026 18:00:12 +0000 Subject: [PATCH] refactor: remove dev-characters and the dev character tag dev-characters kept a server from syncing characters with its website and tagged characters made in game as "dev", so pre-season staff tests stayed off the main site and could be deleted with /rpcharacter wipe tagged. Dev now has its own dev website with realm dev, so that isolation is no longer needed. Remove the setting, the tag and the tagged wipe. Every server now pulls pending creates and kit customisations and pushes its full roster to its own site. The website realm wipe is unchanged. Existing "dev" keys in character files are ignored. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../net/tfminecraft/rpcharacters/Cache.java | 1 - .../creation/CharacterCreation.java | 7 - .../rpcharacters/database/Database.java | 6 - .../ingest/CharacterIngestService.java | 15 +- .../ingest/KitCustomiseIngestService.java | 5 +- .../ingest/RosterSyncService.java | 10 +- .../rpcharacters/kit/KitService.java | 35 ++-- .../rpcharacters/loaders/ConfigLoader.java | 1 - .../rpcharacters/managers/CommandManager.java | 5 - .../rpcharacters/objects/RPCharacter.java | 10 - .../utils/CommandTabCompleter.java | 4 +- .../wipe/CharacterWipeService.java | 191 ------------------ .../rpcharacters/wipe/WipeCommand.java | 104 +++------- src/main/resources/config.yml | 4 - .../wipe/CharacterWipeDeletionTest.java | 57 ------ 15 files changed, 52 insertions(+), 403 deletions(-) delete mode 100644 src/main/java/net/tfminecraft/rpcharacters/wipe/CharacterWipeService.java delete mode 100644 src/test/java/net/tfminecraft/rpcharacters/wipe/CharacterWipeDeletionTest.java diff --git a/src/main/java/net/tfminecraft/rpcharacters/Cache.java b/src/main/java/net/tfminecraft/rpcharacters/Cache.java index 99f03c7..dca9ab4 100644 --- a/src/main/java/net/tfminecraft/rpcharacters/Cache.java +++ b/src/main/java/net/tfminecraft/rpcharacters/Cache.java @@ -32,7 +32,6 @@ public class Cache { public static boolean requireCharacter; /** Playtest server: tag in-game creates and stop syncing characters with the website. */ - public static boolean devCharacters; public static boolean noCharacterFreeze; public static boolean lackingCluesFreeze; public static boolean excessCharactersFreeze; diff --git a/src/main/java/net/tfminecraft/rpcharacters/creation/CharacterCreation.java b/src/main/java/net/tfminecraft/rpcharacters/creation/CharacterCreation.java index 84a1813..60a185d 100644 --- a/src/main/java/net/tfminecraft/rpcharacters/creation/CharacterCreation.java +++ b/src/main/java/net/tfminecraft/rpcharacters/creation/CharacterCreation.java @@ -34,7 +34,6 @@ import net.tfminecraft.rpcharacters.RPCharacters; -import net.tfminecraft.rpcharacters.Cache; import net.tfminecraft.rpcharacters.loaders.StageLoader; @@ -828,12 +827,6 @@ public void finish() { character.update(); - if (Cache.devCharacters) { - - character.setDev(true); - - } - pd.addCharacter(character); net.tfminecraft.rpcharacters.lifecycle.CharacterLifecycle.fireCreated(p, pd.getUniqueId(), character); diff --git a/src/main/java/net/tfminecraft/rpcharacters/database/Database.java b/src/main/java/net/tfminecraft/rpcharacters/database/Database.java index d99b9ac..dedbea6 100644 --- a/src/main/java/net/tfminecraft/rpcharacters/database/Database.java +++ b/src/main/java/net/tfminecraft/rpcharacters/database/Database.java @@ -702,9 +702,6 @@ private void loadPersonaFields(RPCharacter character, JSONObject characterJson) if (characterJson.containsKey("hidden")) { character.setHidden(Boolean.parseBoolean(characterJson.get("hidden").toString())); } - if (characterJson.containsKey("dev")) { - character.setDev(Boolean.parseBoolean(characterJson.get("dev").toString())); - } if (characterJson.containsKey("kit-statuses")) { Object raw = characterJson.get("kit-statuses"); if (raw instanceof JSONObject statuses) { @@ -807,9 +804,6 @@ private void savePersonaFields(HashMap defaults, RPCharacter cha if (character.isHidden()) { defaults.put("hidden", "true"); } - if (character.isDev()) { - defaults.put("dev", "true"); - } if (!character.getKitStatuses().isEmpty()) { JSONObject statuses = new JSONObject(); for (var entry : character.getKitStatuses().entrySet()) { diff --git a/src/main/java/net/tfminecraft/rpcharacters/ingest/CharacterIngestService.java b/src/main/java/net/tfminecraft/rpcharacters/ingest/CharacterIngestService.java index 074207f..1abf096 100644 --- a/src/main/java/net/tfminecraft/rpcharacters/ingest/CharacterIngestService.java +++ b/src/main/java/net/tfminecraft/rpcharacters/ingest/CharacterIngestService.java @@ -16,7 +16,6 @@ import org.json.simple.JSONArray; import org.json.simple.JSONObject; -import net.tfminecraft.rpcharacters.Cache; import net.tfminecraft.rpcharacters.RPCharacters; import net.tfminecraft.rpcharacters.api.ProvinceSystemClient; import net.tfminecraft.rpcharacters.calendar.AgeCalculator; @@ -53,7 +52,7 @@ private CharacterIngestService() {} * Pull if the 15s cooldown has elapsed. Used by join, reload, and the timer. */ public static void tryPullAsync(JavaPlugin plugin) { - if (plugin == null || Cache.devCharacters) { + if (plugin == null) { return; } long now = System.currentTimeMillis(); @@ -74,12 +73,6 @@ public static void forcePullAsync(JavaPlugin plugin) { if (plugin == null) { return; } - if (Cache.devCharacters) { - plugin.getLogger().warning( - "[character-ingest] skipped: dev-characters is on, website characters are not synced" - ); - return; - } lastPullAtMs.set(System.currentTimeMillis()); pullAsync(plugin); } @@ -89,7 +82,7 @@ public static void forcePullAsync(JavaPlugin plugin) { * (or immediately if the pull is skipped). */ public static void tryPullForPlayerAsync(JavaPlugin plugin, UUID playerUuid) { - if (plugin == null || playerUuid == null || Cache.devCharacters) { + if (plugin == null || playerUuid == null) { return; } long now = System.currentTimeMillis(); @@ -127,7 +120,7 @@ public static void stopPeriodicPull() { } public static void pullAsync(JavaPlugin plugin) { - if (plugin == null || Cache.devCharacters) { + if (plugin == null) { return; } Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { @@ -153,7 +146,7 @@ public static void pullAsync(JavaPlugin plugin) { } public static void pullForPlayerAsync(JavaPlugin plugin, UUID playerUuid) { - if (plugin == null || playerUuid == null || Cache.devCharacters) { + if (plugin == null || playerUuid == null) { return; } Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { diff --git a/src/main/java/net/tfminecraft/rpcharacters/ingest/KitCustomiseIngestService.java b/src/main/java/net/tfminecraft/rpcharacters/ingest/KitCustomiseIngestService.java index 9c9872b..97acd72 100644 --- a/src/main/java/net/tfminecraft/rpcharacters/ingest/KitCustomiseIngestService.java +++ b/src/main/java/net/tfminecraft/rpcharacters/ingest/KitCustomiseIngestService.java @@ -13,7 +13,6 @@ import org.json.simple.JSONArray; import org.json.simple.JSONObject; -import net.tfminecraft.rpcharacters.Cache; import net.tfminecraft.rpcharacters.RPCharacters; import net.tfminecraft.rpcharacters.api.ProvinceSystemClient; import net.tfminecraft.rpcharacters.database.Database; @@ -43,7 +42,7 @@ public static void pullAsync(JavaPlugin plugin) { * Safe to call from {@link CharacterIngestService} after creates pull. */ public static void pullNow(JavaPlugin plugin) { - if (plugin == null || Cache.devCharacters) { + if (plugin == null) { return; } ProvinceSystemClient.SimpleResult pending = @@ -83,7 +82,7 @@ public static List applyReadyForCharacterOnMain( RPCharacter character, List all ) { - if (player == null || character == null || Cache.devCharacters) { + if (player == null || character == null) { return List.of(); } String characterId = character.getId(); diff --git a/src/main/java/net/tfminecraft/rpcharacters/ingest/RosterSyncService.java b/src/main/java/net/tfminecraft/rpcharacters/ingest/RosterSyncService.java index fc7d9f8..401595b 100644 --- a/src/main/java/net/tfminecraft/rpcharacters/ingest/RosterSyncService.java +++ b/src/main/java/net/tfminecraft/rpcharacters/ingest/RosterSyncService.java @@ -41,14 +41,14 @@ public final class RosterSyncService { private RosterSyncService() {} public static void pushRosterAsync(UUID playerUuid) { - if (playerUuid == null || RPCharacters.plugin == null || Cache.devCharacters) { + if (playerUuid == null || RPCharacters.plugin == null) { return; } Bukkit.getScheduler().runTaskAsynchronously(RPCharacters.plugin, () -> pushRosterNow(playerUuid)); } public static void pushRosterForPlayer(Player player) { - if (player == null || Cache.devCharacters) { + if (player == null) { return; } pushRosterAsync(player.getUniqueId()); @@ -56,7 +56,7 @@ public static void pushRosterForPlayer(Player player) { /** Push roster for every online player (e.g. after {@code /rpcharacter reload}). */ public static void pushAllOnlineAsync() { - if (RPCharacters.plugin == null || Cache.devCharacters) { + if (RPCharacters.plugin == null) { return; } for (Player player : Bukkit.getOnlinePlayers()) { @@ -68,7 +68,7 @@ public static void pushAllOnlineAsync() { @SuppressWarnings("unchecked") public static void pushRosterNow(UUID playerUuid) { - if (playerUuid == null || Cache.devCharacters) { + if (playerUuid == null) { return; } PlayerData pd = null; @@ -104,7 +104,7 @@ public static void pushRosterNow(UUID playerUuid) { root.put("player_uuid", playerUuid.toString()); JSONArray characters = new JSONArray(); for (RPCharacter c : pd.getCharacters()) { - if (c == null || c.getId() == null || c.isDev()) { + if (c == null || c.getId() == null) { continue; } JSONObject row = new JSONObject(); diff --git a/src/main/java/net/tfminecraft/rpcharacters/kit/KitService.java b/src/main/java/net/tfminecraft/rpcharacters/kit/KitService.java index 2e392b6..ba80f2d 100644 --- a/src/main/java/net/tfminecraft/rpcharacters/kit/KitService.java +++ b/src/main/java/net/tfminecraft/rpcharacters/kit/KitService.java @@ -15,7 +15,6 @@ import org.json.simple.JSONObject; import net.tfminecraft.tlibs.TLibs; -import net.tfminecraft.rpcharacters.Cache; import net.tfminecraft.rpcharacters.loaders.KitLoader; import net.tfminecraft.rpcharacters.managers.PlayerManager; import net.tfminecraft.rpcharacters.objects.PlayerData; @@ -148,27 +147,25 @@ public static void tryClaim(Player player, String kitIdRaw) { } List pendingItems = List.of(); - if (!Cache.devCharacters) { - ProvinceSystemClient.SimpleResult pending = - ProvinceSystemClient.fetchPendingLoreItems(); - if (!pending.ok) { - RPCharacters.plugin.getLogger().warning( - "[kit-customise] claim-pull failed: " + pending.error + ProvinceSystemClient.SimpleResult pending = + ProvinceSystemClient.fetchPendingLoreItems(); + if (!pending.ok) { + RPCharacters.plugin.getLogger().warning( + "[kit-customise] claim-pull failed: " + pending.error + ); + } else { + String body = pending.body != null ? pending.body : ""; + if (body.isBlank()) { + RPCharacters.plugin.getLogger().info( + "[kit-customise] claim-pull empty body char=" + characterId + + " uuid=" + playerId ); } else { - String body = pending.body != null ? pending.body : ""; - if (body.isBlank()) { - RPCharacters.plugin.getLogger().info( - "[kit-customise] claim-pull empty body char=" + characterId - + " uuid=" + playerId - ); - } else { - RPCharacters.plugin.getLogger().info( - "[kit-customise] claim-pull body=" + body - ); - } - pendingItems = ProvinceSystemClient.parsePendingLoreItems(pending.body); + RPCharacters.plugin.getLogger().info( + "[kit-customise] claim-pull body=" + body + ); } + pendingItems = ProvinceSystemClient.parsePendingLoreItems(pending.body); } List itemsForMain = pendingItems; diff --git a/src/main/java/net/tfminecraft/rpcharacters/loaders/ConfigLoader.java b/src/main/java/net/tfminecraft/rpcharacters/loaders/ConfigLoader.java index c1d32fe..9cc540e 100644 --- a/src/main/java/net/tfminecraft/rpcharacters/loaders/ConfigLoader.java +++ b/src/main/java/net/tfminecraft/rpcharacters/loaders/ConfigLoader.java @@ -46,7 +46,6 @@ public void load(File configFile) { Cache.backgroundTraitTypes = config.getStringList("background-trait-types"); Cache.continent = config.getString("continent", "Cerrith"); Cache.requireCharacter = config.getBoolean("require-character", false); - Cache.devCharacters = config.getBoolean("dev-characters", false); Cache.noCharacterFreeze = config.getBoolean("no-character-freeze", true); Cache.lackingCluesFreeze = config.getBoolean("lacking-clues-freeze", true); Cache.excessCharactersFreeze = config.getBoolean("excess-characters-freeze", true); diff --git a/src/main/java/net/tfminecraft/rpcharacters/managers/CommandManager.java b/src/main/java/net/tfminecraft/rpcharacters/managers/CommandManager.java index 9d0d351..f2a1af7 100644 --- a/src/main/java/net/tfminecraft/rpcharacters/managers/CommandManager.java +++ b/src/main/java/net/tfminecraft/rpcharacters/managers/CommandManager.java @@ -124,11 +124,6 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String RPTexts.send(sender, RPTexts.ERROR + "Usage: /rpcharacter pending sync"); return true; } - if (Cache.devCharacters) { - RPTexts.send(sender, RPTexts.ERROR + "Website character sync is off while " - + RPTexts.WARN + "dev-characters" + RPTexts.ERROR + " is enabled."); - return true; - } RPTexts.send(sender, RPTexts.COMMAND + "Pulling pending web character creates…"); net.tfminecraft.rpcharacters.ingest.CharacterIngestService.forcePullAsync(RPCharacters.plugin); RPTexts.send(sender, RPTexts.SUCCESS + "Pending sync started (see console for results)."); diff --git a/src/main/java/net/tfminecraft/rpcharacters/objects/RPCharacter.java b/src/main/java/net/tfminecraft/rpcharacters/objects/RPCharacter.java index a81f02d..e9db746 100644 --- a/src/main/java/net/tfminecraft/rpcharacters/objects/RPCharacter.java +++ b/src/main/java/net/tfminecraft/rpcharacters/objects/RPCharacter.java @@ -70,8 +70,6 @@ public class RPCharacter { private String slug; private boolean hidden; private boolean mailListed = true; - /** Throwaway character made in-game while dev-characters is on. */ - private boolean dev; /** Per grant-kit id. Empty/missing kit = legacy never claim for that kit. */ private final Map kitStatuses = new HashMap<>(); @@ -555,14 +553,6 @@ public void setHidden(boolean hidden) { this.hidden = hidden; } - public boolean isDev() { - return dev; - } - - public void setDev(boolean dev) { - this.dev = dev; - } - public Map getKitStatuses() { return kitStatuses; } diff --git a/src/main/java/net/tfminecraft/rpcharacters/utils/CommandTabCompleter.java b/src/main/java/net/tfminecraft/rpcharacters/utils/CommandTabCompleter.java index 82268e4..5bf5246 100644 --- a/src/main/java/net/tfminecraft/rpcharacters/utils/CommandTabCompleter.java +++ b/src/main/java/net/tfminecraft/rpcharacters/utils/CommandTabCompleter.java @@ -110,7 +110,7 @@ public List onTabComplete(CommandSender sender, Command cmd, String labe return filter(List.of("sync"), args[1]); } if (args[0].equalsIgnoreCase("wipe") && Permissions.isAdmin(sender)) { - return filter(List.of("website", "tagged"), args[1]); + return filter(List.of("website"), args[1]); } if (args[0].equalsIgnoreCase("stage") && Permissions.isAdmin(sender)) { return filter(List.of("preview"), args[1]); @@ -193,7 +193,7 @@ public List onTabComplete(CommandSender sender, Command cmd, String labe } } } else if (args[0].equalsIgnoreCase("wipe") && Permissions.isAdmin(sender) - && (args[1].equalsIgnoreCase("website") || args[1].equalsIgnoreCase("tagged"))) { + && args[1].equalsIgnoreCase("website")) { completions.add("confirm"); } else if (args[0].equalsIgnoreCase("discordgate") && Permissions.isAdmin(sender)) { completions.add("on"); diff --git a/src/main/java/net/tfminecraft/rpcharacters/wipe/CharacterWipeService.java b/src/main/java/net/tfminecraft/rpcharacters/wipe/CharacterWipeService.java deleted file mode 100644 index 324033f..0000000 --- a/src/main/java/net/tfminecraft/rpcharacters/wipe/CharacterWipeService.java +++ /dev/null @@ -1,191 +0,0 @@ -package net.tfminecraft.rpcharacters.wipe; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -import java.util.function.Predicate; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import net.tfminecraft.rpcharacters.RPCharacters; -import net.tfminecraft.rpcharacters.database.Database; -import net.tfminecraft.rpcharacters.managers.PlayerManager; -import net.tfminecraft.rpcharacters.objects.PlayerData; -import net.tfminecraft.rpcharacters.objects.RPCharacter; -import net.tfminecraft.rpcharacters.enums.Status; -import net.tfminecraft.rpcharacters.ingest.RosterSyncService; -import net.tfminecraft.rpcharacters.mail.MailRecipientDirectory; -import net.tfminecraft.rpcharacters.wardrobe.WardrobeService; - -/** - * Hard-deletes characters from RPCharacters data. Unlike permakill this removes the - * character record instead of marking it dead. - */ -public final class CharacterWipeService { - - private static final String CHARACTER_DATA_PATH = "plugins/RPCharacters/data/characterdata"; - - private static final Database DB = new Database(); - - private CharacterWipeService() {} - - public static final class WipeResult { - public final int playersTouched; - public final int charactersDeleted; - public final List deletedIds; - - private WipeResult(int playersTouched, List deletedIds) { - this.playersTouched = playersTouched; - this.charactersDeleted = deletedIds.size(); - this.deletedIds = List.copyOf(deletedIds); - } - } - - /** Main thread only: activating a replacement syncs MMOCore and fires lifecycle events. */ - public static WipeResult wipeTagged() { - return wipe(RPCharacter::isDev); - } - - /** Read-only count of tagged characters on disk, for the wipe confirm prompt. */ - public static int countTagged() { - int tagged = 0; - File[] ownerDirs = new File(CHARACTER_DATA_PATH).listFiles(); - if (ownerDirs == null) { - return 0; - } - for (File ownerDir : ownerDirs) { - if (ownerDir == null || !ownerDir.isDirectory()) { - continue; - } - UUID uuid = parseUuid(ownerDir.getName()); - if (uuid == null) { - continue; - } - PlayerData pd = resolvePlayerData(uuid); - if (pd == null) { - continue; - } - for (RPCharacter c : pd.getCharacters()) { - if (c != null && c.getId() != null && c.isDev()) { - tagged++; - } - } - } - return tagged; - } - - private static WipeResult wipe(Predicate doomedTest) { - List deletedIds = new ArrayList<>(); - int playersTouched = 0; - File[] ownerDirs = new File(CHARACTER_DATA_PATH).listFiles(); - if (ownerDirs == null) { - return new WipeResult(0, deletedIds); - } - for (File ownerDir : ownerDirs) { - if (ownerDir == null || !ownerDir.isDirectory()) { - continue; - } - UUID uuid = parseUuid(ownerDir.getName()); - if (uuid == null) { - continue; - } - if (wipeOwner(uuid, doomedTest, deletedIds) > 0) { - playersTouched++; - } - } - return new WipeResult(playersTouched, deletedIds); - } - - private static PlayerData resolvePlayerData(UUID uuid) { - PlayerData pd = PlayerManager.get(uuid); - if (pd != null) { - return pd; - } - pd = DB.loadPlayerData(uuid); - if (pd == null) { - return null; - } - if (pd.getCharacters().isEmpty()) { - // No playerdata file, so loadPlayerData returned a blank record without characters. - DB.loadCharacters(pd); - } - return pd; - } - - private static int wipeOwner(UUID uuid, Predicate doomedTest, List deletedIds) { - PlayerData pd = resolvePlayerData(uuid); - if (pd == null) { - return 0; - } - - List doomed = new ArrayList<>(); - for (RPCharacter c : pd.getCharacters()) { - if (c != null && c.getId() != null && doomedTest.test(c)) { - doomed.add(c); - } - } - if (doomed.isEmpty()) { - return 0; - } - - boolean activeRemoved = false; - for (RPCharacter c : doomed) { - if (Boolean.TRUE.equals(c.isActive())) { - activeRemoved = true; - } - pd.getCharacters().remove(c); - boolean deleted = deleteCharacterFile(uuid, c.getId()); - MailRecipientDirectory.remove(c.getId()); - if (deleted) { - net.tfminecraft.rpcharacters.playtime.CharacterPlaytimeDirectory.remove(uuid, c.getId()); - } - deletedIds.add(c.getId()); - } - - Player online = Bukkit.getPlayer(uuid); - boolean replaced = false; - if (activeRemoved && online != null) { - // Offline owners are left without an active character: activate() needs a live - // owner for MMOCore. They hit the no-character freeze and pick from the menu. - List alive = pd.getCharacters(Status.ALIVE); - if (!alive.isEmpty()) { - pd.setActiveCharacter(alive.get(0)); - replaced = true; - } - } - - DB.savePlayer(pd); - - if (online != null) { - if (replaced) { - WardrobeService.refreshActiveAsync(online); - } - RPCharacters.getPlayerManager().reevaluateFreeze(online); - RosterSyncService.pushRosterForPlayer(online); - } - return doomed.size(); - } - - private static boolean deleteCharacterFile(UUID uuid, String characterId) { - File file = new File(new File(CHARACTER_DATA_PATH, uuid.toString()), characterId + ".json"); - if (!file.exists() || file.delete()) { - return true; - } - if (RPCharacters.plugin != null) { - RPCharacters.plugin.getLogger().warning( - "[wipe] could not delete character file " + file.getPath() - ); - } - return false; - } - - private static UUID parseUuid(String raw) { - try { - return UUID.fromString(raw); - } catch (IllegalArgumentException e) { - return null; - } - } -} diff --git a/src/main/java/net/tfminecraft/rpcharacters/wipe/WipeCommand.java b/src/main/java/net/tfminecraft/rpcharacters/wipe/WipeCommand.java index 94a993f..916fb65 100644 --- a/src/main/java/net/tfminecraft/rpcharacters/wipe/WipeCommand.java +++ b/src/main/java/net/tfminecraft/rpcharacters/wipe/WipeCommand.java @@ -1,14 +1,12 @@ package net.tfminecraft.rpcharacters.wipe; import java.util.HashMap; -import java.util.List; import java.util.Locale; import java.util.Map; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; -import net.tfminecraft.rpcharacters.Cache; import net.tfminecraft.rpcharacters.Permissions; import net.tfminecraft.rpcharacters.RPCharacters; import net.tfminecraft.rpcharacters.utils.RPTexts; @@ -16,86 +14,65 @@ import net.tfminecraft.rpcharacters.api.ProvinceSystemClient; /** - * {@code /rpcharacter wipe website|tagged [confirm]}. Admin only, console allowed. + * {@code /rpcharacter wipe website [confirm]}. Admin only, console allowed. * The first call arms a confirm for that sender; the confirm expires after 30s. */ public final class WipeCommand { private static final String WEBSITE = "website"; - private static final String TAGGED = "tagged"; private static final long CONFIRM_TTL_MS = 30_000L; - private static final String USAGE = "Usage: /rpcharacter wipe [confirm]"; + private static final String USAGE = "Usage: /rpcharacter wipe website [confirm]"; - private static final Map PENDING = new HashMap<>(); + /** Sender key to confirm expiry time. */ + private static final Map PENDING = new HashMap<>(); private WipeCommand() {} - private static final class Pending { - private final String action; - private final long expiresAtMs; - - private Pending(String action, long expiresAtMs) { - this.action = action; - this.expiresAtMs = expiresAtMs; - } - } - public static boolean handle(CommandSender sender, String[] args) { if (!Permissions.isAdmin(sender)) { RPTexts.send(sender, RPTexts.ERROR + "You do not have permission to use this command."); return true; } - String action = args.length >= 2 ? args[1].toLowerCase(Locale.ROOT) : ""; - if (!WEBSITE.equals(action) && !TAGGED.equals(action)) { + if (args.length < 2 || !WEBSITE.equalsIgnoreCase(args[1])) { RPTexts.send(sender, RPTexts.ERROR + USAGE); return true; } if (args.length == 2) { - return prelude(sender, action); + return prelude(sender); } if (args.length != 3 || !args[2].equalsIgnoreCase("confirm")) { RPTexts.send(sender, RPTexts.ERROR + USAGE); return true; } - if (!takeConfirm(sender, action)) { + if (!takeConfirm(sender)) { return true; } - if (WEBSITE.equals(action)) { - return wipeWebsite(sender); - } - return wipeTagged(sender); + return wipeWebsite(sender); } - private static boolean prelude(CommandSender sender, String action) { - if (WEBSITE.equals(action)) { - String realm = GatewayClient.realmId(); - if (realm == null) { - RPTexts.send(sender, RPTexts.ERROR + "Could not read the realm id from TFMCWeb. Website wipe aborted."); - return true; - } - RPTexts.send(sender, RPTexts.WARN + "Website wipe target: realm " + RPTexts.ACCENT + realm + RPTexts.WARN + "."); - RPTexts.send(sender, RPTexts.ERROR - + "This deletes every website character row for this realm, including pending donor creates."); - } else { - RPTexts.send(sender, RPTexts.WARN + "Tagged characters on disk: " - + RPTexts.ACCENT + CharacterWipeService.countTagged() + RPTexts.WARN + "."); + private static boolean prelude(CommandSender sender) { + String realm = GatewayClient.realmId(); + if (realm == null) { + RPTexts.send(sender, RPTexts.ERROR + "Could not read the realm id from TFMCWeb. Website wipe aborted."); + return true; } - PENDING.put(key(sender), new Pending(action, System.currentTimeMillis() + CONFIRM_TTL_MS)); - RPTexts.send(sender, RPTexts.COMMAND + "Type /rpcharacter wipe " + action + " confirm" + RPTexts.send(sender, RPTexts.WARN + "Website wipe target: realm " + RPTexts.ACCENT + realm + RPTexts.WARN + "."); + RPTexts.send(sender, RPTexts.ERROR + + "This deletes every website character row for this realm, including pending donor creates."); + PENDING.put(key(sender), System.currentTimeMillis() + CONFIRM_TTL_MS); + RPTexts.send(sender, RPTexts.COMMAND + "Type /rpcharacter wipe website confirm" + RPTexts.WARN + " within 30 seconds."); return true; } - /** True when this sender armed this exact action and it has not expired. */ - private static boolean takeConfirm(CommandSender sender, String action) { - String key = key(sender); - Pending pending = PENDING.get(key); - if (pending == null || !pending.action.equals(action)) { + /** True when this sender armed the wipe and it has not expired. */ + private static boolean takeConfirm(CommandSender sender) { + Long expiresAtMs = PENDING.remove(key(sender)); + if (expiresAtMs == null) { RPTexts.send(sender, RPTexts.ERROR + "Nothing to confirm."); return false; } - PENDING.remove(key); - if (System.currentTimeMillis() > pending.expiresAtMs) { + if (System.currentTimeMillis() > expiresAtMs) { RPTexts.send(sender, RPTexts.ERROR + "Confirm expired. Run the wipe command again."); return false; } @@ -129,41 +106,6 @@ private static boolean wipeWebsite(CommandSender sender) { return true; } - private static boolean wipeTagged(CommandSender sender) { - CharacterWipeService.WipeResult result = CharacterWipeService.wipeTagged(); - if (result.charactersDeleted == 0) { - RPTexts.send(sender, RPTexts.WARN + "No tagged characters found."); - return true; - } - RPTexts.send(sender, RPTexts.SUCCESS + "Deleted " + result.charactersDeleted - + " tagged character(s) across " + result.playersTouched + " player(s)."); - if (Cache.devCharacters) { - // Roster push is off while the flag is on, so the site has no mirror to clean. - return true; - } - String realm = GatewayClient.realmId(); - if (realm == null) { - RPTexts.send(sender, RPTexts.MUTED + "Skipped website cleanup: realm id unavailable."); - return true; - } - List ids = result.deletedIds; - Bukkit.getScheduler().runTaskAsynchronously(RPCharacters.plugin, () -> { - ProvinceSystemClient.SimpleResult cleanup = - ProvinceSystemClient.deleteCharacters(realm, ids); - if (cleanup.ok) { - return; - } - RPCharacters.plugin.getLogger().warning( - "[wipe] website cleanup failed for " + ids.size() + " id(s): " + cleanup.error - ); - Bukkit.getScheduler().runTask(RPCharacters.plugin, () -> RPTexts.send( - sender, - RPTexts.MUTED + "Website cleanup for those ids failed: " + cleanup.error - )); - }); - return true; - } - private static String key(CommandSender sender) { return sender.getName().toLowerCase(Locale.ROOT); } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 16784e1..cba3f94 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -37,10 +37,6 @@ base-profession-factor: -10 require-character: true -# When true: in-game creates are tagged as dev, and this server does not pull -# website characters. Keep false on a live world. -dev-characters: false - # Verbose RPCharacters feedback when /mmocore admin skill-points give|set runs (sync breakdown, stripped skills). skill-points-admin-debug-messages: false diff --git a/src/test/java/net/tfminecraft/rpcharacters/wipe/CharacterWipeDeletionTest.java b/src/test/java/net/tfminecraft/rpcharacters/wipe/CharacterWipeDeletionTest.java deleted file mode 100644 index b626f3b..0000000 --- a/src/test/java/net/tfminecraft/rpcharacters/wipe/CharacterWipeDeletionTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package net.tfminecraft.rpcharacters.wipe; - -import static org.junit.jupiter.api.Assertions.*; - -import java.lang.reflect.Method; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.UUID; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -class CharacterWipeDeletionTest { - private final UUID owner = UUID.randomUUID(); - private Path folder; - private Path file; - - @BeforeEach - void prepare() throws Exception { - folder = Files.createDirectories(Path.of("plugins/RPCharacters/data/characterdata", owner.toString())); - file = folder.resolve("test.json"); - } - - @AfterEach - void cleanup() throws Exception { - if (Files.isDirectory(file)) Files.deleteIfExists(file.resolve("blocking-child")); - Files.deleteIfExists(file); - Files.deleteIfExists(folder); - } - - private Object delete() throws Exception { - Method method = CharacterWipeService.class.getDeclaredMethod("deleteCharacterFile", UUID.class, String.class); - method.setAccessible(true); - return method.invoke(null, owner, "test"); - } - - @Test - void missingFileCountsAsAlreadyDeleted() throws Exception { - assertEquals(Boolean.TRUE, delete()); - } - - @Test - void existingFileReportsSuccessfulDeletion() throws Exception { - Files.writeString(file, "{}"); - assertEquals(Boolean.TRUE, delete()); - assertFalse(Files.exists(file)); - } - - @Test - void failedDeletionReportsFailureAndKeepsTheFile() throws Exception { - Files.createDirectory(file); - Files.writeString(file.resolve("blocking-child"), "preserve"); - assertEquals(Boolean.FALSE, delete()); - assertTrue(Files.exists(file.resolve("blocking-child"))); - } -}