mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 09:30:45 +00:00
fix: Check alliance breakage once at launch, and is deterministic now (#2554)
## Description: Removes the check to break alliance after nuke is launched, and alliance breaking is now determined before tiles are randomly chosen for destruction. (It is now slightly stricter, I believe, as a weighted average calculation is a little tricky) ## 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: bibizu
This commit is contained in:
@@ -44,6 +44,24 @@ export class NukeExecution implements Execution {
|
||||
return this.mg.owner(this.dst);
|
||||
}
|
||||
|
||||
private tilesInRange(): Map<TileRef, number> {
|
||||
if (this.nuke === null) {
|
||||
throw new Error("Not initialized");
|
||||
}
|
||||
const tilesInRange = new Map<TileRef, number>();
|
||||
const magnitude = this.mg.config().nukeMagnitudes(this.nuke.type());
|
||||
const inner2 = magnitude.inner * magnitude.inner;
|
||||
this.mg.circleSearch(
|
||||
this.dst,
|
||||
magnitude.outer,
|
||||
(t: TileRef, d2: number) => {
|
||||
tilesInRange.set(t, d2 <= inner2 ? 1 : 0.5);
|
||||
return true;
|
||||
},
|
||||
);
|
||||
return tilesInRange;
|
||||
}
|
||||
|
||||
private tilesToDestroy(): Set<TileRef> {
|
||||
if (this.tilesToDestroyCache !== undefined) {
|
||||
return this.tilesToDestroyCache;
|
||||
@@ -62,23 +80,27 @@ export class NukeExecution implements Execution {
|
||||
return this.tilesToDestroyCache;
|
||||
}
|
||||
|
||||
private maybeBreakAlliances(toDestroy: Set<TileRef>) {
|
||||
/**
|
||||
* Break alliances based on all tiles in range.
|
||||
* Tiles are weighted roughly based on their chance of being destroyed.
|
||||
*/
|
||||
private maybeBreakAlliances(inRange: Map<TileRef, number>) {
|
||||
if (this.nuke === null) {
|
||||
throw new Error("Not initialized");
|
||||
}
|
||||
const attacked = new Map<Player, number>();
|
||||
for (const tile of toDestroy) {
|
||||
for (const [tile, weight] of inRange.entries()) {
|
||||
const owner = this.mg.owner(tile);
|
||||
if (owner.isPlayer()) {
|
||||
const prev = attacked.get(owner) ?? 0;
|
||||
attacked.set(owner, prev + 1);
|
||||
attacked.set(owner, prev + weight);
|
||||
}
|
||||
}
|
||||
|
||||
const threshold = this.mg.config().nukeAllianceBreakThreshold();
|
||||
for (const [attackedPlayer, tilesDestroyed] of attacked) {
|
||||
for (const [attackedPlayer, totalWeight] of attacked) {
|
||||
if (
|
||||
tilesDestroyed > threshold &&
|
||||
totalWeight > threshold &&
|
||||
this.nuke.type() !== UnitType.MIRVWarhead
|
||||
) {
|
||||
// Resolves exploit of alliance breaking in which a pending alliance request
|
||||
@@ -120,7 +142,9 @@ export class NukeExecution implements Execution {
|
||||
targetTile: this.dst,
|
||||
trajectory: this.getTrajectory(this.dst),
|
||||
});
|
||||
this.maybeBreakAlliances(this.tilesToDestroy());
|
||||
if (this.nuke.type() !== UnitType.MIRVWarhead) {
|
||||
this.maybeBreakAlliances(this.tilesInRange());
|
||||
}
|
||||
if (this.mg.hasOwner(this.dst)) {
|
||||
const target = this.mg.owner(this.dst);
|
||||
if (!target.isPlayer()) {
|
||||
@@ -233,7 +257,6 @@ export class NukeExecution implements Execution {
|
||||
|
||||
const magnitude = this.mg.config().nukeMagnitudes(this.nuke.type());
|
||||
const toDestroy = this.tilesToDestroy();
|
||||
this.maybeBreakAlliances(toDestroy);
|
||||
|
||||
const maxTroops = this.target().isPlayer()
|
||||
? this.mg.config().maxTroops(this.target() as Player)
|
||||
|
||||
@@ -926,6 +926,13 @@ export class GameImpl implements Game {
|
||||
euclideanDistSquared(c1: TileRef, c2: TileRef): number {
|
||||
return this._map.euclideanDistSquared(c1, c2);
|
||||
}
|
||||
circleSearch(
|
||||
tile: TileRef,
|
||||
radius: number,
|
||||
filter?: (tile: TileRef, d2: number) => boolean,
|
||||
): Set<TileRef> {
|
||||
return this._map.circleSearch(tile, radius, filter);
|
||||
}
|
||||
bfs(
|
||||
tile: TileRef,
|
||||
filter: (gm: GameMap, tile: TileRef) => boolean,
|
||||
|
||||
@@ -39,6 +39,11 @@ export interface GameMap {
|
||||
|
||||
manhattanDist(c1: TileRef, c2: TileRef): number;
|
||||
euclideanDistSquared(c1: TileRef, c2: TileRef): number;
|
||||
circleSearch(
|
||||
tile: TileRef,
|
||||
radius: number,
|
||||
filter?: (tile: TileRef, d2: number) => boolean,
|
||||
): Set<TileRef>;
|
||||
bfs(
|
||||
tile: TileRef,
|
||||
filter: (gm: GameMap, tile: TileRef) => boolean,
|
||||
@@ -290,6 +295,29 @@ export class GameMapImpl implements GameMap {
|
||||
const y = this.y(c1) - this.y(c2);
|
||||
return x * x + y * y;
|
||||
}
|
||||
circleSearch(
|
||||
tile: TileRef,
|
||||
radius: number,
|
||||
filter?: (tile: TileRef, d2: number) => boolean,
|
||||
): Set<TileRef> {
|
||||
const center = { x: this.x(tile), y: this.y(tile) };
|
||||
const tiles: Set<TileRef> = new Set<TileRef>();
|
||||
const minX = Math.max(0, center.x - radius);
|
||||
const maxX = Math.min(this.width_ - 1, center.x + radius);
|
||||
const minY = Math.max(0, center.y - radius);
|
||||
const maxY = Math.min(this.height_ - 1, center.y + radius);
|
||||
for (let i = minX; i <= maxX; ++i) {
|
||||
for (let j = minY; j <= maxY; j++) {
|
||||
const t = this.yToRef[j] + i;
|
||||
const d2 = this.euclideanDistSquared(tile, t);
|
||||
if (d2 > radius * radius) continue;
|
||||
if (!filter || filter(t, d2)) {
|
||||
tiles.add(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
return tiles;
|
||||
}
|
||||
bfs(
|
||||
tile: TileRef,
|
||||
filter: (gm: GameMap, tile: TileRef) => boolean,
|
||||
|
||||
@@ -885,6 +885,13 @@ export class GameView implements GameMap {
|
||||
euclideanDistSquared(c1: TileRef, c2: TileRef): number {
|
||||
return this._map.euclideanDistSquared(c1, c2);
|
||||
}
|
||||
circleSearch(
|
||||
tile: TileRef,
|
||||
radius: number,
|
||||
filter?: (tile: TileRef, d2: number) => boolean,
|
||||
): Set<TileRef> {
|
||||
return this._map.circleSearch(tile, radius, filter);
|
||||
}
|
||||
bfs(
|
||||
tile: TileRef,
|
||||
filter: (gm: GameMap, tile: TileRef) => boolean,
|
||||
|
||||
Reference in New Issue
Block a user