diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts index 5a672f296..33f7ef61a 100644 --- a/src/core/configuration/DefaultConfig.ts +++ b/src/core/configuration/DefaultConfig.ts @@ -348,6 +348,7 @@ export class DefaultConfig implements Config { return { cost: () => 0n, territoryBound: false, + playerBuildable: false, }; case UnitType.Warship: return { @@ -356,18 +357,21 @@ export class DefaultConfig implements Config { UnitType.Warship, ), territoryBound: false, + playerBuildable: true, maxHealth: 1000, }; case UnitType.Shell: return { cost: () => 0n, territoryBound: false, + playerBuildable: false, damage: 250, }; case UnitType.SAMMissile: return { cost: () => 0n, territoryBound: false, + playerBuildable: false, }; case UnitType.Port: return { @@ -378,19 +382,21 @@ export class DefaultConfig implements Config { UnitType.Factory, ), territoryBound: true, + playerBuildable: true, constructionDuration: this.instantBuild() ? 0 : 2 * 10, upgradable: true, - canBuildTrainStation: true, }; case UnitType.AtomBomb: return { cost: this.costWrapper(() => 750_000, UnitType.AtomBomb), territoryBound: false, + playerBuildable: true, }; case UnitType.HydrogenBomb: return { cost: this.costWrapper(() => 5_000_000, UnitType.HydrogenBomb), territoryBound: false, + playerBuildable: true, }; case UnitType.MIRV: return { @@ -401,21 +407,25 @@ export class DefaultConfig implements Config { return 25_000_000n + game.stats().numMirvsLaunched() * 15_000_000n; }, territoryBound: false, + playerBuildable: true, }; case UnitType.MIRVWarhead: return { cost: () => 0n, territoryBound: false, + playerBuildable: false, }; case UnitType.TradeShip: return { cost: () => 0n, territoryBound: false, + playerBuildable: true, }; case UnitType.MissileSilo: return { cost: this.costWrapper(() => 1_000_000, UnitType.MissileSilo), territoryBound: true, + playerBuildable: true, constructionDuration: this.instantBuild() ? 0 : 10 * 10, upgradable: true, }; @@ -426,6 +436,7 @@ export class DefaultConfig implements Config { UnitType.DefensePost, ), territoryBound: true, + playerBuildable: true, constructionDuration: this.instantBuild() ? 0 : 5 * 10, }; case UnitType.SAMLauncher: @@ -436,6 +447,7 @@ export class DefaultConfig implements Config { UnitType.SAMLauncher, ), territoryBound: true, + playerBuildable: true, constructionDuration: this.instantBuild() ? 0 : 30 * 10, upgradable: true, }; @@ -447,9 +459,9 @@ export class DefaultConfig implements Config { UnitType.City, ), territoryBound: true, + playerBuildable: true, constructionDuration: this.instantBuild() ? 0 : 2 * 10, upgradable: true, - canBuildTrainStation: true, }; case UnitType.Factory: return { @@ -460,16 +472,15 @@ export class DefaultConfig implements Config { UnitType.Port, ), territoryBound: true, + playerBuildable: true, constructionDuration: this.instantBuild() ? 0 : 2 * 10, - canBuildTrainStation: true, - experimental: true, upgradable: true, }; case UnitType.Train: return { cost: () => 0n, territoryBound: false, - experimental: true, + playerBuildable: false, }; default: assertNever(type); diff --git a/src/core/execution/Util.ts b/src/core/execution/Util.ts index 5c795f3ef..aa2919b6e 100644 --- a/src/core/execution/Util.ts +++ b/src/core/execution/Util.ts @@ -119,7 +119,7 @@ export function listNukeBreakAlliance( // Also check if any allied structures would be destroyed game - .nearbyUnits(targetTile, magnitude.outer, game.getStructureTypes()) + .nearbyUnits(targetTile, magnitude.outer, this.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 525b3c05e..29ed93cd7 100644 --- a/src/core/execution/nation/NationStructureBehavior.ts +++ b/src/core/execution/nation/NationStructureBehavior.ts @@ -227,7 +227,7 @@ export class NationStructureBehavior { const structures = this.player.units(type); if ( this.getTotalStructureDensity() > UPGRADE_DENSITY_THRESHOLD && - type !== UnitType.DefensePost + this.game.config().unitInfo(type).upgradable ) { if (this.maybeUpgradeStructure(structures)) { return true; @@ -329,10 +329,7 @@ export class NationStructureBehavior { return false; } const structureToUpgrade = this.findBestStructureToUpgrade(structures); - if ( - structureToUpgrade !== null && - this.player.canUpgradeUnit(structureToUpgrade) - ) { + if (structureToUpgrade !== null) { this.game.addExecution( new UpgradeStructureExecution(this.player, structureToUpgrade.id()), ); @@ -345,12 +342,10 @@ export class NationStructureBehavior { * Calculates total structure density across player's territory. */ private getTotalStructureDensity(): number { - let totalStructures = 0; - for (const type of this.game.getStructureTypes()) { - totalStructures += this.player.units(type).length; // ignoring levels - } const tilesOwned = this.player.numTilesOwned(); - return tilesOwned > 0 ? totalStructures / tilesOwned : 0; + return tilesOwned > 0 + ? this.player.units(...this.game.getStructureTypes()).length / tilesOwned + : 0; //ignoring levels for structures } /** diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index 5a4109a17..b86191d56 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -225,12 +225,11 @@ export interface UnitInfo { cost: (game: Game, player: Player) => Gold; // Determines if its owner changes when its tile is conquered. territoryBound: boolean; + playerBuildable: boolean; maxHealth?: number; damage?: number; constructionDuration?: number; upgradable?: boolean; - canBuildTrainStation?: boolean; - experimental?: boolean; } export enum UnitType { @@ -779,7 +778,7 @@ export interface Game extends GameMap { predicate?: UnitPredicate, includeUnderConstruction?: boolean, ): Array<{ unit: Unit; distSquared: number }>; - getStructureTypes(): UnitType[]; + getStructureTypes(): readonly UnitType[]; isStructureType(type: UnitType): boolean; addExecution(...exec: Execution[]): void; diff --git a/src/core/game/GameImpl.ts b/src/core/game/GameImpl.ts index 2509f699b..9d48cc5e1 100644 --- a/src/core/game/GameImpl.ts +++ b/src/core/game/GameImpl.ts @@ -97,8 +97,9 @@ export class GameImpl implements Game { private _miniWaterGraph: AbstractGraph | null = null; private _miniWaterHPA: AStarWaterHierarchical | null = null; - private _structureTypes: UnitType[]; + private _structureTypes: UnitType[] = []; private _structureTypesSet: Set; + private _buildableUnitTypes: UnitType[] = []; constructor( private _humans: PlayerInfo[], @@ -131,11 +132,17 @@ export class GameImpl implements Game { ); } - this._structureTypes = Object.values(UnitType).filter( - (t) => this._config.unitInfo(t).territoryBound, - ); + this._structureTypes = + Object.values(UnitType).filter( + (t) => this._config.unitInfo(t).territoryBound, + ) ?? []; this._structureTypesSet = new Set(this._structureTypes); + this._buildableUnitTypes = + Object.values(UnitType).filter( + (t) => this._config.unitInfo(t).playerBuildable, + ) ?? []; + console.log( `[GameImpl] Constructor total: ${(performance.now() - constructorStart).toFixed(0)}ms`, ); @@ -925,6 +932,10 @@ export class GameImpl implements Game { return this._structureTypesSet.has(type); } + getPlayerBuildableUnitTypes(): UnitType[] { + return this._buildableUnitTypes; + } + 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 1bda2080d..5495b2229 100644 --- a/src/core/game/GameView.ts +++ b/src/core/game/GameView.ts @@ -630,9 +630,10 @@ export class GameView implements GameMap { } satisfies PlayerCosmetics); } - this._structureTypes = Object.values(UnitType).filter( - (t) => this._config.unitInfo(t).territoryBound, - ); + this._structureTypes = + Object.values(UnitType).filter( + (t) => this._config.unitInfo(t).territoryBound, + ) ?? []; this._structureTypesSet = new Set(this._structureTypes); } diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts index 6c3012853..670fe4f75 100644 --- a/src/core/game/PlayerImpl.ts +++ b/src/core/game/PlayerImpl.ts @@ -967,7 +967,8 @@ export class PlayerImpl implements Player { public buildableUnits(tile: TileRef | null): BuildableUnit[] { const validTiles = tile !== null ? this.validStructureSpawnTiles(tile) : []; - return UNIT_TYPES.map((u) => { + + return this.mg.getPlayerBuildableUnitTypes().map((u) => { const cost = this.mg.config().unitInfo(u).cost(this.mg, this); let canUpgrade: number | false = false; let canBuild: TileRef | false = false;