Files
OpenFrontIO/src/client/controllers/HoverHighlightController.ts
T
EvanandGitHub 7137347b7d 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
2026-06-11 09:25:13 -07:00

48 lines
1.7 KiB
TypeScript

/**
* HoverHighlightController — pushes the cursor's tile-owner to the WebGL
* view so the territory + border passes can highlight the hovered player.
*
* Replaces the hover path inside the renderer's MapInteraction class (which
* was bound to the WebGL canvas; that canvas has pointer-events: none in the
* current input architecture so its listeners never fired). All input flows
* through InputHandler → MouseMoveEvent on the EventBus, so we just listen.
*/
import { EventBus } from "../../core/EventBus";
import { GameView } from "../../core/game/GameView";
import { Controller } from "../Controller";
import { MouseMoveEvent } from "../InputHandler";
import { GameView as WebGLGameView } from "../render/gl";
import { OWNER_MASK } from "../render/gl/utils/TileCodec";
import { TransformHandler } from "../TransformHandler";
export class HoverHighlightController implements Controller {
private lastOwnerID = 0;
constructor(
private game: GameView,
private eventBus: EventBus,
private transformHandler: TransformHandler,
private view: WebGLGameView,
) {}
init() {
this.eventBus.on(MouseMoveEvent, (e) => this.onMouseMove(e));
}
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)) {
const ref = this.game.ref(cell.x, cell.y);
ownerID = this.game.tileState(ref) & OWNER_MASK;
}
if (ownerID === this.lastOwnerID) return;
this.lastOwnerID = ownerID;
this.view.setHighlightOwner(ownerID);
}
}