mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-23 17:06:51 +00:00
feat(nukewars): Enhance Nuke Wars UI for preparation phase and spawn indication
This commit refines the Nuke Wars game mode UI based on user feedback. Key changes include: - Preparation Phase Timer: Modified `SpawnTimer.ts` to display a countdown for the 3-minute preparation phase, aligning with the top-right game timers design. - Spawn Area Indication: - Removed the previous white line separator from `TerrainLayer.ts`. - Implemented team-specific spawn boxes in `TerritoryLayer.ts` for Nuke Wars on the Baikal map. These boxes, labeled "Red Team Spawn" and "Blue Team Spawn", appear during the spawn phase to clearly indicate team territories.
This commit is contained in:
@@ -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`
|
||||
<div
|
||||
class="w-full h-full flex justify-center items-center bg-gray-800/70 text-white text-lg font-bold"
|
||||
>
|
||||
${this.timerText}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (this.ratios.length === 0 || this.colors.length === 0) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user