mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-08-18 09:14:09 +00:00
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:
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user