From 37884858ab5f1b64eb6f33e2aa13123637d6aee5 Mon Sep 17 00:00:00 2001 From: tastybento Date: Wed, 29 Jul 2026 08:24:05 -0700 Subject: [PATCH 1/9] Only offer 'Click to change' in the phases GUI to players who can use it The phases panel offered the SELECT action (and its 'Click to change' tooltip) based on island state and phase requirements alone, without checking whether the player actually holds the permission of the setcount command the click runs. A player with chunkblock.island.setcount negated saw the tooltip and then got a permission error when clicking. The panel now resolves the setcount subcommand and checks its permission before offering the action; if the command cannot be resolved it stays permissive and lets the command's own check decide. Also renames the GUI/list titles from the AOneBlock leftover 'OneBlock Phases' to 'ChunkBlock Phases'. Fixes #11 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Y7xSdVPS5vRu6wqf6XshRq --- .../chunkblock/panels/PhasesPanel.java | 17 ++++ src/main/resources/locales/en-US.yml | 4 +- src/main/resources/locales/id.yml | 2 +- .../chunkblock/panels/PhasesPanelTest.java | 79 +++++++++++++++++++ 4 files changed, 99 insertions(+), 3 deletions(-) diff --git a/src/main/java/world/bentobox/chunkblock/panels/PhasesPanel.java b/src/main/java/world/bentobox/chunkblock/panels/PhasesPanel.java index 88dfeb8..394cd68 100644 --- a/src/main/java/world/bentobox/chunkblock/panels/PhasesPanel.java +++ b/src/main/java/world/bentobox/chunkblock/panels/PhasesPanel.java @@ -539,6 +539,10 @@ private boolean canApplyPhase(OneBlockPhase phase) { return false; } + if (!this.hasSetCountPermission()) + { + return false; + } if (phase.getBlockNumberValue() >= this.oneBlockIsland.getLifetime()) { return false; @@ -546,6 +550,19 @@ private boolean canApplyPhase(OneBlockPhase phase) return !this.phaseRequirementsFail(phase, this.oneBlockIsland); } + /** + * Checks that the player holds the permission of the setcount command that a phase + * click runs, so the "click to change" action is only offered when it can succeed. + */ + private boolean hasSetCountPermission() + { + String label = this.addon.getSettings().getSetCountCommand().split(" ")[0]; + return this.addon.getPlayerCommand() + .flatMap(mainCommand -> mainCommand.getSubCommand(label)) + .map(subCommand -> this.user.hasPermission(subCommand.getPermission())) + .orElse(true); + } + /** * Builds the click handler for a phase button. */ diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index b2c2016..e310d6e 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -132,7 +132,7 @@ chunkblock: count: "&a Island is on block &b [number] &a in the &b [name] &a phase. Lifetime count &b [lifetime] &a." phases: description: show a list of all the phases - title: "&2 OneBlock Phases" + title: "&2 ChunkBlock Phases" name-syntax: "&a [name]" description-syntax: "&b [number] blocks" island: @@ -164,7 +164,7 @@ chunkblock: my-island-phase-default: Unknown gui: titles: - phases: '&0&l OneBlock Phases' + phases: '&0&l ChunkBlock Phases' # This section contains all button names and lore (description) buttons: # List of buttons in GUI's diff --git a/src/main/resources/locales/id.yml b/src/main/resources/locales/id.yml index 23bf9e9..c8abbe9 100644 --- a/src/main/resources/locales/id.yml +++ b/src/main/resources/locales/id.yml @@ -100,7 +100,7 @@ chunkblock: my-island-phase-default: Tidak diketahui gui: titles: - phases: OneBlock Phases + phases: ChunkBlock Phases buttons: previous: name: '&f&l halaman sebelumnya' diff --git a/src/test/java/world/bentobox/chunkblock/panels/PhasesPanelTest.java b/src/test/java/world/bentobox/chunkblock/panels/PhasesPanelTest.java index f1b33d2..c417882 100644 --- a/src/test/java/world/bentobox/chunkblock/panels/PhasesPanelTest.java +++ b/src/test/java/world/bentobox/chunkblock/panels/PhasesPanelTest.java @@ -42,6 +42,7 @@ import world.bentobox.bank.Bank; import world.bentobox.bank.BankManager; import world.bentobox.bank.data.Money; +import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.panels.TemplatedPanel; import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord; import world.bentobox.bentobox.api.user.User; @@ -554,6 +555,84 @@ void testCanApplyPhaseSuccess() throws Exception { assertTrue(result); } + /** + * Test canApplyPhase returns false when the player lacks the setcount command + * permission — the "click to change" action must not be offered. + */ + @Test + void testCanApplyPhaseNoSetCountPermission() throws Exception { + setUpAddonMocks(); + Island testIsland = new Island(); + testIsland.setOwner(uuid); + OneBlockIslands oneBlockIsland = new OneBlockIslands(testIsland.getUniqueId()); + oneBlockIsland.setLifetime(100L); + oneBlockIsland.setLastPhaseChangeTime(System.currentTimeMillis()); + + User user = User.getInstance(mockPlayer); + when(im.getIsland(world, user)).thenReturn(testIsland); + when(blockListener.getIsland(testIsland)).thenReturn(oneBlockIsland); + when(oneBlockManager.getBlockProbs()).thenReturn(createBlockProbs()); + + // The setcount command exists and carries a permission the player does not have + CompositeCommand mainCommand = mock(CompositeCommand.class); + CompositeCommand setCountCommand = mock(CompositeCommand.class); + when(mainCommand.getSubCommand("setcount")).thenReturn(Optional.of(setCountCommand)); + when(setCountCommand.getPermission()).thenReturn("chunkblock.island.setcount"); + when(addon.getPlayerCommand()).thenReturn(Optional.of(mainCommand)); + when(mockPlayer.hasPermission("chunkblock.island.setcount")).thenReturn(false); + + Constructor constructor = PhasesPanel.class.getDeclaredConstructor(ChunkBlock.class, World.class, User.class); + constructor.setAccessible(true); + panel = constructor.newInstance(addon, world, user); + + OneBlockPhase phase = createTestPhase("Plains"); + + Method method = PhasesPanel.class.getDeclaredMethod("canApplyPhase", OneBlockPhase.class); + method.setAccessible(true); + + boolean result = (boolean) method.invoke(panel, phase); + + assertFalse(result); + } + + /** + * Test canApplyPhase returns true when the player holds the setcount permission. + */ + @Test + void testCanApplyPhaseWithSetCountPermission() throws Exception { + setUpAddonMocks(); + Island testIsland = new Island(); + testIsland.setOwner(uuid); + OneBlockIslands oneBlockIsland = new OneBlockIslands(testIsland.getUniqueId()); + oneBlockIsland.setLifetime(100L); + oneBlockIsland.setLastPhaseChangeTime(System.currentTimeMillis()); + + User user = User.getInstance(mockPlayer); + when(im.getIsland(world, user)).thenReturn(testIsland); + when(blockListener.getIsland(testIsland)).thenReturn(oneBlockIsland); + when(oneBlockManager.getBlockProbs()).thenReturn(createBlockProbs()); + + CompositeCommand mainCommand = mock(CompositeCommand.class); + CompositeCommand setCountCommand = mock(CompositeCommand.class); + when(mainCommand.getSubCommand("setcount")).thenReturn(Optional.of(setCountCommand)); + when(setCountCommand.getPermission()).thenReturn("chunkblock.island.setcount"); + when(addon.getPlayerCommand()).thenReturn(Optional.of(mainCommand)); + when(mockPlayer.hasPermission("chunkblock.island.setcount")).thenReturn(true); + + Constructor constructor = PhasesPanel.class.getDeclaredConstructor(ChunkBlock.class, World.class, User.class); + constructor.setAccessible(true); + panel = constructor.newInstance(addon, world, user); + + OneBlockPhase phase = createTestPhase("Plains"); + + Method method = PhasesPanel.class.getDeclaredMethod("canApplyPhase", OneBlockPhase.class); + method.setAccessible(true); + + boolean result = (boolean) method.invoke(panel, phase); + + assertTrue(result); + } + // ========================================================================= // Test phaseRequirementsFail // ========================================================================= From 3bb4907a38d7618b3f37fef7898f40826d64388d Mon Sep 17 00:00:00 2001 From: tastybento Date: Wed, 29 Jul 2026 08:24:16 -0700 Subject: [PATCH 2/9] Send non-members home instead of ejecting them within a stranger's island MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a respawn (or join/teleport) landed a player inside a locked chunk — e.g. because the world had no spawn set and a respawn plugin picked a bad location on an old, abandoned island — backtrack() relocated the player to the nearest unlocked spot of whatever island was at that location and, if the spot was unsafe, created a landing block there. Players ended up standing on a lone generated block on an island they had never visited. backtrack() now only ejects a player locally when they hold coop rank or higher on the island at their location. Everyone else is sent to their own island home (falling back to spawn), and landing blocks can no longer be created on islands the player does not belong to. Fixes #13 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Y7xSdVPS5vRu6wqf6XshRq --- .../listeners/ChunkGuardListener.java | 9 +++++-- .../bentobox/chunkblock/CommonTestSetup.java | 2 ++ .../listeners/ChunkGuardListenerTest.java | 24 +++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/main/java/world/bentobox/chunkblock/listeners/ChunkGuardListener.java b/src/main/java/world/bentobox/chunkblock/listeners/ChunkGuardListener.java index 205611d..750d4df 100644 --- a/src/main/java/world/bentobox/chunkblock/listeners/ChunkGuardListener.java +++ b/src/main/java/world/bentobox/chunkblock/listeners/ChunkGuardListener.java @@ -41,6 +41,7 @@ import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.managers.RanksManager; import world.bentobox.bentobox.util.Util; import world.bentobox.chunkblock.ChunkBlock; import world.bentobox.chunkblock.chunks.ChunkManager; @@ -185,8 +186,12 @@ public void backtrack(Player player) { if (optionalIsland.isEmpty()) { optionalIsland = Optional.ofNullable(addon.getIslands().getIsland(loc.getWorld(), User.getInstance(player))); } - if (optionalIsland.isEmpty()) { - // Not on any island grid and no island of their own: home teleport is all we can do + // Only relocate a player within an island they actually belong to. Anyone else + // (e.g. a respawn that landed on a stranger's or abandoned island) goes to their + // own island home instead — never dropped, with a landing block, onto an island + // that is not theirs. + if (optionalIsland.isEmpty() + || !optionalIsland.get().getMemberSet(RanksManager.COOP_RANK).contains(player.getUniqueId())) { addon.getIslands().homeTeleportAsync(Objects.requireNonNull(loc.getWorld()), player); return; } diff --git a/src/test/java/world/bentobox/chunkblock/CommonTestSetup.java b/src/test/java/world/bentobox/chunkblock/CommonTestSetup.java index d69c7c1..62d33b4 100644 --- a/src/test/java/world/bentobox/chunkblock/CommonTestSetup.java +++ b/src/test/java/world/bentobox/chunkblock/CommonTestSetup.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.mock; @@ -202,6 +203,7 @@ public void setUp() throws Exception { when(island.isAllowed(any(User.class), any())).thenReturn(false); when(island.getOwner()).thenReturn(uuid); when(island.getMemberSet()).thenReturn(ImmutableSet.of(uuid)); + when(island.getMemberSet(anyInt())).thenReturn(ImmutableSet.of(uuid)); // Enable reporting from Flags class @SuppressWarnings("deprecation") diff --git a/src/test/java/world/bentobox/chunkblock/listeners/ChunkGuardListenerTest.java b/src/test/java/world/bentobox/chunkblock/listeners/ChunkGuardListenerTest.java index 119a454..7f0d3bf 100644 --- a/src/test/java/world/bentobox/chunkblock/listeners/ChunkGuardListenerTest.java +++ b/src/test/java/world/bentobox/chunkblock/listeners/ChunkGuardListenerTest.java @@ -23,6 +23,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import com.google.common.collect.ImmutableSet; + +import world.bentobox.bentobox.managers.RanksManager; import world.bentobox.bentobox.util.Util; import world.bentobox.chunkblock.ChunkBlock; import world.bentobox.chunkblock.CommonTestSetup; @@ -184,4 +187,25 @@ void testLockIsTwoDimensional() { listener.onPlayerMove(e); assertTrue(e.isCancelled()); } + + @Test + void testBacktrackNonMemberSentHomeNotEjectedLocally() { + // Player is inside a locked chunk of an island they have no rank on (e.g. a + // respawn that landed on a stranger's or abandoned island) + when(mockPlayer.getLocation()).thenReturn(lockedTo); + when(island.getMemberSet(RanksManager.COOP_RANK)).thenReturn(ImmutableSet.of()); + listener.backtrack(mockPlayer); + verify(im).homeTeleportAsync(world, mockPlayer); + mockedUtil.verify(() -> Util.teleportAsync(any(Player.class), any(Location.class)), never()); + } + + @Test + void testBacktrackMemberEjectedWithinIsland() { + when(mockPlayer.getLocation()).thenReturn(lockedTo); + when(island.getMemberSet(RanksManager.COOP_RANK)).thenReturn(ImmutableSet.of(uuid)); + when(im.isSafeLocation(any())).thenReturn(true); + listener.backtrack(mockPlayer); + verify(im, never()).homeTeleportAsync(any(), any(Player.class)); + mockedUtil.verify(() -> Util.teleportAsync(any(Player.class), any(Location.class))); + } } From 64e383f9f482aa9fff4a1ebe6f812de1e79a5645 Mon Sep 17 00:00:00 2001 From: tastybento Date: Wed, 29 Jul 2026 08:24:26 -0700 Subject: [PATCH 3/9] Stop mining near the border from triggering claim attempts The border-claim detection ran on every left/right click and walked a 5-block horizontal ray from the eye that ignored the clicked block and passed straight through walls. Mining anything within ~5 blocks of a locked chunk (e.g. a cobblestone generator) fired a claim attempt on every swing, nagging 'You need X more level(s) of credit' with a sound each time even though the player was not touching the border. The claim gesture is now: a click on a block inside unlocked territory is ordinary interaction and never a claim; punching a block that is itself in a locked chunk targets that chunk directly; an air click walks the real 3D aim line and stops at the first solid block, so aiming through your own builds no longer probes the chunk beyond them. Failure feedback (no-credit and beyond-limit) is additionally throttled to once per two seconds per player so repeated swings cannot spam chat. Fixes #14 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Y7xSdVPS5vRu6wqf6XshRq --- .../listeners/ChunkClaimListener.java | 57 +++++++++++++++++-- .../listeners/ChunkClaimListenerTest.java | 47 +++++++++++++++ 2 files changed, 98 insertions(+), 6 deletions(-) diff --git a/src/main/java/world/bentobox/chunkblock/listeners/ChunkClaimListener.java b/src/main/java/world/bentobox/chunkblock/listeners/ChunkClaimListener.java index cd49d6a..e273059 100644 --- a/src/main/java/world/bentobox/chunkblock/listeners/ChunkClaimListener.java +++ b/src/main/java/world/bentobox/chunkblock/listeners/ChunkClaimListener.java @@ -1,9 +1,13 @@ package world.bentobox.chunkblock.listeners; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; +import java.util.UUID; import org.bukkit.Location; import org.bukkit.Sound; +import org.bukkit.block.Block; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -32,8 +36,11 @@ public class ChunkClaimListener implements Listener { private static final double REACH = 5.0; /** Ray step size in blocks */ private static final double STEP = 0.25; + /** Minimum time between failure nags per player, so mining swings can't spam */ + private static final long FEEDBACK_COOLDOWN_MS = 2000; private final ChunkBlock addon; + private final Map lastFeedback = new HashMap<>(); public ChunkClaimListener(ChunkBlock addon) { this.addon = addon; @@ -69,7 +76,20 @@ public void onBorderHit(PlayerInteractEvent e) { if (cm.isLocked(island, player.getLocation())) { return; } - int[] target = findTargetLockedChunk(player, island); + // A click on a block inside unlocked territory is ordinary interaction (mining a + // generator, pressing a button...), never a claim gesture — regardless of where + // the aim line would end up beyond it. + Block clicked = e.getClickedBlock(); + if (clicked != null && cm.isUnlocked(island, clicked.getX() >> 4, clicked.getZ() >> 4)) { + return; + } + int[] target; + if (clicked != null) { + // The clicked block is itself in a locked chunk: that chunk is the target + target = new int[] { clicked.getX() >> 4, clicked.getZ() >> 4 }; + } else { + target = findTargetLockedChunk(player, island); + } if (target == null) { return; } @@ -91,12 +111,17 @@ private int[] findTargetLockedChunk(Player player, Island island) { } ChunkManager cm = addon.getChunkManager(); for (double d = STEP; d <= REACH; d += STEP) { - Location point = eye.clone().add(direction.getX() * d, 0, direction.getZ() * d); + Location point = eye.clone().add(direction.getX() * d, direction.getY() * d, direction.getZ() * d); int chunkX = point.getBlockX() >> 4; int chunkZ = point.getBlockZ() >> 4; if (!cm.isUnlocked(island, chunkX, chunkZ)) { return new int[] { chunkX, chunkZ }; } + Block block = point.getBlock(); + if (block != null && !block.isPassable()) { + // The aim line is blocked by the player's own blocks before the border + return null; + } } return null; } @@ -115,14 +140,34 @@ public void attemptClaim(User user, Island island, int chunkX, int chunkZ) { switch (result) { case OK -> addon.getLevelListener().celebrateClaim(island, chunkX, chunkZ); case NO_CREDIT -> { - long needed = cm.getChunkCost() - cm.getCredit(island); - user.notify("chunkblock.chunks.no-credit", "[needed]", String.valueOf(needed)); - user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1F, 0.6F); + if (feedbackReady(user.getUniqueId())) { + long needed = cm.getChunkCost() - cm.getCredit(island); + user.notify("chunkblock.chunks.no-credit", "[needed]", String.valueOf(needed)); + user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1F, 0.6F); + } + } + case BEYOND_LIMIT -> { + if (feedbackReady(user.getUniqueId())) { + user.notify("chunkblock.chunks.beyond-limit"); + } } - case BEYOND_LIMIT -> user.notify("chunkblock.chunks.beyond-limit"); case NOT_ADJACENT, ALREADY_UNLOCKED -> { // Aiming at a diagonal corner or a chunk already owned: no claim, no nag } } } + + /** + * Rate-limits failure feedback: repeated swings while mining should not turn every + * failed claim probe into a chat message and a sound. + */ + private boolean feedbackReady(UUID uuid) { + long now = System.currentTimeMillis(); + Long last = lastFeedback.get(uuid); + if (last != null && now - last < FEEDBACK_COOLDOWN_MS) { + return false; + } + lastFeedback.put(uuid, now); + return true; + } } diff --git a/src/test/java/world/bentobox/chunkblock/listeners/ChunkClaimListenerTest.java b/src/test/java/world/bentobox/chunkblock/listeners/ChunkClaimListenerTest.java index c3f00a6..2e8d19b 100644 --- a/src/test/java/world/bentobox/chunkblock/listeners/ChunkClaimListenerTest.java +++ b/src/test/java/world/bentobox/chunkblock/listeners/ChunkClaimListenerTest.java @@ -6,6 +6,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -14,6 +15,7 @@ import org.bukkit.GameMode; import org.bukkit.Location; +import org.bukkit.block.Block; import org.bukkit.event.block.Action; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.inventory.EquipmentSlot; @@ -142,4 +144,49 @@ void testClaimingChainsOutward() { listener.onBorderHit(hit(Action.LEFT_CLICK_AIR)); assertTrue(data.isChunkUnlocked(2, 0)); } + + private PlayerInteractEvent hitBlock(Action action, int x, int z) { + Block block = mock(Block.class); + when(block.getX()).thenReturn(x); + when(block.getZ()).thenReturn(z); + return new PlayerInteractEvent(mockPlayer, action, null, block, org.bukkit.block.BlockFace.EAST, + EquipmentSlot.HAND); + } + + @Test + void testMiningOwnBlockNearBorderIsNotAClaim() { + // The generator-mining bug: clicked block is in the player's own chunk, but the + // aim line would cross into the locked neighbor. Must not probe a claim. + level = 0; + listener.onBorderHit(hitBlock(Action.LEFT_CLICK_BLOCK, 14, 8)); + assertFalse(data.isChunkUnlocked(1, 0)); + verify(notifier, never()).notify(any(), any()); + } + + @Test + void testPunchingBlockInLockedChunkClaimsIt() { + level = 1; + listener.onBorderHit(hitBlock(Action.LEFT_CLICK_BLOCK, 17, 8)); + assertTrue(data.isChunkUnlocked(1, 0)); + } + + @Test + void testNoCreditFeedbackIsThrottled() { + level = 0; + listener.onBorderHit(hit(Action.LEFT_CLICK_AIR)); + listener.onBorderHit(hit(Action.LEFT_CLICK_AIR)); + // Two rapid failed probes, one nag + verify(notifier, times(1)).notify(any(), any()); + } + + @Test + void testAimLineBlockedBySolidWallDoesNotClaim() { + level = 1; + // A solid wall of the player's own blocks between eye and border + Block wall = mock(Block.class); + when(wall.isPassable()).thenReturn(false); + when(world.getBlockAt(any(Location.class))).thenReturn(wall); + listener.onBorderHit(hit(Action.LEFT_CLICK_AIR)); + assertFalse(data.isChunkUnlocked(1, 0)); + } } From 6eceeef641e549b3f41005d9715d39809867ba79 Mon Sep 17 00:00:00 2001 From: tastybento Date: Wed, 29 Jul 2026 08:24:34 -0700 Subject: [PATCH 4/9] Change default command aliases cb/cbadmin to ch/chadmin CaveBlock already uses /cb and /cbadmin (plus /cba) as its default aliases, so running both gamemodes on one server made the short commands collide. ChunkBlock's defaults are now 'ch chunkblock' for players and 'chadmin chunkblockadmin cha' for admins. Existing servers keep whatever they have in config.yml; only fresh installs get the new defaults. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Y7xSdVPS5vRu6wqf6XshRq --- README.md | 14 +- .../world/bentobox/chunkblock/Settings.java | 14 +- .../commands/admin/AdminBypassCommand.java | 2 +- .../commands/admin/AdminChunksCommand.java | 2 +- .../commands/island/IslandChunksCommand.java | 2 +- src/main/resources/addon.yml | 190 +++++++++--------- src/main/resources/config.yml | 14 +- .../bentobox/chunkblock/SettingsTest.java | 8 +- 8 files changed, 123 insertions(+), 123 deletions(-) diff --git a/README.md b/README.md index 2c2c940..ec31a11 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ earn the levels back. - The classic OneBlock loop: 18 phases, thousands of blocks, mobs, and treasure chests. - Territory that grows the way *you* choose — punch the border to claim the next chunk. - A particle curtain shows the frontier; claiming chunks is celebrated in style. -- `/cb chunks` shows a live map of your territory and what you can claim next. +- `/ch chunks` shows a live map of your territory and what you can claim next. ## Installation @@ -34,15 +34,15 @@ commands, and permissions. ## Commands -The player command is `/cb` (alias `/chunkblock`), the admin command `/cbadmin` +The player command is `/ch` (alias `/chunkblock`), the admin command `/chadmin` (aliases `/chunkblockadmin`, `/cba`). Beyond the standard BentoBox island commands: | Command | Description | |---|---| -| `/cb chunks` | Your chunk count, spendable credit, and a chat map of your territory | -| `/cb count` | Magic block count and phase | -| `/cbadmin chunks [reset]` | Inspect a player's chunks and credit, or re-lock them back to the start | -| `/cbadmin bypass` | Toggle chunk-lock enforcement for yourself (needs `chunkblock.mod.bypasschunks`) | +| `/ch chunks` | Your chunk count, spendable credit, and a chat map of your territory | +| `/ch count` | Magic block count and phase | +| `/chadmin chunks [reset]` | Inspect a player's chunks and credit, or re-lock them back to the start | +| `/chadmin bypass` | Toggle chunk-lock enforcement for yourself (needs `chunkblock.mod.bypasschunks`) | ## How claiming works @@ -106,7 +106,7 @@ Lush Caves, Dripstone Caves, Mangrove Swamp, Meadow, Cherry Grove, and Jagged Pe Q: Why can't I walk past the glowing red wall? A: That chunk is still locked! If you're the island owner and have level credit, hit the -wall to claim the chunk. Check `/cb chunks` to see your credit and what's claimable. +wall to claim the chunk. Check `/ch chunks` to see your credit and what's claimable. Q: I lost levels and my farm is behind the wall now. Is it gone? diff --git a/src/main/java/world/bentobox/chunkblock/Settings.java b/src/main/java/world/bentobox/chunkblock/Settings.java index 0c47f99..f8fd0ec 100644 --- a/src/main/java/world/bentobox/chunkblock/Settings.java +++ b/src/main/java/world/bentobox/chunkblock/Settings.java @@ -41,12 +41,12 @@ public class Settings implements WorldSettings { @ConfigComment("Player command. What command users will run to access their island.") @ConfigComment("To define alias, just separate commands with white space.") @ConfigEntry(path = "chunkblock.command.island") //, since = "1.3.0") - private String playerCommandAliases = "cb chunkblock"; + private String playerCommandAliases = "ch chunkblock"; @ConfigComment("The admin command.") @ConfigComment("To define alias, just separate commands with white space.") @ConfigEntry(path = "chunkblock.command.admin") // , since = "1.3.0") - private String adminCommandAliases = "cbadmin chunkblockadmin cba"; + private String adminCommandAliases = "chadmin chunkblockadmin cha"; @ConfigComment("The default action for new player command call.") @ConfigComment("Sub-command of main player command that will be run on first player command call.") @@ -269,7 +269,7 @@ public class Settings implements WorldSettings { private int islandDistance = 256; @ConfigComment("Default protection range radius in blocks. Cannot be larger than distance.") - @ConfigComment("Admins can change protection sizes for players individually using /cbadmin range set ") + @ConfigComment("Admins can change protection sizes for players individually using /chadmin range set ") @ConfigComment("or set this permission: chunkblock.island.range.") @ConfigComment("ChunkBlock: this must cover the largest unlockable ring of chunks (see chunkblock.max-chunks).") @ConfigEntry(path = "world.protection-range") @@ -475,7 +475,7 @@ public class Settings implements WorldSettings { private int maxHomes = 5; // Reset - @ConfigComment("How many resets a player is allowed (manage with /cbadmin reset add/remove/reset/set command)") + @ConfigComment("How many resets a player is allowed (manage with /chadmin reset add/remove/reset/set command)") @ConfigComment("Value of -1 means unlimited, 0 means hardcore - no resets.") @ConfigComment("Example, 2 resets means they get 2 resets or 3 islands lifetime") @ConfigEntry(path = "island.reset.reset-limit") @@ -612,7 +612,7 @@ public class Settings implements WorldSettings { @ConfigComment("") @ConfigComment("Here are some examples of valid commands to execute:") @ConfigComment(" * \"[SUDO] bbox version\"") - @ConfigComment(" * \"cbadmin deaths set [player] 0\"") + @ConfigComment(" * \"chadmin deaths set [player] 0\"") @ConfigEntry(path = "island.commands.on-join") // , since = "1.8.0") private List onJoinCommands = new ArrayList<>(); @@ -625,7 +625,7 @@ public class Settings implements WorldSettings { @ConfigComment("") @ConfigComment("Here are some examples of valid commands to execute:") @ConfigComment(" * '[SUDO] bbox version'") - @ConfigComment(" * 'cbadmin deaths set [player] 0'") + @ConfigComment(" * 'chadmin deaths set [player] 0'") @ConfigComment("") @ConfigComment("Note that player-executed commands might not work, as these commands can be run with said player being offline.") @ConfigEntry(path = "island.commands.on-leave") // , since = "1.8.0") @@ -640,7 +640,7 @@ public class Settings implements WorldSettings { @ConfigComment("") @ConfigComment("Here are some examples of valid commands to execute:") @ConfigComment(" * '[SUDO] bbox version'") - @ConfigComment(" * 'cbadmin deaths set [player] 0'") + @ConfigComment(" * 'chadmin deaths set [player] 0'") @ConfigComment("") @ConfigComment("Note that player-executed commands might not work, as these commands can be run with said player being offline.") @ConfigEntry(path = "island.commands.on-respawn") // , since = "1.14.0") diff --git a/src/main/java/world/bentobox/chunkblock/commands/admin/AdminBypassCommand.java b/src/main/java/world/bentobox/chunkblock/commands/admin/AdminBypassCommand.java index 4d00094..b72e477 100644 --- a/src/main/java/world/bentobox/chunkblock/commands/admin/AdminBypassCommand.java +++ b/src/main/java/world/bentobox/chunkblock/commands/admin/AdminBypassCommand.java @@ -7,7 +7,7 @@ import world.bentobox.chunkblock.ChunkBlock; /** - * /cbadmin bypass — lets staff with the bypass permission toggle chunk lock enforcement + * /chadmin bypass — lets staff with the bypass permission toggle chunk lock enforcement * for themselves, so they can test the game as players see it and inspect cleanly. * * @author tastybento diff --git a/src/main/java/world/bentobox/chunkblock/commands/admin/AdminChunksCommand.java b/src/main/java/world/bentobox/chunkblock/commands/admin/AdminChunksCommand.java index 394aa20..4048249 100644 --- a/src/main/java/world/bentobox/chunkblock/commands/admin/AdminChunksCommand.java +++ b/src/main/java/world/bentobox/chunkblock/commands/admin/AdminChunksCommand.java @@ -14,7 +14,7 @@ import world.bentobox.chunkblock.chunks.ChunkManager; /** - * /cbadmin chunks <player> [reset] — support and debug tool: shows a player's + * /chadmin chunks <player> [reset] — support and debug tool: shows a player's * unlocked chunks, spending and credit, or re-locks everything back to the center chunk. * * @author tastybento diff --git a/src/main/java/world/bentobox/chunkblock/commands/island/IslandChunksCommand.java b/src/main/java/world/bentobox/chunkblock/commands/island/IslandChunksCommand.java index 09b7fcf..d1fa81b 100644 --- a/src/main/java/world/bentobox/chunkblock/commands/island/IslandChunksCommand.java +++ b/src/main/java/world/bentobox/chunkblock/commands/island/IslandChunksCommand.java @@ -13,7 +13,7 @@ import world.bentobox.chunkblock.chunks.ChunkManager.ClaimResult; /** - * /cb chunks — shows how big your island is, how much level credit you can spend, and a + * /ch chunks — shows how big your island is, how much level credit you can spend, and a * little chat map of your territory with the chunks you could claim next. * * @author tastybento diff --git a/src/main/resources/addon.yml b/src/main/resources/addon.yml index e172aa4..257373a 100755 --- a/src/main/resources/addon.yml +++ b/src/main/resources/addon.yml @@ -40,7 +40,7 @@ permissions: description: Bypasses an island lock default: op chunkblock.mod.bypasschunks: - description: Exempts the holder from chunk locking entirely; also allows '/cbadmin bypass' to toggle it. Not given to ops by default - it must be granted explicitly so staff play by the same rules until they opt in. + description: Exempts the holder from chunk locking entirely; also allows '/chadmin bypass' to toggle it. Not given to ops by default - it must be granted explicitly so staff play by the same rules until they opt in. default: false chunkblock.mod.bypassban: description: Bypasses island ban @@ -53,284 +53,284 @@ permissions: default: op # Command permissions chunkblock.island: - description: Allow use of '/cb' command - the main island command + description: Allow use of '/ch' command - the main island command default: TRUE chunkblock.island.actionbar: - description: Allow use of '/cb actionbar' command - toggle the actionbar + description: Allow use of '/ch actionbar' command - toggle the actionbar default: TRUE chunkblock.island.bossbar: - description: Allow use of '/cb bossbar' command - toggle the bossbar + description: Allow use of '/ch bossbar' command - toggle the bossbar default: TRUE chunkblock.island.home: - description: Allow use of '/cb go' command - teleport you to your island + description: Allow use of '/ch go' command - teleport you to your island default: TRUE chunkblock.island.spawn: - description: Allow use of '/cb spawn' command - teleport you to the spawn + description: Allow use of '/ch spawn' command - teleport you to the spawn default: TRUE chunkblock.island.create: - description: Allow use of '/cb create' command - create an island, using optional blueprint (requires permission) + description: Allow use of '/ch create' command - create an island, using optional blueprint (requires permission) default: TRUE chunkblock.island.reset: - description: Allow use of '/cb reset' command - restart your island and remove the old one + description: Allow use of '/ch reset' command - restart your island and remove the old one default: TRUE chunkblock.island.info: - description: Allow use of '/cb info' command - display info about your island or the player's island + description: Allow use of '/ch info' command - display info about your island or the player's island default: TRUE chunkblock.island.settings: - description: Allow use of '/cb settings' command - display island settings + description: Allow use of '/ch settings' command - display island settings default: TRUE chunkblock.island.name: - description: Allow use of '/cb setname' or '/cb resetname' command - your island name + description: Allow use of '/ch setname' or '/ch resetname' command - your island name default: TRUE chunkblock.island.language: - description: Allow use of '/cb language' command - select language + description: Allow use of '/ch language' command - select language default: TRUE chunkblock.island.ban: - description: Allow use of '/cb ban' or '/cb unban' or '/cb banlist' command - banned players + description: Allow use of '/ch ban' or '/ch unban' or '/ch banlist' command - banned players default: TRUE chunkblock.island.expel: - description: Allow use of '/cb expel' command - expel a player from your island + description: Allow use of '/ch expel' command - expel a player from your island default: TRUE chunkblock.island.near: - description: Allow use of '/cb near' command - show the name of neighboring islands around you + description: Allow use of '/ch near' command - show the name of neighboring islands around you default: TRUE chunkblock.island.team: - description: Allow use of '/cb team' command - manage your team + description: Allow use of '/ch team' command - manage your team default: TRUE chunkblock.island.team.invite: - description: Allow use of '/cb team invite' command - invite a player to join your island + description: Allow use of '/ch team invite' command - invite a player to join your island default: TRUE chunkblock.island.team.leave: - description: Allow use of '/cb team leave' command - leave your island + description: Allow use of '/ch team leave' command - leave your island default: TRUE chunkblock.island.team.setowner: - description: Allow use of '/cb team setowner' command - transfer your island ownership to a member + description: Allow use of '/ch team setowner' command - transfer your island ownership to a member default: TRUE chunkblock.island.team.kick: - description: Allow use of '/cb team kick' command - remove a member from your island + description: Allow use of '/ch team kick' command - remove a member from your island default: TRUE chunkblock.island.team.accept: - description: Allow use of '/cb team accept' command - accept an invitation + description: Allow use of '/ch team accept' command - accept an invitation default: TRUE chunkblock.island.team.reject: - description: Allow use of '/cb team reject' command - reject an invitation + description: Allow use of '/ch team reject' command - reject an invitation default: TRUE chunkblock.island.team.coop: - description: Allow use of '/cb team coop, uncoop' commands + description: Allow use of '/ch team coop, uncoop' commands default: TRUE chunkblock.island.team.trust: - description: Allow use of '/cb team trust, untrust' commands + description: Allow use of '/ch team trust, untrust' commands default: TRUE chunkblock.island.team.promote: - description: Allow use of '/cb team promote, demote' command + description: Allow use of '/ch team promote, demote' command default: TRUE chunkblock.island.sethome: - description: Allow use of '/cb sethome' command - set your home teleport point + description: Allow use of '/ch sethome' command - set your home teleport point default: TRUE chunkblock.island.deletehome: - description: Allow use of '/cb deletehome' command - delete a home location + description: Allow use of '/ch deletehome' command - delete a home location default: OP chunkblock.island.renamehome: - description: Allow use of '/cb renamehome' command - rename a home location + description: Allow use of '/ch renamehome' command - rename a home location default: OP chunkblock.island.homes: - description: Allow use of '/cb homes' command - list your homes + description: Allow use of '/ch homes' command - list your homes default: OP chunkblock.count: - description: Allow use of '/cb count' command - show the block count and phase + description: Allow use of '/ch count' command - show the block count and phase default: TRUE chunkblock.phases: - description: Allow use of '/cb phases' command - show a list of all the phases + description: Allow use of '/ch phases' command - show a list of all the phases default: FALSE chunkblock.island.setcount: - description: Allow use of '/cb setCount' command - set block count to previously completed value + description: Allow use of '/ch setCount' command - set block count to previously completed value default: OP chunkblock.respawn-block: - description: Allow use of '/cb respawnBlock' command - respawns magic block in situations when they disappear + description: Allow use of '/ch respawnBlock' command - respawns magic block in situations when they disappear default: TRUE chunkblock.admin: - description: Allow use of '/cbadmin' command - admin command + description: Allow use of '/chadmin' command - admin command default: OP chunkblock.admin.version: - description: Allow use of '/cbadmin version' command - display BentoBox and addons versions + description: Allow use of '/chadmin version' command - display BentoBox and addons versions default: OP chunkblock.admin.tp: - description: Allow use of '/cbadmin tp/tpnether/tpend' command - teleport to a player's island + description: Allow use of '/chadmin tp/tpnether/tpend' command - teleport to a player's island default: OP chunkblock.admin.getrank: - description: Allow use of '/cbadmin getrank' command - get a player's rank on their island or the island of the owner + description: Allow use of '/chadmin getrank' command - get a player's rank on their island or the island of the owner default: OP chunkblock.admin.setrank: - description: Allow use of '/cbadmin setrank' command - set a player's rank on their island or the island of the owner + description: Allow use of '/chadmin setrank' command - set a player's rank on their island or the island of the owner default: OP chunkblock.mod.info: - description: Allow use of '/cbadmin info' command - get info on where you are or player's island + description: Allow use of '/chadmin info' command - get info on where you are or player's island default: OP chunkblock.mod.team: - description: Allow use of '/cbadmin team' command - manage teams + description: Allow use of '/chadmin team' command - manage teams default: FALSE chunkblock.mod.team.add: - description: Allow use of '/cbadmin team add' or '/cbadmin add' command - add player to owner's team + description: Allow use of '/chadmin team add' or '/chadmin add' command - add player to owner's team default: OP chunkblock.mod.team.disband: - description: Allow use of '/cbadmin team disband' or '/cbadmin disband' command - disband owner's team + description: Allow use of '/chadmin team disband' or '/chadmin disband' command - disband owner's team default: OP chunkblock.mod.team.fix: - description: Allow use of '/cbadmin team fix' or '/cbadmin fix' command - scans and fixes cross island membership in database + description: Allow use of '/chadmin team fix' or '/chadmin fix' command - scans and fixes cross island membership in database default: OP chunkblock.mod.team.kick: - description: Allow use of '/cbadmin team kick' or '/cbadmin kick' command - kick a player from a team + description: Allow use of '/chadmin team kick' or '/chadmin kick' command - kick a player from a team default: OP chunkblock.mod.team.setowner: - description: Allow use of '/cbadmin team setowner' command - transfers island ownership to the player + description: Allow use of '/chadmin team setowner' command - transfers island ownership to the player default: OP chunkblock.admin.blueprint: - description: Allow use of '/cbadmin blueprint' command - manipulate blueprints + description: Allow use of '/chadmin blueprint' command - manipulate blueprints default: OP chunkblock.admin.blueprint.load: - description: Allow use of '/cbadmin blueprint load' command - load blueprint into the clipboard + description: Allow use of '/chadmin blueprint load' command - load blueprint into the clipboard default: OP chunkblock.admin.blueprint.paste: - description: Allow use of '/cbadmin blueprint paste' command - paste the clipboard to your location + description: Allow use of '/chadmin blueprint paste' command - paste the clipboard to your location default: OP chunkblock.admin.blueprint.origin: - description: Allow use of '/cbadmin blueprint origin' command - set the blueprint's origin to your position + description: Allow use of '/chadmin blueprint origin' command - set the blueprint's origin to your position default: OP chunkblock.admin.blueprint.copy: - description: Allow use of '/cbadmin blueprint copy' command - copy the clipboard set by pos1 and pos2 and optionally the air blocks + description: Allow use of '/chadmin blueprint copy' command - copy the clipboard set by pos1 and pos2 and optionally the air blocks default: OP chunkblock.admin.blueprint.save: - description: Allow use of '/cbadmin blueprint save' command - save the copied clipboard + description: Allow use of '/chadmin blueprint save' command - save the copied clipboard default: OP chunkblock.admin.blueprint.rename: - description: Allow use of '/cbadmin blueprint rename' command - rename a blueprint + description: Allow use of '/chadmin blueprint rename' command - rename a blueprint default: OP chunkblock.admin.blueprint.delete: - description: Allow use of '/cbadmin blueprint delete' command - delete the blueprint + description: Allow use of '/chadmin blueprint delete' command - delete the blueprint default: OP chunkblock.admin.blueprint.pos1: - description: Allow use of '/cbadmin blueprint pos1' command - set 1st corner of cuboid clipboard + description: Allow use of '/chadmin blueprint pos1' command - set 1st corner of cuboid clipboard default: OP chunkblock.admin.blueprint.pos2: - description: Allow use of '/cbadmin blueprint pos2' command - set 2nd corner of cuboid clipboard + description: Allow use of '/chadmin blueprint pos2' command - set 2nd corner of cuboid clipboard default: OP chunkblock.admin.blueprint.list: - description: Allow use of '/cbadmin blueprint list' command - list available blueprints + description: Allow use of '/chadmin blueprint list' command - list available blueprints default: OP chunkblock.admin.register: - description: Allow use of '/cbadmin register' command - register player to unowned island you are on + description: Allow use of '/chadmin register' command - register player to unowned island you are on default: OP chunkblock.admin.unregister: - description: Allow use of '/cbadmin unregister' command - unregister owner from island, but keep island blocks + description: Allow use of '/chadmin unregister' command - unregister owner from island, but keep island blocks default: OP chunkblock.admin.range: - description: Allow use of '/cbadmin range' command - admin island range command + description: Allow use of '/chadmin range' command - admin island range command default: OP chunkblock.admin.range.display: - description: Allow use of '/cbadmin range display' command - show/hide island range indicators + description: Allow use of '/chadmin range display' command - show/hide island range indicators default: OP chunkblock.admin.range.set: - description: Allow use of '/cbadmin range set' command - sets the island protected range + description: Allow use of '/chadmin range set' command - sets the island protected range default: OP chunkblock.admin.range.reset: - description: Allow use of '/cbadmin range reset' command - resets the island protected range to the world default + description: Allow use of '/chadmin range reset' command - resets the island protected range to the world default default: OP chunkblock.admin.range.add: - description: Allow use of '/cbadmin range add' command - increases the island protected range + description: Allow use of '/chadmin range add' command - increases the island protected range default: OP chunkblock.admin.range.remove: - description: Allow use of '/cbadmin range remove' command - decreases the island protected range + description: Allow use of '/chadmin range remove' command - decreases the island protected range default: OP chunkblock.admin.resets: - description: Allow use of '/cbadmin resets' command - edit player reset values + description: Allow use of '/chadmin resets' command - edit player reset values default: OP chunkblock.admin.resets.set: - description: Allow use of '/cbadmin resets set' command - sets how many times this player has reset his island + description: Allow use of '/chadmin resets set' command - sets how many times this player has reset his island default: OP chunkblock.admin.resets.add: - description: Allow use of '/cbadmin resets add' command - adds this player's island reset count + description: Allow use of '/chadmin resets add' command - adds this player's island reset count default: OP chunkblock.admin.resets.remove: - description: Allow use of '/cbadmin resets remove' command - reduces the player's island reset count + description: Allow use of '/chadmin resets remove' command - reduces the player's island reset count default: OP chunkblock.admin.delete: - description: Allow use of '/cbadmin delete' command - deletes a player's island + description: Allow use of '/chadmin delete' command - deletes a player's island default: OP chunkblock.admin.why: - description: Allow use of '/cbadmin why' command - toggle console protection debug reporting + description: Allow use of '/chadmin why' command - toggle console protection debug reporting default: OP chunkblock.admin.deaths: - description: Allow use of '/cbadmin deaths' command - edit deaths of players + description: Allow use of '/chadmin deaths' command - edit deaths of players default: OP chunkblock.admin.deaths.reset: - description: Allow use of '/cbadmin deaths reset' command - resets deaths of the player + description: Allow use of '/chadmin deaths reset' command - resets deaths of the player default: OP chunkblock.admin.deaths.set: - description: Allow use of '/cbadmin deaths set' command - sets deaths of the player + description: Allow use of '/chadmin deaths set' command - sets deaths of the player default: OP chunkblock.admin.deaths.add: - description: Allow use of '/cbadmin deaths add' command - adds deaths to the player + description: Allow use of '/chadmin deaths add' command - adds deaths to the player default: OP chunkblock.admin.deaths.remove: - description: Allow use of '/cbadmin deaths remove' command - removes deaths to the player + description: Allow use of '/chadmin deaths remove' command - removes deaths to the player default: OP chunkblock.admin.reload: - description: Allow use of '/cbadmin reload' command - reload + description: Allow use of '/chadmin reload' command - reload default: OP chunkblock.admin.setspawn: - description: Allow use of '/cbadmin setspawn' command - set an island as spawn for this gamemode + description: Allow use of '/chadmin setspawn' command - set an island as spawn for this gamemode default: OP chunkblock.admin.setspawnpoint: - description: Allow use of '/cbadmin setspawnpoint' command - set current location as spawn point for this island + description: Allow use of '/chadmin setspawnpoint' command - set current location as spawn point for this island default: OP chunkblock.admin.resetflags: - description: Allow use of '/cbadmin resetflags' command - Reset all islands to default flag settings in config.yml + description: Allow use of '/chadmin resetflags' command - Reset all islands to default flag settings in config.yml default: OP chunkblock.mod.switch: - description: Allow use of '/cbadmin switch' command - switch on/off protection bypass + description: Allow use of '/chadmin switch' command - switch on/off protection bypass default: OP chunkblock.admin.purge: - description: Allow use of '/cbadmin purge' command - purge islands abandoned for more than [days] + description: Allow use of '/chadmin purge' command - purge islands abandoned for more than [days] default: OP chunkblock.admin.purge.status: - description: Allow use of '/cbadmin purge status' command - displays the status of the purge + description: Allow use of '/chadmin purge status' command - displays the status of the purge default: OP chunkblock.admin.purge.stop: - description: Allow use of '/cbadmin purge stop' command - stop a purge in progress + description: Allow use of '/chadmin purge stop' command - stop a purge in progress default: OP chunkblock.admin.purge.unowned: - description: Allow use of '/cbadmin purge unowned' command - purge unowned islands + description: Allow use of '/chadmin purge unowned' command - purge unowned islands default: OP chunkblock.admin.purge.protect: - description: Allow use of '/cbadmin purge protect' command - toggle island purge protection + description: Allow use of '/chadmin purge protect' command - toggle island purge protection default: OP chunkblock.admin.settings: - description: Allow use of '/cbadmin settings' command - open settings GUI or set settings + description: Allow use of '/chadmin settings' command - open settings GUI or set settings default: OP chunkblock.admin.setprotectionlocation: - description: Allow use of '/cbadmin setprotectionlocation' command - set current location or [x y z] as center of island's protection area + description: Allow use of '/chadmin setprotectionlocation' command - set current location or [x y z] as center of island's protection area default: OP chunkblock.mod.deletehomes: - description: Allow use of '/cbadmin deletehomes' command - deletes all named homes from an island + description: Allow use of '/chadmin deletehomes' command - deletes all named homes from an island default: OP chunkblock.mod.resetname: - description: Allow use of '/cbadmin resetname' command - reset player island name + description: Allow use of '/chadmin resetname' command - reset player island name default: OP chunkblock.admin.setcount: - description: Allow use of '/cbadmin setcount' command - set player's block count + description: Allow use of '/chadmin setcount' command - set player's block count default: OP chunkblock.admin.setchest: - description: Allow use of '/cbadmin setchest' command - put the looked-at chest in a phase with the rarity specified + description: Allow use of '/chadmin setchest' command - put the looked-at chest in a phase with the rarity specified default: OP chunkblock.admin.sanity: - description: Allow use of '/cbadmin sanity' command - display a sanity check of the phase probabilities in the console + description: Allow use of '/chadmin sanity' command - display a sanity check of the phase probabilities in the console default: OP chunkblock.admin.phases: - description: Allow use of '/cbadmin phases' command - open the phase order editor + description: Allow use of '/chadmin phases' command - open the phase order editor default: OP chunkblock.island.chunks: - description: Allow use of '/cb chunks' command - show your unlocked chunks and territory map + description: Allow use of '/ch chunks' command - show your unlocked chunks and territory map default: TRUE chunkblock.admin.chunks: - description: Allow use of '/cbadmin chunks' command - inspect, set or recalculate a player's unlocked chunks + description: Allow use of '/chadmin chunks' command - inspect, set or recalculate a player's unlocked chunks default: OP diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 4024d63..9083574 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -3,10 +3,10 @@ chunkblock: command: # Player command. What command users will run to access their island. # To define alias, just separate commands with white space. - island: cb chunkblock + island: ch chunkblock # The admin command. # To define alias, just separate commands with white space. - admin: cbadmin chunkblockadmin cba + admin: chadmin chunkblockadmin cha # The default action for new player command call. # Sub-command of main player command that will be run on first player command call. # By default, it is sub-command 'create'. @@ -174,7 +174,7 @@ world: # Values that are not a multiple of 8 are snapped to the nearest multiple on load. distance-between-islands: 256 # Default protection range radius in blocks. Cannot be larger than distance. - # Admins can change protection sizes for players individually using /cbadmin range set + # Admins can change protection sizes for players individually using /chadmin range set # or set this permission: chunkblock.island.range. # ChunkBlock: this must cover the largest unlockable ring of chunks (see chunkblock.max-chunks). protection-range: 240 @@ -461,7 +461,7 @@ island: # Accessed via /is sethome or /is go max-homes: 1 reset: - # How many resets a player is allowed (manage with /cbadmin reset add/remove/reset/set command) + # How many resets a player is allowed (manage with /chadmin reset add/remove/reset/set command) # Value of -1 means unlimited, 0 means hardcore - no resets. # Example, 2 resets means they get 2 resets or 3 islands lifetime reset-limit: -1 @@ -560,7 +560,7 @@ island: # # Here are some examples of valid commands to execute: # * "[SUDO] bbox version" - # * "cbadmin deaths set [player] 0" + # * "chadmin deaths set [player] 0" on-join: [] # List of commands to run when a player leaves an island, resets his island or gets kicked from it. # These commands are run by the console, unless otherwise stated using the [SUDO] prefix, @@ -571,7 +571,7 @@ island: # # Here are some examples of valid commands to execute: # * '[SUDO] bbox version' - # * 'cbadmin deaths set [player] 0' + # * 'chadmin deaths set [player] 0' # # Note that player-executed commands might not work, as these commands can be run with said player being offline. on-leave: [] @@ -584,7 +584,7 @@ island: # # Here are some examples of valid commands to execute: # * '[SUDO] bbox version' - # * 'cbadmin deaths set [player] 0' + # * 'chadmin deaths set [player] 0' # # Note that player-executed commands might not work, as these commands can be run with said player being offline. on-respawn: [] diff --git a/src/test/java/world/bentobox/chunkblock/SettingsTest.java b/src/test/java/world/bentobox/chunkblock/SettingsTest.java index 440af2c..bc2a554 100644 --- a/src/test/java/world/bentobox/chunkblock/SettingsTest.java +++ b/src/test/java/world/bentobox/chunkblock/SettingsTest.java @@ -1059,7 +1059,7 @@ void testSetBanLimit() { */ @Test void testGetPlayerCommandAliases() { - assertEquals("cb chunkblock",s.getPlayerCommandAliases()); + assertEquals("ch chunkblock",s.getPlayerCommandAliases()); } /** @@ -1067,7 +1067,7 @@ void testGetPlayerCommandAliases() { */ @Test void testSetPlayerCommandAliases() { - assertEquals("cb chunkblock",s.getPlayerCommandAliases()); + assertEquals("ch chunkblock",s.getPlayerCommandAliases()); s.setPlayerCommandAliases("aliases"); assertEquals("aliases",s.getPlayerCommandAliases()); } @@ -1077,7 +1077,7 @@ void testSetPlayerCommandAliases() { */ @Test void testGetAdminCommandAliases() { - assertEquals("cbadmin chunkblockadmin cba",s.getAdminCommandAliases()); + assertEquals("chadmin chunkblockadmin cha",s.getAdminCommandAliases()); } /** @@ -1085,7 +1085,7 @@ void testGetAdminCommandAliases() { */ @Test void testSetAdminCommandAliases() { - assertEquals("cbadmin chunkblockadmin cba",s.getAdminCommandAliases()); + assertEquals("chadmin chunkblockadmin cha",s.getAdminCommandAliases()); s.setAdminCommandAliases("aliases"); assertEquals("aliases",s.getAdminCommandAliases()); } From bdd9d970b6226900bb6eaf94a9567a0347aa37db Mon Sep 17 00:00:00 2001 From: tastybento Date: Wed, 29 Jul 2026 09:07:14 -0700 Subject: [PATCH 5/9] ci: bump publish-platforms pin to the multipart-JSON fix The pinned SHA predates BentoBoxWorld/.github#13, which moved the CurseForge metadata and Hangar versionUpload JSON out of inline curl -F values and into files. curl treats a ';' in an inline -F value as the start of a type= attribute, so a release body containing a semicolon truncates the JSON and both platforms reject the upload. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01SAQ3YQkcfRoCZEwa6SPJcp --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a386efb..4e34bb2 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -18,7 +18,7 @@ on: jobs: publish: - uses: bentoboxworld/.github/.github/workflows/publish-platforms.yml@ca2dcd167e8db4e0f671a976080744dda43801a6 # master + uses: bentoboxworld/.github/.github/workflows/publish-platforms.yml@1f91a0edf72e8c86d671b3b8fdd3121ac6fb88e1 # master with: use_release_asset: "true" # publish the jar attached to the release; do not rebuild hangar_slug: "ChunkBlock" # blank = skip Hangar From 9b8a25f40564ae85145639349266520fbcb2c025 Mon Sep 17 00:00:00 2001 From: tastybento Date: Wed, 29 Jul 2026 16:38:41 -0700 Subject: [PATCH 6/9] Give ChunkBlock its own flag IDs and harden the flag listeners MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related fixes around the flags copied from AOneBlock: 1. Rename ONEBLOCK_BOSSBAR/ONEBLOCK_ACTIONBAR/START_SAFETY to CHUNKBLOCK_BOSSBAR/CHUNKBLOCK_ACTIONBAR/CHUNKBLOCK_START_SAFETY. FlagsManager rejects duplicate flag IDs, so on a server running AOneBlock and ChunkBlock together, whichever addon loaded second silently lost its flag registration — and the surviving flag's listener belonged to the other addon, so the boss bar/action bar/start-safety features broke for one of the two gamemodes. Locale keys and default config are renamed to match; servers that changed these world settings from the defaults will need to re-apply them once under the new names. 2. Guard BossBarListener and StartSafetyListener against running for an addon that never enabled. These listeners are registered by FlagsManager when the flags are registered in onLoad, so they outlive a load failure — seen in the wild when the Level addon was removed: BentoBox drops ChunkBlock for the missing dependency after onLoad has run, the flag listeners stay registered with no world behind them, and every join and move then NPEs in inWorld() (BentoBoxWorld/BentoBox fix in progress too; this guard also protects released BentoBox versions). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Y7xSdVPS5vRu6wqf6XshRq --- .../world/bentobox/chunkblock/ChunkBlock.java | 14 +++++------ .../island/IslandActionBarCommand.java | 6 ++--- .../commands/island/IslandBossBarCommand.java | 2 +- .../chunkblock/listeners/BossBarListener.java | 24 ++++++++++--------- .../listeners/StartSafetyListener.java | 14 +++++++---- src/main/resources/config.yml | 4 ++-- src/main/resources/locales/cs.yml | 6 ++--- src/main/resources/locales/de.yml | 6 ++--- src/main/resources/locales/en-US.yml | 6 ++--- src/main/resources/locales/es.yml | 6 ++--- src/main/resources/locales/fr.yml | 6 ++--- src/main/resources/locales/hr.yml | 6 ++--- src/main/resources/locales/hu.yml | 6 ++--- src/main/resources/locales/id.yml | 6 ++--- src/main/resources/locales/it.yml | 6 ++--- src/main/resources/locales/ja.yml | 6 ++--- src/main/resources/locales/pl.yml | 6 ++--- src/main/resources/locales/pt.yml | 6 ++--- src/main/resources/locales/ru.yml | 6 ++--- src/main/resources/locales/tr.yml | 6 ++--- src/main/resources/locales/uk.yml | 6 ++--- src/main/resources/locales/vi.yml | 6 ++--- src/main/resources/locales/zh-CN.yml | 6 ++--- src/main/resources/locales/zh-TW.yml | 6 ++--- .../listeners/BossBarListenerTest.java | 8 +++---- .../listeners/StartSafetyListenerTest.java | 2 +- 26 files changed, 95 insertions(+), 87 deletions(-) diff --git a/src/main/java/world/bentobox/chunkblock/ChunkBlock.java b/src/main/java/world/bentobox/chunkblock/ChunkBlock.java index 92f4dde..ce490d5 100644 --- a/src/main/java/world/bentobox/chunkblock/ChunkBlock.java +++ b/src/main/java/world/bentobox/chunkblock/ChunkBlock.java @@ -95,7 +95,7 @@ public class ChunkBlock extends GameModeAddon { /** * Flag to enable or disable start safety for players. */ - public final Flag START_SAFETY = new Flag.Builder("START_SAFETY", Material.BAMBOO_BLOCK) + public final Flag CHUNKBLOCK_START_SAFETY = new Flag.Builder("CHUNKBLOCK_START_SAFETY", Material.BAMBOO_BLOCK) .mode(Mode.BASIC) .type(Type.WORLD_SETTING) .listener(new StartSafetyListener(this)) @@ -106,7 +106,7 @@ public class ChunkBlock extends GameModeAddon { /** * Flag to enable or disable the OneBlock boss bar. */ - public final Flag ONEBLOCK_BOSSBAR = new Flag.Builder("ONEBLOCK_BOSSBAR", Material.DRAGON_HEAD) + public final Flag CHUNKBLOCK_BOSSBAR = new Flag.Builder("CHUNKBLOCK_BOSSBAR", Material.DRAGON_HEAD) .mode(Mode.BASIC) .type(Type.SETTING) .listener(bossBar) @@ -115,7 +115,7 @@ public class ChunkBlock extends GameModeAddon { /** * Flag to enable or disable the OneBlock action bar. */ - public final Flag ONEBLOCK_ACTIONBAR = new Flag.Builder("ONEBLOCK_ACTIONBAR", Material.IRON_BARS) + public final Flag CHUNKBLOCK_ACTIONBAR = new Flag.Builder("CHUNKBLOCK_ACTIONBAR", Material.IRON_BARS) .mode(Mode.BASIC) .type(Type.SETTING) .listener(bossBar) @@ -165,14 +165,14 @@ public void onLoad() { adminCommand = new AdminCommand(this); // Register flag with BentoBox // Register protection flag with BentoBox - getPlugin().getFlagsManager().registerFlag(this, START_SAFETY); + getPlugin().getFlagsManager().registerFlag(this, CHUNKBLOCK_START_SAFETY); // Bossbar if (getSettings().isBossBar()) { - getPlugin().getFlagsManager().registerFlag(this, this.ONEBLOCK_BOSSBAR); + getPlugin().getFlagsManager().registerFlag(this, this.CHUNKBLOCK_BOSSBAR); } // Actionbar if (getSettings().isActionBar()) { - getPlugin().getFlagsManager().registerFlag(this, this.ONEBLOCK_ACTIONBAR); + getPlugin().getFlagsManager().registerFlag(this, this.CHUNKBLOCK_ACTIONBAR); } // Magic Block protection getPlugin().getFlagsManager().registerFlag(this, this.MAGIC_BLOCK); @@ -226,7 +226,7 @@ public void onEnable() { registerListener(new JoinLeaveListener(this)); registerListener(new InfoListener(this)); // Note: bossBar is registered as a listener by the FlagsManager when the - // ONEBLOCK_BOSSBAR or ONEBLOCK_ACTIONBAR flag is registered in onLoad, so it + // CHUNKBLOCK_BOSSBAR or CHUNKBLOCK_ACTIONBAR flag is registered in onLoad, so it // must not be registered here too or events would be handled twice // Register placeholders phManager = new ChunkBlockPlaceholders(this, getPlugin().getPlaceholdersManager()); diff --git a/src/main/java/world/bentobox/chunkblock/commands/island/IslandActionBarCommand.java b/src/main/java/world/bentobox/chunkblock/commands/island/IslandActionBarCommand.java index c06942a..4b645ae 100644 --- a/src/main/java/world/bentobox/chunkblock/commands/island/IslandActionBarCommand.java +++ b/src/main/java/world/bentobox/chunkblock/commands/island/IslandActionBarCommand.java @@ -29,13 +29,13 @@ public void setup() { @Override public boolean execute(User user, String label, List args) { getIslands().getIslandAt(user.getLocation()).ifPresent(i -> { - if (!i.isAllowed(addon.ONEBLOCK_ACTIONBAR)) { + if (!i.isAllowed(addon.CHUNKBLOCK_ACTIONBAR)) { user.sendMessage("chunkblock.actionbar.not-active"); } }); // Toggle state - boolean newState = !user.getMetaData(BossBarListener.AONEBLOCK_ACTIONBAR).map(MetaDataValue::asBoolean).orElse(true); - user.putMetaData(BossBarListener.AONEBLOCK_ACTIONBAR, new MetaDataValue(newState)); + boolean newState = !user.getMetaData(BossBarListener.ACTIONBAR_METADATA).map(MetaDataValue::asBoolean).orElse(true); + user.putMetaData(BossBarListener.ACTIONBAR_METADATA, new MetaDataValue(newState)); if (newState) { user.sendMessage("chunkblock.commands.island.actionbar.status_on"); } else { diff --git a/src/main/java/world/bentobox/chunkblock/commands/island/IslandBossBarCommand.java b/src/main/java/world/bentobox/chunkblock/commands/island/IslandBossBarCommand.java index 8ed54fc..87d0469 100644 --- a/src/main/java/world/bentobox/chunkblock/commands/island/IslandBossBarCommand.java +++ b/src/main/java/world/bentobox/chunkblock/commands/island/IslandBossBarCommand.java @@ -28,7 +28,7 @@ public void setup() { public boolean execute(User user, String label, List args) { addon.getBossBar().toggleUser(user); getIslands().getIslandAt(user.getLocation()).ifPresent(i -> { - if (!i.isAllowed(addon.ONEBLOCK_BOSSBAR)) { + if (!i.isAllowed(addon.CHUNKBLOCK_BOSSBAR)) { user.sendMessage("chunkblock.bossbar.not-active"); } }); diff --git a/src/main/java/world/bentobox/chunkblock/listeners/BossBarListener.java b/src/main/java/world/bentobox/chunkblock/listeners/BossBarListener.java index f9d1765..a059454 100644 --- a/src/main/java/world/bentobox/chunkblock/listeners/BossBarListener.java +++ b/src/main/java/world/bentobox/chunkblock/listeners/BossBarListener.java @@ -32,8 +32,8 @@ public class BossBarListener implements Listener { - private static final String AONEBLOCK_BOSSBAR = "chunkblock.bossbar"; - public static final String AONEBLOCK_ACTIONBAR = "chunkblock.actionbar"; + private static final String BOSSBAR_METADATA = "chunkblock.bossbar"; + public static final String ACTIONBAR_METADATA = "chunkblock.actionbar"; private static final LegacyComponentSerializer LEGACY_SERIALIZER = LegacyComponentSerializer.builder() .character('&') @@ -59,7 +59,9 @@ public void onBreakBlockEvent(MagicBlockEvent e) { @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onEnterIsland(IslandEnterEvent event) { - if (addon.inWorld(event.getIsland().getWorld())) { + // getOverWorld() is null when the addon never enabled (missing dependency); + // this listener outlives that because it is registered with the flags in onLoad + if (addon.getOverWorld() != null && addon.inWorld(event.getIsland().getWorld())) { tryToShowBossBar(event.getPlayerUUID(), event.getIsland()); tryToShowActionBar(event.getPlayerUUID(), event.getIsland()); } @@ -67,7 +69,7 @@ public void onEnterIsland(IslandEnterEvent event) { @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onFlagChange(FlagSettingChangeEvent e) { - if (e.getEditedFlag() == addon.ONEBLOCK_BOSSBAR) { + if (e.getEditedFlag() == addon.CHUNKBLOCK_BOSSBAR) { // Show to players on island. If it isn't allowed then this will clean up the boss bar too e.getIsland().getPlayersOnIsland().stream().map(Player::getUniqueId) .forEach(uuid -> { @@ -100,11 +102,11 @@ private void tryToShowActionBar(UUID uuid, Island island) { Player player = Bukkit.getPlayer(uuid); // Only show if enabled for island - if (!island.isAllowed(addon.ONEBLOCK_ACTIONBAR)) { + if (!island.isAllowed(addon.CHUNKBLOCK_ACTIONBAR)) { return; } // Default to showing action bar unless it is explicitly turned off - if (!user.getMetaData(AONEBLOCK_ACTIONBAR).map(MetaDataValue::asBoolean).orElse(true)) { + if (!user.getMetaData(ACTIONBAR_METADATA).map(MetaDataValue::asBoolean).orElse(true)) { // Do not show an action bar return; } @@ -138,7 +140,7 @@ private void tryToShowBossBar(UUID uuid, Island island) { User user = User.getInstance(uuid); // Only show if enabled for island - if (!island.isAllowed(addon.ONEBLOCK_BOSSBAR)) { + if (!island.isAllowed(addon.CHUNKBLOCK_BOSSBAR)) { BossBar removed = islandBossBars.remove(island); if (removed != null) { // Remove all players from the boss bar @@ -147,7 +149,7 @@ private void tryToShowBossBar(UUID uuid, Island island) { return; } // Default to showing boss bar unless it is explicitly turned off - if (!user.getMetaData(AONEBLOCK_BOSSBAR).map(MetaDataValue::asBoolean).orElse(true)) { + if (!user.getMetaData(BOSSBAR_METADATA).map(MetaDataValue::asBoolean).orElse(true)) { // Remove any boss bar from user if they are in the world removeBar(user, island); // Do not show a boss bar @@ -221,7 +223,7 @@ public void onExitIsland(IslandExitEvent event) { public void onJoin(PlayerJoinEvent e) { // If the player is on an island then show the bar Location playerLoc = e.getPlayer().getLocation(); - if (playerLoc == null || !addon.inWorld(playerLoc)) { + if (addon.getOverWorld() == null || playerLoc == null || !addon.inWorld(playerLoc)) { return; } addon.getIslands().getIslandAt(playerLoc) @@ -240,8 +242,8 @@ public void onQuit(PlayerQuitEvent e) { * @param user user to toggle */ public void toggleUser(User user) { - boolean newState = !user.getMetaData(AONEBLOCK_BOSSBAR).map(MetaDataValue::asBoolean).orElse(true); - user.putMetaData(AONEBLOCK_BOSSBAR, new MetaDataValue(newState)); + boolean newState = !user.getMetaData(BOSSBAR_METADATA).map(MetaDataValue::asBoolean).orElse(true); + user.putMetaData(BOSSBAR_METADATA, new MetaDataValue(newState)); if (newState) { // If the player is on an island then show the bar addon.getIslands().getIslandAt(user.getLocation()).filter(is -> addon.inWorld(is.getWorld())) diff --git a/src/main/java/world/bentobox/chunkblock/listeners/StartSafetyListener.java b/src/main/java/world/bentobox/chunkblock/listeners/StartSafetyListener.java index 69870d1..54bfef4 100644 --- a/src/main/java/world/bentobox/chunkblock/listeners/StartSafetyListener.java +++ b/src/main/java/world/bentobox/chunkblock/listeners/StartSafetyListener.java @@ -37,7 +37,12 @@ public void onNewIsland(IslandCreatedEvent e) { } private void store(World world, UUID playerUUID) { - if (addon.inWorld(world) && addon.START_SAFETY.isSetForWorld(world) && !newIslands.containsKey(playerUUID)) { + // This listener is registered with the CHUNKBLOCK_START_SAFETY flag in onLoad, so it can + // outlive an addon that never enabled (e.g. missing dependency) and has no world + if (addon.getOverWorld() == null) { + return; + } + if (addon.inWorld(world) && addon.CHUNKBLOCK_START_SAFETY.isSetForWorld(world) && !newIslands.containsKey(playerUUID)) { long time = addon.getSettings().getStartingSafetyDuration(); if (time < 0) { time = 10; // 10 seconds @@ -45,7 +50,7 @@ private void store(World world, UUID playerUUID) { newIslands.put(playerUUID, System.currentTimeMillis() + (time * 1000)); Bukkit.getScheduler().runTaskLater(addon.getPlugin(), () -> { newIslands.remove(playerUUID); - User.getInstance(playerUUID).sendMessage("protection.flags.START_SAFETY.free-to-move"); + User.getInstance(playerUUID).sendMessage("protection.flags.CHUNKBLOCK_START_SAFETY.free-to-move"); }, time); } @@ -58,7 +63,8 @@ public void onResetIsland(IslandResetEvent e) { @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onPlayerMove(PlayerMoveEvent e) { - if (addon.inWorld(e.getPlayer().getWorld()) && newIslands.containsKey(e.getPlayer().getUniqueId()) + if (addon.getOverWorld() != null && addon.inWorld(e.getPlayer().getWorld()) + && newIslands.containsKey(e.getPlayer().getUniqueId()) && !e.getPlayer().isSneaking() && (e.getFrom().getX() != e.getTo().getX() || e.getFrom().getZ() != e.getTo().getZ())) { // Do not allow x or z movement @@ -66,7 +72,7 @@ public void onPlayerMove(PlayerMoveEvent e) { e.getTo().getYaw(), e.getTo().getPitch())); String waitTime = String .valueOf((int) ((newIslands.get(e.getPlayer().getUniqueId()) - System.currentTimeMillis()) / 1000)); - User.getInstance(e.getPlayer()).notify(addon.START_SAFETY.getHintReference(), TextVariables.NUMBER, + User.getInstance(e.getPlayer()).notify(addon.CHUNKBLOCK_START_SAFETY.getHintReference(), TextVariables.NUMBER, waitTime); } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 9083574..3897aa6 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -290,7 +290,7 @@ world: CLEAN_SUPER_FLAT: false CHEST_DAMAGE: false PREVENT_TELEPORT_WHEN_FALLING: false - START_SAFETY: false + CHUNKBLOCK_START_SAFETY: false ENTER_EXIT_MESSAGES: true ALLOW_MOVE_BOX: true ENDERMAN_DEATH_DROP: true @@ -421,7 +421,7 @@ world: TNT_DAMAGE: true MONSTER_SPAWNERS_SPAWN: true FIRE_IGNITE: true - ONEBLOCK_BOSSBAR: true + CHUNKBLOCK_BOSSBAR: true BLOCK_EXPLODE_DAMAGE: true ANIMAL_SPAWNERS_SPAWN: true # These settings/flags are hidden from users diff --git a/src/main/resources/locales/cs.yml b/src/main/resources/locales/cs.yml index 739d786..dbf0f44 100644 --- a/src/main/resources/locales/cs.yml +++ b/src/main/resources/locales/cs.yml @@ -7,7 +7,7 @@ protection: &b kouzelný blok, pokud &b dokáže rozbíjet bloky. hint: "&c Vaše hodnost nemůže rozbít kouzelný blok!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: Počáteční Bezpečnost description: |- &b Zabrání novým hráčům @@ -15,12 +15,12 @@ protection: &b aby nespadli. hint: "&c Pohyb zablokován kvůli bezpečnosti na [number] sekund!" free-to-move: "&a Můžete se volně pohybovat. Buďte opatrní!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Boss Bar description: |- &b Zobrazuje stavový panel &b pro každou fázi. - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: Action Bar description: |- &b Zobrazuje stav diff --git a/src/main/resources/locales/de.yml b/src/main/resources/locales/de.yml index 163b1fc..5cd4cb5 100644 --- a/src/main/resources/locales/de.yml +++ b/src/main/resources/locales/de.yml @@ -7,7 +7,7 @@ protection: &b Block zerstören kann, falls &b Blöcke zerstört werden können. hint: "&c Dein Rang kann den magischen Block nicht zerstören!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: Start-Sicherheit description: |- &b Verhindert, dass neue Spieler @@ -15,12 +15,12 @@ protection: &b damit sie nicht herunterfallen. hint: "&c Bewegung aus Sicherheitsgründen für [number] weitere Sekunden blockiert!" free-to-move: "&a Du kannst dich frei bewegen. Sei vorsichtig!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Boss-Balken description: |- &b Zeigt eine Statusleiste &b für jede Phase. - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: Action-Leiste description: |- &b Zeigt einen Status diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index e310d6e..aae8140 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -11,7 +11,7 @@ protection: &b Rank that can break the magic &b block if they can break blocks. hint: "&c Your rank cannot break the magic block!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: Starting Safety description: |- &b Prevents new players @@ -19,12 +19,12 @@ protection: &b so they don't fall off. hint: "&c Movement blocked for safety for [number] more seconds!" free-to-move: "&a You are free to move. Be careful!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Boss Bar description: |- &b Shows a status bar &b for each phase. - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: Action Bar description: |- &b Shows a status diff --git a/src/main/resources/locales/es.yml b/src/main/resources/locales/es.yml index 551b99b..3558929 100644 --- a/src/main/resources/locales/es.yml +++ b/src/main/resources/locales/es.yml @@ -7,7 +7,7 @@ protection: &b bloque mágico si puede &b romper bloques. hint: "&c ¡Tu rango no puede romper el bloque mágico!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: Seguridad Inicial description: |- &b Evita que los nuevos jugadores @@ -15,12 +15,12 @@ protection: &b para que no se caigan. hint: "&c Movimiento bloqueado por seguridad durante [number] segundos más!" free-to-move: "&a Eres libre de moverte. ¡Ten cuidado!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Barra de Jefe (Boss Bar) description: |- &b Muestra una barra de estado &b para cada fase. - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: Barra de Acción (Action Bar) description: |- &b Muestra un estado diff --git a/src/main/resources/locales/fr.yml b/src/main/resources/locales/fr.yml index b59276c..531f1a4 100644 --- a/src/main/resources/locales/fr.yml +++ b/src/main/resources/locales/fr.yml @@ -7,7 +7,7 @@ protection: &b magique s'il peut casser &b des blocs. hint: "&c Votre rang ne peut pas casser le bloc magique!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: Sécurité de Départ description: |- &b Empêche les nouveaux joueurs @@ -15,12 +15,12 @@ protection: &b pour qu'ils ne tombent pas. hint: "&c Mouvement bloqué par sécurité pendant [number] secondes supplémentaires!" free-to-move: "&a Vous êtes libre de bouger. Faites attention!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Barre de Boss description: |- &b Affiche une barre de statut &b pour chaque phase. - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: Barre d'Action description: |- &b Affiche un statut diff --git a/src/main/resources/locales/hr.yml b/src/main/resources/locales/hr.yml index 21d3da7..6cb4138 100644 --- a/src/main/resources/locales/hr.yml +++ b/src/main/resources/locales/hr.yml @@ -7,7 +7,7 @@ protection: &b magični blok, ako &b može razbiti blokove. hint: "&c Vaš rang ne može razbiti magični blok!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: Početna Sigurnost description: |- &b Sprječava nove igrače @@ -15,12 +15,12 @@ protection: &b da ne padnu. hint: "&c Kretanje je blokirano iz sigurnosnih razloga još [number] sekundi!" free-to-move: "&a Slobodni ste za kretanje. Budite oprezni!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Boss Traka description: |- &b Prikazuje statusnu traku &b za svaku fazu. - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: Traka Akcije description: |- &b Prikazuje status diff --git a/src/main/resources/locales/hu.yml b/src/main/resources/locales/hu.yml index cc68103..aaba727 100644 --- a/src/main/resources/locales/hu.yml +++ b/src/main/resources/locales/hu.yml @@ -8,7 +8,7 @@ protection: &b blokkot, ha tud blokkokat &b rombolni. hint: "&c A rangod nem törheti szét a mágikus blokkot!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: Kezdő Biztonság description: |- &b Megakadályozza az új játékosokat @@ -16,12 +16,12 @@ protection: &b hogy ne essenek le. hint: "&c Mozgás blokkolva biztonsági okokból még [number] másodpercig!" free-to-move: "&a Szabadon mozoghatsz. Légy óvatos!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Boss Bar description: |- &b Állapotjelző sávot &b mutat minden fázishoz. - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: Műveleti Sáv (Action Bar) description: |- &b Állapotot mutat diff --git a/src/main/resources/locales/id.yml b/src/main/resources/locales/id.yml index c8abbe9..dce0c28 100644 --- a/src/main/resources/locales/id.yml +++ b/src/main/resources/locales/id.yml @@ -7,7 +7,7 @@ protection: &b menghancurkan blok ajaib &b jika mereka dapat menghancurkan blok. hint: "&c Pangkatmu tidak dapat menghancurkan blok ajaib!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: Keamanan Awal description: |- &b Mencegah pemain baru @@ -15,12 +15,12 @@ protection: &b agar mereka tidak jatuh. hint: "&c Pergerakan diblokir untuk keselamatan selama [number] detik lagi!" free-to-move: "&a Anda bebas bergerak. Hati-hati!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Boss Bar description: |- &b Menampilkan bilah status &b untuk setiap fase. - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: Action Bar description: |- &b Menampilkan status diff --git a/src/main/resources/locales/it.yml b/src/main/resources/locales/it.yml index 0c63978..f9bcbf4 100644 --- a/src/main/resources/locales/it.yml +++ b/src/main/resources/locales/it.yml @@ -7,7 +7,7 @@ protection: &b blocco magico se può &b rompere i blocchi. hint: "&c Il tuo rango non può rompere il blocco magico!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: Sicurezza Iniziale description: |- &b Impedisce ai nuovi giocatori @@ -15,12 +15,12 @@ protection: &b in modo che non cadano. hint: "&c Movimento bloccato per sicurezza per [number] secondi ancora!" free-to-move: "&a Sei libero di muoverti. Fai attenzione!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Boss Bar description: |- &b Mostra una barra di stato &b per ogni fase. - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: Action Bar description: |- &b Mostra uno stato diff --git a/src/main/resources/locales/ja.yml b/src/main/resources/locales/ja.yml index 328e6c1..fc08cba 100644 --- a/src/main/resources/locales/ja.yml +++ b/src/main/resources/locales/ja.yml @@ -7,7 +7,7 @@ protection: &b 魔法ブロックを破壊できる &b ランク。 hint: "&c あなたのランクでは魔法ブロックを破壊できません!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: 開始時の安全対策 description: |- &b 新しいプレイヤーが @@ -15,12 +15,12 @@ protection: &b 落下を防ぎます。 hint: "&c 安全のため、あと [number] 秒間移動がブロックされています!" free-to-move: "&a 自由に動けます。注意してください!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: ボスバー description: |- &b 各フェーズの &b ステータスバーを表示します。 - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: アクションバー description: |- &b 各フェーズの diff --git a/src/main/resources/locales/pl.yml b/src/main/resources/locales/pl.yml index a955774..24e7225 100644 --- a/src/main/resources/locales/pl.yml +++ b/src/main/resources/locales/pl.yml @@ -7,7 +7,7 @@ protection: &b magiczny blok, jeśli &b może niszczyć bloki. hint: "&c Twoja ranga nie może zniszczyć magicznego bloku!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: Bezpieczeństwo Początkowe description: |- &b Zapobiega poruszaniu się @@ -15,12 +15,12 @@ protection: &b aby nie spadli. hint: "&c Ruch zablokowany ze względów bezpieczeństwa na kolejne [number] sekund!" free-to-move: "&a Możesz się swobodnie poruszać. Bądź ostrożny!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Pasek Bossa description: |- &b Pokazuje pasek statusu &b dla każdej fazy. - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: Pasek Akcji description: |- &b Pokazuje status diff --git a/src/main/resources/locales/pt.yml b/src/main/resources/locales/pt.yml index 1289c9f..ef33ccb 100644 --- a/src/main/resources/locales/pt.yml +++ b/src/main/resources/locales/pt.yml @@ -7,7 +7,7 @@ protection: &b bloco mágico se puder &b quebrar blocos. hint: "&c Seu rank não pode quebrar o bloco mágico!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: Segurança Inicial description: |- &b Impede que novos jogadores @@ -15,12 +15,12 @@ protection: &b para que não caiam. hint: "&c Movimento bloqueado por segurança por mais [number] segundos!" free-to-move: "&a Você está livre para se mover. Tenha cuidado!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Boss Bar description: |- &b Mostra uma barra de status &b para cada fase. - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: Action Bar description: |- &b Mostra um status diff --git a/src/main/resources/locales/ru.yml b/src/main/resources/locales/ru.yml index b7485f4..1f3ece3 100644 --- a/src/main/resources/locales/ru.yml +++ b/src/main/resources/locales/ru.yml @@ -11,17 +11,17 @@ protection: Предотвращает ломание магического блока hint: Ломание магического блока запрещено! - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: Защита новичков от падений description: |- Запрещает новичкам двигаться в течение 1 минуты hint: Движение заблокировано в целях безопасности ещё на [number] секунд! free-to-move: Теперь вы можете двигаться. Будьте осторожны! - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Боссбар description: Показывает статус фазы в боссбаре - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: Панель действий description: Показывает статус фазы в панели действий chunkblock: diff --git a/src/main/resources/locales/tr.yml b/src/main/resources/locales/tr.yml index 5c351e6..351ac03 100644 --- a/src/main/resources/locales/tr.yml +++ b/src/main/resources/locales/tr.yml @@ -7,7 +7,7 @@ protection: &b büyülü bloğu kırabilecek &b rütbe. hint: "&c Rütbeniz büyülü bloğu kıramaz!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: Başlangıç Güvenliği description: |- &b Yeni oyuncuların 1 dakika @@ -15,12 +15,12 @@ protection: &b engelleyerek düşmelerini önler. hint: "&c Güvenlik için hareket [number] saniye daha engellendi!" free-to-move: "&a Serbestçe hareket edebilirsiniz. Dikkatli olun!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Boss Çubuğu description: |- &b Her aşama için bir &b durum çubuğu gösterir. - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: Eylem Çubuğu (Action Bar) description: |- &b Her aşama için bir diff --git a/src/main/resources/locales/uk.yml b/src/main/resources/locales/uk.yml index c093f22..76b12bb 100644 --- a/src/main/resources/locales/uk.yml +++ b/src/main/resources/locales/uk.yml @@ -7,7 +7,7 @@ protection: &b магічний блок, якщо &b може ламати блоки. hint: "&c Ваш ранг не може зламати магічний блок!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: Початкова Безпека description: |- &b Запобігає руху нових гравців @@ -15,12 +15,12 @@ protection: &b щоб вони не впали. hint: "&c Рух заблоковано з міркувань безпеки ще на [number] секунд!" free-to-move: "&a Ви можете вільно рухатися. Будьте обережні!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Boss Bar description: |- &b Показує панель стану &b для кожної фази. - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: Action Bar description: |- &b Показує статус diff --git a/src/main/resources/locales/vi.yml b/src/main/resources/locales/vi.yml index 1d04675..53510a5 100644 --- a/src/main/resources/locales/vi.yml +++ b/src/main/resources/locales/vi.yml @@ -7,7 +7,7 @@ protection: &b khối ma thuật nếu họ &b có thể phá khối. hint: "&c Xếp hạng của bạn không thể phá khối ma thuật!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: An Toàn Khởi Đầu description: |- &b Ngăn người chơi mới @@ -15,12 +15,12 @@ protection: &b để họ không bị rơi. hint: "&c Di chuyển bị chặn vì lý do an toàn trong [number] giây nữa!" free-to-move: "&a Bạn được phép di chuyển tự do. Hãy cẩn thận!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Thanh Boss description: |- &b Hiển thị thanh trạng thái &b cho mỗi giai đoạn. - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: Thanh Hành Động (Action Bar) description: |- &b Hiển thị trạng thái diff --git a/src/main/resources/locales/zh-CN.yml b/src/main/resources/locales/zh-CN.yml index d096b91..dc17eb0 100644 --- a/src/main/resources/locales/zh-CN.yml +++ b/src/main/resources/locales/zh-CN.yml @@ -7,19 +7,19 @@ protection: &b 则该等级可以破坏 &b 魔法方块。 hint: "&c 您的等级无法破坏魔法方块!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: 初始安全保护 description: |- &b 阻止新玩家在1分钟内 &b 移动,以防他们跌落。 hint: "&c 出于安全考虑,移动已被阻止 [number] 秒!" free-to-move: "&a 您可以自由移动了。请小心!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Boss 血条 description: |- &b 为每个阶段 &b 显示一个状态条。 - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: 动作栏 description: |- &b 在动作栏中 diff --git a/src/main/resources/locales/zh-TW.yml b/src/main/resources/locales/zh-TW.yml index 46c44b5..b3dd13f 100644 --- a/src/main/resources/locales/zh-TW.yml +++ b/src/main/resources/locales/zh-TW.yml @@ -7,19 +7,19 @@ protection: &b 則該等級可以破壞 &b 魔法方塊。 hint: "&c 您的等級無法破壞魔法方塊!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: 初始安全保護 description: |- &b 阻止新玩家在1分鐘內 &b 移動,以防他們跌落。 hint: "&c 出於安全考慮,移動已被阻止 [number] 秒!" free-to-move: "&a 您可以自由移動了。請小心!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Boss 血條 description: |- &b 為每個階段 &b 顯示一個狀態條。 - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: 動作欄 description: |- &b 在動作欄中 diff --git a/src/test/java/world/bentobox/chunkblock/listeners/BossBarListenerTest.java b/src/test/java/world/bentobox/chunkblock/listeners/BossBarListenerTest.java index 1b0ed66..9202693 100644 --- a/src/test/java/world/bentobox/chunkblock/listeners/BossBarListenerTest.java +++ b/src/test/java/world/bentobox/chunkblock/listeners/BossBarListenerTest.java @@ -92,7 +92,7 @@ private void fireMagicBlockEvent() { */ @Test void testActionBarShownWhenEnabled() { - when(island.isAllowed(addon.ONEBLOCK_ACTIONBAR)).thenReturn(true); + when(island.isAllowed(addon.CHUNKBLOCK_ACTIONBAR)).thenReturn(true); fireMagicBlockEvent(); verify(mockPlayer).sendActionBar(any(Component.class)); } @@ -105,7 +105,7 @@ void testActionBarShownWhenEnabled() { @Test void testActionBarNotShownWhenDisabledInConfig() { settings.setActionBar(false); - when(island.isAllowed(addon.ONEBLOCK_ACTIONBAR)).thenReturn(true); + when(island.isAllowed(addon.CHUNKBLOCK_ACTIONBAR)).thenReturn(true); fireMagicBlockEvent(); verify(mockPlayer, never()).sendActionBar(any(Component.class)); } @@ -125,7 +125,7 @@ void testActionBarNotShownWhenFlagDenied() { */ @Test void testBossBarShownWhenEnabled() { - when(island.isAllowed(addon.ONEBLOCK_BOSSBAR)).thenReturn(true); + when(island.isAllowed(addon.CHUNKBLOCK_BOSSBAR)).thenReturn(true); fireMagicBlockEvent(); verify(bossBar).addPlayer(mockPlayer); } @@ -136,7 +136,7 @@ void testBossBarShownWhenEnabled() { @Test void testBossBarNotShownWhenDisabledInConfig() { settings.setBossBar(false); - when(island.isAllowed(addon.ONEBLOCK_BOSSBAR)).thenReturn(true); + when(island.isAllowed(addon.CHUNKBLOCK_BOSSBAR)).thenReturn(true); fireMagicBlockEvent(); mockedBukkit.verify(() -> Bukkit.createBossBar(anyString(), any(), any()), never()); verify(bossBar, never()).addPlayer(any()); diff --git a/src/test/java/world/bentobox/chunkblock/listeners/StartSafetyListenerTest.java b/src/test/java/world/bentobox/chunkblock/listeners/StartSafetyListenerTest.java index fb442db..432d976 100644 --- a/src/test/java/world/bentobox/chunkblock/listeners/StartSafetyListenerTest.java +++ b/src/test/java/world/bentobox/chunkblock/listeners/StartSafetyListenerTest.java @@ -75,7 +75,7 @@ public void setUp() throws Exception { when(location2.getX()).thenReturn(0.5D); - addon.START_SAFETY.setSetting(world, true); + addon.CHUNKBLOCK_START_SAFETY.setSetting(world, true); ssl = new StartSafetyListener(addon); } From fcc93ac77f8bb8db60059f7d9aaeea9bbd991ee2 Mon Sep 17 00:00:00 2001 From: tastybento Date: Wed, 29 Jul 2026 16:53:32 -0700 Subject: [PATCH 7/9] Disable ChunkBlock cleanly when the Level addon is missing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ChunkBlock cannot function without Level: island levels are the currency spent to claim chunks. addon.yml already declares the hard dependency (depend: Level), which makes BentoBox refuse to load the addon when Level is absent — but that path runs after onLoad has registered flags, and on current BentoBox releases the flag listeners then linger with no world behind them (see 9b8a25f). onEnable now checks for the Level addon itself: if it is not on the server, ChunkBlock logs a clear pair of error lines, unregisters its own flags so no listeners survive, sets its state to DISABLED, and stops before initializing anything. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Y7xSdVPS5vRu6wqf6XshRq --- .../world/bentobox/chunkblock/ChunkBlock.java | 13 +++++++++++ .../bentobox/chunkblock/ChunkBlockTest.java | 22 ++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/chunkblock/ChunkBlock.java b/src/main/java/world/bentobox/chunkblock/ChunkBlock.java index ce490d5..b2fb86e 100644 --- a/src/main/java/world/bentobox/chunkblock/ChunkBlock.java +++ b/src/main/java/world/bentobox/chunkblock/ChunkBlock.java @@ -43,6 +43,7 @@ import world.bentobox.chunkblock.requests.UnlockedChunksHandler; import world.bentobox.chunkblock.requests.LocationStatsHandler; import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.addons.Addon.State; import world.bentobox.bentobox.api.configuration.Config; import world.bentobox.bentobox.api.configuration.WorldSettings; import world.bentobox.bentobox.api.flags.Flag; @@ -200,6 +201,18 @@ private boolean loadSettings() { @Override public void onEnable() { + // ChunkBlock cannot run without the Level addon: island levels are the currency + // spent to claim chunks. addon.yml declares the hard dependency, but check here + // too so a missing Level shuts the addon down cleanly instead of leaving it + // half-alive. + if (getAddonByName("Level").isEmpty()) { + logError("ChunkBlock requires the Level addon - island levels are the currency used to claim chunks."); + logError("Install Level from https://github.com/BentoBoxWorld/Level or remove ChunkBlock. Disabling."); + // Take down the flags registered in onLoad so their listeners do not linger + getPlugin().getFlagsManager().unregister(this); + setState(State.DISABLED); + return; + } // Initialize the OneBlock manager oneBlockManager = new OneBlocksManager(this); // Initialize the chunk lock manager diff --git a/src/test/java/world/bentobox/chunkblock/ChunkBlockTest.java b/src/test/java/world/bentobox/chunkblock/ChunkBlockTest.java index 71d4fa7..8c6d1e5 100644 --- a/src/test/java/world/bentobox/chunkblock/ChunkBlockTest.java +++ b/src/test/java/world/bentobox/chunkblock/ChunkBlockTest.java @@ -19,6 +19,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collections; +import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.jar.JarEntry; @@ -34,6 +35,7 @@ import world.bentobox.chunkblock.dataobjects.OneBlockIslands; import world.bentobox.bentobox.Settings; +import world.bentobox.bentobox.api.addons.Addon; import world.bentobox.bentobox.api.addons.Addon.State; import world.bentobox.bentobox.api.addons.AddonDescription; import world.bentobox.bentobox.api.user.User; @@ -138,9 +140,10 @@ public void setUp() throws Exception { AddonDescription desc = new AddonDescription.Builder("bentobox", "chunkblock", "1.3").description("test") .authors("tasty").build(); addon.setDescription(desc); - // Addons manager + // Addons manager - the Level addon is present by default AddonsManager am = mock(AddonsManager.class); when(plugin.getAddonsManager()).thenReturn(am); + when(am.getAddonByName("Level")).thenReturn(Optional.of(mock(Addon.class))); // Flags manager when(plugin.getFlagsManager()).thenReturn(fm); @@ -175,6 +178,23 @@ void testOnEnable() { } + /** + * Test that ChunkBlock disables itself, and takes its flags with it, when the + * Level addon is not on the server. + */ + @Test + void testOnEnableNoLevelAddonDisables() { + testOnLoad(); + addon.setState(State.ENABLED); + when(plugin.getAddonsManager().getAddonByName("Level")).thenReturn(Optional.empty()); + addon.onEnable(); + assertEquals(State.DISABLED, addon.getState()); + // The flags registered in onLoad must be unregistered so no listeners linger + verify(fm).unregister(addon); + // Nothing else was initialized + assertNull(addon.getBlockListener()); + } + /** * Test method for {@link world.bentobox.chunkblock.ChunkBlock#onLoad()}. */ From 2f373a11dd494800b22f9d48f8f96f3b99ddf4b3 Mon Sep 17 00:00:00 2001 From: tastybento Date: Wed, 29 Jul 2026 17:59:17 -0700 Subject: [PATCH 8/9] Make every BossBarListener handler inert when the addon never enabled The zombie-listener guard added in 9b8a25f only covered onJoin and onEnterIsland. IslandExitEvent, IslandEnterEvent and FlagSettingChangeEvent fire globally from other gamemodes too, so exiting an island in another gamemode still NPE'd through removeBar -> inWorld on the null island world when ChunkBlock had been dropped for the missing Level dependency. Replaces the piecemeal checks with a single ready() gate (overworld exists) that every event handler goes through, and adds a test that fires all handlers against a world-less addon to keep it that way. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Y7xSdVPS5vRu6wqf6XshRq --- .../chunkblock/listeners/BossBarListener.java | 25 ++++++++++-- .../listeners/BossBarListenerTest.java | 38 +++++++++++++++++++ 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/main/java/world/bentobox/chunkblock/listeners/BossBarListener.java b/src/main/java/world/bentobox/chunkblock/listeners/BossBarListener.java index a059454..8ad2450 100644 --- a/src/main/java/world/bentobox/chunkblock/listeners/BossBarListener.java +++ b/src/main/java/world/bentobox/chunkblock/listeners/BossBarListener.java @@ -50,8 +50,21 @@ public BossBarListener(ChunkBlock addon) { // Store a boss bar for each player (using their UUID) private final Map islandBossBars = new HashMap<>(); + /** + * This listener is registered with the flags in onLoad, so it can outlive an addon + * that never enabled (e.g. missing dependency) and therefore has no worlds. Every + * event handler must bail out via this check before touching addon state. + * @return true if the addon is running with its world available + */ + private boolean ready() { + return addon.getOverWorld() != null; + } + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onBreakBlockEvent(MagicBlockEvent e) { + if (!ready()) { + return; + } // Update boss bar tryToShowBossBar(e.getPlayerUUID(), e.getIsland()); tryToShowActionBar(e.getPlayerUUID(), e.getIsland()); @@ -59,9 +72,7 @@ public void onBreakBlockEvent(MagicBlockEvent e) { @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onEnterIsland(IslandEnterEvent event) { - // getOverWorld() is null when the addon never enabled (missing dependency); - // this listener outlives that because it is registered with the flags in onLoad - if (addon.getOverWorld() != null && addon.inWorld(event.getIsland().getWorld())) { + if (ready() && addon.inWorld(event.getIsland().getWorld())) { tryToShowBossBar(event.getPlayerUUID(), event.getIsland()); tryToShowActionBar(event.getPlayerUUID(), event.getIsland()); } @@ -69,6 +80,9 @@ public void onEnterIsland(IslandEnterEvent event) { @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onFlagChange(FlagSettingChangeEvent e) { + if (!ready()) { + return; + } if (e.getEditedFlag() == addon.CHUNKBLOCK_BOSSBAR) { // Show to players on island. If it isn't allowed then this will clean up the boss bar too e.getIsland().getPlayersOnIsland().stream().map(Player::getUniqueId) @@ -215,6 +229,9 @@ private void removeBar(User user, Island island) { @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onExitIsland(IslandExitEvent event) { + if (!ready()) { + return; + } User user = User.getInstance(event.getPlayerUUID()); removeBar(user, event.getIsland()); } @@ -223,7 +240,7 @@ public void onExitIsland(IslandExitEvent event) { public void onJoin(PlayerJoinEvent e) { // If the player is on an island then show the bar Location playerLoc = e.getPlayer().getLocation(); - if (addon.getOverWorld() == null || playerLoc == null || !addon.inWorld(playerLoc)) { + if (!ready() || playerLoc == null || !addon.inWorld(playerLoc)) { return; } addon.getIslands().getIslandAt(playerLoc) diff --git a/src/test/java/world/bentobox/chunkblock/listeners/BossBarListenerTest.java b/src/test/java/world/bentobox/chunkblock/listeners/BossBarListenerTest.java index 9202693..1ac17ba 100644 --- a/src/test/java/world/bentobox/chunkblock/listeners/BossBarListenerTest.java +++ b/src/test/java/world/bentobox/chunkblock/listeners/BossBarListenerTest.java @@ -4,6 +4,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -15,12 +16,16 @@ import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.boss.BossBar; +import org.bukkit.event.player.PlayerJoinEvent; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; import net.kyori.adventure.text.Component; +import world.bentobox.bentobox.api.events.flags.FlagSettingChangeEvent; +import world.bentobox.bentobox.api.events.island.IslandEnterEvent; +import world.bentobox.bentobox.api.events.island.IslandExitEvent; import world.bentobox.chunkblock.ChunkBlock; import world.bentobox.chunkblock.CommonTestSetup; import world.bentobox.chunkblock.Settings; @@ -60,6 +65,8 @@ public void setUp() throws Exception { doNothing().when(addon).logError(anyString()); doReturn(obm).when(addon).getOneBlockManager(); doReturn(obi).when(addon).getOneBlocksIsland(any()); + // The addon enabled normally and has its world + doReturn(world).when(addon).getOverWorld(); // Phase progress when(obi.getPhaseName()).thenReturn("Plains"); @@ -141,4 +148,35 @@ void testBossBarNotShownWhenDisabledInConfig() { mockedBukkit.verify(() -> Bukkit.createBossBar(anyString(), any(), any()), never()); verify(bossBar, never()).addPlayer(any()); } + + /** + * Every handler must be inert when the addon never enabled and has no worlds — the + * listener is registered with the flags in onLoad, so it outlives a load failure + * (e.g. Level missing) and global events keep reaching it. No handler may throw. + */ + @Test + void testAllHandlersInertWhenAddonNeverEnabled() { + doReturn(null).when(addon).getOverWorld(); + when(island.isAllowed(addon.CHUNKBLOCK_BOSSBAR)).thenReturn(true); + + fireMagicBlockEvent(); + + IslandEnterEvent enter = mock(IslandEnterEvent.class); + bbl.onEnterIsland(enter); + + IslandExitEvent exit = mock(IslandExitEvent.class); + bbl.onExitIsland(exit); + + FlagSettingChangeEvent flagChange = mock(FlagSettingChangeEvent.class); + bbl.onFlagChange(flagChange); + + PlayerJoinEvent join = mock(PlayerJoinEvent.class); + when(join.getPlayer()).thenReturn(mockPlayer); + bbl.onJoin(join); + + // Nothing happened + mockedBukkit.verify(() -> Bukkit.createBossBar(anyString(), any(), any()), never()); + verify(bossBar, never()).addPlayer(any()); + verify(bossBar, never()).removePlayer(any()); + } } From 468d051831bf273d05acc277612fe7df477070b3 Mon Sep 17 00:00:00 2001 From: tastybento Date: Wed, 29 Jul 2026 21:13:20 -0700 Subject: [PATCH 9/9] Version 1.0.1 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Y7xSdVPS5vRu6wqf6XshRq --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6afd64c..d3265e0 100644 --- a/pom.xml +++ b/pom.xml @@ -67,7 +67,7 @@ -LOCAL - 1.0.0 + 1.0.1 BentoBoxWorld_ChunkBlock bentobox-world