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.
This commit is contained in:
evanpelle
2026-06-11 15:44:02 -07:00
parent 03a5d691ee
commit 74fc239f96
4 changed files with 26 additions and 8 deletions
+1
View File
@@ -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;
+10 -2
View File
@@ -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 011)
* 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);
+2 -1
View File
@@ -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,
@@ -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);
}