mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 09:20:47 +00:00
Refactor radial menu (#1246)
## Description: Refactor & clean up the Radial menu. * Only show certain build menu items, depending on whether or not you clicked on your own territory * show items as greyed out instead of just the disabled icon * remove back button on hover trigger ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced - [x] 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: evan
This commit is contained in:
@@ -230,7 +230,6 @@ export function createRenderer(
|
||||
emojiTable as EmojiTable,
|
||||
buildMenu,
|
||||
uiState,
|
||||
playerInfo,
|
||||
playerPanel,
|
||||
),
|
||||
new SpawnTimer(game, transformHandler),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { LitElement } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
import { PlayerActions, UnitType } from "../../../core/game/Game";
|
||||
import { PlayerActions, TerraNullius } from "../../../core/game/Game";
|
||||
import { TileRef } from "../../../core/game/GameMap";
|
||||
import { GameView, PlayerView } from "../../../core/game/GameView";
|
||||
import { TransformHandler } from "../TransformHandler";
|
||||
@@ -10,34 +10,29 @@ import { BuildMenu } from "./BuildMenu";
|
||||
import { ChatIntegration } from "./ChatIntegration";
|
||||
import { EmojiTable } from "./EmojiTable";
|
||||
import { Layer } from "./Layer";
|
||||
import { MenuEventManager } from "./MenuEventManager";
|
||||
import { PlayerActionHandler } from "./PlayerActionHandler";
|
||||
import { PlayerInfoOverlay } from "./PlayerInfoOverlay";
|
||||
import { PlayerPanel } from "./PlayerPanel";
|
||||
import { RadialMenu, RadialMenuConfig } from "./RadialMenu";
|
||||
import {
|
||||
centerButtonElement,
|
||||
COLORS,
|
||||
MenuElementParams,
|
||||
rootMenuItems,
|
||||
Slot,
|
||||
} from "./RadialMenuElements";
|
||||
|
||||
import boatIcon from "../../../../resources/images/BoatIconWhite.svg";
|
||||
import buildIcon from "../../../../resources/images/BuildIconWhite.svg";
|
||||
import infoIcon from "../../../../resources/images/InfoIcon.svg";
|
||||
import swordIcon from "../../../../resources/images/SwordIconWhite.svg";
|
||||
import { ContextMenuEvent } from "../../InputHandler";
|
||||
|
||||
@customElement("main-radial-menu")
|
||||
export class MainRadialMenu extends LitElement implements Layer {
|
||||
private radialMenu: RadialMenu;
|
||||
private lastTickRefresh: number = 0;
|
||||
private tickRefreshInterval: number = 500;
|
||||
private needsRefresh: boolean = false;
|
||||
|
||||
private playerActionHandler: PlayerActionHandler;
|
||||
private menuEventManager: MenuEventManager;
|
||||
private chatIntegration: ChatIntegration;
|
||||
|
||||
private clickedTile: TileRef | null = null;
|
||||
private selectedPlayer: PlayerView | TerraNullius | null = null;
|
||||
|
||||
constructor(
|
||||
private eventBus: EventBus,
|
||||
private game: GameView,
|
||||
@@ -45,7 +40,6 @@ export class MainRadialMenu extends LitElement implements Layer {
|
||||
private emojiTable: EmojiTable,
|
||||
private buildMenu: BuildMenu,
|
||||
private uiState: UIState,
|
||||
private playerInfoOverlay: PlayerInfoOverlay,
|
||||
private playerPanel: PlayerPanel,
|
||||
) {
|
||||
super();
|
||||
@@ -70,36 +64,47 @@ export class MainRadialMenu extends LitElement implements Layer {
|
||||
this.uiState,
|
||||
);
|
||||
|
||||
this.menuEventManager = new MenuEventManager(
|
||||
this.eventBus,
|
||||
this.game,
|
||||
this.transformHandler,
|
||||
this.radialMenu,
|
||||
this.buildMenu,
|
||||
this.emojiTable,
|
||||
this.playerInfoOverlay,
|
||||
this.playerPanel,
|
||||
);
|
||||
|
||||
this.chatIntegration = new ChatIntegration(this.game, this.eventBus);
|
||||
|
||||
this.radialMenu.setRootMenuItems(rootMenuItems);
|
||||
this.radialMenu.setRootMenuItems(rootMenuItems, centerButtonElement);
|
||||
}
|
||||
|
||||
init() {
|
||||
this.radialMenu.init();
|
||||
|
||||
this.menuEventManager.setContextMenuCallback((myPlayer, tile, actions) => {
|
||||
this.handlePlayerActions(myPlayer, actions, tile);
|
||||
this.eventBus.on(ContextMenuEvent, (event) => {
|
||||
const worldCoords = this.transformHandler.screenToWorldCoordinates(
|
||||
event.x,
|
||||
event.y,
|
||||
);
|
||||
if (!this.game.isValidCoord(worldCoords.x, worldCoords.y)) {
|
||||
return;
|
||||
}
|
||||
if (this.game.myPlayer() === null) {
|
||||
return;
|
||||
}
|
||||
this.clickedTile = this.game.ref(worldCoords.x, worldCoords.y);
|
||||
this.selectedPlayer = this.game.owner(this.clickedTile);
|
||||
this.game
|
||||
.myPlayer()!
|
||||
.actions(this.clickedTile)
|
||||
.then((actions) => {
|
||||
this.handlePlayerActions(
|
||||
this.game.myPlayer()!,
|
||||
actions,
|
||||
this.clickedTile!,
|
||||
event.x,
|
||||
event.y,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
this.menuEventManager.init();
|
||||
}
|
||||
|
||||
private async handlePlayerActions(
|
||||
myPlayer: PlayerView,
|
||||
actions: PlayerActions,
|
||||
tile: TileRef,
|
||||
screenX: number,
|
||||
screenY: number,
|
||||
) {
|
||||
this.buildMenu.playerActions = actions;
|
||||
|
||||
@@ -113,7 +118,6 @@ export class MainRadialMenu extends LitElement implements Layer {
|
||||
const params: MenuElementParams = {
|
||||
myPlayer,
|
||||
selected: recipient,
|
||||
tileOwner,
|
||||
tile,
|
||||
playerActions: actions,
|
||||
game: this.game,
|
||||
@@ -122,150 +126,22 @@ export class MainRadialMenu extends LitElement implements Layer {
|
||||
playerActionHandler: this.playerActionHandler,
|
||||
playerPanel: this.playerPanel,
|
||||
chatIntegration: this.chatIntegration,
|
||||
closeMenu: () => this.menuEventManager.closeMenu(),
|
||||
closeMenu: () => this.closeMenu(),
|
||||
};
|
||||
|
||||
this.radialMenu.setRootMenuItems(rootMenuItems);
|
||||
this.radialMenu.setRootMenuItems(rootMenuItems, centerButtonElement);
|
||||
this.radialMenu.setParams(params);
|
||||
|
||||
updateCenterButton(params, (enabled, action) => {
|
||||
this.radialMenu.enableCenterButton(enabled, action);
|
||||
});
|
||||
this.radialMenu.showRadialMenu(screenX, screenY);
|
||||
}
|
||||
|
||||
async tick() {
|
||||
const clickedCell = this.menuEventManager.getClickedCell();
|
||||
if (!this.radialMenu.isMenuVisible() || clickedCell === null) return;
|
||||
|
||||
const currentTime = new Date().getTime();
|
||||
if (
|
||||
currentTime - this.lastTickRefresh < this.tickRefreshInterval &&
|
||||
!this.needsRefresh
|
||||
) {
|
||||
if (!this.radialMenu.isMenuVisible() || this.clickedTile === null) return;
|
||||
if (this.selectedPlayer === null) return;
|
||||
const currentPlayer = this.game.owner(this.clickedTile);
|
||||
if (currentPlayer.id() !== this.selectedPlayer.id()) {
|
||||
this.closeMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
const myPlayer = this.game.myPlayer();
|
||||
if (myPlayer === null || !myPlayer.isAlive()) return;
|
||||
|
||||
const tile = this.game.ref(clickedCell.x, clickedCell.y);
|
||||
|
||||
const isSpawnPhase = this.game.inSpawnPhase();
|
||||
const wasInSpawnPhase = this.menuEventManager.getWasInSpawnPhase();
|
||||
|
||||
if (wasInSpawnPhase !== isSpawnPhase) {
|
||||
if (wasInSpawnPhase && !isSpawnPhase) {
|
||||
this.needsRefresh = true;
|
||||
this.menuEventManager.setWasInSpawnPhase(isSpawnPhase);
|
||||
|
||||
const actions = await this.playerActionHandler.getPlayerActions(
|
||||
myPlayer,
|
||||
tile,
|
||||
);
|
||||
this.updateMenuState(myPlayer, actions, tile);
|
||||
this.radialMenu.refreshMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
this.menuEventManager.closeMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if tile ownership has changed
|
||||
const originalTileOwner = this.menuEventManager.getOriginalTileOwner();
|
||||
if (originalTileOwner && originalTileOwner.isPlayer()) {
|
||||
if (this.game.owner(tile) !== originalTileOwner) {
|
||||
this.menuEventManager.closeMenu();
|
||||
return;
|
||||
}
|
||||
} else if (originalTileOwner) {
|
||||
if (
|
||||
this.game.owner(tile).isPlayer() ||
|
||||
this.game.owner(tile) === myPlayer
|
||||
) {
|
||||
this.menuEventManager.closeMenu();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.lastTickRefresh = currentTime;
|
||||
this.needsRefresh = false;
|
||||
|
||||
const actions = await this.playerActionHandler.getPlayerActions(
|
||||
myPlayer,
|
||||
tile,
|
||||
);
|
||||
this.updateMenuState(myPlayer, actions, tile);
|
||||
}
|
||||
|
||||
private updateMenuState(
|
||||
myPlayer: PlayerView,
|
||||
actions: PlayerActions,
|
||||
tile: TileRef,
|
||||
) {
|
||||
if (!this.radialMenu.isMenuVisible()) return;
|
||||
|
||||
const tileOwner = this.game.owner(tile);
|
||||
const recipient = tileOwner.isPlayer() ? (tileOwner as PlayerView) : null;
|
||||
|
||||
const params: MenuElementParams = {
|
||||
myPlayer,
|
||||
selected: recipient,
|
||||
tileOwner,
|
||||
tile,
|
||||
playerActions: actions,
|
||||
game: this.game,
|
||||
buildMenu: this.buildMenu,
|
||||
emojiTable: this.emojiTable,
|
||||
playerActionHandler: this.playerActionHandler,
|
||||
playerPanel: this.playerPanel,
|
||||
chatIntegration: this.chatIntegration,
|
||||
closeMenu: () => this.menuEventManager.closeMenu(),
|
||||
};
|
||||
|
||||
if (this.radialMenu.getCurrentLevel() === 0) {
|
||||
updateCenterButton(params, (enabled, action) => {
|
||||
this.radialMenu.enableCenterButton(enabled, action);
|
||||
});
|
||||
}
|
||||
|
||||
const canBuildTransport = actions.buildableUnits.find(
|
||||
(bu) => bu.type === UnitType.TransportShip,
|
||||
)?.canBuild;
|
||||
|
||||
this.radialMenu.updateMenuItem(
|
||||
Slot.Build,
|
||||
!this.game.inSpawnPhase(),
|
||||
COLORS.build,
|
||||
buildIcon,
|
||||
);
|
||||
|
||||
if (actions?.interaction?.canSendAllianceRequest) {
|
||||
this.radialMenu.updateMenuItem(Slot.Ally, true, COLORS.ally, undefined);
|
||||
} else if (actions?.interaction?.canBreakAlliance) {
|
||||
this.radialMenu.updateMenuItem(
|
||||
Slot.Ally,
|
||||
true,
|
||||
COLORS.breakAlly,
|
||||
undefined,
|
||||
);
|
||||
} else {
|
||||
this.radialMenu.updateMenuItem(Slot.Ally, false, undefined, undefined);
|
||||
}
|
||||
|
||||
this.radialMenu.updateMenuItem(
|
||||
Slot.Boat,
|
||||
!!canBuildTransport,
|
||||
COLORS.boat,
|
||||
boatIcon,
|
||||
);
|
||||
|
||||
this.radialMenu.updateMenuItem(
|
||||
Slot.Info,
|
||||
this.game.hasOwner(tile),
|
||||
COLORS.info,
|
||||
infoIcon,
|
||||
);
|
||||
}
|
||||
|
||||
renderLayer(context: CanvasRenderingContext2D) {
|
||||
@@ -279,23 +155,22 @@ export class MainRadialMenu extends LitElement implements Layer {
|
||||
redraw() {
|
||||
// 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);
|
||||
closeMenu() {
|
||||
if (this.radialMenu.isMenuVisible()) {
|
||||
this.radialMenu.hideRadialMenu();
|
||||
}
|
||||
|
||||
if (this.buildMenu.isVisible) {
|
||||
this.buildMenu.hideMenu();
|
||||
}
|
||||
|
||||
if (this.emojiTable.isVisible) {
|
||||
this.emojiTable.hideTable();
|
||||
}
|
||||
|
||||
if (this.playerPanel.isVisible) {
|
||||
this.playerPanel.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
import { Cell, PlayerActions, TerraNullius } from "../../../core/game/Game";
|
||||
import { TileRef } from "../../../core/game/GameMap";
|
||||
import { GameView, PlayerView } from "../../../core/game/GameView";
|
||||
import {
|
||||
CloseViewEvent,
|
||||
ContextMenuEvent,
|
||||
MouseUpEvent,
|
||||
ShowBuildMenuEvent,
|
||||
} from "../../InputHandler";
|
||||
import { SendSpawnIntentEvent } from "../../Transport";
|
||||
import { TransformHandler } from "../TransformHandler";
|
||||
import { BuildMenu } from "./BuildMenu";
|
||||
import { EmojiTable } from "./EmojiTable";
|
||||
import { PlayerInfoOverlay } from "./PlayerInfoOverlay";
|
||||
import { PlayerPanel } from "./PlayerPanel";
|
||||
import { RadialMenu } from "./RadialMenu";
|
||||
|
||||
export type ContextMenuCallback = (
|
||||
myPlayer: PlayerView,
|
||||
tile: TileRef,
|
||||
actions: PlayerActions,
|
||||
) => void;
|
||||
|
||||
export class MenuEventManager {
|
||||
private clickedCell: Cell | null = null;
|
||||
private lastClosed: number = 0;
|
||||
private originalTileOwner: PlayerView | TerraNullius | null = null;
|
||||
private wasInSpawnPhase: boolean = false;
|
||||
private onContextMenuCallback: ContextMenuCallback | null = null;
|
||||
|
||||
constructor(
|
||||
private eventBus: EventBus,
|
||||
private game: GameView,
|
||||
private transformHandler: TransformHandler,
|
||||
private radialMenu: RadialMenu,
|
||||
private buildMenu: BuildMenu,
|
||||
private emojiTable: EmojiTable,
|
||||
private playerInfoOverlay: PlayerInfoOverlay,
|
||||
private playerPanel: PlayerPanel,
|
||||
) {}
|
||||
|
||||
init() {
|
||||
this.eventBus.on(ContextMenuEvent, (e) => this.onContextMenu(e));
|
||||
this.eventBus.on(MouseUpEvent, (e) => this.onPointerUp(e));
|
||||
this.eventBus.on(CloseViewEvent, () => this.closeMenu());
|
||||
this.eventBus.on(ShowBuildMenuEvent, (e) => this.onShowBuildMenu(e));
|
||||
}
|
||||
|
||||
setContextMenuCallback(callback: ContextMenuCallback) {
|
||||
this.onContextMenuCallback = callback;
|
||||
}
|
||||
|
||||
onContextMenu(event: ContextMenuEvent): Cell | null {
|
||||
if (this.lastClosed + 200 > new Date().getTime()) return null;
|
||||
|
||||
this.closeMenu();
|
||||
|
||||
if (this.radialMenu.isMenuVisible()) {
|
||||
this.radialMenu.hideRadialMenu();
|
||||
return null;
|
||||
} else {
|
||||
this.radialMenu.showRadialMenu(event.x, event.y);
|
||||
}
|
||||
|
||||
this.radialMenu.disableAllButtons();
|
||||
this.clickedCell = this.transformHandler.screenToWorldCoordinates(
|
||||
event.x,
|
||||
event.y,
|
||||
);
|
||||
|
||||
if (
|
||||
!this.clickedCell ||
|
||||
!this.game.isValidCoord(this.clickedCell.x, this.clickedCell.y)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tile = this.game.ref(this.clickedCell.x, this.clickedCell.y);
|
||||
this.originalTileOwner = this.game.owner(tile);
|
||||
this.wasInSpawnPhase = this.game.inSpawnPhase();
|
||||
|
||||
const myPlayer = this.game.myPlayer();
|
||||
if (myPlayer === null) {
|
||||
throw new Error("my player not found");
|
||||
}
|
||||
|
||||
if (myPlayer && !myPlayer.isAlive() && !this.game.inSpawnPhase()) {
|
||||
this.radialMenu.hideRadialMenu();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.game.inSpawnPhase()) {
|
||||
if (this.game.isLand(tile) && !this.game.hasOwner(tile)) {
|
||||
this.radialMenu.enableCenterButton(true, () => {
|
||||
if (this.clickedCell === null) return;
|
||||
this.eventBus.emit(new SendSpawnIntentEvent(this.clickedCell));
|
||||
this.radialMenu.hideRadialMenu();
|
||||
});
|
||||
|
||||
return this.clickedCell;
|
||||
}
|
||||
}
|
||||
|
||||
myPlayer.actions(tile).then((actions) => {
|
||||
if (this.onContextMenuCallback) {
|
||||
this.onContextMenuCallback(myPlayer, tile, actions);
|
||||
}
|
||||
});
|
||||
|
||||
return this.clickedCell;
|
||||
}
|
||||
|
||||
getClickedCell(): Cell | null {
|
||||
return this.clickedCell;
|
||||
}
|
||||
|
||||
getOriginalTileOwner(): PlayerView | TerraNullius | null {
|
||||
return this.originalTileOwner;
|
||||
}
|
||||
|
||||
getWasInSpawnPhase(): boolean {
|
||||
return this.wasInSpawnPhase;
|
||||
}
|
||||
|
||||
setWasInSpawnPhase(value: boolean) {
|
||||
this.wasInSpawnPhase = value;
|
||||
}
|
||||
|
||||
onPointerUp(event: MouseUpEvent) {
|
||||
this.playerInfoOverlay.hide();
|
||||
this.hideEverything();
|
||||
}
|
||||
|
||||
onShowBuildMenu(e: ShowBuildMenuEvent): TileRef | null {
|
||||
const clickedCell = this.transformHandler.screenToWorldCoordinates(
|
||||
e.x,
|
||||
e.y,
|
||||
);
|
||||
if (clickedCell === null) {
|
||||
return null;
|
||||
}
|
||||
if (!this.game.isValidCoord(clickedCell.x, clickedCell.y)) {
|
||||
return null;
|
||||
}
|
||||
const tile = this.game.ref(clickedCell.x, clickedCell.y);
|
||||
const p = this.game.myPlayer();
|
||||
if (p === null) {
|
||||
return null;
|
||||
}
|
||||
this.buildMenu.showMenu(tile);
|
||||
return tile;
|
||||
}
|
||||
|
||||
closeMenu() {
|
||||
if (this.radialMenu.isMenuVisible()) {
|
||||
this.radialMenu.hideRadialMenu();
|
||||
}
|
||||
|
||||
if (this.buildMenu.isVisible) {
|
||||
this.buildMenu.hideMenu();
|
||||
}
|
||||
|
||||
if (this.emojiTable.isVisible) {
|
||||
this.emojiTable.hideTable();
|
||||
}
|
||||
|
||||
if (this.playerPanel.isVisible) {
|
||||
this.playerPanel.hide();
|
||||
}
|
||||
}
|
||||
|
||||
hideEverything() {
|
||||
if (this.radialMenu.isMenuVisible()) {
|
||||
this.radialMenu.hideRadialMenu();
|
||||
this.lastClosed = new Date().getTime();
|
||||
}
|
||||
this.emojiTable.hideTable();
|
||||
this.buildMenu.hideMenu();
|
||||
}
|
||||
|
||||
enableCenterButton(enabled: boolean, action: () => void) {
|
||||
this.radialMenu.enableCenterButton(enabled, action);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
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, MenuElementParams } from "./RadialMenuElements";
|
||||
import {
|
||||
CenterButtonElement,
|
||||
MenuElement,
|
||||
MenuElementParams,
|
||||
} from "./RadialMenuElements";
|
||||
|
||||
export interface TooltipItem {
|
||||
text: string;
|
||||
@@ -24,6 +27,8 @@ export interface RadialMenuConfig {
|
||||
tooltipStyle?: string;
|
||||
}
|
||||
|
||||
type CenterButtonState = "default" | "back";
|
||||
|
||||
type RequiredRadialMenuConfig = Required<RadialMenuConfig>;
|
||||
|
||||
export class RadialMenu implements Layer {
|
||||
@@ -39,11 +44,8 @@ export class RadialMenu implements Layer {
|
||||
private readonly config: RequiredRadialMenuConfig;
|
||||
private readonly backIconSize: number;
|
||||
|
||||
private isCenterButtonEnabled = false;
|
||||
private originalCenterButtonEnabled = false;
|
||||
private centerButtonAction: (() => void) | null = null;
|
||||
private originalCenterButtonAction: (() => void) | null = null;
|
||||
private backAction: (() => void) | null = null;
|
||||
private centerButtonState: CenterButtonState = "default";
|
||||
private centerButtonElement: CenterButtonElement | null = null;
|
||||
|
||||
private isTransitioning: boolean = false;
|
||||
private lastHideTime: number = 0;
|
||||
@@ -68,7 +70,7 @@ export class RadialMenu implements Layer {
|
||||
private navigationInProgress: boolean = false;
|
||||
private originalCenterButtonIcon: string = "";
|
||||
|
||||
private params: MenuElementParams;
|
||||
private params: MenuElementParams | null = null;
|
||||
|
||||
constructor(config: RadialMenuConfig = {}) {
|
||||
this.config = {
|
||||
@@ -215,15 +217,15 @@ export class RadialMenu implements Layer {
|
||||
}
|
||||
|
||||
private getInnerRadiusForLevel(level: number): number {
|
||||
return level === 0
|
||||
? this.config.mainMenuInnerRadius
|
||||
: this.config.mainMenuInnerRadius + 34;
|
||||
return level === 0 ? 50 : 50 + 25;
|
||||
}
|
||||
|
||||
private getOuterRadiusForLevel(level: number): number {
|
||||
const innerRadius = this.getInnerRadiusForLevel(level);
|
||||
const arcWidth =
|
||||
this.config.menuSize / 2 - this.config.mainMenuInnerRadius - 10;
|
||||
let arcWidth = 55;
|
||||
if (level !== 0) {
|
||||
arcWidth = 65;
|
||||
}
|
||||
return innerRadius + arcWidth;
|
||||
}
|
||||
|
||||
@@ -244,12 +246,14 @@ export class RadialMenu implements Layer {
|
||||
|
||||
this.menuGroups.set(level, menuGroup as any);
|
||||
|
||||
const offset = -Math.PI / items.length;
|
||||
|
||||
const pie = d3
|
||||
.pie<MenuElement>()
|
||||
.value(() => 1)
|
||||
.padAngle(0.03)
|
||||
.startAngle(Math.PI / 3)
|
||||
.endAngle(2 * Math.PI + Math.PI / 3);
|
||||
.startAngle(offset)
|
||||
.endAngle(2 * Math.PI + offset);
|
||||
|
||||
const innerRadius = this.getInnerRadiusForLevel(level);
|
||||
const outerRadius = this.getOuterRadiusForLevel(level);
|
||||
@@ -289,10 +293,11 @@ export class RadialMenu implements Layer {
|
||||
.attr("class", "menu-item-path")
|
||||
.attr("d", arc)
|
||||
.attr("fill", (d) => {
|
||||
const color = d.data.disabled(this.params)
|
||||
const disabled = this.params === null || d.data.disabled(this.params);
|
||||
const color = disabled
|
||||
? this.config.disabledColor
|
||||
: d.data.color || "#333333";
|
||||
const opacity = d.data.disabled(this.params) ? 0.5 : 0.7;
|
||||
const opacity = disabled ? 0.5 : 0.7;
|
||||
|
||||
if (d.data.id === this.selectedItemId && this.currentLevel > level) {
|
||||
return color;
|
||||
@@ -303,9 +308,13 @@ export class RadialMenu implements Layer {
|
||||
.attr("stroke", "#ffffff")
|
||||
.attr("stroke-width", "2")
|
||||
.style("cursor", (d) =>
|
||||
d.data.disabled(this.params) ? "not-allowed" : "pointer",
|
||||
this.params === null || d.data.disabled(this.params)
|
||||
? "not-allowed"
|
||||
: "pointer",
|
||||
)
|
||||
.style("opacity", (d) =>
|
||||
this.params === null || d.data.disabled(this.params) ? 0.5 : 1,
|
||||
)
|
||||
.style("opacity", (d) => (d.data.disabled(this.params) ? 0.5 : 1))
|
||||
.style(
|
||||
"transition",
|
||||
`filter ${this.config.menuTransitionDuration / 2}ms, stroke-width ${
|
||||
@@ -327,9 +336,10 @@ export class RadialMenu implements Layer {
|
||||
path.attr("filter", "url(#glow)");
|
||||
path.attr("stroke-width", "3");
|
||||
|
||||
const color = d.data.disabled(this.params)
|
||||
? this.config.disabledColor
|
||||
: d.data.color || "#333333";
|
||||
const color =
|
||||
this.params === null || d.data.disabled(this.params)
|
||||
? this.config.disabledColor
|
||||
: d.data.color || "#333333";
|
||||
path.attr("fill", color);
|
||||
}
|
||||
});
|
||||
@@ -357,29 +367,31 @@ export class RadialMenu implements Layer {
|
||||
level: number,
|
||||
) {
|
||||
const onHover = (d: d3.PieArcDatum<MenuElement>, path: any) => {
|
||||
const disabled = this.params === null || d.data.disabled(this.params);
|
||||
if (d.data.tooltipItems && d.data.tooltipItems.length > 0) {
|
||||
this.showTooltip(d.data.tooltipItems);
|
||||
}
|
||||
if (
|
||||
d.data.disabled(this.params) ||
|
||||
disabled ||
|
||||
(this.currentLevel > 0 && this.currentLevel !== level) ||
|
||||
this.navigationInProgress
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
path.attr("filter", "url(#glow)");
|
||||
path.attr("stroke-width", "3");
|
||||
const color = d.data.disabled(this.params)
|
||||
const color = disabled
|
||||
? this.config.disabledColor
|
||||
: d.data.color || "#333333";
|
||||
path.attr("fill", color);
|
||||
|
||||
if (d.data.tooltipItems && d.data.tooltipItems.length > 0) {
|
||||
this.showTooltip(d.data.tooltipItems);
|
||||
}
|
||||
|
||||
const subMenu = d.data.subMenu?.(this.params);
|
||||
const subMenu =
|
||||
this.params !== null ? d.data.subMenu?.(this.params) : null;
|
||||
if (
|
||||
subMenu &&
|
||||
subMenu.length > 0 &&
|
||||
!d.data.disabled(this.params) &&
|
||||
!disabled &&
|
||||
!(
|
||||
this.currentLevel > 0 &&
|
||||
d.data.id === this.selectedItemId &&
|
||||
@@ -396,12 +408,13 @@ export class RadialMenu implements Layer {
|
||||
this.navigationInProgress = true;
|
||||
this.selectedItemId = d.data.id;
|
||||
this.navigateToSubMenu(subMenu);
|
||||
this.setCenterButtonAsBack();
|
||||
this.updateCenterButtonState("back");
|
||||
}, 200);
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseOut = (d: d3.PieArcDatum<MenuElement>, path: any) => {
|
||||
const disabled = this.params === null || d.data.disabled(this.params);
|
||||
if (this.submenuHoverTimeout !== null) {
|
||||
window.clearTimeout(this.submenuHoverTimeout);
|
||||
this.submenuHoverTimeout = null;
|
||||
@@ -410,7 +423,7 @@ export class RadialMenu implements Layer {
|
||||
this.hideTooltip();
|
||||
|
||||
if (
|
||||
d.data.disabled(this.params) ||
|
||||
disabled ||
|
||||
(this.currentLevel > 0 &&
|
||||
level === 0 &&
|
||||
d.data.id === this.selectedItemId)
|
||||
@@ -418,10 +431,10 @@ export class RadialMenu implements Layer {
|
||||
return;
|
||||
path.attr("filter", null);
|
||||
path.attr("stroke-width", "2");
|
||||
const color = d.data.disabled(this.params)
|
||||
const color = disabled
|
||||
? this.config.disabledColor
|
||||
: d.data.color || "#333333";
|
||||
const opacity = d.data.disabled(this.params) ? 0.5 : 0.7;
|
||||
const opacity = disabled ? 0.5 : 0.7;
|
||||
path.attr(
|
||||
"fill",
|
||||
d3.color(color)?.copy({ opacity: opacity })?.toString() || color,
|
||||
@@ -430,7 +443,12 @@ export class RadialMenu implements Layer {
|
||||
|
||||
const onClick = (d: d3.PieArcDatum<MenuElement>, event: Event) => {
|
||||
event.stopPropagation();
|
||||
if (d.data.disabled(this.params) || this.navigationInProgress) return;
|
||||
if (
|
||||
this.params === null ||
|
||||
d.data.disabled(this.params) ||
|
||||
this.navigationInProgress
|
||||
)
|
||||
return;
|
||||
|
||||
if (
|
||||
this.currentLevel > 0 &&
|
||||
@@ -444,7 +462,7 @@ export class RadialMenu implements Layer {
|
||||
this.navigationInProgress = true;
|
||||
this.selectedItemId = d.data.id;
|
||||
this.navigateToSubMenu(subMenu);
|
||||
this.setCenterButtonAsBack();
|
||||
this.updateCenterButtonState("back");
|
||||
} else {
|
||||
d.data.action?.(this.params);
|
||||
this.hideRadialMenu();
|
||||
@@ -506,6 +524,10 @@ export class RadialMenu implements Layer {
|
||||
.each((d) => {
|
||||
const contentId = d.data.id;
|
||||
const content = d3.select(`g[data-id="${contentId}"]`);
|
||||
const disabled =
|
||||
this.params === null ||
|
||||
this.params.game.inSpawnPhase() ||
|
||||
d.data.disabled(this.params);
|
||||
|
||||
if (d.data.text) {
|
||||
content
|
||||
@@ -517,21 +539,17 @@ 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(this.params) ? 0.5 : 1)
|
||||
.style("opacity", disabled ? 0.5 : 1)
|
||||
.text(d.data.text);
|
||||
} else {
|
||||
content
|
||||
.append("image")
|
||||
.attr(
|
||||
"xlink:href",
|
||||
d.data.disabled(this.params)
|
||||
? disabledIcon
|
||||
: d.data.icon || disabledIcon,
|
||||
)
|
||||
.attr("xlink:href", d.data.icon!)
|
||||
.attr("width", this.config.iconSize)
|
||||
.attr("height", this.config.iconSize)
|
||||
.attr("x", arc.centroid(d)[0] - this.config.iconSize / 2)
|
||||
.attr("y", arc.centroid(d)[1] - this.config.iconSize / 2);
|
||||
.attr("y", arc.centroid(d)[1] - this.config.iconSize / 2)
|
||||
.attr("opacity", disabled ? 0.5 : 1);
|
||||
}
|
||||
|
||||
this.menuIcons.set(contentId, content as any);
|
||||
@@ -577,7 +595,7 @@ export class RadialMenu implements Layer {
|
||||
menuGroup
|
||||
.transition()
|
||||
.duration(this.config.menuTransitionDuration * 0.8)
|
||||
.style("transform", "scale(0.59)")
|
||||
.style("transform", "scale(0.5)")
|
||||
.style("opacity", 0.8);
|
||||
|
||||
menuGroup.selectAll("path").each(function () {
|
||||
@@ -606,7 +624,7 @@ export class RadialMenu implements Layer {
|
||||
currentMenu
|
||||
.transition()
|
||||
.duration(this.config.menuTransitionDuration * 0.8)
|
||||
.style("transform", `scale(${this.currentLevel === 1 ? "0.8" : "0.59"})`)
|
||||
.style("transform", `scale(${this.currentLevel === 1 ? "0.65" : "0.5"})`)
|
||||
.style("opacity", 0.8)
|
||||
.on("end", () => {
|
||||
this.navigationInProgress = false;
|
||||
@@ -638,7 +656,7 @@ export class RadialMenu implements Layer {
|
||||
this.currentMenuItems = previousItems || [];
|
||||
|
||||
if (this.currentLevel === 0) {
|
||||
this.resetCenterButton();
|
||||
this.updateCenterButtonState("default");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -652,10 +670,11 @@ export class RadialMenu implements Layer {
|
||||
|
||||
const item = this.findMenuItem(this.selectedItemId);
|
||||
if (item) {
|
||||
const color = item.disabled(this.params)
|
||||
const disabled = this.params === null || item.disabled(this.params);
|
||||
const color = disabled
|
||||
? this.config.disabledColor
|
||||
: item.color || "#333333";
|
||||
const opacity = item.disabled(this.params) ? 0.5 : 0.7;
|
||||
const opacity = disabled ? 0.5 : 0.7;
|
||||
selectedPath.attr(
|
||||
"fill",
|
||||
d3.color(color)?.copy({ opacity: opacity })?.toString() || color,
|
||||
@@ -683,7 +702,7 @@ export class RadialMenu implements Layer {
|
||||
.duration(this.config.menuTransitionDuration * 0.8)
|
||||
.style(
|
||||
"transform",
|
||||
`scale(${this.currentLevel === 1 ? "0.8" : "0.59"})`,
|
||||
`scale(${this.currentLevel === 1 ? "0.65" : "0.5"})`,
|
||||
)
|
||||
.style("opacity", 0.8);
|
||||
} else if (level !== this.currentLevel + 1) {
|
||||
@@ -754,56 +773,6 @@ export class RadialMenu implements Layer {
|
||||
previousMenu.selectAll("path").style("pointer-events", "auto");
|
||||
}
|
||||
|
||||
private setCenterButtonAsBack() {
|
||||
if (this.currentLevel === 1) {
|
||||
this.originalCenterButtonEnabled = this.isCenterButtonEnabled;
|
||||
this.originalCenterButtonAction = this.centerButtonAction;
|
||||
}
|
||||
|
||||
this.backAction = () => {
|
||||
this.navigateBack();
|
||||
};
|
||||
|
||||
// Clear any hover state on the center button
|
||||
this.menuElement
|
||||
.select(".center-button-hitbox")
|
||||
.transition()
|
||||
.duration(0)
|
||||
.attr("r", this.config.centerButtonSize);
|
||||
this.menuElement
|
||||
.select(".center-button-visible")
|
||||
.transition()
|
||||
.duration(0)
|
||||
.attr("r", this.config.centerButtonSize);
|
||||
|
||||
const backIconImg = this.menuElement.select(".center-button-icon");
|
||||
backIconImg
|
||||
.attr("xlink:href", backIcon)
|
||||
.attr("width", this.backIconSize)
|
||||
.attr("height", this.backIconSize)
|
||||
.attr("x", -this.backIconSize / 2)
|
||||
.attr("y", -this.backIconSize / 2);
|
||||
|
||||
this.enableCenterButton(true, this.backAction);
|
||||
}
|
||||
|
||||
private resetCenterButton() {
|
||||
this.backAction = null;
|
||||
|
||||
const iconImg = this.menuElement.select(".center-button-icon");
|
||||
iconImg
|
||||
.attr("xlink:href", this.originalCenterButtonIcon)
|
||||
.attr("width", this.config.centerIconSize)
|
||||
.attr("height", this.config.centerIconSize)
|
||||
.attr("x", -this.config.centerIconSize / 2)
|
||||
.attr("y", -this.config.centerIconSize / 2);
|
||||
|
||||
this.enableCenterButton(
|
||||
this.originalCenterButtonEnabled,
|
||||
this.originalCenterButtonAction,
|
||||
);
|
||||
}
|
||||
|
||||
public showRadialMenu(x: number, y: number) {
|
||||
if (!this.isReopeningAllowed()) return;
|
||||
|
||||
@@ -846,65 +815,86 @@ export class RadialMenu implements Layer {
|
||||
}
|
||||
|
||||
private handleCenterButtonClick() {
|
||||
if (
|
||||
!this.isCenterButtonEnabled ||
|
||||
!this.centerButtonAction ||
|
||||
this.navigationInProgress
|
||||
) {
|
||||
if (this.centerButtonState === "default") {
|
||||
if (this.params) {
|
||||
this.centerButtonElement?.action(this.params);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.currentLevel > 0 && this.backAction) {
|
||||
if (this.centerButtonState === "back") {
|
||||
this.navigationInProgress = true;
|
||||
this.navigateBack();
|
||||
return;
|
||||
}
|
||||
|
||||
this.centerButtonAction();
|
||||
}
|
||||
|
||||
public disableAllButtons() {
|
||||
this.originalCenterButtonEnabled = this.isCenterButtonEnabled;
|
||||
this.originalCenterButtonAction = this.centerButtonAction;
|
||||
|
||||
this.enableCenterButton(false);
|
||||
this.updateCenterButtonState("default");
|
||||
|
||||
for (const item of this.currentMenuItems) {
|
||||
item.color = this.config.disabledColor;
|
||||
}
|
||||
}
|
||||
|
||||
public enableCenterButton(enabled: boolean, action?: (() => void) | null) {
|
||||
if (this.currentLevel > 0 && this.backAction) {
|
||||
this.isCenterButtonEnabled = true;
|
||||
public updateCenterButtonState(state: CenterButtonState) {
|
||||
this.centerButtonState = state;
|
||||
if (state === "back") {
|
||||
this.menuElement
|
||||
.select(".center-button-hitbox")
|
||||
.transition()
|
||||
.duration(0)
|
||||
.attr("r", this.config.centerButtonSize);
|
||||
this.menuElement
|
||||
.select(".center-button-visible")
|
||||
.transition()
|
||||
.duration(0)
|
||||
.attr("r", this.config.centerButtonSize);
|
||||
|
||||
if (action !== undefined && action !== this.backAction) {
|
||||
this.originalCenterButtonAction = action;
|
||||
}
|
||||
|
||||
this.centerButtonAction = this.backAction;
|
||||
} else {
|
||||
this.isCenterButtonEnabled = enabled;
|
||||
if (action !== undefined) {
|
||||
this.centerButtonAction = action;
|
||||
}
|
||||
const backIconImg = this.menuElement.select(".center-button-icon");
|
||||
backIconImg
|
||||
.attr("xlink:href", backIcon)
|
||||
.attr("width", this.backIconSize)
|
||||
.attr("height", this.backIconSize)
|
||||
.attr("x", -this.backIconSize / 2)
|
||||
.attr("y", -this.backIconSize / 2);
|
||||
}
|
||||
if (state === "default") {
|
||||
const iconImg = this.menuElement.select(".center-button-icon");
|
||||
iconImg
|
||||
.attr("xlink:href", this.originalCenterButtonIcon)
|
||||
.attr("width", this.config.centerIconSize)
|
||||
.attr("height", this.config.centerIconSize)
|
||||
.attr("x", -this.config.centerIconSize / 2)
|
||||
.attr("y", -this.config.centerIconSize / 2);
|
||||
}
|
||||
|
||||
const centerButton = this.menuElement.select(".center-button");
|
||||
|
||||
const enabled = this.isCenterButtonEnabled();
|
||||
|
||||
centerButton
|
||||
.select(".center-button-hitbox")
|
||||
.style("cursor", this.isCenterButtonEnabled ? "pointer" : "not-allowed");
|
||||
.style("cursor", enabled ? "pointer" : "not-allowed");
|
||||
|
||||
centerButton
|
||||
.select(".center-button-visible")
|
||||
.attr("fill", this.isCenterButtonEnabled ? "#2c3e50" : "#999999");
|
||||
.attr("fill", enabled ? "#2c3e50" : "#999999");
|
||||
|
||||
centerButton
|
||||
.select(".center-button-icon")
|
||||
.style("opacity", this.isCenterButtonEnabled ? 1 : 0.5);
|
||||
.style("opacity", enabled ? 1 : 0.5);
|
||||
}
|
||||
|
||||
private isCenterButtonEnabled(): boolean {
|
||||
if (this.params && this.centerButtonElement) {
|
||||
return !this.centerButtonElement.disabled(this.params);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private onCenterButtonHover(isHovering: boolean) {
|
||||
if (!this.isCenterButtonEnabled) return;
|
||||
if (!this.isCenterButtonEnabled()) return;
|
||||
|
||||
const scale = isHovering ? 1.2 : 1;
|
||||
|
||||
@@ -919,26 +909,6 @@ export class RadialMenu implements Layer {
|
||||
.transition()
|
||||
.duration(200)
|
||||
.attr("r", this.config.centerButtonSize * scale);
|
||||
|
||||
if (this.currentLevel > 0 && this.backAction) {
|
||||
if (isHovering) {
|
||||
if (this.backButtonHoverTimeout !== null) {
|
||||
window.clearTimeout(this.backButtonHoverTimeout);
|
||||
}
|
||||
|
||||
this.backButtonHoverTimeout = window.setTimeout(() => {
|
||||
if (this.navigationInProgress || !this.backAction) return;
|
||||
|
||||
this.navigationInProgress = true;
|
||||
this.backAction();
|
||||
}, 300);
|
||||
} else {
|
||||
if (this.backButtonHoverTimeout !== null) {
|
||||
window.clearTimeout(this.backButtonHoverTimeout);
|
||||
this.backButtonHoverTimeout = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public isMenuVisible(): boolean {
|
||||
@@ -949,59 +919,13 @@ export class RadialMenu implements Layer {
|
||||
return this.currentLevel;
|
||||
}
|
||||
|
||||
public updateMenuItem(
|
||||
id: string,
|
||||
enabled: boolean,
|
||||
color?: string,
|
||||
icon?: string,
|
||||
text?: string,
|
||||
public setRootMenuItems(
|
||||
items: MenuElement[],
|
||||
centerButton: CenterButtonElement,
|
||||
) {
|
||||
const path = this.menuPaths.get(id);
|
||||
if (!path) return;
|
||||
|
||||
const item = this.findMenuItem(id);
|
||||
if (item) {
|
||||
if (color) item.color = enabled ? color : this.config.disabledColor;
|
||||
if (icon) item.icon = icon;
|
||||
if (text !== undefined) item.text = text;
|
||||
}
|
||||
|
||||
const fillColor = enabled && color ? color : this.config.disabledColor;
|
||||
const opacity = enabled ? 0.7 : 0.5;
|
||||
|
||||
const isSelected = id === this.selectedItemId && this.currentLevel > 0;
|
||||
const finalOpacity = isSelected ? 1.0 : opacity;
|
||||
|
||||
path
|
||||
.attr(
|
||||
"fill",
|
||||
d3.color(fillColor)?.copy({ opacity: finalOpacity })?.toString() ||
|
||||
fillColor,
|
||||
)
|
||||
.style("opacity", enabled ? 1 : 0.5)
|
||||
.style("cursor", enabled ? "pointer" : "not-allowed");
|
||||
|
||||
const iconElement = this.menuIcons.get(id);
|
||||
if (iconElement) {
|
||||
if (item?.text) {
|
||||
const textElement = iconElement.select("text");
|
||||
if (textElement.size() > 0) {
|
||||
textElement
|
||||
.style("opacity", enabled ? 1 : 0.5)
|
||||
.text(text || item.text);
|
||||
}
|
||||
} else if (icon) {
|
||||
const imageElement = iconElement.select("image");
|
||||
if (imageElement.size() > 0) {
|
||||
imageElement.attr("xlink:href", enabled ? icon : disabledIcon);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public setRootMenuItems(items: MenuElement[]) {
|
||||
this.currentMenuItems = [...items];
|
||||
this.rootMenuItems = [...items];
|
||||
this.centerButtonElement = centerButton;
|
||||
if (this.isVisible) {
|
||||
this.refreshMenu();
|
||||
}
|
||||
@@ -1021,7 +945,6 @@ export class RadialMenu implements Layer {
|
||||
|
||||
this.currentMenuItems = [...this.rootMenuItems];
|
||||
|
||||
this.backAction = null;
|
||||
this.navigationInProgress = false;
|
||||
|
||||
this.menuGroups.clear();
|
||||
@@ -1033,7 +956,7 @@ export class RadialMenu implements Layer {
|
||||
menuContainer.selectAll("[class^='menu-level-']").remove();
|
||||
}
|
||||
|
||||
this.resetCenterButton();
|
||||
this.updateCenterButtonState("default");
|
||||
|
||||
if (this.submenuHoverTimeout !== null) {
|
||||
window.clearTimeout(this.submenuHoverTimeout);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
AllPlayers,
|
||||
Cell,
|
||||
PlayerActions,
|
||||
TerraNullius,
|
||||
UnitType,
|
||||
} from "../../../core/game/Game";
|
||||
import { TileRef } from "../../../core/game/GameMap";
|
||||
@@ -29,7 +29,6 @@ import traitorIcon from "../../../../resources/images/TraitorIconWhite.svg";
|
||||
export interface MenuElementParams {
|
||||
myPlayer: PlayerView;
|
||||
selected: PlayerView | null;
|
||||
tileOwner: PlayerView | TerraNullius;
|
||||
tile: TileRef;
|
||||
playerActions: PlayerActions;
|
||||
game: GameView;
|
||||
@@ -56,6 +55,11 @@ export interface MenuElement {
|
||||
subMenu?: (params: MenuElementParams) => MenuElement[]; // For non-leaf items that open submenus
|
||||
}
|
||||
|
||||
export interface CenterButtonElement {
|
||||
disabled: (params: MenuElementParams) => boolean;
|
||||
action: (params: MenuElementParams) => void;
|
||||
}
|
||||
|
||||
export const COLORS = {
|
||||
build: "#ebe250",
|
||||
building: "#2c2c2c",
|
||||
@@ -111,7 +115,10 @@ const infoChatElement: MenuElement = {
|
||||
const allyTargetElement: MenuElement = {
|
||||
id: "ally_target",
|
||||
name: "target",
|
||||
disabled: () => false,
|
||||
disabled: (params: MenuElementParams): boolean => {
|
||||
if (params.selected === null) return true;
|
||||
return !params.playerActions.interaction?.canTarget;
|
||||
},
|
||||
color: COLORS.target,
|
||||
icon: targetIcon,
|
||||
action: (params: MenuElementParams) => {
|
||||
@@ -130,7 +137,7 @@ const allyTradeElement: MenuElement = {
|
||||
color: COLORS.trade,
|
||||
text: translateText("player_panel.start_trade"),
|
||||
action: (params: MenuElementParams) => {
|
||||
params.playerActionHandler.handleEmbargo(params.selected!, "start");
|
||||
params.playerActionHandler.handleEmbargo(params.selected!, "stop");
|
||||
params.closeMenu();
|
||||
},
|
||||
};
|
||||
@@ -145,7 +152,7 @@ const allyEmbargoElement: MenuElement = {
|
||||
color: COLORS.embargo,
|
||||
text: translateText("player_panel.stop_trade"),
|
||||
action: (params: MenuElementParams) => {
|
||||
params.playerActionHandler.handleEmbargo(params.selected!, "stop");
|
||||
params.playerActionHandler.handleEmbargo(params.selected!, "start");
|
||||
params.closeMenu();
|
||||
},
|
||||
};
|
||||
@@ -230,9 +237,30 @@ const infoEmojiElement: MenuElement = {
|
||||
color: COLORS.infoEmoji,
|
||||
icon: emojiIcon,
|
||||
subMenu: (params: MenuElementParams) => {
|
||||
const emojiElements: MenuElement[] = [];
|
||||
const emojiElements: MenuElement[] = [
|
||||
{
|
||||
id: "emoji_more",
|
||||
name: "more",
|
||||
disabled: () => false,
|
||||
color: COLORS.infoEmoji,
|
||||
icon: emojiIcon,
|
||||
action: (params: MenuElementParams) => {
|
||||
params.emojiTable.showTable((emoji) => {
|
||||
const targetPlayer =
|
||||
params.selected === params.game.myPlayer()
|
||||
? AllPlayers
|
||||
: params.selected;
|
||||
params.playerActionHandler.handleEmoji(
|
||||
targetPlayer!,
|
||||
flattenedEmojiTable.indexOf(emoji),
|
||||
);
|
||||
params.emojiTable.hideTable();
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const emojiCount = 15;
|
||||
const emojiCount = 8;
|
||||
for (let i = 0; i < emojiCount; i++) {
|
||||
emojiElements.push({
|
||||
id: `emoji_${i}`,
|
||||
@@ -251,27 +279,6 @@ const infoEmojiElement: MenuElement = {
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
},
|
||||
};
|
||||
@@ -279,32 +286,46 @@ const infoEmojiElement: MenuElement = {
|
||||
export const infoMenuElement: MenuElement = {
|
||||
id: Slot.Info,
|
||||
name: "info",
|
||||
disabled: () => false,
|
||||
disabled: (params: MenuElementParams) =>
|
||||
!params.selected || params.game.inSpawnPhase(),
|
||||
icon: infoIcon,
|
||||
color: COLORS.info,
|
||||
|
||||
subMenu: (params: MenuElementParams) => {
|
||||
if (params === undefined || params.selected === null) return [];
|
||||
if (!params.selected || params.game.inSpawnPhase()) return [];
|
||||
|
||||
return [
|
||||
infoChatElement,
|
||||
allyTargetElement,
|
||||
allyTradeElement,
|
||||
allyEmbargoElement,
|
||||
allyRequestElement,
|
||||
allyBreakElement,
|
||||
allyDonateGoldElement,
|
||||
allyDonateTroopsElement,
|
||||
if (params.selected === params.myPlayer) {
|
||||
return [infoPlayerElement, infoEmojiElement];
|
||||
}
|
||||
|
||||
const elements: MenuElement[] = [
|
||||
infoPlayerElement,
|
||||
infoEmojiElement,
|
||||
].filter((item) => item.displayed !== false);
|
||||
infoChatElement,
|
||||
];
|
||||
if (params.myPlayer.isAlliedWith(params.selected)) {
|
||||
elements.push(
|
||||
allyBreakElement,
|
||||
allyDonateGoldElement,
|
||||
allyDonateTroopsElement,
|
||||
);
|
||||
} else {
|
||||
elements.push(allyTargetElement, allyRequestElement);
|
||||
}
|
||||
if (params.myPlayer.hasEmbargoAgainst(params.selected)) {
|
||||
elements.push(allyTradeElement);
|
||||
} else {
|
||||
elements.push(allyEmbargoElement);
|
||||
}
|
||||
|
||||
return elements;
|
||||
},
|
||||
};
|
||||
|
||||
export const buildMenuElement: MenuElement = {
|
||||
id: Slot.Build,
|
||||
name: "build",
|
||||
disabled: () => false,
|
||||
disabled: (params: MenuElementParams) => params.game.inSpawnPhase(),
|
||||
icon: buildIcon,
|
||||
color: COLORS.build,
|
||||
|
||||
@@ -367,7 +388,10 @@ export const buildMenuElement: MenuElement = {
|
||||
export const boatMenuElement: MenuElement = {
|
||||
id: Slot.Boat,
|
||||
name: "boat",
|
||||
disabled: () => false,
|
||||
disabled: (params: MenuElementParams) =>
|
||||
!params.playerActions.buildableUnits.some(
|
||||
(unit) => unit.type === UnitType.TransportShip && unit.canBuild,
|
||||
),
|
||||
icon: boatIcon,
|
||||
color: COLORS.boat,
|
||||
|
||||
@@ -388,8 +412,40 @@ export const boatMenuElement: MenuElement = {
|
||||
},
|
||||
};
|
||||
|
||||
export const centerButtonElement: CenterButtonElement = {
|
||||
disabled: (params: MenuElementParams): boolean => {
|
||||
const tileOwner = params.game.owner(params.tile);
|
||||
const isLand = params.game.isLand(params.tile);
|
||||
if (!isLand) {
|
||||
return true;
|
||||
}
|
||||
if (params.game.inSpawnPhase()) {
|
||||
if (tileOwner.isPlayer()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
action: (params: MenuElementParams) => {
|
||||
if (params.game.inSpawnPhase()) {
|
||||
const cell = new Cell(
|
||||
params.game.x(params.tile),
|
||||
params.game.y(params.tile),
|
||||
);
|
||||
params.playerActionHandler.handleSpawn(cell);
|
||||
} else {
|
||||
params.playerActionHandler.handleAttack(
|
||||
params.myPlayer,
|
||||
params.selected?.id() ?? null,
|
||||
);
|
||||
}
|
||||
params.closeMenu();
|
||||
},
|
||||
};
|
||||
|
||||
export const rootMenuItems: MenuElement[] = [
|
||||
infoMenuElement,
|
||||
boatMenuElement,
|
||||
buildMenuElement,
|
||||
infoMenuElement,
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user