From 09ead3791ddd64714849071c4dfe35080f869788 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Wed, 13 Nov 2024 15:08:51 -0800 Subject: [PATCH] add port --- src/client/graphics/layers/UnitLayer.ts | 142 +++++++++++++++++------- src/core/execution/PortExecution.ts | 8 +- src/core/game/Game.ts | 2 +- 3 files changed, 107 insertions(+), 45 deletions(-) diff --git a/src/client/graphics/layers/UnitLayer.ts b/src/client/graphics/layers/UnitLayer.ts index f0ebe0301..5520f4085 100644 --- a/src/client/graphics/layers/UnitLayer.ts +++ b/src/client/graphics/layers/UnitLayer.ts @@ -5,23 +5,34 @@ import { bfs, dist, euclDist } from "../../../core/Util"; import { Layer } from "./Layer"; import { EventBus } from "../../../core/EventBus"; -import anchorIncon from '../../../../../resources/images/AnchorIcon.png'; +import anchorIcon from '../../../../resources/images/AnchorIcon.png'; export class UnitLayer implements Layer { - private canvas: HTMLCanvasElement - private context: CanvasRenderingContext2D - private imageData: ImageData + private canvas: HTMLCanvasElement; + private context: CanvasRenderingContext2D; + private imageData: ImageData; + private anchorImage: HTMLImageElement; + private anchorImageLoaded: boolean = false; - private boatToTrail = new Map>() + private boatToTrail = new Map>(); - private theme: Theme = null + private theme: Theme = null; constructor(private game: Game, private eventBus: EventBus) { - this.theme = game.config().theme() + 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 + return true; } tick() { @@ -29,23 +40,23 @@ export class UnitLayer implements Layer { init(game: Game) { this.canvas = document.createElement('canvas'); - this.context = this.canvas.getContext("2d") + this.context = this.canvas.getContext("2d"); - this.imageData = this.context.getImageData(0, 0, this.game.width(), this.game.height()) + 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.initImageData(); - this.eventBus.on(UnitEvent, e => this.onUnitEvent(e)) + 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 - }) + const index = (tile.cell().y * this.game.width()) + tile.cell().x; + const offset = index * 4; + this.imageData.data[offset + 3] = 0; + }); } renderLayer(context: CanvasRenderingContext2D) { @@ -56,68 +67,115 @@ export class UnitLayer implements Layer { -this.game.height() / 2, this.game.width(), this.game.height() - ) + ); } + private handlePortEvent(event: UnitEvent) { + if (!this.anchorImageLoaded) 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.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.TransportShip: - this.handleBoatEvent(event) - break + this.handleBoatEvent(event); + break; case UnitType.Destroyer: - this.handleDestroyerEvent(event) - break + this.handleDestroyerEvent(event); + break; + case UnitType.Port: + this.handlePortEvent(event); + break; default: - throw Error(`event for unit ${event.unit.type()} not supported`) + throw Error(`event for unit ${event.unit.type()} not supported`); } } private handleDestroyerEvent(event: UnitEvent) { bfs(event.oldTile, euclDist(event.oldTile, 3)).forEach(t => { - this.clearCell(t.cell()) - }) + 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)) + .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)) + .forEach(t => this.paintCell(t.cell(), this.theme.territoryColor(event.unit.owner().info()), 180)); } private handleBoatEvent(event: UnitEvent) { if (!this.boatToTrail.has(event.unit)) { - this.boatToTrail.set(event.unit, new Set()) + this.boatToTrail.set(event.unit, new Set()); } - const trail = this.boatToTrail.get(event.unit) - trail.add(event.oldTile) + const trail = this.boatToTrail.get(event.unit); + trail.add(event.oldTile); bfs(event.oldTile, dist(event.oldTile, 3)).forEach(t => { - this.clearCell(t.cell()) - }) + 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) + 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)) + .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)) + .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) + 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 + 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 + this.imageData.data[offset + 3] = alpha; } clearCell(cell: Cell) { @@ -125,6 +183,4 @@ export class UnitLayer implements Layer { 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/core/execution/PortExecution.ts b/src/core/execution/PortExecution.ts index 73504f4e0..24ad7785a 100644 --- a/src/core/execution/PortExecution.ts +++ b/src/core/execution/PortExecution.ts @@ -1,8 +1,10 @@ -import { AllPlayers, Cell, Execution, MutableGame, MutablePlayer, PlayerID } from "../game/Game"; +import { AllPlayers, Cell, Execution, MutableGame, MutablePlayer, PlayerID, UnitType } from "../game/Game"; export class PortExecution implements Execution { private active = true + private mg: MutableGame + private player: MutablePlayer constructor( private _owner: PlayerID, @@ -11,9 +13,13 @@ export class PortExecution implements Execution { init(mg: MutableGame, ticks: number): void { + this.mg = mg + this.player = mg.player(this._owner) } tick(ticks: number): void { + this.player.addUnit(UnitType.Port, 0, this.mg.tile(this.cell)) + this.active = false } owner(): MutablePlayer { diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index 0976b5744..d002c308e 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -38,7 +38,7 @@ export class Item { export const Items = { Nuke: new Item("Nuke", 1_000_000), Destroyer: new Item("Destroyer", 10), - Port: new Item("Port", 10) + Port: new Item("Port", 0) } as const; export class Nation {