From af0726a1afb848af830d3ad899c7faa071274e5e Mon Sep 17 00:00:00 2001 From: evanpelle Date: Thu, 26 Sep 2024 20:27:46 -0700 Subject: [PATCH] radial menu can send attack --- src/client/ClientGame.ts | 6 +--- src/client/Transport.ts | 13 ++++--- src/client/graphics/GameRenderer.ts | 2 +- src/client/graphics/layers/RadialMenu.ts | 43 ++++++++++++++++++++---- src/core/Schemas.ts | 2 +- src/core/execution/AttackExecution.ts | 8 +++-- src/core/game/Game.ts | 1 + src/core/game/TerraNulliusImpl.ts | 5 +++ 8 files changed, 57 insertions(+), 23 deletions(-) diff --git a/src/client/ClientGame.ts b/src/client/ClientGame.ts index 1ecac1a58..b46b7744a 100644 --- a/src/client/ClientGame.ts +++ b/src/client/ClientGame.ts @@ -269,11 +269,7 @@ export class ClientGame { this.gs.config().boatAttackAmount(this.myPlayer, owner) )) } else { - this.eventBus.emit(new SendAttackIntentEvent( - targetID, - cell, - this.gs.config().attackAmount(this.myPlayer, owner) - )) + this.eventBus.emit(new SendAttackIntentEvent(targetID)) } } diff --git a/src/client/Transport.ts b/src/client/Transport.ts index 71f8f843f..a292dc646 100644 --- a/src/client/Transport.ts +++ b/src/client/Transport.ts @@ -1,5 +1,5 @@ import {EventBus, GameEvent} from "../core/EventBus" -import {AllianceRequest, Cell, Player, PlayerID, PlayerType} from "../core/game/Game" +import {AllianceRequest, Cell, Game, Player, PlayerID, PlayerType} from "../core/game/Game" import {ClientID, ClientIntentMessageSchema, GameID, Intent} from "../core/Schemas" @@ -31,9 +31,8 @@ export class SendSpawnIntentEvent implements GameEvent { } export class SendAttackIntentEvent implements GameEvent { - constructor(public readonly targetID: PlayerID, - public readonly cell: Cell, - public readonly troops: number + constructor( + public readonly targetID: PlayerID, ) { } } @@ -108,11 +107,11 @@ export class Transport { clientID: this.clientID, attackerID: this.playerID, targetID: event.targetID, - troops: event.troops, + troops: null, sourceX: null, sourceY: null, - targetX: event.cell.x, - targetY: event.cell.y + targetX: null, + targetY: null, }) } diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts index 66284ad2e..cbd22ab31 100644 --- a/src/client/graphics/GameRenderer.ts +++ b/src/client/graphics/GameRenderer.ts @@ -20,7 +20,7 @@ export function createRenderer(canvas: HTMLCanvasElement, game: Game, eventBus: new NameLayer(game, game.config().theme(), transformHandler, clientID), new UILayer(eventBus, game, clientID, transformHandler), new EventsDisplay(eventBus, game, clientID), - new RadialMenu(eventBus), + new RadialMenu(eventBus, game, transformHandler, clientID), ] return new GameRenderer(game, eventBus, canvas, transformHandler, layers) diff --git a/src/client/graphics/layers/RadialMenu.ts b/src/client/graphics/layers/RadialMenu.ts index 972f2a493..56321baad 100644 --- a/src/client/graphics/layers/RadialMenu.ts +++ b/src/client/graphics/layers/RadialMenu.ts @@ -1,9 +1,15 @@ import {EventBus} from "../../../core/EventBus"; -import {ContextMenuEvent} from "../../InputHandler"; +import {Cell, Game, Player, PlayerID} from "../../../core/game/Game"; +import {ClientID} from "../../../core/Schemas"; +import {ContextMenuEvent, MouseUpEvent} from "../../InputHandler"; +import {SendAttackIntentEvent} from "../../Transport"; +import {TransformHandler} from "../TransformHandler"; import {Layer} from "./Layer"; import * as d3 from 'd3'; export class RadialMenu implements Layer { + private clickedCell: Cell | null = null + private menuElement: d3.Selection; private isVisible: boolean = false; private readonly menuItems = [ @@ -16,10 +22,16 @@ export class RadialMenu implements Layer { private readonly menuSize = 300; // Increased size private readonly centerButtonSize = 60; - constructor(private eventBus: EventBus) { } + constructor( + private eventBus: EventBus, + private game: Game, + private transformHandler: TransformHandler, + private clientID: ClientID, + ) { } init() { this.eventBus.on(ContextMenuEvent, e => this.onContextMenu(e)) + this.eventBus.on(MouseUpEvent, e => this.onPointerUp(e)) this.createMenuElement(); } @@ -66,22 +78,30 @@ export class RadialMenu implements Layer { .style('font-size', '14px') .text(d => d.data.name); - // Add center button + // Create a larger, transparent circle for better click detection svg.append('circle') .attr('r', this.centerButtonSize) - .attr('fill', '#2c3e50') + .attr('fill', 'transparent') + .style('cursor', 'pointer') .on('click', () => this.handleCenterButtonClick()) .on('touchstart', (event) => { - event.preventDefault(); this.handleCenterButtonClick(); }); + // Add visible center button circle + svg.append('circle') + .attr('r', this.centerButtonSize - 10) + .attr('fill', '#2c3e50') + .style('pointer-events', 'none'); + + // Add text to the center button svg.append('text') .attr('text-anchor', 'middle') .attr('dy', '0.3em') .attr('fill', 'white') - .style('font-size', '14px') - .text('Close'); + .style('font-size', '16px') + .style('pointer-events', 'none') + .text('Attack'); } tick() { @@ -99,6 +119,7 @@ export class RadialMenu implements Layer { private onContextMenu(event: ContextMenuEvent) { console.log('on context menu') + this.clickedCell = this.transformHandler.screenToWorldCoordinates(event.x, event.y) if (this.isVisible) { this.hideRadialMenu() } else { @@ -106,6 +127,10 @@ export class RadialMenu implements Layer { } } + private onPointerUp(event: MouseUpEvent) { + this.hideRadialMenu() + } + private showRadialMenu(x: number, y: number) { this.menuElement .style('left', `${x - this.menuSize / 2}px`) @@ -126,6 +151,10 @@ export class RadialMenu implements Layer { private handleCenterButtonClick() { console.log('Center button clicked'); + const clicked = this.game.tile(this.clickedCell) + if (clicked.owner().clientID() != this.clientID) { + this.eventBus.emit(new SendAttackIntentEvent(clicked.owner().id())) + } this.hideRadialMenu(); } } \ No newline at end of file diff --git a/src/core/Schemas.ts b/src/core/Schemas.ts index aa7a5a49a..0249506c7 100644 --- a/src/core/Schemas.ts +++ b/src/core/Schemas.ts @@ -54,7 +54,7 @@ export const AttackIntentSchema = BaseIntentSchema.extend({ type: z.literal('attack'), attackerID: z.string(), targetID: z.string().nullable(), - troops: z.number(), + troops: z.number().nullable(), sourceX: z.number().nullable(), sourceY: z.number().nullable(), targetX: z.number().nullable(), diff --git a/src/core/execution/AttackExecution.ts b/src/core/execution/AttackExecution.ts index 453959c2a..030892227 100644 --- a/src/core/execution/AttackExecution.ts +++ b/src/core/execution/AttackExecution.ts @@ -27,7 +27,7 @@ export class AttackExecution implements Execution { private border = new Set() constructor( - private troops: number, + private troops: number | null, private _ownerID: PlayerID, private _targetID: PlayerID | null, private sourceCell: Cell | null, @@ -52,8 +52,12 @@ export class AttackExecution implements Execution { this._owner = mg.player(this._ownerID) this.target = this._targetID == this.mg.terraNullius().id() ? mg.terraNullius() : mg.player(this._targetID) + + if (this.troops == null) { + this.troops = this.mg.config().attackAmount(this._owner, this.target) + } this.troops = Math.min(this._owner.troops(), this.troops) - this._owner.setTroops(this._owner.troops() - this.troops) + this._owner.removeTroops(this.troops) for (const exec of mg.executions()) { if (exec.isActive() && exec instanceof AttackExecution && exec != this) { diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index 3f4ce2dd7..fcc0e72d6 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -120,6 +120,7 @@ export interface TerraNullius { ownsTile(cell: Cell): boolean isPlayer(): false id(): PlayerID // always zero, maybe make it TerraNulliusID? + clientID(): ClientID } export interface Player { diff --git a/src/core/game/TerraNulliusImpl.ts b/src/core/game/TerraNulliusImpl.ts index 663586631..fd5afdac4 100644 --- a/src/core/game/TerraNulliusImpl.ts +++ b/src/core/game/TerraNulliusImpl.ts @@ -1,3 +1,4 @@ +import {ClientID} from "../Schemas"; import {TerraNullius, Cell, Tile, PlayerID} from "./Game"; import {GameImpl} from "./GameImpl"; @@ -8,10 +9,14 @@ export class TerraNulliusImpl implements TerraNullius { constructor(private gs: GameImpl) { } + clientID(): ClientID { + return "TERRA_NULLIUS_CLIENT_ID" + } id(): PlayerID { return null } + ownsTile(cell: Cell): boolean { return this.tiles.has(cell); }