mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-22 11:18:11 +00:00
feat: Nuke trajectory prediction now accounts for alliance breakage. (#2912)
## Description: Nuke trajectory prediction now will show interception with allied SAMs if the alliance will break on nuke launch. Code was also refactored to be shared a bit more. In addition, if an incoming alliance would break if accepted, the nuke launch will break the alliance. <img width="1199" height="1002" alt="nukepr" src="https://github.com/user-attachments/assets/c31066d9-66cf-4eaa-be3c-e2fbcfe7965a" /> ## 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:
@@ -1,4 +1,5 @@
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
import { listNukeBreakAlliance } from "../../../core/execution/Util";
|
||||
import { UnitType } from "../../../core/game/Game";
|
||||
import { TileRef } from "../../../core/game/GameMap";
|
||||
import { GameView } from "../../../core/game/GameView";
|
||||
@@ -258,6 +259,18 @@ export class NukeTrajectoryPreviewLayer implements Layer {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const playersToBreakAllianceWith = listNukeBreakAlliance({
|
||||
game: this.game,
|
||||
targetTile,
|
||||
magnitude: this.game.config().nukeMagnitudes(ghostStructure),
|
||||
allySmallIds: new Set(
|
||||
this.game
|
||||
.myPlayer()
|
||||
?.allies()
|
||||
.map((a) => a.smallID()),
|
||||
),
|
||||
threshold: this.game.config().nukeAllianceBreakThreshold(),
|
||||
});
|
||||
// Find the point where SAM can intercept
|
||||
this.targetedIndex = this.trajectoryPoints.length;
|
||||
// Check trajectory
|
||||
@@ -270,7 +283,8 @@ export class NukeTrajectoryPreviewLayer implements Layer {
|
||||
)) {
|
||||
if (
|
||||
sam.unit.owner().isMe() ||
|
||||
this.game.myPlayer()?.isFriendly(sam.unit.owner())
|
||||
(this.game.myPlayer()?.isFriendly(sam.unit.owner()) &&
|
||||
!playersToBreakAllianceWith.has(sam.unit.owner().smallID()))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
isStructureType,
|
||||
MessageType,
|
||||
Player,
|
||||
StructureTypes,
|
||||
TerraNullius,
|
||||
TrajectoryTile,
|
||||
Unit,
|
||||
@@ -16,7 +15,7 @@ import { ParabolaUniversalPathFinder } from "../pathfinding/PathFinder.Parabola"
|
||||
import { PathStatus } from "../pathfinding/types";
|
||||
import { PseudoRandom } from "../PseudoRandom";
|
||||
import { NukeType } from "../StatsSchemas";
|
||||
import { computeNukeBlastCounts } from "./Util";
|
||||
import { listNukeBreakAlliance } from "./Util";
|
||||
|
||||
const SPRITE_RADIUS = 16;
|
||||
|
||||
@@ -85,36 +84,22 @@ export class NukeExecution implements Execution {
|
||||
}
|
||||
|
||||
const magnitude = this.mg.config().nukeMagnitudes(this.nuke.type());
|
||||
const threshold = this.mg.config().nukeAllianceBreakThreshold();
|
||||
|
||||
// Use shared utility to compute weighted tile counts per player
|
||||
const blastCounts = computeNukeBlastCounts({
|
||||
gm: this.mg,
|
||||
const playersToBreakAllianceWith = listNukeBreakAlliance({
|
||||
game: this.mg,
|
||||
targetTile: this.dst,
|
||||
magnitude,
|
||||
allySmallIds: new Set(this.player.allies().map((a) => a.smallID())),
|
||||
threshold: this.mg.config().nukeAllianceBreakThreshold(),
|
||||
});
|
||||
|
||||
// Collect all players that should have alliance broken:
|
||||
// either exceeds tile threshold OR has a structure in blast radius
|
||||
const playersToBreakAllianceWith = new Set<number>();
|
||||
|
||||
for (const [playerSmallId, totalWeight] of blastCounts) {
|
||||
if (totalWeight > threshold) {
|
||||
playersToBreakAllianceWith.add(playerSmallId);
|
||||
// Automatically reject incoming alliance requests.
|
||||
for (const incoming of this.player.incomingAllianceRequests()) {
|
||||
if (playersToBreakAllianceWith.has(incoming.requestor().smallID())) {
|
||||
incoming.reject();
|
||||
}
|
||||
}
|
||||
|
||||
// Also check if any allied structures would be destroyed
|
||||
this.mg
|
||||
.nearbyUnits(this.dst, magnitude.outer, [...StructureTypes])
|
||||
.filter(
|
||||
({ unit }) =>
|
||||
unit.owner().isPlayer() && this.player.isAlliedWith(unit.owner()),
|
||||
)
|
||||
.forEach(({ unit }) =>
|
||||
playersToBreakAllianceWith.add(unit.owner().smallID()),
|
||||
);
|
||||
|
||||
for (const playerSmallId of playersToBreakAllianceWith) {
|
||||
const attackedPlayer = this.mg.playerBySmallID(playerSmallId);
|
||||
if (!attackedPlayer.isPlayer()) {
|
||||
@@ -123,11 +108,12 @@ export class NukeExecution implements Execution {
|
||||
|
||||
// Resolves exploit of alliance breaking in which a pending alliance request
|
||||
// was accepted in the middle of a missile attack.
|
||||
const allianceRequest = attackedPlayer
|
||||
const outgoingAllianceRequest = attackedPlayer
|
||||
.incomingAllianceRequests()
|
||||
.find((ar) => ar.requestor() === this.player);
|
||||
if (allianceRequest) {
|
||||
allianceRequest.reject();
|
||||
if (outgoingAllianceRequest) {
|
||||
outgoingAllianceRequest.reject();
|
||||
continue;
|
||||
}
|
||||
|
||||
const alliance = this.player.allianceWith(attackedPlayer);
|
||||
|
||||
@@ -36,7 +36,7 @@ export function computeNukeBlastCounts(
|
||||
}
|
||||
|
||||
export interface NukeAllianceCheckParams {
|
||||
game: GameView;
|
||||
game: Game | GameView;
|
||||
targetTile: TileRef;
|
||||
magnitude: NukeMagnitude;
|
||||
allySmallIds: Set<number>;
|
||||
@@ -93,6 +93,40 @@ export function wouldNukeBreakAlliance(
|
||||
return result;
|
||||
}
|
||||
|
||||
// Same as wouldNukeBreakAlliance(), but takes time to find every player
|
||||
// that would be "angered" from this nuke.
|
||||
// This includes unallied players!
|
||||
export function listNukeBreakAlliance(
|
||||
params: NukeAllianceCheckParams,
|
||||
): Set<number> {
|
||||
const { game, targetTile, magnitude, threshold } = params;
|
||||
|
||||
// Collect all players that should have alliance broken:
|
||||
// either exceeds tile threshold OR has a structure in blast radius
|
||||
const playersToBreakAllianceWith = new Set<number>();
|
||||
|
||||
// compute tile breakage threshold
|
||||
const blastCounts = computeNukeBlastCounts({
|
||||
gm: game,
|
||||
targetTile,
|
||||
magnitude,
|
||||
});
|
||||
for (const [playerSmallId, totalWeight] of blastCounts) {
|
||||
if (totalWeight > threshold) {
|
||||
playersToBreakAllianceWith.add(playerSmallId);
|
||||
}
|
||||
}
|
||||
|
||||
// Also check if any allied structures would be destroyed
|
||||
game
|
||||
.nearbyUnits(targetTile, magnitude.outer, [...StructureTypes])
|
||||
.forEach(({ unit }) =>
|
||||
playersToBreakAllianceWith.add(unit.owner().smallID()),
|
||||
);
|
||||
|
||||
return playersToBreakAllianceWith;
|
||||
}
|
||||
|
||||
export function getSpawnTiles(gm: GameMap, tile: TileRef): TileRef[] {
|
||||
return Array.from(gm.bfs(tile, euclDistFN(tile, 4, true))).filter(
|
||||
(t) => !gm.hasOwner(t) && gm.isLand(t),
|
||||
|
||||
Reference in New Issue
Block a user