Validate spawn tile (#1512)

## Description:

Enforce valid tile during spawn, to prevent the game from crashing for
all players.

## 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
- [ ] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
- [ ] I have read and accepted the CLA aggreement (only required once).
This commit is contained in:
Scott Anderson
2025-07-20 23:17:29 -04:00
committed by GitHub
parent e31495645f
commit 0489c63f4b
9 changed files with 27 additions and 57 deletions
+1 -1
View File
@@ -385,7 +385,7 @@ export class ClientGameRunner {
!this.gameView.hasOwner(tile) &&
this.gameView.inSpawnPhase()
) {
this.eventBus.emit(new SendSpawnIntentEvent(cell));
this.eventBus.emit(new SendSpawnIntentEvent(tile));
return;
}
if (this.gameView.inSpawnPhase()) {
+4 -7
View File
@@ -2,7 +2,6 @@ import { z } from "zod/v4";
import { EventBus, GameEvent } from "../core/EventBus";
import {
AllPlayers,
Cell,
GameType,
Gold,
PlayerID,
@@ -68,7 +67,7 @@ export class SendAllianceExtensionIntentEvent implements GameEvent {
}
export class SendSpawnIntentEvent implements GameEvent {
constructor(public readonly cell: Cell) {}
constructor(public readonly tile: TileRef) {}
}
export class SendAttackIntentEvent implements GameEvent {
@@ -90,7 +89,7 @@ export class SendBoatAttackIntentEvent implements GameEvent {
export class BuildUnitIntentEvent implements GameEvent {
constructor(
public readonly unit: UnitType,
public readonly cell: Cell,
public readonly tile: TileRef,
) {}
}
@@ -438,8 +437,7 @@ export class Transport {
pattern: this.lobbyConfig.pattern,
name: this.lobbyConfig.playerName,
playerType: PlayerType.Human,
x: event.cell.x,
y: event.cell.y,
tile: event.tile,
});
}
@@ -540,8 +538,7 @@ export class Transport {
type: "build_unit",
clientID: this.lobbyConfig.clientID,
unit: event.unit,
x: event.cell.x,
y: event.cell.y,
tile: event.tile,
});
}
+1 -7
View File
@@ -15,7 +15,6 @@ import { translateText } from "../../../client/Utils";
import { EventBus } from "../../../core/EventBus";
import {
BuildableUnit,
Cell,
Gold,
PlayerActions,
UnitType,
@@ -396,12 +395,7 @@ export class BuildMenu extends LitElement implements Layer {
),
);
} else if (buildableUnit.canBuild) {
this.eventBus.emit(
new BuildUnitIntentEvent(
buildableUnit.type,
new Cell(this.game.x(tile), this.game.y(tile)),
),
);
this.eventBus.emit(new BuildUnitIntentEvent(buildableUnit.type, tile));
}
this.hideMenu();
}
@@ -1,5 +1,5 @@
import { EventBus } from "../../../core/EventBus";
import { Cell, PlayerActions, PlayerID } from "../../../core/game/Game";
import { PlayerActions, PlayerID } from "../../../core/game/Game";
import { TileRef } from "../../../core/game/GameMap";
import { PlayerView } from "../../../core/game/GameView";
import {
@@ -61,8 +61,9 @@ export class PlayerActionHandler {
): Promise<TileRef | false> {
return await player.bestTransportShipSpawn(tile);
}
handleSpawn(spawnCell: Cell) {
this.eventBus.emit(new SendSpawnIntentEvent(spawnCell));
handleSpawn(tile: TileRef) {
this.eventBus.emit(new SendSpawnIntentEvent(tile));
}
handleAllianceRequest(player: PlayerView, recipient: PlayerView) {
@@ -1,10 +1,5 @@
import { Config } from "../../../core/configuration/Config";
import {
AllPlayers,
Cell,
PlayerActions,
UnitType,
} from "../../../core/game/Game";
import { AllPlayers, PlayerActions, UnitType } from "../../../core/game/Game";
import { TileRef } from "../../../core/game/GameMap";
import { GameView, PlayerView } from "../../../core/game/GameView";
import { flattenedEmojiTable } from "../../../core/Util";
@@ -427,11 +422,7 @@ export const centerButtonElement: CenterButtonElement = {
},
action: (params: MenuElementParams) => {
if (params.game.inSpawnPhase()) {
const cell = new Cell(
params.game.x(params.tile),
params.game.y(params.tile),
);
params.playerActionHandler.handleSpawn(cell);
params.playerActionHandler.handleSpawn(params.tile);
} else {
params.playerActionHandler.handleAttack(
params.myPlayer,
+2 -4
View File
@@ -255,8 +255,7 @@ export const SpawnIntentSchema = BaseIntentSchema.extend({
flag: FlagSchema,
pattern: PatternSchema,
playerType: PlayerTypeSchema,
x: z.number(),
y: z.number(),
tile: z.number(),
});
export const BoatAttackIntentSchema = BaseIntentSchema.extend({
@@ -320,8 +319,7 @@ export const TargetTroopRatioIntentSchema = BaseIntentSchema.extend({
export const BuildUnitIntentSchema = BaseIntentSchema.extend({
type: z.literal("build_unit"),
unit: z.enum(UnitType),
x: z.number(),
y: z.number(),
tile: z.number(),
});
export const UpgradeStructureIntentSchema = BaseIntentSchema.extend({
+5 -14
View File
@@ -1,5 +1,4 @@
import {
Cell,
Execution,
Game,
Gold,
@@ -27,12 +26,11 @@ export class ConstructionExecution implements Execution {
private ticksUntilComplete: Tick;
private cost: Gold;
private tile: TileRef;
constructor(
private player: Player,
private constructionType: UnitType,
private tileOrCell: TileRef | Cell,
private tile: TileRef,
) {}
init(mg: Game, ticks: number): void {
@@ -46,17 +44,10 @@ export class ConstructionExecution implements Execution {
return;
}
if (this.tileOrCell instanceof Cell) {
if (!this.mg.isValidCoord(this.tileOrCell.x, this.tileOrCell.y)) {
console.warn(
`cannot build construction invalid coordinates ${this.tileOrCell.x}, ${this.tileOrCell.y}`,
);
this.active = false;
return;
}
this.tile = this.mg.ref(this.tileOrCell.x, this.tileOrCell.y);
} else {
this.tile = this.tileOrCell;
if (!this.mg.isValidRef(this.tile)) {
console.warn(`cannot build construction invalid tile ${this.tile}`);
this.active = false;
return;
}
}
+3 -10
View File
@@ -1,4 +1,4 @@
import { Cell, Execution, Game } from "../game/Game";
import { Execution, Game } from "../game/Game";
import { PseudoRandom } from "../PseudoRandom";
import { ClientID, GameID, Intent, Turn } from "../Schemas";
import { simpleHash } from "../Util";
@@ -67,10 +67,7 @@ export class Executor {
case "move_warship":
return new MoveWarshipExecution(player, intent.unitId, intent.tile);
case "spawn":
return new SpawnExecution(
player.info(),
this.mg.ref(intent.x, intent.y),
);
return new SpawnExecution(player.info(), intent.tile);
case "boat":
return new TransportShipExecution(
player,
@@ -106,11 +103,7 @@ export class Executor {
case "embargo":
return new EmbargoExecution(player, intent.targetID, intent.action);
case "build_unit":
return new ConstructionExecution(
player,
intent.unit,
new Cell(intent.x, intent.y),
);
return new ConstructionExecution(player, intent.unit, intent.tile);
case "allianceExtension": {
return new AllianceExtensionExecution(player, intent.recipient);
}
+5
View File
@@ -20,6 +20,11 @@ export class SpawnExecution implements Execution {
tick(ticks: number) {
this.active = false;
if (!this.mg.isValidRef(this.tile)) {
console.warn(`SpawnExecution: tile ${this.tile} not valid`);
return;
}
if (!this.mg.inSpawnPhase()) {
this.active = false;
return;