mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-04 11:06:09 +00:00
Move player info panel to top of the screen & simplify (#3087)
related to #2260 ## Description: * Moves the player info panel from the right to the top of the screen * Disable the header ad for now because it would cover up the player info, we'll find a better place for it in the future * Remove the collapsable button/functionality. It's hard to even click the button because the panel disappears when you move away from a player, and I think the info is too valuable to ever need to be collapsed. * Removed the "land" and "irradiated land" since it didn't add much value * Remove all alt text & translation, you can't hover over the player overlay so it's irrelevant. * put troop info inside the troop bar to reduce amount of text <img width="479" height="88" alt="Screenshot 2026-02-01 at 8 57 33 PM" src="https://github.com/user-attachments/assets/3b72eb16-2efa-4c00-a4d0-5e085548fa78" /> <img width="438" height="136" alt="Screenshot 2026-02-01 at 8 58 06 PM" src="https://github.com/user-attachments/assets/285bb2c9-6deb-4ee8-bcc8-743cccd6b77e" /> ## 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: evan
This commit is contained in:
@@ -21,7 +21,8 @@ export class InGameHeaderAd extends LitElement implements Layer {
|
||||
}
|
||||
|
||||
init() {
|
||||
this.showHeaderAd();
|
||||
// TODO: move ad and re-enable.
|
||||
// this.showHeaderAd();
|
||||
}
|
||||
|
||||
private showHeaderAd(): void {
|
||||
|
||||
@@ -32,6 +32,7 @@ import goldCoinIcon from "/images/GoldCoinIcon.svg?url";
|
||||
import missileSiloIcon from "/images/MissileSiloIconWhite.svg?url";
|
||||
import portIcon from "/images/PortIcon.svg?url";
|
||||
import samLauncherIcon from "/images/SamLauncherIconWhite.svg?url";
|
||||
import soldierIcon from "/images/SoldierIcon.svg?url";
|
||||
|
||||
function euclideanDistWorld(
|
||||
coord: { x: number; y: number },
|
||||
@@ -73,12 +74,6 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
|
||||
@state()
|
||||
private unit: UnitView | null = null;
|
||||
|
||||
@state()
|
||||
private isWilderness: boolean = false;
|
||||
|
||||
@state()
|
||||
private isIrradiatedWilderness: boolean = false;
|
||||
|
||||
@state()
|
||||
private _isInfoVisible: boolean = false;
|
||||
|
||||
@@ -86,8 +81,6 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
|
||||
|
||||
private lastMouseUpdate = 0;
|
||||
|
||||
private showDetails = true;
|
||||
|
||||
init() {
|
||||
this.eventBus.on(MouseMoveEvent, (e: MouseMoveEvent) =>
|
||||
this.onMouseEvent(e),
|
||||
@@ -112,8 +105,6 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
|
||||
this.setVisible(false);
|
||||
this.unit = null;
|
||||
this.player = null;
|
||||
this.isWilderness = false;
|
||||
this.isIrradiatedWilderness = false;
|
||||
}
|
||||
|
||||
public maybeShow(x: number, y: number) {
|
||||
@@ -134,13 +125,6 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
|
||||
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;
|
||||
}
|
||||
this.setVisible(true);
|
||||
} else if (!this.game.isLand(tile)) {
|
||||
const units = this.game
|
||||
.units(UnitType.Warship, UnitType.TradeShip, UnitType.TransportShip)
|
||||
@@ -201,28 +185,17 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
|
||||
}
|
||||
}
|
||||
|
||||
private displayUnitCount(
|
||||
player: PlayerView,
|
||||
type: UnitType,
|
||||
icon: string,
|
||||
description: string,
|
||||
) {
|
||||
private displayUnitCount(player: PlayerView, type: UnitType, icon: string) {
|
||||
return !this.game.config().isUnitDisabled(type)
|
||||
? html`<div
|
||||
class="flex p-1 w-[calc(50%-0.13rem)] border rounded-md border-gray-500
|
||||
items-center gap-2 text-sm"
|
||||
class="flex items-center justify-center gap-0.5 lg:gap-1 p-0.5 lg:p-1 border rounded-md border-gray-500 text-[10px] lg:text-xs w-9 lg:w-12 h-6 lg:h-7"
|
||||
translate="no"
|
||||
>
|
||||
<img
|
||||
src=${icon}
|
||||
width="20"
|
||||
height="20"
|
||||
alt="${translateText(description)}"
|
||||
class="align-middle"
|
||||
class="w-3 h-3 lg:w-4 lg:h-4 object-contain shrink-0"
|
||||
/>
|
||||
<span class="w-full text-right p-1"
|
||||
>${player.totalUnitLevels(type)}</span
|
||||
>
|
||||
<span>${player.totalUnitLevels(type)}</span>
|
||||
</div>`
|
||||
: "";
|
||||
}
|
||||
@@ -268,7 +241,7 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
|
||||
const myPlayer = this.game.myPlayer();
|
||||
const isFriendly = myPlayer?.isFriendly(player);
|
||||
const isAllied = myPlayer?.isAlliedWith(player);
|
||||
let relationHtml: TemplateResult | null = null;
|
||||
let allianceHtml: TemplateResult | null = null;
|
||||
const maxTroops = this.game.config().maxTroops(player);
|
||||
const attackingTroops = player
|
||||
.outgoingAttacks()
|
||||
@@ -276,34 +249,17 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
|
||||
.reduce((a, b) => a + b, 0);
|
||||
const totalTroops = player.troops();
|
||||
|
||||
if (player.type() === PlayerType.Nation && myPlayer !== null && !isAllied) {
|
||||
const relation =
|
||||
this.playerProfile?.relations[myPlayer.smallID()] ?? Relation.Neutral;
|
||||
const relationClass = this.getRelationClass(relation);
|
||||
const relationName = this.getRelationName(relation);
|
||||
|
||||
relationHtml = html`
|
||||
<span class="ml-auto mr-0 ${relationClass}">${relationName}</span>
|
||||
`;
|
||||
}
|
||||
|
||||
if (isAllied) {
|
||||
const alliance = myPlayer
|
||||
?.alliances()
|
||||
.find((alliance) => alliance.other === player.id());
|
||||
if (alliance !== undefined) {
|
||||
relationHtml = html` <span
|
||||
class="flex gap-2 ml-auto mr-0 text-sm font-bold"
|
||||
allianceHtml = html` <div
|
||||
class="flex flex-col items-center ml-auto mr-0 text-sm font-bold leading-tight"
|
||||
>
|
||||
<img
|
||||
src=${allianceIcon}
|
||||
alt=${translateText("player_info_overlay.alliance_timeout")}
|
||||
width="20"
|
||||
height="20"
|
||||
class="align-middle"
|
||||
/>
|
||||
<img src=${allianceIcon} width="20" height="20" />
|
||||
${this.allianceExpirationText(alliance)}
|
||||
</span>`;
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
let playerType = "";
|
||||
@@ -320,128 +276,83 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="p-2">
|
||||
<button
|
||||
class="items-center text-bold text-sm lg:text-lg font-bold mb-1 inline-flex break-all ${isFriendly
|
||||
? "text-green-500"
|
||||
: "text-white"}"
|
||||
@click=${() => {
|
||||
this.showDetails = !this.showDetails;
|
||||
this.requestUpdate?.();
|
||||
}}
|
||||
>
|
||||
${player.cosmetics.flag
|
||||
? player.cosmetics.flag!.startsWith("!")
|
||||
? html`<div
|
||||
class="h-8 mr-1 aspect-3/4 player-flag"
|
||||
${ref((el) => {
|
||||
if (el instanceof HTMLElement) {
|
||||
requestAnimationFrame(() => {
|
||||
renderPlayerFlag(player.cosmetics.flag!, el);
|
||||
});
|
||||
}
|
||||
})}
|
||||
></div>`
|
||||
: html`<img
|
||||
class="h-8 mr-1 aspect-3/4"
|
||||
src=${"/flags/" + player.cosmetics.flag! + ".svg"}
|
||||
/>`
|
||||
: html``}
|
||||
<span>${player.name()}</span>
|
||||
${this.renderPlayerNameIcons(player)}
|
||||
</button>
|
||||
|
||||
<!-- Collapsible section -->
|
||||
${this.showDetails
|
||||
? html`
|
||||
${player.team() !== null
|
||||
? html`<div class="text-sm">
|
||||
${translateText("player_info_overlay.team")}:
|
||||
${player.team()}
|
||||
</div>`
|
||||
: ""}
|
||||
<div class="flex text-sm">${playerType} ${relationHtml}</div>
|
||||
${player.troops() >= 1
|
||||
? html`<div class="flex gap-2 text-sm" translate="no">
|
||||
${translateText("player_info_overlay.troops")}
|
||||
<span class="ml-auto mr-0 font-bold">
|
||||
${renderTroops(player.troops())}
|
||||
</span>
|
||||
</div>`
|
||||
: ""}
|
||||
${maxTroops >= 1
|
||||
? html`<div class="flex gap-2 text-sm" translate="no">
|
||||
${translateText("player_info_overlay.maxtroops")}
|
||||
<span class="ml-auto mr-0 font-bold">
|
||||
${renderTroops(maxTroops)}
|
||||
</span>
|
||||
</div>`
|
||||
: ""}
|
||||
${attackingTroops >= 1
|
||||
? html`<div class="flex gap-2 text-sm" translate="no">
|
||||
${translateText("player_info_overlay.a_troops")}
|
||||
<span class="ml-auto mr-0 text-red-400 font-bold">
|
||||
${renderTroops(attackingTroops)}
|
||||
</span>
|
||||
</div>`
|
||||
: ""}
|
||||
${this.renderTroopBar(totalTroops, attackingTroops, maxTroops)}
|
||||
<div
|
||||
class="flex p-1 mb-1 mt-1 w-full border rounded-md border-yellow-400
|
||||
font-bold text-yellow-400 text-sm"
|
||||
translate="no"
|
||||
>
|
||||
<img
|
||||
src=${goldCoinIcon}
|
||||
alt=${translateText("player_info_overlay.gold")}
|
||||
width="15"
|
||||
height="15"
|
||||
class="align-middle"
|
||||
/>
|
||||
<span class="w-full text-center"
|
||||
>${renderNumber(player.gold())}</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex flex-wrap max-w-3xl gap-1">
|
||||
${this.displayUnitCount(
|
||||
player,
|
||||
UnitType.City,
|
||||
cityIcon,
|
||||
"player_info_overlay.cities",
|
||||
)}
|
||||
${this.displayUnitCount(
|
||||
player,
|
||||
UnitType.Factory,
|
||||
factoryIcon,
|
||||
"player_info_overlay.factories",
|
||||
)}
|
||||
${this.displayUnitCount(
|
||||
player,
|
||||
UnitType.Port,
|
||||
portIcon,
|
||||
"player_info_overlay.ports",
|
||||
)}
|
||||
${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",
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<div class="flex items-start gap-2 lg:gap-3 p-1.5 lg:p-2">
|
||||
<!-- Left: Gold & Troop bar -->
|
||||
<div class="flex flex-col gap-1 shrink-0 w-28">
|
||||
<div
|
||||
class="flex items-center justify-center p-1 border rounded-md border-yellow-400 font-bold text-yellow-400 text-xs w-28"
|
||||
translate="no"
|
||||
>
|
||||
<img src=${goldCoinIcon} width="13" height="13" />
|
||||
<span class="px-0.5">${renderNumber(player.gold())}</span>
|
||||
</div>
|
||||
<div class="w-28" translate="no">
|
||||
${this.renderTroopBar(totalTroops, attackingTroops, maxTroops)}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Right: Player identity + Units below -->
|
||||
<div class="flex flex-col justify-between self-stretch">
|
||||
<div
|
||||
class="flex items-center gap-2 font-bold text-sm lg:text-lg ${isFriendly
|
||||
? "text-green-500"
|
||||
: "text-white"}"
|
||||
>
|
||||
${player.cosmetics.flag
|
||||
? player.cosmetics.flag!.startsWith("!")
|
||||
? html`<div
|
||||
class="h-6 aspect-3/4 player-flag"
|
||||
${ref((el) => {
|
||||
if (el instanceof HTMLElement) {
|
||||
requestAnimationFrame(() => {
|
||||
renderPlayerFlag(player.cosmetics.flag!, el);
|
||||
});
|
||||
}
|
||||
})}
|
||||
></div>`
|
||||
: html`<img
|
||||
class="h-6 aspect-3/4"
|
||||
src=${"/flags/" + player.cosmetics.flag! + ".svg"}
|
||||
/>`
|
||||
: html``}
|
||||
<span>${player.name()}</span>
|
||||
${player.team() !== null && player.type() !== PlayerType.Bot
|
||||
? html`<div class="flex flex-col leading-tight">
|
||||
<span class="text-gray-400 text-xs font-normal"
|
||||
>${playerType}</span
|
||||
>
|
||||
<span class="text-xs font-normal text-gray-400"
|
||||
>[<span
|
||||
style="color: ${this.game
|
||||
.config()
|
||||
.theme()
|
||||
.teamColor(player.team()!)
|
||||
.toHex()}"
|
||||
>${player.team()}</span
|
||||
>]</span
|
||||
>
|
||||
</div>`
|
||||
: html`<span class="text-gray-400 text-xs font-normal"
|
||||
>${playerType}</span
|
||||
>`}
|
||||
${this.renderPlayerNameIcons(player)} ${allianceHtml ?? ""}
|
||||
</div>
|
||||
<div class="flex gap-0.5 lg:gap-1 items-center mt-1">
|
||||
${this.displayUnitCount(player, UnitType.City, cityIcon)}
|
||||
${this.displayUnitCount(player, UnitType.Factory, factoryIcon)}
|
||||
${this.displayUnitCount(player, UnitType.Port, portIcon)}
|
||||
${this.displayUnitCount(
|
||||
player,
|
||||
UnitType.MissileSilo,
|
||||
missileSiloIcon,
|
||||
)}
|
||||
${this.displayUnitCount(
|
||||
player,
|
||||
UnitType.SAMLauncher,
|
||||
samLauncherIcon,
|
||||
)}
|
||||
${this.displayUnitCount(player, UnitType.Warship, warshipIcon)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -463,7 +374,7 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="w-full mt-2 mb-2 h-5 border border-gray-600 rounded-md bg-gray-900/60 overflow-hidden"
|
||||
class="w-full mt-1 lg:mt-2 h-5 lg:h-6 border border-gray-600 rounded-md bg-gray-900/60 overflow-hidden relative"
|
||||
>
|
||||
<div class="h-full flex">
|
||||
${greenPercent > 0
|
||||
@@ -479,6 +390,25 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
|
||||
></div>`
|
||||
: ""}
|
||||
</div>
|
||||
<div
|
||||
class="absolute inset-0 flex items-center justify-between px-1.5 text-xs font-bold leading-none pointer-events-none"
|
||||
translate="no"
|
||||
>
|
||||
<span class="text-white drop-shadow-[0_1px_1px_rgba(0,0,0,0.8)]"
|
||||
>${renderTroops(totalTroops)}</span
|
||||
>
|
||||
<span class="text-white drop-shadow-[0_1px_1px_rgba(0,0,0,0.8)]"
|
||||
>${renderTroops(maxTroops)}</span
|
||||
>
|
||||
</div>
|
||||
<img
|
||||
src=${soldierIcon}
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
width="14"
|
||||
height="14"
|
||||
class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 brightness-0 invert drop-shadow-[0_1px_1px_rgba(0,0,0,0.8)] pointer-events-none"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -497,18 +427,12 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
|
||||
<div class="mt-1">
|
||||
<div class="text-sm opacity-80">${unit.type()}</div>
|
||||
${unit.hasHealth()
|
||||
? html`
|
||||
<div class="text-sm">
|
||||
${translateText("player_info_overlay.health")}:
|
||||
${unit.health()}
|
||||
</div>
|
||||
`
|
||||
? html` <div class="text-sm">Health: ${unit.health()}</div> `
|
||||
: ""}
|
||||
${unit.type() === UnitType.TransportShip
|
||||
? html`
|
||||
<div class="text-sm">
|
||||
${translateText("player_info_overlay.troops")}:
|
||||
${renderTroops(unit.troops())}
|
||||
Troops: ${renderTroops(unit.troops())}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
@@ -528,21 +452,12 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="block lg:flex fixed top-37.5 right-4 w-full z-50 flex-col max-w-45"
|
||||
class="fixed top-0 lg:top-[10px] left-0 right-0 sm:left-1/2 sm:right-auto sm:-translate-x-1/2 z-[1001]"
|
||||
@contextmenu=${(e: MouseEvent) => e.preventDefault()}
|
||||
>
|
||||
<div
|
||||
class="bg-gray-800/70 backdrop-blur-xs shadow-xs rounded-lg shadow-lg transition-all duration-300 text-white text-lg md:text-base ${containerClasses}"
|
||||
class="bg-gray-800/70 backdrop-blur-xs shadow-xs lg:rounded-lg shadow-lg transition-all duration-300 text-white text-lg lg:text-base w-full sm:w-auto sm:min-w-[400px] overflow-hidden ${containerClasses}"
|
||||
>
|
||||
${this.isWilderness || this.isIrradiatedWilderness
|
||||
? html`<div class="p-2 font-bold">
|
||||
${translateText(
|
||||
this.isIrradiatedWilderness
|
||||
? "player_info_overlay.irradiated_wilderness_title"
|
||||
: "player_info_overlay.wilderness_title",
|
||||
)}
|
||||
</div>`
|
||||
: ""}
|
||||
${this.player !== null ? this.renderPlayerInfo(this.player) : ""}
|
||||
${this.unit !== null ? this.renderUnitInfo(this.unit) : ""}
|
||||
</div>
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Layer } from "./Layer";
|
||||
import warshipIcon from "/images/BattleshipIconWhite.svg?url";
|
||||
import cityIcon from "/images/CityIconWhite.svg?url";
|
||||
import factoryIcon from "/images/FactoryIconWhite.svg?url";
|
||||
import goldCoinIcon from "/images/GoldCoinIcon.svg?url";
|
||||
import mirvIcon from "/images/MIRVIcon.svg?url";
|
||||
import missileSiloIcon from "/images/MissileSiloIconWhite.svg?url";
|
||||
import hydrogenBombIcon from "/images/MushroomCloudIconWhite.svg?url";
|
||||
@@ -256,11 +257,11 @@ export class UnitDisplay extends LitElement implements Layer {
|
||||
<div class="p-2">
|
||||
${translateText("build_menu.desc." + structureKey)}
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex items-center justify-center gap-1">
|
||||
<img src=${goldCoinIcon} width="13" height="13" />
|
||||
<span class="text-yellow-300"
|
||||
>${renderNumber(this.cost(unitType))}</span
|
||||
>
|
||||
${translateText("player_info_overlay.gold")}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
Reference in New Issue
Block a user