From 3ab9f3b617381d21017153eef3b1aa59643eee3e Mon Sep 17 00:00:00 2001 From: Evan Date: Wed, 30 Oct 2024 17:31:01 -0700 Subject: [PATCH] add troop send amount bar. can full send! --- src/client/ClientGame.ts | 5 +- src/client/Transport.ts | 3 +- src/client/graphics/GameRenderer.ts | 40 +++++---- src/client/graphics/UIState.ts | 3 + src/client/graphics/layers/ControlPanel.ts | 85 ++++++++++++++++--- .../graphics/layers/radial/RadialMenu.ts | 37 ++++---- 6 files changed, 123 insertions(+), 50 deletions(-) create mode 100644 src/client/graphics/UIState.ts diff --git a/src/client/ClientGame.ts b/src/client/ClientGame.ts index 062c2abba..d4663178a 100644 --- a/src/client/ClientGame.ts +++ b/src/client/ClientGame.ts @@ -214,14 +214,13 @@ export class GameRunner { if (tile.isLand()) { if (tile.hasOwner()) { if (this.myPlayer.sharesBorderWith(tile.owner())) { - this.eventBus.emit(new SendAttackIntentEvent(targetID)) + this.eventBus.emit(new SendAttackIntentEvent(targetID, this.myPlayer.troops() * this.renderer.uiState.attackRatio)) } } else { - outer_loop: for (const t of bfs(tile, and(t => !t.hasOwner() && t.isLand(), dist(tile, 200)))) { for (const n of t.neighbors()) { if (n.owner() == this.myPlayer) { - this.eventBus.emit(new SendAttackIntentEvent(targetID)) + this.eventBus.emit(new SendAttackIntentEvent(targetID, this.myPlayer.troops() * this.renderer.uiState.attackRatio)) break outer_loop } } diff --git a/src/client/Transport.ts b/src/client/Transport.ts index e17bd68b9..8fd187f59 100644 --- a/src/client/Transport.ts +++ b/src/client/Transport.ts @@ -35,6 +35,7 @@ export class SendSpawnIntentEvent implements GameEvent { export class SendAttackIntentEvent implements GameEvent { constructor( public readonly targetID: PlayerID, + public readonly troops: number, ) { } } @@ -237,7 +238,7 @@ export class Transport { clientID: this.clientID, attackerID: this.playerID, targetID: event.targetID, - troops: null, + troops: event.troops, sourceX: null, sourceY: null, targetX: null, diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts index 9ef9eed11..dbdb80f0c 100644 --- a/src/client/graphics/GameRenderer.ts +++ b/src/client/graphics/GameRenderer.ts @@ -1,22 +1,25 @@ -import {Game} from "../../core/game/Game"; -import {NameLayer} from "./layers/NameLayer"; -import {TerrainLayer} from "./layers/TerrainLayer"; -import {TerritoryLayer} from "./layers/TerritoryLayer"; -import {ClientID} from "../../core/Schemas"; -import {UILayer} from "./layers/UILayer"; -import {EventBus} from "../../core/EventBus"; -import {TransformHandler} from "./TransformHandler"; -import {Layer} from "./layers/Layer"; -import {EventsDisplay} from "./layers/EventsDisplay"; -import {RadialMenu} from "./layers/radial/RadialMenu"; -import {EmojiTable} from "./layers/radial/EmojiTable"; -import {Leaderboard} from "./layers/Leaderboard"; -import {ControlPanel} from "./layers/ControlPanel"; +import { Game } from "../../core/game/Game"; +import { NameLayer } from "./layers/NameLayer"; +import { TerrainLayer } from "./layers/TerrainLayer"; +import { TerritoryLayer } from "./layers/TerritoryLayer"; +import { ClientID } from "../../core/Schemas"; +import { UILayer } from "./layers/UILayer"; +import { EventBus } from "../../core/EventBus"; +import { TransformHandler } from "./TransformHandler"; +import { Layer } from "./layers/Layer"; +import { EventsDisplay } from "./layers/EventsDisplay"; +import { RadialMenu } from "./layers/radial/RadialMenu"; +import { EmojiTable } from "./layers/radial/EmojiTable"; +import { Leaderboard } from "./layers/Leaderboard"; +import { ControlPanel } from "./layers/ControlPanel"; +import { UIState } from "./UIState"; export function createRenderer(canvas: HTMLCanvasElement, game: Game, eventBus: EventBus, clientID: ClientID): GameRenderer { const transformHandler = new TransformHandler(game, eventBus, canvas) + const uiState = { attackRatio: 20 } + const emojiTable = document.querySelector('emoji-table') as EmojiTable; if (!emojiTable || !(emojiTable instanceof EmojiTable)) { console.error('EmojiTable element not found in the DOM'); @@ -33,6 +36,9 @@ export function createRenderer(canvas: HTMLCanvasElement, game: Game, eventBus: if (!(controlPanel instanceof ControlPanel)) { console.error('ControlPanel element not found in the DOM'); } + controlPanel.clientID = clientID + controlPanel.eventBus = eventBus + controlPanel.uiState = uiState const layers: Layer[] = [ new TerrainLayer(game), @@ -40,12 +46,12 @@ 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, game, transformHandler, clientID, emojiTable as EmojiTable), + new RadialMenu(eventBus, game, transformHandler, clientID, emojiTable as EmojiTable, uiState), leaderboard, controlPanel, ] - return new GameRenderer(game, eventBus, canvas, transformHandler, layers) + return new GameRenderer(game, eventBus, canvas, transformHandler, uiState, layers) } @@ -53,7 +59,7 @@ export class GameRenderer { private context: CanvasRenderingContext2D - constructor(private game: Game, private eventBus: EventBus, private canvas: HTMLCanvasElement, public transformHandler: TransformHandler, private layers: Layer[]) { + constructor(private game: Game, private eventBus: EventBus, private canvas: HTMLCanvasElement, public transformHandler: TransformHandler, public uiState: UIState, private layers: Layer[]) { this.context = canvas.getContext("2d") } diff --git a/src/client/graphics/UIState.ts b/src/client/graphics/UIState.ts new file mode 100644 index 000000000..cdd163441 --- /dev/null +++ b/src/client/graphics/UIState.ts @@ -0,0 +1,3 @@ +export interface UIState { + attackRatio: number +} \ No newline at end of file diff --git a/src/client/graphics/layers/ControlPanel.ts b/src/client/graphics/layers/ControlPanel.ts index 9774ddfe8..8b9dfd0b8 100644 --- a/src/client/graphics/layers/ControlPanel.ts +++ b/src/client/graphics/layers/ControlPanel.ts @@ -1,29 +1,61 @@ -import {LitElement, html, css} from 'lit'; -import {customElement, property, state} from 'lit/decorators.js'; -import {Layer} from './Layer'; -import {Game} from '../../../core/game/Game'; -import {ClientID} from '../../../core/Schemas'; +import { LitElement, html, css } from 'lit'; +import { customElement, property, state } from 'lit/decorators.js'; +import { Layer } from './Layer'; +import { Game } from '../../../core/game/Game'; +import { ClientID } from '../../../core/Schemas'; +import { renderTroops } from '../Utils'; +import { EventBus } from '../../../core/EventBus'; +import { UIState } from '../UIState'; @customElement('control-panel') export class ControlPanel extends LitElement implements Layer { private game: Game public clientID: ClientID + public eventBus: EventBus + public uiState: UIState @state() - private _numTroops = 50; + private attackRatio: number = .2; + + @state() + private _troops: number; + + @state() + private _maxTroops: number; + + @state() + private _manpower: number = 0; + + @state() + private _reserve: number = 0; + + @state() + private _gold: number = 0 @state() private _isVisible = false; init(game: Game) { - this.game = game + this.game = game; + this.attackRatio = .20 + this.uiState.attackRatio = this.attackRatio } tick() { // Update game state based on numTroops value if needed if (!this._isVisible && !this.game.inSpawnPhase()) { - this.toggleVisibility() + this.toggleVisibility(); } + + const player = this.game.playerByClientID(this.clientID) + if (player == null) { + return + } + this._troops = player.troops() + } + + onAttackRatioChange(newRatio: number) { + this.uiState.attackRatio = newRatio } renderLayer(context: CanvasRenderingContext2D) { @@ -31,7 +63,7 @@ export class ControlPanel extends LitElement implements Layer { } shouldTransform(): boolean { - return false + return false; } toggleVisibility() { @@ -39,6 +71,11 @@ export class ControlPanel extends LitElement implements Layer { this.requestUpdate(); } + targetTroops(): number { + return this._maxTroops * this.attackRatio + } + + static styles = css` :host { display: block; @@ -63,6 +100,21 @@ export class ControlPanel extends LitElement implements Layer { .slider-container { margin-bottom: 15px; } + .control-panel-info { + color: white; + margin-bottom: 15px; + padding: 10px; + background-color: rgba(0, 0, 0, 0.3); + border-radius: 5px; + } + .info-row { + display: flex; + justify-content: space-between; + margin-bottom: 5px; + } + .info-label { + font-weight: bold; + } label { display: block; color: white; @@ -80,10 +132,19 @@ export class ControlPanel extends LitElement implements Layer { render() { return html`
+
+
+ Troops: + ${renderTroops(this._troops)} +
+
- - this._numTroops = parseInt((e.target as HTMLInputElement).value)}> + + { + this.attackRatio = parseInt((e.target as HTMLInputElement).value) / 10; + this.onAttackRatioChange(this.attackRatio); + }}>
`; diff --git a/src/client/graphics/layers/radial/RadialMenu.ts b/src/client/graphics/layers/radial/RadialMenu.ts index 3edcbcd47..8e2d19c3f 100644 --- a/src/client/graphics/layers/radial/RadialMenu.ts +++ b/src/client/graphics/layers/radial/RadialMenu.ts @@ -1,11 +1,11 @@ -import {EventBus} from "../../../../core/EventBus"; -import {AllPlayers, Cell, Game, Player} from "../../../../core/game/Game"; -import {ClientID} from "../../../../core/Schemas"; -import {and, bfs, dist, manhattanDist, manhattanDistWrapped, sourceDstOceanShore} from "../../../../core/Util"; -import {ContextMenuEvent, MouseUpEvent} from "../../../InputHandler"; -import {SendAllianceRequestIntentEvent, SendAttackIntentEvent, SendBoatAttackIntentEvent, SendBreakAllianceIntentEvent, SendDonateIntentEvent, SendEmojiIntentEvent, SendNukeIntentEvent, SendSpawnIntentEvent, SendTargetPlayerIntentEvent} from "../../../Transport"; -import {TransformHandler} from "../../TransformHandler"; -import {Layer} from "../Layer"; +import { EventBus } from "../../../../core/EventBus"; +import { AllPlayers, Cell, Game, Player } from "../../../../core/game/Game"; +import { ClientID } from "../../../../core/Schemas"; +import { and, bfs, dist, manhattanDist, manhattanDistWrapped, sourceDstOceanShore } from "../../../../core/Util"; +import { ContextMenuEvent, MouseUpEvent } from "../../../InputHandler"; +import { SendAllianceRequestIntentEvent, SendAttackIntentEvent, SendBoatAttackIntentEvent, SendBreakAllianceIntentEvent, SendDonateIntentEvent, SendEmojiIntentEvent, SendNukeIntentEvent, SendSpawnIntentEvent, SendTargetPlayerIntentEvent } from "../../../Transport"; +import { TransformHandler } from "../../TransformHandler"; +import { Layer } from "../Layer"; import * as d3 from 'd3'; import traitorIcon from '../../../../../resources/images/TraitorIconWhite.png'; import allianceIcon from '../../../../../resources/images/AllianceIconWhite.png'; @@ -16,7 +16,8 @@ import emojiIcon from '../../../../../resources/images/EmojiIconWhite.png'; import disabledIcon from '../../../../../resources/images/DisabledIcon.png'; import donateIcon from '../../../../../resources/images/DonateIconWhite.png'; import nukeIcon from '../../../../../resources/images/NukeIconWhite.png'; -import {EmojiTable} from "./EmojiTable"; +import { EmojiTable } from "./EmojiTable"; +import { UIState } from "../../UIState"; enum Slot { @@ -33,11 +34,11 @@ export class RadialMenu implements Layer { private menuElement: d3.Selection; private isVisible: boolean = false; private readonly menuItems = new Map([ - [Slot.Alliance, {name: "alliance", disabled: true, action: () => { }, color: null, icon: null}], - [Slot.Boat, {name: "boat", disabled: true, action: () => { }, color: null, icon: null}], - [Slot.Target, {name: "target", disabled: true, action: () => { }}], - [Slot.Emoji, {name: "emoji", disabled: true, action: () => { }}], - [Slot.Nuke, {name: "nuke", disabled: true, action: () => { }}], + [Slot.Alliance, { name: "alliance", disabled: true, action: () => { }, color: null, icon: null }], + [Slot.Boat, { name: "boat", disabled: true, action: () => { }, color: null, icon: null }], + [Slot.Target, { name: "target", disabled: true, action: () => { } }], + [Slot.Emoji, { name: "emoji", disabled: true, action: () => { } }], + [Slot.Nuke, { name: "nuke", disabled: true, action: () => { } }], ]); private readonly menuSize = 190; @@ -53,7 +54,8 @@ export class RadialMenu implements Layer { private game: Game, private transformHandler: TransformHandler, private clientID: ClientID, - private emojiTable: EmojiTable + private emojiTable: EmojiTable, + private uiState: UIState ) { } init() { @@ -345,7 +347,7 @@ export class RadialMenu implements Layer { if (manhattanDistWrapped(src.cell(), dst.cell(), this.game.width()) < this.game.config().boatMaxDistance()) { this.activateMenuElement(Slot.Boat, "#3f6ab1", boatIcon, () => { this.eventBus.emit( - new SendBoatAttackIntentEvent(other.id(), this.clickedCell, null) + new SendBoatAttackIntentEvent(other.id(), this.clickedCell, this.uiState.attackRatio * myPlayer.troops()) ) }) } @@ -384,7 +386,8 @@ export class RadialMenu implements Layer { this.eventBus.emit(new SendSpawnIntentEvent(this.clickedCell)) } else { if (clicked.owner().clientID() != this.clientID) { - this.eventBus.emit(new SendAttackIntentEvent(clicked.owner().id())) + const myPlayer = this.game.players().find(p => p.clientID() == this.clientID) + this.eventBus.emit(new SendAttackIntentEvent(clicked.owner().id(), this.uiState.attackRatio * myPlayer.troops())) } } this.hideRadialMenu();