diff --git a/src/client/graphics/layers/NukeTrajectoryPreviewLayer.ts b/src/client/graphics/layers/NukeTrajectoryPreviewLayer.ts index 0d1dfc9d0..341254ab8 100644 --- a/src/client/graphics/layers/NukeTrajectoryPreviewLayer.ts +++ b/src/client/graphics/layers/NukeTrajectoryPreviewLayer.ts @@ -138,7 +138,7 @@ export class NukeTrajectoryPreviewLayer implements Layer { // Get buildable units to find spawn tile (expensive call - only on tick when tile changes) player - .actions(targetTile) + .actions(targetTile, [ghostStructure]) .then((actions) => { // Ignore stale results if target changed if (this.lastTargetTile !== targetTile) { diff --git a/src/client/graphics/layers/StructureIconsLayer.ts b/src/client/graphics/layers/StructureIconsLayer.ts index 9cb3cd3af..72d7ef7f9 100644 --- a/src/client/graphics/layers/StructureIconsLayer.ts +++ b/src/client/graphics/layers/StructureIconsLayer.ts @@ -285,7 +285,7 @@ export class StructureIconsLayer implements Layer { this.game ?.myPlayer() - ?.actions(tileRef) + ?.actions(tileRef, [this.ghostUnit?.buildableUnit.type]) .then((actions) => { if (this.potentialUpgrade) { this.potentialUpgrade.iconContainer.filters = []; diff --git a/src/client/graphics/layers/UnitDisplay.ts b/src/client/graphics/layers/UnitDisplay.ts index 5caac75d1..82d530dcb 100644 --- a/src/client/graphics/layers/UnitDisplay.ts +++ b/src/client/graphics/layers/UnitDisplay.ts @@ -22,6 +22,19 @@ import portIcon from "/images/PortIcon.svg?url"; import samLauncherIcon from "/images/SamLauncherIconWhite.svg?url"; import defensePostIcon from "/images/ShieldIconWhite.svg?url"; +const BUILDABLE_UNITS: UnitType[] = [ + UnitType.City, + UnitType.Factory, + UnitType.Port, + UnitType.DefensePost, + UnitType.MissileSilo, + UnitType.SAMLauncher, + UnitType.Warship, + UnitType.AtomBomb, + UnitType.HydrogenBomb, + UnitType.MIRV, +]; + @customElement("unit-display") export class UnitDisplay extends LitElement implements Layer { public game: GameView; @@ -55,17 +68,7 @@ export class UnitDisplay extends LitElement implements Layer { } } - this.allDisabled = - config.isUnitDisabled(UnitType.City) && - config.isUnitDisabled(UnitType.Factory) && - config.isUnitDisabled(UnitType.Port) && - config.isUnitDisabled(UnitType.DefensePost) && - config.isUnitDisabled(UnitType.MissileSilo) && - config.isUnitDisabled(UnitType.SAMLauncher) && - config.isUnitDisabled(UnitType.Warship) && - config.isUnitDisabled(UnitType.AtomBomb) && - config.isUnitDisabled(UnitType.HydrogenBomb) && - config.isUnitDisabled(UnitType.MIRV); + this.allDisabled = BUILDABLE_UNITS.every((u) => config.isUnitDisabled(u)); this.requestUpdate(); } @@ -101,7 +104,7 @@ export class UnitDisplay extends LitElement implements Layer { tick() { const player = this.game?.myPlayer(); - player?.actions().then((actions) => { + player?.actions(undefined, BUILDABLE_UNITS).then((actions) => { this.playerActions = actions; }); if (!player) return; diff --git a/src/core/GameRunner.ts b/src/core/GameRunner.ts index 856e19691..f0453073c 100644 --- a/src/core/GameRunner.ts +++ b/src/core/GameRunner.ts @@ -195,13 +195,14 @@ export class GameRunner { playerID: PlayerID, x?: number, y?: number, + units?: UnitType[], ): PlayerActions { const player = this.game.player(playerID); const tile = x !== undefined && y !== undefined ? this.game.ref(x, y) : null; const actions = { - canAttack: tile !== null && player.canAttack(tile), - buildableUnits: player.buildableUnits(tile), + canAttack: tile !== null && units === undefined && player.canAttack(tile), + buildableUnits: player.buildableUnits(tile, units), canSendEmojiAllPlayers: player.canSendEmoji(AllPlayers), canEmbargoAll: player.canEmbargoAll(), } as PlayerActions; diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index 5e2f0a732..3897da573 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -627,7 +627,7 @@ export interface Player { unitCount(type: UnitType): number; unitsConstructed(type: UnitType): number; unitsOwned(type: UnitType): number; - buildableUnits(tile: TileRef | null): BuildableUnit[]; + buildableUnits(tile: TileRef | null, units?: UnitType[]): BuildableUnit[]; canBuild(type: UnitType, targetTile: TileRef): TileRef | false; buildUnit( type: T, diff --git a/src/core/game/GameView.ts b/src/core/game/GameView.ts index d66b9361b..07d2841e8 100644 --- a/src/core/game/GameView.ts +++ b/src/core/game/GameView.ts @@ -403,11 +403,12 @@ export class PlayerView { return { hasEmbargo, hasFriendly }; } - async actions(tile?: TileRef): Promise { + async actions(tile?: TileRef, units?: UnitType[]): Promise { return this.game.worker.playerInteraction( this.id(), tile && this.game.x(tile), tile && this.game.y(tile), + units, ); } diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts index d00948d44..e2005f248 100644 --- a/src/core/game/PlayerImpl.ts +++ b/src/core/game/PlayerImpl.ts @@ -22,6 +22,7 @@ import { EmojiMessage, GameMode, Gold, + isStructureType, MessageType, MutableAlliance, Player, @@ -960,31 +961,40 @@ export class PlayerImpl implements Player { this.recordUnitConstructed(unit.type()); } - public buildableUnits(tile: TileRef | null): BuildableUnit[] { - const validTiles = tile !== null ? this.validStructureSpawnTiles(tile) : []; - return Object.values(UnitType).map((u) => { - let canUpgrade: number | false = false; - let canBuild: TileRef | false = false; - if (!this.mg.inSpawnPhase()) { - const existingUnit = tile !== null && this.findUnitToUpgrade(u, tile); - if (existingUnit !== false) { - canUpgrade = existingUnit.id(); + public buildableUnits( + tile: TileRef | null, + units?: UnitType[], + ): BuildableUnit[] { + const validTiles = + tile !== null && + (units === undefined || units.some((u) => isStructureType(u))) + ? this.validStructureSpawnTiles(tile) + : []; + return Object.values(UnitType) + .filter((u) => units === undefined || units.includes(u)) + .map((u) => { + let canUpgrade: number | false = false; + let canBuild: TileRef | false = false; + if (!this.mg.inSpawnPhase()) { + const existingUnit = tile !== null && this.findUnitToUpgrade(u, tile); + if (existingUnit !== false) { + canUpgrade = existingUnit.id(); + } + if (tile !== null) { + canBuild = this.canBuild(u, tile, validTiles); + } } - if (tile !== null) { - canBuild = this.canBuild(u, tile, validTiles); - } - } - return { - type: u, - canBuild, - canUpgrade, - cost: this.mg.config().unitInfo(u).cost(this.mg, this), - overlappingRailroads: - canBuild !== false - ? this.mg.railNetwork().overlappingRailroads(canBuild) - : [], - } as BuildableUnit; - }); + return { + type: u, + canBuild, + canUpgrade, + cost: this.mg.config().unitInfo(u).cost(this.mg, this), + overlappingRailroads: + canBuild !== false + ? this.mg.railNetwork().overlappingRailroads(canBuild) + : [], + } as BuildableUnit; + }); } canBuild( diff --git a/src/core/worker/Worker.worker.ts b/src/core/worker/Worker.worker.ts index 31fd3f136..2aa52864f 100644 --- a/src/core/worker/Worker.worker.ts +++ b/src/core/worker/Worker.worker.ts @@ -94,6 +94,7 @@ ctx.addEventListener("message", async (e: MessageEvent) => { message.playerID, message.x, message.y, + message.units, ); sendMessage({ type: "player_actions_result", diff --git a/src/core/worker/WorkerClient.ts b/src/core/worker/WorkerClient.ts index fe0ac38fc..a5039e9d7 100644 --- a/src/core/worker/WorkerClient.ts +++ b/src/core/worker/WorkerClient.ts @@ -4,6 +4,7 @@ import { PlayerBorderTiles, PlayerID, PlayerProfile, + UnitType, } from "../game/Game"; import { TileRef } from "../game/GameMap"; import { ErrorUpdate, GameUpdateViewData } from "../game/GameUpdates"; @@ -164,6 +165,7 @@ export class WorkerClient { playerID: PlayerID, x?: number, y?: number, + units?: UnitType[], ): Promise { return new Promise((resolve, reject) => { if (!this.isInitialized) { @@ -185,9 +187,10 @@ export class WorkerClient { this.worker.postMessage({ type: "player_actions", id: messageId, - playerID: playerID, - x: x, - y: y, + playerID, + x, + y, + units, }); }); } diff --git a/src/core/worker/WorkerMessages.ts b/src/core/worker/WorkerMessages.ts index a8d30e9b1..795df5497 100644 --- a/src/core/worker/WorkerMessages.ts +++ b/src/core/worker/WorkerMessages.ts @@ -3,6 +3,7 @@ import { PlayerBorderTiles, PlayerID, PlayerProfile, + UnitType, } from "../game/Game"; import { TileRef } from "../game/GameMap"; import { GameUpdateViewData } from "../game/GameUpdates"; @@ -62,6 +63,7 @@ export interface PlayerActionsMessage extends BaseWorkerMessage { playerID: PlayerID; x?: number; y?: number; + units?: UnitType[]; } export interface PlayerActionsResultMessage extends BaseWorkerMessage {