mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-24 04:18:00 +00:00
1db02acdc2
**Add approved & assigned issue number here:** N/A — maintainer refactor. ## Description: Replaces the theme class hierarchy (`BaseTheme`/`PastelTheme`/`ColorblindTheme`) with theme JSON files — `default-theme.json` and `colorblind-theme.json` — combined with `render-settings.json` at runtime into a single graphics-configuration pipeline (`settings.theme`). One `SettingsTheme` class keeps the algorithms (color allocation, team-variation generation, LAB-contrast structure colors) and reads all data from `ThemeSettings`; adding a theme is now just adding a JSON file. Colorblind mode (#4150) is fully preserved: - Same palettes — the 32-color CVD-safe pool and Okabe-Ito team colors are baked into `colorblind-theme.json` - The relative border rule (`l × 0.6`) is expressed as a `borderLightnessScale` knob alongside the default theme's absolute `borderDarken` - The mid-game re-theme wiring (`refreshPlayerColors`/`refreshPalette`) and the affiliation/friend-foe tint overrides are unchanged; `applyGraphicsOverrides` now also swaps the `settings.theme` slice - `deepAssign` replaces arrays wholesale so differing palette lengths survive theme switches Verified against the previous implementation with an equivalence test (since removed): default-theme colors are byte-identical including allocation order; colorblind team/derived colors are byte-identical, and FFA assignment may permute within the same palette (hex baking rounds upstream's fractional-RGB colord objects, which can flip the allocator's greedy delta-E ordering — rendered colors round identically either way). Also removes dead theme surface (`terrainColor`, `backgroundColor`, `falloutColor`, `font`, `textColor`, spawn-highlight variants, `PastelThemeDark`) — GL terrain colors and dark mode were already handled in the renderer. Note this means the colorblind terrain bands from #4150 were dead code (nothing calls `terrainColor`; GL terrain comes from `ColorUtils.encodeTerrainTile`); wiring CVD-safe terrain into the terrain texture would be a follow-up. ## Please complete the following: - [x] I have added screenshots for all UI updates — N/A, no UI changes (verified color-identical) - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file — N/A, no user-visible text - [x] I have added relevant tests to the test directory — `tests/Colors.test.ts` updated for the new pipeline (team colors from theme JSON, colorblind palette/border tests) ## Please put your Discord username so you can be contacted if a bug or regression is found: evanpelle 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
58 lines
1.5 KiB
TypeScript
58 lines
1.5 KiB
TypeScript
/**
|
|
* Utilities for RenderSettings persistence — deep-assign, deep-diff.
|
|
*/
|
|
|
|
type Obj = Record<string, any>;
|
|
|
|
/**
|
|
* Recursively assign source values onto target, preserving target's structure.
|
|
* Arrays are replaced wholesale (theme palettes differ in length between
|
|
* themes, so per-index merging would leave stale entries behind).
|
|
*/
|
|
export function deepAssign(target: Obj, source: Obj): void {
|
|
for (const key of Object.keys(source)) {
|
|
if (Array.isArray(source[key])) {
|
|
if (key in target) {
|
|
target[key] = structuredClone(source[key]);
|
|
}
|
|
} else if (
|
|
typeof source[key] === "object" &&
|
|
source[key] !== null &&
|
|
typeof target[key] === "object" &&
|
|
target[key] !== null
|
|
) {
|
|
deepAssign(target[key] as Obj, source[key] as Obj);
|
|
} else if (key in target) {
|
|
target[key] = source[key];
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Compute a sparse deep-partial of values that differ from defaults.
|
|
* Returns `undefined` if nothing differs.
|
|
*/
|
|
export function deepDiff(defaults: Obj, current: Obj): Obj | undefined {
|
|
let result: Obj | undefined;
|
|
for (const key of Object.keys(defaults)) {
|
|
const dv = defaults[key];
|
|
const cv = current[key];
|
|
if (
|
|
typeof dv === "object" &&
|
|
dv !== null &&
|
|
typeof cv === "object" &&
|
|
cv !== null
|
|
) {
|
|
const sub = deepDiff(dv as Obj, cv as Obj);
|
|
if (sub !== undefined) {
|
|
result ??= {};
|
|
result[key] = sub;
|
|
}
|
|
} else if (dv !== cv) {
|
|
result ??= {};
|
|
result[key] = cv;
|
|
}
|
|
}
|
|
return result;
|
|
}
|