diff --git a/resources/lang/en.json b/resources/lang/en.json index 64c491ee9..701c383fa 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -400,7 +400,8 @@ "sams": "SAMs", "warships": "Warships", "health": "Health", - "attitude": "Attitude" + "attitude": "Attitude", + "levels": "Levels" }, "events_display": { "retreating": "retreating", @@ -411,7 +412,9 @@ "unit_type_unknown": "Unknown", "close": "Close", "cooldown": "Cooldown", - "type": "Type" + "type": "Type", + "upgrade": "Upgrade", + "level": "Level" }, "relation": { "hostile": "Hostile", diff --git a/src/client/Transport.ts b/src/client/Transport.ts index e9124b6f5..b1e73fded 100644 --- a/src/client/Transport.ts +++ b/src/client/Transport.ts @@ -44,6 +44,13 @@ export class SendBreakAllianceIntentEvent implements GameEvent { ) {} } +export class SendUpgradeStructureIntentEvent implements GameEvent { + constructor( + public readonly unitId: number, + public readonly unitType: UnitType, + ) {} +} + export class SendAllianceReplyIntentEvent implements GameEvent { constructor( // The original alliance requestor @@ -187,6 +194,9 @@ export class Transport { this.onSendSpawnIntentEvent(e), ); this.eventBus.on(SendAttackIntentEvent, (e) => this.onSendAttackIntent(e)); + this.eventBus.on(SendUpgradeStructureIntentEvent, (e) => + this.onSendUpgradeStructureIntent(e), + ); this.eventBus.on(SendBoatAttackIntentEvent, (e) => this.onSendBoatAttackIntent(e), ); @@ -427,6 +437,15 @@ export class Transport { }); } + private onSendUpgradeStructureIntent(event: SendUpgradeStructureIntentEvent) { + this.sendIntent({ + type: "upgrade_structure", + unit: event.unitType, + clientID: this.lobbyConfig.clientID, + unitId: event.unitId, + }); + } + private onSendTargetPlayerIntent(event: SendTargetPlayerIntentEvent) { this.sendIntent({ type: "targetPlayer", diff --git a/src/client/graphics/layers/PlayerInfoOverlay.ts b/src/client/graphics/layers/PlayerInfoOverlay.ts index 099a865e2..261fd3f0f 100644 --- a/src/client/graphics/layers/PlayerInfoOverlay.ts +++ b/src/client/graphics/layers/PlayerInfoOverlay.ts @@ -240,18 +240,58 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
${translateText("player_info_overlay.ports")}: ${player.units(UnitType.Port).length} + ${player + .units(UnitType.Port) + .map((unit) => unit.level()) + .reduce((a, b) => a + b, 0) > 1 + ? html`(${translateText("player_info_overlay.levels")}: + ${player + .units(UnitType.Port) + .map((unit) => unit.level()) + .reduce((a, b) => a + b, 0)})` + : ""}
${translateText("player_info_overlay.cities")}: ${player.units(UnitType.City).length} + ${player + .units(UnitType.City) + .map((unit) => unit.level()) + .reduce((a, b) => a + b, 0) > 1 + ? html`(${translateText("player_info_overlay.levels")}: + ${player + .units(UnitType.City) + .map((unit) => unit.level()) + .reduce((a, b) => a + b, 0)})` + : ""}
${translateText("player_info_overlay.missile_launchers")}: ${player.units(UnitType.MissileSilo).length} + ${player + .units(UnitType.MissileSilo) + .map((unit) => unit.level()) + .reduce((a, b) => a + b, 0) > 1 + ? html`(${translateText("player_info_overlay.levels")}: + ${player + .units(UnitType.MissileSilo) + .map((unit) => unit.level()) + .reduce((a, b) => a + b, 0)})` + : ""}
${translateText("player_info_overlay.sams")}: ${player.units(UnitType.SAMLauncher).length} + ${player + .units(UnitType.SAMLauncher) + .map((unit) => unit.level()) + .reduce((a, b) => a + b, 0) > 1 + ? html`(${translateText("player_info_overlay.levels")}: + ${player + .units(UnitType.SAMLauncher) + .map((unit) => unit.level()) + .reduce((a, b) => a + b, 0)})` + : ""}
${translateText("player_info_overlay.warships")}: diff --git a/src/client/graphics/layers/StructureLayer.ts b/src/client/graphics/layers/StructureLayer.ts index 180005d26..180e838ab 100644 --- a/src/client/graphics/layers/StructureLayer.ts +++ b/src/client/graphics/layers/StructureLayer.ts @@ -242,13 +242,13 @@ export class StructureLayer implements Layer { const config = this.unitConfigs[unitType]; let icon: ImageData | undefined; - if (unitType === UnitType.SAMLauncher && unit.isCooldown()) { + if (unitType === UnitType.SAMLauncher && unit.isInCooldown()) { icon = this.unitIcons.get("reloadingSam"); } else { icon = this.unitIcons.get(iconType); } - if (unitType === UnitType.MissileSilo && unit.isCooldown()) { + if (unitType === UnitType.MissileSilo && unit.isInCooldown()) { icon = this.unitIcons.get("reloadingSilo"); } else { icon = this.unitIcons.get(iconType); @@ -268,13 +268,13 @@ export class StructureLayer implements Layer { if (!unit.isActive()) return; let borderColor = this.theme.borderColor(unit.owner()); - if (unitType === UnitType.SAMLauncher && unit.isCooldown()) { + if (unitType === UnitType.SAMLauncher && unit.isInCooldown()) { borderColor = reloadingColor; } else if (unit.type() === UnitType.Construction) { borderColor = underConstructionColor; } - if (unitType === UnitType.MissileSilo && unit.isCooldown()) { + if (unitType === UnitType.MissileSilo && unit.isInCooldown()) { borderColor = reloadingColor; } else if (unit.type() === UnitType.Construction) { borderColor = underConstructionColor; @@ -391,6 +391,7 @@ export class StructureLayer implements Layer { const screenPos = this.transformHandler.worldToScreenCoordinates(cell); const unitTile = clickedUnit.tile(); this.unitInfoModal?.onOpenStructureModal({ + eventBus: this.eventBus, unit: clickedUnit, x: screenPos.x, y: screenPos.y, diff --git a/src/client/graphics/layers/UILayer.ts b/src/client/graphics/layers/UILayer.ts index 9918d8c4f..c9fa49933 100644 --- a/src/client/graphics/layers/UILayer.ts +++ b/src/client/graphics/layers/UILayer.ts @@ -128,7 +128,7 @@ export class UILayer implements Layer { } case UnitType.SAMLauncher: case UnitType.MissileSilo: - if (unit.isActive() && unit.isCooldown()) { + if (unit.isActive() && unit.isInCooldown()) { const endTick = unit.ticksLeftInCooldown() || 0; this.drawLoadingBar(unit, endTick); } diff --git a/src/client/graphics/layers/UnitInfoModal.ts b/src/client/graphics/layers/UnitInfoModal.ts index 0066aa309..cee548768 100644 --- a/src/client/graphics/layers/UnitInfoModal.ts +++ b/src/client/graphics/layers/UnitInfoModal.ts @@ -1,8 +1,10 @@ import { LitElement, css, html } from "lit"; import { customElement, property } from "lit/decorators.js"; import { translateText } from "../../../client/Utils"; +import { EventBus } from "../../../core/EventBus"; import { UnitType } from "../../../core/game/Game"; import { GameView, UnitView } from "../../../core/game/GameView"; +import { SendUpgradeStructureIntentEvent } from "../../Transport"; import { Layer } from "./Layer"; import { StructureLayer } from "./StructureLayer"; @@ -15,6 +17,7 @@ export class UnitInfoModal extends LitElement implements Layer { public game: GameView; public structureLayer: StructureLayer | null = null; + private eventBus: EventBus; constructor() { super(); @@ -29,12 +32,14 @@ export class UnitInfoModal extends LitElement implements Layer { } public onOpenStructureModal = ({ + eventBus, unit, x, y, tileX, tileY, }: { + eventBus: EventBus; unit: UnitView; x: number; y: number; @@ -44,6 +49,7 @@ export class UnitInfoModal extends LitElement implements Layer { if (!this.game) return; this.x = x; this.y = y; + this.eventBus = eventBus; const targetRef = this.game.ref(tileX, tileY); const allUnitTypes = Object.values(UnitType); @@ -119,12 +125,44 @@ export class UnitInfoModal extends LitElement implements Layer { .close-button:hover { background: #a00; } + + .upgrade-button { + background: #3a0; + color: #fff; + border: none; + border-radius: 4px; + font-size: 14px; + font-weight: bold; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + line-height: 1; + padding: 6px 12px; + } + + .upgrade-button:hover { + background: #0a0; + } `; render() { if (!this.unit) return null; - const cooldown = this.unit.ticksLeftInCooldown() ?? 0; + const ticksLeftInCooldown = this.unit.ticksLeftInCooldown(); + let configTimer; + switch (this.unit.type()) { + case UnitType.MissileSilo: + configTimer = this.game.config().SiloCooldown(); + break; + case UnitType.SAMLauncher: + configTimer = this.game.config().SAMCooldown(); + break; + } + let cooldown = 0; + if (ticksLeftInCooldown !== undefined && configTimer !== undefined) { + cooldown = configTimer - (this.game.ticks() - ticksLeftInCooldown); + } const secondsLeft = Math.ceil(cooldown / 10); return html` @@ -140,6 +178,16 @@ export class UnitInfoModal extends LitElement implements Layer { ${translateText("unit_info_modal.type")}: ${translateText(+"unit_type." + this.unit.type?.().toLowerCase()) ?? translateText("unit_info_modal.unit_type_unknown")} + ${translateText("unit_info_modal.level")}: + ${this.game.unitInfo(this.unit.type()).upgradable && + this.unit.level?.() + ? this.unit.level?.() + : ""}
${secondsLeft > 0 ? html`
@@ -147,7 +195,30 @@ export class UnitInfoModal extends LitElement implements Layer { ${secondsLeft}s
` : ""} -
+
+