From 63a14738cd802a413bb3925499904948a152804d Mon Sep 17 00:00:00 2001 From: DevelopingTom Date: Mon, 8 Sep 2025 04:47:30 +0200 Subject: [PATCH] Redesign Player info overlay (#2000) ## Description: Redesign the player info panel to match the bottom panel. Changes: - Added alliance timeout - Various css restyling Old: image New: image ## Please complete the following: - [x] 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 - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: IngloriousTom --------- Co-authored-by: evanpelle --- resources/lang/en.json | 3 +- src/client/PublicLobby.ts | 6 +- src/client/Utils.ts | 10 + .../graphics/layers/PlayerInfoOverlay.ts | 192 +++++++++++++----- src/client/graphics/layers/PlayerPanel.ts | 16 +- 5 files changed, 155 insertions(+), 72 deletions(-) diff --git a/resources/lang/en.json b/resources/lang/en.json index d61250b2b..48dde128e 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -494,7 +494,8 @@ "nation": "Nation", "player": "Player", "team": "Team", - "d_troops": "Defending troops", + "alliance_timeout": "Alliance ends in", + "troops": "Troops", "a_troops": "Attacking troops", "gold": "Gold", "ports": "Ports", diff --git a/src/client/PublicLobby.ts b/src/client/PublicLobby.ts index e94ed794b..3c186be1f 100644 --- a/src/client/PublicLobby.ts +++ b/src/client/PublicLobby.ts @@ -1,6 +1,6 @@ import { LitElement, html } from "lit"; import { customElement, state } from "lit/decorators.js"; -import { translateText } from "../client/Utils"; +import { renderDuration, translateText } from "../client/Utils"; import { GameMapType, GameMode } from "../core/game/Game"; import { GameID, GameInfo } from "../core/Schemas"; import { generateID } from "../core/Util"; @@ -104,9 +104,7 @@ export class PublicLobby extends LitElement { const timeRemaining = Math.max(0, Math.floor((start - Date.now()) / 1000)); // Format time to show minutes and seconds - const minutes = Math.floor(timeRemaining / 60); - const seconds = timeRemaining % 60; - const timeDisplay = minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`; + const timeDisplay = renderDuration(timeRemaining); const teamCount = lobby.gameConfig.gameMode === GameMode.Team diff --git a/src/client/Utils.ts b/src/client/Utils.ts index 494109fbd..32335190f 100644 --- a/src/client/Utils.ts +++ b/src/client/Utils.ts @@ -2,6 +2,16 @@ import IntlMessageFormat from "intl-messageformat"; import { MessageType } from "../core/game/Game"; import { LangSelector } from "./LangSelector"; +export function renderDuration(totalSeconds: number): string { + if (totalSeconds <= 0) return "0s"; + const minutes = Math.floor(totalSeconds / 60); + const seconds = totalSeconds % 60; + let time = ""; + if (minutes > 0) time += `${minutes}min `; + time += `${seconds}s`; + return time.trim(); +} + export function renderTroops(troops: number): string { return renderNumber(troops / 10); } diff --git a/src/client/graphics/layers/PlayerInfoOverlay.ts b/src/client/graphics/layers/PlayerInfoOverlay.ts index dab035023..b99c587c8 100644 --- a/src/client/graphics/layers/PlayerInfoOverlay.ts +++ b/src/client/graphics/layers/PlayerInfoOverlay.ts @@ -1,7 +1,14 @@ import { LitElement, TemplateResult, html } from "lit"; import { ref } from "lit-html/directives/ref.js"; import { customElement, property, state } from "lit/decorators.js"; -import { translateText } from "../../../client/Utils"; +import allianceIcon from "../../../../resources/images/AllianceIcon.svg"; +import portIcon from "../../../../resources/images/AnchorIcon.png"; +import warshipIcon from "../../../../resources/images/BattleshipIconWhite.svg"; +import cityIcon from "../../../../resources/images/CityIconWhite.svg"; +import factoryIcon from "../../../../resources/images/FactoryIconWhite.svg"; +import goldCoinIcon from "../../../../resources/images/GoldCoinIcon.svg"; +import missileSiloIcon from "../../../../resources/images/MissileSiloUnit.png"; +import samLauncherIcon from "../../../../resources/images/SamLauncherIconWhite.svg"; import { renderPlayerFlag } from "../../../core/CustomFlag"; import { EventBus } from "../../../core/EventBus"; import { @@ -12,9 +19,15 @@ import { 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"; -import { renderNumber, renderTroops } from "../../Utils"; +import { + renderDuration, + renderNumber, + renderTroops, + translateText, +} from "../../Utils"; import { TransformHandler } from "../TransformHandler"; import { Layer } from "./Layer"; import { CloseRadialMenuEvent } from "./RadialMenu"; @@ -175,37 +188,83 @@ export class PlayerInfoOverlay extends LitElement implements Layer { private displayUnitCount( player: PlayerView, type: UnitType, + icon: string, description: string, ) { return !this.game.config().isUnitDisabled(type) - ? html`
- ${translateText(description)}: ${player.totalUnitLevels(type)} + ? html`
+ ${translateText(description)} + ${player.totalUnitLevels(type)}
` : ""; } + private allianceExpirationText(alliance: AllianceView) { + const { expiresAt } = alliance; + const remainingTicks = expiresAt - this.game.ticks(); + let remainingSeconds = 0; + if (remainingTicks > 0) { + remainingSeconds = Math.max(0, Math.floor(remainingTicks / 10)); // 10 ticks per second + } + return renderDuration(remainingSeconds); + } + private renderPlayerInfo(player: PlayerView) { const myPlayer = this.game.myPlayer(); const isFriendly = myPlayer?.isFriendly(player); + const isAllied = myPlayer?.isAlliedWith(player); let relationHtml: TemplateResult | null = null; const attackingTroops = player .outgoingAttacks() .map((a) => a.troops) .reduce((a, b) => a + b, 0); - if (player.type() === PlayerType.FakeHuman && myPlayer !== null) { + if ( + player.type() === PlayerType.FakeHuman && + myPlayer !== null && + !isAllied + ) { const relation = this.playerProfile?.relations[myPlayer.smallID()] ?? Relation.Neutral; const relationClass = this.getRelationClass(relation); const relationName = this.getRelationName(relation); relationHtml = html` -
- ${translateText("player_info_overlay.attitude")}: - ${relationName} -
+ ${relationName} `; } + + if (isAllied) { + const alliance = myPlayer + ?.alliances() + .find((alliance) => alliance.other === player.id()); + if (alliance !== undefined) { + relationHtml = html` + ${translateText("player_info_overlay.alliance_timeout")} + ${this.allianceExpirationText(alliance)} + `; + } + } let playerType = ""; switch (player.type()) { case PlayerType.Bot: @@ -222,7 +281,7 @@ export class PlayerInfoOverlay extends LitElement implements Layer { return html`
` : ""} -
- ${translateText("player_info_overlay.type")}: ${playerType} -
+
${playerType} ${relationHtml}
${player.troops() >= 1 - ? html`
- ${translateText("player_info_overlay.d_troops")}: - ${renderTroops(player.troops())} + ? html`
+ ${translateText("player_info_overlay.troops")} + + ${renderTroops(player.troops())} +
` : ""} ${attackingTroops >= 1 - ? html`
- ${translateText("player_info_overlay.a_troops")}: - ${renderTroops(attackingTroops)} + ? html`
+ ${translateText("player_info_overlay.a_troops")} + + ${renderTroops(attackingTroops)} +
` : ""} -
- ${translateText("player_info_overlay.gold")}: - ${renderNumber(player.gold())} +
+ ${translateText("player_info_overlay.gold")} + ${renderNumber(player.gold())} +
+
+ ${this.displayUnitCount( + player, + UnitType.City, + cityIcon, + "player_info_overlay.cities", + )} + ${this.displayUnitCount( + player, + UnitType.Port, + portIcon, + "player_info_overlay.ports", + )} + ${this.displayUnitCount( + player, + UnitType.Factory, + factoryIcon, + "player_info_overlay.factories", + )} + ${this.displayUnitCount( + player, + UnitType.MissileSilo, + missileSiloIcon, + "player_info_overlay.missile_launchers", + )} + ${this.displayUnitCount( + player, + UnitType.SAMLauncher, + samLauncherIcon, + "player_info_overlay.sams", + )} + ${this.displayUnitCount( + player, + UnitType.Warship, + warshipIcon, + "player_info_overlay.warships", + )}
- ${this.displayUnitCount( - player, - UnitType.Port, - "player_info_overlay.ports", - )} - ${this.displayUnitCount( - player, - UnitType.City, - "player_info_overlay.cities", - )} - ${this.displayUnitCount( - player, - UnitType.Factory, - "player_info_overlay.factories", - )} - ${this.displayUnitCount( - player, - UnitType.MissileSilo, - "player_info_overlay.missile_launchers", - )} - ${this.displayUnitCount( - player, - UnitType.SAMLauncher, - "player_info_overlay.sams", - )} - ${this.displayUnitCount( - player, - UnitType.Warship, - "player_info_overlay.warships", - )} - ${relationHtml} ` : ""}
diff --git a/src/client/graphics/layers/PlayerPanel.ts b/src/client/graphics/layers/PlayerPanel.ts index 8bdfaa34c..e6851115b 100644 --- a/src/client/graphics/layers/PlayerPanel.ts +++ b/src/client/graphics/layers/PlayerPanel.ts @@ -24,7 +24,7 @@ import { SendEmojiIntentEvent, SendTargetPlayerIntentEvent, } from "../../Transport"; -import { renderNumber, renderTroops } from "../../Utils"; +import { renderDuration, renderNumber, renderTroops } from "../../Utils"; import { UIState } from "../UIState"; import { ChatModal } from "./ChatModal"; import { EmojiTable } from "./EmojiTable"; @@ -188,17 +188,15 @@ export class PlayerPanel extends LitElement implements Layer { const myPlayer = this.g.myPlayer(); if (myPlayer !== null && myPlayer.isAlive()) { this.actions = await myPlayer.actions(this.tile); - if (this.actions?.interaction?.allianceExpiresAt !== undefined) { const expiresAt = this.actions.interaction.allianceExpiresAt; const remainingTicks = expiresAt - this.g.ticks(); - if (remainingTicks > 0) { const remainingSeconds = Math.max( 0, Math.floor(remainingTicks / 10), ); // 10 ticks per second - this.allianceExpiryText = this.formatDuration(remainingSeconds); + this.allianceExpiryText = renderDuration(remainingSeconds); } } else { this.allianceExpiryText = null; @@ -208,16 +206,6 @@ export class PlayerPanel extends LitElement implements Layer { } } - private formatDuration(totalSeconds: number): string { - if (totalSeconds <= 0) return "0s"; - const minutes = Math.floor(totalSeconds / 60); - const seconds = totalSeconds % 60; - let time = ""; - if (minutes > 0) time += `${minutes}m `; - time += `${seconds}s`; - return time.trim(); - } - render() { if (!this.isVisible) { return html``;