Reused the quick‑info hover logic so territory highlighting now follows boats/ships too.

- Added shared hover helper in HoverInfo.ts (same rules as PlayerInfoOverlay, including nearby ships on water).
- PlayerInfoOverlay.ts now uses getHoverInfo(...) instead of its local hover logic.
- TerritoryLayer.ts now uses getHoverInfo(...) and highlights the unit owner when hovering ships/boats.
This commit is contained in:
scamiv
2026-01-09 22:07:51 +01:00
parent 48c369d22f
commit 0dfb903078
3 changed files with 91 additions and 65 deletions
+73
View File
@@ -0,0 +1,73 @@
import { UnitType } from "../../core/game/Game";
import { TileRef } from "../../core/game/GameMap";
import { GameView, PlayerView, UnitView } from "../../core/game/GameView";
export type HoverInfo = {
player: PlayerView | null;
unit: UnitView | null;
isWilderness: boolean;
isIrradiatedWilderness: boolean;
};
function euclideanDistWorld(
coord: { x: number; y: number },
tileRef: TileRef,
game: GameView,
): number {
const x = game.x(tileRef);
const y = game.y(tileRef);
const dx = coord.x - x;
const dy = coord.y - y;
return Math.sqrt(dx * dx + dy * dy);
}
function distSortUnitWorld(coord: { x: number; y: number }, game: GameView) {
return (a: UnitView, b: UnitView) => {
const distA = euclideanDistWorld(coord, a.tile(), game);
const distB = euclideanDistWorld(coord, b.tile(), game);
return distA - distB;
};
}
export function getHoverInfo(
game: GameView,
worldCoord: { x: number; y: number },
): HoverInfo {
const info: HoverInfo = {
player: null,
unit: null,
isWilderness: false,
isIrradiatedWilderness: false,
};
if (!game.isValidCoord(worldCoord.x, worldCoord.y)) {
return info;
}
const tile = game.ref(worldCoord.x, worldCoord.y);
const owner = game.owner(tile);
if (owner && owner.isPlayer()) {
info.player = owner as PlayerView;
return info;
}
if (owner && !owner.isPlayer() && game.isLand(tile)) {
info.isIrradiatedWilderness = game.hasFallout(tile);
info.isWilderness = !info.isIrradiatedWilderness;
return info;
}
if (!game.isLand(tile)) {
const units = game
.units(UnitType.Warship, UnitType.TradeShip, UnitType.TransportShip)
.filter((u) => euclideanDistWorld(worldCoord, u.tile(), game) < 50)
.sort(distSortUnitWorld(worldCoord, game));
if (units.length > 0) {
info.unit = units[0];
}
}
return info;
}
+10 -48
View File
@@ -7,10 +7,8 @@ import {
PlayerProfile,
PlayerType,
Relation,
Unit,
UnitType,
} from "../../../core/game/Game";
import { TileRef } from "../../../core/game/GameMap";
import { AllianceView } from "../../../core/game/GameUpdates";
import { GameView, PlayerView, UnitView } from "../../../core/game/GameView";
import { ContextMenuEvent, MouseMoveEvent } from "../../InputHandler";
@@ -20,6 +18,7 @@ import {
renderTroops,
translateText,
} from "../../Utils";
import { getHoverInfo } from "../HoverInfo";
import { getFirstPlacePlayer, getPlayerIcons } from "../PlayerIcons";
import { TransformHandler } from "../TransformHandler";
import { Layer } from "./Layer";
@@ -33,26 +32,6 @@ import missileSiloIcon from "/images/MissileSiloIconWhite.svg?url";
import portIcon from "/images/PortIcon.svg?url";
import samLauncherIcon from "/images/SamLauncherIconWhite.svg?url";
function euclideanDistWorld(
coord: { x: number; y: number },
tileRef: TileRef,
game: GameView,
): number {
const x = game.x(tileRef);
const y = game.y(tileRef);
const dx = coord.x - x;
const dy = coord.y - y;
return Math.sqrt(dx * dx + dy * dy);
}
function distSortUnitWorld(coord: { x: number; y: number }, game: GameView) {
return (a: Unit | UnitView, b: Unit | UnitView) => {
const distA = euclideanDistWorld(coord, a.tile(), game);
const distB = euclideanDistWorld(coord, b.tile(), game);
return distA - distB;
};
}
@customElement("player-info-overlay")
export class PlayerInfoOverlay extends LitElement implements Layer {
@property({ type: Object })
@@ -119,38 +98,21 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
public maybeShow(x: number, y: number) {
this.hide();
const worldCoord = this.transform.screenToWorldCoordinates(x, y);
if (!this.game.isValidCoord(worldCoord.x, worldCoord.y)) {
return;
}
const info = getHoverInfo(this.game, worldCoord);
const tile = this.game.ref(worldCoord.x, worldCoord.y);
if (!tile) return;
const owner = this.game.owner(tile);
if (owner && owner.isPlayer()) {
this.player = owner as PlayerView;
if (info.player) {
this.player = info.player;
this.player.profile().then((p) => {
this.playerProfile = p;
});
this.setVisible(true);
} else if (owner && !owner.isPlayer() && this.game.isLand(tile)) {
if (this.game.hasFallout(tile)) {
this.isIrradiatedWilderness = true;
} else {
this.isWilderness = true;
}
} else if (info.isWilderness || info.isIrradiatedWilderness) {
this.isWilderness = info.isWilderness;
this.isIrradiatedWilderness = info.isIrradiatedWilderness;
this.setVisible(true);
} else if (info.unit) {
this.unit = info.unit;
this.setVisible(true);
} else if (!this.game.isLand(tile)) {
const units = this.game
.units(UnitType.Warship, UnitType.TradeShip, UnitType.TransportShip)
.filter((u) => euclideanDistWorld(worldCoord, u.tile(), this.game) < 50)
.sort(distSortUnitWorld(worldCoord, this.game));
if (units.length > 0) {
this.unit = units[0];
this.setVisible(true);
}
}
}
+8 -17
View File
@@ -12,6 +12,7 @@ import {
MouseOverEvent,
} from "../../InputHandler";
import { FrameProfiler } from "../FrameProfiler";
import { getHoverInfo } from "../HoverInfo";
import { TransformHandler } from "../TransformHandler";
import { Layer } from "./Layer";
import { TerritoryWebGLRenderer } from "./TerritoryWebGLRenderer";
@@ -322,12 +323,14 @@ export class TerritoryLayer implements Layer {
this.lastMousePosition.x,
this.lastMousePosition.y,
);
if (!this.game.isValidCoord(cell.x, cell.y)) {
return;
}
const previousTerritory = this.highlightedTerritory;
const territory = this.getTerritoryAtCell(cell);
const info = getHoverInfo(this.game, cell);
let territory: PlayerView | null = null;
if (info.player) {
territory = info.player;
} else if (info.unit) {
territory = info.unit.owner();
}
if (territory) {
this.highlightedTerritory = territory;
@@ -342,18 +345,6 @@ export class TerritoryLayer implements Layer {
}
}
private getTerritoryAtCell(cell: { x: number; y: number }) {
const tile = this.game.ref(cell.x, cell.y);
if (!tile) {
return null;
}
if (!this.game.hasOwner(tile)) {
return null;
}
const owner = this.game.owner(tile);
return owner instanceof PlayerView ? owner : null;
}
redraw() {
this.lastMyPlayerSmallId = this.game.myPlayer()?.smallID() ?? null;
this.cachedTerritoryPatternsEnabled = this.userSettings.territoryPatterns();