diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index df11c56c8..d3a63e0cb 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -13,7 +13,7 @@ import { import { createGameRecord } from "../core/Util"; import { ServerConfig } from "../core/configuration/Config"; import { getConfig } from "../core/configuration/ConfigLoader"; -import { Cell, UnitType } from "../core/game/Game"; +import { UnitType } from "../core/game/Game"; import { TileRef } from "../core/game/GameMap"; import { ErrorUpdate, @@ -390,22 +390,15 @@ export class ClientGameRunner { this.gameView.isLand(tile) ) { this.myPlayer - .bestTransportShipSpawn(this.gameView.ref(cell.x, cell.y)) + .bestTransportShipSpawn(tile) .then((spawn: number | false) => { if (this.myPlayer === null) throw new Error("not initialized"); - let spawnCell: Cell | null = null; - if (spawn !== false) { - spawnCell = new Cell( - this.gameView.x(spawn), - this.gameView.y(spawn), - ); - } this.eventBus.emit( new SendBoatAttackIntentEvent( this.gameView.owner(tile).id(), - cell, + tile, this.myPlayer.troops() * this.renderer.uiState.attackRatio, - spawnCell, + spawn !== false ? spawn : null, ), ); }); diff --git a/src/client/Transport.ts b/src/client/Transport.ts index 0cc38ff70..3ee134ffc 100644 --- a/src/client/Transport.ts +++ b/src/client/Transport.ts @@ -10,6 +10,7 @@ import { Tick, UnitType, } from "../core/game/Game"; +import { TileRef } from "../core/game/GameMap"; import { PlayerView } from "../core/game/GameView"; import { AllPlayersStats, @@ -69,9 +70,9 @@ export class SendAttackIntentEvent implements GameEvent { export class SendBoatAttackIntentEvent implements GameEvent { constructor( public readonly targetID: PlayerID | null, - public readonly dst: Cell, + public readonly dst: TileRef, public readonly troops: number, - public readonly src: Cell | null = null, + public readonly src: TileRef | null = null, ) {} } @@ -432,10 +433,8 @@ export class Transport { clientID: this.lobbyConfig.clientID, targetID: event.targetID, troops: event.troops, - dstX: event.dst.x, - dstY: event.dst.y, - srcX: event.src?.x ?? null, - srcY: event.src?.y ?? null, + dst: event.dst, + src: event.src, }); } diff --git a/src/client/graphics/layers/RadialMenu.ts b/src/client/graphics/layers/RadialMenu.ts index db79c507e..d07dfe699 100644 --- a/src/client/graphics/layers/RadialMenu.ts +++ b/src/client/graphics/layers/RadialMenu.ts @@ -287,6 +287,9 @@ export class RadialMenu implements Layer { if (!this.isVisible || this.clickedCell === null) return; const myPlayer = this.g.myPlayer(); if (myPlayer === null || !myPlayer.isAlive()) return; + if (!this.g.isValidCoord(this.clickedCell.x, this.clickedCell.y)) { + return; + } const tile = this.g.ref(this.clickedCell.x, this.clickedCell.y); if (this.originalTileOwner.isPlayer()) { if (this.g.owner(tile) !== this.originalTileOwner) { @@ -399,18 +402,12 @@ export class RadialMenu implements Layer { // BestTransportShipSpawn is an expensive operation, so // we calculate it here and send the spawn tile to other clients. myPlayer.bestTransportShipSpawn(tile).then((spawn) => { - let spawnTile: Cell | null = null; - if (spawn !== false) { - spawnTile = new Cell(this.g.x(spawn), this.g.y(spawn)); - } - - if (this.clickedCell === null) return; this.eventBus.emit( new SendBoatAttackIntentEvent( this.g.owner(tile).id(), - this.clickedCell, + tile, this.uiState.attackRatio * myPlayer.troops(), - spawnTile, + spawn !== false ? spawn : null, ), ); }); diff --git a/src/core/Schemas.ts b/src/core/Schemas.ts index 375aa8956..902b75c76 100644 --- a/src/core/Schemas.ts +++ b/src/core/Schemas.ts @@ -198,10 +198,8 @@ export const BoatAttackIntentSchema = BaseIntentSchema.extend({ type: z.literal("boat"), targetID: ID.nullable(), troops: z.number(), - dstX: z.number(), - dstY: z.number(), - srcX: z.number().nullable(), - srcY: z.number().nullable(), + dst: z.number(), + src: z.number().nullable(), }); export const AllianceRequestIntentSchema = BaseIntentSchema.extend({ diff --git a/src/core/execution/ExecutionManager.ts b/src/core/execution/ExecutionManager.ts index aec3516f2..192c0f9a8 100644 --- a/src/core/execution/ExecutionManager.ts +++ b/src/core/execution/ExecutionManager.ts @@ -1,5 +1,4 @@ import { Execution, Game } from "../game/Game"; -import { TileRef } from "../game/GameMap"; import { PseudoRandom } from "../PseudoRandom"; import { ClientID, GameID, Intent, Turn } from "../Schemas"; import { simpleHash } from "../Util"; @@ -70,16 +69,12 @@ export class Executor { this.mg.ref(intent.x, intent.y), ); case "boat": - let src: TileRef | null = null; - if (intent.srcX !== null && intent.srcY !== null) { - src = this.mg.ref(intent.srcX, intent.srcY); - } return new TransportShipExecution( playerID, intent.targetID, - this.mg.ref(intent.dstX, intent.dstY), + intent.dst, intent.troops, - src, + intent.src, ); case "allianceRequest": return new AllianceRequestExecution(playerID, intent.recipient); diff --git a/src/core/execution/MoveWarshipExecution.ts b/src/core/execution/MoveWarshipExecution.ts index 13431bc1d..0a0aad166 100644 --- a/src/core/execution/MoveWarshipExecution.ts +++ b/src/core/execution/MoveWarshipExecution.ts @@ -9,6 +9,10 @@ export class MoveWarshipExecution implements Execution { ) {} init(mg: Game, ticks: number): void { + if (!mg.isValidRef(this.position)) { + console.warn(`MoveWarshipExecution: position ${this.position} not valid`); + return; + } const warship = this.owner .units(UnitType.Warship) .find((u) => u.id() === this.unitId); diff --git a/src/core/execution/TransportShipExecution.ts b/src/core/execution/TransportShipExecution.ts index 8851d8fdf..d40326435 100644 --- a/src/core/execution/TransportShipExecution.ts +++ b/src/core/execution/TransportShipExecution.ts @@ -60,6 +60,16 @@ export class TransportShipExecution implements Execution { this.active = false; return; } + if (!mg.isValidRef(this.ref)) { + console.warn(`TransportShipExecution: ref ${this.ref} not valid`); + this.active = false; + return; + } + if (this.src !== null && !mg.isValidRef(this.src)) { + console.warn(`TransportShipExecution: src ${this.src} not valid`); + this.active = false; + return; + } this.lastMove = ticks; this.mg = mg; diff --git a/src/core/game/GameImpl.ts b/src/core/game/GameImpl.ts index 3a5fb80ef..c8b1ee586 100644 --- a/src/core/game/GameImpl.ts +++ b/src/core/game/GameImpl.ts @@ -687,6 +687,9 @@ export class GameImpl implements Game { ref(x: number, y: number): TileRef { return this._map.ref(x, y); } + isValidRef(ref: TileRef): boolean { + return this._map.isValidRef(ref); + } x(ref: TileRef): number { return this._map.x(ref); } diff --git a/src/core/game/GameMap.ts b/src/core/game/GameMap.ts index 3a9356f3b..6081cafa3 100644 --- a/src/core/game/GameMap.ts +++ b/src/core/game/GameMap.ts @@ -5,7 +5,7 @@ export type TileUpdate = bigint; export interface GameMap { ref(x: number, y: number): TileRef; - + isValidRef(ref: TileRef): boolean; x(ref: TileRef): number; y(ref: TileRef): number; cell(ref: TileRef): Cell; @@ -117,6 +117,10 @@ export class GameMapImpl implements GameMap { return this.yToRef[y] + x; } + isValidRef(ref: TileRef): boolean { + return this.isValidCoord(this.x(ref), this.y(ref)); + } + x(ref: TileRef): number { return this.refToX[ref]; } diff --git a/src/core/game/GameView.ts b/src/core/game/GameView.ts index e820c2234..839155647 100644 --- a/src/core/game/GameView.ts +++ b/src/core/game/GameView.ts @@ -481,6 +481,9 @@ export class GameView implements GameMap { ref(x: number, y: number): TileRef { return this._map.ref(x, y); } + isValidRef(ref: TileRef): boolean { + return this._map.isValidRef(ref); + } x(ref: TileRef): number { return this._map.x(ref); }