Skip to content

Commit 27a3ac3

Browse files
committed
Add Water and Wilderness info overlays to PlayerInfoOverlay
Resolves #3011
1 parent 38222bf commit 27a3ac3

File tree

3 files changed

+220
-1
lines changed

3 files changed

+220
-1
lines changed

resources/lang/en.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -784,6 +784,10 @@
784784
"show_control": "Show Control",
785785
"show_units": "Show Units"
786786
},
787+
"player_info_overlay": {
788+
"water_title": "Water",
789+
"wilderness_title": "Wilderness"
790+
},
787791
"events_display": {
788792
"events": "Events",
789793
"retreating": "retreating",

src/client/graphics/layers/PlayerInfoOverlay.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,12 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
8585
@state()
8686
private unit: UnitView | null = null;
8787

88+
@state()
89+
private isWater: boolean = false;
90+
91+
@state()
92+
private isWilderness: boolean = false;
93+
8894
@state()
8995
private _isInfoVisible: boolean = false;
9096

@@ -132,6 +138,8 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
132138
this.setVisible(false);
133139
this.unit = null;
134140
this.player = null;
141+
this.isWater = false;
142+
this.isWilderness = false;
135143
}
136144

137145
public maybeShow(x: number, y: number) {
@@ -153,15 +161,21 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
153161
});
154162
this.setVisible(true);
155163
} else if (!this.game.isLand(tile)) {
164+
this.isWater = true;
165+
156166
const units = this.game
157167
.units(UnitType.Warship, UnitType.TradeShip, UnitType.TransportShip)
158168
.filter((u) => euclideanDistWorld(worldCoord, u.tile(), this.game) < 50)
159169
.sort(distSortUnitWorld(worldCoord, this.game));
160170

161171
if (units.length > 0) {
162172
this.unit = units[0];
163-
this.setVisible(true);
164173
}
174+
175+
this.setVisible(true);
176+
} else if (this.game.isLand(tile)) {
177+
this.isWilderness = true;
178+
this.setVisible(true);
165179
}
166180
}
167181

