diff --git a/src/client/graphics/layers/SpawnTimer.ts b/src/client/graphics/layers/SpawnTimer.ts
index 393cf96d4..112951c4f 100644
--- a/src/client/graphics/layers/SpawnTimer.ts
+++ b/src/client/graphics/layers/SpawnTimer.ts
@@ -1,5 +1,5 @@
import { LitElement, html } from "lit";
-import { customElement } from "lit/decorators.js";
+import { customElement, state } from "lit/decorators.js";
import { GameMode, Team } from "../../../core/game/Game";
import { GameView } from "../../../core/game/GameView";
import { TransformHandler } from "../TransformHandler";
@@ -15,6 +15,19 @@ export class SpawnTimer extends LitElement implements Layer {
private isVisible = false;
+ @state()
+ private timerText: string = "";
+
+ private secondsToHms = (d: number): string => {
+ const h = Math.floor(d / 3600);
+ const m = Math.floor((d % 3600) / 60);
+ const s = Math.floor((d % 3600) % 60);
+ let time = d === 0 ? "-" : `${s}s`;
+ if (m > 0) time = `${m}m` + time;
+ if (h > 0) time = `${h}h` + time;
+ return time;
+ };
+
createRenderRoot() {
this.style.position = "fixed";
this.style.top = "0";
@@ -31,16 +44,28 @@ export class SpawnTimer extends LitElement implements Layer {
}
tick() {
- if (this.game.inSpawnPhase()) {
+ const isNukeWars =
+ this.game.config().gameConfig().gameMode === GameMode.NukeWars;
+ const spawnTurns = this.game.config().numSpawnPhaseTurns();
+ const prepTurns = this.game.config().numPreparationPhaseTurns();
+ const ticks = this.game.ticks();
+
+ if (ticks <= spawnTurns) {
// During spawn phase, only one segment filling full width
- this.ratios = [
- this.game.ticks() / this.game.config().numSpawnPhaseTurns(),
- ];
+ this.ratios = [ticks / spawnTurns];
this.colors = ["rgba(0, 128, 255, 0.7)"];
this.requestUpdate();
return;
+ } else if (isNukeWars && ticks <= spawnTurns + prepTurns) {
+ // Nuke Wars Prep phase
+ const elapsedInPrep = ticks - spawnTurns;
+ const remainingSeconds = Math.max(0, (prepTurns - elapsedInPrep) / 10);
+ this.timerText = this.secondsToHms(remainingSeconds);
+ this.requestUpdate();
+ return;
}
+ // Existing logic for team territory ratios
this.ratios = [];
this.colors = [];
@@ -81,6 +106,23 @@ export class SpawnTimer extends LitElement implements Layer {
return html``;
}
+ const isNukeWars =
+ this.game.config().gameConfig().gameMode === GameMode.NukeWars;
+ const spawnTurns = this.game.config().numSpawnPhaseTurns();
+ const prepTurns = this.game.config().numPreparationPhaseTurns();
+ const ticks = this.game.ticks();
+
+ if (isNukeWars && ticks > spawnTurns && ticks <= spawnTurns + prepTurns) {
+ // Display countdown timer for Nuke Wars Prep phase
+ return html`
+
+ ${this.timerText}
+
+ `;
+ }
+
if (this.ratios.length === 0 || this.colors.length === 0) {
return html``;
}
diff --git a/src/client/graphics/layers/TerrainLayer.ts b/src/client/graphics/layers/TerrainLayer.ts
index 36f1d321e..1f7bc0b2d 100644
--- a/src/client/graphics/layers/TerrainLayer.ts
+++ b/src/client/graphics/layers/TerrainLayer.ts
@@ -1,5 +1,4 @@
import { Theme } from "../../../core/configuration/Config";
-import { GameMapType, GameMode } from "../../../core/game/Game";
import { GameView } from "../../../core/game/GameView";
import { TransformHandler } from "../TransformHandler";
import { Layer } from "./Layer";
@@ -74,20 +73,5 @@ export class TerrainLayer implements Layer {
this.game.width(),
this.game.height(),
);
-
- if (
- this.game.config().gameConfig().gameMode === GameMode.NukeWars &&
- this.game.config().gameConfig().gameMap === GameMapType.Baikal
- ) {
- const height = this.game.height();
- const midpoint = 0; // The map is centered, so midpoint is at x=0
-
- context.beginPath();
- context.moveTo(midpoint, -height / 2);
- context.lineTo(midpoint, height / 2);
- context.strokeStyle = "white";
- context.lineWidth = 2 / this.transformHandler.scale; // Make the line width independent of zoom
- context.stroke();
- }
}
}
diff --git a/src/client/graphics/layers/TerritoryLayer.ts b/src/client/graphics/layers/TerritoryLayer.ts
index 945ac3524..3706bc1b9 100644
--- a/src/client/graphics/layers/TerritoryLayer.ts
+++ b/src/client/graphics/layers/TerritoryLayer.ts
@@ -2,7 +2,13 @@ import { PriorityQueue } from "@datastructures-js/priority-queue";
import { Colord } from "colord";
import { Theme } from "../../../core/configuration/Config";
import { EventBus } from "../../../core/EventBus";
-import { Cell, PlayerType, UnitType } from "../../../core/game/Game";
+import {
+ Cell,
+ GameMapType,
+ GameMode,
+ PlayerType,
+ UnitType,
+} from "../../../core/game/Game";
import { euclDistFN, TileRef } from "../../../core/game/GameMap";
import { GameUpdateType } from "../../../core/game/GameUpdates";
import { GameView, PlayerView } from "../../../core/game/GameView";
@@ -151,6 +157,36 @@ export class TerritoryLayer implements Layer {
}
}
+ private drawTeamSpawnBox(
+ context: CanvasRenderingContext2D,
+ x: number,
+ y: number,
+ text: string,
+ color: string,
+ ) {
+ context.font = "bold 16px Arial";
+ context.textAlign = "center";
+ context.textBaseline = "middle";
+ context.fillStyle = color;
+ context.strokeStyle = "black";
+ context.lineWidth = 1;
+
+ const textWidth = context.measureText(text).width;
+ const padding = 10;
+ const boxWidth = textWidth + 2 * padding;
+ const boxHeight = 20 + 2 * padding; // Assuming font size 20
+
+ context.fillRect(x - boxWidth / 2, y - boxHeight / 2, boxWidth, boxHeight);
+ context.strokeRect(
+ x - boxWidth / 2,
+ y - boxHeight / 2,
+ boxWidth,
+ boxHeight,
+ );
+ context.fillStyle = "white";
+ context.fillText(text, x, y);
+ }
+
private spawnHighlight() {
if (this.game.ticks() % 5 === 0) {
return;
@@ -163,43 +199,77 @@ export class TerritoryLayer implements Layer {
this.game.height(),
);
- this.drawFocusedPlayerHighlight();
+ const isNukeWars =
+ this.game.config().gameConfig().gameMode === GameMode.NukeWars;
+ const isBaikal =
+ this.game.config().gameConfig().gameMap === GameMapType.Baikal;
- const humans = this.game
- .playerViews()
- .filter((p) => p.type() === PlayerType.Human);
+ if (isNukeWars && isBaikal && this.game.inSpawnPhase()) {
+ // The map is centered, so coordinates are from -width/2 to width/2
+ // The midpoint is at x=0
+ // Left box at -width/4, right box at width/4
+ // Y coordinate is 0 (center of the map)
- const focusedPlayer = this.game.focusedPlayer();
- for (const human of humans) {
- if (human === focusedPlayer) {
- continue;
- }
- const center = human.nameLocation();
- if (!center) {
- continue;
- }
- const centerTile = this.game.ref(center.x, center.y);
- if (!centerTile) {
- continue;
- }
- let color = this.theme.spawnHighlightColor();
- const myPlayer = this.game.myPlayer();
- if (myPlayer !== null && myPlayer !== human && myPlayer.team() === null) {
- // In FFA games (when team === null), use default yellow spawn highlight color
- color = this.theme.spawnHighlightColor();
- } else if (myPlayer !== null && myPlayer !== human) {
- // In Team games, the spawn highlight color becomes that player's team color
- // Optionally, this could be broken down to teammate or enemy and simplified to green and red, respectively
- const team = human.team();
- if (team !== null) color = this.theme.teamColor(team);
- }
+ // Red Team Spawn (Left Side)
+ this.drawTeamSpawnBox(
+ this.highlightContext,
+ -this.game.width() / 4,
+ 0,
+ "Red Team Spawn",
+ "rgba(255, 0, 0, 0.5)",
+ );
- for (const tile of this.game.bfs(
- centerTile,
- euclDistFN(centerTile, 9, true),
- )) {
- if (!this.game.hasOwner(tile)) {
- this.paintHighlightTile(tile, color, 255);
+ // Blue Team Spawn (Right Side)
+ this.drawTeamSpawnBox(
+ this.highlightContext,
+ this.game.width() / 4,
+ 0,
+ "Blue Team Spawn",
+ "rgba(0, 0, 255, 0.5)",
+ );
+ } else {
+ this.drawFocusedPlayerHighlight();
+
+ const humans = this.game
+ .playerViews()
+ .filter((p) => p.type() === PlayerType.Human);
+
+ const focusedPlayer = this.game.focusedPlayer();
+ for (const human of humans) {
+ if (human === focusedPlayer) {
+ continue;
+ }
+ const center = human.nameLocation();
+ if (!center) {
+ continue;
+ }
+ const centerTile = this.game.ref(center.x, center.y);
+ if (!centerTile) {
+ continue;
+ }
+ let color = this.theme.spawnHighlightColor();
+ const myPlayer = this.game.myPlayer();
+ if (
+ myPlayer !== null &&
+ myPlayer !== human &&
+ myPlayer.team() === null
+ ) {
+ // In FFA games (when team === null), use default yellow spawn highlight color
+ color = this.theme.spawnHighlightColor();
+ } else if (myPlayer !== null && myPlayer !== human) {
+ // In Team games, the spawn highlight color becomes that player's team color
+ // Optionally, this could be broken down to teammate or enemy and simplified to green and red, respectively
+ const team = human.team();
+ if (team !== null) color = this.theme.teamColor(team);
+ }
+
+ for (const tile of this.game.bfs(
+ centerTile,
+ euclDistFN(centerTile, 9, true),
+ )) {
+ if (!this.game.hasOwner(tile)) {
+ this.paintHighlightTile(tile, color, 255);
+ }
}
}
}