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 GitHub
parent 72bb78342e
commit dda9e26596
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 { SendQuickChatEvent } from "../../Transport";
import { translateText } from "../../Utils"; import { translateText } from "../../Utils";
import { ChatModal, QuickChatPhrase, quickChatPhrases } from "./ChatModal"; import { ChatModal, QuickChatPhrase, quickChatPhrases } from "./ChatModal";
import { COLORS, MenuElement } from "./RadialMenuElements"; import { COLORS, MenuElement, MenuElementParams } from "./RadialMenuElements";
export class ChatIntegration { export class ChatIntegration {
private ctModal: ChatModal; private ctModal: ChatModal;
@@ -51,7 +51,7 @@ export class ChatIntegration {
return { return {
id: `phrase-${category.id}-${phrase.key}`, id: `phrase-${category.id}-${phrase.key}`,
name: phraseText, name: phraseText,
disabled: false, disabled: () => false,
text: this.shortenText(phraseText), text: this.shortenText(phraseText),
fontSize: "10px", fontSize: "10px",
color: categoryColor, color: categoryColor,
@@ -61,7 +61,7 @@ export class ChatIntegration {
className: "description", className: "description",
}, },
], ],
action: () => { action: (params: MenuElementParams) => {
if (phrase.requiresPlayer) { if (phrase.requiresPlayer) {
this.ctModal.openWithSelection( this.ctModal.openWithSelection(
category.id, category.id,
@@ -86,7 +86,7 @@ export class ChatIntegration {
return { return {
id: `chat-category-${category.id}`, id: `chat-category-${category.id}`,
name: categoryTranslation, name: categoryTranslation,
disabled: false, disabled: () => false,
text: categoryTranslation, text: categoryTranslation,
color: categoryColor, color: categoryColor,
_action: () => {}, // Empty action placeholder for RadialMenu _action: () => {}, // Empty action placeholder for RadialMenu
+23 -7
View File
@@ -18,10 +18,8 @@ import { RadialMenu, RadialMenuConfig } from "./RadialMenu";
import { import {
COLORS, COLORS,
MenuElementParams, MenuElementParams,
rootMenuItems,
Slot, Slot,
createRadialMenuItems,
getRootMenuItems,
updateCenterButton,
} from "./RadialMenuElements"; } from "./RadialMenuElements";
import boatIcon from "../../../../resources/images/BoatIconWhite.svg"; 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.chatIntegration = new ChatIntegration(this.game, this.eventBus);
this.radialMenu.setRootMenuItems(getRootMenuItems()); this.radialMenu.setRootMenuItems(rootMenuItems);
} }
init() { init() {
@@ -127,9 +125,8 @@ export class MainRadialMenu extends LitElement implements Layer {
closeMenu: () => this.menuEventManager.closeMenu(), closeMenu: () => this.menuEventManager.closeMenu(),
}; };
const menuItems = createRadialMenuItems(params); this.radialMenu.setRootMenuItems(rootMenuItems);
this.radialMenu.setParams(params);
this.radialMenu.setRootMenuItems(menuItems);
updateCenterButton(params, (enabled, action) => { updateCenterButton(params, (enabled, action) => {
this.radialMenu.enableCenterButton(enabled, action); this.radialMenu.enableCenterButton(enabled, action);
@@ -283,3 +280,22 @@ export class MainRadialMenu extends LitElement implements Layer {
// No redraw implementation needed // 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 { 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 { TileRef } from "../../../core/game/GameMap";
import { PlayerView } from "../../../core/game/GameView"; import { PlayerView } from "../../../core/game/GameView";
import { import {
@@ -42,7 +47,7 @@ export class PlayerActionHandler {
handleBoatAttack( handleBoatAttack(
player: PlayerView, player: PlayerView,
targetId: string, targetId: PlayerID | null,
targetCell: Cell, targetCell: Cell,
spawnTile: Cell | null, spawnTile: Cell | null,
) { ) {
+36 -28
View File
@@ -2,7 +2,7 @@ import * as d3 from "d3";
import backIcon from "../../../../resources/images/BackIconWhite.svg"; import backIcon from "../../../../resources/images/BackIconWhite.svg";
import disabledIcon from "../../../../resources/images/DisabledIcon.svg"; import disabledIcon from "../../../../resources/images/DisabledIcon.svg";
import { Layer } from "./Layer"; import { Layer } from "./Layer";
import { MenuElement } from "./RadialMenuElements"; import { MenuElement, MenuElementParams } from "./RadialMenuElements";
export interface TooltipItem { export interface TooltipItem {
text: string; text: string;
@@ -68,6 +68,8 @@ export class RadialMenu implements Layer {
private navigationInProgress: boolean = false; private navigationInProgress: boolean = false;
private originalCenterButtonIcon: string = ""; private originalCenterButtonIcon: string = "";
private params: MenuElementParams;
constructor(config: RadialMenuConfig = {}) { constructor(config: RadialMenuConfig = {}) {
this.config = { this.config = {
menuSize: config.menuSize ?? 190, menuSize: config.menuSize ?? 190,
@@ -287,10 +289,10 @@ export class RadialMenu implements Layer {
.attr("class", "menu-item-path") .attr("class", "menu-item-path")
.attr("d", arc) .attr("d", arc)
.attr("fill", (d) => { .attr("fill", (d) => {
const color = d.data.disabled const color = d.data.disabled(this.params)
? this.config.disabledColor ? this.config.disabledColor
: d.data.color || "#333333"; : 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) { if (d.data.id === this.selectedItemId && this.currentLevel > level) {
return color; return color;
@@ -300,8 +302,10 @@ export class RadialMenu implements Layer {
}) })
.attr("stroke", "#ffffff") .attr("stroke", "#ffffff")
.attr("stroke-width", "2") .attr("stroke-width", "2")
.style("cursor", (d) => (d.data.disabled ? "not-allowed" : "pointer")) .style("cursor", (d) =>
.style("opacity", (d) => (d.data.disabled ? 0.5 : 1)) d.data.disabled(this.params) ? "not-allowed" : "pointer",
)
.style("opacity", (d) => (d.data.disabled(this.params) ? 0.5 : 1))
.style( .style(
"transition", "transition",
`filter ${this.config.menuTransitionDuration / 2}ms, stroke-width ${ `filter ${this.config.menuTransitionDuration / 2}ms, stroke-width ${
@@ -323,7 +327,7 @@ export class RadialMenu implements Layer {
path.attr("filter", "url(#glow)"); path.attr("filter", "url(#glow)");
path.attr("stroke-width", "3"); path.attr("stroke-width", "3");
const color = d.data.disabled const color = d.data.disabled(this.params)
? this.config.disabledColor ? this.config.disabledColor
: d.data.color || "#333333"; : d.data.color || "#333333";
path.attr("fill", color); path.attr("fill", color);
@@ -354,7 +358,7 @@ export class RadialMenu implements Layer {
) { ) {
const onHover = (d: d3.PieArcDatum<MenuElement>, path: any) => { const onHover = (d: d3.PieArcDatum<MenuElement>, path: any) => {
if ( if (
d.data.disabled || d.data.disabled(this.params) ||
(this.currentLevel > 0 && this.currentLevel !== level) || (this.currentLevel > 0 && this.currentLevel !== level) ||
this.navigationInProgress this.navigationInProgress
) )
@@ -362,7 +366,7 @@ export class RadialMenu implements Layer {
path.attr("filter", "url(#glow)"); path.attr("filter", "url(#glow)");
path.attr("stroke-width", "3"); path.attr("stroke-width", "3");
const color = d.data.disabled const color = d.data.disabled(this.params)
? this.config.disabledColor ? this.config.disabledColor
: d.data.color || "#333333"; : d.data.color || "#333333";
path.attr("fill", color); path.attr("fill", color);
@@ -371,10 +375,11 @@ export class RadialMenu implements Layer {
this.showTooltip(d.data.tooltipItems); this.showTooltip(d.data.tooltipItems);
} }
const subMenu = d.data.subMenu?.(this.params);
if ( if (
d.data.children && subMenu &&
d.data.children.length > 0 && subMenu.length > 0 &&
!d.data.disabled && !d.data.disabled(this.params) &&
!( !(
this.currentLevel > 0 && this.currentLevel > 0 &&
d.data.id === this.selectedItemId && d.data.id === this.selectedItemId &&
@@ -390,7 +395,7 @@ export class RadialMenu implements Layer {
if (this.navigationInProgress) return; if (this.navigationInProgress) return;
this.navigationInProgress = true; this.navigationInProgress = true;
this.selectedItemId = d.data.id; this.selectedItemId = d.data.id;
this.navigateToSubMenu(d.data.children || []); this.navigateToSubMenu(subMenu);
this.setCenterButtonAsBack(); this.setCenterButtonAsBack();
}, 200); }, 200);
} }
@@ -405,7 +410,7 @@ export class RadialMenu implements Layer {
this.hideTooltip(); this.hideTooltip();
if ( if (
d.data.disabled || d.data.disabled(this.params) ||
(this.currentLevel > 0 && (this.currentLevel > 0 &&
level === 0 && level === 0 &&
d.data.id === this.selectedItemId) d.data.id === this.selectedItemId)
@@ -413,10 +418,10 @@ export class RadialMenu implements Layer {
return; return;
path.attr("filter", null); path.attr("filter", null);
path.attr("stroke-width", "2"); path.attr("stroke-width", "2");
const color = d.data.disabled const color = d.data.disabled(this.params)
? this.config.disabledColor ? this.config.disabledColor
: d.data.color || "#333333"; : 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( path.attr(
"fill", "fill",
d3.color(color)?.copy({ opacity: opacity })?.toString() || color, 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) => { const onClick = (d: d3.PieArcDatum<MenuElement>, event: Event) => {
event.stopPropagation(); event.stopPropagation();
if (d.data.disabled || this.navigationInProgress) return; if (d.data.disabled(this.params) || this.navigationInProgress) return;
if ( if (
this.currentLevel > 0 && this.currentLevel > 0 &&
@@ -434,16 +439,15 @@ export class RadialMenu implements Layer {
) )
return; 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.navigationInProgress = true;
this.selectedItemId = d.data.id; this.selectedItemId = d.data.id;
this.navigateToSubMenu(d.data.children || []); this.navigateToSubMenu(subMenu);
this.setCenterButtonAsBack(); this.setCenterButtonAsBack();
} else if (d.data._action) {
d.data._action();
this.hideRadialMenu();
} else { } 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("fill", "white")
.attr("font-size", d.data.fontSize ?? "12px") .attr("font-size", d.data.fontSize ?? "12px")
.attr("font-family", "Arial, sans-serif") .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); .text(d.data.text);
} else { } else {
content content
.append("image") .append("image")
.attr( .attr(
"xlink:href", "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("width", this.config.iconSize)
.attr("height", this.config.iconSize) .attr("height", this.config.iconSize)
@@ -646,10 +652,10 @@ export class RadialMenu implements Layer {
const item = this.findMenuItem(this.selectedItemId); const item = this.findMenuItem(this.selectedItemId);
if (item) { if (item) {
const color = item.disabled const color = item.disabled(this.params)
? this.config.disabledColor ? this.config.disabledColor
: item.color || "#333333"; : item.color || "#333333";
const opacity = item.disabled ? 0.5 : 0.7; const opacity = item.disabled(this.params) ? 0.5 : 0.7;
selectedPath.attr( selectedPath.attr(
"fill", "fill",
d3.color(color)?.copy({ opacity: opacity })?.toString() || color, d3.color(color)?.copy({ opacity: opacity })?.toString() || color,
@@ -862,7 +868,6 @@ export class RadialMenu implements Layer {
this.enableCenterButton(false); this.enableCenterButton(false);
for (const item of this.currentMenuItems) { for (const item of this.currentMenuItems) {
item.disabled = true;
item.color = this.config.disabledColor; item.color = this.config.disabledColor;
} }
} }
@@ -956,7 +961,6 @@ export class RadialMenu implements Layer {
const item = this.findMenuItem(id); const item = this.findMenuItem(id);
if (item) { if (item) {
item.disabled = !enabled;
if (color) item.color = enabled ? color : this.config.disabledColor; if (color) item.color = enabled ? color : this.config.disabledColor;
if (icon) item.icon = icon; if (icon) item.icon = icon;
if (text !== undefined) item.text = text; 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 { private findMenuItem(id: string): MenuElement | undefined {
return this.currentMenuItems.find((item) => item.id === id); return this.currentMenuItems.find((item) => item.id === id);
} }
+218 -294
View File
@@ -3,7 +3,6 @@ import {
Cell, Cell,
PlayerActions, PlayerActions,
TerraNullius, TerraNullius,
UnitType,
} from "../../../core/game/Game"; } from "../../../core/game/Game";
import { TileRef } from "../../../core/game/GameMap"; import { TileRef } from "../../../core/game/GameMap";
import { GameView, PlayerView } from "../../../core/game/GameView"; import { GameView, PlayerView } from "../../../core/game/GameView";
@@ -45,20 +44,16 @@ export interface MenuElementParams {
export interface MenuElement { export interface MenuElement {
id: string; id: string;
name: string; name: string;
disabled: boolean; displayed?: boolean | ((params: MenuElementParams) => boolean);
displayed?: boolean;
color?: string; color?: string;
icon?: string; icon?: string;
text?: string; text?: string;
fontSize?: string; fontSize?: string;
tooltipItems?: TooltipItem[]; tooltipItems?: TooltipItem[];
disabled: (params: MenuElementParams) => boolean;
action?: (params: MenuElementParams) => void; // For leaf items that perform actions action?: (params: MenuElementParams) => void; // For leaf items that perform actions
subMenu?: (params: MenuElementParams) => MenuElement[]; // For non-leaf items that open submenus 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 = { export const COLORS = {
@@ -96,42 +91,220 @@ export enum Slot {
Back = "back", Back = "back",
} }
/** const infoChatElement: MenuElement = {
* Convert a MenuElement tree to a version usable by the RadialMenu id: "info_chat",
* by resolving subMenu functions and setting up actions name: "chat",
*/ disabled: () => false,
export function prepareMenuElementsForRadialMenu( color: COLORS.chat.default,
elements: MenuElement[], icon: chatIcon,
params: MenuElementParams, subMenu: (params: MenuElementParams) =>
): MenuElement[] { params.chatIntegration
return elements.map((element) => { .createQuickChatMenu(params.selected!)
const prepared: MenuElement = { ...element }; .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 const allyTargetElement: MenuElement = {
if (element.subMenu) { id: "ally_target",
prepared.children = prepareMenuElementsForRadialMenu( name: "target",
element.subMenu(params), disabled: () => false,
params, color: COLORS.target,
); icon: targetIcon,
// We don't need the subMenu function anymore action: (params: MenuElementParams) => {
prepared.subMenu = undefined; 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 emojiElements.push({
if (element.action) { id: "emoji_more",
prepared._action = () => element.action!(params); name: "more",
} else { disabled: () => false,
prepared._action = () => {}; 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 = { export const buildMenuElement: MenuElement = {
id: Slot.Build, id: Slot.Build,
name: "build", name: "build",
disabled: false, disabled: () => false,
icon: buildIcon, icon: buildIcon,
color: COLORS.build, color: COLORS.build,
@@ -142,7 +315,8 @@ export const buildMenuElement: MenuElement = {
name: item.key name: item.key
? item.key.replace("unit_type.", "") ? item.key.replace("unit_type.", "")
: item.unitType.toString(), : item.unitType.toString(),
disabled: !params.buildMenu.canBuild(item), disabled: (params: MenuElementParams) =>
!params.buildMenu.canBuild(item),
color: params.buildMenu.canBuild(item) ? COLORS.building : undefined, color: params.buildMenu.canBuild(item) ? COLORS.building : undefined,
icon: item.icon, icon: item.icon,
tooltipItems: [ tooltipItems: [
@@ -173,7 +347,7 @@ export const buildMenuElement: MenuElement = {
buildElements.push({ buildElements.push({
id: "build_menu", id: "build_menu",
name: "build", name: "build",
disabled: false, disabled: () => false,
color: COLORS.build, color: COLORS.build,
icon: buildIcon, icon: buildIcon,
action: (params: MenuElementParams) => { action: (params: MenuElementParams) => {
@@ -188,13 +362,11 @@ export const buildMenuElement: MenuElement = {
export const boatMenuElement: MenuElement = { export const boatMenuElement: MenuElement = {
id: Slot.Boat, id: Slot.Boat,
name: "boat", name: "boat",
disabled: false, disabled: () => false,
icon: boatIcon, icon: boatIcon,
color: COLORS.boat, color: COLORS.boat,
action: async (params: MenuElementParams) => { action: async (params: MenuElementParams) => {
if (!params.selected) return;
const spawn = await params.playerActionHandler.findBestTransportShipSpawn( const spawn = await params.playerActionHandler.findBestTransportShipSpawn(
params.myPlayer, params.myPlayer,
params.tile, params.tile,
@@ -207,7 +379,7 @@ export const boatMenuElement: MenuElement = {
params.playerActionHandler.handleBoatAttack( params.playerActionHandler.handleBoatAttack(
params.myPlayer, params.myPlayer,
params.selected.id(), params.selected?.id() || null,
new Cell(params.game.x(params.tile), params.game.y(params.tile)), new Cell(params.game.x(params.tile), params.game.y(params.tile)),
spawnTile, spawnTile,
); );
@@ -216,256 +388,8 @@ export const boatMenuElement: MenuElement = {
}, },
}; };
export const infoMenuElement: MenuElement = { export const rootMenuItems: MenuElement[] = [
id: Slot.Info, boatMenuElement,
name: "info", buildMenuElement,
disabled: false, infoMenuElement,
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);
}
}