Skip to content

Commit 5345a3e

Browse files
authored
feat(arcade): add in-depth zombies map stats (#741)
* feat(arcade): add in-depth zombies map stats * make backgrounds vary with submode * add submode emojis to session * update assets
1 parent 1b1248d commit 5345a3e

File tree

17 files changed

+226
-44
lines changed

17 files changed

+226
-44
lines changed

apps/discord-bot/src/commands/arcade/arcade.command.tsx

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66
* https://github.com/Statsify/statsify/blob/main/LICENSE
77
*/
88

9-
import { ARCADE_MODES, ArcadeModes } from "@statsify/schemas";
9+
import { ARCADE_MODES, ApiModeFromGameModes, ArcadeModes, GameModeWithSubModes, SubModeForMode } from "@statsify/schemas";
1010
import { ArcadeProfile } from "./arcade.profile.js";
1111
import {
1212
BaseHypixelCommand,
1313
BaseProfileProps,
14+
ModeEmoji,
1415
ProfileData,
1516
} from "#commands/base.hypixel-command";
1617
import { Command } from "@statsify/discord";
@@ -21,10 +22,35 @@ export class ArcadeCommand extends BaseHypixelCommand<ArcadeModes> {
2122
super(ARCADE_MODES);
2223
}
2324

25+
public getModeEmojis(modes: GameModeWithSubModes<ArcadeModes>[]): ModeEmoji[] {
26+
return getArcadeModeEmojis(modes);
27+
}
28+
29+
public getSubModeEmojis<M extends ApiModeFromGameModes<ArcadeModes>>(
30+
mode: M,
31+
submodes: SubModeForMode<ArcadeModes, M>[]
32+
): ModeEmoji[] {
33+
return getArcadeSubModeEmojis(mode, submodes);
34+
}
35+
2436
public getProfile(
2537
base: BaseProfileProps,
2638
{ mode }: ProfileData<ArcadeModes>
2739
): JSX.Element {
2840
return <ArcadeProfile {...base} mode={mode} />;
2941
}
3042
}
43+
44+
export function getArcadeModeEmojis(modes: GameModeWithSubModes<ArcadeModes>[]): ModeEmoji[] {
45+
return modes.map((mode) => (t) => t(`emojis:arcade.${mode.api}`));
46+
}
47+
48+
export function getArcadeSubModeEmojis<M extends ApiModeFromGameModes<ArcadeModes>>(
49+
mode: M,
50+
submodes: SubModeForMode<ArcadeModes, M>[]
51+
): ModeEmoji[] {
52+
if (mode === "zombies")
53+
return submodes.map((submode) => (t) => t(`emojis:zombies.${submode.api}`));
54+
55+
return [];
56+
}

