Merge branch 'main' into local-attack

This commit is contained in:
Aotumuri
2026-01-14 19:43:24 +09:00
committed by GitHub
143 changed files with 9451 additions and 5899 deletions
+17 -8
View File
@@ -8,7 +8,9 @@ import {
UnitType,
} from "../game/Game";
import { TileRef } from "../game/GameMap";
import { ParabolaPathFinder } from "../pathfinding/PathFinding";
import { UniversalPathFinding } from "../pathfinding/PathFinder";
import { ParabolaUniversalPathFinder } from "../pathfinding/PathFinder.Parabola";
import { PathStatus } from "../pathfinding/types";
import { PseudoRandom } from "../PseudoRandom";
import { simpleHash } from "../Util";
import { NukeExecution } from "./NukeExecution";
@@ -30,11 +32,12 @@ export class MirvExecution implements Execution {
private random: PseudoRandom;
private pathFinder: ParabolaPathFinder;
private pathFinder: ParabolaUniversalPathFinder;
private targetPlayer: Player | TerraNullius;
private separateDst: TileRef;
private spawnTile: TileRef;
private speed: number = -1;
@@ -46,9 +49,11 @@ export class MirvExecution implements Execution {
init(mg: Game, ticks: number): void {
this.random = new PseudoRandom(mg.ticks() + simpleHash(this.player.id()));
this.mg = mg;
this.pathFinder = new ParabolaPathFinder(mg);
this.targetPlayer = this.mg.owner(this.dst);
this.speed = this.mg.config().defaultNukeSpeed();
this.pathFinder = UniversalPathFinding.Parabola(mg, {
increment: this.speed,
});
// Betrayal on launch
if (this.targetPlayer.isPlayer()) {
@@ -70,6 +75,7 @@ export class MirvExecution implements Execution {
this.active = false;
return;
}
this.spawnTile = spawn;
this.nuke = this.player.buildUnit(UnitType.MIRV, spawn, {
targetTile: this.dst,
});
@@ -79,7 +85,6 @@ export class MirvExecution implements Execution {
);
const y = Math.max(0, this.mg.y(this.dst) - 500) + 50;
this.separateDst = this.mg.ref(x, y);
this.pathFinder.computeControlPoints(spawn, this.separateDst);
this.mg.displayIncomingUnit(
this.nuke.id(),
@@ -90,15 +95,19 @@ export class MirvExecution implements Execution {
);
}
const result = this.pathFinder.nextTile(this.speed);
if (result === true) {
const result = this.pathFinder.next(
this.spawnTile,
this.separateDst,
this.speed,
);
if (result.status === PathStatus.COMPLETE) {
this.separate();
this.active = false;
// Record stats
this.mg.stats().bombLand(this.player, this.targetPlayer, UnitType.MIRV);
return;
} else {
this.nuke.move(result);
} else if (result.status === PathStatus.NEXT) {
this.nuke.move(result.node);
}
}
+54 -35
View File
@@ -4,13 +4,16 @@ import {
isStructureType,
MessageType,
Player,
StructureTypes,
TerraNullius,
TrajectoryTile,
Unit,
UnitType,
} from "../game/Game";
import { TileRef } from "../game/GameMap";
import { ParabolaPathFinder } from "../pathfinding/PathFinding";
import { UniversalPathFinding } from "../pathfinding/PathFinder";
import { ParabolaUniversalPathFinder } from "../pathfinding/PathFinder.Parabola";
import { PathStatus } from "../pathfinding/types";
import { PseudoRandom } from "../PseudoRandom";
import { NukeType } from "../StatsSchemas";
import { computeNukeBlastCounts } from "./Util";
@@ -22,7 +25,7 @@ export class NukeExecution implements Execution {
private mg: Game;
private nuke: Unit | null = null;
private tilesToDestroyCache: Set<TileRef> | undefined;
private pathFinder: ParabolaPathFinder;
private pathFinder: ParabolaUniversalPathFinder;
constructor(
private nukeType: NukeType,
@@ -39,7 +42,11 @@ export class NukeExecution implements Execution {
if (this.speed === -1) {
this.speed = this.mg.config().defaultNukeSpeed();
}
this.pathFinder = new ParabolaPathFinder(mg);
this.pathFinder = UniversalPathFinding.Parabola(mg, {
increment: this.speed,
distanceBasedHeight: this.nukeType !== UnitType.MIRVWarhead,
directionUp: this.rocketDirectionUp,
});
}
public target(): Player | TerraNullius {
@@ -66,7 +73,7 @@ export class NukeExecution implements Execution {
/**
* Break alliances with players significantly affected by the nuke strike.
* Uses weighted tile counting (inner=1, outer=0.5).
* Uses weighted tile counting (inner=1, outer=0.5) OR if any allied structure would be destroyed.
*/
private maybeBreakAlliances() {
if (this.nuke === null) {
@@ -87,29 +94,48 @@ export class NukeExecution implements Execution {
magnitude,
});
// 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) {
const attackedPlayer = this.mg.playerBySmallID(playerSmallId);
if (!attackedPlayer.isPlayer()) {
continue;
}
playersToBreakAllianceWith.add(playerSmallId);
}
}
// Resolves exploit of alliance breaking in which a pending alliance request
// was accepted in the middle of a missile attack.
const allianceRequest = attackedPlayer
.incomingAllianceRequests()
.find((ar) => ar.requestor() === this.player);
if (allianceRequest) {
allianceRequest.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()),
);
const alliance = this.player.allianceWith(attackedPlayer);
if (alliance !== null) {
this.player.breakAlliance(alliance);
}
if (attackedPlayer !== this.player) {
attackedPlayer.updateRelation(this.player, -100);
}
for (const playerSmallId of playersToBreakAllianceWith) {
const attackedPlayer = this.mg.playerBySmallID(playerSmallId);
if (!attackedPlayer.isPlayer()) {
continue;
}
// Resolves exploit of alliance breaking in which a pending alliance request
// was accepted in the middle of a missile attack.
const allianceRequest = attackedPlayer
.incomingAllianceRequests()
.find((ar) => ar.requestor() === this.player);
if (allianceRequest) {
allianceRequest.reject();
}
const alliance = this.player.allianceWith(attackedPlayer);
if (alliance !== null) {
this.player.breakAlliance(alliance);
}
if (attackedPlayer !== this.player) {
attackedPlayer.updateRelation(this.player, -100);
}
}
}
@@ -123,13 +149,6 @@ export class NukeExecution implements Execution {
return;
}
this.src = spawn;
this.pathFinder.computeControlPoints(
spawn,
this.dst,
this.speed,
this.nukeType !== UnitType.MIRVWarhead,
this.rocketDirectionUp,
);
this.nuke = this.player.buildUnit(this.nukeType, spawn, {
targetTile: this.dst,
trajectory: this.getTrajectory(this.dst),
@@ -186,13 +205,13 @@ export class NukeExecution implements Execution {
}
// Move to next tile
const nextTile = this.pathFinder.nextTile(this.speed);
if (nextTile === true) {
const result = this.pathFinder.next(this.src!, this.dst, this.speed);
if (result.status === PathStatus.COMPLETE) {
this.detonate();
return;
} else {
} else if (result.status === PathStatus.NEXT) {
this.updateNukeTargetable();
this.nuke.move(nextTile);
this.nuke.move(result.node);
// Update index so SAM can interpolate future position
this.nuke.setTrajectoryIndex(this.pathFinder.currentIndex());
}
@@ -206,7 +225,7 @@ export class NukeExecution implements Execution {
const trajectoryTiles: TrajectoryTile[] = [];
const targetRangeSquared =
this.mg.config().defaultNukeTargetableRange() ** 2;
const allTiles: TileRef[] = this.pathFinder.allTiles();
const allTiles = this.pathFinder.findPath(this.src!, target) ?? [];
for (const tile of allTiles) {
trajectoryTiles.push({
tile,
+8 -8
View File
@@ -7,13 +7,13 @@ import {
UnitType,
} from "../game/Game";
import { TileRef } from "../game/GameMap";
import { AirPathFinder } from "../pathfinding/PathFinding";
import { PseudoRandom } from "../PseudoRandom";
import { PathFinding } from "../pathfinding/PathFinder";
import { PathStatus, SteppingPathFinder } from "../pathfinding/types";
import { NukeType } from "../StatsSchemas";
export class SAMMissileExecution implements Execution {
private active = true;
private pathFinder: AirPathFinder;
private pathFinder: SteppingPathFinder<TileRef>;
private SAMMissile: Unit | undefined;
private mg: Game;
private speed: number = 0;
@@ -27,7 +27,7 @@ export class SAMMissileExecution implements Execution {
) {}
init(mg: Game, ticks: number): void {
this.pathFinder = new AirPathFinder(mg, new PseudoRandom(mg.ticks()));
this.pathFinder = PathFinding.Air(mg);
this.mg = mg;
this.speed = this.mg.config().defaultSamMissileSpeed();
}
@@ -55,11 +55,11 @@ export class SAMMissileExecution implements Execution {
return;
}
for (let i = 0; i < this.speed; i++) {
const result = this.pathFinder.nextTile(
const result = this.pathFinder.next(
this.SAMMissile.tile(),
this.targetTile,
);
if (result === true) {
if (result.status === PathStatus.COMPLETE) {
this.mg.displayMessage(
"events_display.missile_intercepted",
MessageType.SAM_HIT,
@@ -76,8 +76,8 @@ export class SAMMissileExecution implements Execution {
.stats()
.bombIntercept(this._owner, this.target.type() as NukeType, 1);
return;
} else {
this.SAMMissile.move(result);
} else if (result.status === PathStatus.NEXT) {
this.SAMMissile.move(result.node);
}
}
}
+8 -7
View File
@@ -1,11 +1,12 @@
import { Execution, Game, Player, Unit, UnitType } from "../game/Game";
import { TileRef } from "../game/GameMap";
import { AirPathFinder } from "../pathfinding/PathFinding";
import { PathFinding } from "../pathfinding/PathFinder";
import { PathStatus, SteppingPathFinder } from "../pathfinding/types";
import { PseudoRandom } from "../PseudoRandom";
export class ShellExecution implements Execution {
private active = true;
private pathFinder: AirPathFinder;
private pathFinder: SteppingPathFinder<TileRef>;
private shell: Unit | undefined;
private mg: Game;
private destroyAtTick: number = -1;
@@ -19,7 +20,7 @@ export class ShellExecution implements Execution {
) {}
init(mg: Game, ticks: number): void {
this.pathFinder = new AirPathFinder(mg, new PseudoRandom(mg.ticks()));
this.pathFinder = PathFinding.Air(mg);
this.mg = mg;
this.random = new PseudoRandom(mg.ticks());
}
@@ -45,18 +46,18 @@ export class ShellExecution implements Execution {
}
for (let i = 0; i < 3; i++) {
const result = this.pathFinder.nextTile(
const result = this.pathFinder.next(
this.shell.tile(),
this.target.tile(),
);
if (result === true) {
if (result.status === PathStatus.COMPLETE) {
this.active = false;
this.target.modifyHealth(-this.effectOnTarget(), this._owner);
this.shell.setReachedTarget();
this.shell.delete(false);
return;
} else {
this.shell.move(result);
} else if (result.status === PathStatus.NEXT) {
this.shell.move(result.node);
}
}
}
+4 -3
View File
@@ -8,7 +8,8 @@ import {
UnitType,
} from "../game/Game";
import { TileRef } from "../game/GameMap";
import { PathFinder, PathFinders, PathStatus } from "../pathfinding/PathFinder";
import { PathFinding } from "../pathfinding/PathFinder";
import { PathStatus, SteppingPathFinder } from "../pathfinding/types";
import { distSortUnit } from "../Util";
export class TradeShipExecution implements Execution {
@@ -16,7 +17,7 @@ export class TradeShipExecution implements Execution {
private mg: Game;
private tradeShip: Unit | undefined;
private wasCaptured = false;
private pathFinder: PathFinder;
private pathFinder: SteppingPathFinder<TileRef>;
private tilesTraveled = 0;
constructor(
@@ -27,7 +28,7 @@ export class TradeShipExecution implements Execution {
init(mg: Game, ticks: number): void {
this.mg = mg;
this.pathFinder = PathFinders.Water(mg);
this.pathFinder = PathFinding.Water(mg);
}
tick(ticks: number): void {
+22 -60
View File
@@ -11,7 +11,8 @@ import {
} from "../game/Game";
import { TileRef } from "../game/GameMap";
import { targetTransportTile } from "../game/TransportShipUtils";
import { PathFinder, PathFinders, PathStatus } from "../pathfinding/PathFinder";
import { PathFinding } from "../pathfinding/PathFinder";
import { PathStatus, SteppingPathFinder } from "../pathfinding/types";
import { AttackExecution } from "./AttackExecution";
const malusForRetreat = 25;
@@ -29,11 +30,10 @@ export class TransportShipExecution implements Execution {
// TODO make private
public path: TileRef[];
private dst: TileRef | null;
private dstShore: TileRef | null;
private boat: Unit;
private pathFinder: PathFinder;
private pathFinder: SteppingPathFinder<TileRef>;
private originalOwner: Player;
@@ -70,7 +70,7 @@ export class TransportShipExecution implements Execution {
this.lastMove = ticks;
this.mg = mg;
this.pathFinder = PathFinders.Water(mg);
this.pathFinder = PathFinding.Water(mg);
if (
this.attacker.unitCount(UnitType.TransportShip) >=
@@ -106,8 +106,8 @@ export class TransportShipExecution implements Execution {
this.startTroops = Math.min(this.startTroops, this.attacker.troops());
this.dstShore = targetTransportTile(this.mg, this.ref);
if (this.dstShore === null) {
this.dst = targetTransportTile(this.mg, this.ref);
if (this.dst === null) {
console.warn(
`${this.attacker} cannot send ship to ${this.target}, cannot find attack tile`,
);
@@ -115,18 +115,9 @@ export class TransportShipExecution implements Execution {
return;
}
this.dst = this.adjacentWater(this.dstShore);
if (this.dst === null) {
console.warn(
`${this.attacker} cannot find water tile adjacent to destination`,
);
this.active = false;
return;
}
const closestTileSrc = this.attacker.canBuild(
UnitType.TransportShip,
this.dstShore,
this.dst,
);
if (closestTileSrc === false) {
console.warn(`can't build transport ship`);
@@ -152,21 +143,10 @@ export class TransportShipExecution implements Execution {
this.boat = this.attacker.buildUnit(UnitType.TransportShip, this.src, {
troops: this.startTroops,
targetTile: this.dst ?? undefined,
});
// Move boat from shore to adjacent water for pathfinding
const spawnWater = this.adjacentWater(this.src);
if (spawnWater === null) {
console.warn(`No adjacent water for transport ship spawn`);
this.boat.delete(false);
this.active = false;
return;
}
this.boat.move(spawnWater);
if (this.dstShore !== null) {
this.boat.setTargetTile(this.dstShore);
if (this.dst !== null) {
this.boat.setTargetTile(this.dst);
} else {
this.boat.setTargetTile(undefined);
}
@@ -222,7 +202,6 @@ export class TransportShipExecution implements Execution {
if (this.mg.owner(this.src!) !== this.attacker) {
// Use bestTransportShipSpawn, not canBuild because of its max boats check etc
const newSrc = this.attacker.bestTransportShipSpawn(this.dst);
if (newSrc === false) {
this.src = null;
} else {
@@ -239,19 +218,10 @@ export class TransportShipExecution implements Execution {
this.active = false;
return;
} else {
this.dstShore = this.src;
const retreatWater = this.adjacentWater(this.src);
if (retreatWater === null) {
console.warn(`No adjacent water for retreat destination`);
this.attacker.addTroops(this.boat.troops());
this.boat.delete(false);
this.active = false;
return;
}
this.dst = retreatWater;
this.dst = this.src;
if (this.boat.targetTile() !== this.dstShore) {
this.boat.setTargetTile(this.dstShore!);
if (this.boat.targetTile() !== this.dst) {
this.boat.setTargetTile(this.dst);
}
}
}
@@ -259,7 +229,7 @@ export class TransportShipExecution implements Execution {
const result = this.pathFinder.next(this.boat.tile(), this.dst);
switch (result.status) {
case PathStatus.COMPLETE:
if (this.mg.owner(this.dstShore!) === this.attacker) {
if (this.mg.owner(this.dst) === this.attacker) {
const deaths = this.boat.troops() * (malusForRetreat / 100);
const survivors = this.boat.troops() - deaths;
this.attacker.addTroops(survivors);
@@ -281,7 +251,7 @@ export class TransportShipExecution implements Execution {
}
return;
}
this.attacker.conquer(this.dstShore!);
this.attacker.conquer(this.dst);
if (this.target.isPlayer() && this.attacker.isFriendly(this.target)) {
this.attacker.addTroops(this.boat.troops());
} else {
@@ -290,7 +260,7 @@ export class TransportShipExecution implements Execution {
this.boat.troops(),
this.attacker,
this.targetID,
this.dstShore!,
this.dst,
false,
),
);
@@ -308,13 +278,18 @@ export class TransportShipExecution implements Execution {
break;
case PathStatus.PENDING:
break;
case PathStatus.NOT_FOUND:
case PathStatus.NOT_FOUND: {
// TODO: add to poisoned port list
console.warn(`path not found to dst`);
const map = this.mg.map();
const boatTile = this.boat.tile();
console.warn(
`TransportShip path not found: boat@(${map.x(boatTile)},${map.y(boatTile)}) -> dst@(${map.x(this.dst)},${map.y(this.dst)}), attacker=${this.attacker.id()}, target=${this.targetID}`,
);
this.attacker.addTroops(this.boat.troops());
this.boat.delete(false);
this.active = false;
return;
}
}
}
@@ -325,17 +300,4 @@ export class TransportShipExecution implements Execution {
isActive(): boolean {
return this.active;
}
private adjacentWater(tile: TileRef): TileRef | null {
if (this.mg.isWater(tile)) {
return tile;
}
for (const neighbor of this.mg.neighbors(tile)) {
if (this.mg.isWater(neighbor)) {
return neighbor;
}
}
return null;
}
}
+36 -15
View File
@@ -1,6 +1,7 @@
import { NukeMagnitude } from "../configuration/Config";
import { Game, Player } from "../game/Game";
import { Game, Player, StructureTypes } from "../game/Game";
import { euclDistFN, GameMap, TileRef } from "../game/GameMap";
import { GameView } from "../game/GameView";
export interface NukeBlastParams {
gm: GameMap;
@@ -34,40 +35,60 @@ export function computeNukeBlastCounts(
return counts;
}
export interface NukeAllianceCheckParams extends NukeBlastParams {
export interface NukeAllianceCheckParams {
game: GameView;
targetTile: TileRef;
magnitude: NukeMagnitude;
allySmallIds: Set<number>;
threshold: number;
}
// Checks if nuking this tile would break an alliance.
// Returns true if either:
// 1. The weighted tile count for any ally exceeds the threshold
// 2. Any allied structure would be destroyed
export function wouldNukeBreakAlliance(
params: NukeAllianceCheckParams,
): boolean {
const { gm, targetTile, magnitude, allySmallIds, threshold } = params;
const { game, targetTile, magnitude, allySmallIds, threshold } = params;
if (allySmallIds.size === 0) {
return false;
}
// Check if any allied structure would be destroyed
const wouldDestroyAlliedStructure = game.anyUnitNearby(
targetTile,
magnitude.outer,
StructureTypes,
(unit) =>
unit.owner().isPlayer() && allySmallIds.has(unit.owner().smallID()),
);
if (wouldDestroyAlliedStructure) return true;
const inner2 = magnitude.inner * magnitude.inner;
const allyTileCounts = new Map<number, number>();
let result = false;
gm.circleSearch(targetTile, magnitude.outer, (tile: TileRef, d2: number) => {
const ownerSmallId = gm.ownerID(tile);
if (ownerSmallId > 0 && allySmallIds.has(ownerSmallId)) {
const weight = d2 <= inner2 ? 1 : 0.5;
const newCount = (allyTileCounts.get(ownerSmallId) ?? 0) + weight;
allyTileCounts.set(ownerSmallId, newCount);
game.circleSearch(
targetTile,
magnitude.outer,
(tile: TileRef, d2: number) => {
const ownerSmallId = game.ownerID(tile);
if (ownerSmallId > 0 && allySmallIds.has(ownerSmallId)) {
const weight = d2 <= inner2 ? 1 : 0.5;
const newCount = (allyTileCounts.get(ownerSmallId) ?? 0) + weight;
allyTileCounts.set(ownerSmallId, newCount);
if (newCount > threshold) {
result = true;
return false; // Found one! Stop searching.
if (newCount > threshold) {
result = true;
return false; // Found one! Stop searching.
}
}
}
return true;
});
return true;
},
);
return result;
}
+27 -7
View File
@@ -8,7 +8,8 @@ import {
UnitType,
} from "../game/Game";
import { TileRef } from "../game/GameMap";
import { PathFinder, PathFinders, PathStatus } from "../pathfinding/PathFinder";
import { PathFinding } from "../pathfinding/PathFinder";
import { PathStatus, SteppingPathFinder } from "../pathfinding/types";
import { PseudoRandom } from "../PseudoRandom";
import { ShellExecution } from "./ShellExecution";
@@ -16,7 +17,7 @@ export class WarshipExecution implements Execution {
private random: PseudoRandom;
private warship: Unit;
private mg: Game;
private pathfinder: PathFinder;
private pathfinder: SteppingPathFinder<TileRef>;
private lastShellAttack = 0;
private alreadySentShell = new Set<Unit>();
@@ -26,7 +27,7 @@ export class WarshipExecution implements Execution {
init(mg: Game, ticks: number): void {
this.mg = mg;
this.pathfinder = PathFinders.Water(mg);
this.pathfinder = PathFinding.Water(mg);
this.random = new PseudoRandom(mg.ticks());
if (isUnit(this.input)) {
this.warship = this.input;
@@ -193,9 +194,10 @@ export class WarshipExecution implements Execution {
case PathStatus.PENDING:
this.warship.touch();
break;
case PathStatus.NOT_FOUND:
case PathStatus.NOT_FOUND: {
console.log(`path not found to target`);
break;
}
}
}
}
@@ -223,10 +225,10 @@ export class WarshipExecution implements Execution {
case PathStatus.PENDING:
this.warship.touch();
return;
case PathStatus.NOT_FOUND:
console.warn(`path not found to target tile`);
this.warship.setTargetTile(undefined);
case PathStatus.NOT_FOUND: {
console.log(`path not found to target`);
break;
}
}
}
@@ -243,6 +245,10 @@ export class WarshipExecution implements Execution {
const maxAttemptBeforeExpand: number = 500;
let attempts: number = 0;
let expandCount: number = 0;
// Get warship's water component for connectivity check
const warshipComponent = this.mg.getWaterComponent(this.warship.tile());
while (expandCount < 3) {
const x =
this.mg.x(this.warship.patrolTile()!) +
@@ -267,6 +273,20 @@ export class WarshipExecution implements Execution {
}
continue;
}
// Check water component connectivity
if (
warshipComponent !== null &&
!this.mg.hasWaterComponent(tile, warshipComponent)
) {
attempts++;
if (attempts === maxAttemptBeforeExpand) {
expandCount++;
attempts = 0;
warshipPatrolRange =
warshipPatrolRange + Math.floor(warshipPatrolRange / 2);
}
continue;
}
return tile;
}
console.warn(
+15
View File
@@ -5,6 +5,8 @@ import {
Game,
GameMode,
Player,
PlayerType,
RankedType,
Team,
} from "../game/Game";
@@ -44,6 +46,19 @@ export class WinCheckExecution implements Execution {
if (sorted.length === 0) {
return;
}
if (this.mg.config().gameConfig().rankedType === RankedType.OneVOne) {
const humans = sorted.filter(
(p) => p.type() === PlayerType.Human && !p.isDisconnected(),
);
if (humans.length === 1) {
this.mg.setWinner(humans[0], this.mg.stats().stats());
console.log(`${humans[0].name()} has won the game`);
this.active = false;
return;
}
}
const max = sorted[0];
const timeElapsed =
(this.mg.ticks() - this.mg.config().numSpawnPhaseTurns()) / 10;
@@ -11,7 +11,7 @@ import {
UnitType,
} from "../../game/Game";
import { TileRef, euclDistFN } from "../../game/GameMap";
import { ParabolaPathFinder } from "../../pathfinding/PathFinding";
import { UniversalPathFinding } from "../../pathfinding/PathFinder";
import { PseudoRandom } from "../../PseudoRandom";
import { assertNever, boundingBoxTiles } from "../../Util";
import { NukeExecution } from "../NukeExecution";
@@ -456,20 +456,14 @@ export class NationNukeBehavior {
spawnTile: TileRef,
targetTile: TileRef,
): boolean {
const pathFinder = new ParabolaPathFinder(this.game);
const speed = this.game.config().defaultNukeSpeed();
const distanceBasedHeight = true; // Atom/Hydrogen bombs use distance-based height
const rocketDirectionUp = true; // AI nukes always go "up" for now
const pathFinder = UniversalPathFinding.Parabola(this.game, {
increment: speed,
distanceBasedHeight: true, // Atom/Hydrogen bombs use distance-based height
directionUp: true, // AI nukes always go "up" for now
});
pathFinder.computeControlPoints(
spawnTile,
targetTile,
speed,
distanceBasedHeight,
rocketDirectionUp,
);
const trajectory = pathFinder.allTiles();
const trajectory = pathFinder.findPath(spawnTile, targetTile) ?? [];
if (trajectory.length === 0) {
return false;
}