From 74fc239f96f771312899ed149632cd34504821e0 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Thu, 11 Jun 2026 15:44:02 -0700 Subject: [PATCH] Dim untargetable nukes so players can tell SAMs can't hit them Nukes flying outside SAM-targetable range now render at reduced alpha (unit.untargetableAlpha, default 0.6), including the hydrogen bomb's glow halo. Adds a FLAG_FLICKER_UNTARGETABLE instance flag in UnitPass driven by the existing UnitState.targetable field. Also fixes the alt-view trade-friendly check to match its flag exactly, so retreating warships (flag 4) no longer render ally-yellow in alt view. --- src/client/render/gl/RenderSettings.ts | 1 + src/client/render/gl/passes/UnitPass.ts | 12 ++++++++++-- src/client/render/gl/render-settings.json | 3 ++- .../render/gl/shaders/unit/unit.frag.glsl | 18 +++++++++++++----- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/client/render/gl/RenderSettings.ts b/src/client/render/gl/RenderSettings.ts index ce89784a8..511eafe5d 100644 --- a/src/client/render/gl/RenderSettings.ts +++ b/src/client/render/gl/RenderSettings.ts @@ -223,6 +223,7 @@ export interface RenderSettings { hBombGlowB: number; hBombGlowStrength: number; // peak opacity of the glow hBombGlowInner: number; // radial falloff start (0..1, quad-space) + untargetableAlpha: number; // alpha for nukes SAMs can't target (0..1) }; name: { lerpSpeed: number; diff --git a/src/client/render/gl/passes/UnitPass.ts b/src/client/render/gl/passes/UnitPass.ts index bc96c1e9b..daabd7707 100644 --- a/src/client/render/gl/passes/UnitPass.ts +++ b/src/client/render/gl/passes/UnitPass.ts @@ -93,7 +93,7 @@ const HYDROGEN_BOMB_COL = UNIT_ORDER.indexOf(UT_HYDROGEN_BOMB); * Per-instance data (16 bytes): * float x, y, ownerID — 12 bytes (3 floats) * uint8 atlasIdx — 1 byte (atlas column 0–11) - * uint8 flags — 1 byte (0 = normal, 1 = flicker, 2 = angry, 3 = trade-friendly, 4 = retreating) + * uint8 flags — 1 byte (0 = normal, 1 = flicker, 2 = angry, 3 = trade-friendly, 4 = retreating, 5 = flicker-untargetable) * 2 bytes padding — aligns to 4-byte boundary */ const FLOATS_PER_INSTANCE = 4; @@ -105,6 +105,7 @@ const FLAG_FLICKER = 1; const FLAG_ANGRY = 2; const FLAG_TRADE_FRIENDLY = 3; const FLAG_RETREATING = 4; +const FLAG_FLICKER_UNTARGETABLE = 5; /** Atlas column indices for train sub-types (resolved from trainType + loaded) */ const TRAIN_ENGINE_COL = UNIT_ORDER.indexOf("TrainEngine"); @@ -183,6 +184,7 @@ export class UnitPass { private uHBombGlowColor: WebGLUniformLocation; private uHBombGlowStrength: WebGLUniformLocation; private uHBombGlowInner: WebGLUniformLocation; + private uUntargetableAlpha: WebGLUniformLocation; private affiliationTex: WebGLTexture | null = null; private altView = false; @@ -263,6 +265,10 @@ export class UnitPass { this.program, "uHBombGlowInner", )!; + this.uUntargetableAlpha = gl.getUniformLocation( + this.program, + "uUntargetableAlpha", + )!; // Texture unit bindings gl.useProgram(this.program); @@ -427,7 +433,8 @@ export class UnitPass { } else if (isAngryWarship) { flags = FLAG_ANGRY; } else if (isFlicker) { - flags = FLAG_FLICKER; + // Untargetable nukes render dimmed so players can tell SAMs can't hit them + flags = unit.targetable ? FLAG_FLICKER : FLAG_FLICKER_UNTARGETABLE; } const isMissile = MISSILE_TYPES.has(unit.unitType); @@ -508,6 +515,7 @@ export class UnitPass { ); gl.uniform1f(this.uHBombGlowStrength, us.hBombGlowStrength); gl.uniform1f(this.uHBombGlowInner, us.hBombGlowInner); + gl.uniform1f(this.uUntargetableAlpha, us.untargetableAlpha); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, this.paletteTex); diff --git a/src/client/render/gl/render-settings.json b/src/client/render/gl/render-settings.json index 616fb9864..f52a6d7e7 100644 --- a/src/client/render/gl/render-settings.json +++ b/src/client/render/gl/render-settings.json @@ -189,7 +189,8 @@ "hBombGlowG": 0.72, "hBombGlowB": 0.15, "hBombGlowStrength": 0.5, - "hBombGlowInner": 0.45 + "hBombGlowInner": 0.45, + "untargetableAlpha": 0.6 }, "name": { "lerpSpeed": 10, diff --git a/src/client/render/gl/shaders/unit/unit.frag.glsl b/src/client/render/gl/shaders/unit/unit.frag.glsl index 03d70ec3b..4d37cb5df 100644 --- a/src/client/render/gl/shaders/unit/unit.frag.glsl +++ b/src/client/render/gl/shaders/unit/unit.frag.glsl @@ -11,6 +11,7 @@ uniform int uAltView; uniform vec3 uHBombGlowColor; uniform float uHBombGlowStrength; uniform float uHBombGlowInner; +uniform float uUntargetableAlpha; in vec2 vQuadPos; in vec2 vCellUV; @@ -27,6 +28,7 @@ const float FLAG_FLICKER = 1.0; const float FLAG_ANGRY = 2.0; const float FLAG_TRADE_FRIENDLY = 3.0; const float FLAG_RETREATING = 4.0; +const float FLAG_FLICKER_UNTARGETABLE = 5.0; // nuke out of SAM range — dimmed // Ally color for trade-friendly override (yellow — matches affiliation.ts ALLY) const vec3 ALLY_COLOR = vec3(1.0, 1.0, 0.0); @@ -40,6 +42,11 @@ const vec3 FLICKER_COLORS[4] = vec3[4]( ); void main() { + // Untargetable nukes render translucent so players know SAMs can't hit them + float alphaMul = abs(vFlags - FLAG_FLICKER_UNTARGETABLE) < 0.1 + ? uUntargetableAlpha + : 1.0; + // The sprite lives in the central cell-space region [0,1]; for the enlarged // hydrogen-bomb quad, anything outside that range is glow-only margin. vec4 texel = vec4(0.0); @@ -57,7 +64,7 @@ void main() { float d = length(vQuadPos - 0.5) * 2.0; // 0 at center → ~1 at quad edge float g = (1.0 - smoothstep(uHBombGlowInner, 1.0, d)) * uHBombGlowStrength; if (g > 0.001) { - fragColor = vec4(uHBombGlowColor, g); + fragColor = vec4(uHBombGlowColor, g * alphaMul); return; } } @@ -69,10 +76,10 @@ void main() { // Alt-view: solid affiliation color, no gray-replacement bands if (uAltView != 0) { // Enemy trade ships heading to a self/allied port render as yellow (ally) - vec3 ac = vFlags > 2.5 + vec3 ac = abs(vFlags - FLAG_TRADE_FRIENDLY) < 0.1 ? ALLY_COLOR : texelFetch(uAffiliation, ivec2(int(vOwnerID), 1), 0).rgb; - fragColor = vec4(ac, texel.a); + fragColor = vec4(ac, texel.a * alphaMul); return; } @@ -93,7 +100,8 @@ void main() { } else if (abs(vFlags - FLAG_RETREATING) < 0.1) { // Retreating: slowly blink the center (100 band) black so the ship reads as fleeing retreatBlink = step(0.5, fract(uTick * 0.07)); - } else if (abs(vFlags - FLAG_FLICKER) < 0.1) { + } else if (abs(vFlags - FLAG_FLICKER) < 0.1 || + abs(vFlags - FLAG_FLICKER_UNTARGETABLE) < 0.1) { // Flicker: cycle through hot colors, offset by position hash float phase = fract(uTick * uFlickerSpeed + vHash); int idx = int(phase * 4.0) % 4; @@ -124,5 +132,5 @@ void main() { color = borderColor; } - fragColor = vec4(color, texel.a); + fragColor = vec4(color, texel.a * alphaMul); }