From 935ff7a970014559b8eedb89d0e1bb6c9f113791 Mon Sep 17 00:00:00 2001 From: Thomas Cruveilher <38007824+Sorikairox@users.noreply.github.com> Date: Sun, 2 Nov 2025 04:54:22 +0900 Subject: [PATCH] Fix: prevent scrolling outside the map (#2360) ## Description: Prevent users from "scrolling"/moving the map outside of viewport and "being lost and unable to find the map back". This can happen by pressing keys intentionally (RIP me) or conflict with browser shortcut containing a WASD key which would keep moving. Related to reports from discord as highlighted by here: https://discord.com/channels/1359946986937258015/1360078040222142564/1432750863994192003 Here is the new behavior. I am actively pressing WASD key to try to "get out" but I can't. Same with mouse clicks. https://www.loom.com/share/a9ecb0a7514d4e54b92d24678833eb2e ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: sorikairo --- src/client/graphics/TransformHandler.ts | 38 +++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/client/graphics/TransformHandler.ts b/src/client/graphics/TransformHandler.ts index a493b64fc..6cf43cd77 100644 --- a/src/client/graphics/TransformHandler.ts +++ b/src/client/graphics/TransformHandler.ts @@ -248,13 +248,51 @@ export class TransformHandler { // Adjust the offset this.offsetX = zoomPointX - (canvasX - this.game.width() / 2) / this.scale; this.offsetY = zoomPointY - (canvasY - this.game.height() / 2) / this.scale; + this.clampOffsets(); this.changed = true; } + private clampOffsets() { + const canvasRect = this.boundingRect(); + const canvasWidth = canvasRect.width; + const canvasHeight = canvasRect.height; + const gameWidth = this.game.width(); + const gameH = this.game.height(); + const scale = this.scale; + + // Allow panning so that up to half of the viewport can be outside the map on each side. + // This lets a map corner be placed at the screen center, but no further. + // Derivation (X axis): + // gameLeftX = -gameWidth/(2*scale) + offsetX + gameWidth/2 >= -vw/2 + // gameRightX = (canvasWidth - gameWidth/2)/scale + offsetX + gameWidth/2 <= gameWidth + vw/2 + // Solving gives: + // minOffsetX = -gameWidth/2 + (gameWidth - canvasWidth) / (2*scale) + // maxOffsetX = gameWidth/2 + (gameWidth - canvasWidth) / (2*scale) + const minOffsetX = -gameWidth / 2 + (gameWidth - canvasWidth) / (2 * scale); + const maxOffsetX = gameWidth / 2 + (gameWidth - canvasWidth) / (2 * scale); + + const minOffsetY = -gameH / 2 + (gameH - canvasHeight) / (2 * scale); + const maxOffsetY = gameH / 2 + (gameH - canvasHeight) / (2 * scale); + + // Clamp offsets within computed bounds on each axis + if (this.offsetX < minOffsetX) { + this.offsetX = minOffsetX; + } else if (this.offsetX > maxOffsetX) { + this.offsetX = maxOffsetX; + } + + if (this.offsetY < minOffsetY) { + this.offsetY = minOffsetY; + } else if (this.offsetY > maxOffsetY) { + this.offsetY = maxOffsetY; + } + } + onMove(event: DragEvent) { this.clearTarget(); this.offsetX -= event.deltaX / this.scale; this.offsetY -= event.deltaY / this.scale; + this.clampOffsets(); this.changed = true; }