Cancel nukes when accepting alliance via radial menu (#3155)

Resolves #3154

## Description:

#2716 introduced nuke cancellation logic on alliance acceptance via
`AllianceRequestReplyExecution`. The radial menu action, though, calls
`AllianceRequestExecution` instead, which accepts the alliance if a
request has already been made by the other player.

This PR moves the nuke cancellation logic to `GameImpl`, hooking into
the `acceptAllianceRequest` method, therefore accounting for every
alliance acceptance, regardless of the specific action that brought to
that.

## 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:

deshack_82603
This commit is contained in:
Mattia Migliorini
2026-02-16 11:10:26 -08:00
committed by GitHub
parent d0bb3a016e
commit f362e47413
13 changed files with 171 additions and 232 deletions
@@ -2,8 +2,10 @@ import {
AllianceRequest,
Execution,
Game,
MessageType,
Player,
PlayerID,
UnitType,
} from "../../game/Game";
export class AllianceRequestExecution implements Execution {
@@ -39,6 +41,19 @@ export class AllianceRequestExecution implements Execution {
// then accept it instead of creating a new one.
this.active = false;
incoming.accept();
// Update player relations
this.requestor.updateRelation(recipient, 100);
recipient.updateRelation(this.requestor, 100);
// Automatically remove embargoes only if they were automatically created
if (this.requestor.hasEmbargoAgainst(recipient))
this.requestor.endTemporaryEmbargo(recipient);
if (recipient.hasEmbargoAgainst(this.requestor))
recipient.endTemporaryEmbargo(this.requestor);
// Cancel incoming nukes between players
this.cancelNukesBetweenAlliedPlayers(recipient);
} else {
this.req = this.requestor.createAllianceRequest(recipient);
}
@@ -69,4 +84,51 @@ export class AllianceRequestExecution implements Execution {
activeDuringSpawnPhase(): boolean {
return false;
}
cancelNukesBetweenAlliedPlayers(recipient: Player): void {
const neutralized = new Map<Player, number>();
const players = [this.requestor, recipient];
for (const launcher of players) {
for (const unit of launcher.units(
UnitType.AtomBomb,
UnitType.HydrogenBomb,
)) {
if (!unit.isActive() || unit.reachedTarget()) continue;
const targetTile = unit.targetTile();
if (!targetTile) continue;
const targetOwner = this.mg.owner(targetTile);
if (!targetOwner.isPlayer()) continue;
const other = launcher === this.requestor ? recipient : this.requestor;
if (targetOwner !== other) continue;
unit.delete(false);
neutralized.set(launcher, (neutralized.get(launcher) ?? 0) + 1);
}
}
for (const [launcher, count] of neutralized) {
const other = launcher === this.requestor ? recipient : this.requestor;
this.mg.displayMessage(
"events_display.alliance_nukes_destroyed_outgoing",
MessageType.ALLIANCE_ACCEPTED,
launcher.id(),
undefined,
{ name: other.displayName(), count },
);
this.mg.displayMessage(
"events_display.alliance_nukes_destroyed_incoming",
MessageType.ALLIANCE_ACCEPTED,
other.id(),
undefined,
{ name: launcher.displayName(), count },
);
}
}
}