mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-08-17 05:34:37 +00:00
move unit display to bottom of screen (#1365)
## Description: Using this design: https://cdn.discordapp.com/attachments/1391061082524881078/1391221235324551409/image.png?ex=686d157e&is=686bc3fe&hm=1027b79141dac471dcae4c002beb5f1425790bd282607efda5ff5bfb3e8fda83& <img width="1904" alt="Screenshot 2025-07-07 at 3 11 48 PM" src="https://github.com/user-attachments/assets/d1710c32-9dc1-48e3-8abd-affc0c6c5bfd" /> ## 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 - [x] I understand that submitting code with bugs that could have been caught through manual testing blocks releases and new features for all contributors ## Please put your Discord username so you can be contacted if a bug or regression is found: evan
This commit is contained in:
@@ -35,6 +35,7 @@ import { TeamStats } from "./layers/TeamStats";
|
|||||||
import { TerrainLayer } from "./layers/TerrainLayer";
|
import { TerrainLayer } from "./layers/TerrainLayer";
|
||||||
import { TerritoryLayer } from "./layers/TerritoryLayer";
|
import { TerritoryLayer } from "./layers/TerritoryLayer";
|
||||||
import { UILayer } from "./layers/UILayer";
|
import { UILayer } from "./layers/UILayer";
|
||||||
|
import { UnitDisplay } from "./layers/UnitDisplay";
|
||||||
import { UnitLayer } from "./layers/UnitLayer";
|
import { UnitLayer } from "./layers/UnitLayer";
|
||||||
import { WinModal } from "./layers/WinModal";
|
import { WinModal } from "./layers/WinModal";
|
||||||
|
|
||||||
@@ -158,6 +159,13 @@ export function createRenderer(
|
|||||||
gameTopBar.game = game;
|
gameTopBar.game = game;
|
||||||
gameTopBar.eventBus = eventBus;
|
gameTopBar.eventBus = eventBus;
|
||||||
|
|
||||||
|
const unitDisplay = document.querySelector("unit-display") as UnitDisplay;
|
||||||
|
if (!(unitDisplay instanceof UnitDisplay)) {
|
||||||
|
console.error("unit display not found");
|
||||||
|
}
|
||||||
|
unitDisplay.game = game;
|
||||||
|
unitDisplay.eventBus = eventBus;
|
||||||
|
|
||||||
const playerPanel = document.querySelector("player-panel") as PlayerPanel;
|
const playerPanel = document.querySelector("player-panel") as PlayerPanel;
|
||||||
if (!(playerPanel instanceof PlayerPanel)) {
|
if (!(playerPanel instanceof PlayerPanel)) {
|
||||||
console.error("player panel not found");
|
console.error("player panel not found");
|
||||||
@@ -238,6 +246,7 @@ export function createRenderer(
|
|||||||
leaderboard,
|
leaderboard,
|
||||||
gameLeftSidebar,
|
gameLeftSidebar,
|
||||||
gameTopBar,
|
gameTopBar,
|
||||||
|
unitDisplay,
|
||||||
gameRightSidebar,
|
gameRightSidebar,
|
||||||
controlPanel,
|
controlPanel,
|
||||||
playerInfo,
|
playerInfo,
|
||||||
|
|||||||
@@ -1,34 +1,23 @@
|
|||||||
import { html, LitElement } from "lit";
|
import { html, LitElement } from "lit";
|
||||||
import { customElement, query, state } from "lit/decorators.js";
|
import { customElement, query, state } from "lit/decorators.js";
|
||||||
import cityIcon from "../../../../resources/images/CityIconWhite.svg";
|
|
||||||
import darkModeIcon from "../../../../resources/images/DarkModeIconWhite.svg";
|
import darkModeIcon from "../../../../resources/images/DarkModeIconWhite.svg";
|
||||||
import emojiIcon from "../../../../resources/images/EmojiIconWhite.svg";
|
import emojiIcon from "../../../../resources/images/EmojiIconWhite.svg";
|
||||||
import exitIcon from "../../../../resources/images/ExitIconWhite.svg";
|
import exitIcon from "../../../../resources/images/ExitIconWhite.svg";
|
||||||
import explosionIcon from "../../../../resources/images/ExplosionIconWhite.svg";
|
import explosionIcon from "../../../../resources/images/ExplosionIconWhite.svg";
|
||||||
import factoryIcon from "../../../../resources/images/FactoryIconWhite.svg";
|
|
||||||
import goldCoinIcon from "../../../../resources/images/GoldCoinIcon.svg";
|
import goldCoinIcon from "../../../../resources/images/GoldCoinIcon.svg";
|
||||||
import missileSiloIcon from "../../../../resources/images/MissileSiloUnit.png";
|
|
||||||
import mouseIcon from "../../../../resources/images/MouseIconWhite.svg";
|
import mouseIcon from "../../../../resources/images/MouseIconWhite.svg";
|
||||||
import ninjaIcon from "../../../../resources/images/NinjaIconWhite.svg";
|
import ninjaIcon from "../../../../resources/images/NinjaIconWhite.svg";
|
||||||
import populationIcon from "../../../../resources/images/PopulationIconSolidWhite.svg";
|
import populationIcon from "../../../../resources/images/PopulationIconSolidWhite.svg";
|
||||||
import portIcon from "../../../../resources/images/PortIcon.svg";
|
|
||||||
import samLauncherIcon from "../../../../resources/images/SamLauncherUnitWhite.png";
|
|
||||||
import settingsIcon from "../../../../resources/images/SettingIconWhite.svg";
|
import settingsIcon from "../../../../resources/images/SettingIconWhite.svg";
|
||||||
import defensePostIcon from "../../../../resources/images/ShieldIconWhite.svg";
|
|
||||||
import treeIcon from "../../../../resources/images/TreeIconWhite.svg";
|
import treeIcon from "../../../../resources/images/TreeIconWhite.svg";
|
||||||
import troopIcon from "../../../../resources/images/TroopIconWhite.svg";
|
import troopIcon from "../../../../resources/images/TroopIconWhite.svg";
|
||||||
import workerIcon from "../../../../resources/images/WorkerIconWhite.svg";
|
import workerIcon from "../../../../resources/images/WorkerIconWhite.svg";
|
||||||
import { translateText } from "../../../client/Utils";
|
import { translateText } from "../../../client/Utils";
|
||||||
import { EventBus } from "../../../core/EventBus";
|
import { EventBus } from "../../../core/EventBus";
|
||||||
import { UnitType } from "../../../core/game/Game";
|
|
||||||
import { GameUpdateType } from "../../../core/game/GameUpdates";
|
import { GameUpdateType } from "../../../core/game/GameUpdates";
|
||||||
import { GameView } from "../../../core/game/GameView";
|
import { GameView } from "../../../core/game/GameView";
|
||||||
import { UserSettings } from "../../../core/game/UserSettings";
|
import { UserSettings } from "../../../core/game/UserSettings";
|
||||||
import {
|
import { AlternateViewEvent, RefreshGraphicsEvent } from "../../InputHandler";
|
||||||
AlternateViewEvent,
|
|
||||||
RefreshGraphicsEvent,
|
|
||||||
ToggleStructureEvent,
|
|
||||||
} from "../../InputHandler";
|
|
||||||
import { renderNumber, renderTroops } from "../../Utils";
|
import { renderNumber, renderTroops } from "../../Utils";
|
||||||
import { Layer } from "./Layer";
|
import { Layer } from "./Layer";
|
||||||
|
|
||||||
@@ -37,16 +26,9 @@ export class GameTopBar extends LitElement implements Layer {
|
|||||||
public game: GameView;
|
public game: GameView;
|
||||||
public eventBus: EventBus;
|
public eventBus: EventBus;
|
||||||
private _userSettings: UserSettings = new UserSettings();
|
private _userSettings: UserSettings = new UserSettings();
|
||||||
private _selectedStructure: UnitType | null = null;
|
|
||||||
private _population = 0;
|
private _population = 0;
|
||||||
private _troops = 0;
|
private _troops = 0;
|
||||||
private _cities = 0;
|
|
||||||
private _factories = 0;
|
|
||||||
private _workers = 0;
|
private _workers = 0;
|
||||||
private _missileSilo = 0;
|
|
||||||
private _port = 0;
|
|
||||||
private _defensePost = 0;
|
|
||||||
private _samLauncher = 0;
|
|
||||||
private _lastPopulationIncreaseRate = 0;
|
private _lastPopulationIncreaseRate = 0;
|
||||||
private _popRateIsIncreasing = false;
|
private _popRateIsIncreasing = false;
|
||||||
private hasWinner = false;
|
private hasWinner = false;
|
||||||
@@ -76,12 +58,6 @@ export class GameTopBar extends LitElement implements Layer {
|
|||||||
if (!player) return;
|
if (!player) return;
|
||||||
this._troops = player.troops();
|
this._troops = player.troops();
|
||||||
this._workers = player.workers();
|
this._workers = player.workers();
|
||||||
this._cities = player.totalUnitLevels(UnitType.City);
|
|
||||||
this._missileSilo = player.totalUnitLevels(UnitType.MissileSilo);
|
|
||||||
this._port = player.totalUnitLevels(UnitType.Port);
|
|
||||||
this._defensePost = player.totalUnitLevels(UnitType.DefensePost);
|
|
||||||
this._samLauncher = player.totalUnitLevels(UnitType.SAMLauncher);
|
|
||||||
this._factories = player.totalUnitLevels(UnitType.Factory);
|
|
||||||
const updates = this.game.updatesSinceLastTick();
|
const updates = this.game.updatesSinceLastTick();
|
||||||
if (updates) {
|
if (updates) {
|
||||||
this.hasWinner = this.hasWinner || updates[GameUpdateType.Win].length > 0;
|
this.hasWinner = this.hasWinner || updates[GameUpdateType.Win].length > 0;
|
||||||
@@ -145,12 +121,6 @@ export class GameTopBar extends LitElement implements Layer {
|
|||||||
this.eventBus.emit(new RefreshGraphicsEvent());
|
this.eventBus.emit(new RefreshGraphicsEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
private onToggleStructureClick(structureType: UnitType) {
|
|
||||||
this._selectedStructure =
|
|
||||||
this._selectedStructure === structureType ? null : structureType;
|
|
||||||
this.eventBus.emit(new ToggleStructureEvent(this._selectedStructure));
|
|
||||||
}
|
|
||||||
|
|
||||||
private onToggleRandomNameModeButtonClick() {
|
private onToggleRandomNameModeButtonClick() {
|
||||||
this._userSettings.toggleRandomName();
|
this._userSettings.toggleRandomName();
|
||||||
}
|
}
|
||||||
@@ -222,7 +192,7 @@ export class GameTopBar extends LitElement implements Layer {
|
|||||||
? html`
|
? html`
|
||||||
<div class="overflow-x-auto hide-scrollbar flex-1 max-w-[85vw]">
|
<div class="overflow-x-auto hide-scrollbar flex-1 max-w-[85vw]">
|
||||||
<div
|
<div
|
||||||
class="grid gap-1 grid-cols-[80px_100px_80px_minmax(80px,auto)] w-max md:gap-2 md:grid-cols-[90px_120px_90px_minmax(100px,auto)]"
|
class="grid gap-1 grid-cols-[80px_100px_80px] w-max md:gap-2 md:grid-cols-[90px_120px_90px]"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="flex flex-wrap gap-1 flex-col bg-slate-800/20 border border-slate-400 p-0.5 md:px-1 lg:px-2"
|
class="flex flex-wrap gap-1 flex-col bg-slate-800/20 border border-slate-400 p-0.5 md:px-1 lg:px-2"
|
||||||
@@ -290,130 +260,6 @@ export class GameTopBar extends LitElement implements Layer {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
class="grid grid-rows-1 auto-cols-max grid-flow-col bg-slate-800/20 border border-slate-400 p-0.5 md:px-1 lg:px-2"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="md:px-2 px-1 flex items-center gap-2"
|
|
||||||
style="background: ${this._selectedStructure ===
|
|
||||||
UnitType.City
|
|
||||||
? "#ffffff2e"
|
|
||||||
: "none"}"
|
|
||||||
@mouseenter="${() =>
|
|
||||||
this.onToggleStructureClick(UnitType.City)}"
|
|
||||||
@mouseleave="${() =>
|
|
||||||
this.onToggleStructureClick(UnitType.City)}"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src=${cityIcon}
|
|
||||||
alt="gold"
|
|
||||||
width="20"
|
|
||||||
height="20"
|
|
||||||
style="vertical-align: middle;"
|
|
||||||
/>
|
|
||||||
${renderNumber(this._cities)}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="md:px-2 px-1 flex items-center gap-2"
|
|
||||||
style="background: ${this._selectedStructure ===
|
|
||||||
UnitType.Factory
|
|
||||||
? "#ffffff2e"
|
|
||||||
: "none"}"
|
|
||||||
@mouseenter="${() =>
|
|
||||||
this.onToggleStructureClick(UnitType.Factory)}"
|
|
||||||
@mouseleave="${() =>
|
|
||||||
this.onToggleStructureClick(UnitType.Factory)}"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src=${factoryIcon}
|
|
||||||
alt="gold"
|
|
||||||
width="20"
|
|
||||||
height="20"
|
|
||||||
style="vertical-align: middle;"
|
|
||||||
/>
|
|
||||||
${renderNumber(this._factories)}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="md:px-2 px-1 flex items-center gap-2"
|
|
||||||
style="background: ${this._selectedStructure ===
|
|
||||||
UnitType.Port
|
|
||||||
? "#ffffff2e"
|
|
||||||
: "none"}"
|
|
||||||
@mouseenter="${() =>
|
|
||||||
this.onToggleStructureClick(UnitType.Port)}"
|
|
||||||
@mouseleave="${() =>
|
|
||||||
this.onToggleStructureClick(UnitType.Port)}"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src=${portIcon}
|
|
||||||
alt="gold"
|
|
||||||
width="20"
|
|
||||||
height="20"
|
|
||||||
style="vertical-align: middle;"
|
|
||||||
/>
|
|
||||||
${renderNumber(this._port)}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="md:px-2 px-1 flex items-center gap-2"
|
|
||||||
style="background: ${this._selectedStructure ===
|
|
||||||
UnitType.DefensePost
|
|
||||||
? "#ffffff2e"
|
|
||||||
: "none"}"
|
|
||||||
@mouseenter="${() =>
|
|
||||||
this.onToggleStructureClick(UnitType.DefensePost)}"
|
|
||||||
@mouseleave="${() =>
|
|
||||||
this.onToggleStructureClick(UnitType.DefensePost)}"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src=${defensePostIcon}
|
|
||||||
alt="gold"
|
|
||||||
width="20"
|
|
||||||
height="20"
|
|
||||||
style="vertical-align: middle;"
|
|
||||||
/>
|
|
||||||
${renderNumber(this._defensePost)}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="md:px-2 px-1 flex items-center gap-2"
|
|
||||||
style="background: ${this._selectedStructure ===
|
|
||||||
UnitType.MissileSilo
|
|
||||||
? "#ffffff2e"
|
|
||||||
: "none"}"
|
|
||||||
@mouseenter="${() =>
|
|
||||||
this.onToggleStructureClick(UnitType.MissileSilo)}"
|
|
||||||
@mouseleave="${() =>
|
|
||||||
this.onToggleStructureClick(UnitType.MissileSilo)}"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src=${missileSiloIcon}
|
|
||||||
alt="gold"
|
|
||||||
width="20"
|
|
||||||
height="20"
|
|
||||||
style="vertical-align: middle;"
|
|
||||||
/>
|
|
||||||
${renderNumber(this._missileSilo)}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="md:px-2 px-1 flex items-center gap-2"
|
|
||||||
style="background: ${this._selectedStructure ===
|
|
||||||
UnitType.SAMLauncher
|
|
||||||
? "#ffffff2e"
|
|
||||||
: "none"}"
|
|
||||||
@mouseenter="${() =>
|
|
||||||
this.onToggleStructureClick(UnitType.SAMLauncher)}"
|
|
||||||
@mouseleave="${() =>
|
|
||||||
this.onToggleStructureClick(UnitType.SAMLauncher)}"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src=${samLauncherIcon}
|
|
||||||
alt="gold"
|
|
||||||
width="20"
|
|
||||||
height="20"
|
|
||||||
style="vertical-align: middle;"
|
|
||||||
/>
|
|
||||||
${renderNumber(this._samLauncher)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
import { html, LitElement } from "lit";
|
||||||
|
import { customElement } from "lit/decorators.js";
|
||||||
|
import cityIcon from "../../../../resources/images/CityIconWhite.svg";
|
||||||
|
import factoryIcon from "../../../../resources/images/FactoryIconWhite.svg";
|
||||||
|
import missileSiloIcon from "../../../../resources/images/MissileSiloUnit.png";
|
||||||
|
import portIcon from "../../../../resources/images/PortIcon.svg";
|
||||||
|
import samLauncherIcon from "../../../../resources/images/SamLauncherUnitWhite.png";
|
||||||
|
import defensePostIcon from "../../../../resources/images/ShieldIconWhite.svg";
|
||||||
|
import { EventBus } from "../../../core/EventBus";
|
||||||
|
import { UnitType } from "../../../core/game/Game";
|
||||||
|
import { GameView } from "../../../core/game/GameView";
|
||||||
|
import { ToggleStructureEvent } from "../../InputHandler";
|
||||||
|
import { renderNumber } from "../../Utils";
|
||||||
|
import { Layer } from "./Layer";
|
||||||
|
|
||||||
|
@customElement("unit-display")
|
||||||
|
export class UnitDisplay extends LitElement implements Layer {
|
||||||
|
public game: GameView;
|
||||||
|
public eventBus: EventBus;
|
||||||
|
private _selectedStructure: UnitType | null = null;
|
||||||
|
private _cities = 0;
|
||||||
|
private _factories = 0;
|
||||||
|
private _missileSilo = 0;
|
||||||
|
private _port = 0;
|
||||||
|
private _defensePost = 0;
|
||||||
|
private _samLauncher = 0;
|
||||||
|
|
||||||
|
createRenderRoot() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
tick() {
|
||||||
|
const player = this.game?.myPlayer();
|
||||||
|
if (!player) return;
|
||||||
|
this._cities = player.totalUnitLevels(UnitType.City);
|
||||||
|
this._missileSilo = player.totalUnitLevels(UnitType.MissileSilo);
|
||||||
|
this._port = player.totalUnitLevels(UnitType.Port);
|
||||||
|
this._defensePost = player.totalUnitLevels(UnitType.DefensePost);
|
||||||
|
this._samLauncher = player.totalUnitLevels(UnitType.SAMLauncher);
|
||||||
|
this._factories = player.totalUnitLevels(UnitType.Factory);
|
||||||
|
this.requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderUnitItem(
|
||||||
|
icon: string,
|
||||||
|
number: number,
|
||||||
|
unitType: UnitType,
|
||||||
|
altText: string,
|
||||||
|
) {
|
||||||
|
if (this.game.config().isUnitDisabled(unitType)) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
class="px-2 flex items-center gap-2 cursor-pointer hover:bg-slate-700/50 rounded text-white"
|
||||||
|
style="background: ${this._selectedStructure === unitType
|
||||||
|
? "#ffffff2e"
|
||||||
|
: "none"}"
|
||||||
|
@mouseenter="${() =>
|
||||||
|
this.eventBus.emit(new ToggleStructureEvent(unitType))}"
|
||||||
|
@mouseleave="${() =>
|
||||||
|
this.eventBus.emit(new ToggleStructureEvent(null))}"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src=${icon}
|
||||||
|
alt=${altText}
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
style="vertical-align: middle;"
|
||||||
|
/>
|
||||||
|
${renderNumber(number)}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const myPlayer = this.game?.myPlayer();
|
||||||
|
if (
|
||||||
|
!this.game ||
|
||||||
|
!myPlayer ||
|
||||||
|
this.game.inSpawnPhase() ||
|
||||||
|
!myPlayer.isAlive()
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
class="fixed bottom-4 left-1/2 transform -translate-x-1/2 z-[1100] bg-slate-800/40 backdrop-blur-sm border border-slate-400 rounded-lg p-2 hidden lg:block"
|
||||||
|
>
|
||||||
|
<div class="grid grid-rows-1 auto-cols-max grid-flow-col gap-1">
|
||||||
|
${this.renderUnitItem(cityIcon, this._cities, UnitType.City, "city")}
|
||||||
|
${this.renderUnitItem(
|
||||||
|
factoryIcon,
|
||||||
|
this._factories,
|
||||||
|
UnitType.Factory,
|
||||||
|
"factory",
|
||||||
|
)}
|
||||||
|
${this.renderUnitItem(portIcon, this._port, UnitType.Port, "port")}
|
||||||
|
${this.renderUnitItem(
|
||||||
|
defensePostIcon,
|
||||||
|
this._defensePost,
|
||||||
|
UnitType.DefensePost,
|
||||||
|
"defense post",
|
||||||
|
)}
|
||||||
|
${this.renderUnitItem(
|
||||||
|
missileSiloIcon,
|
||||||
|
this._missileSilo,
|
||||||
|
UnitType.MissileSilo,
|
||||||
|
"missile silo",
|
||||||
|
)}
|
||||||
|
${this.renderUnitItem(
|
||||||
|
samLauncherIcon,
|
||||||
|
this._samLauncher,
|
||||||
|
UnitType.SAMLauncher,
|
||||||
|
"SAM launcher",
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -360,6 +360,7 @@
|
|||||||
<win-modal></win-modal>
|
<win-modal></win-modal>
|
||||||
<game-starting-modal></game-starting-modal>
|
<game-starting-modal></game-starting-modal>
|
||||||
<game-top-bar></game-top-bar>
|
<game-top-bar></game-top-bar>
|
||||||
|
<unit-display></unit-display>
|
||||||
<game-right-sidebar></game-right-sidebar>
|
<game-right-sidebar></game-right-sidebar>
|
||||||
|
|
||||||
<player-panel></player-panel>
|
<player-panel></player-panel>
|
||||||
|
|||||||
Reference in New Issue
Block a user