mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 12:00:44 +00:00
4ee68b4ea7
## 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.  ## 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 <noreply@anthropic.com>
149 lines
6.5 KiB
TypeScript
149 lines
6.5 KiB
TypeScript
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
|
|
* scaling, classic/dark structure and name styling, and the colorblind-safe
|
|
* affiliation/tint palette.
|
|
*/
|
|
export function applyGraphicsOverrides(
|
|
settings: RenderSettings,
|
|
overrides: GraphicsOverrides,
|
|
): void {
|
|
if (overrides.name?.nameScaleFactor !== undefined) {
|
|
settings.name.nameScaleFactor = overrides.name.nameScaleFactor;
|
|
}
|
|
if (overrides.name?.cullThreshold !== undefined) {
|
|
settings.name.cullThreshold = overrides.name.cullThreshold;
|
|
}
|
|
if (overrides.name?.hoverFadeAlpha !== undefined) {
|
|
settings.name.hoverFadeAlpha = overrides.name.hoverFadeAlpha;
|
|
}
|
|
if (overrides.name?.hoverGlowWidth !== undefined) {
|
|
settings.name.hoverGlowWidth = overrides.name.hoverGlowWidth;
|
|
}
|
|
if (overrides.name?.hoverGlowAlpha !== undefined) {
|
|
settings.name.hoverGlowAlpha = overrides.name.hoverGlowAlpha;
|
|
}
|
|
if (overrides.structure?.iconSize !== undefined) {
|
|
settings.structure.iconSize = overrides.structure.iconSize;
|
|
}
|
|
if (overrides.structure?.classicIcons ?? true) {
|
|
// Classic look (default): lighter player-colored shape behind a darkened
|
|
// player-colored icon glyph (matching the old canvas renderer's
|
|
// structureColors().dark), with a touch of translucency.
|
|
settings.structure.borderDarken = 0.7;
|
|
settings.structure.fillDarken = 1.0;
|
|
settings.structure.iconDarken = 0.3;
|
|
settings.structure.iconAlpha = 0.9;
|
|
}
|
|
|
|
if (overrides.structure?.classicNumbers !== undefined) {
|
|
settings.structureLevel.classicFont = overrides.structure.classicNumbers;
|
|
}
|
|
if (overrides.mapOverlay?.highlightFillBrighten !== undefined) {
|
|
settings.mapOverlay.highlightFillBrighten =
|
|
overrides.mapOverlay.highlightFillBrighten;
|
|
}
|
|
if (overrides.mapOverlay?.highlightBrighten !== undefined) {
|
|
settings.mapOverlay.highlightBrighten =
|
|
overrides.mapOverlay.highlightBrighten;
|
|
}
|
|
if (overrides.mapOverlay?.highlightThicken !== undefined) {
|
|
settings.mapOverlay.highlightThicken =
|
|
overrides.mapOverlay.highlightThicken;
|
|
}
|
|
if (overrides.mapOverlay?.territorySaturation !== undefined) {
|
|
settings.mapOverlay.territorySaturation =
|
|
overrides.mapOverlay.territorySaturation;
|
|
}
|
|
if (overrides.mapOverlay?.territoryAlpha !== undefined) {
|
|
settings.mapOverlay.territoryAlpha = overrides.mapOverlay.territoryAlpha;
|
|
}
|
|
if (overrides.mapOverlay?.coordinateGridOpacity !== undefined) {
|
|
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;
|
|
}
|
|
if (overrides.railroad?.railThickness !== undefined) {
|
|
settings.railroad.railThickness = overrides.railroad.railThickness;
|
|
}
|
|
if (overrides.passEnabled?.fx !== undefined) {
|
|
settings.passEnabled.fx = overrides.passEnabled.fx;
|
|
}
|
|
if (overrides.passEnabled?.fallout !== undefined) {
|
|
// One user-facing toggle drives both fallout passes: the territory bloom
|
|
// and its additive light contribution in the day/night composite.
|
|
settings.passEnabled.falloutBloom = overrides.passEnabled.fallout;
|
|
settings.passEnabled.falloutLight = overrides.passEnabled.fallout;
|
|
}
|
|
if (overrides.terrain?.oceanColor !== undefined) {
|
|
settings.terrain.oceanColor = overrides.terrain.oceanColor;
|
|
}
|
|
if (overrides.lighting?.ambient !== undefined) {
|
|
settings.lighting.ambient = overrides.lighting.ambient;
|
|
// The composite only darkens the scene (and reveals the structure/unit
|
|
// glow) when ambient < 1; at ambient === 1 it's a visual identity, so
|
|
// don't pay the scene-capture cost of enabling the lighting pass.
|
|
settings.lighting.enabled = overrides.lighting.ambient < 1;
|
|
}
|
|
if (overrides.lighting?.falloffPower !== undefined) {
|
|
settings.lighting.falloffPower = overrides.lighting.falloffPower;
|
|
}
|
|
if (overrides.name?.darkNames !== undefined) {
|
|
const dark = overrides.name.darkNames;
|
|
// Dark: black fill + player-colored outline. Force outline RGB to black
|
|
// so the shader's defaultFill ramp (mix(uOutlineColor, black, fillT))
|
|
// collapses to pure black regardless of ambient.
|
|
// Colored: player-colored fill + white outline (defaults from JSON).
|
|
settings.name.fillUsePlayerColor = !dark;
|
|
settings.name.outlineUsePlayerColor = dark;
|
|
const channel = dark ? 0 : 1;
|
|
settings.name.outlineR = channel;
|
|
settings.name.outlineG = channel;
|
|
settings.name.outlineB = channel;
|
|
}
|
|
if (overrides.accessibility?.colorblind === true) {
|
|
// Swap the active theme slice for the colorblind palette (replaced
|
|
// wholesale — palette arrays differ in length between themes).
|
|
settings.theme = createThemeSettings("colorblind");
|
|
// Swap the red/green friend-foe encoding (the most common confusion axis)
|
|
// for a colorblind-safe blue/orange pairing (Okabe-Ito).
|
|
// Alt-view affiliation borders: self/ally in the blue family, enemy orange.
|
|
settings.affiliation.selfR = 0;
|
|
settings.affiliation.selfG = 0.447;
|
|
settings.affiliation.selfB = 0.698;
|
|
settings.affiliation.allyR = 0.337;
|
|
settings.affiliation.allyG = 0.706;
|
|
settings.affiliation.allyB = 0.914;
|
|
settings.affiliation.enemyR = 0.835;
|
|
settings.affiliation.enemyG = 0.369;
|
|
settings.affiliation.enemyB = 0;
|
|
// Normal-view relationship border tints: friendly blue, enemy orange,
|
|
// applied strongly so the cue doesn't rely on subtle hue.
|
|
settings.mapOverlay.friendlyTintR = 0;
|
|
settings.mapOverlay.friendlyTintG = 0.447;
|
|
settings.mapOverlay.friendlyTintB = 0.698;
|
|
settings.mapOverlay.embargoTintR = 0.835;
|
|
settings.mapOverlay.embargoTintG = 0.369;
|
|
settings.mapOverlay.embargoTintB = 0;
|
|
// Strong ratio so the friend/foe tint dominates the darkened territory
|
|
// border — neutral keeps its (darkened) fill hue, ally reads blue, enemy
|
|
// reads orange.
|
|
settings.mapOverlay.friendlyTintRatio = 0.85;
|
|
settings.mapOverlay.embargoTintRatio = 0.85;
|
|
}
|
|
}
|