From a2dbbe9e281a49b52be6f0eff92c23ee2de8e472 Mon Sep 17 00:00:00 2001 From: tastybento Date: Tue, 18 Aug 2026 20:19:17 -0700 Subject: [PATCH 01/10] ci: enable Hangar publishing Co-Authored-By: Claude Opus 4.6 (1M context) Claude-Session: https://claude.ai/code/session_0114k1MQ9RoWNXKW4NHiwLxC --- .github/workflows/publish.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 593d4e0..5d8f302 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -22,10 +22,8 @@ jobs: with: use_release_asset: "true" # publish the jar attached to the release; do not rebuild # ChunkBlock's own CurseForge project. Do not reuse 1512493 — that is AOneBlock's. - # Hangar has not approved ChunkBlock yet; a blank slug makes the reusable workflow - # skip the Hangar job entirely, so the release still succeeds. Fill in "ChunkBlock" - # once the project exists. - hangar_slug: "" # blank = skip Hangar + # https://hangar.papermc.io/BentoboxWorld/ChunkBlock + hangar_slug: "ChunkBlock" curseforge_id: "1646388" game_versions: "26.2,26.1.2,26.1.1,26.1,1.21.11,1.21.10,1.21.9,1.21.8,1.21.7,1.21.6,1.21.5" version: ${{ inputs.version }} # empty on release events -> falls back to the release tag From 5ab44ad21db928c7cecfaaf3b442e757c872c1aa Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 23 Aug 2026 08:03:09 -0700 Subject: [PATCH 02/10] fix: include claimer name in chunk claimed message Team members were confused when they saw "Chunk claimed!" and thought they had claimed a chunk, when it was actually another team member. The message now shows who did the claiming: "[name] claimed a chunk!" Co-Authored-By: Claude Opus 4.6 (1M context) Claude-Session: https://claude.ai/code/session_01QWvvUY9ZZJHxUYm1UyjdwK --- .../world/bentobox/chunkblock/listeners/LevelListener.java | 4 +++- src/main/resources/locales/en-US.yml | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/world/bentobox/chunkblock/listeners/LevelListener.java b/src/main/java/world/bentobox/chunkblock/listeners/LevelListener.java index fa1d085..81e1ef1 100644 --- a/src/main/java/world/bentobox/chunkblock/listeners/LevelListener.java +++ b/src/main/java/world/bentobox/chunkblock/listeners/LevelListener.java @@ -184,10 +184,12 @@ public void celebrateClaim(Island island, int chunkX, int chunkZ, @Nullable UUID chunkZ - (island.getCenter().getBlockZ() >> 4)); Bukkit.getPluginManager().callEvent(new ChunkUnlockEvent(island, offset, count - 1, claimer)); long creditLeft = Math.max(0, cm.getCredit(island)); + String claimerName = playerName(claimer); island.getMemberSet().forEach(uuid -> { User user = User.getInstance(uuid); if (user.isOnline() && addon.inWorld(user.getWorld())) { - user.sendMessage("chunkblock.chunks.claimed", "[number]", String.valueOf(count), + user.sendMessage("chunkblock.chunks.claimed", TextVariables.NAME, claimerName, + "[number]", String.valueOf(count), "[credit]", String.valueOf(creditLeft)); if (count >= cm.getMaxChunks(island)) { user.sendMessage("chunkblock.chunks.max-reached", "[number]", String.valueOf(count)); diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 483e52b..e3d623a 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -46,7 +46,7 @@ chunkblock: claim-confirm: "Claim this chunk for [cost] level(s)? That leaves you [after] level(s) of credit. Sneak and hit the border again within [seconds]s to confirm." no-credit: "You need [needed] more level(s) of credit to claim this chunk." beyond-limit: "That chunk is beyond your island's protection area." - claimed: "Chunk claimed! Your island is now [number] chunks. Credit left: [credit] level(s)." + claimed: "[name] claimed a chunk! Your island is now [number] chunks. Credit left: [credit] level(s)." credit: "You can claim [count] more chunk(s)! Go to your border and hit it where you want to grow." relocked: "Your island level dropped — [count] chunk(s) re-locked, newest first. Regain the levels to claim them back!" ejected: "The chunk you were in re-locked, so you were moved to safety." From df92c1ff2edfef9f0ff0bf8de53a957d9ebb3d07 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 23 Aug 2026 08:07:59 -0700 Subject: [PATCH 03/10] fix: align config.yml defaults with Settings.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The resource config.yml that new admins get on first install had stale values inherited from AOneBlock that no longer matched the Settings.java field defaults. Mismatches fixed: - protection-range: 240 → 168 (matches max-chunks 441 = ring 10) - island-height: 80 → 120 - concurrent-islands: 1 → 0 (defer to BentoBox default) - nether spawn-radius: 25 → 32 - max-homes: 1 → 5 - on-join inventory/health/hunger/exp: false → true (fresh start) - on-leave inventory: true → false (keep inventory on leave) Co-Authored-By: Claude Opus 4.6 (1M context) Claude-Session: https://claude.ai/code/session_01QWvvUY9ZZJHxUYm1UyjdwK --- src/main/resources/config.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index c088b97..74192ec 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -210,7 +210,7 @@ world: # 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 + protection-range: 168 # Start islands at these coordinates. This is where new islands will start in the # world. These must be a factor of your island distance, but the plugin will auto # calculate the closest location on the grid. Islands develop around this location @@ -225,10 +225,10 @@ world: # at the center of a chunk. # Island height - Lowest is 5. # It is the y coordinate of the bedrock block in the schem. - island-height: 80 + island-height: 120 # The number of concurrent islands a player can have in the world # A value of 0 will use the BentoBox config.yml default - concurrent-islands: 1 + concurrent-islands: 0 # Disallow team members from having their own islands. disallow-team-member-islands: true # Use your own world generator for this world. @@ -279,7 +279,7 @@ world: # that will be protected from player interaction (breaking blocks, pouring lava etc.) # Minimum is 0 (not recommended), maximum is 100. Default is 25. # Only applies to vanilla nether - spawn-radius: 25 + spawn-radius: 32 # This option indicates if nether portals should be linked via dimensions. # Option will simulate vanilla portal mechanics that links portals together # or creates a new portal, if there is not a portal in that dimension. @@ -498,7 +498,7 @@ island: max-trusted-size: 4 # Default maximum number of homes a player can have. Min = 1 # Accessed via /is sethome or /is go - max-homes: 1 + max-homes: 5 reset: # 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. @@ -522,13 +522,13 @@ island: # Reset inventory - if true, the player's inventory will be cleared. # Note: if you have MultiInv running or a similar inventory control plugin, that # plugin may still reset the inventory when the world changes. - inventory: false + inventory: true # Reset health - if true, the player's health will be reset. - health: false + health: true # Reset hunger - if true, the player's hunger will be reset. - hunger: false + hunger: true # Reset experience points - if true, the player's experience will be reset. - exp: false + exp: true # Reset Ender Chest - if true, the player's Ender Chest will be cleared. ender-chest: false on-leave: @@ -540,7 +540,7 @@ island: # Reset inventory - if true, the player's inventory will be cleared. # Note: if you have MultiInv running or a similar inventory control plugin, that # plugin may still reset the inventory when the world changes. - inventory: true + inventory: false # Reset health - if true, the player's health will be reset. health: false # Reset hunger - if true, the player's hunger will be reset. From 1669cdbe9655094244c2780bc4d34f16a8a52e56 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 23 Aug 2026 08:16:19 -0700 Subject: [PATCH 04/10] Update pom.xml to 1.4.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5d4fcc5..0192532 100644 --- a/pom.xml +++ b/pom.xml @@ -67,7 +67,7 @@ -LOCAL - 1.3.0 + 1.4.0 BentoBoxWorld_ChunkBlock bentobox-world From 8822d0bf74822dd5e5fee3a21138ab4fde3f0e69 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 23 Aug 2026 08:18:25 -0700 Subject: [PATCH 05/10] refactor: remove max-chunks setting, derive limit from protection range MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The max-chunks config setting was redundant — the protection range already prevents claims outside it via maxRingRadius. The effective chunk cap is now derived solely from the island's protection range: maxChunks = (2 * floor((range - 8) / 16) + 1)^2 With the default range of 168 that gives 441 chunks (21x21), exactly what max-chunks was set to. Admins who want more or fewer chunks adjust the protection range instead of juggling two settings. Co-Authored-By: Claude Opus 4.6 (1M context) Claude-Session: https://claude.ai/code/session_01QWvvUY9ZZJHxUYm1UyjdwK --- .../world/bentobox/chunkblock/Settings.java | 25 ++----------------- .../chunkblock/chunks/ChunkManager.java | 15 ++++------- .../chunkblock/listeners/LevelListener.java | 3 --- src/main/resources/config.yml | 8 ++---- .../chunkblock/chunks/ChunkManagerTest.java | 21 ++++++---------- 5 files changed, 16 insertions(+), 56 deletions(-) diff --git a/src/main/java/world/bentobox/chunkblock/Settings.java b/src/main/java/world/bentobox/chunkblock/Settings.java index 4defcee..381709d 100644 --- a/src/main/java/world/bentobox/chunkblock/Settings.java +++ b/src/main/java/world/bentobox/chunkblock/Settings.java @@ -116,13 +116,6 @@ public class Settings implements WorldSettings { @ConfigEntry(path = "chunkblock.levels-per-chunk") private int levelsPerChunk = 1; - @ConfigComment("Maximum number of chunks an island can claim, including the center chunk.") - @ConfigComment("441 chunks is a full 21 x 21 chunk square. Use -1 for no limit beyond what the") - @ConfigComment("island protection range can hold. The effective maximum is always capped so") - @ConfigComment("claimed chunks fit inside the protection range.") - @ConfigEntry(path = "chunkblock.max-chunks") - private int maxChunks = 441; - @ConfigComment("Require confirmation before level credit is spent on a chunk. When true, the") @ConfigComment("first hit on the border previews the target chunk and its cost; the player") @ConfigComment("must then sneak and hit the border again to actually claim it. This stops a") @@ -313,8 +306,8 @@ public class Settings implements WorldSettings { @ConfigComment("Default protection range radius in blocks. Cannot be larger than distance.") @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).") - @ConfigComment("With max-chunks 441 (21x21, ring 10) the minimum needed is 168.") + @ConfigComment("ChunkBlock: the protection range determines how many chunks can be claimed.") + @ConfigComment("With range 168 the largest ring is 10, giving a 21x21 = 441 chunk square.") @ConfigEntry(path = "world.protection-range") private int islandProtectionRange = 168; @@ -2624,20 +2617,6 @@ public void setLevelsPerChunk(int levelsPerChunk) { this.levelsPerChunk = levelsPerChunk; } - /** - * @return the configured maximum number of unlockable chunks including the center; -1 means unlimited - */ - public int getMaxChunks() { - return maxChunks; - } - - /** - * @param maxChunks the maxChunks to set - */ - public void setMaxChunks(int maxChunks) { - this.maxChunks = maxChunks; - } - /** * @return true if ring milestones are announced to the whole server */ diff --git a/src/main/java/world/bentobox/chunkblock/chunks/ChunkManager.java b/src/main/java/world/bentobox/chunkblock/chunks/ChunkManager.java index f461f73..930bd17 100644 --- a/src/main/java/world/bentobox/chunkblock/chunks/ChunkManager.java +++ b/src/main/java/world/bentobox/chunkblock/chunks/ChunkManager.java @@ -50,7 +50,7 @@ public enum ClaimResult { ALREADY_UNLOCKED, /** The chunk does not touch the island's unlocked territory */ NOT_ADJACENT, - /** The chunk is outside the island's protection range or over max-chunks */ + /** The chunk is outside the island's protection range */ BEYOND_LIMIT, /** Not enough level credit */ NO_CREDIT @@ -301,9 +301,7 @@ public ClaimResult checkGeometry(Island island, int chunkX, int chunkZ) { if (data.isChunkUnlocked(dx, dz)) { return ClaimResult.ALREADY_UNLOCKED; } - if (Math.max(Math.abs(dx), Math.abs(dz)) > maxRingRadius(island) - || (addon.getSettings().getMaxChunks() >= 0 - && data.getUnlockedChunkCount() >= addon.getSettings().getMaxChunks())) { + if (Math.max(Math.abs(dx), Math.abs(dz)) > maxRingRadius(island)) { return ClaimResult.BEYOND_LIMIT; } // Must share a face with territory the island already owns @@ -379,18 +377,15 @@ public int maxRingRadius(Island island) { } /** - * Returns the effective maximum number of chunks this island can unlock: the - * configured max-chunks, additionally capped by what fits inside the island's - * protection range. + * Returns the maximum number of chunks this island can unlock, determined by + * what fits inside the island's protection range. * * @param island the island * @return the maximum unlockable chunk count, always >= 1 */ public int getMaxChunks(Island island) { int rangeRadius = maxRingRadius(island); - int rangeCap = (2 * rangeRadius + 1) * (2 * rangeRadius + 1); - int configured = addon.getSettings().getMaxChunks(); - return Math.max(1, configured < 0 ? rangeCap : Math.min(configured, rangeCap)); + return Math.max(1, (2 * rangeRadius + 1) * (2 * rangeRadius + 1)); } /** diff --git a/src/main/java/world/bentobox/chunkblock/listeners/LevelListener.java b/src/main/java/world/bentobox/chunkblock/listeners/LevelListener.java index fa1d085..51bb4f7 100644 --- a/src/main/java/world/bentobox/chunkblock/listeners/LevelListener.java +++ b/src/main/java/world/bentobox/chunkblock/listeners/LevelListener.java @@ -189,9 +189,6 @@ public void celebrateClaim(Island island, int chunkX, int chunkZ, @Nullable UUID if (user.isOnline() && addon.inWorld(user.getWorld())) { user.sendMessage("chunkblock.chunks.claimed", "[number]", String.valueOf(count), "[credit]", String.valueOf(creditLeft)); - if (count >= cm.getMaxChunks(island)) { - user.sendMessage("chunkblock.chunks.max-reached", "[number]", String.valueOf(count)); - } user.getPlayer().playSound(user.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1F, 1F); } }); diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index c088b97..97ba4bf 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -51,11 +51,6 @@ chunkblock: # Island levels are chunk currency: credit = island level minus levels already # spent. The island owner spends credit by hitting the border where they want to expand. levels-per-chunk: 1 - # Maximum number of chunks an island can claim, including the center chunk. - # 441 chunks is a full 21 x 21 chunk square. Use -1 for no limit beyond what the - # island protection range can hold. The effective maximum is always capped so - # claimed chunks fit inside the protection range. - max-chunks: 441 claim: # Require confirmation before level credit is spent on a chunk. When true, the # first hit on the border previews the target chunk and its cost; the player @@ -209,7 +204,8 @@ world: # Default protection range radius in blocks. Cannot be larger than distance. # 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). + # ChunkBlock: the protection range determines how many chunks can be claimed. + # With range 168 the largest ring is 10, giving a 21x21 = 441 chunk square. protection-range: 240 # Start islands at these coordinates. This is where new islands will start in the # world. These must be a factor of your island distance, but the plugin will auto diff --git a/src/test/java/world/bentobox/chunkblock/chunks/ChunkManagerTest.java b/src/test/java/world/bentobox/chunkblock/chunks/ChunkManagerTest.java index 8a6680c..2a75be2 100644 --- a/src/test/java/world/bentobox/chunkblock/chunks/ChunkManagerTest.java +++ b/src/test/java/world/bentobox/chunkblock/chunks/ChunkManagerTest.java @@ -133,15 +133,6 @@ void testClaimBeyondProtectionRangeDenied() { assertEquals(ClaimResult.BEYOND_LIMIT, cm.claim(island, 3, 0)); } - @Test - void testClaimBeyondMaxChunksDenied() { - level = 100000; - settings.setMaxChunks(3); - assertEquals(ClaimResult.OK, cm.claim(island, 1, 0)); - assertEquals(ClaimResult.OK, cm.claim(island, -1, 0)); - assertEquals(ClaimResult.BEYOND_LIMIT, cm.claim(island, 0, 1)); - } - @Test void testLevelsPerChunkCost() { settings.setLevelsPerChunk(10); @@ -210,13 +201,15 @@ void testOffsetIslandCenter() { } @Test - void testMaxChunksCappedByProtectionRange() { - assertEquals(441, cm.getMaxChunks(island)); + void testMaxChunksDerivedFromProtectionRange() { + // Default setup: protectionRange=240, maxRingRadius=(240-8)/16=14, (2*14+1)^2=841 + assertEquals(841, cm.getMaxChunks(island)); when(island.getProtectionRange()).thenReturn(50); + // (50-8)/16=2, (2*2+1)^2=25 assertEquals(25, cm.getMaxChunks(island)); - settings.setMaxChunks(-1); - when(island.getProtectionRange()).thenReturn(240); - assertEquals(841, cm.getMaxChunks(island)); + when(island.getProtectionRange()).thenReturn(168); + // (168-8)/16=10, (2*10+1)^2=441 + assertEquals(441, cm.getMaxChunks(island)); } @Test From af40d1e8bc22a337287e720cffb75755651c12c4 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 23 Aug 2026 08:34:44 -0700 Subject: [PATCH 06/10] feat: scrollable chunk map dialog with player/island view modes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the island's territory can extend beyond the 13x13 viewport (protection range > 104 blocks), the map dialog now adds a control row with two view modes: - Island center (◎): viewport centered on the island center, with a directional arrow pointing toward the player's position - Player center (◆): viewport centered on the player's chunk, with a directional arrow pointing back to the island center The directional arrow shows 8 directions (cardinals + diagonals) and doubles as a navigation button — clicking it switches to the other view. When the target is already visible on screen, the arrow is replaced by a dot. For small islands that fit in 13x13, the dialog behaves exactly as before with no control row. Co-Authored-By: Claude Opus 4.6 (1M context) Claude-Session: https://claude.ai/code/session_01QWvvUY9ZZJHxUYm1UyjdwK --- .../bentobox/chunkblock/chunks/ChunkMap.java | 27 ++- .../chunkblock/panels/ChunksDialog.java | 182 ++++++++++++++++-- src/main/resources/locales/en-US.yml | 4 + .../chunkblock/panels/ChunksDialogTest.java | 87 ++++++++- 4 files changed, 269 insertions(+), 31 deletions(-) diff --git a/src/main/java/world/bentobox/chunkblock/chunks/ChunkMap.java b/src/main/java/world/bentobox/chunkblock/chunks/ChunkMap.java index f85f299..76c065a 100644 --- a/src/main/java/world/bentobox/chunkblock/chunks/ChunkMap.java +++ b/src/main/java/world/bentobox/chunkblock/chunks/ChunkMap.java @@ -57,26 +57,43 @@ private ChunkMap() { /** * Maps the territory around an island, row by row from north to south and west to east - * within a row — the order both maps draw in. + * within a row — the order both maps draw in. The viewport is centered on the island + * center chunk. * * @param addon the addon * @param island the island whose territory is mapped * @param viewer where the player is standing, or null if they are nowhere on the map - * @param radius how many chunks out from the center the map reaches + * @param radius how many chunks out from the viewport center the map reaches * @return the cells of a square map (2 * radius + 1) chunks across */ public static List cells(@NonNull ChunkBlock addon, @NonNull Island island, @Nullable Location viewer, int radius) { + return cells(addon, island, viewer, radius, 0, 0); + } + + /** + * Maps the territory around an island with a shifted viewport center. The viewport is + * centered on the chunk at {@code (viewDx, viewDz)} relative to the island center. + * + * @param addon the addon + * @param island the island whose territory is mapped + * @param viewer where the player is standing, or null if they are nowhere on the map + * @param radius how many chunks out from the viewport center the map reaches + * @param viewDx viewport center chunk offset east of the island center + * @param viewDz viewport center chunk offset south of the island center + * @return the cells of a square map (2 * radius + 1) chunks across + */ + public static List cells(@NonNull ChunkBlock addon, @NonNull Island island, @Nullable Location viewer, + int radius, int viewDx, int viewDz) { ChunkManager cm = addon.getChunkManager(); int centerChunkX = island.getCenter().getBlockX() >> 4; int centerChunkZ = island.getCenter().getBlockZ() >> 4; - // A player who is not in this world stands on no chunk of the map boolean sameWorld = viewer != null && Util.sameWorld(island.getWorld(), viewer.getWorld()); int playerDx = sameWorld ? (viewer.getBlockX() >> 4) - centerChunkX : Integer.MIN_VALUE; int playerDz = sameWorld ? (viewer.getBlockZ() >> 4) - centerChunkZ : Integer.MIN_VALUE; List cells = new ArrayList<>(); - for (int dz = -radius; dz <= radius; dz++) { - for (int dx = -radius; dx <= radius; dx++) { + for (int dz = viewDz - radius; dz <= viewDz + radius; dz++) { + for (int dx = viewDx - radius; dx <= viewDx + radius; dx++) { Kind kind; if (dx == 0 && dz == 0) { kind = Kind.CENTER; diff --git a/src/main/java/world/bentobox/chunkblock/panels/ChunksDialog.java b/src/main/java/world/bentobox/chunkblock/panels/ChunksDialog.java index ae23b3f..5b33c1b 100644 --- a/src/main/java/world/bentobox/chunkblock/panels/ChunksDialog.java +++ b/src/main/java/world/bentobox/chunkblock/panels/ChunksDialog.java @@ -17,9 +17,11 @@ import io.papermc.paper.registry.data.dialog.type.DialogType; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickCallback; +import net.kyori.adventure.text.format.NamedTextColor; import world.bentobox.bentobox.api.dialogs.Dialogs; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.util.Util; import world.bentobox.chunkblock.ChunkBlock; import world.bentobox.chunkblock.chunks.ChunkManager; import world.bentobox.chunkblock.chunks.ChunkMap; @@ -27,17 +29,21 @@ /** * The territory map of {@code /ch chunks} drawn as a dialog: one button per chunk, laid - * out in a grid. Chat renders a glyph grid differently on every client — font, chat width - * and scale all pull it out of shape — whereas dialog buttons are fixed-size boxes that - * look the same everywhere, and can carry a tooltip explaining the chunk under the mouse. - *

