mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 09:30:45 +00:00
playerstats to go with infra (#3520)
## Description: https://github.com/openfrontio/infra/pull/279 to go with this, splits out 1v1 ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: w.o.n
This commit is contained in:
@@ -991,6 +991,8 @@
|
||||
"public": "Public",
|
||||
"private": "Private",
|
||||
"solo": "Solo",
|
||||
"ranked": "Ranked",
|
||||
"ranked_1v1": "1v1",
|
||||
"mode": "Mode",
|
||||
"stats_wins": "Wins",
|
||||
"stats_losses": "Losses",
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
Difficulty,
|
||||
GameMode,
|
||||
GameType,
|
||||
RankedType,
|
||||
isDifficulty,
|
||||
isGameMode,
|
||||
isGameType,
|
||||
@@ -17,11 +18,12 @@ import "./PlayerStatsTable";
|
||||
@customElement("player-stats-tree-view")
|
||||
export class PlayerStatsTreeView extends LitElement {
|
||||
@property({ type: Object }) statsTree?: PlayerStatsTree;
|
||||
@state() selectedType: GameType = GameType.Public;
|
||||
@state() selectedType: GameType | "Ranked" = GameType.Public;
|
||||
@state() selectedMode: GameMode = GameMode.FFA;
|
||||
@state() selectedDifficulty: Difficulty = Difficulty.Medium;
|
||||
|
||||
@state() selectedRankedType: RankedType = RankedType.OneVOne;
|
||||
private get typeNode() {
|
||||
if (this.selectedType === "Ranked") return undefined;
|
||||
return this.statsTree?.[this.selectedType];
|
||||
}
|
||||
|
||||
@@ -33,9 +35,20 @@ export class PlayerStatsTreeView extends LitElement {
|
||||
return this.selectedType === GameType.Public;
|
||||
}
|
||||
|
||||
private get availableTypes(): GameType[] {
|
||||
private get availableTypes(): (GameType | "Ranked")[] {
|
||||
if (!this.statsTree) return [];
|
||||
return Object.keys(this.statsTree).filter(isGameType);
|
||||
const types: (GameType | "Ranked")[] = Object.keys(this.statsTree).filter(
|
||||
(k): k is GameType =>
|
||||
isGameType(k) &&
|
||||
Object.keys(this.statsTree![k as GameType] ?? {}).length > 0,
|
||||
);
|
||||
if (
|
||||
this.statsTree.Ranked &&
|
||||
Object.keys(this.statsTree.Ranked).length > 0
|
||||
) {
|
||||
types.push("Ranked");
|
||||
}
|
||||
return types;
|
||||
}
|
||||
|
||||
private get availableModes(): GameMode[] {
|
||||
@@ -43,6 +56,13 @@ export class PlayerStatsTreeView extends LitElement {
|
||||
return Object.keys(this.typeNode).filter(isGameMode);
|
||||
}
|
||||
|
||||
private get availableRankedTypes(): RankedType[] {
|
||||
if (!this.statsTree?.Ranked) return [];
|
||||
return Object.keys(this.statsTree.Ranked).filter((k): k is RankedType =>
|
||||
Object.values(RankedType).includes(k as RankedType),
|
||||
);
|
||||
}
|
||||
|
||||
private get availableDifficulties(): Difficulty[] {
|
||||
if (!this.modeNode) return [];
|
||||
return Object.keys(this.modeNode).filter(isDifficulty);
|
||||
@@ -54,11 +74,22 @@ export class PlayerStatsTreeView extends LitElement {
|
||||
: translateText("game_mode.teams");
|
||||
}
|
||||
|
||||
private labelForRankedType(r: RankedType) {
|
||||
switch (r) {
|
||||
case RankedType.OneVOne:
|
||||
return translateText("player_stats_tree.ranked_1v1");
|
||||
}
|
||||
}
|
||||
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
|
||||
private getSelectedLeaf(): PlayerStatsLeaf | null {
|
||||
if (this.selectedType === "Ranked") {
|
||||
return this.statsTree?.Ranked?.[this.selectedRankedType] ?? null;
|
||||
}
|
||||
|
||||
const modeNode = this.modeNode;
|
||||
if (!modeNode) return null;
|
||||
|
||||
@@ -91,9 +122,19 @@ export class PlayerStatsTreeView extends LitElement {
|
||||
|
||||
private syncSelection(): void {
|
||||
const types = this.availableTypes;
|
||||
if (types.length && !types.includes(this.selectedType)) {
|
||||
if (types.length && !types.includes(this.selectedType as GameType)) {
|
||||
this.selectedType = types[0];
|
||||
}
|
||||
if (this.selectedType === "Ranked") {
|
||||
const rankedTypes = this.availableRankedTypes;
|
||||
if (
|
||||
rankedTypes.length &&
|
||||
!rankedTypes.includes(this.selectedRankedType)
|
||||
) {
|
||||
this.selectedRankedType = rankedTypes[0];
|
||||
}
|
||||
return;
|
||||
}
|
||||
const modes = this.availableModes;
|
||||
if (modes.length && !modes.includes(this.selectedMode)) {
|
||||
this.selectedMode = modes[0];
|
||||
@@ -113,13 +154,14 @@ export class PlayerStatsTreeView extends LitElement {
|
||||
changedProperties.has("statsTree") ||
|
||||
changedProperties.has("selectedType") ||
|
||||
changedProperties.has("selectedMode") ||
|
||||
changedProperties.has("selectedDifficulty")
|
||||
changedProperties.has("selectedDifficulty") ||
|
||||
changedProperties.has("selectedRankedType")
|
||||
) {
|
||||
this.syncSelection();
|
||||
}
|
||||
}
|
||||
|
||||
private setGameType(t: GameType) {
|
||||
private setGameType(t: GameType | "Ranked") {
|
||||
if (this.selectedType === t) return;
|
||||
this.selectedType = t;
|
||||
this.requestUpdate();
|
||||
@@ -131,6 +173,12 @@ export class PlayerStatsTreeView extends LitElement {
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private setRankedType(r: RankedType) {
|
||||
if (this.selectedRankedType === r) return;
|
||||
this.selectedRankedType = r;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private setDifficulty(d: Difficulty) {
|
||||
if (this.selectedDifficulty === d) return;
|
||||
this.selectedDifficulty = d;
|
||||
@@ -215,6 +263,7 @@ export class PlayerStatsTreeView extends LitElement {
|
||||
const types = this.availableTypes;
|
||||
const modes = this.availableModes;
|
||||
const diffs = this.availableDifficulties;
|
||||
const rankedTypes = this.availableRankedTypes;
|
||||
const leaf = this.getSelectedLeaf();
|
||||
const wlr = leaf
|
||||
? leaf.losses === 0n
|
||||
@@ -239,17 +288,40 @@ export class PlayerStatsTreeView extends LitElement {
|
||||
: "bg-white/5 border-white/10 text-gray-400 hover:bg-white/10 hover:text-white"}"
|
||||
@click=${() => this.setGameType(t)}
|
||||
>
|
||||
${t === GameType.Public
|
||||
? translateText("player_stats_tree.public")
|
||||
: t === GameType.Private
|
||||
? translateText("player_stats_tree.private")
|
||||
: translateText("player_stats_tree.solo")}
|
||||
${t === "Ranked"
|
||||
? translateText("player_stats_tree.ranked")
|
||||
: t === GameType.Public
|
||||
? translateText("player_stats_tree.public")
|
||||
: t === GameType.Private
|
||||
? translateText("player_stats_tree.private")
|
||||
: translateText("player_stats_tree.solo")}
|
||||
</button>
|
||||
`,
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<!-- Ranked type selector -->
|
||||
${this.selectedType === "Ranked" && rankedTypes.length
|
||||
? html`<div
|
||||
class="flex gap-1 bg-black/20 rounded-md p-1 border border-white/5"
|
||||
>
|
||||
${rankedTypes.map(
|
||||
(r) => html`
|
||||
<button
|
||||
class="text-xs px-3 py-1 rounded-sm transition-colors ${this
|
||||
.selectedRankedType === r
|
||||
? "bg-white/20 text-white font-bold"
|
||||
: "text-gray-400 hover:text-white"}"
|
||||
@click=${() => this.setRankedType(r)}
|
||||
>
|
||||
${this.labelForRankedType(r)}
|
||||
</button>
|
||||
`,
|
||||
)}
|
||||
</div>`
|
||||
: html``}
|
||||
|
||||
<!-- Mode selector -->
|
||||
${modes.length
|
||||
? html`<div
|
||||
|
||||
+10
-6
@@ -85,13 +85,17 @@ export const PlayerStatsLeafSchema = z.object({
|
||||
});
|
||||
export type PlayerStatsLeaf = z.infer<typeof PlayerStatsLeafSchema>;
|
||||
|
||||
export const PlayerStatsTreeSchema = z.partialRecord(
|
||||
z.enum(GameType),
|
||||
z.partialRecord(
|
||||
z.enum(GameMode),
|
||||
z.partialRecord(z.enum(Difficulty), PlayerStatsLeafSchema),
|
||||
),
|
||||
const GameModeStatsSchema = z.partialRecord(
|
||||
z.enum(GameMode),
|
||||
z.partialRecord(z.enum(Difficulty), PlayerStatsLeafSchema),
|
||||
);
|
||||
|
||||
export const PlayerStatsTreeSchema = z.object({
|
||||
Singleplayer: GameModeStatsSchema.optional(),
|
||||
Public: GameModeStatsSchema.optional(),
|
||||
Private: GameModeStatsSchema.optional(),
|
||||
Ranked: z.partialRecord(z.enum(RankedType), PlayerStatsLeafSchema).optional(),
|
||||
});
|
||||
export type PlayerStatsTree = z.infer<typeof PlayerStatsTreeSchema>;
|
||||
|
||||
export const PlayerGameSchema = z.object({
|
||||
|
||||
Reference in New Issue
Block a user