mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-22 17:26:42 +00:00
682 lines
20 KiB
TypeScript
682 lines
20 KiB
TypeScript
import { assetUrl } from "../../../core/AssetUrls";
|
|
import { Config } from "../../../core/configuration/Config";
|
|
import {
|
|
AllPlayers,
|
|
BuildableAttacks,
|
|
PlayerActions,
|
|
PlayerBuildableUnitType,
|
|
Structures,
|
|
UnitType,
|
|
} from "../../../core/game/Game";
|
|
import { TileRef } from "../../../core/game/GameMap";
|
|
import { GameView, PlayerView } from "../../../core/game/GameView";
|
|
import { Emoji, findClosestBy, flattenedEmojiTable } from "../../../core/Util";
|
|
import { renderNumber, translateText } from "../../Utils";
|
|
import { UIState } from "../UIState";
|
|
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 { EventBus } from "../../../core/EventBus";
|
|
const allianceIcon = assetUrl("images/AllianceIconWhite.svg");
|
|
const boatIcon = assetUrl("images/BoatIconWhite.svg");
|
|
const buildIcon = assetUrl("images/BuildIconWhite.svg");
|
|
const chatIcon = assetUrl("images/ChatIconWhite.svg");
|
|
const donateGoldIcon = assetUrl("images/DonateGoldIconWhite.svg");
|
|
const donateTroopIcon = assetUrl("images/DonateTroopIconWhite.svg");
|
|
const emojiIcon = assetUrl("images/EmojiIconWhite.svg");
|
|
const infoIcon = assetUrl("images/InfoIcon.svg");
|
|
const swordIcon = assetUrl("images/SwordIconWhite.svg");
|
|
const targetIcon = assetUrl("images/TargetIconWhite.svg");
|
|
const traitorIcon = assetUrl("images/TraitorIconWhite.svg");
|
|
const xIcon = assetUrl("images/XIcon.svg");
|
|
|
|
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;
|
|
uiState?: UIState;
|
|
closeMenu: () => void;
|
|
}
|
|
|
|
export interface MenuElement {
|
|
id: string;
|
|
name: string;
|
|
displayed?: boolean | ((params: MenuElementParams) => boolean);
|
|
color?: string | ((params: MenuElementParams) => string);
|
|
icon?: string;
|
|
text?: string;
|
|
fontSize?: string;
|
|
tooltipItems?: TooltipItem[];
|
|
tooltipKeys?: TooltipKey[];
|
|
|
|
cooldown?: (params: MenuElementParams) => number;
|
|
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
|
|
|
|
renderType?: string;
|
|
|
|
timerFraction?: (params: MenuElementParams) => number; // 0..1, for arc timer overlay
|
|
}
|
|
|
|
export interface TooltipKey {
|
|
key: string;
|
|
className: string;
|
|
params?: Record<string, string | number>;
|
|
}
|
|
|
|
export interface CenterButtonElement {
|
|
disabled: (params: MenuElementParams) => boolean;
|
|
action: (params: MenuElementParams) => void;
|
|
}
|
|
|
|
export const COLORS = {
|
|
build: "#e6c74a",
|
|
building: "#1e3a5f",
|
|
boat: "#2a82c9",
|
|
ally: "#4ade80",
|
|
breakAlly: "#dc2626",
|
|
breakAllyNoDebuff: "#d97706",
|
|
delete: "#ef4444",
|
|
info: "#475569",
|
|
target: "#ef4444",
|
|
attack: "#ef4444",
|
|
infoDetails: "#7f8c8d",
|
|
infoEmoji: "#fbbf24",
|
|
trade: "#0891b2",
|
|
embargo: "#7c3aed",
|
|
tooltip: {
|
|
cost: "#f59e0b",
|
|
count: "#94a3b8",
|
|
},
|
|
chat: {
|
|
default: "#6366f1",
|
|
help: "#22c55e",
|
|
attack: "#ef4444",
|
|
defend: "#3b82f6",
|
|
greet: "#f97316",
|
|
misc: "#a855f7",
|
|
warnings: "#fbbf24",
|
|
},
|
|
};
|
|
|
|
export enum Slot {
|
|
Info = "info",
|
|
Boat = "boat",
|
|
Build = "build",
|
|
Attack = "attack",
|
|
Ally = "ally",
|
|
Back = "back",
|
|
Delete = "delete",
|
|
}
|
|
|
|
function isFriendlyTarget(params: MenuElementParams): boolean {
|
|
const selectedPlayer = params.selected;
|
|
if (selectedPlayer === null) return false;
|
|
const isFriendly = (selectedPlayer as PlayerView).isFriendly;
|
|
if (typeof isFriendly !== "function") return false;
|
|
return isFriendly.call(selectedPlayer, params.myPlayer);
|
|
}
|
|
|
|
function isDisconnectedTarget(params: MenuElementParams): boolean {
|
|
const selectedPlayer = params.selected;
|
|
if (selectedPlayer === null) return false;
|
|
const isDisconnected = (selectedPlayer as PlayerView).isDisconnected;
|
|
if (typeof isDisconnected !== "function") return false;
|
|
return isDisconnected.call(selectedPlayer);
|
|
}
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
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,
|
|
})),
|
|
};
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
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();
|
|
},
|
|
};
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
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();
|
|
},
|
|
};
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
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 allyExtendElement: MenuElement = {
|
|
id: "ally_extend",
|
|
name: "extend",
|
|
displayed: (params: MenuElementParams) =>
|
|
!!params.playerActions?.interaction?.allianceInfo?.inExtensionWindow,
|
|
disabled: (params: MenuElementParams) =>
|
|
!params.playerActions?.interaction?.allianceInfo?.canExtend,
|
|
color: COLORS.ally,
|
|
icon: allianceIcon,
|
|
action: (params: MenuElementParams) => {
|
|
if (!params.playerActions?.interaction?.allianceInfo?.canExtend) return;
|
|
params.playerActionHandler.handleExtendAlliance(params.selected!);
|
|
params.closeMenu();
|
|
},
|
|
timerFraction: (params: MenuElementParams): number => {
|
|
const interaction = params.playerActions?.interaction;
|
|
if (!interaction?.allianceInfo) return 1;
|
|
const remaining = Math.max(
|
|
0,
|
|
interaction.allianceInfo.expiresAt - params.game.ticks(),
|
|
);
|
|
const extensionWindow = Math.max(
|
|
1,
|
|
params.game.config().allianceExtensionPromptOffset(),
|
|
);
|
|
return Math.max(0, Math.min(1, remaining / extensionWindow));
|
|
},
|
|
renderType: "allyExtend",
|
|
};
|
|
|
|
const allyBreakElement: MenuElement = {
|
|
id: "ally_break",
|
|
name: "break",
|
|
disabled: (params: MenuElementParams) =>
|
|
!params.playerActions?.interaction?.canBreakAlliance,
|
|
displayed: (params: MenuElementParams) =>
|
|
!!params.playerActions?.interaction?.canBreakAlliance,
|
|
color: (params: MenuElementParams) =>
|
|
params.selected?.isTraitor() || params.selected?.isDisconnected()
|
|
? COLORS.breakAllyNoDebuff
|
|
: COLORS.breakAlly,
|
|
icon: traitorIcon,
|
|
action: (params: MenuElementParams) => {
|
|
params.playerActionHandler.handleBreakAlliance(
|
|
params.myPlayer,
|
|
params.selected!,
|
|
);
|
|
params.closeMenu();
|
|
},
|
|
};
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
const allyDonateGoldElement: MenuElement = {
|
|
id: "ally_donate_gold",
|
|
name: "donate gold",
|
|
disabled: (params: MenuElementParams) =>
|
|
!params.playerActions?.interaction?.canDonateGold,
|
|
color: COLORS.ally,
|
|
icon: donateGoldIcon,
|
|
action: (params: MenuElementParams) => {
|
|
params.playerActionHandler.handleDonateGold(params.selected!);
|
|
params.closeMenu();
|
|
},
|
|
};
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
const allyDonateTroopsElement: MenuElement = {
|
|
id: "ally_donate_troops",
|
|
name: "donate troops",
|
|
disabled: (params: MenuElementParams) =>
|
|
!params.playerActions?.interaction?.canDonateTroops,
|
|
color: COLORS.ally,
|
|
icon: donateTroopIcon,
|
|
action: (params: MenuElementParams) => {
|
|
params.playerActionHandler.handleDonateTroops(params.selected!);
|
|
params.closeMenu();
|
|
},
|
|
};
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
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);
|
|
},
|
|
};
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
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 as 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<PlayerBuildableUnitType> {
|
|
const units: Set<PlayerBuildableUnitType> =
|
|
new Set<PlayerBuildableUnitType>();
|
|
|
|
const addIfEnabled = (unitType: PlayerBuildableUnitType) => {
|
|
if (!config.isUnitDisabled(unitType)) {
|
|
units.add(unitType);
|
|
}
|
|
};
|
|
|
|
if (myPlayer) {
|
|
Structures.types.forEach(addIfEnabled);
|
|
} else {
|
|
BuildableAttacks.types.forEach(addIfEnabled);
|
|
}
|
|
|
|
return units;
|
|
}
|
|
|
|
function createMenuElements(
|
|
params: MenuElementParams,
|
|
filterType: "attack" | "build",
|
|
elementIdPrefix: string,
|
|
): MenuElement[] {
|
|
const unitTypes: Set<PlayerBuildableUnitType> = getAllEnabledUnits(
|
|
params.selected === params.myPlayer,
|
|
params.game.config(),
|
|
);
|
|
|
|
return flattenedBuildTable
|
|
.filter(
|
|
(item) =>
|
|
unitTypes.has(item.unitType) &&
|
|
(filterType === "attack"
|
|
? BuildableAttacks.has(item.unitType)
|
|
: !BuildableAttacks.has(item.unitType)),
|
|
)
|
|
.map((item: BuildItemDisplay) => {
|
|
return {
|
|
id: `${elementIdPrefix}_${item.unitType}`,
|
|
name: item.key
|
|
? item.key.replace("unit_type.", "")
|
|
: item.unitType.toString(),
|
|
disabled: (p: MenuElementParams) =>
|
|
!p.buildMenu.canBuildOrUpgrade(item),
|
|
color: (p: MenuElementParams) =>
|
|
p.buildMenu.canBuildOrUpgrade(item)
|
|
? filterType === "attack"
|
|
? COLORS.attack
|
|
: COLORS.building
|
|
: COLORS.building,
|
|
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(
|
|
(tooltipItem): tooltipItem is TooltipItem => tooltipItem !== 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();
|
|
},
|
|
};
|
|
});
|
|
}
|
|
|
|
export const attackMenuElement: MenuElement = {
|
|
id: Slot.Attack,
|
|
name: "radial_attack",
|
|
disabled: (params: MenuElementParams) => params.game.inSpawnPhase(),
|
|
icon: swordIcon,
|
|
color: COLORS.attack,
|
|
|
|
subMenu: (params: MenuElementParams) => {
|
|
if (params === undefined) return [];
|
|
return createMenuElements(params, "attack", "attack");
|
|
},
|
|
};
|
|
|
|
const donateGoldRadialElement: MenuElement = {
|
|
id: Slot.Attack,
|
|
name: "radial_donate_gold",
|
|
disabled: (params: MenuElementParams) =>
|
|
params.game.inSpawnPhase() ||
|
|
!params.playerActions?.interaction?.canDonateGold,
|
|
icon: donateGoldIcon,
|
|
color: "#f59e0b",
|
|
action: (params: MenuElementParams) => {
|
|
if (!params.selected) return;
|
|
params.playerPanel.openSendGoldModal(
|
|
params.playerActions,
|
|
params.tile,
|
|
params.selected,
|
|
);
|
|
},
|
|
};
|
|
|
|
export const deleteUnitElement: MenuElement = {
|
|
id: Slot.Delete,
|
|
name: "delete",
|
|
cooldown: (params: MenuElementParams) => params.myPlayer.deleteUnitCooldown(),
|
|
disabled: (params: MenuElementParams) => {
|
|
const tileOwner = params.game.owner(params.tile);
|
|
const isLand = params.game.isLand(params.tile);
|
|
|
|
if (!tileOwner.isPlayer() || tileOwner.id() !== params.myPlayer.id()) {
|
|
return true;
|
|
}
|
|
|
|
if (!isLand) {
|
|
return true;
|
|
}
|
|
|
|
if (params.game.inSpawnPhase()) {
|
|
return true;
|
|
}
|
|
|
|
if (params.myPlayer.deleteUnitCooldown() > 0) {
|
|
return true;
|
|
}
|
|
|
|
const DELETE_SELECTION_RADIUS = 5;
|
|
const myUnits = params.myPlayer
|
|
.units()
|
|
.filter(
|
|
(unit) =>
|
|
!unit.isUnderConstruction() &&
|
|
unit.markedForDeletion() === false &&
|
|
params.game.manhattanDist(unit.tile(), params.tile) <=
|
|
DELETE_SELECTION_RADIUS,
|
|
);
|
|
|
|
return myUnits.length === 0;
|
|
},
|
|
icon: xIcon,
|
|
color: COLORS.delete,
|
|
tooltipKeys: [
|
|
{
|
|
key: "radial_menu.delete_unit_title",
|
|
className: "title",
|
|
},
|
|
{
|
|
key: "radial_menu.delete_unit_description",
|
|
className: "description",
|
|
},
|
|
],
|
|
action: (params: MenuElementParams) => {
|
|
const DELETE_SELECTION_RADIUS = 5;
|
|
const myUnits = params.myPlayer
|
|
.units()
|
|
.filter(
|
|
(unit) =>
|
|
params.game.manhattanDist(unit.tile(), params.tile) <=
|
|
DELETE_SELECTION_RADIUS,
|
|
);
|
|
|
|
const closestUnit = findClosestBy(myUnits, (unit) =>
|
|
params.game.manhattanDist(unit.tile(), params.tile),
|
|
);
|
|
if (closestUnit) {
|
|
params.playerActionHandler.handleDeleteUnit(closestUnit.id());
|
|
}
|
|
|
|
params.closeMenu();
|
|
},
|
|
};
|
|
|
|
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 [];
|
|
return createMenuElements(params, "build", "build");
|
|
},
|
|
};
|
|
|
|
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) => {
|
|
params.playerActionHandler.handleBoatAttack(params.myPlayer, params.tile);
|
|
|
|
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 (params.game.config().isRandomSpawn()) {
|
|
return true;
|
|
}
|
|
if (tileOwner.isPlayer()) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (isFriendlyTarget(params) && !isDisconnectedTarget(params)) {
|
|
return !params.playerActions.interaction?.canDonateTroops;
|
|
}
|
|
|
|
return !params.playerActions.canAttack;
|
|
},
|
|
action: (params: MenuElementParams) => {
|
|
if (params.game.inSpawnPhase()) {
|
|
params.playerActionHandler.handleSpawn(params.tile);
|
|
} else {
|
|
if (isFriendlyTarget(params) && !isDisconnectedTarget(params)) {
|
|
const selectedPlayer = params.selected as PlayerView;
|
|
const ratio = params.uiState?.attackRatio ?? 1;
|
|
const troopsToDonate = Math.floor(ratio * params.myPlayer.troops());
|
|
if (troopsToDonate > 0) {
|
|
params.playerActionHandler.handleDonateTroops(
|
|
selectedPlayer,
|
|
troopsToDonate,
|
|
);
|
|
}
|
|
} else {
|
|
params.playerActionHandler.handleAttack(
|
|
params.myPlayer,
|
|
params.selected?.id() ?? null,
|
|
);
|
|
}
|
|
}
|
|
params.closeMenu();
|
|
},
|
|
};
|
|
|
|
export const rootMenuElement: MenuElement = {
|
|
id: "root",
|
|
name: "root",
|
|
disabled: () => false,
|
|
icon: infoIcon,
|
|
color: COLORS.info,
|
|
subMenu: (params: MenuElementParams) => {
|
|
const isAllied = params.selected?.isAlliedWith(params.myPlayer);
|
|
const isDisconnected = isDisconnectedTarget(params);
|
|
|
|
const tileOwner = params.game.owner(params.tile);
|
|
const isOwnTerritory =
|
|
tileOwner.isPlayer() &&
|
|
(tileOwner as PlayerView).id() === params.myPlayer.id();
|
|
|
|
const inExtensionWindow =
|
|
params.playerActions.interaction?.allianceInfo?.inExtensionWindow;
|
|
|
|
const menuItems: (MenuElement | null)[] = [
|
|
infoMenuElement,
|
|
...(isOwnTerritory
|
|
? [deleteUnitElement, allyRequestElement, buildMenuElement]
|
|
: [
|
|
isAllied && !isDisconnected ? allyBreakElement : boatMenuElement,
|
|
inExtensionWindow ? allyExtendElement : allyRequestElement,
|
|
isFriendlyTarget(params) && !isDisconnected
|
|
? donateGoldRadialElement
|
|
: attackMenuElement,
|
|
]),
|
|
];
|
|
|
|
return menuItems.filter((item): item is MenuElement => item !== null);
|
|
},
|
|
};
|