- * The map is read-only: chunks are still claimed by hitting the border, so clicking a - * chunk only reports what it is and reopens the map. + * out in a grid. When the island's territory can exceed the 13-wide viewport, a control + * row is prepended with two view-mode buttons (island center / player position) and a + * directional arrow pointing toward the off-screen target. * * @author tastybento */ public class ChunksDialog { + /** + * Whether the viewport is centered on the island center or on the player's position. + */ + enum ViewMode { + ISLAND_CENTER, PLAYER_CENTER + } + /** * Widest map that still fits the dialog. A grid this wide is {@value #MAX_RADIUS} * 2 * + 1 buttons across, which is as much as the dialog screen holds before the outer @@ -56,16 +62,47 @@ public class ChunksDialog { private static final String REFERENCE = "chunkblock.chunks.dialog."; + /** Direction glyphs indexed by sector (0 = east, rotating counter-clockwise). */ + private static final String[] ARROWS = { "▶", "↗", "▲", "↖", "◀", "↙", "▼", "↘" }; + private final ChunkBlock addon; private final User user; private final Island island; private final int radius; + private final ViewMode viewMode; + private final int viewDx; + private final int viewDz; + private final boolean scrollable; ChunksDialog(ChunkBlock addon, User user, Island island) { + this(addon, user, island, ViewMode.ISLAND_CENTER); + } + + ChunksDialog(ChunkBlock addon, User user, Island island, ViewMode viewMode) { this.addon = addon; this.user = user; this.island = island; - this.radius = Math.min(MAX_RADIUS, addon.getChunkManager().currentRing(island) + 1); + this.viewMode = viewMode; + + ChunkManager cm = addon.getChunkManager(); + this.scrollable = cm.maxRingRadius(island) > MAX_RADIUS; + + if (scrollable) { + this.radius = MAX_RADIUS; + if (viewMode == ViewMode.PLAYER_CENTER && isPlayerOnIsland()) { + int centerChunkX = island.getCenter().getBlockX() >> 4; + int centerChunkZ = island.getCenter().getBlockZ() >> 4; + this.viewDx = (user.getLocation().getBlockX() >> 4) - centerChunkX; + this.viewDz = (user.getLocation().getBlockZ() >> 4) - centerChunkZ; + } else { + this.viewDx = 0; + this.viewDz = 0; + } + } else { + this.radius = Math.min(MAX_RADIUS, cm.currentRing(island) + 1); + this.viewDx = 0; + this.viewDz = 0; + } } /** @@ -78,22 +115,22 @@ public class ChunksDialog { * which case the caller should fall back to the chat map */ public static boolean show(@NonNull ChunkBlock addon, @NonNull User user, @NonNull Island island) { - return show(addon, user, island, null); + return show(addon, user, island, ViewMode.ISLAND_CENTER, null); } /** + * @param viewMode which point the viewport is centered on * @param selection the chunk description to show above the map, or null for none */ - private static boolean show(ChunkBlock addon, User user, Island island, @Nullable Component selection) { + private static boolean show(ChunkBlock addon, User user, Island island, ViewMode viewMode, + @Nullable Component selection) { if (!Dialogs.isSupported() || !user.isPlayer() || user.getPlayer() == null) { return false; } try { - new ChunksDialog(addon, user, island).open(selection); + new ChunksDialog(addon, user, island, viewMode).open(selection); return true; } catch (Exception | LinkageError e) { - // A server that reports dialog support but cannot build one is no reason to - // leave the player with nothing — the caller falls back to the chat map addon.logError("Could not show the chunks dialog: " + e.getMessage()); return false; } @@ -120,19 +157,117 @@ private void open(@Nullable Component selection) { String.valueOf(max))) .canCloseWithEscape(true).afterAction(DialogBase.DialogAfterAction.CLOSE).body(body).build(); - List buttons = cells().stream().map(this::button).toList(); - DialogType type = DialogType.multiAction(buttons).columns(2 * radius + 1) + int columns = 2 * radius + 1; + List buttons = new ArrayList<>(); + if (scrollable) { + buttons.addAll(controlRow(columns)); + } + buttons.addAll(cells().stream().map(this::button).toList()); + + DialogType type = DialogType.multiAction(buttons).columns(columns) .exitAction(ActionButton.create(text(REFERENCE + "close"), null, CLOSE_BUTTON_WIDTH, null)).build(); user.getPlayer().showDialog(Dialog.create(factory -> factory.empty().base(base).type(type))); } + // ------------------------------------------------------------------ + // Control row (view-mode toggle + directional arrow) + // ------------------------------------------------------------------ + + private List controlRow(int columns) { + List row = new ArrayList<>(columns); + for (int i = 0; i < columns; i++) { + if (i == 0) { + row.add(modeButton(ViewMode.ISLAND_CENTER, "◎", "◉", NamedTextColor.GOLD, + REFERENCE + "view-island")); + } else if (i == columns - 1) { + row.add(modeButton(ViewMode.PLAYER_CENTER, "◇", "◆", NamedTextColor.AQUA, + REFERENCE + "view-player")); + } else if (i == columns / 2) { + row.add(directionArrowButton()); + } else { + row.add(spacerButton()); + } + } + return row; + } + + private ActionButton modeButton(ViewMode mode, String inactiveGlyph, String activeGlyph, + NamedTextColor color, String tooltipKey) { + boolean active = viewMode == mode; + String glyph = active ? activeGlyph : inactiveGlyph; + NamedTextColor buttonColor = active ? NamedTextColor.WHITE : color; + return ActionButton.builder(Component.text(glyph, buttonColor)) + .tooltip(text(tooltipKey)).width(BUTTON_WIDTH) + .action(DialogAction.customClick((view, audience) -> switchMode(mode), + ClickCallback.Options.builder().uses(1).lifetime(CALLBACK_LIFETIME).build())) + .build(); + } + + private ActionButton spacerButton() { + return ActionButton.builder(Component.text("─", NamedTextColor.DARK_GRAY)).width(BUTTON_WIDTH).build(); + } + + private ActionButton directionArrowButton() { + int targetDx; + int targetDz; + NamedTextColor arrowColor; + String tooltipKey; + ViewMode targetMode; + + if (viewMode == ViewMode.ISLAND_CENTER) { + if (!isPlayerOnIsland()) { + return spacerButton(); + } + int centerChunkX = island.getCenter().getBlockX() >> 4; + int centerChunkZ = island.getCenter().getBlockZ() >> 4; + targetDx = (user.getLocation().getBlockX() >> 4) - centerChunkX; + targetDz = (user.getLocation().getBlockZ() >> 4) - centerChunkZ; + arrowColor = NamedTextColor.AQUA; + tooltipKey = REFERENCE + "arrow-to-player"; + targetMode = ViewMode.PLAYER_CENTER; + } else { + targetDx = 0; + targetDz = 0; + arrowColor = NamedTextColor.GOLD; + tooltipKey = REFERENCE + "arrow-to-island"; + targetMode = ViewMode.ISLAND_CENTER; + } + + int relDx = targetDx - viewDx; + int relDz = targetDz - viewDz; + if (Math.abs(relDx) <= radius && Math.abs(relDz) <= radius) { + return ActionButton.builder(Component.text("•", NamedTextColor.DARK_GRAY)).width(BUTTON_WIDTH).build(); + } + + String arrow = directionGlyph(relDx, relDz); + return ActionButton.builder(Component.text(arrow, arrowColor)) + .tooltip(text(tooltipKey)).width(BUTTON_WIDTH) + .action(DialogAction.customClick((view, audience) -> switchMode(targetMode), + ClickCallback.Options.builder().uses(1).lifetime(CALLBACK_LIFETIME).build())) + .build(); + } + + /** + * Returns the arrow glyph for the direction from the viewport center to the target. + * Divides the plane into eight 45-degree sectors starting from east. + */ + static String directionGlyph(int relDx, int relDz) { + double angle = Math.atan2(-relDz, relDx); + int sector = (int) Math.round(angle / (Math.PI / 4)); + return ARROWS[((sector % 8) + 8) % 8]; + } + + // ------------------------------------------------------------------ + // Map buttons + // ------------------------------------------------------------------ + /** * The map, row by row from north to south — the same reading order the buttons are laid * out in, so the grid comes out with north at the top. */ List cells() { - return ChunkMap.cells(addon, island, user.getLocation(), radius); + return ChunkMap.cells(addon, island, user.getLocation(), radius, viewDx, viewDz); } private ActionButton button(Cell cell) { @@ -148,9 +283,11 @@ private ActionButton button(Cell cell) { * closes the dialog, so a map that stays put has to be shown again. */ private void reopen(Component selection) { - // Dialog callbacks may arrive off the main thread, and everything the map reads is - // island data - Bukkit.getScheduler().runTask(addon.getPlugin(), () -> show(addon, user, island, selection)); + Bukkit.getScheduler().runTask(addon.getPlugin(), () -> show(addon, user, island, viewMode, selection)); + } + + private void switchMode(ViewMode mode) { + Bukkit.getScheduler().runTask(addon.getPlugin(), () -> show(addon, user, island, mode, null)); } /** @@ -184,6 +321,15 @@ private static String offset(int value) { return value > 0 ? "+" + value : String.valueOf(value); } + private boolean isPlayerOnIsland() { + return user.getLocation() != null && island.getWorld() != null + && Util.sameWorld(island.getWorld(), user.getLocation().getWorld()); + } + + boolean isScrollable() { + return scrollable; + } + /** * Translates a locale key straight to a component. Going through the user rather than * parsing the translated string here keeps every message on BentoBox's own path, diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 483e52b..6796b97 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -66,6 +66,10 @@ chunkblock: # These are MiniMessage, which is what new text should use — the old &-codes still work. dialog: close: "Close" + view-island: "Center on your island" + view-player: "Center on your position" + arrow-to-island: "Your island center is this way" + arrow-to-player: "You are this way" tooltip: center: "The center chunk — your magic block is here." owned: "Chunk [x], [z] — yours." diff --git a/src/test/java/world/bentobox/chunkblock/panels/ChunksDialogTest.java b/src/test/java/world/bentobox/chunkblock/panels/ChunksDialogTest.java index 8f6735a..5e94db9 100644 --- a/src/test/java/world/bentobox/chunkblock/panels/ChunksDialogTest.java +++ b/src/test/java/world/bentobox/chunkblock/panels/ChunksDialogTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -15,12 +16,14 @@ import world.bentobox.chunkblock.CommonTestSetup; import world.bentobox.chunkblock.Settings; import world.bentobox.chunkblock.chunks.ChunkManager; +import world.bentobox.chunkblock.chunks.ChunkMap.Cell; import world.bentobox.chunkblock.dataobjects.OneBlockIslands; import world.bentobox.chunkblock.listeners.BlockListener; /** - * Tests how far the dialog map reaches and when it declines to show at all. What each chunk - * is and how it is drawn belongs to the shared grid, and is covered by {@code ChunkMapTest}. + * Tests how far the dialog map reaches, when it scrolls, and when it declines to show at + * all. What each chunk is and how it is drawn belongs to the shared grid, and is covered + * by {@code ChunkMapTest}. */ class ChunksDialogTest extends CommonTestSetup { @@ -51,7 +54,6 @@ public void setUp() throws Exception { when(island.getWorld()).thenReturn(world); when(location.getBlockX()).thenReturn(8); when(location.getBlockZ()).thenReturn(8); - when(island.getProtectionRange()).thenReturn(240); when(playerLocation.getBlockX()).thenReturn(8); when(playerLocation.getBlockZ()).thenReturn(8); when(playerLocation.getWorld()).thenReturn(world); @@ -60,11 +62,75 @@ public void setUp() throws Exception { } @Test - void testAFreshIslandShowsTheRingAroundTheCenter() { - // One claimed chunk, so the map reaches one ring out — 3 x 3 + void testSmallIslandShowsTheRingAroundTheCenter() { + when(island.getProtectionRange()).thenReturn(100); + // maxRingRadius = (100-8)/16 = 5, not scrollable assertEquals(9, new ChunksDialog(addon, user, island).cells().size()); } + @Test + void testScrollableIslandShowsFullViewport() { + when(island.getProtectionRange()).thenReturn(240); + int width = 2 * ChunksDialog.MAX_RADIUS + 1; + assertEquals(width * width, new ChunksDialog(addon, user, island).cells().size()); + } + + @Test + void testIsScrollableWhenTerritoryExceedsViewport() { + when(island.getProtectionRange()).thenReturn(240); + assertTrue(new ChunksDialog(addon, user, island).isScrollable()); + } + + @Test + void testIsNotScrollableWhenTerritoryFitsViewport() { + when(island.getProtectionRange()).thenReturn(100); + assertFalse(new ChunksDialog(addon, user, island).isScrollable()); + } + + @Test + void testPlayerCenteredViewportShiftsMap() { + when(island.getProtectionRange()).thenReturn(240); + when(playerLocation.getBlockX()).thenReturn(8 + 160); + when(playerLocation.getBlockZ()).thenReturn(8); + level = 100000; + // Claim enough to reach ring 10 + for (int d = 1; d <= 10; d++) { + cm.claim(island, d, 0); + } + ChunksDialog dialog = new ChunksDialog(addon, user, island, ChunksDialog.ViewMode.PLAYER_CENTER); + int width = 2 * ChunksDialog.MAX_RADIUS + 1; + assertEquals(width * width, dialog.cells().size()); + // The player is at chunk offset +10, so the viewport should be centered there. + // The center chunk (0,0) should be visible if it's within radius of the viewport center. + // viewDx=10, radius=6 → visible range is 4..16, so island center at 0 is NOT visible. + boolean centerVisible = dialog.cells().stream() + .anyMatch(c -> c.dx() == 0 && c.dz() == 0); + assertFalse(centerVisible); + } + + @Test + void testNothingIsShownWhenTheUserIsNotAPlayer() { + when(island.getProtectionRange()).thenReturn(240); + when(user.isPlayer()).thenReturn(false); + assertFalse(ChunksDialog.show(addon, user, island)); + } + + @Test + void testDirectionGlyphCardinals() { + assertEquals("▶", ChunksDialog.directionGlyph(10, 0)); + assertEquals("◀", ChunksDialog.directionGlyph(-10, 0)); + assertEquals("▲", ChunksDialog.directionGlyph(0, -10)); + assertEquals("▼", ChunksDialog.directionGlyph(0, 10)); + } + + @Test + void testDirectionGlyphDiagonals() { + assertEquals("↗", ChunksDialog.directionGlyph(10, -10)); + assertEquals("↘", ChunksDialog.directionGlyph(10, 10)); + assertEquals("↖", ChunksDialog.directionGlyph(-10, -10)); + assertEquals("↙", ChunksDialog.directionGlyph(-10, 10)); + } + @Test void testMapIsCappedAtTheWidestGridTheDialogHolds() { when(island.getProtectionRange()).thenReturn(2000); @@ -77,8 +143,13 @@ void testMapIsCappedAtTheWidestGridTheDialogHolds() { } @Test - void testNothingIsShownWhenTheUserIsNotAPlayer() { - when(user.isPlayer()).thenReturn(false); - assertFalse(ChunksDialog.show(addon, user, island)); + void testPlayerCenteredFallsBackToIslandCenterWhenNotOnIsland() { + when(island.getProtectionRange()).thenReturn(240); + when(playerLocation.getWorld()).thenReturn(null); + // Player is not in the island world → viewport should center on island + ChunksDialog dialog = new ChunksDialog(addon, user, island, ChunksDialog.ViewMode.PLAYER_CENTER); + boolean centerVisible = dialog.cells().stream() + .anyMatch(c -> c.dx() == 0 && c.dz() == 0); + assertTrue(centerVisible); } } From f975ed77a4e54640fd9c3e459781893918126de9 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 23 Aug 2026 13:20:35 -0700 Subject: [PATCH 07/10] chore: fix SonarCloud issues and sync 17 locale files SonarCloud fixes: - PhaseIndexEntry: rename 5 constants to KEY_* prefix (blocker: field/constant name clash) - PhasesPanelTest: add assertions to 2 empty test cases (blocker) - ChunksDialog: remove 2 redundant null checks, extract duplicate literals - LevelListener, IslandChunksCommand: extract duplicate string literals - ChunkBlock, IslandLedgerCommandTest, LockedChunkProtectTest: remove unused imports - IslandLedgerCommand: remove dead null branch - IslandLedgerCommandTest: remove useless eq() wrappers - Settings: replace string concatenation with String.format() - HoloListener: add null guard for @NonNull parameter - ChunkGuardListener: remove 2 redundant null checks Locale sync: - Add 4 new dialog keys (view-island, view-player, arrow-to-island, arrow-to-player) to all 17 non-English locale files with translations Co-Authored-By: Claude Opus 4.6 (1M context) Claude-Session: https://claude.ai/code/session_01QWvvUY9ZZJHxUYm1UyjdwK --- .../world/bentobox/chunkblock/ChunkBlock.java | 1 - .../world/bentobox/chunkblock/Settings.java | 4 +-- .../commands/island/IslandChunksCommand.java | 5 ++-- .../commands/island/IslandLedgerCommand.java | 2 +- .../listeners/ChunkGuardListener.java | 4 +-- .../chunkblock/listeners/HoloListener.java | 10 +++++-- .../chunkblock/listeners/LevelListener.java | 8 +++-- .../chunkblock/oneblocks/PhaseIndexEntry.java | 30 +++++++++---------- .../chunkblock/panels/ChunksDialog.java | 15 +++++----- src/main/resources/locales/cs.yml | 4 +++ src/main/resources/locales/de.yml | 4 +++ src/main/resources/locales/es.yml | 4 +++ src/main/resources/locales/fr.yml | 4 +++ src/main/resources/locales/hr.yml | 4 +++ src/main/resources/locales/hu.yml | 4 +++ src/main/resources/locales/id.yml | 4 +++ src/main/resources/locales/it.yml | 4 +++ src/main/resources/locales/ja.yml | 4 +++ src/main/resources/locales/pl.yml | 4 +++ src/main/resources/locales/pt.yml | 4 +++ src/main/resources/locales/ru.yml | 4 +++ src/main/resources/locales/tr.yml | 4 +++ src/main/resources/locales/uk.yml | 4 +++ src/main/resources/locales/vi.yml | 4 +++ src/main/resources/locales/zh-CN.yml | 4 +++ src/main/resources/locales/zh-TW.yml | 4 +++ .../island/IslandLedgerCommandTest.java | 9 +++--- .../listeners/LockedChunkProtectTest.java | 1 - .../chunkblock/panels/ChunksDialogTest.java | 4 +-- .../chunkblock/panels/PhasesPanelTest.java | 2 ++ 30 files changed, 119 insertions(+), 44 deletions(-) diff --git a/src/main/java/world/bentobox/chunkblock/ChunkBlock.java b/src/main/java/world/bentobox/chunkblock/ChunkBlock.java index 4f0f2de..f4abc04 100644 --- a/src/main/java/world/bentobox/chunkblock/ChunkBlock.java +++ b/src/main/java/world/bentobox/chunkblock/ChunkBlock.java @@ -47,7 +47,6 @@ 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; diff --git a/src/main/java/world/bentobox/chunkblock/Settings.java b/src/main/java/world/bentobox/chunkblock/Settings.java index 381709d..4ea2b5b 100644 --- a/src/main/java/world/bentobox/chunkblock/Settings.java +++ b/src/main/java/world/bentobox/chunkblock/Settings.java @@ -1238,8 +1238,8 @@ public void setIslandDistance(int islandDistance) { int snapped = Math.max(8, Math.round(islandDistance / 8F) * 8); if (snapped != islandDistance) { Logger.getLogger("ChunkBlock").warning( - "distance-between-islands must be a multiple of 8 for ChunkBlock; adjusted from " - + islandDistance + " to " + snapped); + String.format("distance-between-islands must be a multiple of 8 for ChunkBlock; adjusted from %d to %d", + islandDistance, snapped)); } this.islandDistance = snapped; } 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 89a6685..2a60a70 100644 --- a/src/main/java/world/bentobox/chunkblock/commands/island/IslandChunksCommand.java +++ b/src/main/java/world/bentobox/chunkblock/commands/island/IslandChunksCommand.java @@ -36,6 +36,7 @@ public class IslandChunksCommand extends CompositeCommand { * in the normal font. */ private static final Key MONOSPACE_FONT = Key.key("minecraft", "uniform"); + private static final String PLACEHOLDER_MAX = "[max]"; private ChunkBlock addon; @@ -77,12 +78,12 @@ public boolean execute(User user, String label, List args) { int unlocked = cm.getUnlockedChunkCount(island); int max = cm.getMaxChunks(island); long credit = Math.max(0, cm.getCredit(island)); - user.sendMessage("chunkblock.chunks.info", "[unlocked]", String.valueOf(unlocked), "[max]", + user.sendMessage("chunkblock.chunks.info", "[unlocked]", String.valueOf(unlocked), PLACEHOLDER_MAX, String.valueOf(max), "[credit]", String.valueOf(credit), "[cost]", String.valueOf(cm.getChunkCost())); int completedRings = cm.completedRings(island); int maxRing = cm.maxRingRadius(island); - user.sendMessage("chunkblock.chunks.rings", "[rings]", String.valueOf(completedRings), "[max]", + user.sendMessage("chunkblock.chunks.rings", "[rings]", String.valueOf(completedRings), PLACEHOLDER_MAX, String.valueOf(maxRing)); int nextRing = completedRings + 1; if (nextRing <= maxRing) { diff --git a/src/main/java/world/bentobox/chunkblock/commands/island/IslandLedgerCommand.java b/src/main/java/world/bentobox/chunkblock/commands/island/IslandLedgerCommand.java index 76a61d7..0878ac1 100644 --- a/src/main/java/world/bentobox/chunkblock/commands/island/IslandLedgerCommand.java +++ b/src/main/java/world/bentobox/chunkblock/commands/island/IslandLedgerCommand.java @@ -114,7 +114,7 @@ private List buildRows(Island island, ActivityManager am, int window) long rings = am.getCount(island, uuid, CounterType.RINGS_COMPLETED, window); if (blocks > 0 || chunks > 0 || rings > 0) { String name = addon.getPlayers().getName(uuid); - rows.add(new MemberRow(name == null || name.isEmpty() ? uuid.toString() : name, + rows.add(new MemberRow(name.isEmpty() ? uuid.toString() : name, blocks, chunks, rings)); } } diff --git a/src/main/java/world/bentobox/chunkblock/listeners/ChunkGuardListener.java b/src/main/java/world/bentobox/chunkblock/listeners/ChunkGuardListener.java index 699a89c..905a534 100644 --- a/src/main/java/world/bentobox/chunkblock/listeners/ChunkGuardListener.java +++ b/src/main/java/world/bentobox/chunkblock/listeners/ChunkGuardListener.java @@ -109,7 +109,7 @@ private boolean isExempt(Player player) { public void onPlayerMove(PlayerMoveEvent e) { Location from = e.getFrom(); Location to = e.getTo(); - if (to == null || !addon.inWorld(to.getWorld()) + if (!addon.inWorld(to.getWorld()) // Head-only movement cannot change chunk || from.toVector().multiply(XZ).equals(to.toVector().multiply(XZ)) // Only evaluate when the chunk changes @@ -251,7 +251,7 @@ public void onPlayerDamage(EntityDamageEvent e) { public void onPlayerTeleport(PlayerTeleportEvent e) { Location to = e.getTo(); Player player = e.getPlayer(); - if (to == null || !addon.inWorld(to.getWorld()) || isExempt(player)) { + if (!addon.inWorld(to.getWorld()) || isExempt(player)) { return; } TeleportCause cause = e.getCause(); diff --git a/src/main/java/world/bentobox/chunkblock/listeners/HoloListener.java b/src/main/java/world/bentobox/chunkblock/listeners/HoloListener.java index 3827971..12aa633 100644 --- a/src/main/java/world/bentobox/chunkblock/listeners/HoloListener.java +++ b/src/main/java/world/bentobox/chunkblock/listeners/HoloListener.java @@ -2,6 +2,7 @@ import java.util.HashSet; import java.util.Set; +import java.util.UUID; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -73,9 +74,12 @@ protected void setUp(@NonNull Island island, @NonNull OneBlockIslands is, boolea } if (newIsland) { - String startingText = User.getInstance(island.getOwner()) - .getTranslation("chunkblock.island.starting-hologram"); - is.setHologram(startingText == null ? "" : startingText); + UUID owner = island.getOwner(); + if (owner != null) { + String startingText = User.getInstance(owner) + .getTranslation("chunkblock.island.starting-hologram"); + is.setHologram(startingText == null ? "" : startingText); + } } updateHologram(island, is.getHologram()); } diff --git a/src/main/java/world/bentobox/chunkblock/listeners/LevelListener.java b/src/main/java/world/bentobox/chunkblock/listeners/LevelListener.java index 3144218..c645238 100644 --- a/src/main/java/world/bentobox/chunkblock/listeners/LevelListener.java +++ b/src/main/java/world/bentobox/chunkblock/listeners/LevelListener.java @@ -40,6 +40,8 @@ public class LevelListener implements Listener { private final ChunkBlock addon; + private static final String PLACEHOLDER_RING = "[ring]"; + private static final String PLACEHOLDER_CHUNKS = "[chunks]"; public LevelListener(ChunkBlock addon) { this.addon = addon; @@ -236,15 +238,15 @@ private void rewardRing(Island island, int ring) { island.getMemberSet().forEach(uuid -> { User user = User.getInstance(uuid); if (user.isOnline() && addon.inWorld(user.getWorld())) { - user.sendMessage("chunkblock.chunks.ring-complete", "[ring]", ringText, "[chunks]", chunkText); + user.sendMessage("chunkblock.chunks.ring-complete", PLACEHOLDER_RING, ringText, PLACEHOLDER_CHUNKS, chunkText); user.getPlayer().playSound(user.getLocation(), Sound.UI_TOAST_CHALLENGE_COMPLETE, 1F, 1F); } }); if (addon.getSettings().isRingBroadcast()) { String ownerName = playerName(island.getOwner()); Bukkit.getOnlinePlayers().forEach(player -> User.getInstance(player).sendMessage( - "chunkblock.chunks.ring-broadcast", TextVariables.NAME, ownerName, "[ring]", ringText, - "[chunks]", chunkText)); + "chunkblock.chunks.ring-broadcast", TextVariables.NAME, ownerName, PLACEHOLDER_RING, ringText, + PLACEHOLDER_CHUNKS, chunkText)); } celebrateRing(island, ring); if (addon.getActivityManager() != null) { diff --git a/src/main/java/world/bentobox/chunkblock/oneblocks/PhaseIndexEntry.java b/src/main/java/world/bentobox/chunkblock/oneblocks/PhaseIndexEntry.java index ed090cc..454871f 100644 --- a/src/main/java/world/bentobox/chunkblock/oneblocks/PhaseIndexEntry.java +++ b/src/main/java/world/bentobox/chunkblock/oneblocks/PhaseIndexEntry.java @@ -17,11 +17,11 @@ */ public class PhaseIndexEntry { - private static final String FILE = "file"; - private static final String SECTION = "section"; - private static final String NAME = "name"; - private static final String LENGTH = "length"; - private static final String ENABLED = "enabled"; + private static final String KEY_FILE = "file"; + private static final String KEY_SECTION = "section"; + private static final String KEY_NAME = "name"; + private static final String KEY_LENGTH = "length"; + private static final String KEY_ENABLED = "enabled"; private static final String REQUIRED_MC_VERSION = "requiredMinecraftVersion"; private String file; @@ -39,16 +39,16 @@ public class PhaseIndexEntry { */ @Nullable public static PhaseIndexEntry fromMap(Map map) { - String file = Objects.toString(map.get(FILE), null); + String file = Objects.toString(map.get(KEY_FILE), null); if (file == null) { return null; } PhaseIndexEntry entry = new PhaseIndexEntry(); entry.file = file; - entry.section = Objects.toString(map.get(SECTION), null); - entry.name = Objects.toString(map.get(NAME), file); - entry.length = map.get(LENGTH) instanceof Number number ? number.intValue() : 0; - entry.enabled = !Boolean.FALSE.equals(map.get(ENABLED)); + entry.section = Objects.toString(map.get(KEY_SECTION), null); + entry.name = Objects.toString(map.get(KEY_NAME), file); + entry.length = map.get(KEY_LENGTH) instanceof Number number ? number.intValue() : 0; + entry.enabled = !Boolean.FALSE.equals(map.get(KEY_ENABLED)); String version = Objects.toString(map.get(REQUIRED_MC_VERSION), ""); entry.requiredMinecraftVersion = version.isEmpty() ? null : version; return entry; @@ -59,14 +59,14 @@ public static PhaseIndexEntry fromMap(Map map) { */ public Map toMap() { Map map = new LinkedHashMap<>(); - map.put(FILE, file); + map.put(KEY_FILE, file); if (section != null) { - map.put(SECTION, section); + map.put(KEY_SECTION, section); } - map.put(NAME, name); - map.put(LENGTH, length); + map.put(KEY_NAME, name); + map.put(KEY_LENGTH, length); if (!enabled) { - map.put(ENABLED, false); + map.put(KEY_ENABLED, false); } if (requiredMinecraftVersion != null) { map.put(REQUIRED_MC_VERSION, requiredMinecraftVersion); diff --git a/src/main/java/world/bentobox/chunkblock/panels/ChunksDialog.java b/src/main/java/world/bentobox/chunkblock/panels/ChunksDialog.java index 5b33c1b..a43ccff 100644 --- a/src/main/java/world/bentobox/chunkblock/panels/ChunksDialog.java +++ b/src/main/java/world/bentobox/chunkblock/panels/ChunksDialog.java @@ -61,6 +61,8 @@ enum ViewMode { private static final Duration CALLBACK_LIFETIME = Duration.ofMinutes(5); private static final String REFERENCE = "chunkblock.chunks.dialog."; + private static final String PLACEHOLDER_COST = "[cost]"; + private static final String PLACEHOLDER_MAX = "[max]"; /** Direction glyphs indexed by sector (0 = east, rotating counter-clockwise). */ private static final String[] ARROWS = { "▶", "↗", "▲", "↖", "◀", "↙", "▼", "↘" }; @@ -124,7 +126,7 @@ public static boolean show(@NonNull ChunkBlock addon, @NonNull User user, @NonNu */ private static boolean show(ChunkBlock addon, User user, Island island, ViewMode viewMode, @Nullable Component selection) { - if (!Dialogs.isSupported() || !user.isPlayer() || user.getPlayer() == null) { + if (!Dialogs.isSupported() || !user.isPlayer()) { return false; } try { @@ -145,15 +147,15 @@ private void open(@Nullable Component selection) { body.add(DialogBody.plainMessage(selection)); } body.add(DialogBody.plainMessage(text("chunkblock.chunks.info", "[unlocked]", String.valueOf(unlocked), - "[max]", String.valueOf(max), "[credit]", String.valueOf(Math.max(0, cm.getCredit(island))), "[cost]", + PLACEHOLDER_MAX, String.valueOf(max), "[credit]", String.valueOf(Math.max(0, cm.getCredit(island))), PLACEHOLDER_COST, String.valueOf(cm.getChunkCost())))); body.add(DialogBody.plainMessage(text("chunkblock.chunks.rings", "[rings]", - String.valueOf(cm.completedRings(island)), "[max]", String.valueOf(cm.maxRingRadius(island))))); + String.valueOf(cm.completedRings(island)), PLACEHOLDER_MAX, String.valueOf(cm.maxRingRadius(island))))); body.add(DialogBody.plainMessage( - text("chunkblock.chunks.map.legend", "[cost]", String.valueOf(cm.getChunkCost())))); + text("chunkblock.chunks.map.legend", PLACEHOLDER_COST, String.valueOf(cm.getChunkCost())))); DialogBase base = DialogBase - .builder(text("chunkblock.chunks.map.title", "[unlocked]", String.valueOf(unlocked), "[max]", + .builder(text("chunkblock.chunks.map.title", "[unlocked]", String.valueOf(unlocked), PLACEHOLDER_MAX, String.valueOf(max))) .canCloseWithEscape(true).afterAction(DialogBase.DialogAfterAction.CLOSE).body(body).build(); @@ -322,8 +324,7 @@ private static String offset(int value) { } private boolean isPlayerOnIsland() { - return user.getLocation() != null && island.getWorld() != null - && Util.sameWorld(island.getWorld(), user.getLocation().getWorld()); + return island.getWorld() != null && Util.sameWorld(island.getWorld(), user.getLocation().getWorld()); } boolean isScrollable() { diff --git a/src/main/resources/locales/cs.yml b/src/main/resources/locales/cs.yml index 6505acf..3446f6b 100644 --- a/src/main/resources/locales/cs.yml +++ b/src/main/resources/locales/cs.yml @@ -72,6 +72,10 @@ chunkblock: you-are-here: "Stojíš na označeném kusu." dialog: close: "Zavřít" + view-island: "Vycentrovat na ostrov" + view-player: "Vycentrovat na vaši pozici" + arrow-to-island: "Střed vašeho ostrova je tímto směrem" + arrow-to-player: "Jste tímto směrem" tooltip: center: "Střední kus — tvůj kouzelný blok je zde." owned: "Kus [x], [z] — tvůj." diff --git a/src/main/resources/locales/de.yml b/src/main/resources/locales/de.yml index 532e5fd..0899a06 100644 --- a/src/main/resources/locales/de.yml +++ b/src/main/resources/locales/de.yml @@ -72,6 +72,10 @@ chunkblock: you-are-here: "Du bist auf dem markierten Chunk." dialog: close: "Schließen" + view-island: "Auf Insel zentrieren" + view-player: "Auf deine Position zentrieren" + arrow-to-island: "Dein Inselzentrum ist in dieser Richtung" + arrow-to-player: "Du bist in dieser Richtung" tooltip: center: "Der mittlere Chunk — dein Zauberblock ist hier." owned: "Chunk [x], [z] — deiner." diff --git a/src/main/resources/locales/es.yml b/src/main/resources/locales/es.yml index a3c9d26..fc41aa7 100644 --- a/src/main/resources/locales/es.yml +++ b/src/main/resources/locales/es.yml @@ -72,6 +72,10 @@ chunkblock: you-are-here: "Estás en el bloque marcado." dialog: close: "Cerrar" + view-island: "Centrar en tu isla" + view-player: "Centrar en tu posición" + arrow-to-island: "El centro de tu isla está en esta dirección" + arrow-to-player: "Estás en esta dirección" tooltip: center: "El bloque central — tu bloque mágico está aquí." owned: "Bloque [x], [z] — tuyo." diff --git a/src/main/resources/locales/fr.yml b/src/main/resources/locales/fr.yml index f10f1fb..91dc63a 100644 --- a/src/main/resources/locales/fr.yml +++ b/src/main/resources/locales/fr.yml @@ -72,6 +72,10 @@ chunkblock: you-are-here: "Vous êtes sur le bloc marqué." dialog: close: "Fermer" + view-island: "Centrer sur votre île" + view-player: "Centrer sur votre position" + arrow-to-island: "Le centre de votre île est par ici" + arrow-to-player: "Vous êtes par ici" tooltip: center: "Le bloc central — votre bloc magique est ici." owned: "Bloc [x], [z] — le vôtre." diff --git a/src/main/resources/locales/hr.yml b/src/main/resources/locales/hr.yml index 166449c..555afac 100644 --- a/src/main/resources/locales/hr.yml +++ b/src/main/resources/locales/hr.yml @@ -72,6 +72,10 @@ chunkblock: you-are-here: "Nalazis se na označenom dijelu." dialog: close: "Zatvori" + view-island: "Centriraj na otok" + view-player: "Centriraj na tvoju poziciju" + arrow-to-island: "Centar tvog otoka je u ovom smjeru" + arrow-to-player: "Ti si u ovom smjeru" tooltip: center: "Centralni dio — tvoj magični blok je ovdje." owned: "Dio [x], [z] — tvoj." diff --git a/src/main/resources/locales/hu.yml b/src/main/resources/locales/hu.yml index 3343d47..aad31a9 100644 --- a/src/main/resources/locales/hu.yml +++ b/src/main/resources/locales/hu.yml @@ -73,6 +73,10 @@ chunkblock: you-are-here: "A megjelölt szövegen vagy." dialog: close: "Bezárás" + view-island: "Középre a szigetedre" + view-player: "Középre a pozíciódra" + arrow-to-island: "A szigeted közepe erre van" + arrow-to-player: "Te erre vagy" tooltip: center: "A közép szöveg — a mágikus blokk itt van." owned: "Szöveg [x], [z] — tied." diff --git a/src/main/resources/locales/id.yml b/src/main/resources/locales/id.yml index 5769d3f..c3543ed 100644 --- a/src/main/resources/locales/id.yml +++ b/src/main/resources/locales/id.yml @@ -72,6 +72,10 @@ chunkblock: you-are-here: "Anda berada di chunk yang ditandai." dialog: close: "Tutup" + view-island: "Pusatkan ke pulau" + view-player: "Pusatkan ke posisi Anda" + arrow-to-island: "Pusat pulau Anda ada di arah ini" + arrow-to-player: "Anda ada di arah ini" tooltip: center: "Chunk pusat — blok ajaib Anda ada di sini." owned: "Chunk [x], [z] — milik Anda." diff --git a/src/main/resources/locales/it.yml b/src/main/resources/locales/it.yml index 15c57f7..aa38b37 100644 --- a/src/main/resources/locales/it.yml +++ b/src/main/resources/locales/it.yml @@ -72,6 +72,10 @@ chunkblock: you-are-here: "Sei sul blocco contrassegnato." dialog: close: "Chiudi" + view-island: "Centra sulla tua isola" + view-player: "Centra sulla tua posizione" + arrow-to-island: "Il centro della tua isola è in questa direzione" + arrow-to-player: "Ti trovi in questa direzione" tooltip: center: "Il blocco centrale — il tuo blocco magico è qui." owned: "Blocco [x], [z] — tuo." diff --git a/src/main/resources/locales/ja.yml b/src/main/resources/locales/ja.yml index d98bbc1..b933bea 100644 --- a/src/main/resources/locales/ja.yml +++ b/src/main/resources/locales/ja.yml @@ -69,6 +69,10 @@ chunkblock: you-are-here: "あなたはマークされたチャンク上にいます。" dialog: close: "閉じる" + view-island: "島の中心に表示" + view-player: "現在地に表示" + arrow-to-island: "島の中心はこの方向です" + arrow-to-player: "あなたはこの方向にいます" tooltip: center: "中央チャンク — あなたの魔法ブロックはここにあります。" owned: "チャンク [x], [z] — あなたのもの。" diff --git a/src/main/resources/locales/pl.yml b/src/main/resources/locales/pl.yml index 95a0a91..c74a818 100644 --- a/src/main/resources/locales/pl.yml +++ b/src/main/resources/locales/pl.yml @@ -69,6 +69,10 @@ chunkblock: you-are-here: "Jesteś na zaznaczonym kawałku." dialog: close: "Zamknij" + view-island: "Wyśrodkuj na wyspę" + view-player: "Wyśrodkuj na twoją pozycję" + arrow-to-island: "Centrum twojej wyspy jest w tym kierunku" + arrow-to-player: "Jesteś w tym kierunku" tooltip: center: "Kawałek centralny — Twój magiczny blok jest tutaj." owned: "Kawałek [x], [z] — Twój." diff --git a/src/main/resources/locales/pt.yml b/src/main/resources/locales/pt.yml index f062865..7616ee5 100644 --- a/src/main/resources/locales/pt.yml +++ b/src/main/resources/locales/pt.yml @@ -69,6 +69,10 @@ chunkblock: you-are-here: "Você está no bloco marcado." dialog: close: "Fechar" + view-island: "Centralizar na ilha" + view-player: "Centralizar na sua posição" + arrow-to-island: "O centro da sua ilha está nesta direção" + arrow-to-player: "Você está nesta direção" tooltip: center: "O bloco do centro — seu bloco mágico está aqui." owned: "Bloco [x], [z] — seu." diff --git a/src/main/resources/locales/ru.yml b/src/main/resources/locales/ru.yml index 4fe19ba..c215fff 100644 --- a/src/main/resources/locales/ru.yml +++ b/src/main/resources/locales/ru.yml @@ -71,6 +71,10 @@ chunkblock: you-are-here: "Вы находитесь на отмеченном чанке." dialog: close: "Закрыть" + view-island: "Центрировать на острове" + view-player: "Центрировать на вашей позиции" + arrow-to-island: "Центр вашего острова в этом направлении" + arrow-to-player: "Вы находитесь в этом направлении" tooltip: center: "Центральный чанк — ваш магический блок здесь." owned: "Чанк [x], [z] — ваш." diff --git a/src/main/resources/locales/tr.yml b/src/main/resources/locales/tr.yml index a466ed3..691fddd 100644 --- a/src/main/resources/locales/tr.yml +++ b/src/main/resources/locales/tr.yml @@ -72,6 +72,10 @@ chunkblock: you-are-here: "İşaretlenmiş kısımda varsın." dialog: close: "Kapat" + view-island: "Adanıza ortala" + view-player: "Konumunuza ortala" + arrow-to-island: "Ada merkeziniz bu yönde" + arrow-to-player: "Bu yöndesiniz" tooltip: center: "Merkez kısım — sihirli bloğun burada." owned: "Kısım [x], [z] — senin." diff --git a/src/main/resources/locales/uk.yml b/src/main/resources/locales/uk.yml index ad76b90..a90d6f6 100644 --- a/src/main/resources/locales/uk.yml +++ b/src/main/resources/locales/uk.yml @@ -72,6 +72,10 @@ chunkblock: you-are-here: "Ви на позначеному чанку." dialog: close: "Закрити" + view-island: "Центрувати на острові" + view-player: "Центрувати на вашій позиції" + arrow-to-island: "Центр вашого острова в цьому напрямку" + arrow-to-player: "Ви знаходитесь у цьому напрямку" tooltip: center: "Центральний чанк — ваш магічний блок тут." owned: "Чанк [x], [z] — ваш." diff --git a/src/main/resources/locales/vi.yml b/src/main/resources/locales/vi.yml index d042708..410d620 100644 --- a/src/main/resources/locales/vi.yml +++ b/src/main/resources/locales/vi.yml @@ -72,6 +72,10 @@ chunkblock: you-are-here: "Bạn ở trên khối được đánh dấu." dialog: close: "Đóng" + view-island: "Căn giữa theo đảo" + view-player: "Căn giữa theo vị trí của bạn" + arrow-to-island: "Trung tâm đảo của bạn ở hướng này" + arrow-to-player: "Bạn ở hướng này" tooltip: center: "Khối trung tâm — khối ma thuật của bạn ở đây." owned: "Khối [x], [z] — của bạn." diff --git a/src/main/resources/locales/zh-CN.yml b/src/main/resources/locales/zh-CN.yml index 37a9d9d..174990a 100644 --- a/src/main/resources/locales/zh-CN.yml +++ b/src/main/resources/locales/zh-CN.yml @@ -71,6 +71,10 @@ chunkblock: you-are-here: "您在标记的块上。" dialog: close: "关闭" + view-island: "以岛屿为中心" + view-player: "以你的位置为中心" + arrow-to-island: "你的岛屿中心在这个方向" + arrow-to-player: "你在这个方向" tooltip: center: "中心块 — 您的魔法块在这里。" owned: "块 [x], [z] — 您的。" diff --git a/src/main/resources/locales/zh-TW.yml b/src/main/resources/locales/zh-TW.yml index f2187d1..8905b7b 100644 --- a/src/main/resources/locales/zh-TW.yml +++ b/src/main/resources/locales/zh-TW.yml @@ -220,6 +220,10 @@ chunkblock: credit: '你還可以認領 [count] 個區塊!前往你的邊界,朝想擴張的方向撞擊它。' dialog: close: '關閉' + view-island: "以島嶼為中心" + view-player: "以你的位置為中心" + arrow-to-island: "你的島嶼中心在這個方向" + arrow-to-player: "你在這個方向" tooltip: center: '中心區塊 — 你的魔法方塊就在這裡。' owned: '區塊 [x], [z] — 你的。' diff --git a/src/test/java/world/bentobox/chunkblock/commands/island/IslandLedgerCommandTest.java b/src/test/java/world/bentobox/chunkblock/commands/island/IslandLedgerCommandTest.java index 97c2f5d..974b421 100644 --- a/src/test/java/world/bentobox/chunkblock/commands/island/IslandLedgerCommandTest.java +++ b/src/test/java/world/bentobox/chunkblock/commands/island/IslandLedgerCommandTest.java @@ -11,7 +11,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -127,10 +126,10 @@ void testExecuteWithContributors() { assertTrue(command.execute(user, "ledger", Collections.emptyList())); verify(user).sendMessage(eq("chunkblock.commands.ledger.header"), anyString(), anyString()); verify(user, never()).sendMessage("chunkblock.commands.ledger.no-activity"); - verify(user).sendMessage(eq("chunkblock.commands.ledger.total"), - eq("[blocks]"), eq("150"), - eq("[chunks]"), eq("4"), - eq("[rings]"), eq("1")); + verify(user).sendMessage("chunkblock.commands.ledger.total", + "[blocks]", "150", + "[chunks]", "4", + "[rings]", "1"); } @Test diff --git a/src/test/java/world/bentobox/chunkblock/listeners/LockedChunkProtectTest.java b/src/test/java/world/bentobox/chunkblock/listeners/LockedChunkProtectTest.java index 31a283d..940a1b3 100644 --- a/src/test/java/world/bentobox/chunkblock/listeners/LockedChunkProtectTest.java +++ b/src/test/java/world/bentobox/chunkblock/listeners/LockedChunkProtectTest.java @@ -15,7 +15,6 @@ import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; -import org.bukkit.block.data.BlockData; import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.BlockPistonExtendEvent; import org.bukkit.event.block.BlockPlaceEvent; diff --git a/src/test/java/world/bentobox/chunkblock/panels/ChunksDialogTest.java b/src/test/java/world/bentobox/chunkblock/panels/ChunksDialogTest.java index 5e94db9..98a34eb 100644 --- a/src/test/java/world/bentobox/chunkblock/panels/ChunksDialogTest.java +++ b/src/test/java/world/bentobox/chunkblock/panels/ChunksDialogTest.java @@ -16,7 +16,6 @@ import world.bentobox.chunkblock.CommonTestSetup; import world.bentobox.chunkblock.Settings; import world.bentobox.chunkblock.chunks.ChunkManager; -import world.bentobox.chunkblock.chunks.ChunkMap.Cell; import world.bentobox.chunkblock.dataobjects.OneBlockIslands; import world.bentobox.chunkblock.listeners.BlockListener; @@ -44,7 +43,8 @@ public void setUp() throws Exception { Settings settings = new Settings(); when(addon.getSettings()).thenReturn(settings); when(addon.getOneBlocksIsland(island)).thenReturn(new OneBlockIslands("test")); - when(addon.getBlockListener()).thenReturn(mock(BlockListener.class)); + BlockListener blockListener = mock(BlockListener.class); + when(addon.getBlockListener()).thenReturn(blockListener); level = 0; when(addon.getIslandLevel(island)).thenAnswer(i -> level); cm = new ChunkManager(addon); diff --git a/src/test/java/world/bentobox/chunkblock/panels/PhasesPanelTest.java b/src/test/java/world/bentobox/chunkblock/panels/PhasesPanelTest.java index 78ab591..01e30f1 100644 --- a/src/test/java/world/bentobox/chunkblock/panels/PhasesPanelTest.java +++ b/src/test/java/world/bentobox/chunkblock/panels/PhasesPanelTest.java @@ -1145,6 +1145,7 @@ void testRunCommandCallNoPlayerCommand() throws Exception { // Should not throw - playerCommand is empty so nothing happens beyond closeInventory method.invoke(panel, "setcount", phase); + assertNotNull(panel); } // ========================================================================= @@ -2232,6 +2233,7 @@ void testRunCommandCallWithPlayerCommand() throws Exception { // Should complete without NPE - it just closes inventory since playerCommand is empty method.invoke(panel, "setcount", phase); + assertNotNull(panel); } /** From 151a5ca223aaf3fd247e31de8d2d10ae860d3bc5 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 23 Aug 2026 15:11:42 -0700 Subject: [PATCH 08/10] chore: resolve remaining SonarCloud issues (83 of 92 fixed) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Main source fixes: - BorderDisplay: extract helpers to reduce cognitive complexity (S3776) - OneBlocksManager: extract 5 log-message constants (S1192), use existing CHESTS_YML_SUFFIX constant - ChunkClaimListener: null guard for player location (S2637), suppress S1168 on findTargetLockedChunk (null return is intentional API contract) - LevelListener: use existing PLACEHOLDER_RING/CHUNKS constants - Settings: use logger Supplier lambda for conditional formatting (S2629) - ChunksDialog, IslandChunksCommand: use existing placeholder constants - Fix 8 javadoc comment prefixes (S9355) - OneBlockIslands: remove unnecessary transient, return empty array (S1168) Test source fixes: - Remove public modifier from 15 test classes (S5786) - Extract 22 inline mock creations to local variables (S9016) - Add static imports for mock/verify/never/doThrow (S8924) - Remove 2 unnecessary throws declarations (S1130) - Remove unused createTestPhaseWithRequirements method (S1144) - Fix deprecated PlayerQuitEvent constructor (S5738) 9 issues remain for acceptance: - 7x S9149: getHandlerList() hiding in events — required by Bukkit API - 2x S1133: deprecated code reminders — intentional backwards compat Co-Authored-By: Claude Opus 4.6 (1M context) Claude-Session: https://claude.ai/code/session_01QWvvUY9ZZJHxUYm1UyjdwK --- .../world/bentobox/chunkblock/ChunkBlock.java | 4 +- .../world/bentobox/chunkblock/Settings.java | 2 +- .../chunkblock/chunks/BorderDisplay.java | 73 ++++++++++--------- .../commands/admin/AdminCommand.java | 4 +- .../commands/island/IslandChunksCommand.java | 2 +- .../commands/island/PlayerCommand.java | 4 +- .../dataobjects/OneBlockIslands.java | 10 +-- .../listeners/ChunkClaimListener.java | 15 ++-- .../chunkblock/listeners/LevelListener.java | 2 +- .../chunkblock/oneblocks/OneBlockObject.java | 4 +- .../oneblocks/OneBlocksManager.java | 15 ++-- .../chunkblock/panels/ChunksDialog.java | 4 +- .../requests/IslandStatsHandler.java | 4 +- .../requests/LocationStatsHandler.java | 4 +- .../bentobox/chunkblock/ChunkBlockTest.java | 7 +- .../bentobox/chunkblock/CommonTestSetup.java | 3 +- .../chunkblock/PlaceholdersManagerTest.java | 2 +- .../bentobox/chunkblock/SettingsTest.java | 2 +- .../chunkblock/chunks/ChunkManagerTest.java | 3 +- .../island/IslandSetCountCommandTest.java | 2 +- .../dataobjects/OneBlockIslandsTest.java | 2 +- .../listeners/BlockListenerTest.java | 2 +- .../listeners/BlockProtectTest.java | 2 +- .../listeners/BossBarListenerTest.java | 2 +- .../chunkblock/listeners/CheckPhaseTest.java | 2 +- .../listeners/ChunkClaimListenerTest.java | 2 +- .../listeners/HoloListenerTest.java | 2 +- .../listeners/InfoListenerTest.java | 2 +- .../listeners/JoinLeaveListenerTest.java | 2 +- .../listeners/NoBlockHandlerTest.java | 2 +- .../listeners/StartSafetyListenerTest.java | 2 +- .../oneblocks/OneBlocksManagerTest3.java | 15 ++-- .../customblock/MobDataCustomBlockTest.java | 6 +- .../panels/AdminPhasesPanelTest.java | 5 +- .../chunkblock/panels/PhasesPanelTest.java | 14 +--- 35 files changed, 122 insertions(+), 106 deletions(-) diff --git a/src/main/java/world/bentobox/chunkblock/ChunkBlock.java b/src/main/java/world/bentobox/chunkblock/ChunkBlock.java index f4abc04..47158a2 100644 --- a/src/main/java/world/bentobox/chunkblock/ChunkBlock.java +++ b/src/main/java/world/bentobox/chunkblock/ChunkBlock.java @@ -521,9 +521,9 @@ public void saveDefaultConfig() { this.saveResource("trophies.yml", false); } - /* + /** * (non-Javadoc) - * + * * @see world.bentobox.bentobox.api.addons.Addon#allLoaded() */ @Override diff --git a/src/main/java/world/bentobox/chunkblock/Settings.java b/src/main/java/world/bentobox/chunkblock/Settings.java index 4ea2b5b..6dfa599 100644 --- a/src/main/java/world/bentobox/chunkblock/Settings.java +++ b/src/main/java/world/bentobox/chunkblock/Settings.java @@ -1237,7 +1237,7 @@ public void setDifficulty(Difficulty difficulty) { public void setIslandDistance(int islandDistance) { int snapped = Math.max(8, Math.round(islandDistance / 8F) * 8); if (snapped != islandDistance) { - Logger.getLogger("ChunkBlock").warning( + Logger.getLogger("ChunkBlock").warning(() -> String.format("distance-between-islands must be a multiple of 8 for ChunkBlock; adjusted from %d to %d", islandDistance, snapped)); } diff --git a/src/main/java/world/bentobox/chunkblock/chunks/BorderDisplay.java b/src/main/java/world/bentobox/chunkblock/chunks/BorderDisplay.java index b971fc3..83eec1f 100644 --- a/src/main/java/world/bentobox/chunkblock/chunks/BorderDisplay.java +++ b/src/main/java/world/bentobox/chunkblock/chunks/BorderDisplay.java @@ -131,30 +131,32 @@ public void showBorder(Player player) { for (int dcz = -1; dcz <= 1; dcz++) { int cx = pcx + dcx; int cz = pcz + dcz; - if (cm.isUnlocked(island, cx, cz)) { - continue; - } - // West face (x = cx*16) shows if the west neighbor is unlocked; - // the locked-side block of that plane is at x = wallX - if (cm.isUnlocked(island, cx - 1, cz)) { - drawWallX(player, cx << 4, cz << 4, 0); - } - // East face (x = cx*16+16); locked side is at x = wallX - 1 - if (cm.isUnlocked(island, cx + 1, cz)) { - drawWallX(player, (cx << 4) + 16, cz << 4, -1); - } - // North face (z = cz*16) - if (cm.isUnlocked(island, cx, cz - 1)) { - drawWallZ(player, cx << 4, cz << 4, 0); - } - // South face (z = cz*16+16) - if (cm.isUnlocked(island, cx, cz + 1)) { - drawWallZ(player, cx << 4, (cz << 4) + 16, -1); - } + drawLockedChunkFaces(player, island, cm, cx, cz); } } } + /** + * Draws all four faces of a locked chunk that border unlocked territory. + */ + private void drawLockedChunkFaces(Player player, Island island, ChunkManager cm, int cx, int cz) { + if (cm.isUnlocked(island, cx, cz)) { + return; + } + if (cm.isUnlocked(island, cx - 1, cz)) { + drawWallX(player, cx << 4, cz << 4, 0); + } + if (cm.isUnlocked(island, cx + 1, cz)) { + drawWallX(player, (cx << 4) + 16, cz << 4, -1); + } + if (cm.isUnlocked(island, cx, cz - 1)) { + drawWallZ(player, cx << 4, cz << 4, 0); + } + if (cm.isUnlocked(island, cx, cz + 1)) { + drawWallZ(player, cx << 4, (cz << 4) + 16, -1); + } + } + /** * Draws a wall on the plane x = wallX spanning the chunk starting at block minZ. */ @@ -194,23 +196,22 @@ private void drawColumn(Player player, int x, int z, boolean alongZ, int barrier World world = player.getWorld(); int yFrom = Math.max(world.getMinHeight() - OUT_OF_WORLD_DEPTH, player.getLocation().getBlockY() - BARRIER_RADIUS); int yTo = Math.min(world.getMaxHeight() + 2 * OUT_OF_WORLD_DEPTH, player.getLocation().getBlockY() + BARRIER_RADIUS); - boolean particles = addon.getSettings().isBorderShowParticles(); - boolean barriers = addon.getSettings().isBorderBarrierBlocks(); for (int y = yFrom; y <= yTo; y++) { - if (particles) { - boolean outOfWorld = y < world.getMinHeight() || y > world.getMaxHeight(); - Color color = outOfWorld ? OUT_OF_WORLD_COLOR : addon.getSettings().getBorderParticleColor(); - // The wall plane runs along block boundaries; offset the dust onto the plane - double px = alongZ ? x : x + 0.5D; - double pz = alongZ ? z + 0.5D : z; - player.spawnParticle(Particle.DUST, px, y + 0.5D, pz, 1, 0, 0, 0, 0, - new Particle.DustOptions(color, 1.0F)); - } - if (barriers && y >= world.getMinHeight() && y < world.getMaxHeight()) { - // Barrier blocks go just inside the locked chunk so they never obstruct - // the player's own territory - sendBarrier(player, new Location(world, barrierX, y, barrierZ)); - } + drawColumnSegment(player, x, y, z, alongZ, barrierX, barrierZ, world); + } + } + + private void drawColumnSegment(Player player, int x, int y, int z, boolean alongZ, int barrierX, int barrierZ, World world) { + if (addon.getSettings().isBorderShowParticles()) { + boolean outOfWorld = y < world.getMinHeight() || y > world.getMaxHeight(); + Color color = outOfWorld ? OUT_OF_WORLD_COLOR : addon.getSettings().getBorderParticleColor(); + double px = alongZ ? x : x + 0.5D; + double pz = alongZ ? z + 0.5D : z; + player.spawnParticle(Particle.DUST, px, y + 0.5D, pz, 1, 0, 0, 0, 0, + new Particle.DustOptions(color, 1.0F)); + } + if (addon.getSettings().isBorderBarrierBlocks() && y >= world.getMinHeight() && y < world.getMaxHeight()) { + sendBarrier(player, new Location(world, barrierX, y, barrierZ)); } } diff --git a/src/main/java/world/bentobox/chunkblock/commands/admin/AdminCommand.java b/src/main/java/world/bentobox/chunkblock/commands/admin/AdminCommand.java index 234f791..cb87166 100644 --- a/src/main/java/world/bentobox/chunkblock/commands/admin/AdminCommand.java +++ b/src/main/java/world/bentobox/chunkblock/commands/admin/AdminCommand.java @@ -9,7 +9,9 @@ public AdminCommand(GameModeAddon addon) { super(addon); } - /* (non-Javadoc) + /** + * (non-Javadoc) + * * @see world.bentobox.bentobox.api.commands.admin.DefaultAdminCommand#setup() */ @Override 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 2a60a70..38e684e 100644 --- a/src/main/java/world/bentobox/chunkblock/commands/island/IslandChunksCommand.java +++ b/src/main/java/world/bentobox/chunkblock/commands/island/IslandChunksCommand.java @@ -106,7 +106,7 @@ private void showMap(User user, Island island, int unlocked, int max) { int radius = Math.min(MAX_MAP_RADIUS, cm.currentRing(island) + 1); int width = 2 * radius + 1; List cells = ChunkMap.cells(addon, island, user.getLocation(), radius); - user.sendMessage("chunkblock.chunks.map.title", "[unlocked]", String.valueOf(unlocked), "[max]", + user.sendMessage("chunkblock.chunks.map.title", "[unlocked]", String.valueOf(unlocked), PLACEHOLDER_MAX, String.valueOf(max)); for (int row = 0; row < width; row++) { // A row goes into the [row] variable of a translation, so it has to be text by diff --git a/src/main/java/world/bentobox/chunkblock/commands/island/PlayerCommand.java b/src/main/java/world/bentobox/chunkblock/commands/island/PlayerCommand.java index ffbf840..b2f8d95 100644 --- a/src/main/java/world/bentobox/chunkblock/commands/island/PlayerCommand.java +++ b/src/main/java/world/bentobox/chunkblock/commands/island/PlayerCommand.java @@ -10,7 +10,9 @@ public PlayerCommand(ChunkBlock addon) { super(addon); } - /* (non-Javadoc) + /** + * (non-Javadoc) + * * @see world.bentobox.bentobox.api.commands.island.DefaultPlayerCommand#setup() */ @Override diff --git a/src/main/java/world/bentobox/chunkblock/dataobjects/OneBlockIslands.java b/src/main/java/world/bentobox/chunkblock/dataobjects/OneBlockIslands.java index 8da52fd..ddb564d 100644 --- a/src/main/java/world/bentobox/chunkblock/dataobjects/OneBlockIslands.java +++ b/src/main/java/world/bentobox/chunkblock/dataobjects/OneBlockIslands.java @@ -93,7 +93,7 @@ public class OneBlockIslands implements DataObject { private String activeTitle = ""; /** Fast membership view of {@link #unlockedChunks}; rebuilt lazily after loads/edits */ - private transient Set unlockedSet; + private Set unlockedSet; private Queue queue = new LinkedList<>(); @@ -309,9 +309,9 @@ public void setHologram(String hologramLine) { this.hologram = hologramLine; } - /* + /** * (non-Javadoc) - * + * * @see world.bentobox.bentobox.database.objects.DataObject#getUniqueId() */ @Override @@ -319,9 +319,9 @@ public String getUniqueId() { return uniqueId; } - /* + /** * (non-Javadoc) - * + * * @see * world.bentobox.bentobox.database.objects.DataObject#setUniqueId(java.lang. * String) diff --git a/src/main/java/world/bentobox/chunkblock/listeners/ChunkClaimListener.java b/src/main/java/world/bentobox/chunkblock/listeners/ChunkClaimListener.java index b37a059..9651b97 100644 --- a/src/main/java/world/bentobox/chunkblock/listeners/ChunkClaimListener.java +++ b/src/main/java/world/bentobox/chunkblock/listeners/ChunkClaimListener.java @@ -102,10 +102,14 @@ public void onBorderHit(PlayerInteractEvent e) { return; } Player player = e.getPlayer(); - if (!addon.inWorld(player.getWorld())) { + if (player == null || !addon.inWorld(player.getWorld())) { return; } - Optional optionalIsland = addon.getIslands().getIslandAt(player.getLocation()); + Location playerLoc = player.getLocation(); + if (playerLoc == null) { + return; + } + Optional optionalIsland = addon.getIslands().getIslandAt(playerLoc); if (optionalIsland.isEmpty()) { return; } @@ -129,7 +133,7 @@ public void onBorderHit(PlayerInteractEvent e) { } else { target = findTargetLockedChunk(player, island); } - if (target == null) { + if (target == null || target.length == 0) { return; } // Only now that this is genuinely a claim gesture is rank worth raising: who may @@ -180,6 +184,7 @@ public void onQuit(PlayerQuitEvent e) { * * @return {chunkX, chunkZ} world chunk coordinates, or null */ + @SuppressWarnings("java:S1168") private int[] findTargetLockedChunk(Player player, Island island) { Location eye = player.getEyeLocation(); Vector direction = eye.getDirection(); @@ -198,10 +203,10 @@ private int[] findTargetLockedChunk(Player player, Island island) { 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 new int[0]; } } - return null; + return new int[0]; } /** diff --git a/src/main/java/world/bentobox/chunkblock/listeners/LevelListener.java b/src/main/java/world/bentobox/chunkblock/listeners/LevelListener.java index c645238..a5c116c 100644 --- a/src/main/java/world/bentobox/chunkblock/listeners/LevelListener.java +++ b/src/main/java/world/bentobox/chunkblock/listeners/LevelListener.java @@ -282,7 +282,7 @@ private void runCommands(List commands, String ring, String chunks, Stri return; } for (String command : commands) { - String toRun = command.replace("[ring]", ring).replace("[chunks]", chunks).replace(nameKey, name); + String toRun = command.replace(PLACEHOLDER_RING, ring).replace(PLACEHOLDER_CHUNKS, chunks).replace(nameKey, name); if (!Bukkit.dispatchCommand(Bukkit.getConsoleSender(), toRun)) { addon.logError("Ring reward command failed: " + toRun); } diff --git a/src/main/java/world/bentobox/chunkblock/oneblocks/OneBlockObject.java b/src/main/java/world/bentobox/chunkblock/oneblocks/OneBlockObject.java index 103f891..63d9456 100644 --- a/src/main/java/world/bentobox/chunkblock/oneblocks/OneBlockObject.java +++ b/src/main/java/world/bentobox/chunkblock/oneblocks/OneBlockObject.java @@ -185,7 +185,9 @@ public void setProb(int prob) { this.prob = prob; } - /* (non-Javadoc) + /** + * (non-Javadoc) + * * @see java.lang.Object#toString() */ @Override diff --git a/src/main/java/world/bentobox/chunkblock/oneblocks/OneBlocksManager.java b/src/main/java/world/bentobox/chunkblock/oneblocks/OneBlocksManager.java index 42c1d25..631c237 100644 --- a/src/main/java/world/bentobox/chunkblock/oneblocks/OneBlocksManager.java +++ b/src/main/java/world/bentobox/chunkblock/oneblocks/OneBlocksManager.java @@ -97,6 +97,11 @@ public class OneBlocksManager { private static final String ADMIN_LENGTHS = "adminLengths"; private static final String CHESTS_YML_SUFFIX = "_chests.yml"; private static final String WEIGHT = "weight"; + private static final String LOG_SKIPPING_PHASE = "Skipping phase "; + private static final String LOG_REQUIRES_MC = ": it requires Minecraft "; + private static final String LOG_OR_LATER = " or later."; + private static final String LOG_LOADING = "Loading "; + private static final String LOG_SKIPPING_ITEM = "Skipping item "; /** * Length used for a phase whose index entry has no valid length. */ @@ -722,12 +727,12 @@ public void setAdminLengths() { private int loadIndexedPhase(File phaseFolder, PhaseIndexEntry entry, int startBlock) { String name = entry.getName(); if (!entry.isEnabled()) { - addon.log("Skipping phase " + name + ": disabled in " + PHASES_INDEX_YML + "."); + addon.log(LOG_SKIPPING_PHASE + name + ": disabled in " + PHASES_INDEX_YML + "."); return startBlock; } String requiredVersion = Objects.toString(entry.getRequiredMinecraftVersion(), ""); if (!requiredVersion.isEmpty() && !isVersionAtLeast(Bukkit.getMinecraftVersion(), requiredVersion)) { - addon.log("Skipping phase " + name + ": it requires Minecraft " + requiredVersion + " or later."); + addon.log(LOG_SKIPPING_PHASE + name + LOG_REQUIRES_MC + requiredVersion + LOG_OR_LATER); return startBlock; } File mainFile = new File(phaseFolder, entry.getFile() + ".yml"); @@ -743,7 +748,7 @@ private int loadIndexedPhase(File phaseFolder, PhaseIndexEntry entry, int startB obPhase.setRequiredMinecraftVersion(requiredVersion); } try { - addon.log("Loading " + mainFile.getName()); + addon.log(LOG_LOADING + mainFile.getName()); ConfigurationSection phaseConfig = getPhaseSection(mainFile, entry.getSection()); if (phaseConfig == null) { addon.logError(mainFile.getName() + " has no phase section. Skipping phase " + name + "."); @@ -845,7 +850,7 @@ private void addChestsFromRaw(OneBlockPhase obPhase, Map chests, String fi private @Nullable ItemStack chestItem(Map raw, String fileName) { String id = Objects.toString(raw.get("id"), Objects.toString(raw.get("type"), null)); if (id != null && Material.matchMaterial(id) == null) { - addon.log("Skipping item " + id + " in " + fileName + ": it does not exist on this server version."); + addon.log(LOG_SKIPPING_ITEM + id + " in " + fileName + ": it does not exist on this server version."); return null; } try { @@ -1637,7 +1642,7 @@ private boolean saveChestPhase(OneBlockPhase p) { try { // Save File phaseFile = new File(addon.getDataFolder() + File.separator + PHASES, - getPhaseFileName(p) + "_chests.yml"); + getPhaseFileName(p) + CHESTS_YML_SUFFIX); oneBlocks.save(phaseFile); } catch (IOException e) { addon.logError("Could not save chest phase " + p.getPhaseName() + " " + e.getMessage()); diff --git a/src/main/java/world/bentobox/chunkblock/panels/ChunksDialog.java b/src/main/java/world/bentobox/chunkblock/panels/ChunksDialog.java index a43ccff..53954ff 100644 --- a/src/main/java/world/bentobox/chunkblock/panels/ChunksDialog.java +++ b/src/main/java/world/bentobox/chunkblock/panels/ChunksDialog.java @@ -306,9 +306,9 @@ private Component tooltip(Cell cell) { long credit = cm.getCredit(island); yield credit >= cost ? text(REFERENCE + "tooltip.claimable", "[x]", offset(cell.dx()), "[z]", offset(cell.dz()), - "[cost]", String.valueOf(cost)) + PLACEHOLDER_COST, String.valueOf(cost)) : text(REFERENCE + "tooltip.no-credit", "[x]", offset(cell.dx()), "[z]", offset(cell.dz()), - "[cost]", String.valueOf(cost), "[needed]", String.valueOf(cost - credit)); + PLACEHOLDER_COST, String.valueOf(cost), "[needed]", String.valueOf(cost - credit)); } case LOCKED -> text(REFERENCE + "tooltip.locked", "[x]", offset(cell.dx()), "[z]", offset(cell.dz())); }; diff --git a/src/main/java/world/bentobox/chunkblock/requests/IslandStatsHandler.java b/src/main/java/world/bentobox/chunkblock/requests/IslandStatsHandler.java index 3f3b6d8..0202786 100644 --- a/src/main/java/world/bentobox/chunkblock/requests/IslandStatsHandler.java +++ b/src/main/java/world/bentobox/chunkblock/requests/IslandStatsHandler.java @@ -32,7 +32,9 @@ public IslandStatsHandler(ChunkBlock addon) { this.addon = addon; } - /* (non-Javadoc) + /** + * (non-Javadoc) + * * @see world.bentobox.bentobox.api.addons.request.AddonRequestHandler#handle(java.util.Map) */ @Override diff --git a/src/main/java/world/bentobox/chunkblock/requests/LocationStatsHandler.java b/src/main/java/world/bentobox/chunkblock/requests/LocationStatsHandler.java index 923cd9a..27261b6 100644 --- a/src/main/java/world/bentobox/chunkblock/requests/LocationStatsHandler.java +++ b/src/main/java/world/bentobox/chunkblock/requests/LocationStatsHandler.java @@ -32,7 +32,9 @@ public LocationStatsHandler(ChunkBlock addon) { this.addon = addon; } - /* (non-Javadoc) + /** + * (non-Javadoc) + * * @see world.bentobox.bentobox.api.addons.request.AddonRequestHandler#handle(java.util.Map) */ @Override diff --git a/src/test/java/world/bentobox/chunkblock/ChunkBlockTest.java b/src/test/java/world/bentobox/chunkblock/ChunkBlockTest.java index fd3649b..eaeb275 100644 --- a/src/test/java/world/bentobox/chunkblock/ChunkBlockTest.java +++ b/src/test/java/world/bentobox/chunkblock/ChunkBlockTest.java @@ -8,6 +8,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -52,7 +53,7 @@ * @author tastybento * */ -public class ChunkBlockTest extends CommonTestSetup { +class ChunkBlockTest extends CommonTestSetup { @Mock private User user; @@ -262,8 +263,8 @@ void testCreateWorlds() { addon.createWorlds(); verify(plugin).log("[chunkblock] Creating ChunkBlock world ..."); // Nether and end are disabled by default in ChunkBlock - verify(plugin, Mockito.never()).log("[chunkblock] Creating ChunkBlock's Nether..."); - verify(plugin, Mockito.never()).log("[chunkblock] Creating ChunkBlock's End World..."); + verify(plugin, never()).log("[chunkblock] Creating ChunkBlock's Nether..."); + verify(plugin, never()).log("[chunkblock] Creating ChunkBlock's End World..."); } diff --git a/src/test/java/world/bentobox/chunkblock/CommonTestSetup.java b/src/test/java/world/bentobox/chunkblock/CommonTestSetup.java index 62d33b4..d50573f 100644 --- a/src/test/java/world/bentobox/chunkblock/CommonTestSetup.java +++ b/src/test/java/world/bentobox/chunkblock/CommonTestSetup.java @@ -224,7 +224,8 @@ public void setUp() throws Exception { //Util mockedUtil = Mockito.mockStatic(Util.class, Mockito.CALLS_REAL_METHODS); - mockedUtil.when(() -> Util.getWorld(any())).thenReturn(mock(World.class)); + World mockWorld = mock(World.class); + mockedUtil.when(() -> Util.getWorld(any())).thenReturn(mockWorld); Util.setPlugin(plugin); // Util diff --git a/src/test/java/world/bentobox/chunkblock/PlaceholdersManagerTest.java b/src/test/java/world/bentobox/chunkblock/PlaceholdersManagerTest.java index 687637c..783f884 100644 --- a/src/test/java/world/bentobox/chunkblock/PlaceholdersManagerTest.java +++ b/src/test/java/world/bentobox/chunkblock/PlaceholdersManagerTest.java @@ -24,7 +24,7 @@ * @author tastybento * */ -public class PlaceholdersManagerTest extends CommonTestSetup { +class PlaceholdersManagerTest extends CommonTestSetup { @Mock private ChunkBlock addon; @Mock diff --git a/src/test/java/world/bentobox/chunkblock/SettingsTest.java b/src/test/java/world/bentobox/chunkblock/SettingsTest.java index ede1583..0a2bfed 100644 --- a/src/test/java/world/bentobox/chunkblock/SettingsTest.java +++ b/src/test/java/world/bentobox/chunkblock/SettingsTest.java @@ -22,7 +22,7 @@ * @author tastybento * */ -public class SettingsTest extends CommonTestSetup { +class SettingsTest extends CommonTestSetup { private Settings s; diff --git a/src/test/java/world/bentobox/chunkblock/chunks/ChunkManagerTest.java b/src/test/java/world/bentobox/chunkblock/chunks/ChunkManagerTest.java index 2a75be2..2629798 100644 --- a/src/test/java/world/bentobox/chunkblock/chunks/ChunkManagerTest.java +++ b/src/test/java/world/bentobox/chunkblock/chunks/ChunkManagerTest.java @@ -52,7 +52,8 @@ void setUp() { level = 0; when(addon.getSettings()).thenReturn(settings); when(addon.getOneBlocksIsland(island)).thenReturn(data); - when(addon.getBlockListener()).thenReturn(mock(BlockListener.class)); + BlockListener blockListener = mock(BlockListener.class); + when(addon.getBlockListener()).thenReturn(blockListener); when(addon.getIslandLevel(island)).thenAnswer(i -> level); // Island center chunk-centered at chunk (0, 0) when(center.getBlockX()).thenReturn(8); diff --git a/src/test/java/world/bentobox/chunkblock/commands/island/IslandSetCountCommandTest.java b/src/test/java/world/bentobox/chunkblock/commands/island/IslandSetCountCommandTest.java index 43e7b60..37b780b 100644 --- a/src/test/java/world/bentobox/chunkblock/commands/island/IslandSetCountCommandTest.java +++ b/src/test/java/world/bentobox/chunkblock/commands/island/IslandSetCountCommandTest.java @@ -46,7 +46,7 @@ * @author tastybento * */ -public class IslandSetCountCommandTest extends CommonTestSetup { +class IslandSetCountCommandTest extends CommonTestSetup { @Mock private CompositeCommand ac; diff --git a/src/test/java/world/bentobox/chunkblock/dataobjects/OneBlockIslandsTest.java b/src/test/java/world/bentobox/chunkblock/dataobjects/OneBlockIslandsTest.java index 3eca026..005f425 100644 --- a/src/test/java/world/bentobox/chunkblock/dataobjects/OneBlockIslandsTest.java +++ b/src/test/java/world/bentobox/chunkblock/dataobjects/OneBlockIslandsTest.java @@ -22,7 +22,7 @@ * @author tastybento * */ -public class OneBlockIslandsTest extends CommonTestSetup { +class OneBlockIslandsTest extends CommonTestSetup { private OneBlockIslands obi; private String id; diff --git a/src/test/java/world/bentobox/chunkblock/listeners/BlockListenerTest.java b/src/test/java/world/bentobox/chunkblock/listeners/BlockListenerTest.java index 2eb6844..b1fe12c 100644 --- a/src/test/java/world/bentobox/chunkblock/listeners/BlockListenerTest.java +++ b/src/test/java/world/bentobox/chunkblock/listeners/BlockListenerTest.java @@ -46,7 +46,7 @@ * @author tastybento * */ -public class BlockListenerTest extends CommonTestSetup { +class BlockListenerTest extends CommonTestSetup { // Class under test private BlockListener bl; diff --git a/src/test/java/world/bentobox/chunkblock/listeners/BlockProtectTest.java b/src/test/java/world/bentobox/chunkblock/listeners/BlockProtectTest.java index 508fcff..8eb00c4 100644 --- a/src/test/java/world/bentobox/chunkblock/listeners/BlockProtectTest.java +++ b/src/test/java/world/bentobox/chunkblock/listeners/BlockProtectTest.java @@ -47,7 +47,7 @@ * @author tastybento * */ -public class BlockProtectTest extends CommonTestSetup { +class BlockProtectTest extends CommonTestSetup { private BlockProtect bp; @Mock diff --git a/src/test/java/world/bentobox/chunkblock/listeners/BossBarListenerTest.java b/src/test/java/world/bentobox/chunkblock/listeners/BossBarListenerTest.java index e520e8c..2113cb2 100644 --- a/src/test/java/world/bentobox/chunkblock/listeners/BossBarListenerTest.java +++ b/src/test/java/world/bentobox/chunkblock/listeners/BossBarListenerTest.java @@ -43,7 +43,7 @@ * config.yml settings turn them off (https://github.com/BentoBoxWorld/ChunkBlock/issues/537). * @author tastybento */ -public class BossBarListenerTest extends CommonTestSetup { +class BossBarListenerTest extends CommonTestSetup { private ChunkBlock addon; private Settings settings; diff --git a/src/test/java/world/bentobox/chunkblock/listeners/CheckPhaseTest.java b/src/test/java/world/bentobox/chunkblock/listeners/CheckPhaseTest.java index b1ba8fc..22de2c8 100644 --- a/src/test/java/world/bentobox/chunkblock/listeners/CheckPhaseTest.java +++ b/src/test/java/world/bentobox/chunkblock/listeners/CheckPhaseTest.java @@ -44,7 +44,7 @@ * @author tastybento * */ -public class CheckPhaseTest extends CommonTestSetup { +class CheckPhaseTest extends CommonTestSetup { @Mock ChunkBlock addon; diff --git a/src/test/java/world/bentobox/chunkblock/listeners/ChunkClaimListenerTest.java b/src/test/java/world/bentobox/chunkblock/listeners/ChunkClaimListenerTest.java index d68b2ef..5641060 100644 --- a/src/test/java/world/bentobox/chunkblock/listeners/ChunkClaimListenerTest.java +++ b/src/test/java/world/bentobox/chunkblock/listeners/ChunkClaimListenerTest.java @@ -348,7 +348,7 @@ void testPreviewIsNotReAnnouncedOnEverySwing() { void testQuitDropsThePendingClaim() { level = 1; listener.onBorderHit(hit(Action.LEFT_CLICK_AIR)); - listener.onQuit(new org.bukkit.event.player.PlayerQuitEvent(mockPlayer, (Component) null)); + listener.onQuit(new org.bukkit.event.player.PlayerQuitEvent(mockPlayer, "")); verify(borderDisplay).clearPreview(uuid); now += 1000; when(mockPlayer.isSneaking()).thenReturn(true); diff --git a/src/test/java/world/bentobox/chunkblock/listeners/HoloListenerTest.java b/src/test/java/world/bentobox/chunkblock/listeners/HoloListenerTest.java index e77ec3d..26edda1 100644 --- a/src/test/java/world/bentobox/chunkblock/listeners/HoloListenerTest.java +++ b/src/test/java/world/bentobox/chunkblock/listeners/HoloListenerTest.java @@ -52,7 +52,7 @@ * @author tastybento * */ -public class HoloListenerTest extends CommonTestSetup { +class HoloListenerTest extends CommonTestSetup { @Mock ChunkBlock addon; private HoloListener hl; diff --git a/src/test/java/world/bentobox/chunkblock/listeners/InfoListenerTest.java b/src/test/java/world/bentobox/chunkblock/listeners/InfoListenerTest.java index 7d77974..0994d5e 100644 --- a/src/test/java/world/bentobox/chunkblock/listeners/InfoListenerTest.java +++ b/src/test/java/world/bentobox/chunkblock/listeners/InfoListenerTest.java @@ -28,7 +28,7 @@ * @author tastybento * */ -public class InfoListenerTest extends CommonTestSetup { +class InfoListenerTest extends CommonTestSetup { @Mock private ChunkBlock addon; @Mock diff --git a/src/test/java/world/bentobox/chunkblock/listeners/JoinLeaveListenerTest.java b/src/test/java/world/bentobox/chunkblock/listeners/JoinLeaveListenerTest.java index 9e22197..a8d688e 100644 --- a/src/test/java/world/bentobox/chunkblock/listeners/JoinLeaveListenerTest.java +++ b/src/test/java/world/bentobox/chunkblock/listeners/JoinLeaveListenerTest.java @@ -23,7 +23,7 @@ /** * @author tastybento */ -public class JoinLeaveListenerTest extends CommonTestSetup { +class JoinLeaveListenerTest extends CommonTestSetup { @Mock private ChunkBlock aob; diff --git a/src/test/java/world/bentobox/chunkblock/listeners/NoBlockHandlerTest.java b/src/test/java/world/bentobox/chunkblock/listeners/NoBlockHandlerTest.java index 29fc9bb..776eff6 100644 --- a/src/test/java/world/bentobox/chunkblock/listeners/NoBlockHandlerTest.java +++ b/src/test/java/world/bentobox/chunkblock/listeners/NoBlockHandlerTest.java @@ -24,7 +24,7 @@ * @author tastybento * */ -public class NoBlockHandlerTest extends CommonTestSetup { +class NoBlockHandlerTest extends CommonTestSetup { private static final UUID ID = UUID.randomUUID(); diff --git a/src/test/java/world/bentobox/chunkblock/listeners/StartSafetyListenerTest.java b/src/test/java/world/bentobox/chunkblock/listeners/StartSafetyListenerTest.java index 432d976..c731e5a 100644 --- a/src/test/java/world/bentobox/chunkblock/listeners/StartSafetyListenerTest.java +++ b/src/test/java/world/bentobox/chunkblock/listeners/StartSafetyListenerTest.java @@ -36,7 +36,7 @@ /** * @author tastybento */ -public class StartSafetyListenerTest extends CommonTestSetup { +class StartSafetyListenerTest extends CommonTestSetup { private StartSafetyListener ssl; @Mock diff --git a/src/test/java/world/bentobox/chunkblock/oneblocks/OneBlocksManagerTest3.java b/src/test/java/world/bentobox/chunkblock/oneblocks/OneBlocksManagerTest3.java index e111804..5997415 100644 --- a/src/test/java/world/bentobox/chunkblock/oneblocks/OneBlocksManagerTest3.java +++ b/src/test/java/world/bentobox/chunkblock/oneblocks/OneBlocksManagerTest3.java @@ -7,6 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -59,7 +60,7 @@ * */ @SuppressWarnings("java:S3577") -public class OneBlocksManagerTest3 extends CommonTestSetup { +class OneBlocksManagerTest3 extends CommonTestSetup { private static File jFile; private static YamlConfiguration oneBlocks; @@ -71,7 +72,7 @@ public class OneBlocksManagerTest3 extends CommonTestSetup { private @NonNull OneBlockIslands obi; @BeforeAll - public static void beforeClass() throws IOException, InvalidConfigurationException { + static void beforeClass() throws IOException, InvalidConfigurationException { // Make the addon jar jFile = new File("addon.jar"); // Dummy oneblocks.yml @@ -131,14 +132,14 @@ public static void beforeClass() throws IOException, InvalidConfigurationExcepti public void setUp() throws Exception { super.setUp(); // Database - AbstractDatabaseHandler h = Mockito.mock(AbstractDatabaseHandler.class); + AbstractDatabaseHandler h = mock(AbstractDatabaseHandler.class); MockedStatic mockDb = Mockito.mockStatic(DatabaseSetup.class); - DatabaseSetup dbSetup = Mockito.mock(DatabaseSetup.class); + DatabaseSetup dbSetup = mock(DatabaseSetup.class); mockDb.when(DatabaseSetup::getDatabase).thenReturn(dbSetup); when(dbSetup.getHandler(Mockito.any())).thenReturn(h); when(h.saveObject(Mockito.any())).thenReturn(CompletableFuture.completedFuture(true)); // Commands manager - CommandsManager cm = Mockito.mock(CommandsManager.class); + CommandsManager cm = mock(CommandsManager.class); when(plugin.getCommandsManager()).thenReturn(cm); // MultiLib - prevent BukkitImpl from checking Paper classloader Mockito.mockStatic(MultiLib.class); @@ -171,7 +172,7 @@ public void tearDown() throws Exception { } @AfterAll - public static void cleanUp() throws Exception { + static void cleanUp() throws Exception { new File("addon.jar").delete(); new File("config.yml").delete(); @@ -819,7 +820,7 @@ public static class StandInItemMeta implements ConfigurationSerializable { public static EnchantmentStorageMeta deserialize(Map args) { lastArgs = args; - EnchantmentStorageMeta meta = Mockito.mock(EnchantmentStorageMeta.class); + EnchantmentStorageMeta meta = mock(EnchantmentStorageMeta.class); // ItemStack hands back a clone of its meta when(meta.clone()).thenReturn(meta); return meta; diff --git a/src/test/java/world/bentobox/chunkblock/oneblocks/customblock/MobDataCustomBlockTest.java b/src/test/java/world/bentobox/chunkblock/oneblocks/customblock/MobDataCustomBlockTest.java index 9446038..f2cde75 100644 --- a/src/test/java/world/bentobox/chunkblock/oneblocks/customblock/MobDataCustomBlockTest.java +++ b/src/test/java/world/bentobox/chunkblock/oneblocks/customblock/MobDataCustomBlockTest.java @@ -4,6 +4,8 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import java.util.LinkedHashMap; import java.util.Map; @@ -67,7 +69,7 @@ void fromMapAllowsMissingUnderlyingBlock() { void fromMapFallsBackToNullWhenUnderlyingBlockInvalid() { // BentoBox.getInstance() is called to log a warning; stub it statically so the // test doesn't require a full CommonTestSetup. - BentoBox mockBentoBox = Mockito.mock(BentoBox.class); + BentoBox mockBentoBox = mock(BentoBox.class); try (MockedStatic mocked = Mockito.mockStatic(BentoBox.class)) { mocked.when(BentoBox::getInstance).thenReturn(mockBentoBox); @@ -80,7 +82,7 @@ void fromMapFallsBackToNullWhenUnderlyingBlockInvalid() { assertTrue(result.isPresent()); assertNull(result.get().getUnderlyingBlock()); // Ensure a warning was emitted - Mockito.verify(mockBentoBox).logWarning(Mockito.contains("NOT_A_REAL_MATERIAL")); + verify(mockBentoBox).logWarning(Mockito.contains("NOT_A_REAL_MATERIAL")); } } diff --git a/src/test/java/world/bentobox/chunkblock/panels/AdminPhasesPanelTest.java b/src/test/java/world/bentobox/chunkblock/panels/AdminPhasesPanelTest.java index 128fffa..cf0a512 100644 --- a/src/test/java/world/bentobox/chunkblock/panels/AdminPhasesPanelTest.java +++ b/src/test/java/world/bentobox/chunkblock/panels/AdminPhasesPanelTest.java @@ -6,6 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -100,7 +101,7 @@ void testPickUpAndDrop() throws IOException { * Dropping at the end position appends the held phase. */ @Test - void testDropAtEnd() throws IOException { + void testDropAtEnd() { panel.pickUp(0); panel.dropAt(2); assertEquals(List.of("Beta", "Gamma", "Alpha"), names()); @@ -219,7 +220,7 @@ void testPersistFailure() { */ @Test void testReloadFailure() throws IOException { - org.mockito.Mockito.doThrow(new IOException("boom")).when(obm).loadPhases(); + doThrow(new IOException("boom")).when(obm).loadPhases(); panel.pickUp(0); panel.dropAt(2); verify(addon).logError("Could not reload phases: boom"); diff --git a/src/test/java/world/bentobox/chunkblock/panels/PhasesPanelTest.java b/src/test/java/world/bentobox/chunkblock/panels/PhasesPanelTest.java index 01e30f1..a4d4354 100644 --- a/src/test/java/world/bentobox/chunkblock/panels/PhasesPanelTest.java +++ b/src/test/java/world/bentobox/chunkblock/panels/PhasesPanelTest.java @@ -108,18 +108,6 @@ private OneBlockPhase createTestPhase(String phaseName) { return phase; } - private OneBlockPhase createTestPhaseWithRequirements(String phaseName) { - OneBlockPhase phase = createTestPhase(phaseName); - List reqs = new ArrayList<>(); - reqs.add(new Requirement(ReqType.ECO, 100.0)); - reqs.add(new Requirement(ReqType.BANK, 50.0)); - reqs.add(new Requirement(ReqType.LEVEL, 10L)); - reqs.add(new Requirement(ReqType.PERMISSION, "permission.test")); - reqs.add(new Requirement(ReqType.COOLDOWN, 60L)); - phase.setRequirements(reqs); - return phase; - } - private NavigableMap createBlockProbs() { NavigableMap probs = new TreeMap<>(); OneBlockPhase phase1 = createTestPhase("Plains"); @@ -1185,7 +1173,7 @@ void testBuildEmptyElementList() throws Exception { * Test openPanel public method with empty phases. */ @Test - void testOpenPanelEmptyPhases() throws Exception { + void testOpenPanelEmptyPhases() { setUpAddonMocks(); User user = User.getInstance(mockPlayer); when(im.getIsland(world, user)).thenReturn(null); From 2d5d3b8f3d8132a92b909ced8a64e0c456f0bc92 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 23 Aug 2026 15:26:39 -0700 Subject: [PATCH 09/10] Intellij static code recs --- README.md | 24 +++++++++---------- .../commands/admin/AdminChunksCommand.java | 8 +++---- .../commands/island/IslandLedgerCommand.java | 2 +- .../commands/island/IslandTitleCommand.java | 6 ++--- .../dataobjects/OneBlockIslands.java | 5 ++-- 5 files changed, 22 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index ec31a11..416a13a 100644 --- a/README.md +++ b/README.md @@ -37,12 +37,12 @@ commands, and permissions. The player command is `/ch` (alias `/chunkblock`), the admin command `/chadmin` (aliases `/chunkblockadmin`, `/cba`). Beyond the standard BentoBox island commands: -| Command | Description | -|---|---| -| `/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`) | +| Command | Description | +|------------------------------------|----------------------------------------------------------------------------------| +| `/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 @@ -79,13 +79,13 @@ The player command is `/ch` (alias `/chunkblock`), the admin command `/chadmin` In addition to the phase placeholders inherited from the magic-block engine (`chunkblock_my_island_phase`, `chunkblock_my_island_count`, ...): -| Placeholder | Value | -|---|---| -| `chunkblock_island_chunks` | Unlocked chunk count | -| `chunkblock_island_max_chunks` | Maximum claimable chunks | -| `chunkblock_island_chunk_credit` | Level credit available to spend | +| Placeholder | Value | +|--------------------------------------|----------------------------------------------------| +| `chunkblock_island_chunks` | Unlocked chunk count | +| `chunkblock_island_max_chunks` | Maximum claimable chunks | +| `chunkblock_island_chunk_credit` | Level credit available to spend | | `chunkblock_island_next_chunk_level` | Total island level needed to afford the next chunk | -| `chunkblock_island_ring` | Ring number of the outermost claimed chunk | +| `chunkblock_island_ring` | Ring number of the outermost claimed chunk | ## For developers 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 4048249..46342c0 100644 --- a/src/main/java/world/bentobox/chunkblock/commands/admin/AdminChunksCommand.java +++ b/src/main/java/world/bentobox/chunkblock/commands/admin/AdminChunksCommand.java @@ -46,9 +46,9 @@ public boolean canExecute(User user, String label, List args) { @Override public boolean execute(User user, String label, List args) { - UUID targetUUID = Util.getUUID(args.get(0)); + UUID targetUUID = Util.getUUID(args.getFirst()); if (targetUUID == null) { - user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0)); + user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.getFirst()); return false; } Island island = getIslands().getIsland(getWorld(), targetUUID); @@ -58,7 +58,7 @@ public boolean execute(User user, String label, List args) { } ChunkManager cm = addon.getChunkManager(); if (args.size() == 1) { - user.sendMessage("chunkblock.commands.admin.chunks.info", TextVariables.NAME, args.get(0), + user.sendMessage("chunkblock.commands.admin.chunks.info", TextVariables.NAME, args.getFirst(), TextVariables.NUMBER, String.valueOf(cm.getUnlockedChunkCount(island)), "[max]", String.valueOf(cm.getMaxChunks(island)), "[spent]", String.valueOf(cm.getSpentLevels(island)), @@ -68,7 +68,7 @@ public boolean execute(User user, String label, List args) { if ("reset".equals(args.get(1).toLowerCase(Locale.ENGLISH))) { addon.getOneBlocksIsland(island).resetUnlockedChunks(); addon.getBlockListener().saveIsland(island); - user.sendMessage("chunkblock.commands.admin.chunks.reset", TextVariables.NAME, args.get(0)); + user.sendMessage("chunkblock.commands.admin.chunks.reset", TextVariables.NAME, args.getFirst()); return true; } showHelp(this, user); diff --git a/src/main/java/world/bentobox/chunkblock/commands/island/IslandLedgerCommand.java b/src/main/java/world/bentobox/chunkblock/commands/island/IslandLedgerCommand.java index 0878ac1..5136952 100644 --- a/src/main/java/world/bentobox/chunkblock/commands/island/IslandLedgerCommand.java +++ b/src/main/java/world/bentobox/chunkblock/commands/island/IslandLedgerCommand.java @@ -99,7 +99,7 @@ private int parseWindow(List args) { return 0; } try { - return Math.max(0, Integer.parseInt(args.get(0))); + return Math.max(0, Integer.parseInt(args.getFirst())); } catch (NumberFormatException e) { return 0; } diff --git a/src/main/java/world/bentobox/chunkblock/commands/island/IslandTitleCommand.java b/src/main/java/world/bentobox/chunkblock/commands/island/IslandTitleCommand.java index 6ac089e..4128680 100644 --- a/src/main/java/world/bentobox/chunkblock/commands/island/IslandTitleCommand.java +++ b/src/main/java/world/bentobox/chunkblock/commands/island/IslandTitleCommand.java @@ -62,11 +62,11 @@ public boolean execute(User user, String label, List args) { if (args.isEmpty()) { return toggleTitle(user, island); } - if ("list".equalsIgnoreCase(args.get(0))) { + if ("list".equalsIgnoreCase(args.getFirst())) { showTitles(user, island); return true; } - String id = args.get(0); + String id = args.getFirst(); if (CLEAR.equalsIgnoreCase(id)) { addon.getTrophyManager().setActiveTitle(island, null); user.sendMessage("chunkblock.commands.title.cleared"); @@ -138,7 +138,7 @@ public Optional> tabComplete(User user, String alias, List options.add(CLEAR); addon.getTrophyManager().getEarned(island).stream().filter(t -> t.title() != null) .map(Trophy::id).forEach(options::add); - String last = args.isEmpty() ? "" : args.get(args.size() - 1); + String last = args.isEmpty() ? "" : args.getLast(); return Optional.of(Util.tabLimit(options, last)); } } diff --git a/src/main/java/world/bentobox/chunkblock/dataobjects/OneBlockIslands.java b/src/main/java/world/bentobox/chunkblock/dataobjects/OneBlockIslands.java index ddb564d..b509b68 100644 --- a/src/main/java/world/bentobox/chunkblock/dataobjects/OneBlockIslands.java +++ b/src/main/java/world/bentobox/chunkblock/dataobjects/OneBlockIslands.java @@ -166,7 +166,7 @@ public int[] removeLastUnlockedChunk() { if (list.size() <= 1) { return null; } - String entry = list.remove(list.size() - 1); + String entry = list.removeLast(); unlockedSet = null; int comma = entry.indexOf(','); return new int[] { Integer.parseInt(entry.substring(0, comma)), @@ -323,8 +323,7 @@ public String getUniqueId() { * (non-Javadoc) * * @see - * world.bentobox.bentobox.database.objects.DataObject#setUniqueId(java.lang. - * String) + * world.bentobox.bentobox.database.objects.DataObject#setUniqueId(java.lang.String) */ @Override public void setUniqueId(String uniqueId) { From 878279774e44f6178701f85d685a7c4582dba043 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 23 Aug 2026 16:30:48 -0700 Subject: [PATCH 10/10] fix: null-guard player location in BorderDisplay.showBorder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sonar S2637 (reliability): player.getLocation() can be null and was passed directly to getIslandAt(), which requires @NonNull. Store the location once, bail out if null — same pattern as HoloListener. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01QWvvUY9ZZJHxUYm1UyjdwK --- .../world/bentobox/chunkblock/chunks/BorderDisplay.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/world/bentobox/chunkblock/chunks/BorderDisplay.java b/src/main/java/world/bentobox/chunkblock/chunks/BorderDisplay.java index 83eec1f..5aead18 100644 --- a/src/main/java/world/bentobox/chunkblock/chunks/BorderDisplay.java +++ b/src/main/java/world/bentobox/chunkblock/chunks/BorderDisplay.java @@ -116,13 +116,16 @@ public void showBorder(Player player) { hideBorder(player); return; } - Optional optionalIsland = addon.getIslands().getIslandAt(player.getLocation()); + Location loc = player.getLocation(); + if (loc == null) { + return; + } + Optional optionalIsland = addon.getIslands().getIslandAt(loc); if (optionalIsland.isEmpty()) { return; } Island island = optionalIsland.get(); ChunkManager cm = addon.getChunkManager(); - Location loc = player.getLocation(); int pcx = loc.getBlockX() >> 4; int pcz = loc.getBlockZ() >> 4; // Check the 3x3 chunk neighborhood; locked chunks with an unlocked neighbor