mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-07 19:36:00 +00:00
4cee61c7d1
Chip used a world-space scale and y-offset, so it shrank as the camera zoomed out and slid under the cursor. Now both the scale and the downward offset are divided by zoom each frame, mirroring the existing attack-troop-label pattern. Tunable via ghostCost in render-settings.
296 lines
9.4 KiB
TypeScript
296 lines
9.4 KiB
TypeScript
import { GraphicsOverrides } from "./GraphicsOverrides";
|
||
import defaults from "./render-settings.json";
|
||
|
||
export interface RenderSettings {
|
||
passEnabled: {
|
||
terrain: boolean;
|
||
mapOverlay: boolean;
|
||
territoryPatterns: boolean;
|
||
structure: boolean;
|
||
unit: boolean;
|
||
name: boolean;
|
||
falloutBloom: boolean;
|
||
railroad: boolean;
|
||
fx: boolean;
|
||
bar: boolean;
|
||
nameDebug: boolean;
|
||
};
|
||
falloutBloom: {
|
||
broilSpeedCold: number;
|
||
broilSpeedHot: number;
|
||
noiseFreq1: number;
|
||
noiseFreq2: number;
|
||
contrastLoCold: number;
|
||
contrastLoHot: number;
|
||
contrastHiCold: number;
|
||
contrastHiHot: number;
|
||
metaFreq: number;
|
||
intensityCold: number;
|
||
intensityHot: number;
|
||
metaInfluenceCold: number;
|
||
metaInfluenceHot: number;
|
||
opacityFadeEnd: number;
|
||
bloomR: number;
|
||
bloomG: number;
|
||
bloomB: number;
|
||
bloomCoverage: number;
|
||
heatDecayPerTick: number;
|
||
particleColorDarkR: number;
|
||
particleColorDarkG: number;
|
||
particleColorDarkB: number;
|
||
particleColorBrightR: number;
|
||
particleColorBrightG: number;
|
||
particleColorBrightB: number;
|
||
particleThresholdUnowned: number;
|
||
particleThresholdOwned: number;
|
||
particleFlickerSpeed: number;
|
||
particleStrength: number;
|
||
particleFreshScale: number;
|
||
};
|
||
dayNight: {
|
||
mode: "light" | "dark";
|
||
nightAmbient: number;
|
||
dayAmbient: number;
|
||
falloffPower: number;
|
||
falloutLightR: number;
|
||
falloutLightG: number;
|
||
falloutLightB: number;
|
||
falloutLightIntensity: number;
|
||
falloutLightThreshold: number;
|
||
emberLightR: number;
|
||
emberLightG: number;
|
||
emberLightB: number;
|
||
emberLightIntensity: number;
|
||
blurZoomDivisor: number;
|
||
lightRadiusMultiplier: number;
|
||
};
|
||
mapOverlay: {
|
||
trailAlpha: number;
|
||
defenseCheckerDarken: number;
|
||
staleNukeBase: number;
|
||
staleNukeVariation: number;
|
||
staleNukeAlpha: number;
|
||
staleNukeR: number;
|
||
staleNukeG: number;
|
||
staleNukeB: number;
|
||
highlightBrighten: number;
|
||
highlightFillBrighten: number;
|
||
highlightThicken: number;
|
||
defensePostRange: number;
|
||
embargoTintRatio: number;
|
||
friendlyTintRatio: number;
|
||
};
|
||
railroad: {
|
||
railMinZoom: number;
|
||
railFadeRange: number;
|
||
railDetailZoom: number;
|
||
railAlpha: number;
|
||
};
|
||
structure: {
|
||
iconSize: number;
|
||
dotsZoomThreshold: number;
|
||
/** Icon size multiplier when zoomed out past dotsZoomThreshold. */
|
||
dotScale: number;
|
||
iconScaleFactorZoomedOut: number;
|
||
/**
|
||
* Zoom level at which structures begin growing with the canvas.
|
||
* Below this zoom, structures stay at a fixed screen size (capped).
|
||
* Above this zoom, they grow proportionally to zoom — i.e. world-anchored,
|
||
* so they cover a fixed area of the map.
|
||
*/
|
||
iconGrowZoom: number;
|
||
shapes: Record<string, { scale: number; iconFill: number }>;
|
||
highlightOutlineWidth: number;
|
||
highlightDimAlpha: number;
|
||
};
|
||
structureLevel: {
|
||
scale: number;
|
||
outlineWidth: number;
|
||
};
|
||
bar: {
|
||
healthBarW: number;
|
||
healthBarH: number;
|
||
healthBarOffsetY: number;
|
||
progressBarW: number;
|
||
progressBarH: number;
|
||
progressBarOffsetY: number;
|
||
borderWidth: number;
|
||
threshold1: number;
|
||
threshold2: number;
|
||
threshold3: number;
|
||
colorRedR: number;
|
||
colorRedG: number;
|
||
colorRedB: number;
|
||
colorOrangeR: number;
|
||
colorOrangeG: number;
|
||
colorOrangeB: number;
|
||
colorYellowR: number;
|
||
colorYellowG: number;
|
||
colorYellowB: number;
|
||
colorGreenR: number;
|
||
colorGreenG: number;
|
||
colorGreenB: number;
|
||
};
|
||
unit: {
|
||
unitSize: number;
|
||
flickerSpeed: number;
|
||
angryR: number;
|
||
angryG: number;
|
||
angryB: number;
|
||
};
|
||
name: {
|
||
lerpSpeed: number;
|
||
cullThreshold: number;
|
||
nameScaleFactor: number;
|
||
nameScaleCap: number;
|
||
troopSizeMultiplier: number;
|
||
outlineWidth: number;
|
||
outlineR: number;
|
||
outlineG: number;
|
||
outlineB: number;
|
||
outlineUsePlayerColor: boolean;
|
||
fillUsePlayerColor: boolean;
|
||
emojiRowOffset: number;
|
||
statusRowOffset: number;
|
||
};
|
||
fx: {
|
||
shockwaveRingWidth: number;
|
||
nukeShockwaveDurationMs: number;
|
||
nukeShockwaveRadiusFactor: number;
|
||
samShockwaveDurationMs: number;
|
||
samShockwaveRadius: number;
|
||
debrisLifetimeMs: number;
|
||
debrisFadeIn: number; // 0–1 fraction of lifetime
|
||
debrisFadeOut: number; // 0–1 fraction of lifetime (start of fade)
|
||
conquestLifetimeMs: number;
|
||
conquestFadeIn: number;
|
||
conquestFadeOut: number;
|
||
};
|
||
nukeTrajectory: {
|
||
lineWidth: number; // px — main line stroke width
|
||
outlineWidth: number; // px — extra width for outline behind line
|
||
dashTargetable: number; // px — dash length in targetable zone
|
||
gapTargetable: number; // px — gap length in targetable zone
|
||
dashUntargetable: number; // px — dash length in untargetable zone
|
||
gapUntargetable: number; // px — gap length in untargetable zone
|
||
lineR: number; // normal line color
|
||
lineG: number;
|
||
lineB: number;
|
||
interceptR: number; // line color after SAM intercept
|
||
interceptG: number;
|
||
interceptB: number;
|
||
outlineR: number; // outline color (normal)
|
||
outlineG: number;
|
||
outlineB: number;
|
||
interceptOutlineR: number; // outline color (after intercept)
|
||
interceptOutlineG: number;
|
||
interceptOutlineB: number;
|
||
markerCircleRadius: number; // px — zone boundary circle size
|
||
markerXRadius: number; // px — SAM intercept X size
|
||
};
|
||
nukeTelegraph: {
|
||
strokeWidth: number; // world units — circle ring width
|
||
dashLen: number; // world units — outer ring dash length
|
||
gapLen: number; // world units — outer ring gap length
|
||
rotationSpeed: number; // outer ring rotation speed
|
||
baseAlpha: number; // base opacity (0–1)
|
||
pulseAmplitude: number; // alpha pulse ±
|
||
pulseSpeed: number; // pulse frequency (radians/sec)
|
||
fillAlphaOffset: number; // inner fill is baseAlpha minus this
|
||
colorR: number; // circle color
|
||
colorG: number;
|
||
colorB: number;
|
||
};
|
||
moveIndicator: {
|
||
startRadius: number; // screen px — initial distance from center
|
||
chevronSize: number; // screen px — wing span
|
||
lineWidth: number; // screen px — stroke width
|
||
duration: number; // ms — total animation lifetime
|
||
converge: number; // 0–1 — fraction of radius consumed during animation
|
||
};
|
||
samRadius: {
|
||
strokeWidth: number; // ring half-width in world units
|
||
dashLen: number; // dash length in world units
|
||
gapLen: number; // gap length in world units
|
||
rotationSpeed: number; // world units per second
|
||
alpha: number; // base opacity (0–1)
|
||
outlineWidth: number; // outline border width in world units
|
||
outlineSoftness: number; // smoothstep range (0 = hard, higher = softer)
|
||
};
|
||
bonusPopup: {
|
||
scale: number;
|
||
lifetimeMs: number;
|
||
riseSpeed: number;
|
||
yOffset: number;
|
||
outlineWidth: number;
|
||
colorR: number;
|
||
colorG: number;
|
||
colorB: number;
|
||
minScreenScale: number; // minimum world-scale when zoomed out (prevents vanishing)
|
||
cullZoom: number; // popups hidden below this zoom level
|
||
};
|
||
ghostCost: {
|
||
screenScale: number; // screen-relative em scale; divided by zoom each frame for fixed on-screen size
|
||
screenYOffset: number; // screen-relative downward offset from icon center; divided by zoom each frame for fixed on-screen gap
|
||
};
|
||
spawnOverlay: {
|
||
highlightRadius: number; // tile highlight radius (squared internally)
|
||
highlightAlpha: number; // tile highlight opacity (0–1)
|
||
selfMinRad: number; // self ring inner radius
|
||
selfMaxRad: number; // self ring outer radius
|
||
mateMinRad: number; // teammate ring inner radius
|
||
mateMaxRad: number; // teammate ring outer radius
|
||
animSpeed: number; // breathing animation speed
|
||
gradientInnerEdge: number; // static gradient inner ramp end (0–1)
|
||
gradientSolidEnd: number; // static gradient solid band end (0–1)
|
||
};
|
||
altView: {
|
||
gridFontSize: number;
|
||
recolorStructures: boolean;
|
||
};
|
||
tileDrip: {
|
||
/**
|
||
* Round-robin bucket count for staggering territory tile uploads across
|
||
* render frames. One bucket drains per frame at 60Hz. 12 ≈ 200ms max
|
||
* latency, which absorbs a 100ms tick delay without a visible freeze.
|
||
* Changing at runtime requires reload.
|
||
*/
|
||
bucketCount: number;
|
||
};
|
||
lightConfigs: Record<string, { radius: number; intensity: number }>;
|
||
}
|
||
|
||
/** Create a fresh settings object with defaults from render-settings.json. */
|
||
export function createRenderSettings(): RenderSettings {
|
||
return JSON.parse(JSON.stringify(defaults)) as RenderSettings;
|
||
}
|
||
|
||
/**
|
||
* Generate a fresh RenderSettings by layering user overrides on top of the
|
||
* render-settings.json defaults. Pure — does not mutate any input.
|
||
*/
|
||
export function generateRenderSettings(
|
||
overrides: GraphicsOverrides,
|
||
): RenderSettings {
|
||
const settings = createRenderSettings();
|
||
if (overrides.name?.nameScaleFactor !== undefined) {
|
||
settings.name.nameScaleFactor = overrides.name.nameScaleFactor;
|
||
}
|
||
if (overrides.name?.cullThreshold !== undefined) {
|
||
settings.name.cullThreshold = overrides.name.cullThreshold;
|
||
}
|
||
return settings;
|
||
}
|
||
|
||
/** Dump current settings to a downloadable JSON file. */
|
||
export function dumpSettings(settings: RenderSettings): void {
|
||
const json = JSON.stringify(settings, null, 2);
|
||
const blob = new Blob([json], { type: "application/json" });
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement("a");
|
||
a.href = url;
|
||
a.download = "render-settings.json";
|
||
a.click();
|
||
URL.revokeObjectURL(url);
|
||
}
|