From aab4f5634e6f4ff0c609d5c196773b16d43e2b93 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 4 Jul 2026 16:14:53 -0700 Subject: [PATCH 1/4] Add admin command to reset a player's generator data (#149) Adds "/{admin} generator reset ", a confirmable admin subcommand that resets a single island's generator data (unlocked, purchased and active generators) without touching the rest of the database. - StoneGeneratorManager.resetIslandData wipes the island's stored data and recreates a fresh default data object so the island keeps working. - New ResetCommand subcommand (permission admin.stone-generator.reset) with player tab-completion and a confirmation prompt. - New en-US locale strings for the command and the success message. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01D7NWPeGXmsUJnnX42X24Rd --- .../commands/admin/GeneratorAdminCommand.java | 95 +++++++++++++++++++ .../managers/StoneGeneratorManager.java | 12 +++ src/main/resources/locales/en-US.yml | 6 ++ .../admin/GeneratorAdminCommandTest.java | 41 +++++++- .../managers/StoneGeneratorManagerTest.java | 8 ++ 5 files changed, 161 insertions(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/magiccobblestonegenerator/commands/admin/GeneratorAdminCommand.java b/src/main/java/world/bentobox/magiccobblestonegenerator/commands/admin/GeneratorAdminCommand.java index 7beb633..7b870db 100644 --- a/src/main/java/world/bentobox/magiccobblestonegenerator/commands/admin/GeneratorAdminCommand.java +++ b/src/main/java/world/bentobox/magiccobblestonegenerator/commands/admin/GeneratorAdminCommand.java @@ -64,6 +64,7 @@ public void setup() new ImportCommand(this.getAddon(), this); new GeneratorWhyCommand(this.getAddon(), this); new GeneratorDatabaseCommand(this.getAddon(), this); + new ResetCommand(this.getAddon(), this); } @@ -308,4 +309,98 @@ public Optional> tabComplete(User user, String alias, List } } } + + + /** + * This command resets a single player's island generator data (unlocked, purchased and active generators) without + * touching the rest of the database. Requires confirmation as it is destructive. + */ + private static class ResetCommand extends ConfirmableCommand + { + /** + * This is simple constructor for initializing /{admin_command} generator reset command. + * + * @param addon StoneGeneratorAddon addon. + * @param parentCommand Parent Command where we hook our command into. + */ + public ResetCommand(StoneGeneratorAddon addon, CompositeCommand parentCommand) + { + super(addon, parentCommand, "reset"); + } + + + @Override + public void setup() + { + this.setPermission("admin.stone-generator.reset"); + this.setParametersHelp(Constants.ADMIN_COMMANDS + "reset.parameters"); + this.setDescription(Constants.ADMIN_COMMANDS + "reset.description"); + + this.setOnlyPlayer(false); + } + + + @Override + public boolean execute(User user, String label, List args) + { + // If args are not right, show help + if (args.size() != 1) + { + this.showHelp(this, user); + return false; + } + + // Get target + UUID targetUUID = Util.getUUID(args.get(0)); + + if (targetUUID == null) + { + Utils.sendMessage(user, + user.getTranslation("general.errors.unknown-player", + TextVariables.NAME, args.get(0))); + return false; + } + + Island island = this.getAddon().getIslands().getIsland(this.getWorld(), targetUUID); + + if (island == null) + { + Utils.sendMessage(user, user.getTranslation("general.errors.player-has-no-island")); + return false; + } + + final String targetName = this.getPlayers().getName(targetUUID); + + this.askConfirmation(user, + user.getTranslation(Constants.CONVERSATIONS + "prefix") + + user.getTranslation(Constants.ADMIN_COMMANDS + "reset.confirmation", + Constants.PLAYER, targetName), + () -> + { + this.getAddon().getAddonManager().resetIslandData(island); + Utils.sendMessage(user, + user.getTranslation(Constants.MESSAGES + "generator-data-reset", + Constants.PLAYER, targetName)); + }); + + return true; + } + + + @Override + public Optional> tabComplete(User user, String alias, List args) + { + if (args.isEmpty()) + { + // Don't show every player on the server. Require at least the first letter + return Optional.empty(); + } + else + { + return Optional.of(Util.tabLimit( + new ArrayList<>(Util.getOnlinePlayerList(user)), + args.get(args.size() - 1))); + } + } + } } diff --git a/src/main/java/world/bentobox/magiccobblestonegenerator/managers/StoneGeneratorManager.java b/src/main/java/world/bentobox/magiccobblestonegenerator/managers/StoneGeneratorManager.java index bfd3e1e..f61c4ee 100644 --- a/src/main/java/world/bentobox/magiccobblestonegenerator/managers/StoneGeneratorManager.java +++ b/src/main/java/world/bentobox/magiccobblestonegenerator/managers/StoneGeneratorManager.java @@ -1448,6 +1448,18 @@ public void wipeGeneratorData(GeneratorDataObject dataObject) { this.wipeGeneratorData(dataObject.getUniqueId()); } + /** + * This method resets all generator data for the given island: its stored data is removed and a fresh default data + * object is recreated. Unlocked, purchased and active generators are cleared (#149). + * + * @param island Island whose generator data must be reset. + */ + public void resetIslandData(@NotNull Island island) { + this.wipeGeneratorData(island.getUniqueId()); + // Recreate a fresh, default data object so the island keeps working immediately. + this.validateIslandData(island); + } + // --------------------------------------------------------------------- // Section: Methods // --------------------------------------------------------------------- diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 88714fe..2570868 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -11,6 +11,10 @@ stone-generator: why: parameters: "" description: "toggles Magic Cobblestone Generator debug messages" + reset: + parameters: "" + description: "resets a player's island generator data" + confirmation: "This will reset [player]'s generator data (unlocked, purchased and active generators) - please confirm" database: description: "Main database command" import-database: @@ -999,6 +1003,8 @@ stone-generator: generator-exhausted: "Generator '[generator]' has reached its generation limit and is now on cooldown for [number] more minute(s)." # Message that appears when purchasing the generator. generator-purchased: "Generator '[generator]' is purchased." + # Message that appears after an admin resets a player's generator data. + generator-data-reset: "Generator data for [player] has been reset." # Message that appears when trying to buy already purchased generator. generator-already-purchased: "Generator '[generator]' is already purchased." # Message that appears when trying to buy generator without required island level diff --git a/src/test/java/world/bentobox/magiccobblestonegenerator/commands/admin/GeneratorAdminCommandTest.java b/src/test/java/world/bentobox/magiccobblestonegenerator/commands/admin/GeneratorAdminCommandTest.java index e4f6dae..e75d739 100644 --- a/src/test/java/world/bentobox/magiccobblestonegenerator/commands/admin/GeneratorAdminCommandTest.java +++ b/src/test/java/world/bentobox/magiccobblestonegenerator/commands/admin/GeneratorAdminCommandTest.java @@ -41,6 +41,7 @@ class GeneratorAdminCommandTest extends CommonTestSetup { private Settings settings; private CompositeCommand ic; private CompositeCommand why; + private CompositeCommand reset; @Override @BeforeEach @@ -78,6 +79,7 @@ public void setUp() throws Exception { gac = new GeneratorAdminCommand(addon, ac); ic = gac.getSubCommand("import").get(); why = gac.getSubCommand("why").get(); + reset = gac.getSubCommand("reset").get(); } @Test @@ -130,7 +132,7 @@ void testExecuteUserStringListOfStringHelp() { "stone-generator.commands.admin.main.parameters"); verify(user).getTranslation( "stone-generator.commands.admin.main.description"); - verify(user, times(4)).isPlayer(); + verify(user, times(5)).isPlayer(); verify(user).sendMessage( "commands.help.syntax-no-parameters", "[usage]", @@ -155,6 +157,12 @@ void testExecuteUserStringListOfStringHelp() { "/null generator database", "[description]", "stone-generator.commands.admin.database.description"); + verify(user).sendMessage( + "commands.help.syntax-no-parameters", + "[usage]", + "/null generator reset", + "[description]", + "stone-generator.commands.admin.reset.description"); verify(user).sendMessage( "commands.help.end"); } @@ -181,4 +189,35 @@ void testExecuteUserStringListOfStringWhyPlayer() { "stone-generator.conversations.prefixgeneral.errors.player-is-not-owner"); } + @Test + void testSetupReset() { + assertEquals("admin.stone-generator.reset", reset.getPermission()); + assertEquals("stone-generator.commands.admin.reset.parameters", reset.getParameters()); + assertEquals("stone-generator.commands.admin.reset.description", reset.getDescription()); + assertFalse(reset.isOnlyPlayer()); + } + + @Test + void testExecuteResetNoArgs() { + assertFalse(reset.execute(user, "bskyblock", List.of())); + verify(user).sendMessage( + "commands.help.header", + "[label]", + "BSkyBlock World"); + verify(user).getTranslationOrNothing( + "stone-generator.commands.admin.reset.parameters"); + verify(user).getTranslation( + "stone-generator.commands.admin.reset.description"); + verify(user).sendMessage( + "commands.help.end"); + } + + @Test + void testExecuteResetPlayerNoIsland() { + // Target resolves to a UUID but has no island in this world. + assertFalse(reset.execute(user, "bskyblock", List.of("tastybento"))); + verify(user).sendMessage( + "stone-generator.conversations.prefixgeneral.errors.player-has-no-island"); + } + } diff --git a/src/test/java/world/bentobox/magiccobblestonegenerator/managers/StoneGeneratorManagerTest.java b/src/test/java/world/bentobox/magiccobblestonegenerator/managers/StoneGeneratorManagerTest.java index 8f3b76a..abfa428 100644 --- a/src/test/java/world/bentobox/magiccobblestonegenerator/managers/StoneGeneratorManagerTest.java +++ b/src/test/java/world/bentobox/magiccobblestonegenerator/managers/StoneGeneratorManagerTest.java @@ -710,6 +710,14 @@ void testWipeGeneratorDataString() { assertDoesNotThrow(() -> sgm.wipeGeneratorData(uuid.toString())); } + @Test + void testResetIslandData() { + when(island.getUniqueId()).thenReturn("island-149"); + assertDoesNotThrow(() -> sgm.resetIslandData(island)); + // The island's stored data is deleted as part of the reset. + verify(h).deleteID("island-149"); + } + @Test void testWipeGeneratorDataGeneratorDataObject() { assertDoesNotThrow(() -> sgm.wipeGeneratorData(generatorData)); From a24e91d7f80e812ae80805c872c8aa67d5fe0a51 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 4 Jul 2026 16:52:04 -0700 Subject: [PATCH 2/4] Dedupe admin subcommands and address review feedback (#149) SonarCloud flagged the new ResetCommand as duplicating GeneratorWhyCommand (identical player tab-completion and target-resolution). Extract two shared static helpers in GeneratorAdminCommand - playerTabComplete and resolveTargetUUID - and use them from both subcommands, removing the duplicated block. Review feedback: - resetIslandData now recreates the data object via addIslandData, which works even for ownerless islands (e.g. spawn), where validateIslandData returns early; validateIslandData is still called for owned islands. This ensures reset never deletes data without recreating it. - The reset command falls back to the typed argument for the player name when the server cannot resolve a name for the UUID, so confirmation and success messages are never blank. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01D7NWPeGXmsUJnnX42X24Rd --- .../commands/admin/GeneratorAdminCommand.java | 86 ++++++++++++------- .../managers/StoneGeneratorManager.java | 10 ++- 2 files changed, 63 insertions(+), 33 deletions(-) diff --git a/src/main/java/world/bentobox/magiccobblestonegenerator/commands/admin/GeneratorAdminCommand.java b/src/main/java/world/bentobox/magiccobblestonegenerator/commands/admin/GeneratorAdminCommand.java index 7b870db..b2f042c 100644 --- a/src/main/java/world/bentobox/magiccobblestonegenerator/commands/admin/GeneratorAdminCommand.java +++ b/src/main/java/world/bentobox/magiccobblestonegenerator/commands/admin/GeneratorAdminCommand.java @@ -93,6 +93,54 @@ public boolean execute(User user, String label, List args) } + // --------------------------------------------------------------------- + // Section: Shared helpers + // --------------------------------------------------------------------- + + + /** + * Shared tab-completer for subcommands that take a single player name argument. Online player names are only + * suggested once at least one character has been typed. + * + * @param user the user completing the command. + * @param args the current command arguments. + * @return the list of matching online player names. + */ + private static Optional> playerTabComplete(User user, List args) + { + if (args.isEmpty()) + { + // Don't show every player on the server. Require at least the first letter + return Optional.empty(); + } + + return Optional.of(Util.tabLimit( + new ArrayList<>(Util.getOnlinePlayerList(user)), + args.get(args.size() - 1))); + } + + + /** + * Resolves the target player UUID from the given name and messages the user if it cannot be resolved. + * + * @param user the user running the command. + * @param name the player name argument. + * @return the resolved UUID, or null if it could not be resolved. + */ + private static UUID resolveTargetUUID(User user, String name) + { + UUID targetUUID = Util.getUUID(name); + + if (targetUUID == null) + { + Utils.sendMessage(user, + user.getTranslation("general.errors.unknown-player", TextVariables.NAME, name)); + } + + return targetUUID; + } + + // --------------------------------------------------------------------- // Section: Subcommadns // --------------------------------------------------------------------- @@ -221,13 +269,10 @@ public boolean execute(User user, String label, List args) } // Get target - UUID targetUUID = Util.getUUID(args.get(0)); + UUID targetUUID = resolveTargetUUID(user, args.get(0)); if (targetUUID == null) { - Utils.sendMessage(user, - user.getTranslation("general.errors.unknown-player", - TextVariables.NAME, args.get(0))); return false; } @@ -296,17 +341,7 @@ public boolean execute(User user, String label, List args) @Override public Optional> tabComplete(User user, String alias, List args) { - if (args.isEmpty()) - { - // Don't show every player on the server. Require at least the first letter - return Optional.empty(); - } - else - { - return Optional.of(Util.tabLimit( - new ArrayList<>(Util.getOnlinePlayerList(user)), - args.get(args.size() - 1))); - } + return playerTabComplete(user, args); } } @@ -351,13 +386,10 @@ public boolean execute(User user, String label, List args) } // Get target - UUID targetUUID = Util.getUUID(args.get(0)); + UUID targetUUID = resolveTargetUUID(user, args.get(0)); if (targetUUID == null) { - Utils.sendMessage(user, - user.getTranslation("general.errors.unknown-player", - TextVariables.NAME, args.get(0))); return false; } @@ -369,7 +401,9 @@ public boolean execute(User user, String label, List args) return false; } - final String targetName = this.getPlayers().getName(targetUUID); + // Fall back to the typed argument if the server cannot resolve a name for the UUID. + final String resolvedName = this.getPlayers().getName(targetUUID); + final String targetName = resolvedName == null || resolvedName.isEmpty() ? args.get(0) : resolvedName; this.askConfirmation(user, user.getTranslation(Constants.CONVERSATIONS + "prefix") + @@ -390,17 +424,7 @@ public boolean execute(User user, String label, List args) @Override public Optional> tabComplete(User user, String alias, List args) { - if (args.isEmpty()) - { - // Don't show every player on the server. Require at least the first letter - return Optional.empty(); - } - else - { - return Optional.of(Util.tabLimit( - new ArrayList<>(Util.getOnlinePlayerList(user)), - args.get(args.size() - 1))); - } + return playerTabComplete(user, args); } } } diff --git a/src/main/java/world/bentobox/magiccobblestonegenerator/managers/StoneGeneratorManager.java b/src/main/java/world/bentobox/magiccobblestonegenerator/managers/StoneGeneratorManager.java index f61c4ee..f02dbc5 100644 --- a/src/main/java/world/bentobox/magiccobblestonegenerator/managers/StoneGeneratorManager.java +++ b/src/main/java/world/bentobox/magiccobblestonegenerator/managers/StoneGeneratorManager.java @@ -1456,8 +1456,14 @@ public void wipeGeneratorData(GeneratorDataObject dataObject) { */ public void resetIslandData(@NotNull Island island) { this.wipeGeneratorData(island.getUniqueId()); - // Recreate a fresh, default data object so the island keeps working immediately. - this.validateIslandData(island); + // Recreate a fresh, default data object so the island keeps working immediately. addIslandData works even + // for ownerless islands (e.g. spawn), unlike validateIslandData which returns early when there is no owner. + this.addIslandData(island); + + if (island.getOwner() != null) { + // For owned islands, also re-apply owner bundles/limits and re-evaluate unlocks. + this.validateIslandData(island); + } } // --------------------------------------------------------------------- From ed109d0a6193dc11a0fd0eaa300e20cd4c6c408a Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 4 Jul 2026 17:06:16 -0700 Subject: [PATCH 3/4] Extract PlayerTargetCommand base to remove subcommand duplication (#149) The static helpers only removed part of the duplication SonarCloud flagged between ResetCommand and GeneratorWhyCommand; the identical execute preamble (arg check + UUID resolution) and tab-completion remained. Introduce an abstract PlayerTargetCommand base (extends ConfirmableCommand) that handles argument validation, target-UUID resolution and player tab-completion, and delegates to an abstract executeForTarget(user, name, uuid). GeneratorWhyCommand and ResetCommand now extend it and only implement setup() and their own executeForTarget, removing the duplicated block. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01D7NWPeGXmsUJnnX42X24Rd --- .../commands/admin/GeneratorAdminCommand.java | 171 ++++++------------ 1 file changed, 57 insertions(+), 114 deletions(-) diff --git a/src/main/java/world/bentobox/magiccobblestonegenerator/commands/admin/GeneratorAdminCommand.java b/src/main/java/world/bentobox/magiccobblestonegenerator/commands/admin/GeneratorAdminCommand.java index b2f042c..5c51a0a 100644 --- a/src/main/java/world/bentobox/magiccobblestonegenerator/commands/admin/GeneratorAdminCommand.java +++ b/src/main/java/world/bentobox/magiccobblestonegenerator/commands/admin/GeneratorAdminCommand.java @@ -94,56 +94,72 @@ public boolean execute(User user, String label, List args) // --------------------------------------------------------------------- - // Section: Shared helpers + // Section: Subcommands // --------------------------------------------------------------------- /** - * Shared tab-completer for subcommands that take a single player name argument. Online player names are only - * suggested once at least one character has been typed. - * - * @param user the user completing the command. - * @param args the current command arguments. - * @return the list of matching online player names. + * Base class for admin subcommands that take a single player name argument. It validates the argument, resolves the + * target player UUID and provides player name tab-completion, delegating the actual work to + * {@link #executeForTarget(User, String, UUID)}. */ - private static Optional> playerTabComplete(User user, List args) + private abstract static class PlayerTargetCommand extends ConfirmableCommand { - if (args.isEmpty()) + protected PlayerTargetCommand(StoneGeneratorAddon addon, CompositeCommand parentCommand, String label) { - // Don't show every player on the server. Require at least the first letter - return Optional.empty(); + super(addon, parentCommand, label); } - return Optional.of(Util.tabLimit( - new ArrayList<>(Util.getOnlinePlayerList(user)), - args.get(args.size() - 1))); - } + @Override + public boolean execute(User user, String label, List args) + { + // If args are not right, show help + if (args.size() != 1) + { + this.showHelp(this, user); + return false; + } - /** - * Resolves the target player UUID from the given name and messages the user if it cannot be resolved. - * - * @param user the user running the command. - * @param name the player name argument. - * @return the resolved UUID, or null if it could not be resolved. - */ - private static UUID resolveTargetUUID(User user, String name) - { - UUID targetUUID = Util.getUUID(name); + // Get target + UUID targetUUID = Util.getUUID(args.get(0)); - if (targetUUID == null) - { - Utils.sendMessage(user, - user.getTranslation("general.errors.unknown-player", TextVariables.NAME, name)); + if (targetUUID == null) + { + Utils.sendMessage(user, + user.getTranslation("general.errors.unknown-player", TextVariables.NAME, args.get(0))); + return false; + } + + return this.executeForTarget(user, args.get(0), targetUUID); } - return targetUUID; - } + /** + * Runs the subcommand for the resolved target player. + * + * @param user the user running the command. + * @param targetName the player name argument as typed. + * @param targetUUID the resolved target player UUID. + * @return {@code true} if the command executed successfully. + */ + protected abstract boolean executeForTarget(User user, String targetName, UUID targetUUID); - // --------------------------------------------------------------------- - // Section: Subcommadns - // --------------------------------------------------------------------- + + @Override + public Optional> tabComplete(User user, String alias, List args) + { + if (args.isEmpty()) + { + // Don't show every player on the server. Require at least the first letter + return Optional.empty(); + } + + return Optional.of(Util.tabLimit( + new ArrayList<>(Util.getOnlinePlayerList(user)), + args.get(args.size() - 1))); + } + } /** @@ -213,7 +229,7 @@ public boolean execute(User user, String label, List args) * This is a debug command for admins. Admins could use to check which generator user is using and could faster find * an issue. */ - private static class GeneratorWhyCommand extends ConfirmableCommand + private static class GeneratorWhyCommand extends PlayerTargetCommand { /** * This is simple constructor for initializing /{admin_command} why generator command. @@ -227,17 +243,6 @@ public GeneratorWhyCommand(StoneGeneratorAddon addon, CompositeCommand parentCom } - /** - * Setups anything that is needed for this command.

It is recommended you do the following in this - * method: - *
    - *
  • Register any of the sub-commands of this command;
  • - *
  • Define the permission required to use this command using {@link - * CompositeCommand#setPermission(String)};
  • - *
  • Define whether this command can only be run by players or not using {@link - * CompositeCommand#setOnlyPlayer(boolean)};
  • - *
- */ @Override public void setup() { @@ -249,33 +254,9 @@ public void setup() } - /** - * Defines what will be executed when this command is run. - * - * @param user the {@link User} who is executing this command. - * @param label the label which has been used to execute this command. It can be {@link - * CompositeCommand#getLabel()} or an alias. - * @param args the command arguments. - * @return {@code true} if the command executed successfully, {@code false} otherwise. - */ @Override - public boolean execute(User user, String label, List args) + protected boolean executeForTarget(User user, String targetName, UUID targetUUID) { - // If args are not right, show help - if (args.size() != 1) - { - this.showHelp(this, user); - return false; - } - - // Get target - UUID targetUUID = resolveTargetUUID(user, args.get(0)); - - if (targetUUID == null) - { - return false; - } - // Set meta data on player Island island = this.getAddon().getIslands().getIsland(this.getWorld(), targetUUID); @@ -327,22 +308,6 @@ public boolean execute(User user, String label, List args) return true; } - - - /** - * Tab Completer for CompositeCommands. Note that any registered sub-commands will be automatically added to the - * list. Use this to add tab-complete for things like names. - * - * @param user the {@link User} who is executing this command. - * @param alias alias for command - * @param args command arguments - * @return List of strings that could be used to complete this command. - */ - @Override - public Optional> tabComplete(User user, String alias, List args) - { - return playerTabComplete(user, args); - } } @@ -350,7 +315,7 @@ public Optional> tabComplete(User user, String alias, List * This command resets a single player's island generator data (unlocked, purchased and active generators) without * touching the rest of the database. Requires confirmation as it is destructive. */ - private static class ResetCommand extends ConfirmableCommand + private static class ResetCommand extends PlayerTargetCommand { /** * This is simple constructor for initializing /{admin_command} generator reset command. @@ -376,23 +341,8 @@ public void setup() @Override - public boolean execute(User user, String label, List args) + protected boolean executeForTarget(User user, String targetName, UUID targetUUID) { - // If args are not right, show help - if (args.size() != 1) - { - this.showHelp(this, user); - return false; - } - - // Get target - UUID targetUUID = resolveTargetUUID(user, args.get(0)); - - if (targetUUID == null) - { - return false; - } - Island island = this.getAddon().getIslands().getIsland(this.getWorld(), targetUUID); if (island == null) @@ -403,28 +353,21 @@ public boolean execute(User user, String label, List args) // Fall back to the typed argument if the server cannot resolve a name for the UUID. final String resolvedName = this.getPlayers().getName(targetUUID); - final String targetName = resolvedName == null || resolvedName.isEmpty() ? args.get(0) : resolvedName; + final String displayName = resolvedName == null || resolvedName.isEmpty() ? targetName : resolvedName; this.askConfirmation(user, user.getTranslation(Constants.CONVERSATIONS + "prefix") + user.getTranslation(Constants.ADMIN_COMMANDS + "reset.confirmation", - Constants.PLAYER, targetName), + Constants.PLAYER, displayName), () -> { this.getAddon().getAddonManager().resetIslandData(island); Utils.sendMessage(user, user.getTranslation(Constants.MESSAGES + "generator-data-reset", - Constants.PLAYER, targetName)); + Constants.PLAYER, displayName)); }); return true; } - - - @Override - public Optional> tabComplete(User user, String alias, List args) - { - return playerTabComplete(user, args); - } } } From 6dc1f06de207a1cea3bf6bb8ed86c883bccb8f3a Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 4 Jul 2026 17:27:40 -0700 Subject: [PATCH 4/4] Move subcommand setup() into PlayerTargetCommand base (#149) The remaining SonarCloud duplication was the structurally identical constructor + setup() shared by GeneratorWhyCommand and ResetCommand (Sonar normalizes the differing string literals). Implement setup() once in the PlayerTargetCommand base, deriving the permission and locale keys from the subcommand label, and remove the per-command setup() overrides. The subcommands now only provide a constructor and their own executeForTarget. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01D7NWPeGXmsUJnnX42X24Rd --- .../commands/admin/GeneratorAdminCommand.java | 34 +++++++------------ 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/src/main/java/world/bentobox/magiccobblestonegenerator/commands/admin/GeneratorAdminCommand.java b/src/main/java/world/bentobox/magiccobblestonegenerator/commands/admin/GeneratorAdminCommand.java index 5c51a0a..f97c652 100644 --- a/src/main/java/world/bentobox/magiccobblestonegenerator/commands/admin/GeneratorAdminCommand.java +++ b/src/main/java/world/bentobox/magiccobblestonegenerator/commands/admin/GeneratorAdminCommand.java @@ -111,6 +111,18 @@ protected PlayerTargetCommand(StoneGeneratorAddon addon, CompositeCommand parent } + @Override + public void setup() + { + // Permission and locale references are derived from the subcommand label. + this.setPermission("admin.stone-generator." + this.getLabel()); + this.setParametersHelp(Constants.ADMIN_COMMANDS + this.getLabel() + ".parameters"); + this.setDescription(Constants.ADMIN_COMMANDS + this.getLabel() + ".description"); + + this.setOnlyPlayer(false); + } + + @Override public boolean execute(User user, String label, List args) { @@ -243,17 +255,6 @@ public GeneratorWhyCommand(StoneGeneratorAddon addon, CompositeCommand parentCom } - @Override - public void setup() - { - this.setPermission("admin.stone-generator.why"); - this.setParametersHelp(Constants.ADMIN_COMMANDS + "why.parameters"); - this.setDescription(Constants.ADMIN_COMMANDS + "why.description"); - - this.setOnlyPlayer(false); - } - - @Override protected boolean executeForTarget(User user, String targetName, UUID targetUUID) { @@ -329,17 +330,6 @@ public ResetCommand(StoneGeneratorAddon addon, CompositeCommand parentCommand) } - @Override - public void setup() - { - this.setPermission("admin.stone-generator.reset"); - this.setParametersHelp(Constants.ADMIN_COMMANDS + "reset.parameters"); - this.setDescription(Constants.ADMIN_COMMANDS + "reset.description"); - - this.setOnlyPlayer(false); - } - - @Override protected boolean executeForTarget(User user, String targetName, UUID targetUUID) {