import { LitElement, html } from "lit"; import { customElement, query, state } from "lit/decorators.js"; import { translateText } from "../client/Utils"; import "./components/Difficulties"; import "./components/Maps"; @customElement("help-modal") export class HelpModal extends LitElement { @query("o-modal") private modalEl!: HTMLElement & { open: () => void; close: () => void; }; @state() private keybinds: Record = this.getKeybinds(); createRenderRoot() { return this; } connectedCallback() { super.connectedCallback(); window.addEventListener("keydown", this.handleKeyDown); } disconnectedCallback() { window.removeEventListener("keydown", this.handleKeyDown); super.disconnectedCallback(); } private isKeybindObject(v: unknown): v is { value: string } { return ( typeof v === "object" && v !== null && "value" in v && typeof (v as any).value === "string" ); } private handleKeyDown = (e: KeyboardEvent) => { if (e.code === "Escape") { e.preventDefault(); this.close(); } }; private getKeybinds(): Record { let saved: Record = {}; try { const parsed = JSON.parse( localStorage.getItem("settings.keybinds") ?? "{}", ); saved = Object.fromEntries( Object.entries(parsed) .map(([k, v]) => { if (this.isKeybindObject(v)) return [k, v.value]; if (typeof v === "string") return [k, v]; return [k, undefined]; }) .filter(([, v]) => typeof v === "string" && v !== "Null"), ) as Record; } catch (e) { console.warn("Invalid keybinds JSON:", e); } const isMac = /Mac/.test(navigator.userAgent); return { toggleView: "Space", centerCamera: "KeyC", moveUp: "KeyW", moveDown: "KeyS", moveLeft: "KeyA", moveRight: "KeyD", zoomOut: "KeyQ", zoomIn: "KeyE", attackRatioDown: "KeyT", attackRatioUp: "KeyY", shiftKey: "ShiftLeft", modifierKey: isMac ? "MetaLeft" : "ControlLeft", altKey: "AltLeft", resetGfx: "KeyR", ...saved, }; } private getKeyLabel(code: string): string { if (!code) return ""; const specialLabels: Record = { ShiftLeft: "⇧ Shift", ShiftRight: "⇧ Shift", ControlLeft: "Ctrl", ControlRight: "Ctrl", AltLeft: "Alt", AltRight: "Alt", MetaLeft: "⌘", MetaRight: "⌘", Space: "Space", ArrowUp: "↑", ArrowDown: "↓", ArrowLeft: "←", ArrowRight: "→", }; if (specialLabels[code]) return specialLabels[code]; if (code.startsWith("Key") && code.length === 4) return code.slice(3); if (code.startsWith("Digit")) return code.slice(5); if (code.startsWith("Numpad")) return `Num ${code.slice(6)}`; return code; } private renderKey(code: string) { const label = this.getKeyLabel(code); return html`${label}`; } render() { const keybinds = this.keybinds; return html`
${translateText("help_modal.hotkeys")}
${translateText("help_modal.table_key")} ${translateText("help_modal.table_action")}
${this.renderKey(keybinds.toggleView)} ${translateText("help_modal.action_alt_view")}
U ${translateText("help_modal.bomb_direction")}
${this.renderKey(keybinds.shiftKey)} +
${translateText("help_modal.action_attack_altclick")}
${this.renderKey(keybinds.modifierKey)} +
${translateText("help_modal.action_build")}
${this.renderKey(keybinds.altKey)} +
${translateText("help_modal.action_emote")}
${this.renderKey(keybinds.centerCamera)} ${translateText("help_modal.action_center")}
${this.renderKey(keybinds.zoomOut)} / ${this.renderKey(keybinds.zoomIn)} ${translateText("help_modal.action_zoom")}
${this.renderKey(keybinds.moveUp)} ${this.renderKey(keybinds.moveLeft)} ${this.renderKey(keybinds.moveDown)} ${this.renderKey(keybinds.moveRight)} ${translateText("help_modal.action_move_camera")}
${this.renderKey(keybinds.attackRatioDown)} / ${this.renderKey(keybinds.attackRatioUp)} ${translateText("help_modal.action_ratio_change")}
${this.renderKey(keybinds.shiftKey)} +
${translateText("help_modal.action_ratio_change")}
${this.renderKey(keybinds.altKey)} + ${this.renderKey(keybinds.resetGfx)} ${translateText("help_modal.action_reset_gfx")}
${translateText("help_modal.action_auto_upgrade")}

${translateText("help_modal.ui_section")}
${translateText("help_modal.ui_leaderboard")}
Leaderboard

${translateText("help_modal.ui_leaderboard_desc")}


${translateText("help_modal.ui_control")}
Control panel

${translateText("help_modal.ui_control_desc")}

  • ${translateText("help_modal.ui_gold")}
  • ${translateText("help_modal.ui_attack_ratio")}

${translateText("help_modal.ui_events")}
Event panel Event panel

${translateText("help_modal.ui_events_desc")}

  • ${translateText("help_modal.ui_events_alliance")}
  • ${translateText("help_modal.ui_events_attack")}
  • ${translateText("help_modal.ui_events_quickchat")}

${translateText("help_modal.ui_options")}
Options

${translateText("help_modal.ui_options_desc")}

  • ${translateText("help_modal.option_pause")}
  • ${translateText("help_modal.option_timer")}
  • ${translateText("help_modal.option_exit")}
  • ${translateText("help_modal.option_settings")}

${translateText("help_modal.ui_playeroverlay")}
Player info overlay

${translateText("help_modal.ui_playeroverlay_desc")}


${translateText("help_modal.radial_title")}
Radial menu Radial menu ally

${translateText("help_modal.radial_desc")}

  • ${translateText("help_modal.radial_build")}
  • ${translateText("help_modal.radial_info")}
  • ${translateText("help_modal.radial_boat")}
  • ${translateText("help_modal.info_alliance")}
  • ${translateText("help_modal.ally_betray")}
  • ${translateText("help_modal.radial_donate_troops")}
  • ${translateText("help_modal.radial_donate_gold")}

${translateText("help_modal.info_title")}
${translateText("help_modal.info_enemy_panel")}
Enemy info panel

${translateText("help_modal.info_enemy_desc")}

  • ${translateText("help_modal.info_chat")}
  • ${translateText("help_modal.info_target")}
  • ${translateText("help_modal.info_alliance")}
  • ${translateText("help_modal.info_emoji")}
  • ${translateText("help_modal.info_trade")}

${translateText("help_modal.info_ally_panel")}
Ally info panel

${translateText("help_modal.info_ally_desc")}

  • ${translateText("help_modal.ally_betray")}
  • ${translateText("help_modal.ally_donate")}
  • ${translateText("help_modal.ally_donate_gold")}

${translateText("help_modal.build_menu_title")}

${translateText("help_modal.build_menu_desc")}

${translateText("help_modal.build_name")} ${translateText("help_modal.build_icon")} ${translateText("help_modal.build_desc")}
${translateText("help_modal.build_city")}
${translateText("help_modal.build_city_desc")}
${translateText("help_modal.build_defense")}
${translateText("help_modal.build_defense_desc")}
${translateText("help_modal.build_port")}
${translateText("help_modal.build_port_desc")}
${translateText("help_modal.build_factory")}
${translateText("help_modal.build_factory_desc")}
${translateText("help_modal.build_warship")}
${translateText("help_modal.build_warship_desc")}
${translateText("help_modal.build_silo")}
${translateText("help_modal.build_silo_desc")}
${translateText("help_modal.build_sam")}
${translateText("help_modal.build_sam_desc")}
${translateText("help_modal.build_atom")}
${translateText("help_modal.build_atom_desc")}
${translateText("help_modal.build_hydrogen")}
${translateText("help_modal.build_hydrogen_desc")}
${translateText("help_modal.build_mirv")}
${translateText("help_modal.build_mirv_desc")}

${translateText("help_modal.player_icons")}

${translateText("help_modal.icon_desc")}

${translateText("help_modal.icon_crown")}
Number 1 player
${translateText("help_modal.icon_traitor")}
Traitor
${translateText("help_modal.icon_ally")}
Ally
${translateText("help_modal.icon_embargo")}
Stopped trading
${translateText("help_modal.icon_request")}
Alliance Request
`; } public open() { this.keybinds = this.getKeybinds(); this.modalEl?.open(); } public close() { this.modalEl?.close(); } }