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; } }