From 2c4d2334dd865b2a6866b9802e3f03faf65aecb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Kosman?= Date: Sat, 24 May 2025 04:03:53 +0200 Subject: [PATCH] Feat : focus attack average position and some movement camera fixes (#740) ## Description: Makes so that when clicking on the attack warning message in chat, the camera focuses on the "average position" of the attack, instead of just the player. The average position is calculated by taking the average position of all attacking border cells. It makes the calculation for every AttackUpdate, which adds some calculations every tick, but it shouldn't affect performance that much, as it's just a sum of coordinates. If you have a better way of getting the averagePosition information (calculating it only when necessary instead of every tick), it would be great. closes #703 ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced - [x] I understand that submitting code with bugs that could have been caught through manual testing blocks releases and new features for all contributors ## Please put your Discord username so you can be contacted if a bug or regression is found: leo21_ --------- Co-authored-by: Scott Anderson Co-authored-by: evanpelle --- src/client/graphics/TransformHandler.ts | 67 +++++++++++++-------- src/client/graphics/layers/EventsDisplay.ts | 37 ++++++++++-- src/client/graphics/layers/Leaderboard.ts | 7 +++ src/core/GameRunner.ts | 21 +++++++ src/core/execution/AttackExecution.ts | 31 ++++++---- src/core/game/AttackImpl.ts | 52 +++++++++++++++- src/core/game/Game.ts | 7 +++ src/core/game/GameView.ts | 7 +++ src/core/game/PlayerImpl.ts | 41 +++++++------ src/core/worker/Worker.worker.ts | 22 +++++++ src/core/worker/WorkerClient.ts | 36 +++++++++++ src/core/worker/WorkerMessages.ts | 16 +++++ 12 files changed, 279 insertions(+), 65 deletions(-) diff --git a/src/client/graphics/TransformHandler.ts b/src/client/graphics/TransformHandler.ts index 5a7be6c84..475d65eef 100644 --- a/src/client/graphics/TransformHandler.ts +++ b/src/client/graphics/TransformHandler.ts @@ -2,12 +2,21 @@ import { EventBus } from "../../core/EventBus"; import { Cell } from "../../core/game/Game"; import { GameView } from "../../core/game/GameView"; import { CenterCameraEvent, DragEvent, ZoomEvent } from "../InputHandler"; -import { GoToPlayerEvent, GoToUnitEvent } from "./layers/Leaderboard"; +import { + GoToPlayerEvent, + GoToPositionEvent, + GoToUnitEvent, +} from "./layers/Leaderboard"; + +export const GOTO_INTERVAL_MS = 16; +export const CAMERA_MAX_SPEED = 15; +export const CAMERA_SMOOTHING = 0.03; export class TransformHandler { public scale: number = 1.8; private offsetX: number = -350; private offsetY: number = -200; + private lastGoToCallTime: number | null = null; private target: Cell | null; private intervalID: NodeJS.Timeout | null = null; @@ -21,6 +30,7 @@ export class TransformHandler { this.eventBus.on(ZoomEvent, (e) => this.onZoom(e)); this.eventBus.on(DragEvent, (e) => this.onMove(e)); this.eventBus.on(GoToPlayerEvent, (e) => this.onGoToPlayer(e)); + this.eventBus.on(GoToPositionEvent, (e) => this.onGoToPosition(e)); this.eventBus.on(GoToUnitEvent, (e) => this.onGoToUnit(e)); this.eventBus.on(CenterCameraEvent, () => this.centerCamera()); } @@ -146,7 +156,13 @@ export class TransformHandler { event.player.nameLocation().x, event.player.nameLocation().y, ); - this.intervalID = setInterval(() => this.goTo(), 1); + this.intervalID = setInterval(() => this.goTo(), GOTO_INTERVAL_MS); + } + + onGoToPosition(event: GoToPositionEvent) { + this.clearTarget(); + this.target = new Cell(event.x, event.y); + this.intervalID = setInterval(() => this.goTo(), GOTO_INTERVAL_MS); } onGoToUnit(event: GoToUnitEvent) { @@ -155,7 +171,7 @@ export class TransformHandler { this.game.x(event.unit.lastTile()), this.game.y(event.unit.lastTile()), ); - this.intervalID = setInterval(() => this.goTo(), 1); + this.intervalID = setInterval(() => this.goTo(), GOTO_INTERVAL_MS); } centerCamera() { @@ -163,43 +179,42 @@ export class TransformHandler { const player = this.game.myPlayer(); if (!player || !player.nameLocation()) return; this.target = new Cell(player.nameLocation().x, player.nameLocation().y); - this.intervalID = setInterval(() => this.goTo(), 1); + this.intervalID = setInterval(() => this.goTo(), GOTO_INTERVAL_MS); } private goTo() { const { screenX, screenY } = this.screenCenter(); - const screenMapCenter = new Cell(screenX, screenY); if (this.target === null) throw new Error("null target"); if ( - this.game.manhattanDist( - this.game.ref(screenX, screenY), - this.game.ref(this.target.x, this.target.y), - ) < 2 + Math.abs(this.target.x - screenX) + Math.abs(this.target.y - screenY) < + 2 ) { this.clearTarget(); return; } - const dX = Math.abs(screenMapCenter.x - this.target.x); - if (dX > 2) { - const offsetDx = Math.max(1, Math.floor(dX / 25)); - if (screenMapCenter.x > this.target.x) { - this.offsetX -= offsetDx; - } else { - this.offsetX += offsetDx; - } - } - const dY = Math.abs(screenMapCenter.y - this.target.y); - if (dY > 2) { - const offsetDy = Math.max(1, Math.floor(dY / 25)); - if (screenMapCenter.y > this.target.y) { - this.offsetY -= offsetDy; - } else { - this.offsetY += offsetDy; - } + let dt: number; + const now = window.performance.now(); + if (this.lastGoToCallTime === null) { + dt = GOTO_INTERVAL_MS; + } else { + dt = now - this.lastGoToCallTime; } + this.lastGoToCallTime = now; + + const r = 1 - Math.pow(CAMERA_SMOOTHING, dt / 1000); + + this.offsetX += Math.max( + Math.min((this.target.x - screenX) * r, CAMERA_MAX_SPEED), + -CAMERA_MAX_SPEED, + ); + this.offsetY += Math.max( + Math.min((this.target.y - screenY) * r, CAMERA_MAX_SPEED), + -CAMERA_MAX_SPEED, + ); + this.changed = true; } diff --git a/src/client/graphics/layers/EventsDisplay.ts b/src/client/graphics/layers/EventsDisplay.ts index c8452c505..f95f4d84b 100644 --- a/src/client/graphics/layers/EventsDisplay.ts +++ b/src/client/graphics/layers/EventsDisplay.ts @@ -34,7 +34,11 @@ import { Layer } from "./Layer"; import { GameView, PlayerView, UnitView } from "../../../core/game/GameView"; import { onlyImages } from "../../../core/Util"; import { renderTroops } from "../../Utils"; -import { GoToPlayerEvent, GoToUnitEvent } from "./Leaderboard"; +import { + GoToPlayerEvent, + GoToPositionEvent, + GoToUnitEvent, +} from "./Leaderboard"; import { translateText } from "../../Utils"; @@ -393,6 +397,10 @@ export class EventsDisplay extends LitElement implements Layer { this.eventBus.emit(new GoToPlayerEvent(attacker)); } + emitGoToPositionEvent(x: number, y: number) { + this.eventBus.emit(new GoToPositionEvent(x, y)); + } + emitGoToUnitEvent(unit: UnitView) { this.eventBus.emit(new GoToUnitEvent(unit)); } @@ -476,6 +484,26 @@ export class EventsDisplay extends LitElement implements Layer { : event.description; } + private async attackWarningOnClick(attack: AttackUpdate) { + const playerView = this.game.playerBySmallID(attack.attackerID); + if (playerView !== undefined) { + if (playerView instanceof PlayerView) { + const averagePosition = await playerView.attackAveragePosition( + attack.attackerID, + attack.id, + ); + + if (averagePosition === null) { + this.emitGoToPlayerEvent(attack.attackerID); + } else { + this.emitGoToPositionEvent(averagePosition.x, averagePosition.y); + } + } + } else { + this.emitGoToPlayerEvent(attack.attackerID); + } + } + private renderIncomingAttacks() { return html` ${this.incomingAttacks.length > 0 @@ -487,10 +515,7 @@ export class EventsDisplay extends LitElement implements Layer {