diff --git a/TODO.txt b/TODO.txt index efa419671..6d3d9d56d 100644 --- a/TODO.txt +++ b/TODO.txt @@ -171,24 +171,20 @@ * add troop max DONE 10/31/2024 * rewrite EventsDisplay DONE 11/1/2024 * update Mena NPC locations DONE 11/1/2024 -* fix name rendering -* use twitter emojis * create build menu * NPC has relations +* fix name rendering +* use twitter emojis * private game shows how many players joined * optimize sendBoat function * NPC more likely to accept alliance fewer alliance player has -* better NPC relation logic * surface NPC relations +* use SVGs for icons * block user inputs if too far behind server * BUG: FakeHuman don't be enemy if don't share border (or send boat) * store cookies -* Load terrain dataImage in background * BUG: half encircle Antartica causes capture * improve front page (make map larger?) -* Add additional maps * REFACTOR: give terranullius an ID, game.player() returns terranullius -* REFACTOR: ocean is considered TerraNullius ? -* Make icons svgs -testing webhook \ No newline at end of file +* REFACTOR: ocean is considered TerraNullius ? \ No newline at end of file diff --git a/resources/images/BuildIcon.svg b/resources/images/BuildIcon.svg new file mode 100644 index 000000000..da97e3625 --- /dev/null +++ b/resources/images/BuildIcon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts index f85ab688e..b962ab4e8 100644 --- a/src/client/graphics/GameRenderer.ts +++ b/src/client/graphics/GameRenderer.ts @@ -13,6 +13,7 @@ import { EmojiTable } from "./layers/radial/EmojiTable"; import { Leaderboard } from "./layers/Leaderboard"; import { ControlPanel } from "./layers/ControlPanel"; import { UIState } from "./UIState"; +import { BuildMenu } from "./layers/radial/BuildMenu"; export function createRenderer(canvas: HTMLCanvasElement, game: Game, eventBus: EventBus, clientID: ClientID): GameRenderer { @@ -20,10 +21,17 @@ export function createRenderer(canvas: HTMLCanvasElement, game: Game, eventBus: const uiState = { attackRatio: 20 } + // TODO maybe append this to dcoument instead of querying for them? const emojiTable = document.querySelector('emoji-table') as EmojiTable; if (!emojiTable || !(emojiTable instanceof EmojiTable)) { console.error('EmojiTable element not found in the DOM'); } + const buildMenu = document.querySelector('build-menu') as BuildMenu; + if (!buildMenu || !(buildMenu instanceof BuildMenu)) { + console.error('BuildMenu element not found in the DOM') + } + buildMenu.game = game + buildMenu.eventBus = eventBus const leaderboard = document.querySelector('leader-board') as Leaderboard; if (!emojiTable || !(leaderboard instanceof Leaderboard)) { @@ -55,7 +63,7 @@ export function createRenderer(canvas: HTMLCanvasElement, game: Game, eventBus: new NameLayer(game, game.config().theme(), transformHandler, clientID), new UILayer(eventBus, game, clientID, transformHandler), eventsDisplay, - new RadialMenu(eventBus, game, transformHandler, clientID, emojiTable as EmojiTable, uiState), + new RadialMenu(eventBus, game, transformHandler, clientID, emojiTable as EmojiTable, buildMenu, uiState), leaderboard, controlPanel, ] diff --git a/src/client/graphics/layers/radial/BuildMenu.ts b/src/client/graphics/layers/radial/BuildMenu.ts new file mode 100644 index 000000000..835ceeb1f --- /dev/null +++ b/src/client/graphics/layers/radial/BuildMenu.ts @@ -0,0 +1,168 @@ +import { LitElement, html, css } from 'lit'; +import { customElement, state } from 'lit/decorators.js'; +import { EventBus } from '../../../../core/EventBus'; +import { Cell, Game, Player } from '../../../../core/game/Game'; +import { SendNukeIntentEvent } from '../../../Transport'; + +interface BuildItem { + id: string; + name: string; + icon: string; + cost: number; + buildTime: number; +} + +const buildTable: BuildItem[][] = [ + [ + { id: 'missile', name: 'Missile', icon: '🚀', cost: 100, buildTime: 5 }, + { id: 'battleship', name: 'Battleship', icon: '🚢', cost: 500, buildTime: 20 } + ] +]; + +@customElement('build-menu') +export class BuildMenu extends LitElement { + public game: Game; + public eventBus: EventBus + + + private myPlayer: Player + private clickedCell: Cell + + static styles = css` + :host { + display: block; + } + .build-menu { + position: fixed; + top: 50%; + left: 50%; + transform: translateX(-50%); + z-index: 9999; + background-color: #1E1E1E; + padding: 15px; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.5); + border-radius: 10px; + display: flex; + flex-direction: column; + align-items: center; + max-width: 95vw; + max-height: 95vh; + overflow-y: auto; + } + .build-row { + display: flex; + justify-content: center; + width: 100%; + } + .build-button { + width: 120px; + height: 120px; + border: 2px solid #444; + background-color: #2C2C2C; + color: white; + border-radius: 12px; + cursor: pointer; + transition: all 0.3s ease; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + margin: 8px; + padding: 10px; + } + .build-button:hover { + background-color: #3A3A3A; + transform: scale(1.05); + border-color: #666; + } + .build-button:active { + background-color: #4A4A4A; + transform: scale(0.95); + } + .build-icon { + font-size: 40px; + margin-bottom: 5px; + } + .build-name { + font-size: 14px; + font-weight: bold; + margin-bottom: 5px; + } + .build-cost { + font-size: 12px; + color: #FFD700; + } + .hidden { + display: none !important; + } + + @media (max-width: 600px) { + .build-button { + width: 100px; + height: 100px; + margin: 5px; + } + .build-icon { + font-size: 32px; + } + .build-name { + font-size: 12px; + } + .build-cost { + font-size: 10px; + } + } + @media (max-width: 400px) { + .build-button { + width: 80px; + height: 80px; + margin: 3px; + } + .build-icon { + font-size: 28px; + } + } + `; + + @state() + private _hidden = true; + + public onBuildSelected: (item: BuildItem) => void = () => { + this.eventBus.emit(new SendNukeIntentEvent(this.myPlayer, this.clickedCell, null)) + this.hideMenu() + }; + + render() { + return html` +
+ `; + } + + hideMenu() { + this._hidden = true; + this.requestUpdate(); + } + + showMenu(player: Player, clickedCell: Cell) { + this.myPlayer = player + this.clickedCell = clickedCell + this._hidden = false; + this.requestUpdate(); + } + + get isVisible() { + return !this._hidden; + } +} \ No newline at end of file diff --git a/src/client/graphics/layers/radial/RadialMenu.ts b/src/client/graphics/layers/radial/RadialMenu.ts index 8e2d19c3f..3a1df4b5c 100644 --- a/src/client/graphics/layers/radial/RadialMenu.ts +++ b/src/client/graphics/layers/radial/RadialMenu.ts @@ -15,9 +15,10 @@ import targetIcon from '../../../../../resources/images/TargetIconWhite.png'; 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 buildIcon from '../../../../../resources/images/BuildIcon.svg'; import { EmojiTable } from "./EmojiTable"; import { UIState } from "../../UIState"; +import { BuildMenu } from "./BuildMenu"; enum Slot { @@ -25,7 +26,7 @@ enum Slot { Boat, Target, Emoji, - Nuke, + Build, } export class RadialMenu implements Layer { @@ -38,7 +39,7 @@ export class RadialMenu implements Layer { [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.Build, { name: "build", disabled: true, action: () => { } }], ]); private readonly menuSize = 190; @@ -55,6 +56,7 @@ export class RadialMenu implements Layer { private transformHandler: TransformHandler, private clientID: ClientID, private emojiTable: EmojiTable, + private buildMenu: BuildMenu, private uiState: UIState ) { } @@ -230,8 +232,8 @@ export class RadialMenu implements Layer { return } - this.activateMenuElement(Slot.Nuke, "#ebe250", nukeIcon, () => { - this.eventBus.emit(new SendNukeIntentEvent(myPlayer, this.clickedCell, null)) + this.activateMenuElement(Slot.Build, "#ebe250", buildIcon, () => { + this.buildMenu.showMenu(myPlayer, this.clickedCell) }) if (tile.hasOwner()) { @@ -358,6 +360,7 @@ export class RadialMenu implements Layer { private onPointerUp(event: MouseUpEvent) { this.hideRadialMenu() this.emojiTable.hideTable() + this.buildMenu.hideMenu() } private showRadialMenu(x: number, y: number) { diff --git a/src/client/index.html b/src/client/index.html index f8840d61f..07781fc03 100644 --- a/src/client/index.html +++ b/src/client/index.html @@ -74,6 +74,7 @@