Skip to content

Commit a579c6e

Browse files
committed
fix: Alliance breakage is deterministic now
And only breaks alliances once, on launch.
1 parent 997cfea commit a579c6e

File tree

1 file changed

+29
-6
lines changed

1 file changed

+29
-6
lines changed

src/core/execution/NukeExecution.ts

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export class NukeExecution implements Execution {
2020
private active = true;
2121
private mg: Game;
2222
private nuke: Unit | null = null;
23+
private tilesInRangeCache: Map<TileRef, number>;
2324
private tilesToDestroyCache: Set<TileRef> | undefined;
2425
private pathFinder: ParabolaPathFinder;
2526

@@ -44,6 +45,25 @@ export class NukeExecution implements Execution {
4445
return this.mg.owner(this.dst);
4546
}
4647

48+
private tilesInRange(): Map<TileRef, number> {
49+
if (this.tilesInRangeCache !== undefined) {
50+
return this.tilesInRangeCache;
51+
}
52+
if (this.nuke === null) {
53+
throw new Error("Not initialized");
54+
}
55+
this.tilesInRangeCache = new Map<TileRef, number>();
56+
const magnitude = this.mg.config().nukeMagnitudes(this.nuke.type());
57+
const inner2 = magnitude.inner * magnitude.inner;
58+
const outer2 = magnitude.outer * magnitude.outer;
59+
this.mg.bfs(this.dst, (_, n: TileRef) => {
60+
const d2 = this.mg?.euclideanDistSquared(this.dst, n) ?? 0;
61+
this.tilesInRangeCache.set(n, d2 <= inner2 ? 1 : 0.5);
62+
return d2 <= outer2;
63+
});
64+
return this.tilesInRangeCache;
65+
}
66+
4767
private tilesToDestroy(): Set<TileRef> {
4868
if (this.tilesToDestroyCache !== undefined) {
4969
return this.tilesToDestroyCache;
@@ -62,16 +82,20 @@ export class NukeExecution implements Execution {
6282
return this.tilesToDestroyCache;
6383
}
6484

65-
private maybeBreakAlliances(toDestroy: Set<TileRef>) {
85+
/**
86+
* Break alliances based on all tiles in range.
87+
* Tiles are weighted based on their chance (1/odds) of being destroyed.
88+
*/
89+
private maybeBreakAlliances(inRange: Map<TileRef, number>) {
6690
if (this.nuke === null) {
6791
throw new Error("Not initialized");
6892
}
6993
const attacked = new Map<Player, number>();
70-
for (const tile of toDestroy) {
94+
for (const [tile, weight] of inRange.entries()) {
7195
const owner = this.mg.owner(tile);
7296
if (owner.isPlayer()) {
7397
const prev = attacked.get(owner) ?? 0;
74-
attacked.set(owner, prev + 1);
98+
attacked.set(owner, prev + weight);
7599
}
76100
}
77101

@@ -82,7 +106,7 @@ export class NukeExecution implements Execution {
82106
this.nuke.type() !== UnitType.MIRVWarhead
83107
) {
84108
// Resolves exploit of alliance breaking in which a pending alliance request
85-
// was accepeted in the middle of an missle attack.
109+
// was accepted in the middle of a missile attack.
86110
const allianceRequest = attackedPlayer
87111
.incomingAllianceRequests()
88112
.find((ar) => ar.requestor() === this.player);
@@ -120,7 +144,7 @@ export class NukeExecution implements Execution {
120144
targetTile: this.dst,
121145
trajectory: this.getTrajectory(this.dst),
122146
});
123-
this.maybeBreakAlliances(this.tilesToDestroy());
147+
this.maybeBreakAlliances(this.tilesInRange());
124148
if (this.mg.hasOwner(this.dst)) {
125149
const target = this.mg.owner(this.dst);
126150
if (!target.isPlayer()) {
@@ -233,7 +257,6 @@ export class NukeExecution implements Execution {
233257

234258
const magnitude = this.mg.config().nukeMagnitudes(this.nuke.type());
235259
const toDestroy = this.tilesToDestroy();
236-
this.maybeBreakAlliances(toDestroy);
237260

238261
const maxTroops = this.target().isPlayer()
239262
? this.mg.config().maxTroops(this.target() as Player)

0 commit comments

Comments
 (0)