From e5771e118ed5171104697ec1ff856bf35d4f12d7 Mon Sep 17 00:00:00 2001 From: tastybento Date: Mon, 24 Aug 2026 07:33:44 -0700 Subject: [PATCH 1/2] feat: pannable chunk map with always-visible scroll arrows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The scrollable map's two snap views (island/player) left players unable to reach parts of a large island's map, and the direction arrow only appeared when a jump target was off-screen — players expected arrows they could click to move around. The control row is now: [◎] ─ ─ ─ [◀] [▲] [•] [▼] [▶] ─ ─ ─ [◇] - ◀ ▲ ▼ ▶ pan the viewport 6 chunks (half a screen) per click and disappear at the map edge - ◎ jumps to the island center, ◇ jumps to the player's position (filled white when the view is already there) - the middle indicator still points at the island center or player when off-screen and jumps there on click - clicking a chunk keeps the panned position - the viewport clamps so the last screen ends on the outermost ring ViewMode is replaced by explicit (viewDx, viewDz) viewport coordinates. Adds 4 pan tooltip locale keys, synced to all 17 translations. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01QWvvUY9ZZJHxUYm1UyjdwK --- .../chunkblock/panels/ChunksDialog.java | 216 +++++++++++------- src/main/resources/locales/cs.yml | 4 + src/main/resources/locales/de.yml | 4 + src/main/resources/locales/en-US.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 + .../chunkblock/panels/ChunksDialogTest.java | 47 ++-- 20 files changed, 231 insertions(+), 104 deletions(-) diff --git a/src/main/java/world/bentobox/chunkblock/panels/ChunksDialog.java b/src/main/java/world/bentobox/chunkblock/panels/ChunksDialog.java index 53954ff..c39829f 100644 --- a/src/main/java/world/bentobox/chunkblock/panels/ChunksDialog.java +++ b/src/main/java/world/bentobox/chunkblock/panels/ChunksDialog.java @@ -30,20 +30,14 @@ /** * The territory map of {@code /ch chunks} drawn as a dialog: one button per chunk, laid * 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. + * row is prepended: pan arrows that slide the viewport half a screen per click, a jump + * button to the island center, a jump button to the player's own position, and an + * indicator in the middle that points at whichever of those two is off-screen. * * @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 @@ -51,6 +45,9 @@ enum ViewMode { */ static final int MAX_RADIUS = 6; + /** How many chunks one pan-arrow click slides the viewport: half a screen. */ + static final int PAN_STEP = MAX_RADIUS; + /** Button size in dialog units. Roughly square once the client adds its own padding. */ private static final int BUTTON_WIDTH = 26; @@ -71,35 +68,35 @@ enum ViewMode { 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); + this(addon, user, island, 0, 0); } - ChunksDialog(ChunkBlock addon, User user, Island island, ViewMode viewMode) { + /** + * @param viewDx requested viewport center, chunks east of the island center + * @param viewDz requested viewport center, chunks south of the island center; both are + * clamped so the viewport never scrolls past the edge of the claimable map + */ + ChunksDialog(ChunkBlock addon, User user, Island island, int viewDx, int viewDz) { this.addon = addon; this.user = user; this.island = island; - this.viewMode = viewMode; ChunkManager cm = addon.getChunkManager(); - this.scrollable = cm.maxRingRadius(island) > MAX_RADIUS; + int maxRing = cm.maxRingRadius(island); + this.scrollable = maxRing > 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; - } + // The viewport center stops radius short of the edge so the last screen ends + // exactly on the outermost ring instead of scrolling into the void + int limit = Math.max(0, maxRing - radius); + this.viewDx = Math.clamp(viewDx, -limit, limit); + this.viewDz = Math.clamp(viewDz, -limit, limit); } else { this.radius = Math.min(MAX_RADIUS, cm.currentRing(island) + 1); this.viewDx = 0; @@ -117,20 +114,21 @@ enum ViewMode { * 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, ViewMode.ISLAND_CENTER, null); + return show(addon, user, island, 0, 0, null); } /** - * @param viewMode which point the viewport is centered on + * @param viewDx viewport center chunk offset east of the island center + * @param viewDz viewport center chunk offset south of the island center * @param selection the chunk description to show above the map, or null for none */ - private static boolean show(ChunkBlock addon, User user, Island island, ViewMode viewMode, + private static boolean show(ChunkBlock addon, User user, Island island, int viewDx, int viewDz, @Nullable Component selection) { if (!Dialogs.isSupported() || !user.isPlayer()) { return false; } try { - new ChunksDialog(addon, user, island, viewMode).open(selection); + new ChunksDialog(addon, user, island, viewDx, viewDz).open(selection); return true; } catch (Exception | LinkageError e) { addon.logError("Could not show the chunks dialog: " + e.getMessage()); @@ -173,20 +171,27 @@ private void open(@Nullable Component selection) { } // ------------------------------------------------------------------ - // Control row (view-mode toggle + directional arrow) + // Control row: [◎] ─ ─ ─ [◀] [▲] [•] [▼] [▶] ─ ─ ─ [◇] // ------------------------------------------------------------------ private List controlRow(int columns) { + int mid = columns / 2; 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")); + row.add(islandJumpButton()); } else if (i == columns - 1) { - row.add(modeButton(ViewMode.PLAYER_CENTER, "◇", "◆", NamedTextColor.AQUA, - REFERENCE + "view-player")); - } else if (i == columns / 2) { - row.add(directionArrowButton()); + row.add(playerJumpButton()); + } else if (i == mid - 2) { + row.add(panButton("◀", -PAN_STEP, 0, "pan-west", canPan(-1, 0))); + } else if (i == mid - 1) { + row.add(panButton("▲", 0, -PAN_STEP, "pan-north", canPan(0, -1))); + } else if (i == mid) { + row.add(indicatorButton()); + } else if (i == mid + 1) { + row.add(panButton("▼", 0, PAN_STEP, "pan-south", canPan(0, 1))); + } else if (i == mid + 2) { + row.add(panButton("▶", PAN_STEP, 0, "pan-east", canPan(1, 0))); } else { row.add(spacerButton()); } @@ -194,14 +199,51 @@ private List controlRow(int columns) { 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)) + /** + * Whether panning one step toward (signX, signZ) would reveal anything: the viewport + * must not already touch that edge of the claimable map. + */ + private boolean canPan(int signX, int signZ) { + int maxRing = addon.getChunkManager().maxRingRadius(island); + if (signX != 0) { + return signX < 0 ? viewDx - radius > -maxRing : viewDx + radius < maxRing; + } + return signZ < 0 ? viewDz - radius > -maxRing : viewDz + radius < maxRing; + } + + private ActionButton panButton(String glyph, int stepX, int stepZ, String tooltipKey, boolean enabled) { + if (!enabled) { + return spacerButton(); + } + return ActionButton.builder(Component.text(glyph, NamedTextColor.WHITE)) + .tooltip(text(REFERENCE + tooltipKey)).width(BUTTON_WIDTH) + .action(DialogAction.customClick((view, audience) -> moveViewport(viewDx + stepX, viewDz + stepZ), + ClickCallback.Options.builder().uses(1).lifetime(CALLBACK_LIFETIME).build())) + .build(); + } + + private ActionButton islandJumpButton() { + boolean active = viewDx == 0 && viewDz == 0; + return jumpButton(active ? "◉" : "◎", active, NamedTextColor.GOLD, REFERENCE + "view-island", 0, 0); + } + + private ActionButton playerJumpButton() { + int[] player = playerChunkOffset(); + if (player == null) { + return spacerButton(); + } + // Compare against where the jump would actually land, which is the clamped spot + ChunksDialog target = new ChunksDialog(addon, user, island, player[0], player[1]); + boolean active = viewDx == target.viewDx && viewDz == target.viewDz; + return jumpButton(active ? "◆" : "◇", active, NamedTextColor.AQUA, REFERENCE + "view-player", player[0], + player[1]); + } + + private ActionButton jumpButton(String glyph, boolean active, NamedTextColor color, String tooltipKey, + int targetDx, int targetDz) { + return ActionButton.builder(Component.text(glyph, active ? NamedTextColor.WHITE : color)) .tooltip(text(tooltipKey)).width(BUTTON_WIDTH) - .action(DialogAction.customClick((view, audience) -> switchMode(mode), + .action(DialogAction.customClick((view, audience) -> moveViewport(targetDx, targetDz), ClickCallback.Options.builder().uses(1).lifetime(CALLBACK_LIFETIME).build())) .build(); } @@ -210,42 +252,32 @@ 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; + /** + * The middle of the control row: an arrow pointing at the island center when it has + * been panned off-screen (clicking jumps home), otherwise at the player when they are + * off-screen (clicking jumps to them), otherwise a plain dot. + */ + private ActionButton indicatorButton() { + if (offScreen(0, 0)) { + return indicatorArrow(-viewDx, -viewDz, NamedTextColor.GOLD, REFERENCE + "arrow-to-island", 0, 0); } - - 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(); + int[] player = playerChunkOffset(); + if (player != null && offScreen(player[0], player[1])) { + return indicatorArrow(player[0] - viewDx, player[1] - viewDz, NamedTextColor.AQUA, + REFERENCE + "arrow-to-player", player[0], player[1]); } + return ActionButton.builder(Component.text("•", NamedTextColor.DARK_GRAY)).width(BUTTON_WIDTH).build(); + } + + private boolean offScreen(int dx, int dz) { + return Math.abs(dx - viewDx) > radius || Math.abs(dz - viewDz) > radius; + } - String arrow = directionGlyph(relDx, relDz); - return ActionButton.builder(Component.text(arrow, arrowColor)) + private ActionButton indicatorArrow(int relDx, int relDz, NamedTextColor color, String tooltipKey, + int targetDx, int targetDz) { + return ActionButton.builder(Component.text(directionGlyph(relDx, relDz), color)) .tooltip(text(tooltipKey)).width(BUTTON_WIDTH) - .action(DialogAction.customClick((view, audience) -> switchMode(targetMode), + .action(DialogAction.customClick((view, audience) -> moveViewport(targetDx, targetDz), ClickCallback.Options.builder().uses(1).lifetime(CALLBACK_LIFETIME).build())) .build(); } @@ -260,6 +292,18 @@ static String directionGlyph(int relDx, int relDz) { return ARROWS[((sector % 8) + 8) % 8]; } + /** + * @return the chunk the player is standing on as an offset from the island center, or + * null when the player is not in the island's world + */ + private int @Nullable [] playerChunkOffset() { + if (island.getWorld() == null || !Util.sameWorld(island.getWorld(), user.getLocation().getWorld())) { + return null; + } + return new int[] { (user.getLocation().getBlockX() >> 4) - (island.getCenter().getBlockX() >> 4), + (user.getLocation().getBlockZ() >> 4) - (island.getCenter().getBlockZ() >> 4) }; + } + // ------------------------------------------------------------------ // Map buttons // ------------------------------------------------------------------ @@ -281,15 +325,19 @@ private ActionButton button(Cell cell) { } /** - * Puts the map back up with the clicked chunk named at the top. Clicking any button - * closes the dialog, so a map that stays put has to be shown again. + * Puts the map back up with the clicked chunk named at the top, keeping the panned + * position. Clicking any button closes the dialog, so a map that stays put has to be + * shown again. */ private void reopen(Component selection) { - Bukkit.getScheduler().runTask(addon.getPlugin(), () -> show(addon, user, island, viewMode, selection)); + Bukkit.getScheduler().runTask(addon.getPlugin(), () -> show(addon, user, island, viewDx, viewDz, selection)); } - private void switchMode(ViewMode mode) { - Bukkit.getScheduler().runTask(addon.getPlugin(), () -> show(addon, user, island, mode, null)); + /** + * Reopens the map with the viewport centered on the given offset (clamped to the map). + */ + private void moveViewport(int newDx, int newDz) { + Bukkit.getScheduler().runTask(addon.getPlugin(), () -> show(addon, user, island, newDx, newDz, null)); } /** @@ -323,14 +371,18 @@ private static String offset(int value) { return value > 0 ? "+" + value : String.valueOf(value); } - private boolean isPlayerOnIsland() { - return island.getWorld() != null && Util.sameWorld(island.getWorld(), user.getLocation().getWorld()); - } - boolean isScrollable() { return scrollable; } + int getViewDx() { + return viewDx; + } + + int getViewDz() { + return viewDz; + } + /** * 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/cs.yml b/src/main/resources/locales/cs.yml index 3446f6b..92eb2e8 100644 --- a/src/main/resources/locales/cs.yml +++ b/src/main/resources/locales/cs.yml @@ -74,6 +74,10 @@ chunkblock: close: "Zavřít" view-island: "Vycentrovat na ostrov" view-player: "Vycentrovat na vaši pozici" + pan-north: "Posunout mapu na sever" + pan-south: "Posunout mapu na jih" + pan-east: "Posunout mapu na východ" + pan-west: "Posunout mapu na západ" arrow-to-island: "Střed vašeho ostrova je tímto směrem" arrow-to-player: "Jste tímto směrem" tooltip: diff --git a/src/main/resources/locales/de.yml b/src/main/resources/locales/de.yml index 0899a06..bf06082 100644 --- a/src/main/resources/locales/de.yml +++ b/src/main/resources/locales/de.yml @@ -74,6 +74,10 @@ chunkblock: close: "Schließen" view-island: "Auf Insel zentrieren" view-player: "Auf deine Position zentrieren" + pan-north: "Karte nach Norden verschieben" + pan-south: "Karte nach Süden verschieben" + pan-east: "Karte nach Osten verschieben" + pan-west: "Karte nach Westen verschieben" arrow-to-island: "Dein Inselzentrum ist in dieser Richtung" arrow-to-player: "Du bist in dieser Richtung" tooltip: diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index eea9612..31473b4 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -68,6 +68,10 @@ chunkblock: close: "Close" view-island: "Center on your island" view-player: "Center on your position" + pan-north: "Scroll the map north" + pan-south: "Scroll the map south" + pan-east: "Scroll the map east" + pan-west: "Scroll the map west" arrow-to-island: "Your island center is this way" arrow-to-player: "You are this way" tooltip: diff --git a/src/main/resources/locales/es.yml b/src/main/resources/locales/es.yml index fc41aa7..c34daa5 100644 --- a/src/main/resources/locales/es.yml +++ b/src/main/resources/locales/es.yml @@ -74,6 +74,10 @@ chunkblock: close: "Cerrar" view-island: "Centrar en tu isla" view-player: "Centrar en tu posición" + pan-north: "Desplazar el mapa al norte" + pan-south: "Desplazar el mapa al sur" + pan-east: "Desplazar el mapa al este" + pan-west: "Desplazar el mapa al oeste" arrow-to-island: "El centro de tu isla está en esta dirección" arrow-to-player: "Estás en esta dirección" tooltip: diff --git a/src/main/resources/locales/fr.yml b/src/main/resources/locales/fr.yml index 91dc63a..bce0d1f 100644 --- a/src/main/resources/locales/fr.yml +++ b/src/main/resources/locales/fr.yml @@ -74,6 +74,10 @@ chunkblock: close: "Fermer" view-island: "Centrer sur votre île" view-player: "Centrer sur votre position" + pan-north: "Faire défiler la carte vers le nord" + pan-south: "Faire défiler la carte vers le sud" + pan-east: "Faire défiler la carte vers l'est" + pan-west: "Faire défiler la carte vers l'ouest" arrow-to-island: "Le centre de votre île est par ici" arrow-to-player: "Vous êtes par ici" tooltip: diff --git a/src/main/resources/locales/hr.yml b/src/main/resources/locales/hr.yml index 555afac..d18019c 100644 --- a/src/main/resources/locales/hr.yml +++ b/src/main/resources/locales/hr.yml @@ -74,6 +74,10 @@ chunkblock: close: "Zatvori" view-island: "Centriraj na otok" view-player: "Centriraj na tvoju poziciju" + pan-north: "Pomakni kartu na sjever" + pan-south: "Pomakni kartu na jug" + pan-east: "Pomakni kartu na istok" + pan-west: "Pomakni kartu na zapad" arrow-to-island: "Centar tvog otoka je u ovom smjeru" arrow-to-player: "Ti si u ovom smjeru" tooltip: diff --git a/src/main/resources/locales/hu.yml b/src/main/resources/locales/hu.yml index aad31a9..5f9d614 100644 --- a/src/main/resources/locales/hu.yml +++ b/src/main/resources/locales/hu.yml @@ -75,6 +75,10 @@ chunkblock: close: "Bezárás" view-island: "Középre a szigetedre" view-player: "Középre a pozíciódra" + pan-north: "Térkép görgetése északra" + pan-south: "Térkép görgetése délre" + pan-east: "Térkép görgetése keletre" + pan-west: "Térkép görgetése nyugatra" arrow-to-island: "A szigeted közepe erre van" arrow-to-player: "Te erre vagy" tooltip: diff --git a/src/main/resources/locales/id.yml b/src/main/resources/locales/id.yml index c3543ed..d6f4da4 100644 --- a/src/main/resources/locales/id.yml +++ b/src/main/resources/locales/id.yml @@ -74,6 +74,10 @@ chunkblock: close: "Tutup" view-island: "Pusatkan ke pulau" view-player: "Pusatkan ke posisi Anda" + pan-north: "Geser peta ke utara" + pan-south: "Geser peta ke selatan" + pan-east: "Geser peta ke timur" + pan-west: "Geser peta ke barat" arrow-to-island: "Pusat pulau Anda ada di arah ini" arrow-to-player: "Anda ada di arah ini" tooltip: diff --git a/src/main/resources/locales/it.yml b/src/main/resources/locales/it.yml index aa38b37..08ad4bf 100644 --- a/src/main/resources/locales/it.yml +++ b/src/main/resources/locales/it.yml @@ -74,6 +74,10 @@ chunkblock: close: "Chiudi" view-island: "Centra sulla tua isola" view-player: "Centra sulla tua posizione" + pan-north: "Scorri la mappa a nord" + pan-south: "Scorri la mappa a sud" + pan-east: "Scorri la mappa a est" + pan-west: "Scorri la mappa a ovest" arrow-to-island: "Il centro della tua isola è in questa direzione" arrow-to-player: "Ti trovi in questa direzione" tooltip: diff --git a/src/main/resources/locales/ja.yml b/src/main/resources/locales/ja.yml index b933bea..2f59abe 100644 --- a/src/main/resources/locales/ja.yml +++ b/src/main/resources/locales/ja.yml @@ -71,6 +71,10 @@ chunkblock: close: "閉じる" view-island: "島の中心に表示" view-player: "現在地に表示" + pan-north: "マップを北へスクロール" + pan-south: "マップを南へスクロール" + pan-east: "マップを東へスクロール" + pan-west: "マップを西へスクロール" arrow-to-island: "島の中心はこの方向です" arrow-to-player: "あなたはこの方向にいます" tooltip: diff --git a/src/main/resources/locales/pl.yml b/src/main/resources/locales/pl.yml index c74a818..d667720 100644 --- a/src/main/resources/locales/pl.yml +++ b/src/main/resources/locales/pl.yml @@ -71,6 +71,10 @@ chunkblock: close: "Zamknij" view-island: "Wyśrodkuj na wyspę" view-player: "Wyśrodkuj na twoją pozycję" + pan-north: "Przewiń mapę na północ" + pan-south: "Przewiń mapę na południe" + pan-east: "Przewiń mapę na wschód" + pan-west: "Przewiń mapę na zachód" arrow-to-island: "Centrum twojej wyspy jest w tym kierunku" arrow-to-player: "Jesteś w tym kierunku" tooltip: diff --git a/src/main/resources/locales/pt.yml b/src/main/resources/locales/pt.yml index 7616ee5..13fa156 100644 --- a/src/main/resources/locales/pt.yml +++ b/src/main/resources/locales/pt.yml @@ -71,6 +71,10 @@ chunkblock: close: "Fechar" view-island: "Centralizar na ilha" view-player: "Centralizar na sua posição" + pan-north: "Rolar o mapa para o norte" + pan-south: "Rolar o mapa para o sul" + pan-east: "Rolar o mapa para o leste" + pan-west: "Rolar o mapa para o oeste" arrow-to-island: "O centro da sua ilha está nesta direção" arrow-to-player: "Você está nesta direção" tooltip: diff --git a/src/main/resources/locales/ru.yml b/src/main/resources/locales/ru.yml index c215fff..354b378 100644 --- a/src/main/resources/locales/ru.yml +++ b/src/main/resources/locales/ru.yml @@ -73,6 +73,10 @@ chunkblock: close: "Закрыть" view-island: "Центрировать на острове" view-player: "Центрировать на вашей позиции" + pan-north: "Прокрутить карту на север" + pan-south: "Прокрутить карту на юг" + pan-east: "Прокрутить карту на восток" + pan-west: "Прокрутить карту на запад" arrow-to-island: "Центр вашего острова в этом направлении" arrow-to-player: "Вы находитесь в этом направлении" tooltip: diff --git a/src/main/resources/locales/tr.yml b/src/main/resources/locales/tr.yml index 691fddd..e3ba4b9 100644 --- a/src/main/resources/locales/tr.yml +++ b/src/main/resources/locales/tr.yml @@ -74,6 +74,10 @@ chunkblock: close: "Kapat" view-island: "Adanıza ortala" view-player: "Konumunuza ortala" + pan-north: "Haritayı kuzeye kaydır" + pan-south: "Haritayı güneye kaydır" + pan-east: "Haritayı doğuya kaydır" + pan-west: "Haritayı batıya kaydır" arrow-to-island: "Ada merkeziniz bu yönde" arrow-to-player: "Bu yöndesiniz" tooltip: diff --git a/src/main/resources/locales/uk.yml b/src/main/resources/locales/uk.yml index a90d6f6..4d6f26e 100644 --- a/src/main/resources/locales/uk.yml +++ b/src/main/resources/locales/uk.yml @@ -74,6 +74,10 @@ chunkblock: close: "Закрити" view-island: "Центрувати на острові" view-player: "Центрувати на вашій позиції" + pan-north: "Прокрутити карту на північ" + pan-south: "Прокрутити карту на південь" + pan-east: "Прокрутити карту на схід" + pan-west: "Прокрутити карту на захід" arrow-to-island: "Центр вашого острова в цьому напрямку" arrow-to-player: "Ви знаходитесь у цьому напрямку" tooltip: diff --git a/src/main/resources/locales/vi.yml b/src/main/resources/locales/vi.yml index 410d620..9ee0be9 100644 --- a/src/main/resources/locales/vi.yml +++ b/src/main/resources/locales/vi.yml @@ -74,6 +74,10 @@ chunkblock: close: "Đóng" view-island: "Căn giữa theo đảo" view-player: "Căn giữa theo vị trí của bạn" + pan-north: "Cuộn bản đồ về phía bắc" + pan-south: "Cuộn bản đồ về phía nam" + pan-east: "Cuộn bản đồ về phía đông" + pan-west: "Cuộn bản đồ về phía tây" 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: diff --git a/src/main/resources/locales/zh-CN.yml b/src/main/resources/locales/zh-CN.yml index 174990a..1a1732f 100644 --- a/src/main/resources/locales/zh-CN.yml +++ b/src/main/resources/locales/zh-CN.yml @@ -73,6 +73,10 @@ chunkblock: close: "关闭" view-island: "以岛屿为中心" view-player: "以你的位置为中心" + pan-north: "向北滚动地图" + pan-south: "向南滚动地图" + pan-east: "向东滚动地图" + pan-west: "向西滚动地图" arrow-to-island: "你的岛屿中心在这个方向" arrow-to-player: "你在这个方向" tooltip: diff --git a/src/main/resources/locales/zh-TW.yml b/src/main/resources/locales/zh-TW.yml index 8905b7b..b1f4139 100644 --- a/src/main/resources/locales/zh-TW.yml +++ b/src/main/resources/locales/zh-TW.yml @@ -222,6 +222,10 @@ chunkblock: close: '關閉' view-island: "以島嶼為中心" view-player: "以你的位置為中心" + pan-north: "向北捲動地圖" + pan-south: "向南捲動地圖" + pan-east: "向東捲動地圖" + pan-west: "向西捲動地圖" arrow-to-island: "你的島嶼中心在這個方向" arrow-to-player: "你在這個方向" tooltip: diff --git a/src/test/java/world/bentobox/chunkblock/panels/ChunksDialogTest.java b/src/test/java/world/bentobox/chunkblock/panels/ChunksDialogTest.java index 98a34eb..ecf91ed 100644 --- a/src/test/java/world/bentobox/chunkblock/panels/ChunksDialogTest.java +++ b/src/test/java/world/bentobox/chunkblock/panels/ChunksDialogTest.java @@ -88,24 +88,31 @@ void testIsNotScrollableWhenTerritoryFitsViewport() { } @Test - void testPlayerCenteredViewportShiftsMap() { + void testPannedViewportShiftsMap() { 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); + // Pan the viewport 8 chunks east: visible range is dx 2..14, island center off-screen + ChunksDialog dialog = new ChunksDialog(addon, user, island, 8, 0); 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); + assertFalse(dialog.cells().stream().anyMatch(c -> c.dx() == 0 && c.dz() == 0)); + assertTrue(dialog.cells().stream().anyMatch(c -> c.dx() == 14 && c.dz() == 0)); + } + + @Test + void testViewportClampsAtMapEdge() { + when(island.getProtectionRange()).thenReturn(240); + // maxRingRadius = (240-8)/16 = 14; clamp limit = 14 - 6 = 8 + ChunksDialog dialog = new ChunksDialog(addon, user, island, 100, -100); + assertEquals(8, dialog.getViewDx()); + assertEquals(-8, dialog.getViewDz()); + } + + @Test + void testNonScrollableIgnoresRequestedViewport() { + when(island.getProtectionRange()).thenReturn(100); + ChunksDialog dialog = new ChunksDialog(addon, user, island, 5, 5); + assertEquals(0, dialog.getViewDx()); + assertEquals(0, dialog.getViewDz()); } @Test @@ -143,13 +150,9 @@ void testMapIsCappedAtTheWidestGridTheDialogHolds() { } @Test - void testPlayerCenteredFallsBackToIslandCenterWhenNotOnIsland() { + void testDefaultViewportShowsIslandCenter() { 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); + ChunksDialog dialog = new ChunksDialog(addon, user, island); + assertTrue(dialog.cells().stream().anyMatch(c -> c.dx() == 0 && c.dz() == 0)); } } From 4c13eaf9abb56d87ac833d311a303eec05871926 Mon Sep 17 00:00:00 2001 From: tastybento Date: Mon, 24 Aug 2026 07:35:40 -0700 Subject: [PATCH 2/2] chore: bump version to 1.4.1 1.4.0 has been released; the pannable map targets 1.4.1. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01QWvvUY9ZZJHxUYm1UyjdwK --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0192532..c20c7f7 100644 --- a/pom.xml +++ b/pom.xml @@ -67,7 +67,7 @@ -LOCAL - 1.4.0 + 1.4.1 BentoBoxWorld_ChunkBlock bentobox-world