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:
Evan
2026-06-11 09:25:13 -07:00
committed by GitHub
parent af2849a2d7
commit 7137347b7d
19 changed files with 210 additions and 9 deletions
@@ -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}