Skip to content

Commit 14a5128

Browse files
authored
playerstats to go with infra (#3520)
## Description: openfrontio/infra#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
1 parent c213a1d commit 14a5128

3 files changed

Lines changed: 96 additions & 18 deletions

File tree

resources/lang/en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1017,6 +1017,8 @@
10171017
"public": "Public",
10181018
"private": "Private",
10191019
"solo": "Solo",
1020+
"ranked": "Ranked",
1021+
"ranked_1v1": "1v1",
10201022
"mode": "Mode",
10211023
"stats_wins": "Wins",
10221024
"stats_losses": "Losses",

src/client/components/baseComponents/stats/PlayerStatsTree.ts

Lines changed: 84 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
Difficulty,
66
GameMode,
77
GameType,
8+
RankedType,
89
isDifficulty,
910
isGameMode,
1011
isGameType,
@@ -17,11 +18,12 @@ import "./PlayerStatsTable";
1718
@customElement("player-stats-tree-view")
1819
export class PlayerStatsTreeView extends LitElement {
1920
@property({ type: Object }) statsTree?: PlayerStatsTree;
20-
@state() selectedType: GameType = GameType.Public;
21+
@state() selectedType: GameType | "Ranked" = GameType.Public;
2122
@state() selectedMode: GameMode = GameMode.FFA;
2223
@state() selectedDifficulty: Difficulty = Difficulty.Medium;
23-
24+
@state() selectedRankedType: RankedType = RankedType.OneVOne;
2425
private get typeNode() {
26+
if (this.selectedType === "Ranked") return undefined;
2527
return this.statsTree?.[this.selectedType];
2628
}
2729

@@ -33,16 +35,34 @@ export class PlayerStatsTreeView extends LitElement {
3335
return this.selectedType === GameType.Public;
3436
}
3537

36-
private get availableTypes(): GameType[] {
38+
private get availableTypes(): (GameType | "Ranked")[] {
3739
if (!this.statsTree) return [];
38-
return Object.keys(this.statsTree).filter(isGameType);
40+
const types: (GameType | "Ranked")[] = Object.keys(this.statsTree).filter(
41+
(k): k is GameType =>
42+
isGameType(k) &&
43+
Object.keys(this.statsTree![k as GameType] ?? {}).length > 0,
44+
);
45+
if (
46+
this.statsTree.Ranked &&
47+
Object.keys(this.statsTree.Ranked).length > 0
48+
) {
49+
types.push("Ranked");
50+
}
51+
return types;
3952
}
4053

4154
private get availableModes(): GameMode[] {
4255
if (!this.typeNode) return [];
4356
return Object.keys(this.typeNode).filter(isGameMode);
4457
}
4558

59+
private get availableRankedTypes(): RankedType[] {
60+
if (!this.statsTree?.Ranked) return [];
61+
return Object.keys(this.statsTree.Ranked).filter((k): k is RankedType =>
62+
Object.values(RankedType).includes(k as RankedType),
63+
);
64+
}
65+
4666
private get availableDifficulties(): Difficulty[] {
4767
if (!this.modeNode) return [];
4868
return Object.keys(this.modeNode).filter(isDifficulty);
@@ -54,11 +74,22 @@ export class PlayerStatsTreeView extends LitElement {
5474
: translateText("game_mode.teams");
5575
}
5676

77+
private labelForRankedType(r: RankedType) {
78+
switch (r) {
79+
case RankedType.OneVOne:
80+
return translateText("player_stats_tree.ranked_1v1");
81+
}
82+
}
83+
5784
createRenderRoot() {
5885
return this;
5986
}
6087

6188
private getSelectedLeaf(): PlayerStatsLeaf | null {
89+
if (this.selectedType === "Ranked") {
90+
return this.statsTree?.Ranked?.[this.selectedRankedType] ?? null;
91+
}
92+
6293
const modeNode = this.modeNode;
6394
if (!modeNode) return null;
6495

@@ -91,9 +122,19 @@ export class PlayerStatsTreeView extends LitElement {
91122

92123
private syncSelection(): void {
93124
const types = this.availableTypes;
94-
if (types.length && !types.includes(this.selectedType)) {
125+
if (types.length && !types.includes(this.selectedType as GameType)) {
95126
this.selectedType = types[0];
96127
}
128+
if (this.selectedType === "Ranked") {
129+
const rankedTypes = this.availableRankedTypes;
130+
if (
131+
rankedTypes.length &&
132+
!rankedTypes.includes(this.selectedRankedType)
133+
) {
134+
this.selectedRankedType = rankedTypes[0];
135+
}
136+
return;
137+
}
97138
const modes = this.availableModes;
98139
if (modes.length && !modes.includes(this.selectedMode)) {
99140
this.selectedMode = modes[0];
@@ -113,13 +154,14 @@ export class PlayerStatsTreeView extends LitElement {
113154
changedProperties.has("statsTree") ||
114155
changedProperties.has("selectedType") ||
115156
changedProperties.has("selectedMode") ||
116-
changedProperties.has("selectedDifficulty")
157+
changedProperties.has("selectedDifficulty") ||
158+
changedProperties.has("selectedRankedType")
117159
) {
118160
this.syncSelection();
119161
}
120162
}
121163

122-
private setGameType(t: GameType) {
164+
private setGameType(t: GameType | "Ranked") {
123165
if (this.selectedType === t) return;
124166
this.selectedType = t;
125167
this.requestUpdate();
@@ -131,6 +173,12 @@ export class PlayerStatsTreeView extends LitElement {
131173
this.requestUpdate();
132174
}
133175

176+
private setRankedType(r: RankedType) {
177+
if (this.selectedRankedType === r) return;
178+
this.selectedRankedType = r;
179+
this.requestUpdate();
180+
}
181+
134182
private setDifficulty(d: Difficulty) {
135183
if (this.selectedDifficulty === d) return;
136184
this.selectedDifficulty = d;
@@ -215,6 +263,7 @@ export class PlayerStatsTreeView extends LitElement {
215263
const types = this.availableTypes;
216264
const modes = this.availableModes;
217265
const diffs = this.availableDifficulties;
266+
const rankedTypes = this.availableRankedTypes;
218267
const leaf = this.getSelectedLeaf();
219268
const wlr = leaf
220269
? leaf.losses === 0n
@@ -239,17 +288,40 @@ export class PlayerStatsTreeView extends LitElement {
239288
: "bg-white/5 border-white/10 text-gray-400 hover:bg-white/10 hover:text-white"}"
240289
@click=${() => this.setGameType(t)}
241290
>
242-
${t === GameType.Public
243-
? translateText("player_stats_tree.public")
244-
: t === GameType.Private
245-
? translateText("player_stats_tree.private")
246-
: translateText("player_stats_tree.solo")}
291+
${t === "Ranked"
292+
? translateText("player_stats_tree.ranked")
293+
: t === GameType.Public
294+
? translateText("player_stats_tree.public")
295+
: t === GameType.Private
296+
? translateText("player_stats_tree.private")
297+
: translateText("player_stats_tree.solo")}
247298
</button>
248299
`,
249300
)}
250301
</div>
251302
252303
<div class="flex gap-2">
304+
<!-- Ranked type selector -->
305+
${this.selectedType === "Ranked" && rankedTypes.length
306+
? html`<div
307+
class="flex gap-1 bg-black/20 rounded-md p-1 border border-white/5"
308+
>
309+
${rankedTypes.map(
310+
(r) => html`
311+
<button
312+
class="text-xs px-3 py-1 rounded-sm transition-colors ${this
313+
.selectedRankedType === r
314+
? "bg-white/20 text-white font-bold"
315+
: "text-gray-400 hover:text-white"}"
316+
@click=${() => this.setRankedType(r)}
317+
>
318+
${this.labelForRankedType(r)}
319+
</button>
320+
`,
321+
)}
322+
</div>`
323+
: html``}
324+
253325
<!-- Mode selector -->
254326
${modes.length
255327
? html`<div

src/core/ApiSchemas.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,17 @@ export const PlayerStatsLeafSchema = z.object({
9898
});
9999
export type PlayerStatsLeaf = z.infer<typeof PlayerStatsLeafSchema>;
100100

101-
export const PlayerStatsTreeSchema = z.partialRecord(
102-
z.enum(GameType),
103-
z.partialRecord(
104-
z.enum(GameMode),
105-
z.partialRecord(z.enum(Difficulty), PlayerStatsLeafSchema),
106-
),
101+
const GameModeStatsSchema = z.partialRecord(
102+
z.enum(GameMode),
103+
z.partialRecord(z.enum(Difficulty), PlayerStatsLeafSchema),
107104
);
105+
106+
export const PlayerStatsTreeSchema = z.object({
107+
Singleplayer: GameModeStatsSchema.optional(),
108+
Public: GameModeStatsSchema.optional(),
109+
Private: GameModeStatsSchema.optional(),
110+
Ranked: z.partialRecord(z.enum(RankedType), PlayerStatsLeafSchema).optional(),
111+
});
108112
export type PlayerStatsTree = z.infer<typeof PlayerStatsTreeSchema>;
109113

110114
export const PlayerGameSchema = z.object({

0 commit comments

Comments
 (0)