Destroy incoming nukes when alliance is created (#2716)

Resolves #2484

## Description:

- When an alliance is created between two players, any incoming nukes
between them are destroyed mid-air.
This prevents the traitor debuff from being applied on impact, even if
the nukes were launched before the alliance was formed.
- If a player has launched nukes at multiple nations, only the nukes
targeting the newly allied nation are destroyed.

This is what the players will see after the alliance is created (in case
they have launched nukes at each other):
<img width="423" height="125" alt="Screenshot 2026-01-04 092907"
src="https://github.com/user-attachments/assets/6544fb7a-7623-4fc3-b799-89ef8fe897d6"
/>


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

assessin.
This commit is contained in:
Achim Marius
2026-01-16 06:09:01 +02:00
committed by GitHub
parent 9cd87f8906
commit b2ba37e0ab
3 changed files with 230 additions and 1 deletions
@@ -1,4 +1,11 @@
import { Execution, Game, Player, PlayerID } from "../../game/Game";
import {
Execution,
Game,
MessageType,
Player,
PlayerID,
UnitType,
} from "../../game/Game";
export class AllianceRequestReplyExecution implements Execution {
private active = true;
@@ -10,6 +17,57 @@ export class AllianceRequestReplyExecution implements Execution {
private accept: boolean,
) {}
private cancelNukesBetweenAlliedPlayers(
mg: Game,
p1: Player,
p2: Player,
): void {
const neutralized = new Map<Player, number>();
const players = [p1, p2];
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 = mg.owner(targetTile);
if (!targetOwner.isPlayer()) continue;
const other = launcher === p1 ? p2 : p1;
if (targetOwner !== other) continue;
unit.delete(false);
neutralized.set(launcher, (neutralized.get(launcher) ?? 0) + 1);
}
}
for (const [launcher, count] of neutralized) {
const other = launcher === p1 ? p2 : p1;
mg.displayMessage(
"events_display.alliance_nukes_destroyed_outgoing",
MessageType.ALLIANCE_ACCEPTED,
launcher.id(),
undefined,
{ name: other.displayName(), count },
);
mg.displayMessage(
"events_display.alliance_nukes_destroyed_incoming",
MessageType.ALLIANCE_ACCEPTED,
other.id(),
undefined,
{ name: launcher.displayName(), count },
);
}
}
init(mg: Game, ticks: number): void {
if (!mg.hasPlayer(this.requestorID)) {
console.warn(
@@ -33,6 +91,12 @@ export class AllianceRequestReplyExecution implements Execution {
request.accept();
this.requestor.updateRelation(this.recipient, 100);
this.recipient.updateRelation(this.requestor, 100);
this.cancelNukesBetweenAlliedPlayers(
mg,
this.requestor,
this.recipient,
);
} else {
request.reject();
}