Coordinate grid (#4224)

**Add approved & assigned issue number here:**

Resolves #3839

## Description:
A bunch of small updates to make the coordinate grid a lot nicer.
 - Removes black backgrounds on text.
 - Allows user to modify the opacity of the coordinate grid
 - Persist the enable state of the coordinate grid

### Before
<img width="2344" height="1168" alt="image"
src="https://github.com/user-attachments/assets/22c2fb77-9db6-41bf-a50a-987f651cc19a"
/>

### After
<img width="2331" height="1174" alt="image"
src="https://github.com/user-attachments/assets/0e5a9575-8a79-407b-8d78-8564df02b259"
/>

<img width="407" height="947" alt="image"
src="https://github.com/user-attachments/assets/b9e5f9f1-3cc1-4832-b7d4-38e1f5e93d57"
/>

## Please complete the following:

- [X] I have added screenshots for all UI updates
- [X] I process any text displayed to the user through translateText()
and I've added it to the en.json file

## Please put your Discord username so you can be contacted if a bug or
regression is found:

FrederikJA
This commit is contained in:
FrederikJA
2026-06-11 20:11:34 -07:00
committed by GitHub
parent 94f2293149
commit b1e9955af3
9 changed files with 93 additions and 29 deletions
@@ -24,6 +24,7 @@ export const GraphicsOverridesSchema = z
highlightThicken: z.number(),
territorySaturation: z.number(),
territoryAlpha: z.number(),
coordinateGridOpacity: z.number(),
})
.partial(),
railroad: z
+4
View File
@@ -56,6 +56,10 @@ export function applyGraphicsOverrides(
if (overrides.mapOverlay?.territoryAlpha !== undefined) {
settings.mapOverlay.territoryAlpha = overrides.mapOverlay.territoryAlpha;
}
if (overrides.mapOverlay?.coordinateGridOpacity !== undefined) {
settings.mapOverlay.coordinateGridOpacity =
overrides.mapOverlay.coordinateGridOpacity;
}
if (overrides.railroad?.railMinZoom !== undefined) {
settings.railroad.railMinZoom = overrides.railroad.railMinZoom;
}
+1
View File
@@ -113,6 +113,7 @@ export interface RenderSettings {
territorySaturation: number;
/** Absolute opacity of the territory fill. 1 = fully opaque (terrain hidden), ~0.588 = default. */
territoryAlpha: number;
coordinateGridOpacity: number;
staleNukeBase: number;
staleNukeVariation: number;
staleNukeAlpha: number;
+12
View File
@@ -91,6 +91,8 @@ const SAM_RADIUS_HIGHLIGHT_TYPES = new Set([
"Hydrogen Bomb",
]);
const GRID_VIEW_KEY = "renderer:grid_view_enabled";
export class GPURenderer {
private gl: WebGL2RenderingContext;
private camera: Camera;
@@ -526,6 +528,11 @@ export class GPURenderer {
mapH,
this.settings,
);
try {
this.gridView = window.localStorage.getItem(GRID_VIEW_KEY) === "true";
} catch {
this.setGridView(false);
}
for (const p of header.players) {
if (p.team !== null) this.playerTeams.set(p.smallID, p.team);
@@ -1078,6 +1085,11 @@ export class GPURenderer {
setGridView(active: boolean): void {
this.gridView = active;
try {
window.localStorage.setItem(GRID_VIEW_KEY, active ? "true" : "false");
} catch {
// Ignore if we are unable to use localstorage.
}
}
getSettings(): RenderSettings {
@@ -31,6 +31,7 @@ export class CoordinateGridPass {
private uCellSize: WebGLUniformLocation;
private uZoom: WebGLUniformLocation;
private uFontSize: WebGLUniformLocation;
private uOpacity: WebGLUniformLocation;
private mapW: number;
private mapH: number;
@@ -57,6 +58,7 @@ export class CoordinateGridPass {
this.uCellSize = gl.getUniformLocation(this.program, "uCellSize")!;
this.uZoom = gl.getUniformLocation(this.program, "uZoom")!;
this.uFontSize = gl.getUniformLocation(this.program, "uFontSize")!;
this.uOpacity = gl.getUniformLocation(this.program, "uOpacity")!;
gl.useProgram(this.program);
gl.uniform1i(gl.getUniformLocation(this.program, "uGlyphTex"), 0);
@@ -73,6 +75,7 @@ export class CoordinateGridPass {
gl.uniform1f(this.uCellSize, this.cellSize);
gl.uniform1f(this.uZoom, zoom);
gl.uniform1f(this.uFontSize, this.settings.altView.gridFontSize);
gl.uniform1f(this.uOpacity, this.settings.mapOverlay.coordinateGridOpacity);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.glyphTex);
@@ -95,14 +98,16 @@ export class CoordinateGridPass {
canvas.height = GLYPH_H;
const ctx = canvas.getContext("2d")!;
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = "black";
ctx.lineWidth = 4;
ctx.fillStyle = "white";
ctx.font = `bold ${GLYPH_H - 8}px monospace`;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
for (let i = 0; i < CHARS.length; i++) {
ctx.strokeText(CHARS[i], i * GLYPH_W + GLYPH_W / 2, GLYPH_H / 2);
ctx.fillText(CHARS[i], i * GLYPH_W + GLYPH_W / 2, GLYPH_H / 2);
}
@@ -69,6 +69,7 @@
"territoryDefenseDarken": 0.85,
"territorySaturation": 1,
"territoryAlpha": 0.588,
"coordinateGridOpacity": 0.5,
"staleNukeBase": 0,
"staleNukeVariation": 0.05,
"staleNukeAlpha": 1,
@@ -6,6 +6,7 @@ uniform float uCellSize;
uniform float uZoom;
uniform float uFontSize;
uniform sampler2D uGlyphTex;
uniform float uOpacity;
in vec2 vWorldPos;
out vec4 fragColor;
@@ -29,7 +30,7 @@ void main() {
// --- Grid lines (at cell boundaries) ---
if (localX < lineW || localY < lineW) {
fragColor = vec4(1.0, 1.0, 1.0, 0.35);
fragColor = vec4(1.0, 1.0, 1.0, uOpacity);
return;
}
@@ -41,7 +42,6 @@ void main() {
float gw = fontSize * 0.6 * px; // glyph width in world units
float gh = fontSize * px; // glyph height
float pad = 8.0 * px; // padding from cell corner
float bgPad = 2.0 * px; // background extends beyond text
float lx = localX - pad;
float ly = localY - pad;
@@ -73,32 +73,24 @@ void main() {
float totalW = float(nc) * gw;
// Check label background area (text + padding)
if (lx < -bgPad || ly < -bgPad || lx >= totalW + bgPad || ly >= gh + bgPad)
discard;
// Check if on actual glyph
if (lx >= 0.0 && ly >= 0.0 && lx < totalW && ly < gh) {
int ci = int(floor(lx / gw));
if (ci < nc) {
int g;
if (ci == 0) g = c0;
else if (ci == 1) g = c1;
else if (ci == 2) g = c2;
else g = c3;
float cu = fract(lx / gw);
float cv = ly / gh;
float au = (float(g) + cu) / GLYPH_COUNT;
float mask = texture(uGlyphTex, vec2(au, cv)).r;
if (mask > 0.3) {
fragColor = vec4(1.0, 1.0, 1.0, 0.9);
return;
}
}
if (lx < 0.0 || ly < 0.0 || lx >= totalW || ly >= gh) {
discard;
}
// Background behind label
fragColor = vec4(0.08, 0.08, 0.08, 0.7);
int ci = int(floor(lx / gw));
if (ci < nc) {
int g;
if (ci == 0) g = c0;
else if (ci == 1) g = c1;
else if (ci == 2) g = c2;
else g = c3;
float cu = fract(lx / gw);
float cv = ly / gh;
float au = (float(g) + cu) / GLYPH_COUNT;
vec4 gColor = texture(uGlyphTex, vec2(au, cv));
fragColor = gColor.a * vec4(gColor.rgb, uOpacity);
return;
}
}