diff --git a/TODO.txt b/TODO.txt
index d17d7f272..d41f42b41 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -172,11 +172,21 @@
* rewrite EventsDisplay DONE 11/1/2024
* update Mena NPC locations DONE 11/1/2024
* create build menu DONE 11/3/2024
-* add gold
-* add troop/worker slider
+* add gold DONE 11/4/2024
+* add troop/worker slider DONE 11/4/2024
+* create Unit layer DONE 11/9/2024
+* create Unit interface DONE 11/10/2024
+* add destroyer DONE 11/12/2024
+* add ports DONE 11/14/2024
+* destroyer spawn from port DONE 11/14/2024
+* create trade routes DONE 11/15/2024
+* add trade ship DONE 11/15/2024
+* trade ship gives gold when completes route DONE 11/15/2024
+* add missile silo
+* nuke spawns from missile silo
+* destroyer can capture trade ships
* add battleship
* NPC has relations
-* fix name rendering
* use twitter emojis
* private game shows how many players joined
* optimize sendBoat function
diff --git a/resources/images/AnchorIcon.png b/resources/images/AnchorIcon.png
new file mode 100644
index 000000000..78bdfe73e
Binary files /dev/null and b/resources/images/AnchorIcon.png differ
diff --git a/resources/images/Destroyer.svg b/resources/images/Destroyer.svg
new file mode 100644
index 000000000..bebcde38f
--- /dev/null
+++ b/resources/images/Destroyer.svg
@@ -0,0 +1,47 @@
+
+
diff --git a/resources/images/DestroyerIconWhite.svg b/resources/images/DestroyerIconWhite.svg
new file mode 100644
index 000000000..6bad21f6a
--- /dev/null
+++ b/resources/images/DestroyerIconWhite.svg
@@ -0,0 +1,59 @@
+
+
diff --git a/resources/images/PortIcon.svg b/resources/images/PortIcon.svg
new file mode 100644
index 000000000..b3e417cd6
--- /dev/null
+++ b/resources/images/PortIcon.svg
@@ -0,0 +1,40 @@
+
+
diff --git a/src/client/ClientGame.ts b/src/client/ClientGame.ts
index d4663178a..d307749be 100644
--- a/src/client/ClientGame.ts
+++ b/src/client/ClientGame.ts
@@ -1,5 +1,5 @@
import { Executor } from "../core/execution/ExecutionManager";
-import { Cell, MutableGame, PlayerEvent, PlayerID, MutablePlayer, TileEvent, Player, Game, BoatEvent, Tile, PlayerType, GameMap, Difficulty } from "../core/game/Game";
+import { Cell, MutableGame, PlayerEvent, PlayerID, MutablePlayer, TileEvent, Player, Game, UnitEvent, Tile, PlayerType, GameMap, Difficulty } from "../core/game/Game";
import { createGame } from "../core/game/GameImpl";
import { EventBus } from "../core/EventBus";
import { Config, getConfig } from "../core/configuration/Config";
diff --git a/src/client/Transport.ts b/src/client/Transport.ts
index fe969c6af..11ac917b9 100644
--- a/src/client/Transport.ts
+++ b/src/client/Transport.ts
@@ -1,7 +1,7 @@
import { Config } from "../core/configuration/Config"
import { EventBus, GameEvent } from "../core/EventBus"
-import { AllianceRequest, AllPlayers, Cell, Player, PlayerID, PlayerType } from "../core/game/Game"
-import { ClientID, ClientIntentMessageSchema, ClientJoinMessageSchema, ClientLeaveMessageSchema, GameID, Intent, ServerMessage, ServerMessageSchema } from "../core/Schemas"
+import { AllianceRequest, AllPlayers, Cell, BuildItem, Player, PlayerID, PlayerType, Tile, UnitType } from "../core/game/Game"
+import { ClientID, ClientIntentMessageSchema, ClientJoinMessageSchema, ClientLeaveMessageSchema, BuildUnitIntentSchema, GameID, Intent, ServerMessage, ServerMessageSchema } from "../core/Schemas"
import { LocalServer } from "./LocalServer"
@@ -47,6 +47,13 @@ export class SendBoatAttackIntentEvent implements GameEvent {
) { }
}
+export class BuildUnitIntentEvent implements GameEvent {
+ constructor(
+ public readonly unit: UnitType,
+ public readonly cell: Cell,
+ ) { }
+}
+
export class SendTargetPlayerIntentEvent implements GameEvent {
constructor(
public readonly targetID: PlayerID,
@@ -115,6 +122,7 @@ export class Transport {
this.eventBus.on(SendDonateIntentEvent, (e) => this.onSendDonateIntent(e))
this.eventBus.on(SendNukeIntentEvent, (e) => this.onSendNukeIntent(e))
this.eventBus.on(SendSetTargetTroopRatioEvent, (e) => this.onSendSetTargetTroopRatioEvent(e))
+ this.eventBus.on(BuildUnitIntentEvent, (e) => this.onCreateDestroyerIntent(e))
}
connect(onconnect: () => void, onmessage: (message: ServerMessage) => void) {
@@ -314,6 +322,17 @@ export class Transport {
})
}
+ private onCreateDestroyerIntent(event: BuildUnitIntentEvent) {
+ this.sendIntent({
+ type: "build_unit",
+ clientID: this.clientID,
+ player: this.playerID,
+ unit: event.unit,
+ x: event.cell.x,
+ y: event.cell.y,
+ })
+ }
+
private sendIntent(intent: Intent) {
if (this.isLocal || this.socket.readyState === WebSocket.OPEN) {
const msg = ClientIntentMessageSchema.parse({
diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts
index 579de4a00..7a248fd5c 100644
--- a/src/client/graphics/GameRenderer.ts
+++ b/src/client/graphics/GameRenderer.ts
@@ -14,6 +14,9 @@ import { Leaderboard } from "./layers/Leaderboard";
import { ControlPanel } from "./layers/ControlPanel";
import { UIState } from "./UIState";
import { BuildMenu } from "./layers/radial/BuildMenu";
+import { UnitLayer } from "./layers/UnitLayer";
+import { BuildValidator } from "../../core/game/BuildValidator";
+import { StructureLayer } from "./layers/StructureLayer";
export function createRenderer(canvas: HTMLCanvasElement, game: Game, eventBus: EventBus, clientID: ClientID): GameRenderer {
@@ -32,7 +35,7 @@ export function createRenderer(canvas: HTMLCanvasElement, game: Game, eventBus:
}
buildMenu.game = game
buildMenu.eventBus = eventBus
- buildMenu.init()
+ buildMenu.buildValidator = new BuildValidator(game)
const leaderboard = document.querySelector('leader-board') as Leaderboard;
if (!emojiTable || !(leaderboard instanceof Leaderboard)) {
@@ -61,6 +64,8 @@ export function createRenderer(canvas: HTMLCanvasElement, game: Game, eventBus:
const layers: Layer[] = [
new TerrainLayer(game),
new TerritoryLayer(game, eventBus),
+ new StructureLayer(game, eventBus),
+ new UnitLayer(game, eventBus),
new NameLayer(game, game.config().theme(), transformHandler, clientID),
new UILayer(eventBus, game, clientID, transformHandler),
eventsDisplay,
diff --git a/src/client/graphics/layers/StructureLayer.ts b/src/client/graphics/layers/StructureLayer.ts
new file mode 100644
index 000000000..94f904a7c
--- /dev/null
+++ b/src/client/graphics/layers/StructureLayer.ts
@@ -0,0 +1,149 @@
+import { Colord } from "colord";
+import { Theme } from "../../../core/configuration/Config";
+import { Unit, UnitEvent, Cell, Game, Tile, UnitType } from "../../../core/game/Game";
+import { bfs, dist, euclDist } from "../../../core/Util";
+import { Layer } from "./Layer";
+import { EventBus } from "../../../core/EventBus";
+
+import anchorIcon from '../../../../resources/images/AnchorIcon.png';
+
+export class StructureLayer implements Layer {
+ private canvas: HTMLCanvasElement;
+ private context: CanvasRenderingContext2D;
+ private imageData: ImageData;
+ private anchorImage: HTMLImageElement;
+ private anchorImageLoaded: boolean = false;
+
+
+ private theme: Theme = null;
+
+ constructor(private game: Game, private eventBus: EventBus) {
+ this.theme = game.config().theme();
+ this.loadAnchorImage();
+ }
+
+ private loadAnchorImage() {
+ this.anchorImage = new Image();
+ this.anchorImage.onload = () => {
+ this.anchorImageLoaded = true;
+ };
+ this.anchorImage.src = anchorIcon;
+ }
+
+ shouldTransform(): boolean {
+ return true;
+ }
+
+ tick() {
+ }
+
+ init(game: Game) {
+ this.canvas = document.createElement('canvas');
+ this.context = this.canvas.getContext("2d");
+
+ this.imageData = this.context.getImageData(0, 0, this.game.width(), this.game.height());
+ this.canvas.width = this.game.width();
+ this.canvas.height = this.game.height();
+ this.context.putImageData(this.imageData, 0, 0);
+ this.initImageData();
+
+ this.eventBus.on(UnitEvent, e => this.onUnitEvent(e));
+ }
+
+ initImageData() {
+ this.game.forEachTile((tile) => {
+ const index = (tile.cell().y * this.game.width()) + tile.cell().x;
+ const offset = index * 4;
+ this.imageData.data[offset + 3] = 0;
+ });
+ }
+
+ renderLayer(context: CanvasRenderingContext2D) {
+ this.context.putImageData(this.imageData, 0, 0);
+ context.drawImage(
+ this.canvas,
+ -this.game.width() / 2,
+ -this.game.height() / 2,
+ this.game.width(),
+ this.game.height()
+ );
+ }
+
+ private handlePortEvent(event: UnitEvent) {
+ if (!this.anchorImageLoaded) return;
+
+ bfs(event.unit.tile(), euclDist(event.unit.tile(), 8))
+ .forEach(t => this.clearCell(t.cell()));
+
+ if (!event.unit.isActive()) {
+ return
+ }
+ // Create a temporary canvas to process the anchor icon
+ const tempCanvas = document.createElement('canvas');
+ const tempContext = tempCanvas.getContext('2d');
+ tempCanvas.width = this.anchorImage.width;
+ tempCanvas.height = this.anchorImage.height;
+
+ // Draw the anchor icon to the temporary canvas
+ tempContext.drawImage(this.anchorImage, 0, 0);
+ const iconData = tempContext.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
+
+ // Calculate position to center the icon on the port
+ const cell = event.unit.tile().cell();
+ const startX = cell.x - Math.floor(tempCanvas.width / 2);
+ const startY = cell.y - Math.floor(tempCanvas.height / 2);
+
+ bfs(event.unit.tile(), euclDist(event.unit.tile(), 8))
+ .forEach(t => this.paintCell(t.cell(), this.theme.borderColor(event.unit.owner().info()), 255));
+
+ bfs(event.unit.tile(), euclDist(event.unit.tile(), 6))
+ .forEach(t => this.paintCell(t.cell(), this.theme.territoryColor(event.unit.owner().info()), 255));
+ // Process each pixel of the icon
+ for (let y = 0; y < tempCanvas.height; y++) {
+ for (let x = 0; x < tempCanvas.width; x++) {
+ const iconIndex = (y * tempCanvas.width + x) * 4;
+ const alpha = iconData.data[iconIndex + 3];
+
+ if (alpha > 0) { // Only process non-transparent pixels
+ const targetX = startX + x;
+ const targetY = startY + y;
+
+ // Check if the target pixel is within the game bounds
+ if (targetX >= 0 && targetX < this.game.width() &&
+ targetY >= 0 && targetY < this.game.height()) {
+
+ // Color the pixel using the unit owner's colors
+ this.paintCell(
+ new Cell(targetX, targetY),
+ this.theme.borderColor(event.unit.owner().info()),
+ alpha
+ );
+ }
+ }
+ }
+ }
+ }
+
+ onUnitEvent(event: UnitEvent) {
+ switch (event.unit.type()) {
+ case UnitType.Port:
+ this.handlePortEvent(event);
+ break;
+ }
+ }
+
+ paintCell(cell: Cell, color: Colord, alpha: number) {
+ const index = (cell.y * this.game.width()) + cell.x;
+ const offset = index * 4;
+ this.imageData.data[offset] = color.rgba.r;
+ this.imageData.data[offset + 1] = color.rgba.g;
+ this.imageData.data[offset + 2] = color.rgba.b;
+ this.imageData.data[offset + 3] = alpha;
+ }
+
+ clearCell(cell: Cell) {
+ const index = (cell.y * this.game.width()) + cell.x;
+ const offset = index * 4;
+ this.imageData.data[offset + 3] = 0; // Set alpha to 0 (fully transparent)
+ }
+}
\ No newline at end of file
diff --git a/src/client/graphics/layers/TerritoryLayer.ts b/src/client/graphics/layers/TerritoryLayer.ts
index 85d76d87a..c6dfd6c31 100644
--- a/src/client/graphics/layers/TerritoryLayer.ts
+++ b/src/client/graphics/layers/TerritoryLayer.ts
@@ -1,33 +1,33 @@
-import {PriorityQueue} from "@datastructures-js/priority-queue";
-import {Boat, BoatEvent, Cell, Game, Player, Tile, TileEvent} from "../../../core/game/Game";
-import {PseudoRandom} from "../../../core/PseudoRandom";
-import {Colord} from "colord";
-import {bfs, dist} from "../../../core/Util";
-import {Theme} from "../../../core/configuration/Config";
-import {Layer} from "./Layer";
-import {TransformHandler} from "../TransformHandler";
-import {EventBus} from "../../../core/EventBus";
+import { PriorityQueue } from "@datastructures-js/priority-queue";
+import { Cell, Game, Player, Tile, TileEvent } from "../../../core/game/Game";
+import { PseudoRandom } from "../../../core/PseudoRandom";
+import { Colord } from "colord";
+import { bfs, dist } from "../../../core/Util";
+import { Theme } from "../../../core/configuration/Config";
+import { Layer } from "./Layer";
+import { TransformHandler } from "../TransformHandler";
+import { EventBus } from "../../../core/EventBus";
export class TerritoryLayer implements Layer {
private canvas: HTMLCanvasElement
private context: CanvasRenderingContext2D
private imageData: ImageData
- private tileToRenderQueue: PriorityQueue<{tileEvent: TileEvent, lastUpdate: number}> = new PriorityQueue((a, b) => {return a.lastUpdate - b.lastUpdate})
+ private tileToRenderQueue: PriorityQueue<{ tileEvent: TileEvent, lastUpdate: number }> = new PriorityQueue((a, b) => { return a.lastUpdate - b.lastUpdate })
private random = new PseudoRandom(123)
private theme: Theme = null
- private boatToTrail = new Map>()
constructor(private game: Game, eventBus: EventBus) {
this.theme = game.config().theme()
eventBus.on(TileEvent, e => this.tileUpdate(e))
- eventBus.on(BoatEvent, e => this.boatEvent(e))
}
+
shouldTransform(): boolean {
return true
}
+
tick() {
}
@@ -62,31 +62,6 @@ export class TerritoryLayer implements Layer {
)
}
- boatEvent(event: BoatEvent) {
- if (!this.boatToTrail.has(event.boat)) {
- this.boatToTrail.set(event.boat, new Set())
- }
- const trail = this.boatToTrail.get(event.boat)
- trail.add(event.oldTile)
- bfs(event.oldTile, dist(event.oldTile, 3)).forEach(t => {
- this.paintTerritory(t)
- })
- if (event.boat.isActive()) {
- bfs(event.boat.tile(), dist(event.boat.tile(), 4)).forEach(
- t => {
- if (trail.has(t)) {
- this.paintCell(t.cell(), this.theme.territoryColor(event.boat.owner().info()), 150)
- }
- }
- )
- bfs(event.boat.tile(), dist(event.boat.tile(), 2)).forEach(t => this.paintCell(t.cell(), this.theme.borderColor(event.boat.owner().info()), 255))
- bfs(event.boat.tile(), dist(event.boat.tile(), 1)).forEach(t => this.paintCell(t.cell(), this.theme.territoryColor(event.boat.owner().info()), 180))
- } else {
- trail.forEach(t => this.paintTerritory(t))
- this.boatToTrail.delete(event.boat)
- }
- }
-
renderTerritory() {
let numToRender = Math.floor(this.tileToRenderQueue.size() / 10)
if (numToRender == 0) {
@@ -138,6 +113,6 @@ export class TerritoryLayer implements Layer {
}
tileUpdate(event: TileEvent) {
- this.tileToRenderQueue.push({tileEvent: event, lastUpdate: this.game.ticks() + this.random.nextFloat(0, .5)})
+ this.tileToRenderQueue.push({ tileEvent: event, lastUpdate: this.game.ticks() + this.random.nextFloat(0, .5) })
}
}
\ No newline at end of file
diff --git a/src/client/graphics/layers/UnitLayer.ts b/src/client/graphics/layers/UnitLayer.ts
new file mode 100644
index 000000000..c6b93aa69
--- /dev/null
+++ b/src/client/graphics/layers/UnitLayer.ts
@@ -0,0 +1,147 @@
+import { Colord } from "colord";
+import { Theme } from "../../../core/configuration/Config";
+import { Unit, UnitEvent, Cell, Game, Tile, UnitType } from "../../../core/game/Game";
+import { bfs, dist, euclDist } from "../../../core/Util";
+import { Layer } from "./Layer";
+import { EventBus } from "../../../core/EventBus";
+
+import anchorIcon from '../../../../resources/images/AnchorIcon.png';
+
+export class UnitLayer implements Layer {
+ private canvas: HTMLCanvasElement;
+ private context: CanvasRenderingContext2D;
+ private imageData: ImageData;
+ private anchorImage: HTMLImageElement;
+ private anchorImageLoaded: boolean = false;
+
+ private boatToTrail = new Map>();
+
+ private theme: Theme = null;
+
+ constructor(private game: Game, private eventBus: EventBus) {
+ this.theme = game.config().theme();
+ this.loadAnchorImage();
+ }
+
+ private loadAnchorImage() {
+ this.anchorImage = new Image();
+ this.anchorImage.onload = () => {
+ this.anchorImageLoaded = true;
+ };
+ this.anchorImage.src = anchorIcon;
+ }
+
+ shouldTransform(): boolean {
+ return true;
+ }
+
+ tick() {
+ }
+
+ init(game: Game) {
+ this.canvas = document.createElement('canvas');
+ this.context = this.canvas.getContext("2d");
+
+ this.imageData = this.context.getImageData(0, 0, this.game.width(), this.game.height());
+ this.canvas.width = this.game.width();
+ this.canvas.height = this.game.height();
+ this.context.putImageData(this.imageData, 0, 0);
+ this.initImageData();
+
+ this.eventBus.on(UnitEvent, e => this.onUnitEvent(e));
+ }
+
+ initImageData() {
+ this.game.forEachTile((tile) => {
+ const index = (tile.cell().y * this.game.width()) + tile.cell().x;
+ const offset = index * 4;
+ this.imageData.data[offset + 3] = 0;
+ });
+ }
+
+ renderLayer(context: CanvasRenderingContext2D) {
+ this.context.putImageData(this.imageData, 0, 0);
+ context.drawImage(
+ this.canvas,
+ -this.game.width() / 2,
+ -this.game.height() / 2,
+ this.game.width(),
+ this.game.height()
+ );
+ }
+
+ onUnitEvent(event: UnitEvent) {
+ switch (event.unit.type()) {
+ case UnitType.TransportShip:
+ this.handleBoatEvent(event);
+ break;
+ case UnitType.Destroyer:
+ this.handleDestroyerEvent(event);
+ break;
+ case UnitType.TradeShip:
+ this.handleTradeShipEvent(event)
+ }
+ }
+
+ private handleDestroyerEvent(event: UnitEvent) {
+ bfs(event.oldTile, euclDist(event.oldTile, 3)).forEach(t => {
+ this.clearCell(t.cell());
+ });
+ bfs(event.unit.tile(), euclDist(event.unit.tile(), 3))
+ .forEach(t => this.paintCell(t.cell(), this.theme.borderColor(event.unit.owner().info()), 255));
+ bfs(event.unit.tile(), euclDist(event.unit.tile(), 2))
+ .forEach(t => this.paintCell(t.cell(), this.theme.territoryColor(event.unit.owner().info()), 180));
+ }
+
+ private handleTradeShipEvent(event: UnitEvent) {
+ bfs(event.oldTile, euclDist(event.oldTile, 1)).forEach(t => {
+ this.clearCell(t.cell());
+ });
+ if (event.unit.isActive()) {
+ bfs(event.unit.tile(), euclDist(event.unit.tile(), 1))
+ .forEach(t => this.paintCell(t.cell(), this.theme.borderColor(event.unit.owner().info()), 255));
+ }
+ }
+
+ private handleBoatEvent(event: UnitEvent) {
+ if (!this.boatToTrail.has(event.unit)) {
+ this.boatToTrail.set(event.unit, new Set());
+ }
+ const trail = this.boatToTrail.get(event.unit);
+ trail.add(event.oldTile);
+ bfs(event.oldTile, dist(event.oldTile, 3)).forEach(t => {
+ this.clearCell(t.cell());
+ });
+ if (event.unit.isActive()) {
+ bfs(event.unit.tile(), dist(event.unit.tile(), 4)).forEach(
+ t => {
+ if (trail.has(t)) {
+ this.paintCell(t.cell(), this.theme.territoryColor(event.unit.owner().info()), 150);
+ }
+ }
+ );
+ bfs(event.unit.tile(), dist(event.unit.tile(), 2))
+ .forEach(t => this.paintCell(t.cell(), this.theme.borderColor(event.unit.owner().info()), 255));
+ bfs(event.unit.tile(), dist(event.unit.tile(), 1))
+ .forEach(t => this.paintCell(t.cell(), this.theme.territoryColor(event.unit.owner().info()), 180));
+ } else {
+ trail.forEach(t => this.clearCell(t.cell()));
+ this.boatToTrail.delete(event.unit);
+ }
+ }
+
+ paintCell(cell: Cell, color: Colord, alpha: number) {
+ const index = (cell.y * this.game.width()) + cell.x;
+ const offset = index * 4;
+ this.imageData.data[offset] = color.rgba.r;
+ this.imageData.data[offset + 1] = color.rgba.g;
+ this.imageData.data[offset + 2] = color.rgba.b;
+ this.imageData.data[offset + 3] = alpha;
+ }
+
+ clearCell(cell: Cell) {
+ const index = (cell.y * this.game.width()) + cell.x;
+ const offset = index * 4;
+ this.imageData.data[offset + 3] = 0; // Set alpha to 0 (fully transparent)
+ }
+}
\ No newline at end of file
diff --git a/src/client/graphics/layers/radial/BuildMenu.ts b/src/client/graphics/layers/radial/BuildMenu.ts
index 5ff17f655..4cb243426 100644
--- a/src/client/graphics/layers/radial/BuildMenu.ts
+++ b/src/client/graphics/layers/radial/BuildMenu.ts
@@ -1,22 +1,26 @@
import { LitElement, html, css } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { EventBus } from '../../../../core/EventBus';
-import { Cell, Game, Item, Items, Player } from '../../../../core/game/Game';
-import { SendNukeIntentEvent } from '../../../Transport';
+import { Cell, Game, BuildItem, BuildItems, Player, UnitType } from '../../../../core/game/Game';
+import { BuildUnitIntentEvent, SendNukeIntentEvent } from '../../../Transport';
import nukeIcon from '../../../../../resources/images/NukeIconWhite.svg';
+import destroyerIcon from '../../../../../resources/images/DestroyerIconWhite.svg';
import goldCoinIcon from '../../../../../resources/images/GoldCoinIcon.svg';
+import portIcon from '../../../../../resources/images/PortIcon.svg';
import { renderNumber } from '../../Utils';
+import { BuildValidator } from '../../../../core/game/BuildValidator';
import { ContextMenuEvent } from '../../../InputHandler';
-interface BuildItem {
- item: Item
+interface BuildItemDisplay {
+ item: BuildItem
icon: string;
}
-const buildTable: BuildItem[][] = [
+const buildTable: BuildItemDisplay[][] = [
[
- { item: Items.Nuke, icon: nukeIcon },
- // { id: 'battleship', name: 'Battleship', icon: '🚢', cost: 500, buildTime: 20 }
+ { item: BuildItems.Nuke, icon: nukeIcon },
+ { item: BuildItems.Destroyer, icon: destroyerIcon },
+ { item: BuildItems.Port, icon: portIcon }
]
];
@@ -24,6 +28,7 @@ const buildTable: BuildItem[][] = [
export class BuildMenu extends LitElement {
public game: Game;
public eventBus: EventBus;
+ public buildValidator: BuildValidator;
private myPlayer: Player;
private clickedCell: Cell;
@@ -142,12 +147,25 @@ export class BuildMenu extends LitElement {
@state()
private _hidden = true;
- private canAfford(item: BuildItem): boolean {
- return this.myPlayer && this.myPlayer.gold() >= item.item.cost;
+ private canBuild(item: BuildItemDisplay): boolean {
+ if (this.myPlayer == null) {
+ return false
+ }
+ return this.buildValidator.canBuild(this.myPlayer, this.game.tile(this.clickedCell), item.item)
}
- public onBuildSelected: (item: BuildItem) => void = () => {
- this.eventBus.emit(new SendNukeIntentEvent(this.myPlayer, this.clickedCell, null))
+ public onBuildSelected = (item: BuildItemDisplay) => {
+ switch (item.item) {
+ case BuildItems.Nuke:
+ this.eventBus.emit(new SendNukeIntentEvent(this.myPlayer, this.clickedCell, null))
+ break
+ case BuildItems.Destroyer:
+ this.eventBus.emit(new BuildUnitIntentEvent(UnitType.Destroyer, this.clickedCell))
+ break
+ case BuildItems.Port:
+ this.eventBus.emit(new BuildUnitIntentEvent(UnitType.Port, this.clickedCell))
+ break
+ }
this.hideMenu()
};
@@ -160,11 +178,11 @@ export class BuildMenu extends LitElement {