diff --git a/src/client/RankedQueue.ts b/src/client/RankedQueue.ts index ccdb782f5..47b593973 100644 --- a/src/client/RankedQueue.ts +++ b/src/client/RankedQueue.ts @@ -7,7 +7,7 @@ import { JoinLobbyEvent } from "./Main"; import { translateText } from "./Utils"; type QueueType = "ranked" | "unranked"; -type GameMode = "ffa" | "team"; +type GameMode = "ffa" | "team" | "duel"; interface QueueStatus { queueSize: number; @@ -30,7 +30,10 @@ export class RankedQueue extends LitElement { @state() private queueType: QueueType = "ranked"; @state() private gameMode: GameMode = "ffa"; @state() private queueStatus: QueueStatus | null = null; - @state() private playerElo: number | null = null; + @state() private playerEloByMode: { + ffa: number | null; + duel: number | null; + } = { ffa: null, duel: null }; @state() private isConnecting: boolean = false; @state() private error: string | null = null; @state() private isLoadingElo: boolean = false; @@ -48,6 +51,15 @@ export class RankedQueue extends LitElement { return this; } + /** + * Get the current player's ELO for the selected game mode + */ + private get currentPlayerElo(): number | null { + return this.gameMode === "duel" + ? this.playerEloByMode.duel + : this.playerEloByMode.ffa; + } + async connectedCallback() { super.connectedCallback(); // Fetch player ELO and leaderboard immediately when component loads @@ -66,8 +78,18 @@ export class RankedQueue extends LitElement { this.isLoadingElo = true; try { const userMe = await getUserMe(); - if (userMe !== false && userMe.player.elo !== undefined) { - this.playerElo = userMe.player.elo; + if (userMe !== false) { + // Use eloByMode if available, fall back to elo for backward compatibility + const eloByMode = (userMe.player as any).eloByMode; + if (eloByMode) { + this.playerEloByMode = { + ffa: eloByMode.ffa ?? null, + duel: eloByMode.duel ?? null, + }; + } else if (userMe.player.elo !== undefined) { + // Backward compatibility: only FFA ELO available + this.playerEloByMode = { ffa: userMe.player.elo, duel: null }; + } } } catch (error) { console.error("Failed to fetch player ELO:", error); @@ -83,7 +105,10 @@ export class RankedQueue extends LitElement { this.isLoadingLeaderboard = true; try { const apiBase = getApiBase(); - const response = await fetch(`${apiBase}/leaderboard/public/ffa`); + const leaderboardMode = this.gameMode === "duel" ? "duel" : "ffa"; + const response = await fetch( + `${apiBase}/leaderboard/public/${leaderboardMode}`, + ); if (response.ok) { const data = await response.json(); @@ -215,7 +240,18 @@ export class RankedQueue extends LitElement { case "auth_success": console.log("Authentication successful"); if (message.playerElo !== undefined) { - this.playerElo = message.playerElo; + // Update the ELO for the current game mode from the server response + if (this.gameMode === "duel") { + this.playerEloByMode = { + ...this.playerEloByMode, + duel: message.playerElo, + }; + } else { + this.playerEloByMode = { + ...this.playerEloByMode, + ffa: message.playerElo, + }; + } } break; @@ -336,120 +372,163 @@ export class RankedQueue extends LitElement { if (this.inQueue) { return; // Can't change while in queue } - this.gameMode = mode; + if (this.gameMode !== mode) { + this.gameMode = mode; + // Refresh leaderboard for the new mode + this.fetchLeaderboard(); + } } render() { return html` -
- - + +
+ + + + + - - + + ${this.showLeaderboard - ? translateText("ranked_queue.hide_leaderboard") - : translateText("ranked_queue.view_leaderboard")} - - - - ${this.showLeaderboard - ? html` -
- ${this.isLoadingLeaderboard - ? html`
- ${translateText("ranked_queue.loading_leaderboard")} -
` - : this.leaderboard.length === 0 - ? html`
- ${translateText("ranked_queue.no_ranked_players")} + ? html` +
+ ${this.isLoadingLeaderboard + ? html`
+ ${translateText("ranked_queue.loading_leaderboard")}
` - : html` -
- ${this.leaderboard.slice(0, 10).map( - (entry) => html` -
-
-
- #${entry.rank} + : this.leaderboard.length === 0 + ? html`
+ ${translateText("ranked_queue.no_ranked_players")} +
` + : html` +
+ ${this.leaderboard.slice(0, 10).map( + (entry) => html` +
+
+
+ #${entry.rank} +
+
+
+ ${entry.username} +
+
+ ${entry.gamesPlayed} + ${translateText("ranked_queue.games")} • + ${entry.wins}${translateText( + "ranked_queue.wins_short", + )} + ${entry.losses}${translateText( + "ranked_queue.losses_short", + )} +
+
-
-
- ${entry.username} +
+
+ ${entry.currentElo}
- ${entry.gamesPlayed} - ${translateText("ranked_queue.games")} • - ${entry.wins}${translateText( - "ranked_queue.wins_short", - )} - ${entry.losses}${translateText( - "ranked_queue.losses_short", - )} + ${translateText("ranked_queue.elo")}
-
-
- ${entry.currentElo} -
-
- ${translateText("ranked_queue.elo")} -
-
-
- `, - )} -
- `} -
- ` - : ""} + `, + )} +
+ `} +
+ ` + : ""} +
`; } diff --git a/src/server/RankedGameConfig.ts b/src/server/RankedGameConfig.ts index b3772557f..9a12e3799 100644 --- a/src/server/RankedGameConfig.ts +++ b/src/server/RankedGameConfig.ts @@ -9,7 +9,7 @@ import { GameConfig, TeamCountConfig } from "../core/Schemas"; export interface RankedMatchConfig { queueType: "ranked" | "unranked"; - gameMode: "ffa" | "team"; + gameMode: "ffa" | "team" | "duel"; playerCount: number; teamConfig?: TeamCountConfig; } @@ -24,18 +24,19 @@ export function buildRankedGameConfig( matchConfig: RankedMatchConfig, ): GameConfig { const { gameMode, playerCount } = matchConfig; - const mode = gameMode === "ffa" ? GameMode.FFA : GameMode.Team; + const isDuel = gameMode === "duel"; + const mode = gameMode === "team" ? GameMode.Team : GameMode.FFA; return { gameMap: map, - gameMapSize: selectMapSize(playerCount), + gameMapSize: isDuel ? GameMapSize.Compact : selectMapSize(playerCount), gameType: GameType.Public, gameMode: mode, maxPlayers: playerCount, bots: 400, difficulty: Difficulty.Medium, - disableNPCs: false, + disableNPCs: isDuel ? true : false, // Donation rules donateGold: mode === GameMode.Team,