mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 09:30:45 +00:00
Fix nation nuke crash when attacker has no remaining tiles 🛡️ (#3703)
## Description: Fixes a game crash (`Error: array must not be empty`) thrown from `PseudoRandom.randElement` when a nation tries to pick a nuke target whose territory no longer exists. ## Root cause `NationNukeBehavior.findBestNukeTarget` calls `findIncomingAttackPlayer`, which iterates the player's `_incomingAttacks`. `AttackImpl` instances can linger in this array past the point where the attacker has lost all tiles — the attack is only removed on explicit `delete()` and `removeOnDeath` cleanup isn't guaranteed to run before other executions tick within the same turn. A dead attacker gets returned as the nuke target, `randTerritoryTileArray` samples their (empty) territory, and `randElement` throws on the empty array. ## Fix - `Player.incomingAttacks()` now filters out attacks whose attacker is no longer alive, so consumers can't observe stale references from mid-tick deaths. - `randTerritoryTile` guards against `numTilesOwned() === 0` before falling back to `randElement(p.tiles())` as a defense-in-depth safeguard at the util level. - `PlayerImpl.toUpdate()` now uses the `incomingAttacks()` / `outgoingAttacks()` accessors (rather than the raw `_` arrays) so serialized client state stays consistent with the server-side view. `outgoingAttacks()` is intentionally left unfiltered, the engine relies on seeing in-flight attacks during their retreat phase after a target is conquered (attack merging/cancellation in `AttackExecution.init`). ### Bug report from discord Please paste the following in your bug report in Discord: Game crashed! game id: LQDSWbh6 client id: JjwysSLN Error: array must not be empty Message: Error: array must not be empty at sa.randElement (https://main.openfront.dev/assets/Worker.worker-DoPM94lr.js:28:39166) at H1 (https://main.openfront.dev/assets/Worker.worker-DoPM94lr.js:31:116429) at Jc (https://main.openfront.dev/assets/Worker.worker-DoPM94lr.js:31:116135) at G1.maybeSendNuke (https://main.openfront.dev/assets/Worker.worker-DoPM94lr.js:31:117597) at n5.tick (https://main.openfront.dev/assets/Worker.worker-DoPM94lr.js:31:171764) at https://main.openfront.dev/assets/Worker.worker-DoPM94lr.js:31:251463 at Array.forEach (<anonymous>) at c.executeNextTick (https://main.openfront.dev/assets/Worker.worker-DoPM94lr.js:31:251383) at b.executeNextTick (https://main.openfront.dev/assets/Worker.worker-DoPM94lr.js:31:271256) at S_ (https://main.openfront.dev/assets/Worker.worker-DoPM94lr.js:31:366356) ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin
This commit is contained in:
@@ -41,7 +41,7 @@ function randTerritoryTile(
|
||||
}
|
||||
}
|
||||
|
||||
if (p.numTilesOwned() <= 100) {
|
||||
if (p.numTilesOwned() > 0 && p.numTilesOwned() <= 100) {
|
||||
return random.randElement(Array.from(p.tiles()));
|
||||
}
|
||||
|
||||
|
||||
@@ -144,7 +144,7 @@ export class PlayerImpl implements Player {
|
||||
traitorRemainingTicks: this.getTraitorRemainingTicks(),
|
||||
targets: this.targets().map((p) => p.smallID()),
|
||||
outgoingEmojis: this.outgoingEmojis(),
|
||||
outgoingAttacks: this._outgoingAttacks.map((a) => {
|
||||
outgoingAttacks: this.outgoingAttacks().map((a) => {
|
||||
return {
|
||||
attackerID: a.attacker().smallID(),
|
||||
targetID: a.target().smallID(),
|
||||
@@ -153,7 +153,7 @@ export class PlayerImpl implements Player {
|
||||
retreating: a.retreating(),
|
||||
} satisfies AttackUpdate;
|
||||
}),
|
||||
incomingAttacks: this._incomingAttacks.map((a) => {
|
||||
incomingAttacks: this.incomingAttacks().map((a) => {
|
||||
return {
|
||||
attackerID: a.attacker().smallID(),
|
||||
targetID: a.target().smallID(),
|
||||
@@ -1381,7 +1381,7 @@ export class PlayerImpl implements Player {
|
||||
return this._outgoingAttacks;
|
||||
}
|
||||
incomingAttacks(): Attack[] {
|
||||
return this._incomingAttacks;
|
||||
return this._incomingAttacks.filter((a) => a.attacker().isAlive());
|
||||
}
|
||||
|
||||
public isImmune(): boolean {
|
||||
|
||||
Reference in New Issue
Block a user