import { html, LitElement } from "lit"; import { customElement } from "lit/decorators.js"; import { EventBus } from "../../../core/EventBus"; import { BuildableUnit, BuildMenus, Gold, PlayerBuildableUnitType, UnitType, } from "../../../core/game/Game"; import { GameView } from "../../../core/game/GameView"; import { GhostStructureChangedEvent, ToggleStructureEvent, } from "../../InputHandler"; import { renderNumber, translateText } from "../../Utils"; import { UIState } from "../UIState"; import { Layer } from "./Layer"; import warshipIcon from "/images/BattleshipIconWhite.svg?url"; import cityIcon from "/images/CityIconWhite.svg?url"; import factoryIcon from "/images/FactoryIconWhite.svg?url"; import goldCoinIcon from "/images/GoldCoinIcon.svg?url"; import mirvIcon from "/images/MIRVIcon.svg?url"; import missileSiloIcon from "/images/MissileSiloIconWhite.svg?url"; import hydrogenBombIcon from "/images/MushroomCloudIconWhite.svg?url"; import atomBombIcon from "/images/NukeIconWhite.svg?url"; import portIcon from "/images/PortIcon.svg?url"; import samLauncherIcon from "/images/SamLauncherIconWhite.svg?url"; import defensePostIcon from "/images/ShieldIconWhite.svg?url"; @customElement("unit-display") export class UnitDisplay extends LitElement implements Layer { public game: GameView; public eventBus: EventBus; public uiState: UIState; private playerBuildables: BuildableUnit[] | null = null; private keybinds: Record = {}; private _cities = 0; private _warships = 0; private _factories = 0; private _missileSilo = 0; private _port = 0; private _defensePost = 0; private _samLauncher = 0; private allDisabled = false; private _hoveredUnit: PlayerBuildableUnitType | null = null; createRenderRoot() { return this; } init() { const config = this.game.config(); const savedKeybinds = localStorage.getItem("settings.keybinds"); if (savedKeybinds) { try { this.keybinds = JSON.parse(savedKeybinds); } catch (e) { console.warn("Invalid keybinds JSON:", e); } } this.allDisabled = BuildMenus.types.every((u) => config.isUnitDisabled(u)); this.requestUpdate(); } private cost(item: UnitType): Gold { for (const bu of this.playerBuildables ?? []) { if (bu.type === item) { return bu.cost; } } return 0n; } private canBuild(item: UnitType): boolean { if (this.game?.config().isUnitDisabled(item)) return false; const player = this.game?.myPlayer(); switch (item) { case UnitType.AtomBomb: case UnitType.HydrogenBomb: case UnitType.MIRV: return ( this.cost(item) <= (player?.gold() ?? 0n) && (player?.units(UnitType.MissileSilo).length ?? 0) > 0 ); case UnitType.Warship: return ( this.cost(item) <= (player?.gold() ?? 0n) && (player?.units(UnitType.Port).length ?? 0) > 0 ); default: return this.cost(item) <= (player?.gold() ?? 0n); } } tick() { const player = this.game?.myPlayer(); if (!player) return; player.buildables(undefined, BuildMenus.types).then((buildables) => { this.playerBuildables = buildables; }); this._cities = player.totalUnitLevels(UnitType.City); this._missileSilo = player.totalUnitLevels(UnitType.MissileSilo); this._port = player.totalUnitLevels(UnitType.Port); this._defensePost = player.totalUnitLevels(UnitType.DefensePost); this._samLauncher = player.totalUnitLevels(UnitType.SAMLauncher); this._factories = player.totalUnitLevels(UnitType.Factory); this._warships = player.totalUnitLevels(UnitType.Warship); this.requestUpdate(); } render() { const myPlayer = this.game?.myPlayer(); if ( !this.game || !myPlayer || this.game.inSpawnPhase() || !myPlayer.isAlive() ) { return null; } if (this.allDisabled) { return null; } return html`
${this.renderUnitItem( cityIcon, this._cities, UnitType.City, "city", this.keybinds["buildCity"]?.key ?? "1", )} ${this.renderUnitItem( factoryIcon, this._factories, UnitType.Factory, "factory", this.keybinds["buildFactory"]?.key ?? "2", )} ${this.renderUnitItem( portIcon, this._port, UnitType.Port, "port", this.keybinds["buildPort"]?.key ?? "3", )} ${this.renderUnitItem( defensePostIcon, this._defensePost, UnitType.DefensePost, "defense_post", this.keybinds["buildDefensePost"]?.key ?? "4", )} ${this.renderUnitItem( missileSiloIcon, this._missileSilo, UnitType.MissileSilo, "missile_silo", this.keybinds["buildMissileSilo"]?.key ?? "5", )} ${this.renderUnitItem( samLauncherIcon, this._samLauncher, UnitType.SAMLauncher, "sam_launcher", this.keybinds["buildSamLauncher"]?.key ?? "6", )} ${this.renderUnitItem( warshipIcon, this._warships, UnitType.Warship, "warship", this.keybinds["buildWarship"]?.key ?? "7", )} ${this.renderUnitItem( atomBombIcon, null, UnitType.AtomBomb, "atom_bomb", this.keybinds["buildAtomBomb"]?.key ?? "8", )} ${this.renderUnitItem( hydrogenBombIcon, null, UnitType.HydrogenBomb, "hydrogen_bomb", this.keybinds["buildHydrogenBomb"]?.key ?? "9", )} ${this.renderUnitItem( mirvIcon, null, UnitType.MIRV, "mirv", this.keybinds["buildMIRV"]?.key ?? "0", )}
`; } private renderUnitItem( icon: string, number: number | null, unitType: PlayerBuildableUnitType, structureKey: string, hotkey: string, ) { if (this.game.config().isUnitDisabled(unitType)) { return html``; } const selected = this.uiState.ghostStructure === unitType; const hovered = this._hoveredUnit === unitType; const displayHotkey = hotkey .replace("Digit", "") .replace("Key", "") .toUpperCase(); return html`
{ this._hoveredUnit = unitType; this.requestUpdate(); }} @mouseleave=${() => { this._hoveredUnit = null; this.requestUpdate(); }} > ${hovered ? html`
${translateText( "unit_type." + structureKey, )}${` [${displayHotkey}]`}
${translateText("build_menu.desc." + structureKey)}
${renderNumber(this.cost(unitType))}
` : null}
{ if (selected) { this.uiState.ghostStructure = null; this.eventBus?.emit(new GhostStructureChangedEvent(null)); } else if (this.canBuild(unitType)) { this.uiState.ghostStructure = unitType; this.eventBus?.emit(new GhostStructureChangedEvent(unitType)); } this.requestUpdate(); }} @mouseenter=${() => { switch (unitType) { case UnitType.AtomBomb: case UnitType.HydrogenBomb: this.eventBus?.emit( new ToggleStructureEvent([ UnitType.MissileSilo, UnitType.SAMLauncher, ]), ); break; case UnitType.Warship: this.eventBus?.emit(new ToggleStructureEvent([UnitType.Port])); break; default: this.eventBus?.emit(new ToggleStructureEvent([unitType])); } }} @mouseleave=${() => this.eventBus?.emit(new ToggleStructureEvent(null))} > ${html`
${displayHotkey}
`}
${structureKey} ${number !== null ? html`${renderNumber(number)}` : null}
`; } }