From ef08966af4017e9b5c12219509549b2766b4d7d2 Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 10 Jul 2026 14:05:28 -0700 Subject: [PATCH] Add setting to open the challenges GUI while off-island New gui-settings option (default off) that skips only the location-on-island check when opening /challenges. Completion checks in TryToComplete are unchanged, so island protection still applies when actually completing challenges. Fixes #349 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01NKxodNE4h3TsSHMqDEeC8v --- .../commands/ChallengesPlayerCommand.java | 3 +- .../bentobox/challenges/config/Settings.java | 28 +++++++++++++++++++ .../panel/admin/EditSettingsPanel.java | 23 ++++++++++++++- src/main/resources/config.yml | 4 +++ src/main/resources/locales/en-US.yml | 8 ++++++ .../commands/ChallengesCommandTest.java | 21 ++++++++++++++ 6 files changed, 85 insertions(+), 2 deletions(-) diff --git a/src/main/java/world/bentobox/challenges/commands/ChallengesPlayerCommand.java b/src/main/java/world/bentobox/challenges/commands/ChallengesPlayerCommand.java index e0b517ad..c2bc82d4 100644 --- a/src/main/java/world/bentobox/challenges/commands/ChallengesPlayerCommand.java +++ b/src/main/java/world/bentobox/challenges/commands/ChallengesPlayerCommand.java @@ -66,10 +66,11 @@ public boolean canExecute(User user, String label, List args) Utils.sendMessage(user, this.getWorld(), "general.errors.no-island"); return false; } else if (ChallengesAddon.CHALLENGES_WORLD_PROTECTION.isSetForWorld(this.getWorld()) && + !((ChallengesAddon) this.getAddon()).getChallengesSettings().isOpenAnywhere() && !this.getIslands().locationIsOnIsland(user.getPlayer(), user.getLocation())) { // Do not open gui if player is not on the island, but challenges requires island for - // completion. + // completion, unless open-anywhere is enabled. Utils.sendMessage(user, this.getWorld(), Constants.ERRORS + "not-on-island"); return false; } diff --git a/src/main/java/world/bentobox/challenges/config/Settings.java b/src/main/java/world/bentobox/challenges/config/Settings.java index 1822c133..505d4147 100644 --- a/src/main/java/world/bentobox/challenges/config/Settings.java +++ b/src/main/java/world/bentobox/challenges/config/Settings.java @@ -115,6 +115,12 @@ public class Settings implements ConfigObject @ConfigEntry(path = "gui-settings.undeployed-view-mode") private VisibilityMode visibilityMode = VisibilityMode.VISIBLE; + @ConfigComment("") + @ConfigComment("Allow players to open the challenges GUI without being on their island.") + @ConfigComment("Note: Challenges completion still requires being on the island when world protection is enabled.") + @ConfigEntry(path = "gui-settings.open-anywhere") + private boolean openAnywhere = false; + @ConfigComment("") @ConfigComment("This allows to change default locked level icon. This option may be") @@ -712,4 +718,26 @@ public void setIncludeUndeployed(boolean includeUndeployed) { this.includeUndeployed = includeUndeployed; } + + + /** + * Is open anywhere boolean. + * + * @return the boolean + */ + public boolean isOpenAnywhere() + { + return openAnywhere; + } + + + /** + * Sets open anywhere. + * + * @param openAnywhere whether to allow opening GUI from anywhere + */ + public void setOpenAnywhere(boolean openAnywhere) + { + this.openAnywhere = openAnywhere; + } } diff --git a/src/main/java/world/bentobox/challenges/panel/admin/EditSettingsPanel.java b/src/main/java/world/bentobox/challenges/panel/admin/EditSettingsPanel.java index 69f0a20e..fe147b4c 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/EditSettingsPanel.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/EditSettingsPanel.java @@ -121,6 +121,7 @@ protected void build() panelBuilder.item(20, this.getSettingsButton(Button.REMOVE_COMPLETED)); panelBuilder.item(29, this.getSettingsButton(Button.VISIBILITY_MODE)); panelBuilder.item(30, this.getSettingsButton(Button.INCLUDE_UNDEPLOYED)); + panelBuilder.item(32, this.getSettingsButton(Button.OPEN_ANYWHERE)); panelBuilder.item(21, this.getSettingsButton(Button.LOCKED_LEVEL_ICON)); @@ -485,6 +486,22 @@ else if (this.settings.getVisibilityMode().equals(VisibilityMode.HIDDEN)) description.add(""); description.add(this.user.getTranslation(Constants.CLICK_TO_TOGGLE)); } + case OPEN_ANYWHERE -> { + description.add(this.user.getTranslation(reference + + (this.settings.isOpenAnywhere() ? Constants.ENABLED_KEY : Constants.DISABLED_KEY))); + + icon = new ItemStack(Material.ELYTRA); + clickHandler = (panel, user1, clickType, i) -> { + this.settings.setOpenAnywhere(!this.settings.isOpenAnywhere()); + panel.getInventory().setItem(i, this.getSettingsButton(button).getItem()); + this.addon.saveSettings(); + return true; + }; + glow = this.settings.isOpenAnywhere(); + + description.add(""); + description.add(this.user.getTranslation(Constants.CLICK_TO_TOGGLE)); + } default -> { icon = new ItemStack(Material.PAPER); clickHandler = null; @@ -596,7 +613,11 @@ private enum Button /** * This allows to switch between different challenges visibility modes. */ - VISIBILITY_MODE + VISIBILITY_MODE, + /** + * This allows players to open the GUI without being on their island. + */ + OPEN_ANYWHERE } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 64f9eeab..d2364a61 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -76,6 +76,10 @@ gui-settings: # TOGGLEABLE - Currently not implemented. undeployed-view-mode: VISIBLE # + # Allow players to open the challenges GUI without being on their island. + # Note: Challenges completion still requires being on the island when world protection is enabled. + open-anywhere: false + # # This allows to change default locked level icon. This option may be # overwritten by each challenge level. If challenge level has specified # their locked level icon, then it will be used, instead of this one. diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index c2a95997..28c2e670 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -770,6 +770,14 @@ challenges: should be counted towards level completion. enabled: "Enabled" disabled: "Disabled" + open_anywhere: + name: "Open GUI Anywhere" + description: |- + Allows players to open the challenges GUI + from anywhere. Completion still requires + being on the island when protected. + enabled: "Enabled" + disabled: "Disabled" download: name: "Download Libraries" description: |- diff --git a/src/test/java/world/bentobox/challenges/commands/ChallengesCommandTest.java b/src/test/java/world/bentobox/challenges/commands/ChallengesCommandTest.java index e3061199..ae974fea 100644 --- a/src/test/java/world/bentobox/challenges/commands/ChallengesCommandTest.java +++ b/src/test/java/world/bentobox/challenges/commands/ChallengesCommandTest.java @@ -247,4 +247,25 @@ void testSetup() { assertEquals(1, cc.getSubCommands(true).size()); } + @Test + void testCanExecuteOffIslandWithProtectionAndNoOpenAnywhere() { + // Player is off island and world protection is on, but openAnywhere is false + when(im.locationIsOnIsland(any(Player.class), any())).thenReturn(false); + // Note: Since CHALLENGES_WORLD_PROTECTION flag has default setting true, + // and TestWorldSetting.getWorldFlags() returns an empty map by default, + // the flag will check as enabled. We test behavior when it's restricted. + assertFalse(cc.canExecute(user, "challenges", Collections.emptyList())); + verify(user).getTranslation(world, "challenges.errors.not-on-island"); + } + + @Test + void testCanExecuteOffIslandWithProtectionAndOpenAnywhere() { + // Player is off island but openAnywhere is enabled + when(im.locationIsOnIsland(any(Player.class), any())).thenReturn(false); + Settings settings = (Settings) addon.getChallengesSettings(); + settings.setOpenAnywhere(true); + assertTrue(cc.canExecute(user, "challenges", Collections.emptyList())); + verify(user, never()).getTranslation(world, "challenges.errors.not-on-island"); + } + }