import { LitElement, html } from "lit"; import { customElement, query, state } from "lit/decorators.js"; import randomMap from "../../resources/images/RandomMap.webp"; import { translateText } from "../client/Utils"; import { Difficulty, Duos, GameMapType, GameMode, GameType, UnitType, mapCategories, } from "../core/game/Game"; import { generateID } from "../core/Util"; import "./components/baseComponents/Button"; import "./components/baseComponents/Modal"; import "./components/Difficulties"; import { DifficultyDescription } from "./components/Difficulties"; import "./components/Maps"; import { FlagInput } from "./FlagInput"; import { JoinLobbyEvent } from "./Main"; import { UsernameInput } from "./UsernameInput"; import { renderUnitTypeOptions } from "./utilities/RenderUnitTypeOptions"; @customElement("single-player-modal") export class SinglePlayerModal extends LitElement { @query("o-modal") private modalEl!: HTMLElement & { open: () => void; close: () => void; }; @state() private selectedMap: GameMapType = GameMapType.World; @state() private selectedDifficulty: Difficulty = Difficulty.Medium; @state() private disableNPCs: boolean = false; @state() private bots: number = 400; @state() private infiniteGold: boolean = false; @state() private infiniteTroops: boolean = false; @state() private instantBuild: boolean = false; @state() private useRandomMap: boolean = false; @state() private gameMode: GameMode = GameMode.FFA; @state() private teamCount: number | typeof Duos = 2; @state() private disabledUnits: UnitType[] = []; render() { return html`
${translateText("map.map")}
${Object.entries(mapCategories).map( ([categoryKey, maps]) => html`

${translateText(`map_categories.${categoryKey}`)}

${maps.map((mapValue) => { const mapKey = Object.keys(GameMapType).find( (key) => GameMapType[key] === mapValue, ); return html`
this.handleMapSelection(mapValue)} >
`; })}
`, )}
Random Map
${translateText("map.random")}
${translateText("difficulty.difficulty")}
${Object.entries(Difficulty) .filter(([key]) => isNaN(Number(key))) .map( ([key, value]) => html`
this.handleDifficultySelection(value)} >

${translateText( `difficulty.${DifficultyDescription[key]}`, )}

`, )}
${translateText("host_modal.mode")}
this.handleGameModeSelection(GameMode.FFA)} >
${translateText("game_mode.ffa")}
this.handleGameModeSelection(GameMode.Team)} >
${translateText("game_mode.teams")}
${this.gameMode === GameMode.FFA ? "" : html`
${translateText("host_modal.team_count")}
${["Duos", 2, 3, 4, 5, 6, 7].map( (o) => html`
this.handleTeamCountSelection(o)} >
${o}
`, )}
`}
${translateText("single_modal.options_title")}

${translateText("single_modal.enables_title")}
${renderUnitTypeOptions({ disabledUnits: this.disabledUnits, toggleUnit: this.toggleUnit.bind(this), })}
`; } createRenderRoot() { return this; // light DOM } public open() { this.modalEl?.open(); this.useRandomMap = false; } public close() { this.modalEl?.close(); } private handleRandomMapToggle() { this.useRandomMap = true; } private handleMapSelection(value: GameMapType) { this.selectedMap = value; this.useRandomMap = false; } private handleDifficultySelection(value: Difficulty) { this.selectedDifficulty = value; } private handleBotsChange(e: Event) { const value = parseInt((e.target as HTMLInputElement).value); if (isNaN(value) || value < 0 || value > 400) { return; } this.bots = value; } private handleInstantBuildChange(e: Event) { this.instantBuild = Boolean((e.target as HTMLInputElement).checked); } private handleInfiniteGoldChange(e: Event) { this.infiniteGold = Boolean((e.target as HTMLInputElement).checked); } private handleInfiniteTroopsChange(e: Event) { this.infiniteTroops = Boolean((e.target as HTMLInputElement).checked); } private handleDisableNPCsChange(e: Event) { this.disableNPCs = Boolean((e.target as HTMLInputElement).checked); } private handleGameModeSelection(value: GameMode) { this.gameMode = value; } private handleTeamCountSelection(value: number | string) { this.teamCount = value === "Duos" ? Duos : Number(value); } private getRandomMap(): GameMapType { const maps = Object.values(GameMapType); const randIdx = Math.floor(Math.random() * maps.length); return maps[randIdx] as GameMapType; } private toggleUnit(unit: UnitType, checked: boolean): void { console.log(`Toggling unit type: ${unit} to ${checked}`); this.disabledUnits = checked ? [...this.disabledUnits, unit] : this.disabledUnits.filter((u) => u !== unit); } private startGame() { // If random map is selected, choose a random map now if (this.useRandomMap) { this.selectedMap = this.getRandomMap(); } console.log( `Starting single player game with map: ${GameMapType[this.selectedMap]}${this.useRandomMap ? " (Randomly selected)" : ""}`, ); const clientID = generateID(); const gameID = generateID(); const usernameInput = document.querySelector( "username-input", ) as UsernameInput; if (!usernameInput) { console.warn("Username input element not found"); } const flagInput = document.querySelector("flag-input") as FlagInput; if (!flagInput) { console.warn("Flag input element not found"); } this.dispatchEvent( new CustomEvent("join-lobby", { detail: { clientID: clientID, gameID: gameID, gameStartInfo: { gameID: gameID, players: [ { clientID, username: usernameInput.getCurrentUsername(), flag: flagInput.getCurrentFlag() === "xx" ? "" : flagInput.getCurrentFlag(), }, ], config: { gameMap: this.selectedMap, gameType: GameType.Singleplayer, gameMode: this.gameMode, playerTeams: this.teamCount, difficulty: this.selectedDifficulty, disableNPCs: this.disableNPCs, bots: this.bots, infiniteGold: this.infiniteGold, infiniteTroops: this.infiniteTroops, instantBuild: this.instantBuild, disabledUnits: this.disabledUnits .map((u) => Object.values(UnitType).find((ut) => ut === u)) .filter((ut): ut is UnitType => ut !== undefined), }, }, } satisfies JoinLobbyEvent, bubbles: true, composed: true, }), ); this.close(); } }