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-12 05:11:34 +02:00
committed by GitHub
parent 94f2293149
commit b1e9955af3
9 changed files with 93 additions and 29 deletions
+2
View File
@@ -972,6 +972,8 @@
"territory_sat_desc": "How vivid the territory fill colors are (lower mutes them)",
"territory_alpha_label": "Territory opacity",
"territory_alpha_desc": "How opaque the territory fill is (lower lets terrain show through)",
"coordinate_grid_opacity_label": "Coordinate grid opacity",
"coordinate_grid_opacity_desc": "How opaque the coordinate grid is (lower lets more things show through)",
"rail_distance_label": "Train track draw distance",
"rail_distance_desc": "How far zoomed out train tracks remain visible",
"rail_thickness_label": "Train track thickness",
@@ -52,6 +52,10 @@ const TERRITORY_ALPHA_MIN = 0;
const TERRITORY_ALPHA_MAX = 1;
const TERRITORY_ALPHA_STEP = 0.01;
const COORDINATE_GRID_OPACITY_MIN = 0;
const COORDINATE_GRID_OPACITY_MAX = 1;
const COORDINATE_GRID_OPACITY_STEP = 0.01;
// Train track "draw distance" is presented inverted: a higher slider value means
// tracks stay visible when more zoomed out, i.e. a lower railMinZoom.
const RAIL_ZOOM_MIN = 0;
@@ -252,6 +256,13 @@ export class GraphicsSettingsModal extends LitElement implements Controller {
);
}
private currentCoordinateGridOpacity(): number {
return (
this.userSettings.graphicsOverrides().mapOverlay?.coordinateGridOpacity ??
renderDefaults.mapOverlay.coordinateGridOpacity
);
}
private currentRailMinZoom(): number {
return (
this.userSettings.graphicsOverrides().railroad?.railMinZoom ??
@@ -291,6 +302,11 @@ export class GraphicsSettingsModal extends LitElement implements Controller {
this.patchMapOverlay({ territoryAlpha: value });
}
private onCoordinateGridOpacityChange(event: Event) {
const value = parseFloat((event.target as HTMLInputElement).value);
this.patchMapOverlay({ coordinateGridOpacity: value });
}
private onRailDrawDistanceChange(event: Event) {
const drawDistance = parseFloat((event.target as HTMLInputElement).value);
// Invert: higher draw distance => tracks visible when more zoomed out.
@@ -412,6 +428,7 @@ export class GraphicsSettingsModal extends LitElement implements Controller {
const highlightThicken = this.currentHighlightThicken();
const territorySat = this.currentTerritorySat();
const territoryAlpha = this.currentTerritoryAlpha();
const coordinateGridOpacity = this.currentCoordinateGridOpacity();
const railDrawDistance = RAIL_ZOOM_MAX - this.currentRailMinZoom();
const railThickness = this.currentRailThickness();
const colorblind = this.currentColorblind();
@@ -751,6 +768,35 @@ export class GraphicsSettingsModal extends LitElement implements Controller {
</div>
</div>
<div
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded-sm text-white transition-colors"
>
<div class="flex-1">
<div class="font-medium">
${translateText(
"graphics_setting.coordinate_grid_opacity_label",
)}
</div>
<div class="text-sm text-slate-400">
${translateText(
"graphics_setting.coordinate_grid_opacity_desc",
)}
</div>
<input
type="range"
min=${COORDINATE_GRID_OPACITY_MIN}
max=${COORDINATE_GRID_OPACITY_MAX}
step=${COORDINATE_GRID_OPACITY_STEP}
.value=${String(coordinateGridOpacity)}
@input=${this.onCoordinateGridOpacityChange}
class="w-full border border-slate-500 rounded-lg"
/>
</div>
<div class="text-sm text-slate-400 w-12 text-right">
${coordinateGridOpacity.toFixed(2)}
</div>
</div>
<div
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded-sm text-white transition-colors"
>
@@ -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;
}
}