diff --git a/apps/discord-bot/src/commands/historical/session.command.tsx b/apps/discord-bot/src/commands/historical/session.command.tsx
index a10cb4972..9fd1049d6 100644
--- a/apps/discord-bot/src/commands/historical/session.command.tsx
+++ b/apps/discord-bot/src/commands/historical/session.command.tsx
@@ -219,7 +219,7 @@ export class SessionCommand {
@SubCommand({ description: (t) => t("commands.session-tntgames"), args: [PlayerArgument] })
public tntgames(context: CommandContext) {
- return this.run(context, TNT_GAMES_MODES, (base) => );
+ return this.run(context, TNT_GAMES_MODES, (base, mode) => );
}
@SubCommand({
diff --git a/apps/discord-bot/src/commands/tntgames/tntgames.command.tsx b/apps/discord-bot/src/commands/tntgames/tntgames.command.tsx
index c50cf76a1..8ac0b1712 100644
--- a/apps/discord-bot/src/commands/tntgames/tntgames.command.tsx
+++ b/apps/discord-bot/src/commands/tntgames/tntgames.command.tsx
@@ -6,9 +6,9 @@
* https://github.com/Statsify/statsify/blob/main/LICENSE
*/
-import { BaseHypixelCommand, BaseProfileProps } from "#commands/base.hypixel-command";
+import { BaseHypixelCommand, type BaseProfileProps, type ProfileData } from "#commands/base.hypixel-command";
import { Command } from "@statsify/discord";
-import { TNTGamesModes, TNT_GAMES_MODES } from "@statsify/schemas";
+import { type TNTGamesModes, TNT_GAMES_MODES } from "@statsify/schemas";
import { TNTGamesProfile } from "./tntgames.profile.js";
@Command({ description: (t) => t("commands.tntgames") })
@@ -17,7 +17,7 @@ export class TNTGamesCommand extends BaseHypixelCommand {
super(TNT_GAMES_MODES);
}
- public getProfile(base: BaseProfileProps): JSX.Element {
- return ;
+ public getProfile(base: BaseProfileProps, { mode }: ProfileData): JSX.Element {
+ return ;
}
}
diff --git a/apps/discord-bot/src/commands/tntgames/tntgames.profile.tsx b/apps/discord-bot/src/commands/tntgames/tntgames.profile.tsx
index 30506e580..e213da2b8 100644
--- a/apps/discord-bot/src/commands/tntgames/tntgames.profile.tsx
+++ b/apps/discord-bot/src/commands/tntgames/tntgames.profile.tsx
@@ -6,28 +6,15 @@
* https://github.com/Statsify/statsify/blob/main/LICENSE
*/
-import { Container, Footer, Header, SidebarItem, Table } from "#components";
-import { FormattedGame } from "@statsify/schemas";
-import { formatTime } from "@statsify/util";
+import { Container, Footer, Header, Historical, SidebarItem, Table, formatProgression } from "#components";
+import { FormattedGame, type GameMode, type TNTGamesModes } from "@statsify/schemas";
+import { formatTime, prettify } from "@statsify/util";
import type { BaseProfileProps } from "#commands/base.hypixel-command";
-interface TNTGamesModeColumnProps {
- title: string;
- stats: [string, string][];
+export interface TNTGamesProfileProps extends BaseProfileProps {
+ mode: GameMode;
}
-const TNTGamesModeColumn = ({ title, stats }: TNTGamesModeColumnProps) => {
- const colors = ["§a", "§c", "§6"];
-
- return (
-
- {stats.map(([title, value], index) => (
-
- ))}
-
- );
-};
-
export const TNTGamesProfile = ({
player,
background,
@@ -36,16 +23,125 @@ export const TNTGamesProfile = ({
t,
badge,
user,
+ mode,
time,
-}: BaseProfileProps) => {
+}: TNTGamesProfileProps) => {
const { tntgames } = player.stats;
const sidebar: SidebarItem[] = [
[t("stats.coins"), t(tntgames.coins), "§6"],
[t("stats.overallWins"), t(tntgames.wins), "§e"],
- [t("stats.blocksRan"), t(tntgames.blocksRan), "§7"],
];
+ let table;
+
+ switch (mode.api) {
+ case "tntRun":
+ table = (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+ break;
+ case "pvpRun":
+ table = (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+ break;
+ case "bowSpleef":
+ table = (
+
+
+
+
+
+
+ );
+ break;
+
+ case "tntTag":
+ sidebar.push([t("stats.powerups"), t(tntgames.tntTag.powerups), "§d"]);
+ table = (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ );
+ break;
+ case "wizards":
+ switch (mode.submode.api) {
+ case "overall":
+ sidebar.push(
+ [t("stats.class"), prettify(tntgames.wizards.class), "§2"],
+ [t("stats.powerOrbs"), t(tntgames.wizards.powerOrbs), "§a"]
+ );
+
+ table = (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+ break;
+ default: {
+ const stats = tntgames.wizards[mode.submode.api];
+
+ table = (
+
+
+
+
+
+
+ );
+ break;
+ }
+ }
+ break;
+ }
+
return (
-
-
-
-
-
-
-
+ {table}
diff --git a/apps/discord-bot/src/constants.ts b/apps/discord-bot/src/constants.ts
index 06b42c9cd..3d3a1b399 100644
--- a/apps/discord-bot/src/constants.ts
+++ b/apps/discord-bot/src/constants.ts
@@ -257,7 +257,7 @@ export const mapBackground = (
return mapBackground(SPEED_UHC_MODES, getDefaultApiMode(SPEED_UHC_MODES));
case "TNT_GAMES":
- return mapBackground(TNT_GAMES_MODES, getDefaultApiMode(TNT_GAMES_MODES));
+ return ["tntgames", "overall"];
case "TURBO_KART_RACERS":
return mapBackground(
@@ -278,7 +278,7 @@ export const mapBackground = (
return mapBackground(WARLORDS_MODES, getDefaultApiMode(WARLORDS_MODES));
case "WOOLGAMES":
- return mapBackground(WOOLGAMES_MODES, getDefaultApiMode(WOOLGAMES_MODES));
+ return ["woolgames", "overall"];
}
throw new Error(`Missing background for mode: ${mode}`);
@@ -322,7 +322,7 @@ export const mapBackground = (
return ["smashheroes", "overall"];
case TNT_GAMES_MODES:
- return ["tntgames", "overall"];
+ return ["tntgames", mode];
case TURBO_KART_RACERS_MODES:
return ["turbokartracers", "overall"];
diff --git a/locales/en-US/default.json b/locales/en-US/default.json
index 255223da5..adb847ff1 100644
--- a/locales/en-US/default.json
+++ b/locales/en-US/default.json
@@ -792,8 +792,11 @@
"arcadeWins": "Arcade Wins",
"draws": "Draws",
"sheepThrown": "Sheep Thrown",
+ "sheepKilled": "Sheep Killed",
"magicWool": "Magic Wool",
- "sheepKilled": "Sheep Killed"
+ "airTime": "Air Time",
+ "potionsSplashed": "Potions Splashed",
+ "powerOrbs": "Power Orbs"
},
"tips": {
"discord": "$t(emojis:socials.discord) Join our **$t(socials.discord)** and get **20% lower cooldowns** $t(emojis:heart)",
diff --git a/packages/discord/src/services/paginate.service.ts b/packages/discord/src/services/paginate.service.ts
index 4237273d1..d53215e53 100644
--- a/packages/discord/src/services/paginate.service.ts
+++ b/packages/discord/src/services/paginate.service.ts
@@ -99,7 +99,6 @@ export class PaginateService {
}
if (index !== currentIndex) {
- // [TODO]: remove all hooks for sub pages
subController?.unregister(listener);
mainController.switchPage(index);
diff --git a/packages/schemas/src/player/gamemodes/prefixes.ts b/packages/schemas/src/player/gamemodes/prefixes.ts
index 8c5ab2b26..7bade860f 100644
--- a/packages/schemas/src/player/gamemodes/prefixes.ts
+++ b/packages/schemas/src/player/gamemodes/prefixes.ts
@@ -124,6 +124,12 @@ export const defaultPrefix = (
) => getFormattedPrefix({ prefixes, score: prefixes[0].req, ...options });
const RAINBOW_COLORS = ["c", "6", "e", "a", "b", "d", "9"];
+const ALT_RAINBOW_COLORS = ["c", "6", "e", "a", "b", "d", "5"];
-export const rainbow = (text: string) =>
- [...text].map((l, i) => `§${RAINBOW_COLORS[i % RAINBOW_COLORS.length]}${l}`).join("");
+export const rainbow = (text: string, useAltPalette = false) => {
+ const colors = useAltPalette ? ALT_RAINBOW_COLORS : RAINBOW_COLORS;
+
+ return [...text]
+ .map((l, i) => `§${colors[i % colors.length]}${l}`)
+ .join("");
+};
diff --git a/packages/schemas/src/player/gamemodes/tntgames/index.ts b/packages/schemas/src/player/gamemodes/tntgames/index.ts
index 7b805c044..556c0a7ec 100644
--- a/packages/schemas/src/player/gamemodes/tntgames/index.ts
+++ b/packages/schemas/src/player/gamemodes/tntgames/index.ts
@@ -12,12 +12,26 @@ import { Field } from "#metadata";
import type { APIData } from "@statsify/util";
export const TNT_GAMES_MODES = new GameModes([
- { api: "overall" },
- { hypixel: "PVPRUN", formatted: "PVP Run" },
- { hypixel: "TNTAG", formatted: "TNT Tag" },
- { hypixel: "TNTRUN", formatted: "TNT Run" },
- { hypixel: "BOWSPLEEF", formatted: "Bow Spleef" },
- { hypixel: "CAPTURE", formatted: "Wizards" },
+ { api: "pvpRun", hypixel: "PVPRUN", formatted: "PVP Run" },
+ { api: "tntTag", hypixel: "TNTAG", formatted: "TNT Tag" },
+ { api: "tntRun", hypixel: "TNTRUN", formatted: "TNT Run" },
+ { api: "bowSpleef", hypixel: "BOWSPLEEF" },
+ {
+ api: "wizards",
+ hypixel: "CAPTURE",
+ submodes: [
+ { api: "overall" },
+ { api: "fireWizard" },
+ { api: "iceWizard" },
+ { api: "witherWizard" },
+ { api: "kineticWizard" },
+ { api: "bloodWizard" },
+ { api: "toxicWizard" },
+ { api: "hydroWizard" },
+ { api: "ancientWizard" },
+ { api: "arcaneWizard" },
+ ],
+ },
] as const);
export type TNTGamesModes = ExtractGameModes;
@@ -29,33 +43,31 @@ export class TNTGames {
@Field()
public wins: number;
- @Field()
- public blocksRan: number;
-
- @Field({ leaderboard: { fieldName: "TNT Run" } })
+ @Field({
+ leaderboard: { fieldName: "TNT Run", extraDisplay: "this.tntRun.naturalPrefix" },
+ })
public tntRun: TNTRun;
- @Field({ leaderboard: { fieldName: "PVP Run" } })
+ @Field({ leaderboard: { fieldName: "PVP Run", extraDisplay: "this.pvpRun.naturalPrefix" } })
public pvpRun: PVPRun;
- @Field()
+ @Field({ leaderboard: { extraDisplay: "this.bowSpleef.naturalPrefix" } })
public bowSpleef: BowSpleef;
- @Field()
+ @Field({ leaderboard: { extraDisplay: "this.wizards.naturalPrefix" } })
public wizards: Wizards;
- @Field({ leaderboard: { fieldName: "TNT Tag" } })
+ @Field({ leaderboard: { fieldName: "TNT Tag", extraDisplay: "this.tntTag.naturalPrefix" } })
public tntTag: TNTTag;
public constructor(data: APIData, ap: APIData) {
this.coins = data.coins;
this.wins = data.wins;
- this.blocksRan = ap.tntgames_block_runner;
- this.tntRun = new TNTRun(data);
+ this.tntRun = new TNTRun(data, ap);
this.pvpRun = new PVPRun(data);
this.bowSpleef = new BowSpleef(data);
- this.wizards = new Wizards(data);
+ this.wizards = new Wizards(data, ap);
this.tntTag = new TNTTag(data, ap);
}
}
diff --git a/packages/schemas/src/player/gamemodes/tntgames/mode.ts b/packages/schemas/src/player/gamemodes/tntgames/mode.ts
index 6d5e7afa4..1d17fca2e 100644
--- a/packages/schemas/src/player/gamemodes/tntgames/mode.ts
+++ b/packages/schemas/src/player/gamemodes/tntgames/mode.ts
@@ -8,26 +8,75 @@
import { type APIData, formatTime } from "@statsify/util";
import { Field } from "#metadata";
+import { type GamePrefix, createPrefixProgression, defaultPrefix, getFormattedPrefix, rainbow } from "#prefixes";
+import { Progression } from "#progression";
import { ratio } from "@statsify/math";
+// Prefixes for TNT Run, PVP Run and Bow Spleef
+const prefixes1: GamePrefix[] = [
+ { fmt: (n) => `§8[${n}]`, req: 0 },
+ { fmt: (n) => `§7[${n}]`, req: 25 },
+ { fmt: (n) => `§f[${n}]`, req: 100 },
+ { fmt: (n) => `§2[${n}]`, req: 250 },
+ { fmt: (n) => `§a[${n}]`, req: 500 },
+ { fmt: (n) => `§9[${n}]`, req: 1000 },
+ { fmt: (n) => `§5[${n}]`, req: 2500 },
+ { fmt: (n) => `§6[${n}]`, req: 5000 },
+ { fmt: (n) => `§c[${n}]`, req: 7500 },
+ { fmt: (n) => `§0[${n}]`, req: 10_000 },
+ { fmt: (n) => rainbow(`[${n}]`, true), req: 15_000 },
+];
+
export class BowSpleef {
@Field()
public wins: number;
+ @Field()
+ public losses: number;
+
+ @Field()
+ public wlr: number;
+
@Field({ leaderboard: { enabled: false } })
public hits: number;
@Field()
- public losses: number;
+ public progression: Progression;
@Field()
- public wlr: number;
+ public currentPrefix: string;
+
+ @Field({ store: { default: defaultPrefix(prefixes1) } })
+ public naturalPrefix: string;
+
+ @Field()
+ public nextPrefix: string;
public constructor(data: APIData) {
this.wins = data.wins_bowspleef;
this.hits = data.tags_bowspleef;
this.losses = data.deaths_bowspleef;
this.wlr = ratio(this.wins, this.losses);
+
+ const score = this.wins ?? 0;
+
+ this.currentPrefix = getFormattedPrefix({ prefixes: prefixes1, score, abbreviation: false });
+
+ this.naturalPrefix = getFormattedPrefix({
+ prefixes: prefixes1,
+ score,
+ trueScore: true,
+ abbreviation: false,
+ });
+
+ this.nextPrefix = getFormattedPrefix({
+ prefixes: prefixes1,
+ score,
+ skip: true,
+ abbreviation: false,
+ });
+
+ this.progression = createPrefixProgression(prefixes1, score);
}
}
@@ -44,11 +93,47 @@ export class PVPRun {
@Field()
public kdr: number;
+ @Field({ leaderboard: { formatter: formatTime }, historical: { enabled: false } })
+ public record: number;
+
+ @Field()
+ public progression: Progression;
+
+ @Field()
+ public currentPrefix: string;
+
+ @Field({ store: { default: defaultPrefix(prefixes1) } })
+ public naturalPrefix: string;
+
+ @Field()
+ public nextPrefix: string;
+
public constructor(data: APIData) {
this.wins = data.wins_pvprun;
this.kills = data.kills_pvprun;
this.deaths = data.deaths_pvprun;
this.kdr = ratio(this.kills, this.deaths);
+ this.record = (data.record_pvprun ?? 0) * 1000;
+
+ const score = this.wins ?? 0;
+
+ this.currentPrefix = getFormattedPrefix({ prefixes: prefixes1, score, abbreviation: false });
+
+ this.naturalPrefix = getFormattedPrefix({
+ prefixes: prefixes1,
+ score,
+ trueScore: true,
+ abbreviation: false,
+ });
+
+ this.nextPrefix = getFormattedPrefix({
+ prefixes: prefixes1,
+ score,
+ skip: true,
+ abbreviation: false,
+ });
+
+ this.progression = createPrefixProgression(prefixes1, score);
}
}
@@ -65,14 +150,69 @@ export class TNTRun {
@Field({ leaderboard: { formatter: formatTime }, historical: { enabled: false } })
public record: number;
- public constructor(data: APIData) {
+ @Field()
+ public potionsSplashed: number;
+
+ @Field()
+ public blocksRan: number;
+
+ @Field()
+ public progression: Progression;
+
+ @Field()
+ public currentPrefix: string;
+
+ @Field({ store: { default: defaultPrefix(prefixes1) } })
+ public naturalPrefix: string;
+
+ @Field()
+ public nextPrefix: string;
+
+ public constructor(data: APIData, ap: APIData) {
this.wins = data.wins_tntrun;
this.losses = data.deaths_tntrun;
this.wlr = ratio(this.wins, this.losses);
this.record = (data.record_tntrun ?? 0) * 1000;
+ this.potionsSplashed = data.run_potions_splashed_on_players;
+ this.blocksRan = ap?.tntgames_block_runner;
+
+ const score = this.wins ?? 0;
+
+ this.currentPrefix = getFormattedPrefix({ prefixes: prefixes1, score, abbreviation: false });
+
+ this.naturalPrefix = getFormattedPrefix({
+ prefixes: prefixes1,
+ score,
+ trueScore: true,
+ abbreviation: false,
+ });
+
+ this.nextPrefix = getFormattedPrefix({
+ prefixes: prefixes1,
+ score,
+ skip: true,
+ abbreviation: false,
+ });
+
+ this.progression = createPrefixProgression(prefixes1, score);
}
}
+// Prefixes for TNT Tag and Wizards
+const prefixes2: GamePrefix[] = [
+ { fmt: (n) => `§8[${n}]`, req: 0 },
+ { fmt: (n) => `§7[${n}]`, req: 15 },
+ { fmt: (n) => `§f[${n}]`, req: 50 },
+ { fmt: (n) => `§2[${n}]`, req: 100 },
+ { fmt: (n) => `§a[${n}]`, req: 250 },
+ { fmt: (n) => `§9[${n}]`, req: 500 },
+ { fmt: (n) => `§5[${n}]`, req: 1000 },
+ { fmt: (n) => `§6[${n}]`, req: 1500 },
+ { fmt: (n) => `§c[${n}]`, req: 2500 },
+ { fmt: (n) => `§0[${n}]`, req: 5000 },
+ { fmt: (n) => rainbow(`[${n}]`, true), req: 10_000 },
+];
+
export class TNTTag {
@Field()
public wins: number;
@@ -80,17 +220,85 @@ export class TNTTag {
@Field()
public kills: number;
- @Field({ leaderboard: { enabled: false } })
+ @Field()
+ public deaths: number;
+
+ @Field()
+ public kdr: number;
+
+ @Field()
public tags: number;
+ @Field({ leaderboard: { name: "Power-Ups" } })
+ public powerups: number;
+
+ @Field()
+ public progression: Progression;
+
+ @Field()
+ public currentPrefix: string;
+
+ @Field({ store: { default: defaultPrefix(prefixes2) } })
+ public naturalPrefix: string;
+
+ @Field()
+ public nextPrefix: string;
+
public constructor(data: APIData, ap: APIData) {
this.wins = data.wins_tntag;
this.kills = data.kills_tntag;
+ this.deaths = data.deaths_tntag;
+ this.kdr = ratio(this.kills, this.deaths);
this.tags = ap?.tntgames_clinic;
+ this.powerups = ap?.tntgames_the_upper_hand;
+
+ const score = this.wins ?? 0;
+
+ this.currentPrefix = getFormattedPrefix({ prefixes: prefixes2, score, abbreviation: false });
+
+ this.naturalPrefix = getFormattedPrefix({
+ prefixes: prefixes2,
+ score,
+ trueScore: true,
+ abbreviation: false,
+ });
+
+ this.nextPrefix = getFormattedPrefix({
+ prefixes: prefixes2,
+ score,
+ skip: true,
+ abbreviation: false,
+ });
+
+ this.progression = createPrefixProgression(prefixes2, score);
+ }
+}
+
+export class WizardsClass {
+ @Field()
+ public kills: number;
+
+ @Field()
+ public deaths: number;
+
+ @Field()
+ public kdr: number;
+
+ @Field()
+ public assists: number;
+
+ public constructor(data: APIData, clazz: string) {
+ this.kills = data[`new_${clazz}_kills`];
+ this.deaths = data[`new_${clazz}_deaths`];
+ this.kdr = ratio(this.kills, this.deaths);
+ this.assists = data[`new_${clazz}_assists`];
}
}
export class Wizards {
+ @Field({ store: { default: "none" } })
+ public class: string;
+
@Field()
public wins: number;
@@ -103,10 +311,98 @@ export class Wizards {
@Field()
public kdr: number;
- public constructor(data: APIData) {
+ @Field()
+ public assists: number;
+
+ @Field()
+ public points: number;
+
+ @Field({ leaderboard: { formatter: formatTime } })
+ public airTime: number;
+
+ @Field()
+ public powerOrbs: number;
+
+ @Field()
+ public progression: Progression;
+
+ @Field()
+ public currentPrefix: string;
+
+ @Field({ store: { default: defaultPrefix(prefixes2) } })
+ public naturalPrefix: string;
+
+ @Field()
+ public nextPrefix: string;
+
+ @Field()
+ public fireWizard: WizardsClass;
+
+ @Field()
+ public iceWizard: WizardsClass;
+
+ @Field()
+ public witherWizard: WizardsClass;
+
+ @Field()
+ public kineticWizard: WizardsClass;
+
+ @Field()
+ public bloodWizard: WizardsClass;
+
+ @Field()
+ public toxicWizard: WizardsClass;
+
+ @Field()
+ public hydroWizard: WizardsClass;
+
+ @Field()
+ public ancientWizard: WizardsClass;
+
+ @Field()
+ public arcaneWizard: WizardsClass;
+
+ public constructor(data: APIData, ap: APIData) {
+ this.class = data.wizards_selected_class ?? "none";
+ // Hypixel doesn't capitalize the word "Wizard" so the class name cant't be pretty printed
+ this.class = this.class.replace("new_", "").replace("wizard", "Wizard");
+
this.wins = data.wins_capture;
this.kills = data.kills_capture;
this.deaths = data.deaths_capture;
this.kdr = ratio(this.kills, this.deaths);
+ this.assists = data.assists_capture;
+ this.points = data.points_capture;
+ this.airTime = data.air_time_capture;
+ this.powerOrbs = ap?.tntgames_power_hungry;
+
+ const score = this.wins ?? 0;
+
+ this.currentPrefix = getFormattedPrefix({ prefixes: prefixes2, score });
+
+ this.naturalPrefix = getFormattedPrefix({
+ prefixes: prefixes2,
+ score,
+ trueScore: true,
+ });
+
+ this.nextPrefix = getFormattedPrefix({
+ prefixes: prefixes2,
+ score,
+ skip: true,
+ });
+
+ this.progression = createPrefixProgression(prefixes2, score);
+
+ this.fireWizard = new WizardsClass(data, "firewizard");
+ this.iceWizard = new WizardsClass(data, "icewizard");
+ this.witherWizard = new WizardsClass(data, "witherwizard");
+ this.kineticWizard = new WizardsClass(data, "kineticwizard");
+ this.bloodWizard = new WizardsClass(data, "bloodwizard");
+ this.toxicWizard = new WizardsClass(data, "toxicwizard");
+ this.hydroWizard = new WizardsClass(data, "hydrowizard");
+ this.ancientWizard = new WizardsClass(data, "ancientwizard");
+ this.arcaneWizard = new WizardsClass(data, "arcanewizard");
}
}
+
diff --git a/packages/schemas/src/player/gamemodes/woolgames/woolwars.ts b/packages/schemas/src/player/gamemodes/woolgames/woolwars.ts
index 055f4f12d..78693da8e 100644
--- a/packages/schemas/src/player/gamemodes/woolgames/woolwars.ts
+++ b/packages/schemas/src/player/gamemodes/woolgames/woolwars.ts
@@ -23,7 +23,7 @@ export class WoolWarsClass {
@Field()
public assists: number;
- @Field()
+ @Field({ leaderboard: { name: "Power-Ups" } })
public powerups: number;
@Field({ leaderboard: { additionalFields: ["this.woolPlaced"] } })