diff --git a/resources/images/buildings/silo4-reloading.png b/resources/images/buildings/silo4-reloading.png new file mode 100755 index 000000000..f7c22aa90 Binary files /dev/null and b/resources/images/buildings/silo4-reloading.png differ diff --git a/src/client/graphics/layers/StructureLayer.ts b/src/client/graphics/layers/StructureLayer.ts index b991c81eb..9e8fa4ea5 100644 --- a/src/client/graphics/layers/StructureLayer.ts +++ b/src/client/graphics/layers/StructureLayer.ts @@ -6,6 +6,7 @@ import { EventBus } from "../../../core/EventBus"; import anchorIcon from "../../../../resources/images/buildings/port1.png"; import missileSiloIcon from "../../../../resources/images/buildings/silo1.png"; import SAMMissileIcon from "../../../../resources/images/buildings/silo4.png"; +import SAMMissileReloadingIcon from "../../../../resources/images/buildings/silo4-reloading.png"; import shieldIcon from "../../../../resources/images/buildings/fortAlt2.png"; import cityIcon from "../../../../resources/images/buildings/cityAlt1.png"; import { GameView, UnitView } from "../../../core/game/GameView"; @@ -14,6 +15,7 @@ import { GameUpdateType } from "../../../core/game/GameUpdates"; import { euclDistFN } from "../../../core/game/GameMap"; const underConstructionColor = colord({ r: 150, g: 150, b: 150 }); +const reloadingColor = colord({ r: 255, g: 0, b: 0 }); interface UnitRenderConfig { icon: string; @@ -41,17 +43,17 @@ export class StructureLayer implements Layer { }, [UnitType.MissileSilo]: { icon: missileSiloIcon, - borderRadius: 8, + borderRadius: 9.5, territoryRadius: 6, }, [UnitType.DefensePost]: { icon: shieldIcon, - borderRadius: 8, + borderRadius: 9.5, territoryRadius: 6, }, [UnitType.SAMLauncher]: { icon: SAMMissileIcon, - borderRadius: 8, + borderRadius: 10, territoryRadius: 6, }, }; @@ -62,32 +64,41 @@ export class StructureLayer implements Layer { ) { this.theme = game.config().theme(); this.loadIconData(); + this.loadIcon("reloadingSam", { + icon: SAMMissileReloadingIcon, + borderRadius: 8.525, + territoryRadius: 6.525, + }); + } + + private loadIcon(unitType: string, config: UnitRenderConfig) { + const image = new Image(); + image.src = config.icon; + image.onload = () => { + // Create temporary canvas for icon processing + const tempCanvas = document.createElement("canvas"); + const tempContext = tempCanvas.getContext("2d"); + tempCanvas.width = image.width; + tempCanvas.height = image.height; + + // Draw the unit icon + tempContext.drawImage(image, 0, 0); + const iconData = tempContext.getImageData( + 0, + 0, + tempCanvas.width, + tempCanvas.height, + ); + this.unitIcons.set(unitType, iconData); + console.log( + `icond data width height: ${iconData.width}, ${iconData.height}`, + ); + }; } private loadIconData() { Object.entries(this.unitConfigs).forEach(([unitType, config]) => { - const image = new Image(); - image.src = config.icon; - image.onload = () => { - // Create temporary canvas for icon processing - const tempCanvas = document.createElement("canvas"); - const tempContext = tempCanvas.getContext("2d"); - tempCanvas.width = image.width; - tempCanvas.height = image.height; - - // Draw the unit icon - tempContext.drawImage(image, 0, 0); - const iconData = tempContext.getImageData( - 0, - 0, - tempCanvas.width, - tempCanvas.height, - ); - this.unitIcons.set(unitType, iconData); - console.log( - `icond data width height: ${iconData.width}, ${iconData.height}`, - ); - }; + this.loadIcon(unitType, config); }); } @@ -132,10 +143,17 @@ export class StructureLayer implements Layer { private handleUnitRendering(unit: UnitView) { const unitType = unit.constructionType() ?? unit.type(); + let iconType = unitType; if (!this.isUnitTypeSupported(unitType)) return; const config = this.unitConfigs[unitType]; - const icon = this.unitIcons.get(unitType); + let icon: ImageData; + + if (unitType == UnitType.SAMLauncher && unit.isSamCooldown()) { + icon = this.unitIcons.get("reloadingSam"); + } else { + icon = this.unitIcons.get(iconType); + } if (!config || !icon) return; @@ -151,6 +169,13 @@ export class StructureLayer implements Layer { return; } + let borderColor = this.theme.borderColor(unit.owner().info()); + if (unitType == UnitType.SAMLauncher && unit.isSamCooldown()) { + borderColor = reloadingColor; + } else if (unit.type() == UnitType.Construction) { + borderColor = underConstructionColor; + } + // Draw border and territory for (const tile of this.game.bfs( unit.tile(), @@ -158,9 +183,7 @@ export class StructureLayer implements Layer { )) { this.paintCell( new Cell(this.game.x(tile), this.game.y(tile)), - unit.type() == UnitType.Construction - ? underConstructionColor - : this.theme.borderColor(unit.owner().info()), + borderColor, 255, ); } diff --git a/src/core/configuration/Config.ts b/src/core/configuration/Config.ts index e2c8ed4c2..634c1b3b8 100644 --- a/src/core/configuration/Config.ts +++ b/src/core/configuration/Config.ts @@ -170,6 +170,7 @@ export interface Config { export interface Theme { territoryColor(playerInfo: PlayerInfo): Colord; + specialBuildingColor(playerInfo: PlayerInfo): Colord; borderColor(playerInfo: PlayerInfo): Colord; defendedBorderColor(playerInfo: PlayerInfo): Colord; terrainColor(gm: GameMap, tile: TileRef): Colord; diff --git a/src/core/configuration/PastelTheme.ts b/src/core/configuration/PastelTheme.ts index 50c5278c4..a1c3a1ecc 100644 --- a/src/core/configuration/PastelTheme.ts +++ b/src/core/configuration/PastelTheme.ts @@ -256,6 +256,15 @@ export const pastelTheme = new (class implements Theme { return playerInfo.playerType == PlayerType.Human ? "#000000" : "#4D4D4D"; } + specialBuildingColor(playerInfo: PlayerInfo): Colord { + const tc = this.territoryColor(playerInfo).rgba; + return colord({ + r: Math.max(tc.r - 50, 0), + g: Math.max(tc.g - 50, 0), + b: Math.max(tc.b - 50, 0), + }); + } + borderColor(playerInfo: PlayerInfo): Colord { const tc = this.territoryColor(playerInfo).rgba; return colord({ diff --git a/src/core/configuration/PastelThemeDark.ts b/src/core/configuration/PastelThemeDark.ts index cea7fc82e..a7b030889 100644 --- a/src/core/configuration/PastelThemeDark.ts +++ b/src/core/configuration/PastelThemeDark.ts @@ -256,6 +256,15 @@ export const pastelThemeDark = new (class implements Theme { return playerInfo.playerType == PlayerType.Human ? "#ffffff" : "#e6e6e6"; } + specialBuildingColor(playerInfo: PlayerInfo): Colord { + const tc = this.territoryColor(playerInfo).rgba; + return colord({ + r: Math.max(tc.r - 50, 0), + g: Math.max(tc.g - 50, 0), + b: Math.max(tc.b - 50, 0), + }); + } + borderColor(playerInfo: PlayerInfo): Colord { const tc = this.territoryColor(playerInfo).rgba; return colord({ diff --git a/src/core/execution/SAMLauncherExecution.ts b/src/core/execution/SAMLauncherExecution.ts index cbf2f583a..73a0ee8bb 100644 --- a/src/core/execution/SAMLauncherExecution.ts +++ b/src/core/execution/SAMLauncherExecution.ts @@ -92,8 +92,15 @@ export class SAMLauncherExecution implements Execution { ); })[0] ?? null; + const cooldown = + this.lastMissileAttack != 0 && + this.mg.ticks() - this.lastMissileAttack <= this.missileAttackRate; + if (this.post.isSamCooldown() != cooldown) { + this.post.setSamCooldown(cooldown); + } + if (this.target != null) { - if (this.mg.ticks() - this.lastMissileAttack > this.missileAttackRate) { + if (!this.post.isSamCooldown()) { this.lastMissileAttack = this.mg.ticks(); this.mg.addExecution( new SAMMissileExecution( diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index e3244781b..77c9a7d94 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -235,6 +235,8 @@ export interface Unit { setWarshipTarget(target: Unit): void; // warship only warshipTarget(): Unit; + setSamCooldown(isCoolingDown: boolean): void; // Only for sam + isSamCooldown(): boolean; setDstPort(dstPort: Unit): void; dstPort(): Unit; // Only for trade ships detonationDst(): TileRef; // Only for nukes diff --git a/src/core/game/GameUpdates.ts b/src/core/game/GameUpdates.ts index 7efaab80f..9461a05b4 100644 --- a/src/core/game/GameUpdates.ts +++ b/src/core/game/GameUpdates.ts @@ -74,6 +74,7 @@ export interface UnitUpdate { warshipTargetId?: number; health?: number; constructionType?: UnitType; + isSamCooldown?: boolean; } export interface AttackUpdate { diff --git a/src/core/game/GameView.ts b/src/core/game/GameView.ts index e0ebf40f8..6a04f96ad 100644 --- a/src/core/game/GameView.ts +++ b/src/core/game/GameView.ts @@ -111,6 +111,9 @@ export class UnitView { } return this.data.warshipTargetId; } + isSamCooldown(): boolean { + return this.data.isSamCooldown; + } } export class PlayerView { diff --git a/src/core/game/UnitImpl.ts b/src/core/game/UnitImpl.ts index 43cb48c14..aaa783508 100644 --- a/src/core/game/UnitImpl.ts +++ b/src/core/game/UnitImpl.ts @@ -18,6 +18,7 @@ export class UnitImpl implements Unit { private _constructionType: UnitType = undefined; + private _isSamCooldown: boolean; private _dstPort: Unit | null = null; // Only for trade ships private _detonationDst: TileRef | null = null; // Only for nukes private _warshipTarget: Unit | null = null; @@ -59,6 +60,7 @@ export class UnitImpl implements Unit { dstPortId: dstPort ? dstPort.id() : null, warshipTargetId: warshipTarget ? warshipTarget.id() : null, detonationDst: this.detonationDst(), + isSamCooldown: this.isSamCooldown() ? this.isSamCooldown() : null, }; } @@ -182,6 +184,15 @@ export class UnitImpl implements Unit { return this._dstPort; } + setSamCooldown(cooldown: boolean): void { + this._isSamCooldown = cooldown; + this.mg.addUpdate(this.toUpdate()); + } + + isSamCooldown(): boolean { + return this._isSamCooldown; + } + setDstPort(dstPort: Unit): void { this._dstPort = dstPort; }