From d0bb3a016e235b22328bc91c4cc9cfe6dff4eb97 Mon Sep 17 00:00:00 2001 From: FloPinguin <25036848+FloPinguin@users.noreply.github.com> Date: Mon, 16 Feb 2026 20:08:11 +0100 Subject: [PATCH] =?UTF-8?q?"Catching=20up..."=20HeadsUpMessage=20?= =?UTF-8?q?=F0=9F=8F=83=E2=80=8D=E2=99=80=EF=B8=8F=20(#3194)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description: After an internet problem or page reload the game catches up, replaying the ticks. But especially new players might be confused what is happening. The game runs fast??? And you can't easily tell when its finished catching up. You need to spot when it stops running faster than usual. So add a HeadsUpMessage to tell people what is happening. https://github.com/user-attachments/assets/6fcdd85f-c58e-4549-89d0-5ba51df39339 ## 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: FloPinguin --------- Co-authored-by: iamlewis --- resources/lang/en.json | 3 ++- src/client/graphics/layers/HeadsUpMessage.ts | 26 +++++++++++++++++++- src/core/GameRunner.ts | 3 ++- src/core/game/GameUpdates.ts | 1 + src/core/game/GameView.ts | 4 +++ src/core/worker/Worker.worker.ts | 5 ++-- 6 files changed, 37 insertions(+), 5 deletions(-) diff --git a/resources/lang/en.json b/resources/lang/en.json index 5947ad4e2..55b302322 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -849,7 +849,8 @@ "random_spawn": "Random spawn is enabled. Selecting starting location for you...", "singleplayer_game_paused": "Game paused", "multiplayer_game_paused": "Game paused by Lobby Creator", - "pvp_immunity_active": "PVP immunity active for {seconds}s" + "pvp_immunity_active": "PVP immunity active for {seconds}s", + "catching_up": "Catching up..." }, "territory_patterns": { "title": "Skins", diff --git a/src/client/graphics/layers/HeadsUpMessage.ts b/src/client/graphics/layers/HeadsUpMessage.ts index fbe2cb1e4..5a66733a7 100644 --- a/src/client/graphics/layers/HeadsUpMessage.ts +++ b/src/client/graphics/layers/HeadsUpMessage.ts @@ -19,6 +19,12 @@ export class HeadsUpMessage extends LitElement implements Layer { @state() private isImmunityActive = false; + @state() + private isCatchingUp = false; + private catchingUpTicks = 0; + + private static readonly CATCHING_UP_SHOW_THRESHOLD = 10; + @state() private toastMessage: string | import("lit").TemplateResult | null = null; @state() @@ -92,12 +98,30 @@ export class HeadsUpMessage extends LitElement implements Layer { this.game.isSpawnImmunityActive() && ticksSinceSpawnEnd < showImmunityHudDuration; + const currentlyCatchingUp = + !this.game.config().isReplay() && this.game.isCatchingUp(); + + if (currentlyCatchingUp) { + this.catchingUpTicks++; + } else { + this.catchingUpTicks = 0; + } + + this.isCatchingUp = + this.catchingUpTicks >= HeadsUpMessage.CATCHING_UP_SHOW_THRESHOLD; + this.isVisible = - this.game.inSpawnPhase() || this.isPaused || this.isImmunityActive; + this.game.inSpawnPhase() || + this.isPaused || + this.isImmunityActive || + this.isCatchingUp; this.requestUpdate(); } private getMessage(): string { + if (this.isCatchingUp) { + return translateText("heads_up_message.catching_up"); + } if (this.isPaused) { if (this.game.config().gameConfig().gameType === GameType.Singleplayer) { return translateText("heads_up_message.singleplayer_game_paused"); diff --git a/src/core/GameRunner.ts b/src/core/GameRunner.ts index f0453073c..2179d2df5 100644 --- a/src/core/GameRunner.ts +++ b/src/core/GameRunner.ts @@ -118,7 +118,7 @@ export class GameRunner { this.turns.push(turn); } - public executeNextTick(): boolean { + public executeNextTick(pendingTurns?: number): boolean { if (this.isExecuting) { return false; } @@ -182,6 +182,7 @@ export class GameRunner { updates: updates, playerNameViewData: this.playerViewData, tickExecutionDuration: tickExecutionDuration, + pendingTurns: pendingTurns ?? 0, }); this.isExecuting = false; return true; diff --git a/src/core/game/GameUpdates.ts b/src/core/game/GameUpdates.ts index b8786e14b..f5e125c3b 100644 --- a/src/core/game/GameUpdates.ts +++ b/src/core/game/GameUpdates.ts @@ -20,6 +20,7 @@ export interface GameUpdateViewData { packedTileUpdates: BigUint64Array; playerNameViewData: Record; tickExecutionDuration?: number; + pendingTurns?: number; } export interface ErrorUpdate { diff --git a/src/core/game/GameView.ts b/src/core/game/GameView.ts index 07d2841e8..44b34dfd0 100644 --- a/src/core/game/GameView.ts +++ b/src/core/game/GameView.ts @@ -637,6 +637,10 @@ export class GameView implements GameMap { return this.lastUpdate?.updates ?? null; } + public isCatchingUp(): boolean { + return (this.lastUpdate?.pendingTurns ?? 0) > 1; + } + public update(gu: GameUpdateViewData) { this.toDelete.forEach((id) => this._units.delete(id)); this.toDelete.clear(); diff --git a/src/core/worker/Worker.worker.ts b/src/core/worker/Worker.worker.ts index 2aa52864f..4b26f6783 100644 --- a/src/core/worker/Worker.worker.ts +++ b/src/core/worker/Worker.worker.ts @@ -42,9 +42,10 @@ ctx.addEventListener("message", async (e: MessageEvent) => { if (!gr) { break; } - const ticksToRun = Math.min(gr.pendingTurns(), MAX_TICKS_PER_HEARTBEAT); + const pendingTurns = gr.pendingTurns(); + const ticksToRun = Math.min(pendingTurns, MAX_TICKS_PER_HEARTBEAT); for (let i = 0; i < ticksToRun; i++) { - if (!gr.executeNextTick()) { + if (!gr.executeNextTick(gr.pendingTurns())) { break; } }