mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-08-08 03:29:44 +00:00
leaderboard
This commit is contained in:
+10
-6
@@ -327,11 +327,18 @@
|
|||||||
<build-menu></build-menu>
|
<build-menu></build-menu>
|
||||||
<win-modal></win-modal>
|
<win-modal></win-modal>
|
||||||
<game-starting-modal></game-starting-modal>
|
<game-starting-modal></game-starting-modal>
|
||||||
|
<!-- Top HUD: mirrors bottom HUD grid layout -->
|
||||||
<div
|
<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>
|
<div class="lg:col-start-3 flex flex-col items-end pointer-events-auto">
|
||||||
<replay-panel></replay-panel>
|
<game-menu></game-menu>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<settings-modal></settings-modal>
|
<settings-modal></settings-modal>
|
||||||
<player-panel></player-panel>
|
<player-panel></player-panel>
|
||||||
@@ -342,11 +349,8 @@
|
|||||||
<alert-frame></alert-frame>
|
<alert-frame></alert-frame>
|
||||||
<chat-modal></chat-modal>
|
<chat-modal></chat-modal>
|
||||||
<multi-tab-modal></multi-tab-modal>
|
<multi-tab-modal></multi-tab-modal>
|
||||||
<game-left-sidebar></game-left-sidebar>
|
|
||||||
<performance-overlay></performance-overlay>
|
<performance-overlay></performance-overlay>
|
||||||
<player-info-overlay></player-info-overlay>
|
<player-info-overlay></player-info-overlay>
|
||||||
<leader-board></leader-board>
|
|
||||||
<team-stats></team-stats>
|
|
||||||
<heads-up-message></heads-up-message>
|
<heads-up-message></heads-up-message>
|
||||||
|
|
||||||
<!-- Scripts -->
|
<!-- Scripts -->
|
||||||
|
|||||||
@@ -18,13 +18,11 @@ import { DynamicUILayer } from "./layers/DynamicUILayer";
|
|||||||
import { EmojiTable } from "./layers/EmojiTable";
|
import { EmojiTable } from "./layers/EmojiTable";
|
||||||
import { EventsDisplay } from "./layers/EventsDisplay";
|
import { EventsDisplay } from "./layers/EventsDisplay";
|
||||||
import { FxLayer } from "./layers/FxLayer";
|
import { FxLayer } from "./layers/FxLayer";
|
||||||
import { GameLeftSidebar } from "./layers/GameLeftSidebar";
|
import { GameMenu } from "./layers/GameMenu";
|
||||||
import { GameRightSidebar } from "./layers/GameRightSidebar";
|
|
||||||
import { HeadsUpMessage } from "./layers/HeadsUpMessage";
|
import { HeadsUpMessage } from "./layers/HeadsUpMessage";
|
||||||
import { ImmunityTimer } from "./layers/ImmunityTimer";
|
import { ImmunityTimer } from "./layers/ImmunityTimer";
|
||||||
import { InGamePromo } from "./layers/InGamePromo";
|
import { InGamePromo } from "./layers/InGamePromo";
|
||||||
import { Layer } from "./layers/Layer";
|
import { Layer } from "./layers/Layer";
|
||||||
import { Leaderboard } from "./layers/Leaderboard";
|
|
||||||
import { MainRadialMenu } from "./layers/MainRadialMenu";
|
import { MainRadialMenu } from "./layers/MainRadialMenu";
|
||||||
import { MultiTabModal } from "./layers/MultiTabModal";
|
import { MultiTabModal } from "./layers/MultiTabModal";
|
||||||
import { NameLayer } from "./layers/NameLayer";
|
import { NameLayer } from "./layers/NameLayer";
|
||||||
@@ -33,13 +31,11 @@ import { PerformanceOverlay } from "./layers/PerformanceOverlay";
|
|||||||
import { PlayerInfoOverlay } from "./layers/PlayerInfoOverlay";
|
import { PlayerInfoOverlay } from "./layers/PlayerInfoOverlay";
|
||||||
import { PlayerPanel } from "./layers/PlayerPanel";
|
import { PlayerPanel } from "./layers/PlayerPanel";
|
||||||
import { RailroadLayer } from "./layers/RailroadLayer";
|
import { RailroadLayer } from "./layers/RailroadLayer";
|
||||||
import { ReplayPanel } from "./layers/ReplayPanel";
|
|
||||||
import { SAMRadiusLayer } from "./layers/SAMRadiusLayer";
|
import { SAMRadiusLayer } from "./layers/SAMRadiusLayer";
|
||||||
import { SettingsModal } from "./layers/SettingsModal";
|
import { SettingsModal } from "./layers/SettingsModal";
|
||||||
import { SpawnTimer } from "./layers/SpawnTimer";
|
import { SpawnTimer } from "./layers/SpawnTimer";
|
||||||
import { StructureIconsLayer } from "./layers/StructureIconsLayer";
|
import { StructureIconsLayer } from "./layers/StructureIconsLayer";
|
||||||
import { StructureLayer } from "./layers/StructureLayer";
|
import { StructureLayer } from "./layers/StructureLayer";
|
||||||
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";
|
||||||
@@ -88,28 +84,12 @@ export function createRenderer(
|
|||||||
buildMenu.uiState = uiState;
|
buildMenu.uiState = uiState;
|
||||||
buildMenu.transformHandler = transformHandler;
|
buildMenu.transformHandler = transformHandler;
|
||||||
|
|
||||||
const leaderboard = document.querySelector("leader-board") as Leaderboard;
|
const gameMenu = document.querySelector("game-menu") as GameMenu;
|
||||||
if (!leaderboard || !(leaderboard instanceof Leaderboard)) {
|
if (!gameMenu || !(gameMenu instanceof GameMenu)) {
|
||||||
console.error("LeaderBoard element not found in the DOM");
|
console.error("GameMenu element not found in the DOM");
|
||||||
}
|
}
|
||||||
leaderboard.eventBus = eventBus;
|
gameMenu.game = game;
|
||||||
leaderboard.game = game;
|
gameMenu.eventBus = eventBus;
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
const controlPanel = document.querySelector("control-panel") as ControlPanel;
|
const controlPanel = document.querySelector("control-panel") as ControlPanel;
|
||||||
if (!(controlPanel instanceof ControlPanel)) {
|
if (!(controlPanel instanceof ControlPanel)) {
|
||||||
@@ -163,22 +143,6 @@ export function createRenderer(
|
|||||||
winModal.eventBus = eventBus;
|
winModal.eventBus = eventBus;
|
||||||
winModal.game = game;
|
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(
|
const settingsModal = document.querySelector(
|
||||||
"settings-modal",
|
"settings-modal",
|
||||||
) as SettingsModal;
|
) as SettingsModal;
|
||||||
@@ -304,16 +268,12 @@ export function createRenderer(
|
|||||||
),
|
),
|
||||||
spawnTimer,
|
spawnTimer,
|
||||||
immunityTimer,
|
immunityTimer,
|
||||||
leaderboard,
|
gameMenu,
|
||||||
gameLeftSidebar,
|
|
||||||
unitDisplay,
|
unitDisplay,
|
||||||
gameRightSidebar,
|
|
||||||
controlPanel,
|
controlPanel,
|
||||||
playerInfo,
|
playerInfo,
|
||||||
winModal,
|
winModal,
|
||||||
replayPanel,
|
|
||||||
settingsModal,
|
settingsModal,
|
||||||
teamStats,
|
|
||||||
playerPanel,
|
playerPanel,
|
||||||
headsUpMessage,
|
headsUpMessage,
|
||||||
multiTabModal,
|
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 { customElement, property, state } from "lit/decorators.js";
|
||||||
import { repeat } from "lit/directives/repeat.js";
|
import { repeat } from "lit/directives/repeat.js";
|
||||||
import { renderTroops, translateText } from "../../../client/Utils";
|
import { renderTroops, translateText } from "../../../client/Utils";
|
||||||
|
import { assetUrl } from "../../../core/AssetUrls";
|
||||||
import { EventBus } from "../../../core/EventBus";
|
import { EventBus } from "../../../core/EventBus";
|
||||||
import { GameView, PlayerView } from "../../../core/game/GameView";
|
import { GameView, PlayerView } from "../../../core/game/GameView";
|
||||||
import { formatPercentage, renderNumber } from "../../Utils";
|
import { formatPercentage, renderNumber } from "../../Utils";
|
||||||
import { GoToPlayerEvent } from "../TransformHandler";
|
import { GoToPlayerEvent } from "../TransformHandler";
|
||||||
import { Layer } from "./Layer";
|
import { Layer } from "./Layer";
|
||||||
|
|
||||||
|
const profileIcon = assetUrl("images/ProfileIcon.svg");
|
||||||
|
const goldIcon = assetUrl("images/GoldCoinIcon.svg");
|
||||||
|
const troopIcon = assetUrl("images/TroopIconWhite.svg");
|
||||||
|
|
||||||
interface Entry {
|
interface Entry {
|
||||||
name: string;
|
name: string;
|
||||||
position: number;
|
position: number;
|
||||||
@@ -21,8 +26,8 @@ interface Entry {
|
|||||||
|
|
||||||
@customElement("leader-board")
|
@customElement("leader-board")
|
||||||
export class Leaderboard extends LitElement implements Layer {
|
export class Leaderboard extends LitElement implements Layer {
|
||||||
public game: GameView | null = null;
|
@property({ attribute: false }) game: GameView | null = null;
|
||||||
public eventBus: EventBus | null = null;
|
@property({ attribute: false }) eventBus: EventBus | null = null;
|
||||||
|
|
||||||
players: Entry[] = [];
|
players: Entry[] = [];
|
||||||
|
|
||||||
@@ -41,8 +46,8 @@ export class Leaderboard extends LitElement implements Layer {
|
|||||||
|
|
||||||
init() {}
|
init() {}
|
||||||
|
|
||||||
willUpdate(changed: Map<string, unknown>) {
|
willUpdate(_changed: Map<string, unknown>) {
|
||||||
if (changed.has("visible") && this.visible) {
|
if (this.visible && this.game !== null) {
|
||||||
this.updateLeaderboard();
|
this.updateLeaderboard();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,22 +182,28 @@ export class Leaderboard extends LitElement implements Layer {
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="grid bg-gray-800/85 w-full text-xs md:text-xs lg:text-sm rounded-lg overflow-hidden"
|
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="contents font-bold bg-gray-700/60">
|
||||||
<div class="py-1 md:py-2 text-center border-b border-slate-500">
|
<div class="py-1 md:py-2 text-center border-b border-slate-500">
|
||||||
#
|
#
|
||||||
</div>
|
</div>
|
||||||
<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>
|
||||||
<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")}
|
@click=${() => this.setSort("tiles")}
|
||||||
>
|
>
|
||||||
${translateText("leaderboard.owned")}
|
<span class="text-base leading-none">🌐</span>
|
||||||
${this._sortKey === "tiles"
|
${this._sortKey === "tiles"
|
||||||
? this._sortOrder === "asc"
|
? this._sortOrder === "asc"
|
||||||
? "⬆️"
|
? "⬆️"
|
||||||
@@ -200,10 +211,15 @@ export class Leaderboard extends LitElement implements Layer {
|
|||||||
: ""}
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
<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")}
|
@click=${() => this.setSort("gold")}
|
||||||
>
|
>
|
||||||
${translateText("leaderboard.gold")}
|
<img
|
||||||
|
src=${goldIcon}
|
||||||
|
alt=${translateText("leaderboard.gold")}
|
||||||
|
class="w-4 h-4"
|
||||||
|
/>
|
||||||
${this._sortKey === "gold"
|
${this._sortKey === "gold"
|
||||||
? this._sortOrder === "asc"
|
? this._sortOrder === "asc"
|
||||||
? "⬆️"
|
? "⬆️"
|
||||||
@@ -211,10 +227,15 @@ export class Leaderboard extends LitElement implements Layer {
|
|||||||
: ""}
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
<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")}
|
@click=${() => this.setSort("maxtroops")}
|
||||||
>
|
>
|
||||||
${translateText("leaderboard.maxtroops")}
|
<img
|
||||||
|
src=${troopIcon}
|
||||||
|
alt=${translateText("leaderboard.maxtroops")}
|
||||||
|
class="w-4 h-4"
|
||||||
|
/>
|
||||||
${this._sortKey === "maxtroops"
|
${this._sortKey === "maxtroops"
|
||||||
? this._sortOrder === "asc"
|
? this._sortOrder === "asc"
|
||||||
? "⬆️"
|
? "⬆️"
|
||||||
|
|||||||
@@ -498,13 +498,13 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div
|
<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;"
|
style="margin-top: ${this.barOffset}px;"
|
||||||
@click=${() => this.hide()}
|
@click=${() => this.hide()}
|
||||||
@contextmenu=${(e: MouseEvent) => e.preventDefault()}
|
@contextmenu=${(e: MouseEvent) => e.preventDefault()}
|
||||||
>
|
>
|
||||||
<div
|
<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.player !== null ? this.renderPlayerInfo(this.player) : ""}
|
||||||
${this.unit !== null ? this.renderUnitInfo(this.unit) : ""}
|
${this.unit !== null ? this.renderUnitInfo(this.unit) : ""}
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ export class ShowReplayPanelEvent {
|
|||||||
|
|
||||||
@customElement("replay-panel")
|
@customElement("replay-panel")
|
||||||
export class ReplayPanel extends LitElement implements Layer {
|
export class ReplayPanel extends LitElement implements Layer {
|
||||||
public game: GameView | undefined;
|
@property({ attribute: false }) game: GameView | undefined;
|
||||||
public eventBus: EventBus | undefined;
|
@property({ attribute: false }) eventBus: EventBus | undefined;
|
||||||
|
|
||||||
@property({ type: Boolean })
|
@property({ type: Boolean })
|
||||||
visible: boolean = false;
|
visible: boolean = false;
|
||||||
@@ -31,12 +31,21 @@ export class ReplayPanel extends LitElement implements Layer {
|
|||||||
@property({ type: Boolean })
|
@property({ type: Boolean })
|
||||||
isSingleplayer = false;
|
isSingleplayer = false;
|
||||||
|
|
||||||
|
private _eventBusSubscribed = false;
|
||||||
|
|
||||||
createRenderRoot() {
|
createRenderRoot() {
|
||||||
return this; // Enable Tailwind CSS
|
return this; // Enable Tailwind CSS
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {}
|
||||||
if (this.eventBus) {
|
|
||||||
|
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.eventBus.on(ShowReplayPanelEvent, (event: ShowReplayPanelEvent) => {
|
||||||
this.visible = event.visible;
|
this.visible = event.visible;
|
||||||
this.isSingleplayer = event.isSingleplayer;
|
this.isSingleplayer = event.isSingleplayer;
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ interface TeamEntry {
|
|||||||
|
|
||||||
@customElement("team-stats")
|
@customElement("team-stats")
|
||||||
export class TeamStats extends LitElement implements Layer {
|
export class TeamStats extends LitElement implements Layer {
|
||||||
public game: GameView;
|
@property({ attribute: false }) game!: GameView;
|
||||||
public eventBus: EventBus;
|
@property({ attribute: false }) eventBus!: EventBus;
|
||||||
|
|
||||||
@property({ type: Boolean }) visible = false;
|
@property({ type: Boolean }) visible = false;
|
||||||
teams: TeamEntry[] = [];
|
teams: TeamEntry[] = [];
|
||||||
|
|||||||
Reference in New Issue
Block a user