diff --git a/src/client/GameRunner.ts b/src/client/GameRunner.ts index b1f118c1d..5d77f2a75 100644 --- a/src/client/GameRunner.ts +++ b/src/client/GameRunner.ts @@ -188,7 +188,7 @@ export class GameRunner { } catch (error) { const errorText = `Error: ${error.message}\nStack: ${error.stack}`; consolex.error(errorText) - alert(`Game crashed! client id: ${this.clientID}\n Please paste the following your bug report in Discord:\n` + errorText); + alert(`Game crashed! client id: ${this.clientID}\n Please paste the following in your bug report in Discord:\n` + errorText); } this.renderer.tick() this.currTurn++ diff --git a/src/client/LocalServer.ts b/src/client/LocalServer.ts index 56ee39bfa..cb5527088 100644 --- a/src/client/LocalServer.ts +++ b/src/client/LocalServer.ts @@ -1,19 +1,21 @@ -import { Config, ServerConfig } from "../core/configuration/Config"; +import { Config, GameEnv, ServerConfig } from "../core/configuration/Config"; import { consolex } from "../core/Consolex"; +import { GameEvent } from "../core/EventBus"; import { ClientID, ClientMessage, ClientMessageSchema, GameConfig, GameID, GameRecordSchema, Intent, PlayerRecord, ServerMessage, ServerStartGameMessageSchema, ServerTurnMessageSchema, Turn } from "../core/Schemas"; import { CreateGameRecord, generateID } from "../core/Util"; import { LobbyConfig } from "./GameRunner"; import { getPersistentIDFromCookie } from "./Main"; + export class LocalServer { - - private turns: Turn[] = [] private intents: Intent[] = [] private startedAt: number private endTurnIntervalID + private paused = false + constructor( private serverConfig: ServerConfig, @@ -35,14 +37,33 @@ export class LocalServer { })) } + pause() { + this.paused = true + } + + resume() { + this.paused = false + } + onMessage(message: string) { const clientMsg: ClientMessage = ClientMessageSchema.parse(JSON.parse(message)) if (clientMsg.type == "intent") { + if (this.paused) { + if (clientMsg.intent.type == "troop_ratio") { + // Store troop change events because otherwise they are + // not registered when game is paused. + this.intents.push(clientMsg.intent) + } + return + } this.intents.push(clientMsg.intent) } } private endTurn() { + if (this.paused) { + return + } const pastTurn: Turn = { turnNumber: this.turns.length, gameID: this.lobbyConfig.gameID, diff --git a/src/client/Transport.ts b/src/client/Transport.ts index 11c8eafcf..b8990f68a 100644 --- a/src/client/Transport.ts +++ b/src/client/Transport.ts @@ -6,6 +6,9 @@ import { ClientID, ClientIntentMessageSchema, ClientJoinMessageSchema, GameID, I import { LobbyConfig } from "./GameRunner" import { LocalServer } from "./LocalServer" +export class PauseGameEvent implements GameEvent { + constructor(public readonly paused: boolean) { } +} export class SendAllianceRequestIntentEvent implements GameEvent { constructor( @@ -121,6 +124,7 @@ export class Transport { this.eventBus.on(BuildUnitIntentEvent, (e) => this.onBuildUnitIntent(e)) this.eventBus.on(SendLogEvent, (e) => this.onSendLogEvent(e)) + this.eventBus.on(PauseGameEvent, (e) => this.onPauseGameEvent(e)) } private startPing() { @@ -352,6 +356,18 @@ export class Transport { }) } + private onPauseGameEvent(event: PauseGameEvent) { + if (!this.isLocal) { + console.log(`cannot pause multiplayer games`) + return + } + if (event.paused) { + this.localServer.pause() + } else { + this.localServer.resume() + } + } + private sendIntent(intent: Intent) { if (this.isLocal || this.socket.readyState === WebSocket.OPEN) { const msg = ClientIntentMessageSchema.parse({ diff --git a/src/client/graphics/layers/PlayerInfoOverlay.ts b/src/client/graphics/layers/PlayerInfoOverlay.ts index 1f9c6a54e..d401d4b94 100644 --- a/src/client/graphics/layers/PlayerInfoOverlay.ts +++ b/src/client/graphics/layers/PlayerInfoOverlay.ts @@ -1,13 +1,14 @@ import { LitElement, html, css } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { Layer } from './Layer'; -import { Game, Player, Unit, UnitType } from '../../../core/game/Game'; +import { Game, GameType, Player, Unit, UnitType } from '../../../core/game/Game'; import { ClientID } from '../../../core/Schemas'; import { EventBus } from '../../../core/EventBus'; import { TransformHandler } from '../TransformHandler'; import { MouseMoveEvent } from '../../InputHandler'; import { euclideanDist, distSortUnit } from '../../../core/Util'; import { renderNumber, renderTroops } from '../../Utils'; +import { PauseGameEvent } from '../../Transport'; @customElement('player-info-overlay') export class PlayerInfoOverlay extends LitElement implements Layer { @@ -30,11 +31,21 @@ export class PlayerInfoOverlay extends LitElement implements Layer { private unit: Unit | null = null; @state() - private _isVisible: boolean = false; + private showPauseButton: boolean = true + + @state() + private _isInfoVisible: boolean = false; + + @state() + private _isPaused: boolean = false; + + private _isActive = false init(game: Game) { this.game = game; this.eventBus.on(MouseMoveEvent, (e: MouseMoveEvent) => this.onMouseEvent(e)); + this._isActive = true + this.showPauseButton = this.game.config().gameConfig().gameType == GameType.Singleplayer } private onMouseEvent(event: MouseMoveEvent) { @@ -45,7 +56,6 @@ export class PlayerInfoOverlay extends LitElement implements Layer { const worldCoord = this.transform.screenToWorldCoordinates(event.x, event.y); if (!this.game.isOnMap(worldCoord)) { return; - return; } const tile = this.game.tile(worldCoord); @@ -70,9 +80,13 @@ export class PlayerInfoOverlay extends LitElement implements Layer { window.location.reload(); } + private onPauseButtonClick() { + this._isPaused = !this._isPaused; + this.eventBus.emit(new PauseGameEvent(this._isPaused)); + } + tick() { - this.requestUpdate() - // Implementation for Layer interface + this.requestUpdate(); } renderLayer(context: CanvasRenderingContext2D) { @@ -84,7 +98,7 @@ export class PlayerInfoOverlay extends LitElement implements Layer { } setVisible(visible: boolean) { - this._isVisible = visible; + this._isInfoVisible = visible; this.requestUpdate(); } @@ -109,25 +123,31 @@ export class PlayerInfoOverlay extends LitElement implements Layer { private renderUnitInfo(unit: Unit) { const isAlly = (unit.owner() == this.myPlayer() || this.myPlayer()?.isAlliedWith(unit.owner())) ?? false; return html` -