apps/discord-bot/src/commands/arcade/arcade.profile.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
PixelPartyTable,
2828
SeasonalTable,
2929
ThrowOutTable,
30+
ZombiesMapTable,
3031
ZombiesTable,
3132
} from "./modes/index.js";
3233
import { Container, Footer, Header, SidebarItem } from "#components";
@@ -55,7 +56,7 @@ export const ArcadeProfile = ({
5556
[t("stats.arcadeWins"), t(arcade.wins), "§b"],
5657
];
5758

58-
const { api } = mode;
59+
const { api, submode } = mode;
5960
let table: JSX.Element;
6061

6162
switch (api) {
@@ -132,7 +133,9 @@ export const ArcadeProfile = ({
132133
break;
133134

134135
case "zombies":
135-
table = <ZombiesTable stats={arcade[api]} t={t} time={time} />;
136+
table = submode.api === "overall" ?
137+
<ZombiesTable stats={arcade[api]} t={t} time={time} /> :
138+
<ZombiesMapTable stats={arcade[api][submode.api]} t={t} time={time} />;
136139
break;
137140

138141
default:

apps/discord-bot/src/commands/arcade/modes/dropper.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66
* https://github.com/Statsify/statsify/blob/main/LICENSE
77
*/
88

9-
import { type ArcadeModes, type Dropper, DropperMaps, MetadataScanner, type SubModesForMode } from "@statsify/schemas";
9+
import { type ArcadeModes, type Dropper, DropperMaps, MetadataScanner, type SubModeForMode } from "@statsify/schemas";
1010
import { Historical, If, Table } from "#components";
1111
import { arrayGroup, formatRaceTime, formatTime } from "@statsify/util";
1212
import type { LocalizeFunction } from "@statsify/discord";
1313
import type { ProfileTime } from "#commands/base.hypixel-command";
1414

1515
interface DropperTableProps {
1616
stats: Dropper;
17-
submode: SubModesForMode<ArcadeModes, "dropper">;
17+
submode: SubModeForMode<ArcadeModes, "dropper">;
1818
t: LocalizeFunction;
1919
time: ProfileTime;
2020
}
@@ -61,7 +61,7 @@ const EASY_DROPPER_MAP_GROUPS = arrayGroup(DROPPER_MAPS.filter(([_, name]) => na
6161
interface DropperMapsTableProps {
6262
dropper: Dropper;
6363
t: LocalizeFunction;
64-
stat: Exclude<SubModesForMode<ArcadeModes, "dropper">["api"], "overall">;
64+
stat: Exclude<SubModeForMode<ArcadeModes, "dropper">["api"], "overall">;
6565
}
6666

6767
const DropperMapsTable = ({ dropper, t, stat }: DropperMapsTableProps) => (

apps/discord-bot/src/commands/arcade/modes/party-games.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@
99
import { Historical, Table } from "#components";
1010
import { ProfileTime } from "#commands/base.hypixel-command";
1111
import { formatRaceTime } from "@statsify/util";
12-
import type { ArcadeModes, PartyGames, SubModesForMode } from "@statsify/schemas";
12+
import type { ArcadeModes, PartyGames, SubModeForMode } from "@statsify/schemas";
1313
import type { LocalizeFunction } from "@statsify/discord";
1414

1515
interface PartyGamesTableProps {
1616
stats: PartyGames;
17-
submode: SubModesForMode<ArcadeModes, "partyGames">;
17+
submode: SubModeForMode<ArcadeModes, "partyGames">;
1818
t: LocalizeFunction;
1919
time: ProfileTime;
2020
}

apps/discord-bot/src/commands/arcade/modes/zombies.tsx

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import { Historical, Table } from "#components";
1010
import { LocalizeFunction } from "@statsify/discord";
11-
import { Zombies, ZombiesMap } from "@statsify/schemas";
11+
import { Zombies, ZombiesMap, ZombiesMapDifficulty } from "@statsify/schemas";
1212
import { formatTime } from "@statsify/util";
1313
import type { ProfileTime } from "#commands/base.hypixel-command";
1414

@@ -21,13 +21,13 @@ interface ZombiesMapColumnProps {
2121

2222
const ZombiesMapColumn = ({ title, stats, t, time }: ZombiesMapColumnProps) => {
2323
const mapStat =
24-
stats.wins > 0 ?
25-
[t("stats.fastestWin"), formatTime(stats.fastestWin)] :
26-
[t("stats.bestRound"), t(stats.bestRound)];
24+
(stats.overall.bestRound >= 30 && stats.overall.wins >= 1) ?
25+
[t("stats.fastestWin"), formatTime(stats.overall.fastestWin)] :
26+
[t("stats.bestRound"), t(stats.overall.bestRound)];
2727

2828
return (
2929
<Table.ts title={title}>
30-
<Table.td title={t("stats.wins")} value={t(stats.wins)} color="§a" size="small" />
30+
<Table.td title={t("stats.wins")} value={t(stats.overall.wins)} color="§a" size="small" />
3131
<Historical.exclude time={time}>
3232
<Table.td title={mapStat[0]} value={mapStat[1]} color="§e" size="small" />
3333
</Historical.exclude>
@@ -72,3 +72,47 @@ export const ZombiesTable = ({ stats, t, time }: ZombiesTableProps) => {
7272
</Table.table>
7373
);
7474
};
75+
76+
interface ZombiesMapDifficultyTableProps {
77+
stats: ZombiesMapDifficulty;
78+
t: LocalizeFunction;
79+
time: ProfileTime;
80+
difficulty: string;
81+
}
82+
83+
export const ZombiesMapDifficultyTable = ({ stats, t, time, difficulty }: ZombiesMapDifficultyTableProps) => (
84+
<Table.ts title={difficulty}>
85+
<Table.tr>
86+
{time === "LIVE" && stats.wins === 0 ?
87+
<Table.td title={t("stats.bestRound")} value={t(stats.bestRound)} color="§a" /> :
88+
<Table.td title={t("stats.wins")} value={t(stats.wins)} color="§a" />}
89+
<Table.td title={t("stats.kills")} value={t(stats.kills)} color="§e" />
90+
<Table.td title={t("stats.deaths")} value={t(stats.deaths)} color="§c" />
91+
</Table.tr>
92+
<Historical.exclude time={time}>
93+
<Table.tr>
94+
<Table.td title={t("stats.fastestWin")} value={stats.fastestWin ? formatTime(stats.fastestWin) : "N/A"} color="§b" size="small" />
95+
<Table.td title={t("stats.totalRounds")} value={t(stats.totalRounds)} color="§d" size="small" />
96+
</Table.tr>
97+
</Historical.exclude>
98+
</Table.ts>
99+
);
100+
101+
export interface ZombiesMapTableProps {
102+
stats: ZombiesMap;
103+
t: LocalizeFunction;
104+
time: ProfileTime;
105+
}
106+
107+
export const ZombiesMapTable = ({ stats, t, time }: ZombiesMapTableProps) => (
108+
<Table.table>
109+
<Table.tr>
110+
<ZombiesMapDifficultyTable difficulty="§6Overall" stats={stats.overall} t={t} time={time} />
111+
<ZombiesMapDifficultyTable difficulty="§aNormal" stats={stats.normal} t={t} time={time} />
112+
</Table.tr>
113+
<Table.tr>
114+
<ZombiesMapDifficultyTable difficulty="§cHard" stats={stats.hard} t={t} time={time} />
115+
<ZombiesMapDifficultyTable difficulty="§4RIP" stats={stats.rip} t={t} time={time} />
116+
</Table.tr>
117+
</Table.table>
118+
);

apps/discord-bot/src/commands/base.hypixel-command.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { getBackground, getLogo } from "@statsify/assets";
2424
import { getTheme } from "#themes";
2525
import { noop } from "@statsify/util";
2626
import { render } from "@statsify/rendering";
27-
import type { GameMode, GameModeWithSubModes, GameModes, Player, User } from "@statsify/schemas";
27+
import type { ApiModeFromGameModes, ApiSubModeForMode, GameMode, GameModeWithSubModes, GameModes, Player, SubModeForMode, User } from "@statsify/schemas";
2828
import type { Image } from "skia-canvas";
2929

3030
export type ProfileTime = "LIVE" | HistoricalTimeData;
@@ -52,6 +52,7 @@ export interface BaseHypixelCommand<T extends GamesWithBackgrounds, K = never> {
5252
filterModes?(player: Player, modes: GameModeWithSubModes<T>[]): GameModeWithSubModes<T>[];
5353
filterSubmodes?(player: Player, mode: GameModeWithSubModes<T>): GameModeWithSubModes<T>["submodes"];
5454
getModeEmojis?(modes: GameModeWithSubModes<T>[]): ModeEmoji[];
55+
getSubModeEmojis?<M extends ApiModeFromGameModes<T>>(mode: M, submodes: SubModeForMode<T, M>[]): ModeEmoji[];
5556
}
5657

5758
@Command({
@@ -92,6 +93,10 @@ export abstract class BaseHypixelCommand<T extends GamesWithBackgrounds, K = nev
9293
};
9394

9495
const filteredSubmodes = this.filterSubmodes?.(player, mode) ?? mode.submodes;
96+
const submodeEmojis = this.getSubModeEmojis?.(
97+
mode.api,
98+
filteredSubmodes as SubModeForMode<T, (typeof mode)["api"]>[]
99+
) ?? [];
95100

96101
if (filteredSubmodes.length === 0) {
97102
const gameMode = { ...mode, submode: undefined } as unknown as GameMode<T>;
@@ -120,10 +125,11 @@ export abstract class BaseHypixelCommand<T extends GamesWithBackgrounds, K = nev
120125
};
121126
}
122127

123-
const subPages = filteredSubmodes.map((submode): SubPage => ({
128+
const subPages = filteredSubmodes.map((submode, index): SubPage => ({
124129
label: submode.formatted,
130+
emoji: submodeEmojis[index],
125131
generator: async (t) => {
126-
const background = await getBackground(...mapBackground(this.modes, mode.api));
132+
const background = await getBackground(...mapBackground(this.modes, mode.api, submode.api as ApiSubModeForMode<T, (typeof mode)["api"]>));
127133

128134
const gameMode = {
129135
api: mode.api,

apps/discord-bot/src/commands/historical/session.command.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@
99
import {
1010
ARCADE_MODES,
1111
ARENA_BRAWL_MODES,
12+
type ApiModeFromGameModes,
13+
ApiSubModeForMode,
1214
BEDWARS_MODES,
1315
BLITZSG_MODES,
1416
BUILD_BATTLE_MODES,
1517
COPS_AND_CRIMS_MODES,
1618
DUELS_MODES,
1719
GENERAL_MODES,
1820
GameMode,
19-
GameModeWithSubModes,
21+
type GameModeWithSubModes,
2022
GameModes,
2123
MEGAWALLS_MODES,
2224
MURDER_MYSTERY_MODES,
@@ -27,6 +29,7 @@ import {
2729
SKYWARS_MODES,
2830
SMASH_HEROES_MODES,
2931
SPEED_UHC_MODES,
32+
type SubModeForMode,
3033
TNT_GAMES_MODES,
3134
TURBO_KART_RACERS_MODES,
3235
UHC_MODES,
@@ -75,6 +78,7 @@ import { WarlordsProfile } from "../warlords/warlords.profile.js";
7578
import { WoolGamesProfile } from "../woolgames/woolgames.profile.js";
7679
import { filterBlitzKits } from "../blitzsg/blitzsg.command.js";
7780
import { filterMegaWallsKits } from "../megawalls/megawalls.command.js";
81+
import { getArcadeModeEmojis, getArcadeSubModeEmojis } from "../arcade/arcade.command.js";
7882
import { getBackground, getLogo } from "@statsify/assets";
7983
import { getDuelsModeEmojis } from "../duels/duels.command.js";
8084
import { getTheme } from "#themes";
@@ -100,6 +104,8 @@ export class SessionCommand {
100104
if (mode.api === "partyGames") return mode.submodes.filter((submode) => submode.api !== "roundWins");
101105
return mode.submodes;
102106
},
107+
getModeEmojis: getArcadeModeEmojis,
108+
getSubModeEmojis: getArcadeSubModeEmojis,
103109
});
104110
}
105111

@@ -351,13 +357,15 @@ export class SessionCommand {
351357
filterModes,
352358
filterSubmodes,
353359
getModeEmojis,
360+
getSubModeEmojis,
354361
}: {
355362
context: CommandContext;
356363
modes: GameModes<T>;
357364
getProfile: (base: Omit<BaseProfileProps, "time"> & { time: HistoricalTimeData }, mode: GameMode<T>) => JSX.Element;
358365
filterModes?: (player: Player, modes: GameModeWithSubModes<T>[]) => GameModeWithSubModes<T>[];
359366
filterSubmodes?: (player: Player, mode: GameModeWithSubModes<T>) => GameModeWithSubModes<T>["submodes"];
360367
getModeEmojis?(modes: GameModeWithSubModes<T>[]): ModeEmoji[];
368+
getSubModeEmojis?<M extends ApiModeFromGameModes<T>>(mode: M, submodes: SubModeForMode<T, M>[]): ModeEmoji[];
361369
}) {
362370
const user = context.getUser();
363371

@@ -426,10 +434,13 @@ export class SessionCommand {
426434
},
427435
};
428436

429-
const subPages = submodes.map((submode): SubPage => ({
437+
const submodeEmojis = getSubModeEmojis ? getSubModeEmojis(mode.api, submodes) : [];
438+
439+
const subPages = submodes.map((submode, index): SubPage => ({
430440
label: submode.formatted,
441+
emoji: submodeEmojis[index],
431442
generator: async (t) => {
432-
const background = await getBackground(...mapBackground(modes, mode.api));
443+
const background = await getBackground(...mapBackground(modes, mode.api, submode.api as ApiSubModeForMode<T, (typeof mode)["api"]>));
433444

434445
const profile = getProfile(
435446
{

apps/discord-bot/src/commands/quests/quests.command.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
PlayerArgument,
1616
SubCommand,
1717
} from "@statsify/discord";
18-
import { GameModes, QUEST_MODES, QuestModes, QuestTime } from "@statsify/schemas";
18+
import { QUEST_MODES, QuestTime } from "@statsify/schemas";
1919
import { QuestProfileProps, QuestsProfile } from "./quests.profile.js";
2020
import { getAllGameIcons, getBackground, getLogo } from "@statsify/assets";
2121
import { getTheme } from "#themes";
@@ -24,7 +24,7 @@ import { render } from "@statsify/rendering";
2424

2525
@Command({ description: (t) => t("commands.quests") })
2626
export class QuestsCommand {
27-
private readonly modes: GameModes<QuestModes> = QUEST_MODES;
27+
private readonly modes = QUEST_MODES;
2828

2929
public constructor(
3030
private readonly apiService: ApiService,

apps/discord-bot/src/commands/ratios/ratios.command.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -303,8 +303,7 @@ export class RatiosCommand {
303303

304304
if (mode.submodes.length === 0) return modeType;
305305

306-
// @ts-expect-error TypeScript doesn't realize that an api field will always be present when submodes is not empty
307-
const submode = mode.submodes[0].api as string;
306+
const submode = mode.submodes[0].api;
308307
const submodeType = Reflect.getMetadata("design:type", modeType.prototype, submode);
309308
return submode === "overall" ? submodeType || modeType : submodeType;
310309
}

apps/discord-bot/src/commands/woolgames/woolwars.table.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88

99
import { If, Table } from "#components";
1010
import type { LocalizeFunction } from "@statsify/discord";
11-
import type { SubModesForMode, WoolGamesModes, WoolWars, WoolWarsOverall } from "@statsify/schemas";
11+
import type { SubModeForMode, WoolGamesModes, WoolWars, WoolWarsOverall } from "@statsify/schemas";
1212

1313
export interface WoolWarsTableProps {
1414
woolwars: WoolWars;
15-
submode: SubModesForMode<WoolGamesModes, "woolwars">;
15+
submode: SubModeForMode<WoolGamesModes, "woolwars">;
1616
t: LocalizeFunction;
1717
}
1818

apps/discord-bot/src/constants.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
ARCADE_MODES,
1111
ARENA_BRAWL_MODES,
1212
ApiModeFromGameModes,
13+
ApiSubModeForMode,
1314
ArcadeModes,
1415
ArenaBrawlModes,
1516
BEDWARS_MODES,
@@ -101,9 +102,10 @@ export type GamesWithBackgrounds =
101102
| QuestModes
102103
| ChallengeModes;
103104

104-
export const mapBackground = <T extends GamesWithBackgrounds>(
105+
export const mapBackground = <T extends GamesWithBackgrounds, M extends ApiModeFromGameModes<T>>(
105106
modes: GameModes<T>,
106-
mode: ApiModeFromGameModes<T>
107+
mode: M,
108+
submode?: ApiSubModeForMode<T, M>
107109
): [game: string, mode: string] => {
108110
switch (modes) {
109111
case BEDWARS_MODES: {
@@ -136,7 +138,9 @@ export const mapBackground = <T extends GamesWithBackgrounds>(
136138
return ["bedwars", map];
137139
}
138140
case ARCADE_MODES:
139-
return ["arcade", mode === "seasonal" ? "overall" : mode];
141+
if (mode === "seasonal") return ["arcade", "overall"];
142+
if (mode === "zombies" && submode !== "overall") return ["arcade", `zombies_${submode}`];
143+
return ["arcade", mode];
140144

141145
case ARENA_BRAWL_MODES:
142146
return ["arenabrawl", "overall"];

assets/private

locales/en-US/default.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,7 @@
794794
"positions": "Positions",
795795
"powerupsCollected": "Power-Ups Collected",
796796
"roundsCompleted": "Rounds Completed",
797+
"totalRounds": "Total Rounds",
797798
"totalExp": "Total EXP",
798799
"gamesFinished": "Games Finished",
799800
"flawlessGames": "Flawless Games",

0 commit comments

Comments
 (0)