mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 11:20:45 +00:00
Fade player names under the cursor, with a graphics setting to tune it (#4221)
## Description: Player name plates can block the view of what's underneath them (structures, units, terrain). This PR fades the entire name plate — name, troop count, flag, and emoji/status row — to 25% opacity while the cursor is over it, so you can see and click what's behind it. **How it works:** - `HoverHighlightController` pushes the cursor's world position into the renderer on mouse move. - `NamePass` hit-tests the cursor against each player's name plate bounds on the CPU (mirroring the lerp/sizing math in `name.vert.glsl`) and passes the matched player's ID to the text, icon, and status-icon programs, which apply the alpha multiplier in their shaders. **Graphics setting:** - New "Name opacity under cursor" slider in the Graphics Settings modal (Name Labels section), range 0–1, default 0.25. Setting it to 1 disables the fade entirely. - Wired through the existing `GraphicsOverrides` pipeline: changes apply live and are cleared by "Reset to defaults". - Tuning knob exposed as `name.hoverFadeAlpha` in `render-settings.json` and the debug GUI. ## Please complete the following: - [ ] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [ ] I have added relevant tests to the test directory ## Please put your Discord username so you can be contacted if a bug or regression is found: evan
This commit is contained in:
@@ -939,6 +939,8 @@
|
||||
"name_scale_label": "Name Scale",
|
||||
"name_cull_label": "Minimum name size",
|
||||
"name_cull_desc": "Hide names smaller than this size",
|
||||
"hover_fade_label": "Name opacity under cursor",
|
||||
"hover_fade_desc": "How visible names are while your cursor is over them (1 to disable fading)",
|
||||
"colored_names_label": "Name color",
|
||||
"colored_names_desc": "Show player names in their player color or in black",
|
||||
"colored": "Colored",
|
||||
|
||||
@@ -31,6 +31,9 @@ export class HoverHighlightController implements Controller {
|
||||
}
|
||||
|
||||
private onMouseMove(e: MouseMoveEvent): void {
|
||||
const world = this.transformHandler.screenToWorldCoordinatesFloat(e.x, e.y);
|
||||
this.view.setMouseWorldPos(world.x, world.y);
|
||||
|
||||
const cell = this.transformHandler.screenToWorldCoordinates(e.x, e.y);
|
||||
let ownerID = 0;
|
||||
if (this.game.isValidCoord(cell.x, cell.y)) {
|
||||
|
||||
@@ -20,6 +20,10 @@ const NAME_CULL_MIN = 0;
|
||||
const NAME_CULL_MAX = 0.05;
|
||||
const NAME_CULL_STEP = 0.001;
|
||||
|
||||
const HOVER_FADE_MIN = 0;
|
||||
const HOVER_FADE_MAX = 1;
|
||||
const HOVER_FADE_STEP = 0.05;
|
||||
|
||||
const HIGHLIGHT_FILL_MIN = 0;
|
||||
const HIGHLIGHT_FILL_MAX = 1;
|
||||
const HIGHLIGHT_FILL_STEP = 0.01;
|
||||
@@ -148,6 +152,13 @@ export class GraphicsSettingsModal extends LitElement implements Controller {
|
||||
);
|
||||
}
|
||||
|
||||
private currentHoverFade(): number {
|
||||
return (
|
||||
this.userSettings.graphicsOverrides().name?.hoverFadeAlpha ??
|
||||
renderDefaults.name.hoverFadeAlpha
|
||||
);
|
||||
}
|
||||
|
||||
private patchName(patch: Partial<GraphicsOverrides["name"]>) {
|
||||
const current = this.userSettings.graphicsOverrides();
|
||||
this.userSettings.setGraphicsOverrides({
|
||||
@@ -309,6 +320,11 @@ export class GraphicsSettingsModal extends LitElement implements Controller {
|
||||
this.patchName({ cullThreshold: value });
|
||||
}
|
||||
|
||||
private onHoverFadeChange(event: Event) {
|
||||
const value = parseFloat((event.target as HTMLInputElement).value);
|
||||
this.patchName({ hoverFadeAlpha: value });
|
||||
}
|
||||
|
||||
private currentDarkNames(): boolean {
|
||||
return (
|
||||
this.userSettings.graphicsOverrides().name?.darkNames ??
|
||||
@@ -330,6 +346,7 @@ export class GraphicsSettingsModal extends LitElement implements Controller {
|
||||
|
||||
const nameScale = this.currentNameScale();
|
||||
const nameCull = this.currentNameCull();
|
||||
const hoverFade = this.currentHoverFade();
|
||||
const namesColored = !this.currentDarkNames();
|
||||
const classicIcons = this.currentClassicIcons();
|
||||
const highlightFill = this.currentHighlightFill();
|
||||
@@ -425,6 +442,31 @@ export class GraphicsSettingsModal extends LitElement implements Controller {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded-sm text-white transition-colors"
|
||||
>
|
||||
<div class="flex-1">
|
||||
<div class="font-medium">
|
||||
${translateText("graphics_setting.hover_fade_label")}
|
||||
</div>
|
||||
<div class="text-sm text-slate-400">
|
||||
${translateText("graphics_setting.hover_fade_desc")}
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
min=${HOVER_FADE_MIN}
|
||||
max=${HOVER_FADE_MAX}
|
||||
step=${HOVER_FADE_STEP}
|
||||
.value=${String(hoverFade)}
|
||||
@input=${this.onHoverFadeChange}
|
||||
class="w-full border border-slate-500 rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
<div class="text-sm text-slate-400 w-12 text-right">
|
||||
${hoverFade.toFixed(2)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded-sm text-white transition-colors"
|
||||
@click=${this.onToggleNamesColored}
|
||||
|
||||
@@ -400,6 +400,9 @@ export class GameView {
|
||||
setHighlightOwner(ownerID: number): void {
|
||||
this.renderer?.setHighlightOwner(ownerID);
|
||||
}
|
||||
setMouseWorldPos(x: number, y: number): void {
|
||||
this.renderer?.setMouseWorldPos(x, y);
|
||||
}
|
||||
setHighlightStructureTypes(unitTypes: string[] | null): void {
|
||||
this.renderer?.setHighlightStructureTypes(unitTypes);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ export const GraphicsOverridesSchema = z
|
||||
nameScaleFactor: z.number(),
|
||||
cullThreshold: z.number(),
|
||||
darkNames: z.boolean(),
|
||||
hoverFadeAlpha: z.number(),
|
||||
})
|
||||
.partial(),
|
||||
structure: z
|
||||
|
||||
@@ -13,6 +13,9 @@ export function applyGraphicsOverrides(
|
||||
if (overrides.name?.cullThreshold !== undefined) {
|
||||
settings.name.cullThreshold = overrides.name.cullThreshold;
|
||||
}
|
||||
if (overrides.name?.hoverFadeAlpha !== undefined) {
|
||||
settings.name.hoverFadeAlpha = overrides.name.hoverFadeAlpha;
|
||||
}
|
||||
if (overrides.structure?.classicIcons === true) {
|
||||
// Classic look: lighter player-colored shape behind a dark icon glyph,
|
||||
// with a touch of translucency.
|
||||
|
||||
@@ -201,6 +201,8 @@ export interface RenderSettings {
|
||||
nameShadeBot: number;
|
||||
emojiRowOffset: number;
|
||||
statusRowOffset: number;
|
||||
/** Alpha multiplier applied to a name while the cursor is over it. */
|
||||
hoverFadeAlpha: number;
|
||||
};
|
||||
fx: {
|
||||
shockwaveRingWidth: number;
|
||||
|
||||
@@ -951,6 +951,9 @@ export class GPURenderer {
|
||||
this.territoryPass.setHighlightOwner(ownerID);
|
||||
this.namePass.setHighlightOwner(ownerID);
|
||||
}
|
||||
setMouseWorldPos(x: number, y: number): void {
|
||||
this.namePass.setMouseWorldPos(x, y);
|
||||
}
|
||||
setHighlightStructureTypes(unitTypes: string[] | null): void {
|
||||
this.structurePass.setHighlightTypes(unitTypes);
|
||||
this.structureLevelPass.setHighlightTypes(unitTypes);
|
||||
|
||||
@@ -325,6 +325,7 @@ export function buildTree(s: RenderSettings, d: RenderSettings): DebugNode[] {
|
||||
toggle(s.name, "fillUsePlayerColor", d.name, "Fill = Player Color"),
|
||||
slider(s.name, "emojiRowOffset", d.name, 0, 5, 0.1, "Emoji Row Offset"),
|
||||
slider(s.name, "statusRowOffset", d.name, 0, 5, 0.1, "Status Row Offset"),
|
||||
slider(s.name, "hoverFadeAlpha", d.name, 0, 1, 0.05, "Hover Fade Alpha"),
|
||||
]),
|
||||
|
||||
folder("FX", [
|
||||
|
||||
@@ -40,6 +40,8 @@ export class IconProgram {
|
||||
private uNameScaleFactor: WebGLUniformLocation;
|
||||
private uNameScaleCap: WebGLUniformLocation;
|
||||
private uEmojiRowOffset: WebGLUniformLocation;
|
||||
private uFadeOwnerID: WebGLUniformLocation;
|
||||
private uHoverFadeAlpha: WebGLUniformLocation;
|
||||
|
||||
constructor(
|
||||
gl: WebGL2RenderingContext,
|
||||
@@ -107,6 +109,11 @@ export class IconProgram {
|
||||
this.program,
|
||||
"uEmojiRowOffset",
|
||||
)!;
|
||||
this.uFadeOwnerID = gl.getUniformLocation(this.program, "uFadeOwnerID")!;
|
||||
this.uHoverFadeAlpha = gl.getUniformLocation(
|
||||
this.program,
|
||||
"uHoverFadeAlpha",
|
||||
)!;
|
||||
|
||||
this.loadEmojiAtlas();
|
||||
}
|
||||
@@ -142,6 +149,7 @@ export class IconProgram {
|
||||
cameraMatrix: Float32Array,
|
||||
settings: RenderSettings,
|
||||
vao: WebGLVertexArrayObject,
|
||||
fadeOwnerID: number,
|
||||
): void {
|
||||
if (!this.emojiReady) return;
|
||||
|
||||
@@ -156,6 +164,8 @@ export class IconProgram {
|
||||
gl.uniform1f(this.uNameScaleFactor, ns.nameScaleFactor);
|
||||
gl.uniform1f(this.uNameScaleCap, ns.nameScaleCap);
|
||||
gl.uniform1f(this.uEmojiRowOffset, ns.emojiRowOffset);
|
||||
gl.uniform1f(this.uFadeOwnerID, fadeOwnerID);
|
||||
gl.uniform1f(this.uHoverFadeAlpha, ns.hoverFadeAlpha);
|
||||
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, this.playerDataTex);
|
||||
|
||||
@@ -38,6 +38,8 @@ export class StatusIconProgram {
|
||||
private uNameScaleFactor: WebGLUniformLocation;
|
||||
private uNameScaleCap: WebGLUniformLocation;
|
||||
private uStatusRowOffset: WebGLUniformLocation;
|
||||
private uFadeOwnerID: WebGLUniformLocation;
|
||||
private uHoverFadeAlpha: WebGLUniformLocation;
|
||||
|
||||
constructor(
|
||||
gl: WebGL2RenderingContext,
|
||||
@@ -98,6 +100,11 @@ export class StatusIconProgram {
|
||||
this.program,
|
||||
"uStatusRowOffset",
|
||||
)!;
|
||||
this.uFadeOwnerID = gl.getUniformLocation(this.program, "uFadeOwnerID")!;
|
||||
this.uHoverFadeAlpha = gl.getUniformLocation(
|
||||
this.program,
|
||||
"uHoverFadeAlpha",
|
||||
)!;
|
||||
|
||||
this.loadAtlas();
|
||||
}
|
||||
@@ -129,6 +136,7 @@ export class StatusIconProgram {
|
||||
cameraMatrix: Float32Array,
|
||||
settings: RenderSettings,
|
||||
vao: WebGLVertexArrayObject,
|
||||
fadeOwnerID: number,
|
||||
): void {
|
||||
if (!this.atlasReady) return;
|
||||
|
||||
@@ -143,6 +151,8 @@ export class StatusIconProgram {
|
||||
gl.uniform1f(this.uNameScaleFactor, ns.nameScaleFactor);
|
||||
gl.uniform1f(this.uNameScaleCap, ns.nameScaleCap);
|
||||
gl.uniform1f(this.uStatusRowOffset, ns.statusRowOffset);
|
||||
gl.uniform1f(this.uFadeOwnerID, fadeOwnerID);
|
||||
gl.uniform1f(this.uHoverFadeAlpha, ns.hoverFadeAlpha);
|
||||
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, this.playerDataTex);
|
||||
|
||||
@@ -42,6 +42,8 @@ export class TextProgram {
|
||||
private uNameScaleCap: WebGLUniformLocation;
|
||||
private uTroopSizeMultiplier: WebGLUniformLocation;
|
||||
private uHighlightOwnerID: WebGLUniformLocation;
|
||||
private uFadeOwnerID: WebGLUniformLocation;
|
||||
private uHoverFadeAlpha: WebGLUniformLocation;
|
||||
private uOutlineWidth: WebGLUniformLocation;
|
||||
private uNightAmbient: WebGLUniformLocation;
|
||||
private uOutlineColor: WebGLUniformLocation;
|
||||
@@ -110,6 +112,11 @@ export class TextProgram {
|
||||
this.program,
|
||||
"uHighlightOwnerID",
|
||||
)!;
|
||||
this.uFadeOwnerID = gl.getUniformLocation(this.program, "uFadeOwnerID")!;
|
||||
this.uHoverFadeAlpha = gl.getUniformLocation(
|
||||
this.program,
|
||||
"uHoverFadeAlpha",
|
||||
)!;
|
||||
this.uOutlineWidth = gl.getUniformLocation(this.program, "uOutlineWidth")!;
|
||||
this.uNightAmbient = gl.getUniformLocation(this.program, "uNightAmbient")!;
|
||||
this.uOutlineColor = gl.getUniformLocation(this.program, "uOutlineColor")!;
|
||||
@@ -154,6 +161,7 @@ export class TextProgram {
|
||||
maxPlayers: number,
|
||||
ambient: number,
|
||||
highlightOwnerID: number,
|
||||
fadeOwnerID: number,
|
||||
): void {
|
||||
if (!this.atlasReady) return;
|
||||
|
||||
@@ -170,6 +178,8 @@ export class TextProgram {
|
||||
gl.uniform1f(this.uNameScaleCap, ns.nameScaleCap);
|
||||
gl.uniform1f(this.uTroopSizeMultiplier, ns.troopSizeMultiplier);
|
||||
gl.uniform1f(this.uHighlightOwnerID, highlightOwnerID);
|
||||
gl.uniform1f(this.uFadeOwnerID, fadeOwnerID);
|
||||
gl.uniform1f(this.uHoverFadeAlpha, ns.hoverFadeAlpha);
|
||||
gl.uniform1f(this.uOutlineWidth, ns.outlineWidth);
|
||||
gl.uniform1f(this.uNightAmbient, ambient);
|
||||
gl.uniform3f(this.uOutlineColor, ns.outlineR, ns.outlineG, ns.outlineB);
|
||||
|
||||
@@ -50,6 +50,9 @@ import { TextProgram } from "./TextProgram";
|
||||
import type { PlayerSlot } from "./Types";
|
||||
import { LINES_PER_PLAYER, MAX_CHARS } from "./Types";
|
||||
|
||||
// Flag quad aspect ratio — must match FLAG_CELL_W / FLAG_CELL_H in FlagAtlasArray.ts.
|
||||
const FLAG_ASPECT = 128 / 85;
|
||||
|
||||
export class NamePass {
|
||||
private gl: WebGL2RenderingContext;
|
||||
private settings: RenderSettings;
|
||||
@@ -72,6 +75,8 @@ export class NamePass {
|
||||
// Atlas + glyph data
|
||||
private glyph: GlyphTables;
|
||||
private kernTable: Int8Array;
|
||||
private fontSize: number;
|
||||
private fontBase: number;
|
||||
|
||||
// Player management
|
||||
private playerByID: Map<string, PlayerStatic>;
|
||||
@@ -102,6 +107,9 @@ export class NamePass {
|
||||
|
||||
// Hovered player's small ID (0 = no highlight, matches TerritoryPass).
|
||||
private highlightOwnerID = 0;
|
||||
// Cursor in world coords — fades names under it (far off-map = no fade).
|
||||
private mouseWorldX = -1e9;
|
||||
private mouseWorldY = -1e9;
|
||||
private playerStateByID = new Map<string, PlayerState>();
|
||||
|
||||
constructor(
|
||||
@@ -116,6 +124,8 @@ export class NamePass {
|
||||
|
||||
// Parse atlas + build CPU lookup tables
|
||||
const atlas = parseAtlasData();
|
||||
this.fontSize = atlas.fontSize;
|
||||
this.fontBase = atlas.base;
|
||||
this.glyph = buildGlyphTables(atlas.chars);
|
||||
this.kernTable = buildKernTable(atlas.kernings);
|
||||
this.emojiCharToIndex = buildEmojiLookup();
|
||||
@@ -548,6 +558,65 @@ export class NamePass {
|
||||
this.highlightOwnerID = ownerID;
|
||||
}
|
||||
|
||||
setMouseWorldPos(x: number, y: number): void {
|
||||
this.mouseWorldX = x;
|
||||
this.mouseWorldY = y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the player whose name plate (name + troops + flag + emoji/status row)
|
||||
* is under the cursor, so the whole plate can fade as a unit. Mirrors the
|
||||
* lerp + sizing math in name.vert.glsl. Returns the smallID, or 0 for none.
|
||||
*/
|
||||
private hitTestNamePlate(now: number): number {
|
||||
const mx = this.mouseWorldX;
|
||||
const my = this.mouseWorldY;
|
||||
const ns = this.settings.name;
|
||||
for (const slot of this.slots.values()) {
|
||||
if (!slot.alive) continue;
|
||||
const t = Math.min(
|
||||
1 - Math.exp(-ns.lerpSpeed * (now - slot.startTime)),
|
||||
1,
|
||||
);
|
||||
const wx = slot.srcX + (slot.tgtX - slot.srcX) * t;
|
||||
const wy = slot.srcY + (slot.tgtY - slot.srcY) * t;
|
||||
const ws = slot.srcScale + (slot.tgtScale - slot.srcScale) * t;
|
||||
const baseSize = Math.max(1, Math.floor(ws));
|
||||
const nameSize = Math.max(4, Math.floor(baseSize * ns.nameScaleFactor));
|
||||
const nameScale = Math.min(baseSize * 0.25, ns.nameScaleCap);
|
||||
const lineH = this.fontBase * ((nameSize * nameScale) / this.fontSize);
|
||||
|
||||
const halfW =
|
||||
slot.nameHalfWidth * ((nameSize * nameScale) / this.fontSize);
|
||||
let left = wx - halfW;
|
||||
const right = wx + halfW;
|
||||
if (slot.flagLayerIdx >= 0) left -= lineH * 1.2 * FLAG_ASPECT;
|
||||
|
||||
// Name line (flag is slightly taller); emoji/status rows sit above it.
|
||||
let top = wy - lineH * 0.6;
|
||||
const hasStatus =
|
||||
slot.crown ||
|
||||
slot.traitor ||
|
||||
slot.disconnected ||
|
||||
slot.alliance ||
|
||||
slot.allianceReq ||
|
||||
slot.target ||
|
||||
slot.embargo ||
|
||||
slot.nukeActive;
|
||||
if (slot.emojiAtlasIdx >= 0) {
|
||||
top = wy - lineH * ns.emojiRowOffset;
|
||||
} else if (hasStatus) {
|
||||
top = wy - lineH * ns.statusRowOffset;
|
||||
}
|
||||
const bottom = wy + lineH * (1.1 + 0.5 * ns.troopSizeMultiplier);
|
||||
|
||||
if (mx >= left && mx <= right && my >= top && my <= bottom) {
|
||||
return slot.static.smallID;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
draw(cameraMatrix: Float32Array, ambient: number): void {
|
||||
if (!this.textProgram.ready) return;
|
||||
if (this.slots.size === 0) return;
|
||||
@@ -599,6 +668,8 @@ export class NamePass {
|
||||
this.playerDataDirty = false;
|
||||
}
|
||||
|
||||
const fadeOwnerID = this.hitTestNamePlate(performance.now() / 1000);
|
||||
|
||||
this.textProgram.draw(
|
||||
cameraMatrix,
|
||||
this.settings,
|
||||
@@ -606,9 +677,15 @@ export class NamePass {
|
||||
this.maxPlayers,
|
||||
ambient,
|
||||
this.highlightOwnerID,
|
||||
fadeOwnerID,
|
||||
);
|
||||
this.statusIconProgram.draw(cameraMatrix, this.settings, this.vao);
|
||||
this.iconProgram.draw(cameraMatrix, this.settings, this.vao);
|
||||
this.statusIconProgram.draw(
|
||||
cameraMatrix,
|
||||
this.settings,
|
||||
this.vao,
|
||||
fadeOwnerID,
|
||||
);
|
||||
this.iconProgram.draw(cameraMatrix, this.settings, this.vao, fadeOwnerID);
|
||||
|
||||
if (this.settings.passEnabled.nameDebug) {
|
||||
this.debugProgram.draw(cameraMatrix, this.settings, this.vao);
|
||||
|
||||
@@ -206,7 +206,8 @@
|
||||
"nameShadeNation": 0.3,
|
||||
"nameShadeBot": 0.4,
|
||||
"emojiRowOffset": 1.4,
|
||||
"statusRowOffset": 1.4
|
||||
"statusRowOffset": 1.4,
|
||||
"hoverFadeAlpha": 0.25
|
||||
},
|
||||
"fx": {
|
||||
"shockwaveRingWidth": 0.04,
|
||||
|
||||
@@ -8,6 +8,7 @@ uniform sampler2D uEmojiAtlas;
|
||||
in vec2 vUV;
|
||||
flat in int vIconType; // 0 = flag, 1 = emoji, -1 = discard
|
||||
flat in int vFlagLayer;
|
||||
in float vHoverAlpha;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
@@ -22,5 +23,5 @@ void main() {
|
||||
}
|
||||
|
||||
if (texel.a < 0.01) discard;
|
||||
fragColor = texel;
|
||||
fragColor = vec4(texel.rgb, texel.a * vHoverAlpha);
|
||||
}
|
||||
|
||||
@@ -31,9 +31,13 @@ uniform float uEmojiAtlasH; // emoji atlas texture height
|
||||
// Row offset (multiples of uFontBase * nameWorldScale)
|
||||
uniform float uEmojiRowOffset;
|
||||
|
||||
uniform float uFadeOwnerID; // smallID of player whose name plate the cursor is over (0 = none)
|
||||
uniform float uHoverFadeAlpha; // alpha multiplier applied to that player's name plate
|
||||
|
||||
out vec2 vUV;
|
||||
flat out int vIconType; // 0 = flag, 1 = emoji, -1 = discard
|
||||
flat out int vFlagLayer; // valid when vIconType == 0
|
||||
out float vHoverAlpha;
|
||||
|
||||
void main() {
|
||||
// Decode instance ID → playerIdx + iconType (0=flag, 1=emoji)
|
||||
@@ -44,7 +48,7 @@ void main() {
|
||||
vec4 pd0 = texelFetch(uPlayerData, ivec2(0, playerIdx), 0); // srcX, srcY, srcScale, startTime
|
||||
vec4 pd1 = texelFetch(uPlayerData, ivec2(1, playerIdx), 0); // tgtX, tgtY, tgtScale, alive
|
||||
vec4 pd3 = texelFetch(uPlayerData, ivec2(3, playerIdx), 0); // nameLen, troopLen, isHuman, nameHalfWidth
|
||||
vec4 pd4 = texelFetch(uPlayerData, ivec2(4, playerIdx), 0); // flagLayer, emojiIdx, [free], [free]
|
||||
vec4 pd4 = texelFetch(uPlayerData, ivec2(4, playerIdx), 0); // flagLayer, emojiIdx, smallID, [free]
|
||||
|
||||
// Early out: dead player
|
||||
if (pd1.w <= 0.0) {
|
||||
@@ -52,6 +56,7 @@ void main() {
|
||||
vUV = vec2(0.0);
|
||||
vIconType = -1;
|
||||
vFlagLayer = 0;
|
||||
vHoverAlpha = 1.0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -62,6 +67,7 @@ void main() {
|
||||
vUV = vec2(0.0);
|
||||
vIconType = -1;
|
||||
vFlagLayer = 0;
|
||||
vHoverAlpha = 1.0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -86,6 +92,7 @@ void main() {
|
||||
vUV = vec2(0.0);
|
||||
vIconType = -1;
|
||||
vFlagLayer = 0;
|
||||
vHoverAlpha = 1.0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -138,6 +145,11 @@ void main() {
|
||||
vFlagLayer = 0;
|
||||
}
|
||||
|
||||
// Fade the icon along with the rest of the name plate when the cursor is
|
||||
// over any part of it. Hit test runs on the CPU (NamePass).
|
||||
vHoverAlpha = (uFadeOwnerID > 0.0 && pd4.z == uFadeOwnerID)
|
||||
? uHoverFadeAlpha : 1.0;
|
||||
|
||||
// Quad world position
|
||||
vec2 worldPos = iconOrigin + aPos * vec2(iconW, iconH);
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ uniform float uNameScaleFactor;
|
||||
uniform float uNameScaleCap;
|
||||
uniform float uTroopSizeMultiplier;
|
||||
uniform float uHighlightOwnerID;
|
||||
uniform float uFadeOwnerID; // smallID of player whose name plate the cursor is over (0 = none)
|
||||
uniform float uHoverFadeAlpha; // alpha multiplier applied to that player's name plate
|
||||
|
||||
out vec2 vUV;
|
||||
out vec4 vPlayerColor; // player territory color (rgb) + alpha
|
||||
@@ -155,8 +157,13 @@ void main() {
|
||||
vec3 clip = uCamera * vec3(worldPos, 1.0);
|
||||
gl_Position = vec4(clip.xy, 0.0, 1.0);
|
||||
|
||||
// 10. UV interpolation across quad
|
||||
// 10. Fade the whole name plate when the cursor is on top of any part of it
|
||||
// so units underneath stay visible. Hit test runs on the CPU (NamePass).
|
||||
float hoverAlpha = (uFadeOwnerID > 0.0 && smallID == uFadeOwnerID)
|
||||
? uHoverFadeAlpha : 1.0;
|
||||
|
||||
// 11. UV interpolation across quad
|
||||
vUV = vec2(mix(u0, u1, aPos.x), mix(v0, v1, aPos.y));
|
||||
vPlayerColor = pd2; // player territory color (rgb) + alpha
|
||||
vPlayerColor = vec4(pd2.rgb, pd2.a * hoverAlpha); // player territory color + alpha
|
||||
vNameShade = pd3.z; // name fill grayscale shade (0.0 = black)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ flat in float vAllianceFraction;
|
||||
flat in vec2 vFadedUV0;
|
||||
flat in vec2 vFadedUV1;
|
||||
flat in float vFlashAlpha;
|
||||
in float vHoverAlpha;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
@@ -33,7 +34,7 @@ void main() {
|
||||
}
|
||||
|
||||
// Traitor flash: modulate alpha for urgency pulse
|
||||
texel.a *= vFlashAlpha;
|
||||
texel.a *= vFlashAlpha * vHoverAlpha;
|
||||
|
||||
if (texel.a < 0.01) discard;
|
||||
fragColor = texel;
|
||||
|
||||
@@ -28,6 +28,9 @@ uniform float uStatusPad; // transparent padding in texels per side
|
||||
// Configurable layout
|
||||
uniform float uStatusRowOffset; // row Y offset (multiples of uFontBase * nameWorldScale)
|
||||
|
||||
uniform float uFadeOwnerID; // smallID of player whose name plate the cursor is over (0 = none)
|
||||
uniform float uHoverFadeAlpha; // alpha multiplier applied to that player's name plate
|
||||
|
||||
out vec2 vUV;
|
||||
out vec2 vLocalUV; // 0..1 within the icon cell
|
||||
flat out int vDiscard;
|
||||
@@ -35,6 +38,7 @@ flat out float vAllianceFraction; // 0 = no drain effect, >0 = active drain
|
||||
flat out vec2 vFadedUV0; // top-left UV of faded alliance cell
|
||||
flat out vec2 vFadedUV1; // bottom-right UV of faded alliance cell
|
||||
flat out float vFlashAlpha; // traitor flash opacity (1.0 = fully visible)
|
||||
out float vHoverAlpha;
|
||||
|
||||
// Status flag float array — indexed by icon slot.
|
||||
// Slot mapping: 0=crown, 1=traitor, 2=disconnected, 3=alliance,
|
||||
@@ -85,7 +89,7 @@ void main() {
|
||||
// Read player data
|
||||
vec4 pd0 = texelFetch(uPlayerData, ivec2(0, playerIdx), 0); // srcX, srcY, srcScale, startTime
|
||||
vec4 pd1 = texelFetch(uPlayerData, ivec2(1, playerIdx), 0); // tgtX, tgtY, tgtScale, alive
|
||||
vec4 pd4 = texelFetch(uPlayerData, ivec2(4, playerIdx), 0); // flagIdx, emojiIdx, [free], [free]
|
||||
vec4 pd4 = texelFetch(uPlayerData, ivec2(4, playerIdx), 0); // flagIdx, emojiIdx, smallID, [free]
|
||||
vec4 pd7 = texelFetch(uPlayerData, ivec2(7, playerIdx), 0); // nukeTargetsMe, traitorRemainingTicks, allianceFraction, [free]
|
||||
|
||||
// Early out: dead player OR emoji is active
|
||||
@@ -98,6 +102,7 @@ void main() {
|
||||
vFadedUV0 = vec2(0.0);
|
||||
vFadedUV1 = vec2(0.0);
|
||||
vFlashAlpha = 1.0;
|
||||
vHoverAlpha = 1.0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -114,6 +119,7 @@ void main() {
|
||||
vFadedUV0 = vec2(0.0);
|
||||
vFadedUV1 = vec2(0.0);
|
||||
vFlashAlpha = 1.0;
|
||||
vHoverAlpha = 1.0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -142,6 +148,7 @@ void main() {
|
||||
vFadedUV0 = vec2(0.0);
|
||||
vFadedUV1 = vec2(0.0);
|
||||
vFlashAlpha = 1.0;
|
||||
vHoverAlpha = 1.0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -172,6 +179,11 @@ void main() {
|
||||
atlasIdx = (pd7.x > 0.5) ? 7 : 8;
|
||||
}
|
||||
|
||||
// Fade the status row along with the rest of the name plate when the cursor
|
||||
// is over any part of it. Hit test runs on the CPU (NamePass).
|
||||
vHoverAlpha = (uFadeOwnerID > 0.0 && pd4.z == uFadeOwnerID)
|
||||
? uHoverFadeAlpha : 1.0;
|
||||
|
||||
// Quad world position
|
||||
vec2 iconOrigin = vec2(iconX, iconY);
|
||||
vec2 worldPos = iconOrigin + aPos * vec2(iconWorldSize, iconWorldSize);
|
||||
|
||||
Reference in New Issue
Block a user