From 89297bbe9ec29c6cc4e801ea38512b6e8e0ae94f Mon Sep 17 00:00:00 2001 From: Evan Date: Fri, 19 Jun 2026 20:28:11 -0700 Subject: [PATCH] Add nuke fallout color graphics option (#4355) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What Adds a **Nuke fallout color** option to the in-game graphics settings modal (Effects section), letting players recolor the fallout tint left on territory after a nuke. ![modal](https://github.com/user-attachments/assets/placeholder) ## How Mirrors the existing **Ocean color** override pattern: - `GraphicsOverrides.ts` — adds `staleNukeColor` (hex string) to the `mapOverlay` override schema. - `RenderOverrides.ts` — `applyGraphicsOverrides` parses the hex and writes the renderer's `staleNukeR/G/B` 0–1 float channels (`hexToRgb` yields 0–255, so it divides by 255). - `GraphicsSettingsModal.ts` — new hex-text + native color-picker row, default computed from `render-settings.json`. - `en.json` — `nuke_color_label` / `nuke_color_desc`. The value persists via `UserSettings.graphicsOverrides()` and is cleared by the modal's existing "Reset to defaults". The render debug GUI already exposes the same setting as **Stale Nuke Color** (Map Overlay), so no change was needed there. ## Testing - `tsc --noEmit` clean. - Verified in a headless solo game: the row renders with the green default (`#0d8c12`), changing it persists `mapOverlay.staleNukeColor`, and `applyGraphicsOverrides("#ff0000")` produces `staleNukeR=1, G=0, B=0`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 --- resources/lang/en.json | 2 + .../hud/layers/GraphicsSettingsModal.ts | 58 +++++++++++++++++++ src/client/render/gl/GraphicsOverrides.ts | 3 + src/client/render/gl/RenderOverrides.ts | 10 ++++ 4 files changed, 73 insertions(+) diff --git a/resources/lang/en.json b/resources/lang/en.json index 353f6f420..9d152ece4 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -553,6 +553,8 @@ "name_cull_desc": "Hide names smaller than this size", "name_cull_label": "Minimum name size", "name_scale_label": "Name Scale", + "nuke_color_desc": "Color of the fallout tint left on territory after a nuke.", + "nuke_color_label": "Nuke fallout color", "ocean_color_desc": "Base color of ocean.", "ocean_color_label": "Ocean color", "rail_distance_desc": "How far zoomed out train tracks remain visible", diff --git a/src/client/hud/layers/GraphicsSettingsModal.ts b/src/client/hud/layers/GraphicsSettingsModal.ts index d0b65abac..bf423453d 100644 --- a/src/client/hud/layers/GraphicsSettingsModal.ts +++ b/src/client/hud/layers/GraphicsSettingsModal.ts @@ -115,6 +115,22 @@ function falloffToUnitGlowSlider(falloff: number): number { const HEX_COLOR_RE = /^#?([0-9a-fA-F]{6})$/; +// The stale-nuke (fallout ground tint) color is stored in render-settings.json +// as three 0-1 floats; the color picker wants a "#rrggbb" hex string. +function rgbFloatsToHex(r: number, g: number, b: number): string { + const ch = (v: number) => + Math.round(v * 255) + .toString(16) + .padStart(2, "0"); + return `#${ch(r)}${ch(g)}${ch(b)}`; +} + +const NUKE_COLOR_DEFAULT = rgbFloatsToHex( + renderDefaults.mapOverlay.staleNukeR, + renderDefaults.mapOverlay.staleNukeG, + renderDefaults.mapOverlay.staleNukeB, +); + export class ShowGraphicsSettingsModalEvent { constructor( public readonly isVisible: boolean = true, @@ -356,6 +372,20 @@ export class GraphicsSettingsModal extends LitElement implements Controller { this.patchMapOverlay({ coordinateGridOpacity: value }); } + private currentNukeColor(): string { + return ( + this.userSettings.graphicsOverrides().mapOverlay?.staleNukeColor ?? + NUKE_COLOR_DEFAULT + ); + } + + private onNukeColorChange(event: Event) { + const value = (event.target as HTMLInputElement).value.trim(); + const match = HEX_COLOR_RE.exec(value); + if (!match) return; // ignore partial/invalid hex while typing + this.patchMapOverlay({ staleNukeColor: `#${match[1].toLowerCase()}` }); + } + private onRailDrawDistanceChange(event: Event) { const drawDistance = parseFloat((event.target as HTMLInputElement).value); // Invert: higher draw distance => tracks visible when more zoomed out. @@ -572,6 +602,7 @@ export class GraphicsSettingsModal extends LitElement implements Controller { const railDrawDistance = RAIL_ZOOM_MAX - this.currentRailMinZoom(); const railThickness = this.currentRailThickness(); const oceanColor = this.currentOceanColor(); + const nukeColor = this.currentNukeColor(); const ambientLevel = this.currentAmbientLevel(); const unitGlow = this.currentUnitGlow(); const colorblind = this.currentColorblind(); @@ -1123,6 +1154,33 @@ export class GraphicsSettingsModal extends LitElement implements Controller { /> +
+
+
+ ${translateText("graphics_setting.nuke_color_label")} +
+
+ ${translateText("graphics_setting.nuke_color_desc")} +
+
+ + +
+
diff --git a/src/client/render/gl/GraphicsOverrides.ts b/src/client/render/gl/GraphicsOverrides.ts index 021450c6a..b697e2d24 100644 --- a/src/client/render/gl/GraphicsOverrides.ts +++ b/src/client/render/gl/GraphicsOverrides.ts @@ -27,6 +27,9 @@ export const GraphicsOverridesSchema = z territorySaturation: z.number(), territoryAlpha: z.number(), coordinateGridOpacity: z.number(), + // "#rrggbb" hex string; overrides the lingering fallout ground tint + // left on territory after a nuke. + staleNukeColor: z.string(), }) .partial(), railroad: z diff --git a/src/client/render/gl/RenderOverrides.ts b/src/client/render/gl/RenderOverrides.ts index 5d53883e5..001c9ca7d 100644 --- a/src/client/render/gl/RenderOverrides.ts +++ b/src/client/render/gl/RenderOverrides.ts @@ -1,5 +1,6 @@ import type { GraphicsOverrides } from "./GraphicsOverrides"; import { createThemeSettings, type RenderSettings } from "./RenderSettings"; +import { hexToRgb } from "./utils/ColorUtils"; /** * Apply the user's graphics overrides onto a RenderSettings in place: name @@ -64,6 +65,15 @@ export function applyGraphicsOverrides( settings.mapOverlay.coordinateGridOpacity = overrides.mapOverlay.coordinateGridOpacity; } + if (overrides.mapOverlay?.staleNukeColor !== undefined) { + // hexToRgb yields 0-255 channels; the stale-nuke uniforms are 0-1 floats. + const rgb = hexToRgb(overrides.mapOverlay.staleNukeColor); + if (rgb !== null) { + settings.mapOverlay.staleNukeR = rgb[0] / 255; + settings.mapOverlay.staleNukeG = rgb[1] / 255; + settings.mapOverlay.staleNukeB = rgb[2] / 255; + } + } if (overrides.railroad?.railMinZoom !== undefined) { settings.railroad.railMinZoom = overrides.railroad.railMinZoom; }