@@ -520,6 +534,16 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
520534
<div
521535
class="bg-gray-800/92 backdrop-blur-sm shadow-xs min-[1200px]:rounded-lg sm:rounded-b-lg shadow-lg text-white text-lg lg:text-base w-full sm:w-[500px] overflow-hidden ${containerClasses}"
522536
>
537+
${this.isWater
538+
? html`<div class="p-2 font-bold">
539+
${translateText("player_info_overlay.water_title")}
540+
</div>`
541+
: ""}
542+
${this.isWilderness
543+
? html`<div class="p-2 font-bold">
544+
${translateText("player_info_overlay.wilderness_title")}
545+
</div>`
546+
: ""}
523547
${this.player !== null ? this.renderPlayerInfo(this.player) : ""}
524548
${this.unit !== null ? this.renderUnitInfo(this.unit) : ""}
525549
</div>
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
vi.mock("lit", () => ({
2+
html: (strings: TemplateStringsArray, ...values: unknown[]) => ({
3+
strings,
4+
values,
5+
}),
6+
LitElement: class extends EventTarget {
7+
requestUpdate() {}
8+
},
9+
}));
10+
11+
vi.mock("lit/decorators.js", () => ({
12+
customElement: () => (clazz: unknown) => clazz,
13+
state: () => () => {},
14+
property: () => () => {},
15+
query: () => () => {},
16+
}));
17+
18+
vi.mock("../../../../src/client/Utils", () => ({
19+
translateText: vi.fn((key: string) => key),
20+
renderDuration: vi.fn(),
21+
renderNumber: vi.fn(),
22+
renderTroops: vi.fn(),
23+
getTranslatedPlayerTeamLabel: vi.fn(() => ""),
24+
}));
25+
26+
vi.mock("../../../../src/core/AssetUrls", () => ({
27+
assetUrl: vi.fn((p: string) => p),
28+
}));
29+
30+
vi.mock("../../../../src/client/graphics/PlayerIcons", () => ({
31+
getFirstPlacePlayer: vi.fn(),
32+
getPlayerIcons: vi.fn(() => []),
33+
EMOJI_ICON_KIND: "emoji",
34+
IMAGE_ICON_KIND: "image",
35+
}));
36+
37+
vi.mock("../../../../src/client/graphics/layers/ImmunityTimer", () => ({
38+
ImmunityBarVisibleEvent: class {},
39+
}));
40+
41+
vi.mock("../../../../src/client/graphics/layers/SpawnTimer", () => ({
42+
SpawnBarVisibleEvent: class {},
43+
}));
44+
45+
vi.mock("../../../../src/client/graphics/layers/RadialMenu", () => ({
46+
CloseRadialMenuEvent: class {},
47+
}));
48+
49+
import { PlayerInfoOverlay } from "../../../../src/client/graphics/layers/PlayerInfoOverlay";
50+
import { UnitType } from "../../../../src/core/game/Game";
51+
52+
function makeOverlay(gameOverrides: Record<string, unknown> = {}) {
53+
const overlay = new PlayerInfoOverlay();
54+
55+
const game = {
56+
isValidCoord: vi.fn(() => true),
57+
ref: vi.fn(() => 42),
58+
owner: vi.fn(() => ({ isPlayer: () => false })),
59+
isLand: vi.fn(() => false),
60+
units: vi.fn(() => []),
61+
x: vi.fn(() => 0),
62+
y: vi.fn(() => 0),
63+
myPlayer: vi.fn(() => null),
64+
config: vi.fn(() => ({
65+
isUnitDisabled: () => false,
66+
theme: () => ({ teamColor: () => ({ toHex: () => "#fff" }) }),
67+
maxTroops: () => 1000,
68+
disableAlliances: () => false,
69+
})),
70+
ticks: vi.fn(() => 0),
71+
...gameOverrides,
72+
};
73+
74+
const transform = {
75+
screenToWorldCoordinates: vi.fn(() => ({ x: 10, y: 10 })),
76+
};
77+
78+
(overlay as any).game = game;
79+
(overlay as any).transform = transform;
80+
(overlay as any).eventBus = { on: vi.fn() };
81+
82+
return { overlay, game, transform };
83+
}
84+
85+
describe("PlayerInfoOverlay", () => {
86+
describe("maybeShow", () => {
87+
test("water tile with no ships shows water overlay", () => {
88+
const { overlay } = makeOverlay();
89+
90+
overlay.maybeShow(100, 100);
91+
92+
expect((overlay as any).isWater).toBe(true);
93+
expect((overlay as any).unit).toBeNull();
94+
expect((overlay as any)._isInfoVisible).toBe(true);
95+
});
96+
97+
test("water tile with nearby ship shows water overlay and unit", () => {
98+
const mockUnit = {
99+
tile: () => 42,
100+
type: () => UnitType.Warship,
101+
owner: () => ({ displayName: () => "Player1" }),
102+
hasHealth: () => true,
103+
health: () => 100,
104+
troops: () => 0,
105+
};
106+
107+
const { overlay } = makeOverlay({
108+
units: vi.fn(() => [mockUnit]),
109+
x: vi.fn(() => 10),
110+
y: vi.fn(() => 10),
111+
});
112+
113+
overlay.maybeShow(100, 100);
114+
115+
expect((overlay as any).isWater).toBe(true);
116+
expect((overlay as any).unit).toBe(mockUnit);
117+
expect((overlay as any)._isInfoVisible).toBe(true);
118+
});
119+
120+
test("player-owned tile shows player info, not water", () => {
121+
const mockPlayer = {
122+
isPlayer: () => true,
123+
profile: () => Promise.resolve({}),
124+
displayName: () => "TestPlayer",
125+
type: () => 0,
126+
team: () => null,
127+
cosmetics: { flag: null },
128+
gold: () => 100,
129+
troops: () => 50,
130+
outgoingAttacks: () => [],
131+
alliances: () => [],
132+
totalUnitLevels: () => 0,
133+
smallID: () => 1,
134+
id: () => 1,
135+
isFriendly: () => false,
136+
isAlliedWith: () => false,
137+
};
138+
139+
const { overlay } = makeOverlay({
140+
owner: vi.fn(() => mockPlayer),
141+
});
142+
143+
overlay.maybeShow(100, 100);
144+
145+
expect((overlay as any).isWater).toBe(false);
146+
expect((overlay as any).player).toBe(mockPlayer);
147+
expect((overlay as any)._isInfoVisible).toBe(true);
148+
});
149+
150+
test("unowned land tile shows wilderness overlay", () => {
151+
const { overlay } = makeOverlay({
152+
isLand: vi.fn(() => true),
153+
});
154+
155+
overlay.maybeShow(100, 100);
156+
157+
expect((overlay as any).isWilderness).toBe(true);
158+
expect((overlay as any).isWater).toBe(false);
159+
expect((overlay as any).player).toBeNull();
160+
expect((overlay as any)._isInfoVisible).toBe(true);
161+
});
162+
163+
test("invalid coordinates shows nothing", () => {
164+
const { overlay } = makeOverlay({
165+
isValidCoord: vi.fn(() => false),
166+
});
167+
168+
overlay.maybeShow(100, 100);
169+
170+
expect((overlay as any).isWater).toBe(false);
171+
expect((overlay as any).isWilderness).toBe(false);
172+
expect((overlay as any)._isInfoVisible).toBe(false);
173+
});
174+
});
175+
176+
describe("hide", () => {
177+
test("resets isWater and isWilderness state", () => {
178+
const { overlay } = makeOverlay();
179+
180+
overlay.maybeShow(100, 100);
181+
expect((overlay as any).isWater).toBe(true);
182+
183+
overlay.hide();
184+
expect((overlay as any).isWater).toBe(false);
185+
expect((overlay as any).isWilderness).toBe(false);
186+
expect((overlay as any).unit).toBeNull();
187+
expect((overlay as any).player).toBeNull();
188+
expect((overlay as any)._isInfoVisible).toBe(false);
189+
});
190+
});
191+
});

0 commit comments

Comments
 (0)