From aadac1a490dc8e6d65ce5464304a01a7cd8250dc Mon Sep 17 00:00:00 2001 From: Cody Date: Tue, 12 May 2026 03:21:18 -0600 Subject: [PATCH 1/4] feat(ratios): include duels submodes --- .../src/commands/ratios/ratios.command.tsx | 212 ++++++++++++------ .../src/player/gamemodes/duels/index.ts | 43 +++- .../src/player/gamemodes/duels/mode.ts | 38 +++- 3 files changed, 214 insertions(+), 79 deletions(-) diff --git a/apps/discord-bot/src/commands/ratios/ratios.command.tsx b/apps/discord-bot/src/commands/ratios/ratios.command.tsx index bdd522891..8f236f37e 100644 --- a/apps/discord-bot/src/commands/ratios/ratios.command.tsx +++ b/apps/discord-bot/src/commands/ratios/ratios.command.tsx @@ -38,6 +38,7 @@ import { ApiService, Command, CommandContext, + type LocalizeFunction, Page, PaginateService, PlayerArgument, @@ -57,6 +58,12 @@ import { render } from "@statsify/rendering"; const args = [PlayerArgument]; +type RatioMode = { + mode: GameModeWithSubModes; + formatted: string; + submode?: GameModeWithSubModes["submodes"][number]; +}; + @Command({ description: (t) => t("commands.ratios") }) export class RatiosCommand { public constructor( @@ -92,7 +99,7 @@ export class RatiosCommand { const filteredKits = kits .sort( (a, b) => - (blitzsg[b.api] as BlitzSGKit).exp - (blitzsg[a.api] as BlitzSGKit).exp + (blitzsg[b.mode.api] as BlitzSGKit).exp - (blitzsg[a.mode.api] as BlitzSGKit).exp ) .slice(0, 24); @@ -186,7 +193,7 @@ export class RatiosCommand { private async run( context: CommandContext, modes: GameModes, - filterModes?: (player: Player, modes: GameModeWithSubModes[]) => GameModeWithSubModes[] + filterModes?: (player: Player, modes: RatioMode[]) => RatioMode[] ) { const user = context.getUser(); const player = await this.apiService.getPlayer(context.option("player"), user); @@ -203,55 +210,79 @@ export class RatiosCommand { const ratiosPerMode = this.getRatiosPerMode(key, modes); - const allModes = ratiosPerMode.map(([mode]) => mode); + const allModes = ratiosPerMode.map(({ ratioMode }) => ratioMode); const displayedModes = filterModes ? filterModes(player, allModes) : allModes; - - const pages: Page[] = displayedModes.map((mode, index) => ({ - label: mode.formatted, - generator: async (t) => { - const background = await getBackground(...mapBackground(modes, mode.api)); - - const game = player.stats[key]; - const stats = this.getModeStats(game, mode); - - const ratios = ratiosPerMode[index][1]; - - const props: RatiosProfileProps = { - player, - skin, - background, - logo, - t, - user, - badge, - mode: { ...mode, submode: mode.submodes.length === 0 ? undefined : mode.submodes[0] }, - gameName: MODES_TO_FORMATTED.get(modes)!, - ratios: ratios.map((r) => [ - stats[r[0] as keyof typeof stats], - stats[r[1] as keyof typeof stats], - prettify(r[0]), - r[3], - ]), - }; - - const canvas = render(, getTheme(user)); - const buffer = await canvas.toBuffer("png"); + const displayedModeKeys = new Set(displayedModes.map((mode) => this.getRatioModeKey(mode))); + const displayedRatiosPerMode = ratiosPerMode.filter(({ ratioMode }) => + displayedModeKeys.has(this.getRatioModeKey(ratioMode)) + ); + + const getGenerator = (ratioMode: RatioMode, ratios: Ratio[]) => async (t: LocalizeFunction) => { + const background = await getBackground(...mapBackground(modes, ratioMode.mode.api)); + + const game = player.stats[key]; + const stats = this.getModeStats(game, ratioMode); + + const props: RatiosProfileProps = { + player, + skin, + background, + logo, + t, + user, + badge, + mode: { + ...ratioMode.mode, + formatted: ratioMode.formatted, + submode: ratioMode.submode, + }, + gameName: MODES_TO_FORMATTED.get(modes)!, + ratios: ratios.map((r) => [ + stats[r[0] as keyof typeof stats], + stats[r[1] as keyof typeof stats], + prettify(r[0]), + r[3], + ]), + }; + + const canvas = render(, getTheme(user)); + const buffer = await canvas.toBuffer("png"); + + return { + files: [{ name: "ratios.png", data: buffer, type: "image/png" }], + attachments: [], + }; + }; + + const pages: Page[] = this.groupRatioModes(displayedRatiosPerMode).map((group) => { + if (group.length === 1) { + const [{ ratioMode, ratios }] = group; return { - files: [{ name: "ratios.png", data: buffer, type: "image/png" }], - attachments: [], + label: ratioMode.formatted, + generator: getGenerator(ratioMode, ratios), }; - }, - })); + } + + return { + label: group[0].ratioMode.mode.formatted, + subPages: group.map(({ ratioMode, ratios }) => ({ + label: this.getSubPageLabel(ratioMode), + generator: getGenerator(ratioMode, ratios), + })), + }; + }); return this.paginateService.paginate(context, pages); } - private getModeStats(game: PlayerStats[keyof PlayerStats], mode: GameModeWithSubModes) { - if (mode.submodes.length !== 0) { - let stats = game[mode.api as keyof typeof game]; - stats = stats[mode.submodes[0].api as keyof typeof game]; - return mode.submodes[0].api === "overall" ? stats || game : stats; + private getModeStats(game: PlayerStats[keyof PlayerStats], ratioMode: RatioMode) { + const { mode, submode } = ratioMode; + + if (submode) { + const modeStats = game[mode.api as keyof typeof game]; + const submodeStats = modeStats?.[submode.api as keyof typeof modeStats]; + return submode.api === "overall" ? submodeStats || modeStats : submodeStats; } const stats = game[mode.api as keyof typeof game]; @@ -264,47 +295,96 @@ export class RatiosCommand { ) { const gameClass = Reflect.getMetadata("design:type", PlayerStats.prototype, key); - const ratioModes: [mode: GameModeWithSubModes, ratios: Ratio[]][] = []; + const ratioModes: { ratioMode: RatioMode; ratios: Ratio[] }[] = []; const gameModes = modes.getModes(); for (const mode of gameModes) { if (!mode.api) continue; - const modeClass = this.getModeClass(mode, gameClass); - if (!modeClass) continue; + for (const ratioMode of this.getRatioModes(mode)) { + const modeClass = this.getModeClass(ratioMode, gameClass); + if (!modeClass) continue; - const ratios = LEADERBOARD_RATIOS.filter(([numerator, denominator]) => { - const numeratorType = Reflect.getMetadata( - "design:type", - modeClass.prototype, - numerator - ); + const ratios = LEADERBOARD_RATIOS.filter(([numerator, denominator]) => { + const numeratorType = Reflect.getMetadata( + "design:type", + modeClass.prototype, + numerator + ); - const denominatorType = Reflect.getMetadata( - "design:type", - modeClass.prototype, - denominator - ); + const denominatorType = Reflect.getMetadata( + "design:type", + modeClass.prototype, + denominator + ); - return numeratorType === Number && denominatorType === Number; - }); + return numeratorType === Number && denominatorType === Number; + }); - if (!ratios.length) continue; + if (!ratios.length) continue; - ratioModes.push([mode, ratios]); + ratioModes.push({ ratioMode, ratios }); + } } return ratioModes; } - private getModeClass(mode: GameModeWithSubModes, gameClass: Constructor) { + private getModeClass(ratioMode: RatioMode, gameClass: Constructor) { + const { mode, submode } = ratioMode; const apiType = Reflect.getMetadata("design:type", gameClass.prototype, mode.api); const modeType = mode.api === "overall" ? apiType || gameClass : apiType; - if (mode.submodes.length === 0) return modeType; + if (!submode) return modeType; + + const submodeType = Reflect.getMetadata("design:type", modeType.prototype, submode.api); + return submode.api === "overall" ? submodeType || modeType : submodeType; + } + + private getRatioModes(mode: GameModeWithSubModes): RatioMode[] { + const baseMode = { + mode, + formatted: mode.formatted, + }; + + if (mode.submodes.length === 0) return [baseMode]; + + const submodes = mode.submodes + .filter((submode) => submode.api !== "stats" && submode.api !== "titles") + .map((submode) => ({ + mode, + submode, + formatted: this.formatSubmode(mode.formatted, submode), + })); + + return mode.api === "overall" ? [baseMode, ...submodes] : submodes; + } + + private formatSubmode(mode: string, submode: { api: string; formatted: string }) { + if (submode.api === "overall") return `${mode} Overall`; + if (submode.formatted.startsWith(mode)) return submode.formatted; + return `${mode} ${submode.formatted}`; + } + + private getRatioModeKey(ratioMode: RatioMode) { + return `${ratioMode.mode.api}:${ratioMode.submode?.api ?? ""}`; + } + + private groupRatioModes( + ratiosPerMode: { ratioMode: RatioMode; ratios: Ratio[] }[] + ) { + const groups = new Map; ratios: Ratio[] }[]>(); + + for (const ratio of ratiosPerMode) { + const group = groups.get(ratio.ratioMode.mode.api) ?? []; + group.push(ratio); + groups.set(ratio.ratioMode.mode.api, group); + } + + return [...groups.values()]; + } - const submode = mode.submodes[0].api; - const submodeType = Reflect.getMetadata("design:type", modeType.prototype, submode); - return submode === "overall" ? submodeType || modeType : submodeType; + private getSubPageLabel(ratioMode: RatioMode) { + return ratioMode.submode?.formatted ?? ratioMode.formatted; } } diff --git a/packages/schemas/src/player/gamemodes/duels/index.ts b/packages/schemas/src/player/gamemodes/duels/index.ts index 7a2d60cc7..8db68805c 100644 --- a/packages/schemas/src/player/gamemodes/duels/index.ts +++ b/packages/schemas/src/player/gamemodes/duels/index.ts @@ -54,16 +54,51 @@ export const DUELS_MODES = new GameModes([ { api: "doubles" }, { api: "threes" }, { api: "fours" }, + { api: "twoVTwoVTwoVTwo", formatted: "2v2v2v2" }, + { api: "threeVThreeVThreeVThree", formatted: "3v3v3v3" }, + { api: "capture", formatted: "CTF" }, + ], + }, + { + api: "classic", + hypixel: "DUELS_CLASSIC_DUEL", + submodes: [ + { api: "overall" }, + { api: "solo" }, + { api: "doubles" }, ], }, - { api: "classic", hypixel: "DUELS_CLASSIC_DUEL" }, { api: "combo", hypixel: "DUELS_COMBO_DUEL" }, - { api: "megawalls", formatted: "MegaWalls" }, + { + api: "megawalls", + formatted: "MegaWalls", + submodes: [ + { api: "overall" }, + { api: "solo" }, + { api: "doubles" }, + ], + }, { api: "nodebuff", hypixel: "DUELS_POTION_DUEL", formatted: "NoDebuff" }, - { api: "op", formatted: "OP" }, + { + api: "op", + formatted: "OP", + submodes: [ + { api: "overall" }, + { api: "solo" }, + { api: "doubles" }, + ], + }, { api: "quake", hypixel: "DUELS_QUAKE_DUEL" }, { api: "parkour", hypixel: "DUELS_PARKOUR_EIGHT" }, - { api: "skywars", formatted: "SkyWars" }, + { + api: "skywars", + formatted: "SkyWars", + submodes: [ + { api: "overall" }, + { api: "solo" }, + { api: "doubles" }, + ], + }, { api: "spleef", submodes: [ diff --git a/packages/schemas/src/player/gamemodes/duels/mode.ts b/packages/schemas/src/player/gamemodes/duels/mode.ts index 57cd014a0..41fa84889 100644 --- a/packages/schemas/src/player/gamemodes/duels/mode.ts +++ b/packages/schemas/src/player/gamemodes/duels/mode.ts @@ -143,20 +143,32 @@ export class BridgeDuels { @Field() public fours: BridgeDuelsMode; + @Field() + public twoVTwoVTwoVTwo: BridgeDuelsMode; + + @Field() + public threeVThreeVThreeVThree: BridgeDuelsMode; + + @Field() + public capture: BridgeDuelsMode; + public constructor(data: APIData) { this.solo = new BridgeDuelsMode(data, "bridge_duel"); this.doubles = new BridgeDuelsMode(data, "bridge_doubles"); this.threes = new BridgeDuelsMode(data, "bridge_threes"); this.fours = new BridgeDuelsMode(data, "bridge_four"); + this.twoVTwoVTwoVTwo = new BridgeDuelsMode(data, "bridge_2v2v2v2"); + this.threeVThreeVThreeVThree = new BridgeDuelsMode(data, "bridge_3v3v3v3"); + this.capture = new BridgeDuelsMode(data, "capture_threes"); this.overall = deepAdd( this.solo, this.doubles, this.threes, this.fours, - new BridgeDuelsMode(data, "bridge_2v2v2v2"), - new BridgeDuelsMode(data, "bridge_3v3v3v3"), - new BridgeDuelsMode(data, "capture_threes") + this.twoVTwoVTwoVTwo, + this.threeVThreeVThreeVThree, + this.capture ); this.overall.winstreak = data.current_bridge_winstreak; @@ -591,16 +603,24 @@ export class ParkourDuels extends SingleDuelsGameMode { } export class MegaWallsDuels extends SinglePVPDuelsGameMode { + @Field() + public solo: PVPBaseDuelsGameMode; + + @Field() + public doubles: PVPBaseDuelsGameMode; + public constructor(data: APIData) { super(data, "Mega Walls", "mw_duel", "half"); // add back doubles stats - const doubles = new PVPBaseDuelsGameMode(data, "mw_doubles"); - this.wins = add(this.wins, doubles.wins); - this.losses = add(this.losses, doubles.losses); - this.kills = add(this.kills, doubles.kills); - this.deaths = add(this.deaths, doubles.deaths); - this.blocksPlaced = add(this.blocksPlaced, doubles.blocksPlaced); + this.solo = new PVPBaseDuelsGameMode(data, "mw_duel"); + this.doubles = new PVPBaseDuelsGameMode(data, "mw_doubles"); + + this.wins = add(this.solo.wins, this.doubles.wins); + this.losses = add(this.solo.losses, this.doubles.losses); + this.kills = add(this.solo.kills, this.doubles.kills); + this.deaths = add(this.solo.deaths, this.doubles.deaths); + this.blocksPlaced = add(this.solo.blocksPlaced, this.doubles.blocksPlaced); PVPBaseDuelsGameMode.applyRatios(this); From f00d3c95879b56ff750d5ea0f2603167ea123451 Mon Sep 17 00:00:00 2001 From: Cody Date: Sat, 30 May 2026 00:09:14 -0600 Subject: [PATCH 2/4] refactor(duels): simplify duels modes and remove unused submodes * Removed unnecessary submodes from DUELS_MODES * Streamlined BridgeDuels class by eliminating unused fields * Enhanced displayed ratios sorting logic in RatiosCommand --- .../src/commands/ratios/ratios.command.tsx | 11 +++-- .../src/player/gamemodes/duels/index.ts | 8 ---- .../src/player/gamemodes/duels/mode.ts | 42 +------------------ 3 files changed, 9 insertions(+), 52 deletions(-) diff --git a/apps/discord-bot/src/commands/ratios/ratios.command.tsx b/apps/discord-bot/src/commands/ratios/ratios.command.tsx index 8f236f37e..2b10e0fdc 100644 --- a/apps/discord-bot/src/commands/ratios/ratios.command.tsx +++ b/apps/discord-bot/src/commands/ratios/ratios.command.tsx @@ -212,10 +212,15 @@ export class RatiosCommand { const allModes = ratiosPerMode.map(({ ratioMode }) => ratioMode); const displayedModes = filterModes ? filterModes(player, allModes) : allModes; - const displayedModeKeys = new Set(displayedModes.map((mode) => this.getRatioModeKey(mode))); - const displayedRatiosPerMode = ratiosPerMode.filter(({ ratioMode }) => - displayedModeKeys.has(this.getRatioModeKey(ratioMode)) + const displayedModeOrder = new Map( + displayedModes.map((mode, index) => [this.getRatioModeKey(mode), index]) ); + const displayedRatiosPerMode = ratiosPerMode + .flatMap((ratio) => { + const index = displayedModeOrder.get(this.getRatioModeKey(ratio.ratioMode)); + return index === undefined ? [] : [{ ...ratio, index }]; + }) + .sort((a, b) => a.index - b.index); const getGenerator = (ratioMode: RatioMode, ratios: Ratio[]) => async (t: LocalizeFunction) => { const background = await getBackground(...mapBackground(modes, ratioMode.mode.api)); diff --git a/packages/schemas/src/player/gamemodes/duels/index.ts b/packages/schemas/src/player/gamemodes/duels/index.ts index 8db68805c..66ad25154 100644 --- a/packages/schemas/src/player/gamemodes/duels/index.ts +++ b/packages/schemas/src/player/gamemodes/duels/index.ts @@ -54,9 +54,6 @@ export const DUELS_MODES = new GameModes([ { api: "doubles" }, { api: "threes" }, { api: "fours" }, - { api: "twoVTwoVTwoVTwo", formatted: "2v2v2v2" }, - { api: "threeVThreeVThreeVThree", formatted: "3v3v3v3" }, - { api: "capture", formatted: "CTF" }, ], }, { @@ -75,7 +72,6 @@ export const DUELS_MODES = new GameModes([ submodes: [ { api: "overall" }, { api: "solo" }, - { api: "doubles" }, ], }, { api: "nodebuff", hypixel: "DUELS_POTION_DUEL", formatted: "NoDebuff" }, @@ -119,7 +115,6 @@ export const DUELS_MODES = new GameModes([ ], }, { hypixel: "DUELS_MW_DUEL", formatted: "MegaWalls Solo" }, - { hypixel: "DUELS_MW_DOUBLES", formatted: "MegaWalls Doubles" }, { hypixel: "DUELS_UHC_DUEL", formatted: "UHC Solo" }, { hypixel: "DUELS_UHC_DOUBLES", formatted: "UHC Doubles" }, { hypixel: "DUELS_UHC_FOUR", formatted: "UHC Fours" }, @@ -132,9 +127,6 @@ export const DUELS_MODES = new GameModes([ { hypixel: "DUELS_BRIDGE_DOUBLES", formatted: "Bridge Doubles" }, { hypixel: "DUELS_BRIDGE_THREES", formatted: "Bridge Threes" }, { hypixel: "DUELS_BRIDGE_FOUR", formatted: "Bridge Fours" }, - { hypixel: "DUELS_BRIDGE_2V2V2V2", formatted: "Bridge 2v2v2v2" }, - { hypixel: "DUELS_BRIDGE_3V3V3V3", formatted: "Bridge 3v3v3v3" }, - { hypixel: "DUELS_CAPTURE_THREES", formatted: "Bridge CTF" }, { hypixel: "DUELS_BEDWARS_TWO_ONE", formatted: "BedWars Duel" }, { hypixel: "DUELS_BEDWARS_TWO_ONE_RUSH", formatted: "Bed Rush" }, { hypixel: "DUELS_SPLEEF_DUEL", formatted: "Spleef" }, diff --git a/packages/schemas/src/player/gamemodes/duels/mode.ts b/packages/schemas/src/player/gamemodes/duels/mode.ts index 41fa84889..d411a593e 100644 --- a/packages/schemas/src/player/gamemodes/duels/mode.ts +++ b/packages/schemas/src/player/gamemodes/duels/mode.ts @@ -143,32 +143,17 @@ export class BridgeDuels { @Field() public fours: BridgeDuelsMode; - @Field() - public twoVTwoVTwoVTwo: BridgeDuelsMode; - - @Field() - public threeVThreeVThreeVThree: BridgeDuelsMode; - - @Field() - public capture: BridgeDuelsMode; - public constructor(data: APIData) { this.solo = new BridgeDuelsMode(data, "bridge_duel"); this.doubles = new BridgeDuelsMode(data, "bridge_doubles"); this.threes = new BridgeDuelsMode(data, "bridge_threes"); this.fours = new BridgeDuelsMode(data, "bridge_four"); - this.twoVTwoVTwoVTwo = new BridgeDuelsMode(data, "bridge_2v2v2v2"); - this.threeVThreeVThreeVThree = new BridgeDuelsMode(data, "bridge_3v3v3v3"); - this.capture = new BridgeDuelsMode(data, "capture_threes"); this.overall = deepAdd( this.solo, this.doubles, this.threes, - this.fours, - this.twoVTwoVTwoVTwo, - this.threeVThreeVThreeVThree, - this.capture + this.fours ); this.overall.winstreak = data.current_bridge_winstreak; @@ -606,35 +591,10 @@ export class MegaWallsDuels extends SinglePVPDuelsGameMode { @Field() public solo: PVPBaseDuelsGameMode; - @Field() - public doubles: PVPBaseDuelsGameMode; - public constructor(data: APIData) { super(data, "Mega Walls", "mw_duel", "half"); - // add back doubles stats this.solo = new PVPBaseDuelsGameMode(data, "mw_duel"); - this.doubles = new PVPBaseDuelsGameMode(data, "mw_doubles"); - - this.wins = add(this.solo.wins, this.doubles.wins); - this.losses = add(this.solo.losses, this.doubles.losses); - this.kills = add(this.solo.kills, this.doubles.kills); - this.deaths = add(this.solo.deaths, this.doubles.deaths); - this.blocksPlaced = add(this.solo.blocksPlaced, this.doubles.blocksPlaced); - - PVPBaseDuelsGameMode.applyRatios(this); - - const { titleFormatted, titleLevelFormatted, nextTitleLevelFormatted, progression } = getTitleAndProgression({ - score: this.wins, - mode: "Mega Walls", - data, - titleRequirement: "half", - }); - - this.titleFormatted = titleFormatted; - this.titleLevelFormatted = titleLevelFormatted; - this.nextTitleLevelFormatted = nextTitleLevelFormatted; - this.progression = progression; } } From 865733678793c65b37d5ca404b0a2adb246af294 Mon Sep 17 00:00:00 2001 From: Cody Date: Sat, 30 May 2026 00:40:47 -0600 Subject: [PATCH 3/4] feat(duels): enhance bridge and mega walls modes * Add new bridge duels modes for accurate overall stats calculation * Update mega walls constructor to include doubles stats * Apply ratios for mega walls duels --- .../src/player/gamemodes/duels/index.ts | 35 ++++++------------- .../src/player/gamemodes/duels/mode.ts | 33 +++++++++++++---- 2 files changed, 37 insertions(+), 31 deletions(-) diff --git a/packages/schemas/src/player/gamemodes/duels/index.ts b/packages/schemas/src/player/gamemodes/duels/index.ts index 66ad25154..19f625881 100644 --- a/packages/schemas/src/player/gamemodes/duels/index.ts +++ b/packages/schemas/src/player/gamemodes/duels/index.ts @@ -56,44 +56,25 @@ export const DUELS_MODES = new GameModes([ { api: "fours" }, ], }, - { + { // Classic has submodes, but are displayed in 1 image api: "classic", - hypixel: "DUELS_CLASSIC_DUEL", - submodes: [ - { api: "overall" }, - { api: "solo" }, - { api: "doubles" }, - ], + hypixel: "DUELS_CLASSIC_DUEL" }, { api: "combo", hypixel: "DUELS_COMBO_DUEL" }, { api: "megawalls", - formatted: "MegaWalls", - submodes: [ - { api: "overall" }, - { api: "solo" }, - ], + formatted: "MegaWalls" }, { api: "nodebuff", hypixel: "DUELS_POTION_DUEL", formatted: "NoDebuff" }, - { + { // OP has submodes, but are displayed in 1 image api: "op", - formatted: "OP", - submodes: [ - { api: "overall" }, - { api: "solo" }, - { api: "doubles" }, - ], + formatted: "OP" }, { api: "quake", hypixel: "DUELS_QUAKE_DUEL" }, { api: "parkour", hypixel: "DUELS_PARKOUR_EIGHT" }, - { + { // Skywars has submodes, but are displayed in 1 image api: "skywars", formatted: "SkyWars", - submodes: [ - { api: "overall" }, - { api: "solo" }, - { api: "doubles" }, - ], }, { api: "spleef", @@ -115,6 +96,7 @@ export const DUELS_MODES = new GameModes([ ], }, { hypixel: "DUELS_MW_DUEL", formatted: "MegaWalls Solo" }, + { hypixel: "DUELS_MW_DOUBLES", formatted: "MegaWalls Doubles" }, // Needed for calculation of MW Overall { hypixel: "DUELS_UHC_DUEL", formatted: "UHC Solo" }, { hypixel: "DUELS_UHC_DOUBLES", formatted: "UHC Doubles" }, { hypixel: "DUELS_UHC_FOUR", formatted: "UHC Fours" }, @@ -127,6 +109,9 @@ export const DUELS_MODES = new GameModes([ { hypixel: "DUELS_BRIDGE_DOUBLES", formatted: "Bridge Doubles" }, { hypixel: "DUELS_BRIDGE_THREES", formatted: "Bridge Threes" }, { hypixel: "DUELS_BRIDGE_FOUR", formatted: "Bridge Fours" }, + { hypixel: "DUELS_BRIDGE_2V2V2V2", formatted: "Bridge 2v2v2v2" }, // Needed for calculation of Bridge Overall + { hypixel: "DUELS_BRIDGE_3V3V3V3", formatted: "Bridge 3v3v3v3" }, // Needed for calculation of Bridge Overall + { hypixel: "DUELS_CAPTURE_THREES", formatted: "Bridge CTF" }, // Needed for calculation of Bridge Overall { hypixel: "DUELS_BEDWARS_TWO_ONE", formatted: "BedWars Duel" }, { hypixel: "DUELS_BEDWARS_TWO_ONE_RUSH", formatted: "Bed Rush" }, { hypixel: "DUELS_SPLEEF_DUEL", formatted: "Spleef" }, diff --git a/packages/schemas/src/player/gamemodes/duels/mode.ts b/packages/schemas/src/player/gamemodes/duels/mode.ts index d411a593e..db544754e 100644 --- a/packages/schemas/src/player/gamemodes/duels/mode.ts +++ b/packages/schemas/src/player/gamemodes/duels/mode.ts @@ -153,7 +153,11 @@ export class BridgeDuels { this.solo, this.doubles, this.threes, - this.fours + this.fours, + // Needed for accurate calculation of overall stats + new BridgeDuelsMode(data, "bridge_2v2v2v2"), + new BridgeDuelsMode(data, "bridge_3v3v3v3"), + new BridgeDuelsMode(data, "capture_threes") ); this.overall.winstreak = data.current_bridge_winstreak; @@ -588,14 +592,31 @@ export class ParkourDuels extends SingleDuelsGameMode { } export class MegaWallsDuels extends SinglePVPDuelsGameMode { - @Field() - public solo: PVPBaseDuelsGameMode; - public constructor(data: APIData) { super(data, "Mega Walls", "mw_duel", "half"); - this.solo = new PVPBaseDuelsGameMode(data, "mw_duel"); + // add back doubles stats + const doubles = new PVPBaseDuelsGameMode(data, "mw_doubles"); + this.wins = add(this.wins, doubles.wins); + this.losses = add(this.losses, doubles.losses); + this.kills = add(this.kills, doubles.kills); + this.deaths = add(this.deaths, doubles.deaths); + this.blocksPlaced = add(this.blocksPlaced, doubles.blocksPlaced); + + PVPBaseDuelsGameMode.applyRatios(this); + + const { titleFormatted, titleLevelFormatted, nextTitleLevelFormatted, progression } = getTitleAndProgression({ + score: this.wins, + mode: "Mega Walls", + data, + titleRequirement: "half", + }); + + this.titleFormatted = titleFormatted; + this.titleLevelFormatted = titleLevelFormatted; + this.nextTitleLevelFormatted = nextTitleLevelFormatted; + this.progression = progression; } } -// [INFO]: Hypixel doesn't seem to store MegaWalls Duels kits in the API +// [INFO]: Hypixel doesn't seem to store MegaWalls Duels kits in the API \ No newline at end of file From c39f2425d76dcc833065e9b1ece1c5345f786301 Mon Sep 17 00:00:00 2001 From: Cody Date: Sat, 30 May 2026 00:44:25 -0600 Subject: [PATCH 4/4] style(duels): format code for consistency --- .../src/player/gamemodes/duels/index.ts | 18 +++++++++--------- .../schemas/src/player/gamemodes/duels/mode.ts | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/schemas/src/player/gamemodes/duels/index.ts b/packages/schemas/src/player/gamemodes/duels/index.ts index 19f625881..8a89d9730 100644 --- a/packages/schemas/src/player/gamemodes/duels/index.ts +++ b/packages/schemas/src/player/gamemodes/duels/index.ts @@ -58,21 +58,21 @@ export const DUELS_MODES = new GameModes([ }, { // Classic has submodes, but are displayed in 1 image api: "classic", - hypixel: "DUELS_CLASSIC_DUEL" + hypixel: "DUELS_CLASSIC_DUEL", }, { api: "combo", hypixel: "DUELS_COMBO_DUEL" }, { api: "megawalls", - formatted: "MegaWalls" + formatted: "MegaWalls", }, { api: "nodebuff", hypixel: "DUELS_POTION_DUEL", formatted: "NoDebuff" }, - { // OP has submodes, but are displayed in 1 image + { // OP has submodes, but are displayed in 1 image api: "op", - formatted: "OP" + formatted: "OP", }, { api: "quake", hypixel: "DUELS_QUAKE_DUEL" }, { api: "parkour", hypixel: "DUELS_PARKOUR_EIGHT" }, - { // Skywars has submodes, but are displayed in 1 image + { // Skywars has submodes, but are displayed in 1 image api: "skywars", formatted: "SkyWars", }, @@ -96,7 +96,7 @@ export const DUELS_MODES = new GameModes([ ], }, { hypixel: "DUELS_MW_DUEL", formatted: "MegaWalls Solo" }, - { hypixel: "DUELS_MW_DOUBLES", formatted: "MegaWalls Doubles" }, // Needed for calculation of MW Overall + { hypixel: "DUELS_MW_DOUBLES", formatted: "MegaWalls Doubles" }, // Needed for MW Overall { hypixel: "DUELS_UHC_DUEL", formatted: "UHC Solo" }, { hypixel: "DUELS_UHC_DOUBLES", formatted: "UHC Doubles" }, { hypixel: "DUELS_UHC_FOUR", formatted: "UHC Fours" }, @@ -109,9 +109,9 @@ export const DUELS_MODES = new GameModes([ { hypixel: "DUELS_BRIDGE_DOUBLES", formatted: "Bridge Doubles" }, { hypixel: "DUELS_BRIDGE_THREES", formatted: "Bridge Threes" }, { hypixel: "DUELS_BRIDGE_FOUR", formatted: "Bridge Fours" }, - { hypixel: "DUELS_BRIDGE_2V2V2V2", formatted: "Bridge 2v2v2v2" }, // Needed for calculation of Bridge Overall - { hypixel: "DUELS_BRIDGE_3V3V3V3", formatted: "Bridge 3v3v3v3" }, // Needed for calculation of Bridge Overall - { hypixel: "DUELS_CAPTURE_THREES", formatted: "Bridge CTF" }, // Needed for calculation of Bridge Overall + { hypixel: "DUELS_BRIDGE_2V2V2V2", formatted: "Bridge 2v2v2v2" }, // Needed for Bridge Overall + { hypixel: "DUELS_BRIDGE_3V3V3V3", formatted: "Bridge 3v3v3v3" }, // Needed for Bridge Overall + { hypixel: "DUELS_CAPTURE_THREES", formatted: "Bridge CTF" }, // Needed for Bridge Overall { hypixel: "DUELS_BEDWARS_TWO_ONE", formatted: "BedWars Duel" }, { hypixel: "DUELS_BEDWARS_TWO_ONE_RUSH", formatted: "Bed Rush" }, { hypixel: "DUELS_SPLEEF_DUEL", formatted: "Spleef" }, diff --git a/packages/schemas/src/player/gamemodes/duels/mode.ts b/packages/schemas/src/player/gamemodes/duels/mode.ts index db544754e..231239d2c 100644 --- a/packages/schemas/src/player/gamemodes/duels/mode.ts +++ b/packages/schemas/src/player/gamemodes/duels/mode.ts @@ -619,4 +619,4 @@ export class MegaWallsDuels extends SinglePVPDuelsGameMode { } } -// [INFO]: Hypixel doesn't seem to store MegaWalls Duels kits in the API \ No newline at end of file +// [INFO]: Hypixel doesn't seem to store MegaWalls Duels kits in the API