From 9cd7480a4bfb05465c82c511cf14d6219e2b04d2 Mon Sep 17 00:00:00 2001 From: Cody Date: Tue, 12 May 2026 01:34:03 -0600 Subject: [PATCH] feat(duels): update titles and progression logic for arena mode --- .../src/commands/duels/duels.profile.tsx | 2 +- .../commands/duels/tables/titles.table.tsx | 81 ++++++++++++------- .../src/player/gamemodes/duels/index.ts | 5 +- .../src/player/gamemodes/duels/mode.ts | 16 +++- .../src/player/gamemodes/duels/util.ts | 10 ++- 5 files changed, 78 insertions(+), 36 deletions(-) diff --git a/apps/discord-bot/src/commands/duels/duels.profile.tsx b/apps/discord-bot/src/commands/duels/duels.profile.tsx index d45cfdd5a..ec8f31f80 100644 --- a/apps/discord-bot/src/commands/duels/duels.profile.tsx +++ b/apps/discord-bot/src/commands/duels/duels.profile.tsx @@ -131,7 +131,7 @@ export const DuelsProfile = ({ duels[mode.api].titleFormatted }\n${formatProgression({ t, - label: t("stats.progression.win"), + label: t(mode.api === "arena" ? "stats.progression.kill" : "stats.progression.win"), progression: duels[mode.api].progression, currentLevel: duels[mode.api].titleLevelFormatted, nextLevel: duels[mode.api].nextTitleLevelFormatted, diff --git a/apps/discord-bot/src/commands/duels/tables/titles.table.tsx b/apps/discord-bot/src/commands/duels/tables/titles.table.tsx index 25319fcd7..b7bc933de 100644 --- a/apps/discord-bot/src/commands/duels/tables/titles.table.tsx +++ b/apps/discord-bot/src/commands/duels/tables/titles.table.tsx @@ -6,19 +6,19 @@ * https://github.com/Statsify/statsify/blob/main/LICENSE */ +import * as Schemas from "@statsify/schemas"; import { arrayGroup } from "@statsify/util"; -import type { Duels } from "@statsify/schemas"; import type { DuelsModeIcons } from "../duels.command.js"; import type { Image } from "skia-canvas"; import type { LocalizeFunction } from "@statsify/discord"; interface TitlesTableProps { - duels: Duels; + duels: Schemas.Duels; t: LocalizeFunction; modeIcons: DuelsModeIcons; } -function ModeTitle({ icon, title, wins, t }: { icon: Image; title: string; wins: number; t: LocalizeFunction }) { +function ModeTitle({ icon, title, score, t }: { icon: Image; title: string; score: number; t: LocalizeFunction }) { return ( @@ -26,50 +26,77 @@ function ModeTitle({ icon, title, wins, t }: { icon: Image; title: string; wins: {title}
- {t(wins)} + {t(score)} ); } export const TitlesTable = ({ duels, t, modeIcons }: TitlesTableProps) => { + const titleData = { active_prefix_icon: duels.icon, active_prefix_scheme: duels.scheme }; + const getBaseTitle = (score: number, mode: string, titleRequirement: Schemas.TitleRequirement) => + Schemas.getTitleAndProgression({ + score, + mode, + data: titleData, + titleRequirement, + scheme: "default", + }).titleFormatted; + + const titleSortScore = (score: number, titleRequirement: Schemas.TitleRequirement) => { + if (titleRequirement === "half") return score * 2; + if (titleRequirement === "overall") return score / 2; + return score; + }; + const games = [ - { icon: modeIcons.bedwars, title: duels.bedwars.titleFormatted, wins: duels.bedwars.overall.wins }, - { icon: modeIcons.blitzsg, title: duels.blitzsg.titleFormatted, wins: duels.blitzsg.wins }, - { icon: modeIcons.bow, title: duels.bow.titleFormatted, wins: duels.bow.wins }, - { icon: modeIcons.spleef, title: duels.spleef.titleFormatted, wins: duels.spleef.overallWins }, - { icon: modeIcons.boxing, title: duels.boxing.titleFormatted, wins: duels.boxing.wins }, - { icon: modeIcons.bridge, title: duels.bridge.titleFormatted, wins: duels.bridge.overall.wins }, - { icon: modeIcons.classic, title: duels.classic.titleFormatted, wins: duels.classic.overall.wins }, - { icon: modeIcons.combo, title: duels.combo.titleFormatted, wins: duels.combo.wins }, - { icon: modeIcons.megawalls, title: duels.megawalls.titleFormatted, wins: duels.megawalls.wins }, - { icon: modeIcons.nodebuff, title: duels.nodebuff.titleFormatted, wins: duels.nodebuff.wins }, - { icon: modeIcons.op, title: duels.op.titleFormatted, wins: duels.op.overall.wins }, - { icon: modeIcons.quake, title: duels.quake.titleFormatted, wins: duels.quake.wins }, - { icon: modeIcons.parkour, title: duels.parkour.titleFormatted, wins: duels.parkour.wins }, - { icon: modeIcons.skywars, title: duels.skywars.titleFormatted, wins: duels.skywars.overall.wins }, - { icon: modeIcons.sumo, title: duels.sumo.titleFormatted, wins: duels.sumo.wins }, - { icon: modeIcons.uhc, title: duels.uhc.titleFormatted, wins: duels.uhc.overall.wins }, - ]; + { icon: modeIcons.arena, mode: "Duel Arena", score: duels.arena.kills, titleRequirement: "default" }, + { icon: modeIcons.bedwars, mode: "Bed Wars", score: duels.bedwars.overall.wins, titleRequirement: "default" }, + { icon: modeIcons.blitzsg, mode: "Blitz", score: duels.blitzsg.wins, titleRequirement: "default" }, + { icon: modeIcons.bow, mode: "Bow", score: duels.bow.wins, titleRequirement: "default" }, + { icon: modeIcons.spleef, mode: "Spleef", score: duels.spleef.overallWins, titleRequirement: "default" }, + { icon: modeIcons.boxing, mode: "Boxing", score: duels.boxing.wins, titleRequirement: "half" }, + { icon: modeIcons.bridge, mode: "Bridge", score: duels.bridge.overall.wins, titleRequirement: "half" }, + { icon: modeIcons.classic, mode: "Classic", score: duels.classic.overall.wins, titleRequirement: "default" }, + { icon: modeIcons.combo, mode: "Combo", score: duels.combo.wins, titleRequirement: "default" }, + { icon: modeIcons.megawalls, mode: "Mega Walls", score: duels.megawalls.wins, titleRequirement: "half" }, + { icon: modeIcons.nodebuff, mode: "NoDebuff", score: duels.nodebuff.wins, titleRequirement: "half" }, + { icon: modeIcons.op, mode: "OP", score: duels.op.overall.wins, titleRequirement: "default" }, + { icon: modeIcons.quake, mode: "Quakecraft", score: duels.quake.wins, titleRequirement: "default" }, + { icon: modeIcons.parkour, mode: "Parkour", score: duels.parkour.wins, titleRequirement: "half" }, + { icon: modeIcons.skywars, mode: "SkyWars", score: duels.skywars.overall.wins, titleRequirement: "default" }, + { icon: modeIcons.sumo, mode: "Sumo", score: duels.sumo.wins, titleRequirement: "default" }, + { icon: modeIcons.uhc, mode: "UHC", score: duels.uhc.overall.wins, titleRequirement: "default" }, + ] satisfies { + icon: Image; + mode: string; + score: number; + titleRequirement: Schemas.TitleRequirement; + }[]; - games.sort((a, b) => b.wins - a.wins); + games.sort( + (a, b) => + titleSortScore(b.score, b.titleRequirement) - + titleSortScore(a.score, a.titleRequirement) + ); const groups = arrayGroup(games, games.length / 2); + const overallTitle = getBaseTitle(duels.overall.wins, "", "overall"); return (
{groups.map((group) => (
- {group.map(({ icon, title, wins }) => ( + {group.map(({ icon, mode, score, titleRequirement }) => ( ))} diff --git a/packages/schemas/src/player/gamemodes/duels/index.ts b/packages/schemas/src/player/gamemodes/duels/index.ts index 7a2d60cc7..90e7f3276 100644 --- a/packages/schemas/src/player/gamemodes/duels/index.ts +++ b/packages/schemas/src/player/gamemodes/duels/index.ts @@ -33,7 +33,7 @@ export const DUELS_MODES = new GameModes([ { api: "titles" }, ], }, - { api: "arena", hypixel: "DUELS_DUEL_ARENA" }, + { api: "arena", hypixel: "DUELS_DUEL_ARENA", formatted: "Duel Arena" }, { api: "bedwars", formatted: "BedWars", @@ -122,7 +122,7 @@ export class Duels { @Field({ leaderboard: { extraDisplay: "this.overall.titleFormatted" } }) public overall: SingleBowPVPDuelsGameMode; - @Field({ leaderboard: { extraDisplay: "this.arena.titleFormatted" } }) + @Field({ leaderboard: { name: "Duel Arena", extraDisplay: "this.arena.titleFormatted" } }) public arena: ArenaDuels; @Field({ @@ -236,3 +236,4 @@ export class Duels { } export * from "./mode.js"; +export * from "./util.js"; diff --git a/packages/schemas/src/player/gamemodes/duels/mode.ts b/packages/schemas/src/player/gamemodes/duels/mode.ts index 57cd014a0..e85e0fa06 100644 --- a/packages/schemas/src/player/gamemodes/duels/mode.ts +++ b/packages/schemas/src/player/gamemodes/duels/mode.ts @@ -295,13 +295,25 @@ export class SingleDuelsGameMode extends BaseDuelsGameMode { } } -export class ArenaDuels extends SingleDuelsGameMode { +export class ArenaDuels extends SinglePVPDuelsGameMode { @Field() public shotsFired: number; public constructor(data: APIData) { - super(data, "Arena", "duel_arena", "default"); + super(data, "Duel Arena", "duel_arena", "default"); this.shotsFired = data[`duel_arena_bow_shots`]; + + const { titleFormatted, titleLevelFormatted, nextTitleLevelFormatted, progression } = getTitleAndProgression({ + score: this.kills, + mode: "Duel Arena", + data, + titleRequirement: "default", + }); + + this.titleFormatted = titleFormatted; + this.titleLevelFormatted = titleLevelFormatted; + this.nextTitleLevelFormatted = nextTitleLevelFormatted; + this.progression = progression; } } diff --git a/packages/schemas/src/player/gamemodes/duels/util.ts b/packages/schemas/src/player/gamemodes/duels/util.ts index 1910a5600..114f798c3 100644 --- a/packages/schemas/src/player/gamemodes/duels/util.ts +++ b/packages/schemas/src/player/gamemodes/duels/util.ts @@ -76,11 +76,13 @@ export const getTitleAndProgression = ({ mode, data, titleRequirement, + scheme: schemeOption = "active", }: { score: number; mode: string; data: APIData; titleRequirement: TitleRequirement; + scheme?: "active" | "default"; }) => { mode = mode ? `${mode} ` : mode; @@ -98,9 +100,10 @@ export const getTitleAndProgression = ({ const icon = iconKey in ICON_MAP ? ICON_MAP[iconKey as keyof typeof ICON_MAP] : ICON_MAP.none; - const scheme = schemeKey in SCHEME_MAP ? - SCHEME_MAP[schemeKey as keyof typeof SCHEME_MAP] : - SCHEME_MAP.default; + let scheme = SCHEME_MAP.default; + + if (schemeOption === "active" && schemeKey in SCHEME_MAP) + scheme = SCHEME_MAP[schemeKey as keyof typeof SCHEME_MAP]; const titleFormatted = scheme.title(icon, `${mode}${title}${division > 1 ? ` ${romanNumeral(division)}` : ""}`, bold, defaultColor); const titleLevelFormatted = scheme.level(romanNumeral(division), bold, defaultColor); @@ -269,4 +272,3 @@ const SCHEME_MAP: Record = { variety_values: gradientColorScheme(["§b", "§f", "§f", "§f", "§b"]), og_fade: gradientColorScheme(["§6", "§e", "§f", "§7", "§8"]), }; -