refactor radial, fix boat on terra nullius not working fixes (#1095)

## Description:
refactor the MenuElements to be more decoupled by passing in MenuParams.

Fix boat not working on terra nullius.

fixes #1088

## Please complete the following:

- [ ] I have added screenshots for all UI updates
- [ ] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [ ] I have added relevant tests to the test directory
- [ ] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
- [ ] I understand that submitting code with bugs that could have been
caught through manual testing blocks releases and new features for all
contributors

## Please put your Discord username so you can be contacted if a bug or
regression is found:

<DISCORD USERNAME>
This commit is contained in:
evanpelle
2025-06-07 15:34:10 -07:00
committed by evanpelle
parent a084f35c2d
commit ebbf4dd5e5
5 changed files with 288 additions and 335 deletions
@@ -3,7 +3,7 @@ import { GameView, PlayerView } from "../../../core/game/GameView";
import { SendQuickChatEvent } from "../../Transport";
import { translateText } from "../../Utils";
import { ChatModal, QuickChatPhrase, quickChatPhrases } from "./ChatModal";
import { COLORS, MenuElement } from "./RadialMenuElements";
import { COLORS, MenuElement, MenuElementParams } from "./RadialMenuElements";
export class ChatIntegration {
private ctModal: ChatModal;
@@ -51,7 +51,7 @@ export class ChatIntegration {
return {
id: `phrase-${category.id}-${phrase.key}`,
name: phraseText,
disabled: false,
disabled: () => false,
text: this.shortenText(phraseText),
fontSize: "10px",
color: categoryColor,
@@ -61,7 +61,7 @@ export class ChatIntegration {
className: "description",
},
],
action: () => {
action: (params: MenuElementParams) => {
if (phrase.requiresPlayer) {
this.ctModal.openWithSelection(
category.id,
@@ -86,7 +86,7 @@ export class ChatIntegration {
return {
id: `chat-category-${category.id}`,
name: categoryTranslation,
disabled: false,
disabled: () => false,
text: categoryTranslation,
color: categoryColor,
_action: () => {}, // Empty action placeholder for RadialMenu
+23 -7
View File
@@ -18,10 +18,8 @@ import { RadialMenu, RadialMenuConfig } from "./RadialMenu";
import {
COLORS,
MenuElementParams,
rootMenuItems,
Slot,
createRadialMenuItems,
getRootMenuItems,
updateCenterButton,
} from "./RadialMenuElements";
import boatIcon from "../../../../resources/images/BoatIconWhite.svg";
@@ -85,7 +83,7 @@ export class MainRadialMenu extends LitElement implements Layer {
this.chatIntegration = new ChatIntegration(this.game, this.eventBus);
this.radialMenu.setRootMenuItems(getRootMenuItems());
this.radialMenu.setRootMenuItems(rootMenuItems);
}
init() {
@@ -127,9 +125,8 @@ export class MainRadialMenu extends LitElement implements Layer {
closeMenu: () => this.menuEventManager.closeMenu(),
};
const menuItems = createRadialMenuItems(params);
this.radialMenu.setRootMenuItems(menuItems);
this.radialMenu.setRootMenuItems(rootMenuItems);
this.radialMenu.setParams(params);
updateCenterButton(params, (enabled, action) => {
this.radialMenu.enableCenterButton(enabled, action);
@@ -283,3 +280,22 @@ export class MainRadialMenu extends LitElement implements Layer {
// No redraw implementation needed
}
}
function updateCenterButton(
params: MenuElementParams,
enableCenterButton: (enabled: boolean, action?: (() => void) | null) => void,
) {
if (params.playerActions.canAttack) {
enableCenterButton(true, () => {
if (params.tileOwner !== params.myPlayer) {
params.playerActionHandler.handleAttack(
params.myPlayer,
params.tileOwner.id(),
);
}
params.closeMenu();
});
} else {
enableCenterButton(false);
}
}
@@ -1,5 +1,10 @@
import { EventBus } from "../../../core/EventBus";
import { Cell, PlayerActions, UnitType } from "../../../core/game/Game";
import {
Cell,
PlayerActions,
PlayerID,
UnitType,
} from "../../../core/game/Game";
import { TileRef } from "../../../core/game/GameMap";
import { PlayerView } from "../../../core/game/GameView";
import {
@@ -42,7 +47,7 @@ export class PlayerActionHandler {
handleBoatAttack(
player: PlayerView,
targetId: string,
targetId: PlayerID | null,
targetCell: Cell,
spawnTile: Cell | null,
) {
+36 -28
View File
@@ -2,7 +2,7 @@ import * as d3 from "d3";
import backIcon from "../../../../resources/images/BackIconWhite.svg";
import disabledIcon from "../../../../resources/images/DisabledIcon.svg";
import { Layer } from "./Layer";
import { MenuElement } from "./RadialMenuElements";
import { MenuElement, MenuElementParams } from "./RadialMenuElements";
export interface TooltipItem {
text: string;
@@ -68,6 +68,8 @@ export class RadialMenu implements Layer {
private navigationInProgress: boolean = false;
private originalCenterButtonIcon: string = "";
private params: MenuElementParams;
constructor(config: RadialMenuConfig = {}) {
this.config = {
menuSize: config.menuSize ?? 190,
@@ -287,10 +289,10 @@ export class RadialMenu implements Layer {
.attr("class", "menu-item-path")
.attr("d", arc)
.attr("fill", (d) => {
const color = d.data.disabled
const color = d.data.disabled(this.params)
? this.config.disabledColor
: d.data.color || "#333333";
const opacity = d.data.disabled ? 0.5 : 0.7;
const opacity = d.data.disabled(this.params) ? 0.5 : 0.7;
if (d.data.id === this.selectedItemId && this.currentLevel > level) {
return color;
@@ -300,8 +302,10 @@ export class RadialMenu implements Layer {
})
.attr("stroke", "#ffffff")
.attr("stroke-width", "2")
.style("cursor", (d) => (d.data.disabled ? "not-allowed" : "pointer"))
.style("opacity", (d) => (d.data.disabled ? 0.5 : 1))
.style("cursor", (d) =>
d.data.disabled(this.params) ? "not-allowed" : "pointer",
)
.style("opacity", (d) => (d.data.disabled(this.params) ? 0.5 : 1))
.style(
"transition",
`filter ${this.config.menuTransitionDuration / 2}ms, stroke-width ${
@@ -323,7 +327,7 @@ export class RadialMenu implements Layer {
path.attr("filter", "url(#glow)");
path.attr("stroke-width", "3");
const color = d.data.disabled
const color = d.data.disabled(this.params)
? this.config.disabledColor
: d.data.color || "#333333";
path.attr("fill", color);
@@ -354,7 +358,7 @@ export class RadialMenu implements Layer {
) {
const onHover = (d: d3.PieArcDatum<MenuElement>, path: any) => {
if (
d.data.disabled ||
d.data.disabled(this.params) ||
(this.currentLevel > 0 && this.currentLevel !== level) ||
this.navigationInProgress
)
@@ -362,7 +366,7 @@ export class RadialMenu implements Layer {
path.attr("filter", "url(#glow)");
path.attr("stroke-width", "3");
const color = d.data.disabled
const color = d.data.disabled(this.params)
? this.config.disabledColor
: d.data.color || "#333333";
path.attr("fill", color);
@@ -371,10 +375,11 @@ export class RadialMenu implements Layer {
this.showTooltip(d.data.tooltipItems);
}
const subMenu = d.data.subMenu?.(this.params);
if (
d.data.children &&
d.data.children.length > 0 &&
!d.data.disabled &&
subMenu &&
subMenu.length > 0 &&
!d.data.disabled(this.params) &&
!(
this.currentLevel > 0 &&
d.data.id === this.selectedItemId &&
@@ -390,7 +395,7 @@ export class RadialMenu implements Layer {
if (this.navigationInProgress) return;
this.navigationInProgress = true;
this.selectedItemId = d.data.id;
this.navigateToSubMenu(d.data.children || []);
this.navigateToSubMenu(subMenu);
this.setCenterButtonAsBack();
}, 200);
}
@@ -405,7 +410,7 @@ export class RadialMenu implements Layer {
this.hideTooltip();
if (
d.data.disabled ||
d.data.disabled(this.params) ||
(this.currentLevel > 0 &&
level === 0 &&
d.data.id === this.selectedItemId)
@@ -413,10 +418,10 @@ export class RadialMenu implements Layer {
return;
path.attr("filter", null);
path.attr("stroke-width", "2");
const color = d.data.disabled
const color = d.data.disabled(this.params)
? this.config.disabledColor
: d.data.color || "#333333";
const opacity = d.data.disabled ? 0.5 : 0.7;
const opacity = d.data.disabled(this.params) ? 0.5 : 0.7;
path.attr(
"fill",
d3.color(color)?.copy({ opacity: opacity })?.toString() || color,
@@ -425,7 +430,7 @@ export class RadialMenu implements Layer {
const onClick = (d: d3.PieArcDatum<MenuElement>, event: Event) => {
event.stopPropagation();
if (d.data.disabled || this.navigationInProgress) return;
if (d.data.disabled(this.params) || this.navigationInProgress) return;
if (
this.currentLevel > 0 &&
@@ -434,16 +439,15 @@ export class RadialMenu implements Layer {
)
return;
if (d.data.children && d.data.children.length > 0) {
const subMenu = d.data.subMenu?.(this.params);
if (subMenu && subMenu.length > 0) {
this.navigationInProgress = true;
this.selectedItemId = d.data.id;
this.navigateToSubMenu(d.data.children || []);
this.navigateToSubMenu(subMenu);
this.setCenterButtonAsBack();
} else if (d.data._action) {
d.data._action();
this.hideRadialMenu();
} else {
throw new Error(`Menu item action is not a function: ${d.data.id}`);
d.data.action?.(this.params);
this.hideRadialMenu();
}
};
@@ -513,14 +517,16 @@ export class RadialMenu implements Layer {
.attr("fill", "white")
.attr("font-size", d.data.fontSize ?? "12px")
.attr("font-family", "Arial, sans-serif")
.style("opacity", d.data.disabled ? 0.5 : 1)
.style("opacity", d.data.disabled(this.params) ? 0.5 : 1)
.text(d.data.text);
} else {
content
.append("image")
.attr(
"xlink:href",
d.data.disabled ? disabledIcon : d.data.icon || disabledIcon,
d.data.disabled(this.params)
? disabledIcon
: d.data.icon || disabledIcon,
)
.attr("width", this.config.iconSize)
.attr("height", this.config.iconSize)
@@ -646,10 +652,10 @@ export class RadialMenu implements Layer {
const item = this.findMenuItem(this.selectedItemId);
if (item) {
const color = item.disabled
const color = item.disabled(this.params)
? this.config.disabledColor
: item.color || "#333333";
const opacity = item.disabled ? 0.5 : 0.7;
const opacity = item.disabled(this.params) ? 0.5 : 0.7;
selectedPath.attr(
"fill",
d3.color(color)?.copy({ opacity: opacity })?.toString() || color,
@@ -862,7 +868,6 @@ export class RadialMenu implements Layer {
this.enableCenterButton(false);
for (const item of this.currentMenuItems) {
item.disabled = true;
item.color = this.config.disabledColor;
}
}
@@ -956,7 +961,6 @@ export class RadialMenu implements Layer {
const item = this.findMenuItem(id);
if (item) {
item.disabled = !enabled;
if (color) item.color = enabled ? color : this.config.disabledColor;
if (icon) item.icon = icon;
if (text !== undefined) item.text = text;
@@ -1003,6 +1007,10 @@ export class RadialMenu implements Layer {
}
}
public setParams(params: MenuElementParams) {
this.params = params;
}
private findMenuItem(id: string): MenuElement | undefined {
return this.currentMenuItems.find((item) => item.id === id);
}
+218 -294
View File
@@ -3,7 +3,6 @@ import {
Cell,
PlayerActions,
TerraNullius,
UnitType,
} from "../../../core/game/Game";
import { TileRef } from "../../../core/game/GameMap";
import { GameView, PlayerView } from "../../../core/game/GameView";
@@ -45,20 +44,16 @@ export interface MenuElementParams {
export interface MenuElement {
id: string;
name: string;
disabled: boolean;
displayed?: boolean;
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
// Runtime properties used by RadialMenu (not to be set by menu element creators)
children?: MenuElement[];
_action?: () => void;
}
export const COLORS = {
@@ -96,42 +91,220 @@ export enum Slot {
Back = "back",
}
/**
* Convert a MenuElement tree to a version usable by the RadialMenu
* by resolving subMenu functions and setting up actions
*/
export function prepareMenuElementsForRadialMenu(
elements: MenuElement[],
params: MenuElementParams,
): MenuElement[] {
return elements.map((element) => {
const prepared: MenuElement = { ...element };
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,
})),
};
// If the element has a subMenu function, execute it to get the children
if (element.subMenu) {
prepared.children = prepareMenuElementsForRadialMenu(
element.subMenu(params),
params,
);
// We don't need the subMenu function anymore
prepared.subMenu = undefined;
const allyTargetElement: MenuElement = {
id: "ally_target",
name: "target",
disabled: () => false,
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!, "start");
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!, "stop");
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[] = [];
const emojiCount = 15;
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();
},
});
}
// Set up the action function to call the element's action with params
if (element.action) {
prepared._action = () => element.action!(params);
} else {
prepared._action = () => {};
}
emojiElements.push({
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();
});
},
});
return prepared;
});
}
return emojiElements;
},
};
export const infoMenuElement: MenuElement = {
id: Slot.Info,
name: "info",
disabled: () => false,
icon: infoIcon,
color: COLORS.info,
subMenu: (params: MenuElementParams) => {
if (!params.selected) return [];
return [
infoChatElement,
allyTargetElement,
allyTradeElement,
allyEmbargoElement,
allyRequestElement,
allyBreakElement,
allyDonateGoldElement,
allyDonateTroopsElement,
infoPlayerElement,
infoEmojiElement,
].filter((item) => item.displayed !== false);
},
};
export const buildMenuElement: MenuElement = {
id: Slot.Build,
name: "build",
disabled: false,
disabled: () => false,
icon: buildIcon,
color: COLORS.build,
@@ -142,7 +315,8 @@ export const buildMenuElement: MenuElement = {
name: item.key
? item.key.replace("unit_type.", "")
: item.unitType.toString(),
disabled: !params.buildMenu.canBuild(item),
disabled: (params: MenuElementParams) =>
!params.buildMenu.canBuild(item),
color: params.buildMenu.canBuild(item) ? COLORS.building : undefined,
icon: item.icon,
tooltipItems: [
@@ -173,7 +347,7 @@ export const buildMenuElement: MenuElement = {
buildElements.push({
id: "build_menu",
name: "build",
disabled: false,
disabled: () => false,
color: COLORS.build,
icon: buildIcon,
action: (params: MenuElementParams) => {
@@ -188,13 +362,11 @@ export const buildMenuElement: MenuElement = {
export const boatMenuElement: MenuElement = {
id: Slot.Boat,
name: "boat",
disabled: false,
disabled: () => false,
icon: boatIcon,
color: COLORS.boat,
action: async (params: MenuElementParams) => {
if (!params.selected) return;
const spawn = await params.playerActionHandler.findBestTransportShipSpawn(
params.myPlayer,
params.tile,
@@ -207,7 +379,7 @@ export const boatMenuElement: MenuElement = {
params.playerActionHandler.handleBoatAttack(
params.myPlayer,
params.selected.id(),
params.selected?.id() || null,
new Cell(params.game.x(params.tile), params.game.y(params.tile)),
spawnTile,
);
@@ -216,256 +388,8 @@ export const boatMenuElement: MenuElement = {
},
};
export const infoMenuElement: MenuElement = {
id: Slot.Info,
name: "info",
disabled: false,
icon: infoIcon,
color: COLORS.info,
subMenu: (params: MenuElementParams) => {
if (!params.selected) return [];
return [
{
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,
})),
},
{
id: "ally_target",
name: "target",
disabled: false,
color: COLORS.target,
icon: targetIcon,
action: (params: MenuElementParams) => {
params.playerActionHandler.handleTargetPlayer(params.selected!.id());
params.closeMenu();
},
},
{
id: "ally_trade",
name: "trade",
disabled: !!params.playerActions?.interaction?.canEmbargo,
displayed: !params.playerActions?.interaction?.canEmbargo,
color: COLORS.trade,
text: translateText("player_panel.start_trade"),
action: (params: MenuElementParams) => {
params.playerActionHandler.handleEmbargo(params.selected!, "start");
params.closeMenu();
},
},
{
id: "ally_embargo",
name: "embargo",
disabled: !params.playerActions?.interaction?.canEmbargo,
displayed: !!params.playerActions?.interaction?.canEmbargo,
color: COLORS.embargo,
text: translateText("player_panel.stop_trade"),
action: (params: MenuElementParams) => {
params.playerActionHandler.handleEmbargo(params.selected!, "stop");
params.closeMenu();
},
},
{
id: "ally_request",
name: "request",
disabled: !params.playerActions?.interaction?.canSendAllianceRequest,
displayed: !params.playerActions?.interaction?.canBreakAlliance,
color: COLORS.ally,
icon: allianceIcon,
action: (params: MenuElementParams) => {
params.playerActionHandler.handleAllianceRequest(
params.myPlayer,
params.selected!,
);
params.closeMenu();
},
},
{
id: "ally_break",
name: "break",
disabled: !params.playerActions?.interaction?.canBreakAlliance,
displayed: !!params.playerActions?.interaction?.canBreakAlliance,
color: COLORS.breakAlly,
icon: traitorIcon,
action: (params: MenuElementParams) => {
params.playerActionHandler.handleBreakAlliance(
params.myPlayer,
params.selected!,
);
params.closeMenu();
},
},
{
id: "ally_donate_gold",
name: "donate gold",
disabled: !params.playerActions?.interaction?.canDonate,
color: COLORS.ally,
icon: donateGoldIcon,
action: (params: MenuElementParams) => {
params.playerActionHandler.handleDonateGold(params.selected!);
params.closeMenu();
},
},
{
id: "ally_donate_troops",
name: "donate troops",
disabled: !params.playerActions?.interaction?.canDonate,
color: COLORS.ally,
icon: donateTroopIcon,
action: (params: MenuElementParams) => {
params.playerActionHandler.handleDonateTroops(params.selected!);
params.closeMenu();
},
},
{
id: "info_player",
name: "player",
disabled: false,
color: COLORS.info,
icon: infoIcon,
action: (params: MenuElementParams) => {
params.playerPanel.show(params.playerActions, params.tile);
},
},
{
id: "info_emoji",
name: "emoji",
disabled: false,
color: COLORS.infoEmoji,
icon: emojiIcon,
subMenu: () => {
const emojiElements: MenuElement[] = [];
const emojiCount = 15;
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();
},
});
}
emojiElements.push({
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();
});
},
});
return emojiElements;
},
},
].filter((item) => item.displayed !== false);
},
};
export function createMenuItems(params: MenuElementParams): MenuElement[] {
const canBuildTransport = params.playerActions.buildableUnits.find(
(bu) => bu.type === UnitType.TransportShip,
)?.canBuild;
return [
{
...boatMenuElement,
disabled: !canBuildTransport || !params.selected,
},
{
...buildMenuElement,
disabled: params.game.inSpawnPhase(),
},
{
...infoMenuElement,
disabled: !params.game.hasOwner(params.tile),
},
];
}
export function createRadialMenuItems(
params: MenuElementParams,
): MenuElement[] {
const elements = createMenuItems(params);
return prepareMenuElementsForRadialMenu(elements, params);
}
export function getRootMenuItems(): MenuElement[] {
return [
{
id: Slot.Boat,
name: "boat",
disabled: true,
_action: () => {},
icon: boatIcon,
},
{
id: Slot.Build,
name: "build",
disabled: true,
_action: () => {},
icon: buildIcon,
},
{
id: Slot.Info,
name: "info",
disabled: true,
_action: () => {},
icon: infoIcon,
},
];
}
export function updateCenterButton(
params: MenuElementParams,
enableCenterButton: (enabled: boolean, action?: (() => void) | null) => void,
) {
if (params.playerActions.canAttack) {
enableCenterButton(true, () => {
if (params.tileOwner !== params.myPlayer) {
params.playerActionHandler.handleAttack(
params.myPlayer,
params.tileOwner.id(),
);
}
params.closeMenu();
});
} else {
enableCenterButton(false);
}
}
export const rootMenuItems: MenuElement[] = [
boatMenuElement,
buildMenuElement,
infoMenuElement,
];