import { Config } from "../../../core/configuration/Config"; import { AllPlayers, Cell, PlayerActions, UnitType, } from "../../../core/game/Game"; import { TileRef } from "../../../core/game/GameMap"; import { GameView, PlayerView } from "../../../core/game/GameView"; import { flattenedEmojiTable } from "../../../core/Util"; import { renderNumber, translateText } from "../../Utils"; import { BuildItemDisplay, BuildMenu, flattenedBuildTable } from "./BuildMenu"; import { ChatIntegration } from "./ChatIntegration"; import { EmojiTable } from "./EmojiTable"; import { PlayerActionHandler } from "./PlayerActionHandler"; import { PlayerPanel } from "./PlayerPanel"; import { TooltipItem } from "./RadialMenu"; import allianceIcon from "../../../../resources/images/AllianceIconWhite.svg"; import boatIcon from "../../../../resources/images/BoatIconWhite.svg"; import buildIcon from "../../../../resources/images/BuildIconWhite.svg"; import chatIcon from "../../../../resources/images/ChatIconWhite.svg"; import donateGoldIcon from "../../../../resources/images/DonateGoldIconWhite.svg"; import donateTroopIcon from "../../../../resources/images/DonateTroopIconWhite.svg"; import emojiIcon from "../../../../resources/images/EmojiIconWhite.svg"; import infoIcon from "../../../../resources/images/InfoIcon.svg"; import targetIcon from "../../../../resources/images/TargetIconWhite.svg"; import traitorIcon from "../../../../resources/images/TraitorIconWhite.svg"; import { EventBus } from "../../../core/EventBus"; export interface MenuElementParams { myPlayer: PlayerView; selected: PlayerView | null; tile: TileRef; playerActions: PlayerActions; game: GameView; buildMenu: BuildMenu; emojiTable: EmojiTable; playerActionHandler: PlayerActionHandler; playerPanel: PlayerPanel; chatIntegration: ChatIntegration; eventBus: EventBus; closeMenu: () => void; } export interface MenuElement { id: string; name: string; displayed?: boolean | ((params: MenuElementParams) => boolean); color?: string; icon?: string; text?: string; fontSize?: string; tooltipItems?: TooltipItem[]; disabled: (params: MenuElementParams) => boolean; action?: (params: MenuElementParams) => void; // For leaf items that perform actions subMenu?: (params: MenuElementParams) => MenuElement[]; // For non-leaf items that open submenus } export interface CenterButtonElement { disabled: (params: MenuElementParams) => boolean; action: (params: MenuElementParams) => void; } export const COLORS = { build: "#ebe250", building: "#2c2c2c", boat: "#3f6ab1", ally: "#53ac75", breakAlly: "#c74848", info: "#64748B", target: "#ff0000", infoDetails: "#7f8c8d", infoEmoji: "#f1c40f", trade: "#008080", embargo: "#6600cc", tooltip: { cost: "#ffd700", count: "#aaa", }, chat: { default: "#66c", help: "#4caf50", attack: "#f44336", defend: "#2196f3", greet: "#ff9800", misc: "#9c27b0", warnings: "#e3c532", }, }; export enum Slot { Info = "info", Boat = "boat", Build = "build", Ally = "ally", Back = "back", } const infoChatElement: MenuElement = { id: "info_chat", name: "chat", disabled: () => false, color: COLORS.chat.default, icon: chatIcon, subMenu: (params: MenuElementParams) => params.chatIntegration .createQuickChatMenu(params.selected!) .map((item) => ({ ...item, action: item.action ? (_params: MenuElementParams) => item.action!(params) : undefined, })), }; const allyTargetElement: MenuElement = { id: "ally_target", name: "target", disabled: (params: MenuElementParams): boolean => { if (params.selected === null) return true; return !params.playerActions.interaction?.canTarget; }, color: COLORS.target, icon: targetIcon, action: (params: MenuElementParams) => { params.playerActionHandler.handleTargetPlayer(params.selected!.id()); params.closeMenu(); }, }; const allyTradeElement: MenuElement = { id: "ally_trade", name: "trade", disabled: (params: MenuElementParams) => !!params.playerActions?.interaction?.canEmbargo, displayed: (params: MenuElementParams) => !params.playerActions?.interaction?.canEmbargo, color: COLORS.trade, text: translateText("player_panel.start_trade"), action: (params: MenuElementParams) => { params.playerActionHandler.handleEmbargo(params.selected!, "stop"); params.closeMenu(); }, }; const allyEmbargoElement: MenuElement = { id: "ally_embargo", name: "embargo", disabled: (params: MenuElementParams) => !params.playerActions?.interaction?.canEmbargo, displayed: (params: MenuElementParams) => !!params.playerActions?.interaction?.canEmbargo, color: COLORS.embargo, text: translateText("player_panel.stop_trade"), action: (params: MenuElementParams) => { params.playerActionHandler.handleEmbargo(params.selected!, "start"); params.closeMenu(); }, }; const allyRequestElement: MenuElement = { id: "ally_request", name: "request", disabled: (params: MenuElementParams) => !params.playerActions?.interaction?.canSendAllianceRequest, displayed: (params: MenuElementParams) => !params.playerActions?.interaction?.canBreakAlliance, color: COLORS.ally, icon: allianceIcon, action: (params: MenuElementParams) => { params.playerActionHandler.handleAllianceRequest( params.myPlayer, params.selected!, ); params.closeMenu(); }, }; const allyBreakElement: MenuElement = { id: "ally_break", name: "break", disabled: (params: MenuElementParams) => !params.playerActions?.interaction?.canBreakAlliance, displayed: (params: MenuElementParams) => !!params.playerActions?.interaction?.canBreakAlliance, color: COLORS.breakAlly, icon: traitorIcon, action: (params: MenuElementParams) => { params.playerActionHandler.handleBreakAlliance( params.myPlayer, params.selected!, ); params.closeMenu(); }, }; const allyDonateGoldElement: MenuElement = { id: "ally_donate_gold", name: "donate gold", disabled: (params: MenuElementParams) => !params.playerActions?.interaction?.canDonate, color: COLORS.ally, icon: donateGoldIcon, action: (params: MenuElementParams) => { params.playerActionHandler.handleDonateGold(params.selected!); params.closeMenu(); }, }; const allyDonateTroopsElement: MenuElement = { id: "ally_donate_troops", name: "donate troops", disabled: (params: MenuElementParams) => !params.playerActions?.interaction?.canDonate, color: COLORS.ally, icon: donateTroopIcon, action: (params: MenuElementParams) => { params.playerActionHandler.handleDonateTroops(params.selected!); params.closeMenu(); }, }; const infoPlayerElement: MenuElement = { id: "info_player", name: "player", disabled: () => false, color: COLORS.info, icon: infoIcon, action: (params: MenuElementParams) => { params.playerPanel.show(params.playerActions, params.tile); }, }; const infoEmojiElement: MenuElement = { id: "info_emoji", name: "emoji", disabled: () => false, color: COLORS.infoEmoji, icon: emojiIcon, subMenu: (params: MenuElementParams) => { const emojiElements: MenuElement[] = [ { id: "emoji_more", name: "more", disabled: () => false, color: COLORS.infoEmoji, icon: emojiIcon, action: (params: MenuElementParams) => { params.emojiTable.showTable((emoji) => { const targetPlayer = params.selected === params.game.myPlayer() ? AllPlayers : params.selected; params.playerActionHandler.handleEmoji( targetPlayer!, flattenedEmojiTable.indexOf(emoji), ); params.emojiTable.hideTable(); }); }, }, ]; const emojiCount = 8; for (let i = 0; i < emojiCount; i++) { emojiElements.push({ id: `emoji_${i}`, name: flattenedEmojiTable[i], text: flattenedEmojiTable[i], disabled: () => false, fontSize: "25px", action: (params: MenuElementParams) => { const targetPlayer = params.selected === params.game.myPlayer() ? AllPlayers : params.selected; params.playerActionHandler.handleEmoji(targetPlayer!, i); params.closeMenu(); }, }); } return emojiElements; }, }; export const infoMenuElement: MenuElement = { id: Slot.Info, name: "info", disabled: (params: MenuElementParams) => !params.selected || params.game.inSpawnPhase(), icon: infoIcon, color: COLORS.info, action: (params: MenuElementParams) => { params.playerPanel.show(params.playerActions, params.tile); }, }; function getAllEnabledUnits(myPlayer: boolean, config: Config): Set { const Units: Set = new Set(); const addStructureIfEnabled = (unitType: UnitType) => { if (!config.isUnitDisabled(unitType)) { Units.add(unitType); } }; if (myPlayer) { addStructureIfEnabled(UnitType.City); addStructureIfEnabled(UnitType.DefensePost); addStructureIfEnabled(UnitType.Port); addStructureIfEnabled(UnitType.MissileSilo); addStructureIfEnabled(UnitType.SAMLauncher); addStructureIfEnabled(UnitType.Factory); } else { addStructureIfEnabled(UnitType.Warship); addStructureIfEnabled(UnitType.HydrogenBomb); addStructureIfEnabled(UnitType.MIRV); addStructureIfEnabled(UnitType.AtomBomb); } return Units; } export const buildMenuElement: MenuElement = { id: Slot.Build, name: "build", disabled: (params: MenuElementParams) => params.game.inSpawnPhase(), icon: buildIcon, color: COLORS.build, subMenu: (params: MenuElementParams) => { if (params === undefined) return []; const unitTypes: Set = getAllEnabledUnits( params.selected === params.myPlayer, params.game.config(), ); const buildElements: MenuElement[] = flattenedBuildTable .filter((item) => unitTypes.has(item.unitType)) .map((item: BuildItemDisplay) => ({ id: `build_${item.unitType}`, name: item.key ? item.key.replace("unit_type.", "") : item.unitType.toString(), disabled: (params: MenuElementParams) => !params.buildMenu.canBuildOrUpgrade(item), color: params.buildMenu.canBuildOrUpgrade(item) ? COLORS.building : undefined, icon: item.icon, tooltipItems: [ { text: translateText(item.key || ""), className: "title" }, { text: translateText(item.description || ""), className: "description", }, { text: `${renderNumber(params.buildMenu.cost(item))} ${translateText("player_panel.gold")}`, className: "cost", }, item.countable ? { text: `${params.buildMenu.count(item)}x`, className: "count" } : null, ].filter((item): item is TooltipItem => item !== null), action: (params: MenuElementParams) => { const buildableUnit = params.playerActions.buildableUnits.find( (bu) => bu.type === item.unitType, ); if (buildableUnit === undefined) { return; } if (params.buildMenu.canBuildOrUpgrade(item)) { params.buildMenu.sendBuildOrUpgrade(buildableUnit, params.tile); } params.closeMenu(); }, })); return buildElements; }, }; export const boatMenuElement: MenuElement = { id: Slot.Boat, name: "boat", disabled: (params: MenuElementParams) => !params.playerActions.buildableUnits.some( (unit) => unit.type === UnitType.TransportShip && unit.canBuild, ), icon: boatIcon, color: COLORS.boat, action: async (params: MenuElementParams) => { const spawn = await params.playerActionHandler.findBestTransportShipSpawn( params.myPlayer, params.tile, ); params.playerActionHandler.handleBoatAttack( params.myPlayer, params.selected?.id() || null, params.tile, spawn !== false ? spawn : null, ); params.closeMenu(); }, }; export const centerButtonElement: CenterButtonElement = { disabled: (params: MenuElementParams): boolean => { const tileOwner = params.game.owner(params.tile); const isLand = params.game.isLand(params.tile); if (!isLand) { return true; } if (params.game.inSpawnPhase()) { if (tileOwner.isPlayer()) { return true; } return false; } return false; }, action: (params: MenuElementParams) => { if (params.game.inSpawnPhase()) { const cell = new Cell( params.game.x(params.tile), params.game.y(params.tile), ); params.playerActionHandler.handleSpawn(cell); } else { params.playerActionHandler.handleAttack( params.myPlayer, params.selected?.id() ?? null, ); } params.closeMenu(); }, }; export const rootMenuItems: MenuElement[] = [ infoMenuElement, boatMenuElement, buildMenuElement, ];