Skip to content
Draft
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions resources/images/GridIconWhite.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions resources/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,8 @@
"attack_ratio_desc": "What percentage of your troops to send in an attack (1–100%)",
"territory_patterns_label": "🏳️ Territory Skins",
"territory_patterns_desc": "Choose whether to display territory skin designs in game",
"coordinate_grid_label": "Coordinate Grid",
"coordinate_grid_desc": "Show the alphanumeric grid in Alternate View",
"performance_overlay_label": "Performance Overlay",
"performance_overlay_desc": "Toggle the performance overlay. When enabled, the performance overlay will be displayed. Press shift-D during game to toggle.",
"easter_writing_speed_label": "Writing Speed Multiplier",
Expand Down
16 changes: 16 additions & 0 deletions src/client/UserSettingModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,13 @@ export class UserSettingModal extends BaseModal {
console.log("🏳️ Territory Patterns:", enabled ? "ON" : "OFF");
}

private toggleCoordinateGrid(e: CustomEvent<{ checked: boolean }>) {
const enabled = e.detail?.checked;
if (typeof enabled !== "boolean") return;

this.userSettings.set("settings.coordinateGridEnabled", enabled);
}

private togglePerformanceOverlay(e: CustomEvent<{ checked: boolean }>) {
const enabled = e.detail?.checked;
if (typeof enabled !== "boolean") return;
Expand Down Expand Up @@ -875,6 +882,15 @@ export class UserSettingModal extends BaseModal {
@change=${this.toggleTerritoryPatterns}
></setting-toggle>

<!-- 🧭 Coordinate Grid -->
<setting-toggle
label="${translateText("user_setting.coordinate_grid_label")}"
description="${translateText("user_setting.coordinate_grid_desc")}"
id="coordinate-grid-toggle"
.checked=${this.userSettings.coordinateGridEnabled()}
@change=${this.toggleCoordinateGrid}
></setting-toggle>

<!-- 📱 Performance Overlay -->
<setting-toggle
label="${translateText("user_setting.performance_overlay_label")}"
Expand Down
2 changes: 2 additions & 0 deletions src/client/graphics/GameRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { BuildMenu } from "./layers/BuildMenu";
import { ChatDisplay } from "./layers/ChatDisplay";
import { ChatModal } from "./layers/ChatModal";
import { ControlPanel } from "./layers/ControlPanel";
import { CoordinateGridLayer } from "./layers/CoordinateGridLayer";
import { DynamicUILayer } from "./layers/DynamicUILayer";
import { EmojiTable } from "./layers/EmojiTable";
import { EventsDisplay } from "./layers/EventsDisplay";
Expand Down Expand Up @@ -251,6 +252,7 @@ export function createRenderer(
new TerrainLayer(game, transformHandler),
new TerritoryLayer(game, eventBus, transformHandler, userSettings),
new RailroadLayer(game, eventBus, transformHandler),
new CoordinateGridLayer(game, eventBus, transformHandler, userSettings),
structureLayer,
samRadiusLayer,
new UnitLayer(game, eventBus, transformHandler),
Expand Down
178 changes: 178 additions & 0 deletions src/client/graphics/layers/CoordinateGridLayer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import { EventBus } from "../../../core/EventBus";
import { Cell } from "../../../core/game/Game";
import { GameView } from "../../../core/game/GameView";
import { UserSettings } from "../../../core/game/UserSettings";
import { AlternateViewEvent } from "../../InputHandler";
import { TransformHandler } from "../TransformHandler";
import { Layer } from "./Layer";

const BASE_CELL_COUNT = 10;
const MAX_COLUMNS = 50;
const MIN_ROWS = 2;
const LABEL_PADDING = 8;
const LABEL_BG_PADDING = 4;

const toAlphaLabel = (index: number): string => {
let value = index;
let label = "";
do {
label = String.fromCharCode(65 + (value % 26)) + label;
value = Math.floor(value / 26) - 1;
} while (value >= 0);
return label;
};

const computeGrid = (width: number, height: number) => {
let cellSize = Math.min(width, height) / BASE_CELL_COUNT;
let rows = Math.max(1, Math.round(height / cellSize));
let cols = Math.max(1, Math.round(width / cellSize));

if (cols > MAX_COLUMNS) {
const maxRowsForCols = Math.floor((MAX_COLUMNS * height) / width);
rows = Math.max(MIN_ROWS, Math.min(rows, maxRowsForCols));
}

cellSize = height / rows;
cols = Math.max(1, Math.round(width / cellSize));

return { cellSize, rows, cols };
};

export class CoordinateGridLayer implements Layer {
private isVisible = false;

constructor(
private game: GameView,
private eventBus: EventBus,
private transformHandler: TransformHandler,
private userSettings: UserSettings,
) {}

init() {
this.eventBus.on(AlternateViewEvent, (event) => {
this.isVisible = event.alternateView;
});
}

shouldTransform(): boolean {
return false;
}

renderLayer(context: CanvasRenderingContext2D) {
if (!this.isVisible || !this.userSettings.coordinateGridEnabled()) return;

const width = this.game.width();
const height = this.game.height();
if (width <= 0 || height <= 0) return;

const { cellSize, rows, cols } = computeGrid(width, height);
const cellWidth = cellSize;
const cellHeight = cellSize;
const canvasWidth = context.canvas.width;
const canvasHeight = context.canvas.height;

context.save();
context.strokeStyle = "rgba(255, 255, 255, 0.35)";
context.lineWidth = 1.25;
context.beginPath();

for (let col = 0; col <= cols; col++) {
const worldX = col * cellWidth;
if (worldX > width) break;
const screenX = this.transformHandler.worldToScreenCoordinates(
new Cell(worldX, 0),
).x;
if (screenX < -1 || screenX > canvasWidth + 1) continue;
context.moveTo(screenX, 0);
context.lineTo(screenX, canvasHeight);
}

for (let row = 0; row <= rows; row++) {
const worldY = row * cellHeight;
if (worldY > height) break;
const screenY = this.transformHandler.worldToScreenCoordinates(
new Cell(0, worldY),
).y;
if (screenY < -1 || screenY > canvasHeight + 1) continue;
context.moveTo(0, screenY);
context.lineTo(canvasWidth, screenY);
}

context.stroke();

context.font = "12px monospace";

const drawLabel = (
text: string,
x: number,
y: number,
align: CanvasTextAlign,
baseline: CanvasTextBaseline,
) => {
context.textAlign = align;
context.textBaseline = baseline;
const metrics = context.measureText(text);
const textWidth = metrics.width;
const textHeight =
(metrics.actualBoundingBoxAscent ?? 8) +
(metrics.actualBoundingBoxDescent ?? 4);

let rectX = x;
let rectY = y;

if (align === "center") rectX -= textWidth / 2;
if (align === "right") rectX -= textWidth;
if (baseline === "middle") rectY -= textHeight / 2;
if (baseline === "bottom") rectY -= textHeight;

context.fillStyle = "rgba(0, 0, 0, 0.55)";
context.fillRect(
rectX - LABEL_BG_PADDING,
rectY - LABEL_BG_PADDING,
textWidth + LABEL_BG_PADDING * 2,
textHeight + LABEL_BG_PADDING * 2,
);

context.fillStyle = "rgba(255, 255, 255, 0.95)";
context.fillText(text, x, y);
};

const mapTopScreen = this.transformHandler.worldToScreenCoordinates(
new Cell(0, 0),
).y;
const mapLeftScreen = this.transformHandler.worldToScreenCoordinates(
new Cell(0, 0),
).x;

const labelY = Math.min(
Math.max(mapTopScreen + LABEL_PADDING, LABEL_PADDING),
canvasHeight - LABEL_PADDING,
);
const labelX = Math.min(
Math.max(mapLeftScreen + LABEL_PADDING, LABEL_PADDING),
canvasWidth - LABEL_PADDING,
);

for (let col = 0; col < cols; col++) {
const centerX = (col + 0.5) * cellWidth;
if (centerX > width) break;
const screenX = this.transformHandler.worldToScreenCoordinates(
new Cell(centerX, 0),
).x;
if (screenX < 0 || screenX > canvasWidth) continue;
drawLabel(String(col + 1), screenX, labelY, "center", "top");
}

for (let row = 0; row < rows; row++) {
const centerY = (row + 0.5) * cellHeight;
if (centerY > height) break;
const screenY = this.transformHandler.worldToScreenCoordinates(
new Cell(0, centerY),
).y;
if (screenY < 0 || screenY > canvasHeight) continue;
drawLabel(toAlphaLabel(row), labelX, screenY, "left", "middle");
}

context.restore();
}
}
26 changes: 26 additions & 0 deletions src/client/graphics/layers/SettingsModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import darkModeIcon from "/images/DarkModeIconWhite.svg?url";
import emojiIcon from "/images/EmojiIconWhite.svg?url";
import exitIcon from "/images/ExitIconWhite.svg?url";
import explosionIcon from "/images/ExplosionIconWhite.svg?url";
import gridIcon from "/images/GridIconWhite.svg?url";
import mouseIcon from "/images/MouseIconWhite.svg?url";
import ninjaIcon from "/images/NinjaIconWhite.svg?url";
import settingsIcon from "/images/SettingIconWhite.svg?url";
Expand Down Expand Up @@ -120,6 +121,11 @@ export class SettingsModal extends LitElement implements Layer {
this.requestUpdate();
}

private onToggleCoordinateGridButtonClick() {
this.userSettings.toggleCoordinateGrid();
this.requestUpdate();
}

private onToggleStructureSpritesButtonClick() {
this.userSettings.toggleStructureSprites();
this.requestUpdate();
Expand Down Expand Up @@ -286,6 +292,26 @@ export class SettingsModal extends LitElement implements Layer {
</div>
</button>

<button
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded-sm text-white transition-colors"
@click="${this.onToggleCoordinateGridButtonClick}"
>
<img src=${gridIcon} alt="gridIcon" width="20" height="20" />
<div class="flex-1">
<div class="font-medium">
${translateText("user_setting.coordinate_grid_label")}
</div>
<div class="text-sm text-slate-400">
${translateText("user_setting.coordinate_grid_desc")}
</div>
</div>
<div class="text-sm text-slate-400">
${this.userSettings.coordinateGridEnabled()
? translateText("user_setting.on")
: translateText("user_setting.off")}
</div>
</button>

<button
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded-sm text-white transition-colors"
@click="${this.onToggleEmojisButtonClick}"
Expand Down
8 changes: 8 additions & 0 deletions src/core/game/UserSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ export class UserSettings {
return this.get("settings.territoryPatterns", true);
}

coordinateGridEnabled() {
return this.get("settings.coordinateGridEnabled", true);
}

cursorCostLabel() {
const legacy = this.get("settings.ghostPricePill", true);
return this.get("settings.cursorCostLabel", legacy);
Expand Down Expand Up @@ -128,6 +132,10 @@ export class UserSettings {
this.set("settings.territoryPatterns", !this.territoryPatterns());
}

toggleCoordinateGrid() {
this.set("settings.coordinateGridEnabled", !this.coordinateGridEnabled());
}

toggleDarkMode() {
this.set("settings.darkMode", !this.darkMode());
if (this.darkMode()) {
Expand Down
Loading