fix bad tile crash (#1237)

## Description:

Sending invalid coords can cause game to crash. Make sure to validate
tile ref.

## 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
- [x] I understand that submitting code with bugs that could have been
caught through manual testing blocks releases and new features for all
contributors

## Please put your Discord username so you can be contacted if a bug or
regression is found:
evan
This commit is contained in:
evanpelle
2025-06-20 09:49:55 -07:00
committed by GitHub
parent 0cd663df02
commit 9ae544595b
11 changed files with 57 additions and 54 deletions
+18 -25
View File
@@ -12,7 +12,7 @@ import {
import { createGameRecord } from "../core/Util";
import { ServerConfig } from "../core/configuration/Config";
import { getConfig } from "../core/configuration/ConfigLoader";
import { Cell, PlayerActions, UnitType } from "../core/game/Game";
import { PlayerActions, UnitType } from "../core/game/Game";
import { TileRef } from "../core/game/GameMap";
import {
ErrorUpdate,
@@ -404,7 +404,7 @@ export class ClientGameRunner {
),
);
} else if (this.canBoatAttack(actions, tile)) {
this.sendBoatAttackIntent(tile, cell);
this.sendBoatAttackIntent(tile);
}
const owner = this.gameView.owner(tile);
@@ -420,6 +420,9 @@ export class ClientGameRunner {
if (!this.isActive || !this.lastMousePosition) {
return;
}
if (this.gameView.inSpawnPhase()) {
return;
}
const cell = this.renderer.transformHandler.screenToWorldCoordinates(
this.lastMousePosition.x,
this.lastMousePosition.y,
@@ -427,11 +430,7 @@ export class ClientGameRunner {
if (!this.gameView.isValidCoord(cell.x, cell.y)) {
return;
}
const tile = this.gameView.ref(cell.x, cell.y);
if (this.gameView.inSpawnPhase()) {
return;
}
if (this.myPlayer === null) {
const myPlayer = this.gameView.playerByClientID(this.lobby.clientID);
@@ -441,7 +440,7 @@ export class ClientGameRunner {
this.myPlayer.actions(tile).then((actions) => {
if (!actions.canAttack && this.canBoatAttack(actions, tile)) {
this.sendBoatAttackIntent(tile, cell);
this.sendBoatAttackIntent(tile);
}
});
}
@@ -461,26 +460,20 @@ export class ClientGameRunner {
);
}
private sendBoatAttackIntent(tile: TileRef, cell: Cell) {
private sendBoatAttackIntent(tile: TileRef) {
if (!this.myPlayer) return;
this.myPlayer
.bestTransportShipSpawn(this.gameView.ref(cell.x, cell.y))
.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,
this.myPlayer.troops() * this.renderer.uiState.attackRatio,
spawnCell,
),
);
});
this.myPlayer.bestTransportShipSpawn(tile).then((spawn: number | false) => {
if (this.myPlayer === null) throw new Error("not initialized");
this.eventBus.emit(
new SendBoatAttackIntentEvent(
this.gameView.owner(tile).id(),
tile,
this.myPlayer.troops() * this.renderer.uiState.attackRatio,
spawn === false ? null : spawn,
),
);
});
}
private shouldBoat(tile: TileRef, src: TileRef) {
+5 -6
View File
@@ -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,
@@ -75,9 +76,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,
) {}
}
@@ -437,10 +438,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,
});
}
@@ -48,13 +48,13 @@ export class PlayerActionHandler {
handleBoatAttack(
player: PlayerView,
targetId: PlayerID | null,
targetCell: Cell,
spawnTile: Cell | null,
targetTile: TileRef,
spawnTile: TileRef | null,
) {
this.eventBus.emit(
new SendBoatAttackIntentEvent(
targetId,
targetCell,
targetTile,
this.uiState.attackRatio * player.troops(),
spawnTile,
),
@@ -1,6 +1,5 @@
import {
AllPlayers,
Cell,
PlayerActions,
TerraNullius,
UnitType,
@@ -378,16 +377,11 @@ export const boatMenuElement: MenuElement = {
params.tile,
);
let spawnTile: Cell | null = null;
if (spawn !== false) {
spawnTile = new Cell(params.game.x(spawn), params.game.y(spawn));
}
params.playerActionHandler.handleBoatAttack(
params.myPlayer,
params.selected?.id() || null,
new Cell(params.game.x(params.tile), params.game.y(params.tile)),
spawnTile,
params.tile,
spawn !== false ? spawn : null,
);
params.closeMenu();