diff --git a/src/core/execution/BotExecution.ts b/src/core/execution/BotExecution.ts index 85fabb34a..89e33a91a 100644 --- a/src/core/execution/BotExecution.ts +++ b/src/core/execution/BotExecution.ts @@ -1,4 +1,4 @@ -import { Execution, Game, isStructureType, Player } from "../game/Game"; +import { Execution, Game, Player } from "../game/Game"; import { PseudoRandom } from "../PseudoRandom"; import { simpleHash } from "../Util"; import { AllianceExtensionExecution } from "./alliance/AllianceExtensionExecution"; @@ -84,7 +84,7 @@ export class BotExecution implements Execution { private deleteAllStructures() { for (const unit of this.bot.units()) { - if (isStructureType(unit.type()) && this.bot.canDeleteUnit()) { + if (this.mg.isStructureType(unit.type()) && this.bot.canDeleteUnit()) { this.mg.addExecution(new DeleteUnitExecution(this.bot, unit.id())); } } diff --git a/src/core/execution/NukeExecution.ts b/src/core/execution/NukeExecution.ts index d35be7a39..448b8ec06 100644 --- a/src/core/execution/NukeExecution.ts +++ b/src/core/execution/NukeExecution.ts @@ -1,7 +1,6 @@ import { Execution, Game, - isStructureType, MessageType, Player, TerraNullius, @@ -333,7 +332,7 @@ export class NukeExecution implements Execution { private redrawBuildings(range: number) { const rangeSquared = range * range; for (const unit of this.mg.units()) { - if (isStructureType(unit.type())) { + if (this.mg.isStructureType(unit.type())) { if ( this.mg.euclideanDistSquared(this.dst, unit.tile()) < rangeSquared ) { diff --git a/src/core/execution/Util.ts b/src/core/execution/Util.ts index a4a414e89..5c795f3ef 100644 --- a/src/core/execution/Util.ts +++ b/src/core/execution/Util.ts @@ -1,5 +1,5 @@ import { NukeMagnitude } from "../configuration/Config"; -import { Game, Player, StructureTypes } from "../game/Game"; +import { Game, Player } from "../game/Game"; import { euclDistFN, GameMap, TileRef } from "../game/GameMap"; import { GameView } from "../game/GameView"; @@ -60,7 +60,7 @@ export function wouldNukeBreakAlliance( const wouldDestroyAlliedStructure = game.anyUnitNearby( targetTile, magnitude.outer, - StructureTypes, + game.getStructureTypes(), (unit) => unit.owner().isPlayer() && allySmallIds.has(unit.owner().smallID()), ); @@ -119,7 +119,7 @@ export function listNukeBreakAlliance( // Also check if any allied structures would be destroyed game - .nearbyUnits(targetTile, magnitude.outer, [...StructureTypes]) + .nearbyUnits(targetTile, magnitude.outer, game.getStructureTypes()) .forEach(({ unit }) => playersToBreakAllianceWith.add(unit.owner().smallID()), ); diff --git a/src/core/execution/nation/NationStructureBehavior.ts b/src/core/execution/nation/NationStructureBehavior.ts index c6e937985..525b3c05e 100644 --- a/src/core/execution/nation/NationStructureBehavior.ts +++ b/src/core/execution/nation/NationStructureBehavior.ts @@ -5,7 +5,6 @@ import { Player, PlayerType, Relation, - StructureTypes, Unit, UnitType, } from "../../game/Game"; @@ -347,7 +346,7 @@ export class NationStructureBehavior { */ private getTotalStructureDensity(): number { let totalStructures = 0; - for (const type of StructureTypes) { + for (const type of this.game.getStructureTypes()) { totalStructures += this.player.units(type).length; // ignoring levels } const tilesOwned = this.player.numTilesOwned(); diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index 1057e7e6b..06df80a92 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -258,21 +258,6 @@ export enum TrainType { Carriage = "Carriage", } -const _structureTypes: ReadonlySet = new Set([ - UnitType.City, - UnitType.DefensePost, - UnitType.SAMLauncher, - UnitType.MissileSilo, - UnitType.Port, - UnitType.Factory, -]); - -export const StructureTypes: readonly UnitType[] = [..._structureTypes]; - -export function isStructureType(type: UnitType): boolean { - return _structureTypes.has(type); -} - export interface OwnerComp { owner: Player; } @@ -793,6 +778,8 @@ export interface Game extends GameMap { predicate?: UnitPredicate, includeUnderConstruction?: boolean, ): Array<{ unit: Unit; distSquared: number }>; + getStructureTypes(): UnitType[]; + isStructureType(type: UnitType): boolean; addExecution(...exec: Execution[]): void; displayMessage( diff --git a/src/core/game/GameImpl.ts b/src/core/game/GameImpl.ts index 6dd76beff..1af469669 100644 --- a/src/core/game/GameImpl.ts +++ b/src/core/game/GameImpl.ts @@ -97,6 +97,9 @@ export class GameImpl implements Game { private _miniWaterGraph: AbstractGraph | null = null; private _miniWaterHPA: AStarWaterHierarchical | null = null; + private _structureTypes: UnitType[]; + private _structureTypesSet: Set; + constructor( private _humans: PlayerInfo[], private _nations: Nation[], @@ -128,6 +131,11 @@ export class GameImpl implements Game { ); } + this._structureTypes = Object.values(UnitType).filter( + (t) => this._config.unitInfo(t).territoryBound, + ); + this._structureTypesSet = new Set(this._structureTypes); + console.log( `[GameImpl] Constructor total: ${(performance.now() - constructorStart).toFixed(0)}ms`, ); @@ -903,6 +911,14 @@ export class GameImpl implements Game { }>; } + getStructureTypes(): UnitType[] { + return this._structureTypes; + } + + isStructureType(type: UnitType): boolean { + return this._structureTypesSet.has(type); + } + ref(x: number, y: number): TileRef { return this._map.ref(x, y); } diff --git a/src/core/game/GameView.ts b/src/core/game/GameView.ts index 65a5b74d8..26755d064 100644 --- a/src/core/game/GameView.ts +++ b/src/core/game/GameView.ts @@ -598,6 +598,9 @@ export class GameView implements GameMap { private _map: GameMap; + private _structureTypes: UnitType[]; + private _structureTypesSet: Set; + constructor( public worker: WorkerClient, private _config: Config, @@ -626,6 +629,11 @@ export class GameView implements GameMap { flag: nation.flag, } satisfies PlayerCosmetics); } + + this._structureTypes = Object.values(UnitType).filter( + (t) => this._config.unitInfo(t).territoryBound, + ); + this._structureTypesSet = new Set(this._structureTypes); } isOnEdgeOfMap(ref: TileRef): boolean { @@ -754,6 +762,14 @@ export class GameView implements GameMap { ); } + getStructureTypes(): UnitType[] { + return this._structureTypes; + } + + isStructureType(type: UnitType): boolean { + return this._structureTypesSet.has(type); + } + myClientID(): ClientID { return this._myClientID; } diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts index dd72a3307..6dcd9eeab 100644 --- a/src/core/game/PlayerImpl.ts +++ b/src/core/game/PlayerImpl.ts @@ -30,7 +30,6 @@ import { PlayerProfile, PlayerType, Relation, - StructureTypes, Team, TerraNullius, Tick, @@ -52,6 +51,8 @@ import { } from "./TransportShipUtils"; import { UnitImpl } from "./UnitImpl"; +const UNIT_TYPES = Object.freeze(Object.values(UnitType)); + interface Target { tick: Tick; target: Player; @@ -911,6 +912,10 @@ export class PlayerImpl implements Player { } public findUnitToUpgrade(type: UnitType, targetTile: TileRef): Unit | false { + if (!this.mg.config().unitInfo(type).upgradable) { + return false; + } + const range = this.mg.config().structureMinDist(); const existing = this.mg .nearbyUnits(targetTile, range, type, undefined, true) @@ -958,23 +963,22 @@ export class PlayerImpl implements Player { public buildableUnits(tile: TileRef | null): BuildableUnit[] { const validTiles = tile !== null ? this.validStructureSpawnTiles(tile) : []; - return Object.values(UnitType).map((u) => { + return UNIT_TYPES.map((u) => { + const cost = this.mg.config().unitInfo(u).cost(this.mg, this); let canUpgrade: number | false = false; let canBuild: TileRef | false = false; - if (!this.mg.inSpawnPhase()) { - const existingUnit = tile !== null && this.findUnitToUpgrade(u, tile); + if (tile !== null && !this.mg.inSpawnPhase()) { + const existingUnit = this.findUnitToUpgrade(u, tile); if (existingUnit !== false) { canUpgrade = existingUnit.id(); } - if (tile !== null) { - canBuild = this.canBuild(u, tile, validTiles); - } + canBuild = this.canBuild(u, tile, validTiles, cost); } return { type: u, canBuild, canUpgrade, - cost: this.mg.config().unitInfo(u).cost(this.mg, this), + cost, overlappingRailroads: canBuild !== false ? this.mg.railNetwork().overlappingRailroads(canBuild) @@ -987,12 +991,13 @@ export class PlayerImpl implements Player { unitType: UnitType, targetTile: TileRef, validTiles: TileRef[] | null = null, + knownCost: Gold | null = null, ): TileRef | false { if (this.mg.config().isUnitDisabled(unitType)) { return false; } - const cost = this.mg.unitInfo(unitType).cost(this.mg, this); + const cost = knownCost ?? this.mg.unitInfo(unitType).cost(this.mg, this); if ( unitType !== UnitType.MIRVWarhead && (!this.isAlive() || this.gold() < cost) @@ -1054,7 +1059,7 @@ export class PlayerImpl implements Player { const wouldHitTeammate = this.mg.anyUnitNearby( tile, magnitude.outer, - StructureTypes, + this.mg.getStructureTypes(), (unit) => unit.owner().isPlayer() && this.isOnSameTeam(unit.owner()), ); if (wouldHitTeammate) { @@ -1133,14 +1138,11 @@ export class PlayerImpl implements Player { } const searchRadius = 15; const searchRadiusSquared = searchRadius ** 2; - const types = Object.values(UnitType).filter((unitTypeValue) => { - return this.mg.config().unitInfo(unitTypeValue).territoryBound; - }); const nearbyUnits = this.mg.nearbyUnits( tile, searchRadius * 2, - types, + this.mg.getStructureTypes(), undefined, true, );