mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 07:40:43 +00:00
leaderboard
This commit is contained in:
+10
-6
@@ -327,11 +327,18 @@
|
||||
<build-menu></build-menu>
|
||||
<win-modal></win-modal>
|
||||
<game-starting-modal></game-starting-modal>
|
||||
<!-- Top HUD: mirrors bottom HUD grid layout -->
|
||||
<div
|
||||
class="flex flex-col items-end fixed top-0 right-0 min-[1200px]:top-4 min-[1200px]:right-4 z-1000 gap-2"
|
||||
class="fixed top-0 left-0 w-full z-[1000] flex flex-col pointer-events-none lg:grid lg:grid-cols-[1fr_500px_1fr] lg:items-start min-[1200px]:px-4"
|
||||
style="
|
||||
padding-top: env(safe-area-inset-top);
|
||||
padding-left: env(safe-area-inset-left);
|
||||
padding-right: env(safe-area-inset-right);
|
||||
"
|
||||
>
|
||||
<game-right-sidebar></game-right-sidebar>
|
||||
<replay-panel></replay-panel>
|
||||
<div class="lg:col-start-3 flex flex-col items-end pointer-events-auto">
|
||||
<game-menu></game-menu>
|
||||
</div>
|
||||
</div>
|
||||
<settings-modal></settings-modal>
|
||||
<player-panel></player-panel>
|
||||
@@ -342,11 +349,8 @@
|
||||
<alert-frame></alert-frame>
|
||||
<chat-modal></chat-modal>
|
||||
<multi-tab-modal></multi-tab-modal>
|
||||
<game-left-sidebar></game-left-sidebar>
|
||||
<performance-overlay></performance-overlay>
|
||||
<player-info-overlay></player-info-overlay>
|
||||
<leader-board></leader-board>
|
||||
<team-stats></team-stats>
|
||||
<heads-up-message></heads-up-message>
|
||||
|
||||
<!-- Scripts -->
|
||||
|
||||
@@ -18,13 +18,11 @@ import { DynamicUILayer } from "./layers/DynamicUILayer";
|
||||
import { EmojiTable } from "./layers/EmojiTable";
|
||||
import { EventsDisplay } from "./layers/EventsDisplay";
|
||||
import { FxLayer } from "./layers/FxLayer";
|
||||
import { GameLeftSidebar } from "./layers/GameLeftSidebar";
|
||||
import { GameRightSidebar } from "./layers/GameRightSidebar";
|
||||
import { GameMenu } from "./layers/GameMenu";
|
||||
import { HeadsUpMessage } from "./layers/HeadsUpMessage";
|
||||
import { ImmunityTimer } from "./layers/ImmunityTimer";
|
||||
import { InGamePromo } from "./layers/InGamePromo";
|
||||
import { Layer } from "./layers/Layer";
|
||||
import { Leaderboard } from "./layers/Leaderboard";
|
||||
import { MainRadialMenu } from "./layers/MainRadialMenu";
|
||||
import { MultiTabModal } from "./layers/MultiTabModal";
|
||||
import { NameLayer } from "./layers/NameLayer";
|
||||
@@ -33,13 +31,11 @@ import { PerformanceOverlay } from "./layers/PerformanceOverlay";
|
||||
import { PlayerInfoOverlay } from "./layers/PlayerInfoOverlay";
|
||||
import { PlayerPanel } from "./layers/PlayerPanel";
|
||||
import { RailroadLayer } from "./layers/RailroadLayer";
|
||||
import { ReplayPanel } from "./layers/ReplayPanel";
|
||||
import { SAMRadiusLayer } from "./layers/SAMRadiusLayer";
|
||||
import { SettingsModal } from "./layers/SettingsModal";
|
||||
import { SpawnTimer } from "./layers/SpawnTimer";
|
||||
import { StructureIconsLayer } from "./layers/StructureIconsLayer";
|
||||
import { StructureLayer } from "./layers/StructureLayer";
|
||||
import { TeamStats } from "./layers/TeamStats";
|
||||
import { TerrainLayer } from "./layers/TerrainLayer";
|
||||
import { TerritoryLayer } from "./layers/TerritoryLayer";
|
||||
import { UILayer } from "./layers/UILayer";
|
||||
@@ -88,28 +84,12 @@ export function createRenderer(
|
||||
buildMenu.uiState = uiState;
|
||||
buildMenu.transformHandler = transformHandler;
|
||||
|
||||
const leaderboard = document.querySelector("leader-board") as Leaderboard;
|
||||
if (!leaderboard || !(leaderboard instanceof Leaderboard)) {
|
||||
console.error("LeaderBoard element not found in the DOM");
|
||||
const gameMenu = document.querySelector("game-menu") as GameMenu;
|
||||
if (!gameMenu || !(gameMenu instanceof GameMenu)) {
|
||||
console.error("GameMenu element not found in the DOM");
|
||||
}
|
||||
leaderboard.eventBus = eventBus;
|
||||
leaderboard.game = game;
|
||||
|
||||
const gameLeftSidebar = document.querySelector(
|
||||
"game-left-sidebar",
|
||||
) as GameLeftSidebar;
|
||||
if (!gameLeftSidebar || !(gameLeftSidebar instanceof GameLeftSidebar)) {
|
||||
console.error("GameLeftSidebar element not found in the DOM");
|
||||
}
|
||||
gameLeftSidebar.game = game;
|
||||
gameLeftSidebar.eventBus = eventBus;
|
||||
|
||||
const teamStats = document.querySelector("team-stats") as TeamStats;
|
||||
if (!teamStats || !(teamStats instanceof TeamStats)) {
|
||||
console.error("TeamStats element not found in the DOM");
|
||||
}
|
||||
teamStats.eventBus = eventBus;
|
||||
teamStats.game = game;
|
||||
gameMenu.game = game;
|
||||
gameMenu.eventBus = eventBus;
|
||||
|
||||
const controlPanel = document.querySelector("control-panel") as ControlPanel;
|
||||
if (!(controlPanel instanceof ControlPanel)) {
|
||||
@@ -163,22 +143,6 @@ export function createRenderer(
|
||||
winModal.eventBus = eventBus;
|
||||
winModal.game = game;
|
||||
|
||||
const replayPanel = document.querySelector("replay-panel") as ReplayPanel;
|
||||
if (!(replayPanel instanceof ReplayPanel)) {
|
||||
console.error("replay panel not found");
|
||||
}
|
||||
replayPanel.eventBus = eventBus;
|
||||
replayPanel.game = game;
|
||||
|
||||
const gameRightSidebar = document.querySelector(
|
||||
"game-right-sidebar",
|
||||
) as GameRightSidebar;
|
||||
if (!(gameRightSidebar instanceof GameRightSidebar)) {
|
||||
console.error("Game Right bar not found");
|
||||
}
|
||||
gameRightSidebar.game = game;
|
||||
gameRightSidebar.eventBus = eventBus;
|
||||
|
||||
const settingsModal = document.querySelector(
|
||||
"settings-modal",
|
||||
) as SettingsModal;
|
||||
@@ -304,16 +268,12 @@ export function createRenderer(
|
||||
),
|
||||
spawnTimer,
|
||||
immunityTimer,
|
||||
leaderboard,
|
||||
gameLeftSidebar,
|
||||
gameMenu,
|
||||
unitDisplay,
|
||||
gameRightSidebar,
|
||||
controlPanel,
|
||||
playerInfo,
|
||||
winModal,
|
||||
replayPanel,
|
||||
settingsModal,
|
||||
teamStats,
|
||||
playerPanel,
|
||||
headsUpMessage,
|
||||
multiTabModal,
|
||||
|
||||
@@ -1,205 +0,0 @@
|
||||
import { Colord } from "colord";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import { assetUrl } from "../../../core/AssetUrls";
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
import { GameMode, Team } from "../../../core/game/Game";
|
||||
import { GameView } from "../../../core/game/GameView";
|
||||
import { Platform } from "../../Platform";
|
||||
import { getTranslatedPlayerTeamLabel, translateText } from "../../Utils";
|
||||
import { ImmunityBarVisibleEvent } from "./ImmunityTimer";
|
||||
import { Layer } from "./Layer";
|
||||
import { SpawnBarVisibleEvent } from "./SpawnTimer";
|
||||
const leaderboardRegularIcon = assetUrl(
|
||||
"images/LeaderboardIconRegularWhite.svg",
|
||||
);
|
||||
const leaderboardSolidIcon = assetUrl("images/LeaderboardIconSolidWhite.svg");
|
||||
const teamRegularIcon = assetUrl("images/TeamIconRegularWhite.svg");
|
||||
const teamSolidIcon = assetUrl("images/TeamIconSolidWhite.svg");
|
||||
|
||||
@customElement("game-left-sidebar")
|
||||
export class GameLeftSidebar extends LitElement implements Layer {
|
||||
@state()
|
||||
private isLeaderboardShow = false;
|
||||
@state()
|
||||
private isTeamLeaderboardShow = false;
|
||||
@state()
|
||||
private isVisible = false;
|
||||
@state()
|
||||
private isPlayerTeamLabelVisible = false;
|
||||
@state()
|
||||
private playerTeam: Team | null = null;
|
||||
@state()
|
||||
private spawnBarVisible = false;
|
||||
@state()
|
||||
private immunityBarVisible = false;
|
||||
|
||||
private playerColor: Colord = new Colord("#FFFFFF");
|
||||
public game: GameView;
|
||||
public eventBus: EventBus;
|
||||
private _shownOnInit = false;
|
||||
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
|
||||
init() {
|
||||
this.isVisible = true;
|
||||
this.eventBus.on(SpawnBarVisibleEvent, (e) => {
|
||||
this.spawnBarVisible = e.visible;
|
||||
});
|
||||
this.eventBus.on(ImmunityBarVisibleEvent, (e) => {
|
||||
this.immunityBarVisible = e.visible;
|
||||
});
|
||||
if (this.isTeamGame) {
|
||||
this.isPlayerTeamLabelVisible = true;
|
||||
}
|
||||
// Make it visible by default on large screens
|
||||
if (Platform.isDesktopWidth) {
|
||||
// lg breakpoint
|
||||
this._shownOnInit = true;
|
||||
}
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
tick() {
|
||||
if (!this.playerTeam && this.game.myPlayer()?.team()) {
|
||||
this.playerTeam = this.game.myPlayer()!.team();
|
||||
if (this.playerTeam) {
|
||||
this.playerColor = this.game
|
||||
.config()
|
||||
.theme()
|
||||
.teamColor(this.playerTeam);
|
||||
this.requestUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
if (this._shownOnInit && !this.game.inSpawnPhase()) {
|
||||
this._shownOnInit = false;
|
||||
this.isLeaderboardShow = true;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
if (!this.game.inSpawnPhase() && this.isPlayerTeamLabelVisible) {
|
||||
this.isPlayerTeamLabelVisible = false;
|
||||
this.requestUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
private get barOffset(): number {
|
||||
return (this.spawnBarVisible ? 7 : 0) + (this.immunityBarVisible ? 7 : 0);
|
||||
}
|
||||
|
||||
private toggleLeaderboard(): void {
|
||||
this.isLeaderboardShow = !this.isLeaderboardShow;
|
||||
}
|
||||
|
||||
private toggleTeamLeaderboard(): void {
|
||||
this.isTeamLeaderboardShow = !this.isTeamLeaderboardShow;
|
||||
}
|
||||
|
||||
private get isTeamGame(): boolean {
|
||||
return this.game?.config().gameConfig().gameMode === GameMode.Team;
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<aside
|
||||
class=${`fixed top-0 min-[1200px]:top-4 left-0 min-[1200px]:left-4 z-900 flex flex-col max-h-[calc(100vh-80px)] overflow-y-auto p-2 bg-gray-800/92 backdrop-blur-sm shadow-xs min-[1200px]:rounded-lg rounded-br-lg ${this.isLeaderboardShow || this.isTeamLeaderboardShow ? "max-[400px]:w-full max-[400px]:rounded-none" : ""} transition-all duration-300 ease-out transform ${
|
||||
this.isVisible ? "translate-x-0" : "hidden"
|
||||
}`}
|
||||
style="margin-top: ${this.barOffset}px;"
|
||||
>
|
||||
<div class="flex items-center gap-4 xl:gap-6 text-white">
|
||||
<div
|
||||
class="cursor-pointer p-0.5 bg-gray-700/50 hover:bg-gray-600 border rounded-md border-slate-500 transition-colors"
|
||||
@click=${this.toggleLeaderboard}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@keydown=${(e: KeyboardEvent) => {
|
||||
if (e.key === "Enter" || e.key === " " || e.code === "Space") {
|
||||
e.preventDefault();
|
||||
this.toggleLeaderboard();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src=${this.isLeaderboardShow
|
||||
? leaderboardSolidIcon
|
||||
: leaderboardRegularIcon}
|
||||
alt=${translateText("help_modal.icon_alt_player_leaderboard") ||
|
||||
"Player Leaderboard Icon"}
|
||||
width="20"
|
||||
height="20"
|
||||
/>
|
||||
</div>
|
||||
${this.isTeamGame
|
||||
? html`
|
||||
<div
|
||||
class="cursor-pointer p-0.5 bg-gray-700/50 hover:bg-gray-600 border rounded-md border-slate-500 transition-colors"
|
||||
@click=${this.toggleTeamLeaderboard}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@keydown=${(e: KeyboardEvent) => {
|
||||
if (
|
||||
e.key === "Enter" ||
|
||||
e.key === " " ||
|
||||
e.code === "Space"
|
||||
) {
|
||||
e.preventDefault();
|
||||
this.toggleTeamLeaderboard();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src=${this.isTeamLeaderboardShow
|
||||
? teamSolidIcon
|
||||
: teamRegularIcon}
|
||||
alt=${translateText(
|
||||
"help_modal.icon_alt_team_leaderboard",
|
||||
) || "Team Leaderboard Icon"}
|
||||
width="20"
|
||||
height="20"
|
||||
/>
|
||||
</div>
|
||||
`
|
||||
: null}
|
||||
${this.isLeaderboardShow || this.isTeamLeaderboardShow
|
||||
? html`<span
|
||||
class="ml-auto text-[10px] text-slate-500 select-all leading-none self-start"
|
||||
title=${translateText("help_modal.game_id_tooltip")}
|
||||
>${this.game?.gameID() ?? ""}</span
|
||||
>`
|
||||
: null}
|
||||
</div>
|
||||
${this.isPlayerTeamLabelVisible
|
||||
? html`
|
||||
<div
|
||||
class="flex items-center w-full text-white mt-2"
|
||||
@contextmenu=${(e: Event) => e.preventDefault()}
|
||||
>
|
||||
${translateText("help_modal.ui_your_team")}
|
||||
<span
|
||||
style="--color: ${this.playerColor.toRgbString()}"
|
||||
class="text-(--color)"
|
||||
>
|
||||
${getTranslatedPlayerTeamLabel(this.playerTeam)}
|
||||
⦿
|
||||
</span>
|
||||
</div>
|
||||
`
|
||||
: null}
|
||||
<div
|
||||
class=${`block lg:flex flex-wrap overflow-x-auto min-w-0 w-full ${this.isLeaderboardShow && this.isTeamLeaderboardShow ? "gap-2" : ""}`}
|
||||
>
|
||||
<leader-board .visible=${this.isLeaderboardShow}></leader-board>
|
||||
<team-stats
|
||||
class="flex-1"
|
||||
.visible=${this.isTeamLeaderboardShow && this.isTeamGame}
|
||||
></team-stats>
|
||||
</div>
|
||||
<slot></slot>
|
||||
</aside>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,415 @@
|
||||
import { Colord } from "colord";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { assetUrl } from "../../../core/AssetUrls";
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
import { GameMode, GameType, Team } from "../../../core/game/Game";
|
||||
import { GameView } from "../../../core/game/GameView";
|
||||
import { crazyGamesSDK } from "../../CrazyGamesSDK";
|
||||
import { Platform } from "../../Platform";
|
||||
import { PauseGameIntentEvent, SendWinnerEvent } from "../../Transport";
|
||||
import { getTranslatedPlayerTeamLabel, translateText } from "../../Utils";
|
||||
import { ImmunityBarVisibleEvent } from "./ImmunityTimer";
|
||||
import { Layer } from "./Layer";
|
||||
import { Leaderboard } from "./Leaderboard";
|
||||
import { ReplayPanel, ShowReplayPanelEvent } from "./ReplayPanel";
|
||||
import { ShowSettingsModalEvent } from "./SettingsModal";
|
||||
import { SpawnBarVisibleEvent } from "./SpawnTimer";
|
||||
import { TeamStats } from "./TeamStats";
|
||||
|
||||
const exitIcon = assetUrl("images/ExitIconWhite.svg");
|
||||
const FastForwardIconSolid = assetUrl("images/FastForwardIconSolidWhite.svg");
|
||||
const leaderboardRegularIcon = assetUrl(
|
||||
"images/LeaderboardIconRegularWhite.svg",
|
||||
);
|
||||
const leaderboardSolidIcon = assetUrl("images/LeaderboardIconSolidWhite.svg");
|
||||
const pauseIcon = assetUrl("images/PauseIconWhite.svg");
|
||||
const playIcon = assetUrl("images/PlayIconWhite.svg");
|
||||
const settingsIcon = assetUrl("images/SettingIconWhite.svg");
|
||||
const teamRegularIcon = assetUrl("images/TeamIconRegularWhite.svg");
|
||||
const teamSolidIcon = assetUrl("images/TeamIconSolidWhite.svg");
|
||||
|
||||
@customElement("game-menu")
|
||||
export class GameMenu extends LitElement implements Layer {
|
||||
@property({ attribute: false }) game: GameView;
|
||||
public eventBus: EventBus;
|
||||
|
||||
@state()
|
||||
private _isSinglePlayer: boolean = false;
|
||||
|
||||
@state()
|
||||
private _isReplayVisible: boolean = false;
|
||||
|
||||
@state()
|
||||
private _isVisible: boolean = true;
|
||||
|
||||
@state()
|
||||
private isPaused: boolean = false;
|
||||
|
||||
@state()
|
||||
private timer: number = 0;
|
||||
|
||||
@state()
|
||||
private isLeaderboardShow = false;
|
||||
|
||||
@state()
|
||||
private isTeamLeaderboardShow = false;
|
||||
|
||||
@state()
|
||||
private isPlayerTeamLabelVisible = false;
|
||||
|
||||
@state()
|
||||
private playerTeam: Team | null = null;
|
||||
|
||||
private get leaderboard(): Leaderboard | null {
|
||||
const el = this.querySelector("leader-board");
|
||||
return el instanceof Leaderboard ? el : null;
|
||||
}
|
||||
private get teamStats(): TeamStats | null {
|
||||
const el = this.querySelector("team-stats");
|
||||
return el instanceof TeamStats ? el : null;
|
||||
}
|
||||
private get replayPanel(): ReplayPanel | null {
|
||||
const el = this.querySelector("replay-panel");
|
||||
return el instanceof ReplayPanel ? el : null;
|
||||
}
|
||||
|
||||
private playerColor: Colord = new Colord("#FFFFFF");
|
||||
private hasWinner = false;
|
||||
private isLobbyCreator = false;
|
||||
private spawnBarVisible = false;
|
||||
private immunityBarVisible = false;
|
||||
private _shownOnInit = false;
|
||||
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
|
||||
init() {
|
||||
this._isSinglePlayer =
|
||||
this.game?.config()?.gameConfig()?.gameType === GameType.Singleplayer ||
|
||||
this.game.config().isReplay();
|
||||
this._isVisible = true;
|
||||
|
||||
this.eventBus.on(SpawnBarVisibleEvent, (e) => {
|
||||
this.spawnBarVisible = e.visible;
|
||||
this.updateParentOffset();
|
||||
});
|
||||
this.eventBus.on(ImmunityBarVisibleEvent, (e) => {
|
||||
this.immunityBarVisible = e.visible;
|
||||
this.updateParentOffset();
|
||||
});
|
||||
|
||||
this.eventBus.on(SendWinnerEvent, () => {
|
||||
this.hasWinner = true;
|
||||
this.requestUpdate();
|
||||
});
|
||||
|
||||
if (this.isTeamGame) {
|
||||
this.isPlayerTeamLabelVisible = true;
|
||||
}
|
||||
|
||||
if (Platform.isDesktopWidth) {
|
||||
this._shownOnInit = true;
|
||||
}
|
||||
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
getTickIntervalMs() {
|
||||
return 250;
|
||||
}
|
||||
|
||||
tick() {
|
||||
// Check if the player is the lobby creator
|
||||
if (!this.isLobbyCreator && this.game.myPlayer()?.isLobbyCreator()) {
|
||||
this.isLobbyCreator = true;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
// Team color
|
||||
if (!this.playerTeam && this.game.myPlayer()?.team()) {
|
||||
this.playerTeam = this.game.myPlayer()!.team();
|
||||
if (this.playerTeam) {
|
||||
this.playerColor = this.game
|
||||
.config()
|
||||
.theme()
|
||||
.teamColor(this.playerTeam);
|
||||
this.requestUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
if (this._shownOnInit && !this.game.inSpawnPhase()) {
|
||||
this._shownOnInit = false;
|
||||
this.isLeaderboardShow = true;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
if (!this.game.inSpawnPhase() && this.isPlayerTeamLabelVisible) {
|
||||
this.isPlayerTeamLabelVisible = false;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
// Timer logic
|
||||
const maxTimerValue = this.game.config().gameConfig().maxTimerValue;
|
||||
const spawnPhaseTurns = this.game.config().numSpawnPhaseTurns();
|
||||
const ticks = this.game.ticks();
|
||||
const gameTicks = Math.max(0, ticks - spawnPhaseTurns);
|
||||
const elapsedSeconds = Math.floor(gameTicks / 10); // 10 ticks per second
|
||||
|
||||
const hasMaxTimer = maxTimerValue !== null && maxTimerValue !== undefined;
|
||||
|
||||
if (this.game.inSpawnPhase()) {
|
||||
this.timer = hasMaxTimer ? maxTimerValue * 60 : 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.hasWinner) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasMaxTimer) {
|
||||
this.timer = Math.max(0, maxTimerValue * 60 - elapsedSeconds);
|
||||
} else {
|
||||
this.timer = elapsedSeconds;
|
||||
}
|
||||
|
||||
this.leaderboard?.tick();
|
||||
this.teamStats?.tick();
|
||||
this.replayPanel?.tick?.();
|
||||
}
|
||||
|
||||
private updateParentOffset(): void {
|
||||
const offset =
|
||||
(this.spawnBarVisible ? 7 : 0) + (this.immunityBarVisible ? 7 : 0);
|
||||
const parent = this.parentElement as HTMLElement;
|
||||
if (parent) {
|
||||
parent.style.marginTop = `${offset}px`;
|
||||
}
|
||||
}
|
||||
|
||||
private secondsToHms = (d: number): string => {
|
||||
const pad = (n: number) => (n < 10 ? `0${n}` : n);
|
||||
|
||||
const h = Math.floor(d / 3600);
|
||||
const m = Math.floor((d % 3600) / 60);
|
||||
const s = Math.floor((d % 3600) % 60);
|
||||
|
||||
if (h !== 0) {
|
||||
return `${pad(h)}:${pad(m)}:${pad(s)}`;
|
||||
} else {
|
||||
return `${pad(m)}:${pad(s)}`;
|
||||
}
|
||||
};
|
||||
|
||||
private toggleReplayPanel(): void {
|
||||
this._isReplayVisible = !this._isReplayVisible;
|
||||
this.eventBus.emit(
|
||||
new ShowReplayPanelEvent(this._isReplayVisible, this._isSinglePlayer),
|
||||
);
|
||||
}
|
||||
|
||||
private onPauseButtonClick() {
|
||||
this.isPaused = !this.isPaused;
|
||||
if (this.isPaused) {
|
||||
crazyGamesSDK.gameplayStop();
|
||||
} else {
|
||||
crazyGamesSDK.gameplayStart();
|
||||
}
|
||||
this.eventBus.emit(new PauseGameIntentEvent(this.isPaused));
|
||||
}
|
||||
|
||||
private async onExitButtonClick() {
|
||||
const isAlive = this.game.myPlayer()?.isAlive();
|
||||
if (isAlive) {
|
||||
const isConfirmed = confirm(
|
||||
translateText("help_modal.exit_confirmation"),
|
||||
);
|
||||
if (!isConfirmed) return;
|
||||
}
|
||||
await crazyGamesSDK.requestMidgameAd();
|
||||
await crazyGamesSDK.gameplayStop();
|
||||
window.location.href = "/";
|
||||
}
|
||||
|
||||
private onSettingsButtonClick() {
|
||||
this.eventBus.emit(
|
||||
new ShowSettingsModalEvent(true, this._isSinglePlayer, this.isPaused),
|
||||
);
|
||||
}
|
||||
|
||||
private toggleLeaderboard(): void {
|
||||
this.isLeaderboardShow = !this.isLeaderboardShow;
|
||||
}
|
||||
|
||||
private toggleTeamLeaderboard(): void {
|
||||
this.isTeamLeaderboardShow = !this.isTeamLeaderboardShow;
|
||||
}
|
||||
|
||||
private get isTeamGame(): boolean {
|
||||
return this.game?.config().gameConfig().gameMode === GameMode.Team;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.game === undefined) return html``;
|
||||
|
||||
const timerColor =
|
||||
this.game.config().gameConfig().maxTimerValue !== undefined &&
|
||||
this.timer < 60
|
||||
? "text-red-400"
|
||||
: "";
|
||||
|
||||
return html`
|
||||
<div class="relative">
|
||||
<aside
|
||||
class=${`flex flex-row items-center gap-3 py-2 px-3 bg-gray-800/92 backdrop-blur-sm shadow-xs rounded-bl-lg min-[1200px]:rounded-lg transition-transform duration-300 ease-out transform text-white ${
|
||||
this._isVisible ? "translate-x-0" : "translate-x-full"
|
||||
}`}
|
||||
@contextmenu=${(e: Event) => e.preventDefault()}
|
||||
>
|
||||
<!-- Leaderboard button -->
|
||||
<div
|
||||
class="cursor-pointer p-0.5 bg-gray-700/50 hover:bg-gray-600 border rounded-md border-slate-500 transition-colors"
|
||||
@click=${this.toggleLeaderboard}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@keydown=${(e: KeyboardEvent) => {
|
||||
if (e.key === "Enter" || e.key === " " || e.code === "Space") {
|
||||
e.preventDefault();
|
||||
this.toggleLeaderboard();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src=${this.isLeaderboardShow
|
||||
? leaderboardSolidIcon
|
||||
: leaderboardRegularIcon}
|
||||
alt=${translateText("help_modal.icon_alt_player_leaderboard") ||
|
||||
"Player Leaderboard Icon"}
|
||||
width="20"
|
||||
height="20"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Team leaderboard button -->
|
||||
${this.isTeamGame
|
||||
? html`
|
||||
<div
|
||||
class="cursor-pointer p-0.5 bg-gray-700/50 hover:bg-gray-600 border rounded-md border-slate-500 transition-colors"
|
||||
@click=${this.toggleTeamLeaderboard}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@keydown=${(e: KeyboardEvent) => {
|
||||
if (
|
||||
e.key === "Enter" ||
|
||||
e.key === " " ||
|
||||
e.code === "Space"
|
||||
) {
|
||||
e.preventDefault();
|
||||
this.toggleTeamLeaderboard();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src=${this.isTeamLeaderboardShow
|
||||
? teamSolidIcon
|
||||
: teamRegularIcon}
|
||||
alt=${translateText(
|
||||
"help_modal.icon_alt_team_leaderboard",
|
||||
) || "Team Leaderboard Icon"}
|
||||
width="20"
|
||||
height="20"
|
||||
/>
|
||||
</div>
|
||||
`
|
||||
: null}
|
||||
|
||||
<!-- In-game time -->
|
||||
<div class=${timerColor}>${this.secondsToHms(this.timer)}</div>
|
||||
|
||||
<!-- Replay/pause buttons -->
|
||||
${this.maybeRenderReplayButtons()}
|
||||
|
||||
<div class="cursor-pointer" @click=${this.onSettingsButtonClick}>
|
||||
<img src=${settingsIcon} alt="settings" width="20" height="20" />
|
||||
</div>
|
||||
|
||||
<div class="cursor-pointer" @click=${this.onExitButtonClick}>
|
||||
<img src=${exitIcon} alt="exit" width="20" height="20" />
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div class="absolute right-0 top-full flex flex-col items-end w-96">
|
||||
<replay-panel
|
||||
.game=${this.game}
|
||||
.eventBus=${this.eventBus}
|
||||
></replay-panel>
|
||||
${this.isPlayerTeamLabelVisible
|
||||
? html`
|
||||
<div
|
||||
class="flex items-center text-white px-3 text-sm"
|
||||
@contextmenu=${(e: Event) => e.preventDefault()}
|
||||
>
|
||||
${translateText("help_modal.ui_your_team")}
|
||||
<span
|
||||
style="--color: ${this.playerColor.toRgbString()}"
|
||||
class="text-(--color)"
|
||||
>
|
||||
${getTranslatedPlayerTeamLabel(this.playerTeam)}
|
||||
⦿
|
||||
</span>
|
||||
</div>
|
||||
`
|
||||
: null}
|
||||
<div
|
||||
class=${`flex flex-wrap justify-end overflow-x-auto min-w-0 w-full ${this.isLeaderboardShow && this.isTeamLeaderboardShow ? "gap-2" : ""}`}
|
||||
>
|
||||
<leader-board
|
||||
.game=${this.game}
|
||||
.eventBus=${this.eventBus}
|
||||
.visible=${this.isLeaderboardShow}
|
||||
></leader-board>
|
||||
<team-stats
|
||||
.game=${this.game}
|
||||
.eventBus=${this.eventBus}
|
||||
.visible=${this.isTeamLeaderboardShow && this.isTeamGame}
|
||||
></team-stats>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
maybeRenderReplayButtons() {
|
||||
const isReplayOrSingleplayer =
|
||||
this._isSinglePlayer || this.game?.config()?.isReplay();
|
||||
const showPauseButton = isReplayOrSingleplayer || this.isLobbyCreator;
|
||||
|
||||
return html`
|
||||
${isReplayOrSingleplayer
|
||||
? html`
|
||||
<div class="cursor-pointer" @click=${this.toggleReplayPanel}>
|
||||
<img
|
||||
src=${FastForwardIconSolid}
|
||||
alt="replay"
|
||||
width="20"
|
||||
height="20"
|
||||
/>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${showPauseButton
|
||||
? html`
|
||||
<div class="cursor-pointer" @click=${this.onPauseButtonClick}>
|
||||
<img
|
||||
src=${this.isPaused ? playIcon : pauseIcon}
|
||||
alt="play/pause"
|
||||
width="20"
|
||||
height="20"
|
||||
/>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -1,294 +0,0 @@
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import { assetUrl } from "../../../core/AssetUrls";
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
import { GameType } from "../../../core/game/Game";
|
||||
import { GameView } from "../../../core/game/GameView";
|
||||
import { crazyGamesSDK } from "../../CrazyGamesSDK";
|
||||
import { TogglePauseIntentEvent } from "../../InputHandler";
|
||||
import { PauseGameIntentEvent, SendWinnerEvent } from "../../Transport";
|
||||
import { translateText } from "../../Utils";
|
||||
import { ImmunityBarVisibleEvent } from "./ImmunityTimer";
|
||||
import { Layer } from "./Layer";
|
||||
import { ShowReplayPanelEvent } from "./ReplayPanel";
|
||||
import { ShowSettingsModalEvent } from "./SettingsModal";
|
||||
import { SpawnBarVisibleEvent } from "./SpawnTimer";
|
||||
const exitIcon = assetUrl("images/ExitIconWhite.svg");
|
||||
const FastForwardIconSolid = assetUrl("images/FastForwardIconSolidWhite.svg");
|
||||
const pauseIcon = assetUrl("images/PauseIconWhite.svg");
|
||||
const playIcon = assetUrl("images/PlayIconWhite.svg");
|
||||
const settingsIcon = assetUrl("images/SettingIconWhite.svg");
|
||||
const fullscreenIcon = assetUrl("images/FullscreenIconWhite.svg");
|
||||
const exitFullscreenIcon = assetUrl("images/ExitFullscreenIconWhite.svg");
|
||||
|
||||
@customElement("game-right-sidebar")
|
||||
export class GameRightSidebar extends LitElement implements Layer {
|
||||
public game: GameView;
|
||||
public eventBus: EventBus;
|
||||
|
||||
@state()
|
||||
private _isSinglePlayer: boolean = false;
|
||||
|
||||
@state()
|
||||
private _isReplayVisible: boolean = false;
|
||||
|
||||
@state()
|
||||
private _isVisible: boolean = true;
|
||||
|
||||
@state()
|
||||
private isPaused: boolean = false;
|
||||
|
||||
@state()
|
||||
private isFullscreen: boolean = false;
|
||||
|
||||
@state()
|
||||
private timer: number = 0;
|
||||
|
||||
private hasWinner = false;
|
||||
private isLobbyCreator = false;
|
||||
private spawnBarVisible = false;
|
||||
private immunityBarVisible = false;
|
||||
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
|
||||
init() {
|
||||
this._isSinglePlayer =
|
||||
this.game?.config()?.gameConfig()?.gameType === GameType.Singleplayer ||
|
||||
this.game.config().isReplay();
|
||||
this._isVisible = true;
|
||||
this.game.inSpawnPhase();
|
||||
|
||||
this.eventBus.on(SpawnBarVisibleEvent, (e) => {
|
||||
this.spawnBarVisible = e.visible;
|
||||
this.updateParentOffset();
|
||||
});
|
||||
this.eventBus.on(ImmunityBarVisibleEvent, (e) => {
|
||||
this.immunityBarVisible = e.visible;
|
||||
this.updateParentOffset();
|
||||
});
|
||||
|
||||
this.eventBus.on(SendWinnerEvent, () => {
|
||||
this.hasWinner = true;
|
||||
this.requestUpdate();
|
||||
});
|
||||
|
||||
this.eventBus.on(TogglePauseIntentEvent, () => {
|
||||
const isReplayOrSingleplayer =
|
||||
this._isSinglePlayer || this.game?.config()?.isReplay();
|
||||
if (isReplayOrSingleplayer || this.isLobbyCreator) {
|
||||
this.onPauseButtonClick();
|
||||
}
|
||||
});
|
||||
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private onFullscreenChange = () => {
|
||||
this.isFullscreen = !!document.fullscreenElement;
|
||||
};
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
document.addEventListener("fullscreenchange", this.onFullscreenChange);
|
||||
this.onFullscreenChange();
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
document.removeEventListener("fullscreenchange", this.onFullscreenChange);
|
||||
}
|
||||
|
||||
getTickIntervalMs() {
|
||||
return 250;
|
||||
}
|
||||
|
||||
tick() {
|
||||
// Timer logic
|
||||
// Check if the player is the lobby creator
|
||||
if (!this.isLobbyCreator && this.game.myPlayer()?.isLobbyCreator()) {
|
||||
this.isLobbyCreator = true;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
const maxTimerValue = this.game.config().gameConfig().maxTimerValue;
|
||||
const spawnPhaseTurns = this.game.config().numSpawnPhaseTurns();
|
||||
const ticks = this.game.ticks();
|
||||
const gameTicks = Math.max(0, ticks - spawnPhaseTurns);
|
||||
const elapsedSeconds = Math.floor(gameTicks / 10); // 10 ticks per second
|
||||
|
||||
if (this.game.inSpawnPhase()) {
|
||||
this.timer =
|
||||
maxTimerValue !== null && maxTimerValue !== undefined
|
||||
? maxTimerValue * 60
|
||||
: 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.hasWinner) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (maxTimerValue !== null && maxTimerValue !== undefined) {
|
||||
this.timer = Math.max(0, maxTimerValue * 60 - elapsedSeconds);
|
||||
} else {
|
||||
this.timer = elapsedSeconds;
|
||||
}
|
||||
}
|
||||
|
||||
private updateParentOffset(): void {
|
||||
const offset =
|
||||
(this.spawnBarVisible ? 7 : 0) + (this.immunityBarVisible ? 7 : 0);
|
||||
const parent = this.parentElement as HTMLElement;
|
||||
if (parent) {
|
||||
parent.style.marginTop = `${offset}px`;
|
||||
}
|
||||
}
|
||||
|
||||
private secondsToHms = (d: number): string => {
|
||||
const pad = (n: number) => (n < 10 ? `0${n}` : n);
|
||||
|
||||
const h = Math.floor(d / 3600);
|
||||
const m = Math.floor((d % 3600) / 60);
|
||||
const s = Math.floor((d % 3600) % 60);
|
||||
|
||||
if (h !== 0) {
|
||||
return `${pad(h)}:${pad(m)}:${pad(s)}`;
|
||||
} else {
|
||||
return `${pad(m)}:${pad(s)}`;
|
||||
}
|
||||
};
|
||||
|
||||
private toggleReplayPanel(): void {
|
||||
this._isReplayVisible = !this._isReplayVisible;
|
||||
this.eventBus.emit(
|
||||
new ShowReplayPanelEvent(this._isReplayVisible, this._isSinglePlayer),
|
||||
);
|
||||
}
|
||||
|
||||
private onPauseButtonClick() {
|
||||
this.isPaused = !this.isPaused;
|
||||
if (this.isPaused) {
|
||||
crazyGamesSDK.gameplayStop();
|
||||
} else {
|
||||
crazyGamesSDK.gameplayStart();
|
||||
}
|
||||
this.eventBus.emit(new PauseGameIntentEvent(this.isPaused));
|
||||
}
|
||||
|
||||
private async onExitButtonClick() {
|
||||
const isAlive = this.game.myPlayer()?.isAlive();
|
||||
if (isAlive) {
|
||||
const isConfirmed = confirm(
|
||||
translateText("help_modal.exit_confirmation"),
|
||||
);
|
||||
if (!isConfirmed) return;
|
||||
}
|
||||
await crazyGamesSDK.requestMidgameAd();
|
||||
await crazyGamesSDK.gameplayStop();
|
||||
// redirect to the home page
|
||||
window.location.href = "/";
|
||||
}
|
||||
|
||||
private onSettingsButtonClick() {
|
||||
this.eventBus.emit(
|
||||
new ShowSettingsModalEvent(true, this._isSinglePlayer, this.isPaused),
|
||||
);
|
||||
}
|
||||
|
||||
private onFullscreenButtonClick() {
|
||||
if (!document.fullscreenElement) {
|
||||
document.documentElement.requestFullscreen().catch((err) => {
|
||||
console.warn("Failed to enter fullscreen:", err);
|
||||
});
|
||||
} else {
|
||||
document.exitFullscreen().catch((err) => {
|
||||
console.warn("Failed to exit fullscreen:", err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.game === undefined) return html``;
|
||||
|
||||
const timerColor =
|
||||
this.game.config().gameConfig().maxTimerValue !== undefined &&
|
||||
this.game.config().gameConfig().maxTimerValue !== null &&
|
||||
this.timer < 60
|
||||
? "text-red-400"
|
||||
: "";
|
||||
|
||||
return html`
|
||||
<aside
|
||||
class=${`w-fit flex flex-row items-center gap-3 py-2 px-3 bg-gray-800/92 backdrop-blur-sm shadow-xs min-[1200px]:rounded-lg rounded-bl-lg transition-transform duration-300 ease-out transform text-white ${
|
||||
this._isVisible ? "translate-x-0" : "translate-x-full"
|
||||
}`}
|
||||
@contextmenu=${(e: Event) => e.preventDefault()}
|
||||
>
|
||||
<!-- In-game time -->
|
||||
<div class=${timerColor}>${this.secondsToHms(this.timer)}</div>
|
||||
|
||||
<!-- Buttons -->
|
||||
${this.maybeRenderReplayButtons()}
|
||||
|
||||
<div class="cursor-pointer" @click=${this.onSettingsButtonClick}>
|
||||
<img src=${settingsIcon} alt="settings" width="20" height="20" />
|
||||
</div>
|
||||
|
||||
${document.fullscreenEnabled
|
||||
? html`<div
|
||||
class="cursor-pointer"
|
||||
@click=${this.onFullscreenButtonClick}
|
||||
>
|
||||
<img
|
||||
src=${this.isFullscreen ? exitFullscreenIcon : fullscreenIcon}
|
||||
alt=${this.isFullscreen
|
||||
? translateText("fullscreen.exit")
|
||||
: translateText("fullscreen.enter")}
|
||||
width="20"
|
||||
height="20"
|
||||
/>
|
||||
</div>`
|
||||
: ""}
|
||||
|
||||
<div class="cursor-pointer" @click=${this.onExitButtonClick}>
|
||||
<img src=${exitIcon} alt="exit" width="20" height="20" />
|
||||
</div>
|
||||
</aside>
|
||||
`;
|
||||
}
|
||||
|
||||
maybeRenderReplayButtons() {
|
||||
const isReplayOrSingleplayer =
|
||||
this._isSinglePlayer || this.game?.config()?.isReplay();
|
||||
const showPauseButton = isReplayOrSingleplayer || this.isLobbyCreator;
|
||||
|
||||
return html`
|
||||
${isReplayOrSingleplayer
|
||||
? html`
|
||||
<div class="cursor-pointer" @click=${this.toggleReplayPanel}>
|
||||
<img
|
||||
src=${FastForwardIconSolid}
|
||||
alt="replay"
|
||||
width="20"
|
||||
height="20"
|
||||
/>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${showPauseButton
|
||||
? html`
|
||||
<div class="cursor-pointer" @click=${this.onPauseButtonClick}>
|
||||
<img
|
||||
src=${this.isPaused ? playIcon : pauseIcon}
|
||||
alt="play/pause"
|
||||
width="20"
|
||||
height="20"
|
||||
/>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,17 @@ import { LitElement, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { repeat } from "lit/directives/repeat.js";
|
||||
import { renderTroops, translateText } from "../../../client/Utils";
|
||||
import { assetUrl } from "../../../core/AssetUrls";
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
import { GameView, PlayerView } from "../../../core/game/GameView";
|
||||
import { formatPercentage, renderNumber } from "../../Utils";
|
||||
import { GoToPlayerEvent } from "../TransformHandler";
|
||||
import { Layer } from "./Layer";
|
||||
|
||||
const profileIcon = assetUrl("images/ProfileIcon.svg");
|
||||
const goldIcon = assetUrl("images/GoldCoinIcon.svg");
|
||||
const troopIcon = assetUrl("images/TroopIconWhite.svg");
|
||||
|
||||
interface Entry {
|
||||
name: string;
|
||||
position: number;
|
||||
@@ -21,8 +26,8 @@ interface Entry {
|
||||
|
||||
@customElement("leader-board")
|
||||
export class Leaderboard extends LitElement implements Layer {
|
||||
public game: GameView | null = null;
|
||||
public eventBus: EventBus | null = null;
|
||||
@property({ attribute: false }) game: GameView | null = null;
|
||||
@property({ attribute: false }) eventBus: EventBus | null = null;
|
||||
|
||||
players: Entry[] = [];
|
||||
|
||||
@@ -41,8 +46,8 @@ export class Leaderboard extends LitElement implements Layer {
|
||||
|
||||
init() {}
|
||||
|
||||
willUpdate(changed: Map<string, unknown>) {
|
||||
if (changed.has("visible") && this.visible) {
|
||||
willUpdate(_changed: Map<string, unknown>) {
|
||||
if (this.visible && this.game !== null) {
|
||||
this.updateLeaderboard();
|
||||
}
|
||||
}
|
||||
@@ -177,22 +182,28 @@ export class Leaderboard extends LitElement implements Layer {
|
||||
>
|
||||
<div
|
||||
class="grid bg-gray-800/85 w-full text-xs md:text-xs lg:text-sm rounded-lg overflow-hidden"
|
||||
style="grid-template-columns: minmax(24px, 30px) minmax(60px, 100px) minmax(45px, 70px) minmax(40px, 55px) minmax(55px, 105px);"
|
||||
style="grid-template-columns: minmax(24px, 30px) minmax(60px, 100px) minmax(45px, 70px) minmax(35px, 50px) minmax(45px, 65px);"
|
||||
>
|
||||
<div class="contents font-bold bg-gray-700/60">
|
||||
<div class="py-1 md:py-2 text-center border-b border-slate-500">
|
||||
#
|
||||
</div>
|
||||
<div
|
||||
class="py-1 md:py-2 text-center border-b border-slate-500 truncate"
|
||||
class="py-1 md:py-2 flex items-center justify-center border-b border-slate-500"
|
||||
title=${translateText("leaderboard.player")}
|
||||
>
|
||||
${translateText("leaderboard.player")}
|
||||
<img
|
||||
src=${profileIcon}
|
||||
alt=${translateText("leaderboard.player")}
|
||||
class="w-4 h-4"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="py-1 md:py-2 text-center border-b border-slate-500 cursor-pointer whitespace-nowrap truncate"
|
||||
class="py-1 md:py-2 flex items-center justify-center gap-0.5 border-b border-slate-500 cursor-pointer whitespace-nowrap"
|
||||
title=${translateText("leaderboard.owned")}
|
||||
@click=${() => this.setSort("tiles")}
|
||||
>
|
||||
${translateText("leaderboard.owned")}
|
||||
<span class="text-base leading-none">🌐</span>
|
||||
${this._sortKey === "tiles"
|
||||
? this._sortOrder === "asc"
|
||||
? "⬆️"
|
||||
@@ -200,10 +211,15 @@ export class Leaderboard extends LitElement implements Layer {
|
||||
: ""}
|
||||
</div>
|
||||
<div
|
||||
class="py-1 md:py-2 text-center border-b border-slate-500 cursor-pointer whitespace-nowrap truncate"
|
||||
class="py-1 md:py-2 flex items-center justify-center gap-0.5 border-b border-slate-500 cursor-pointer whitespace-nowrap"
|
||||
title=${translateText("leaderboard.gold")}
|
||||
@click=${() => this.setSort("gold")}
|
||||
>
|
||||
${translateText("leaderboard.gold")}
|
||||
<img
|
||||
src=${goldIcon}
|
||||
alt=${translateText("leaderboard.gold")}
|
||||
class="w-4 h-4"
|
||||
/>
|
||||
${this._sortKey === "gold"
|
||||
? this._sortOrder === "asc"
|
||||
? "⬆️"
|
||||
@@ -211,10 +227,15 @@ export class Leaderboard extends LitElement implements Layer {
|
||||
: ""}
|
||||
</div>
|
||||
<div
|
||||
class="py-1 md:py-2 text-center border-b border-slate-500 cursor-pointer whitespace-nowrap truncate"
|
||||
class="py-1 md:py-2 flex items-center justify-center gap-0.5 border-b border-slate-500 cursor-pointer whitespace-nowrap"
|
||||
title=${translateText("leaderboard.maxtroops")}
|
||||
@click=${() => this.setSort("maxtroops")}
|
||||
>
|
||||
${translateText("leaderboard.maxtroops")}
|
||||
<img
|
||||
src=${troopIcon}
|
||||
alt=${translateText("leaderboard.maxtroops")}
|
||||
class="w-4 h-4"
|
||||
/>
|
||||
${this._sortKey === "maxtroops"
|
||||
? this._sortOrder === "asc"
|
||||
? "⬆️"
|
||||
|
||||
@@ -498,13 +498,13 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="fixed top-0 left-0 right-0 sm:left-1/2 sm:right-auto sm:-translate-x-1/2 z-[1001]"
|
||||
class="fixed top-0 left-0 right-0 sm:right-auto lg:left-1/2 lg:-translate-x-1/2 z-[1001]"
|
||||
style="margin-top: ${this.barOffset}px;"
|
||||
@click=${() => this.hide()}
|
||||
@contextmenu=${(e: MouseEvent) => e.preventDefault()}
|
||||
>
|
||||
<div
|
||||
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}"
|
||||
class="bg-gray-800/92 backdrop-blur-sm shadow-xs rounded-b-lg min-[1200px]:rounded-lg shadow-lg text-white text-lg lg:text-base w-full sm:w-[500px] overflow-hidden ${containerClasses}"
|
||||
>
|
||||
${this.player !== null ? this.renderPlayerInfo(this.player) : ""}
|
||||
${this.unit !== null ? this.renderUnitInfo(this.unit) : ""}
|
||||
|
||||
@@ -19,8 +19,8 @@ export class ShowReplayPanelEvent {
|
||||
|
||||
@customElement("replay-panel")
|
||||
export class ReplayPanel extends LitElement implements Layer {
|
||||
public game: GameView | undefined;
|
||||
public eventBus: EventBus | undefined;
|
||||
@property({ attribute: false }) game: GameView | undefined;
|
||||
@property({ attribute: false }) eventBus: EventBus | undefined;
|
||||
|
||||
@property({ type: Boolean })
|
||||
visible: boolean = false;
|
||||
@@ -31,12 +31,21 @@ export class ReplayPanel extends LitElement implements Layer {
|
||||
@property({ type: Boolean })
|
||||
isSingleplayer = false;
|
||||
|
||||
private _eventBusSubscribed = false;
|
||||
|
||||
createRenderRoot() {
|
||||
return this; // Enable Tailwind CSS
|
||||
}
|
||||
|
||||
init() {
|
||||
if (this.eventBus) {
|
||||
init() {}
|
||||
|
||||
willUpdate(changed: Map<string, unknown>) {
|
||||
if (
|
||||
!this._eventBusSubscribed &&
|
||||
this.eventBus &&
|
||||
(changed.has("eventBus") || changed.size === 0)
|
||||
) {
|
||||
this._eventBusSubscribed = true;
|
||||
this.eventBus.on(ShowReplayPanelEvent, (event: ShowReplayPanelEvent) => {
|
||||
this.visible = event.visible;
|
||||
this.isSingleplayer = event.isSingleplayer;
|
||||
|
||||
@@ -27,8 +27,8 @@ interface TeamEntry {
|
||||
|
||||
@customElement("team-stats")
|
||||
export class TeamStats extends LitElement implements Layer {
|
||||
public game: GameView;
|
||||
public eventBus: EventBus;
|
||||
@property({ attribute: false }) game!: GameView;
|
||||
@property({ attribute: false }) eventBus!: EventBus;
|
||||
|
||||
@property({ type: Boolean }) visible = false;
|
||||
teams: TeamEntry[] = [];
|
||||
|
||||
Reference in New Issue
Block a user