diff --git a/apps/discord-bot/src/commands/arcade/modes/dropper.tsx b/apps/discord-bot/src/commands/arcade/modes/dropper.tsx index 4e06ca84b..3aa32ecfd 100644 --- a/apps/discord-bot/src/commands/arcade/modes/dropper.tsx +++ b/apps/discord-bot/src/commands/arcade/modes/dropper.tsx @@ -12,6 +12,8 @@ import { arrayGroup, formatRaceTime, formatTime } from "@statsify/util"; import type { LocalizeFunction } from "@statsify/discord"; import type { ProfileTime } from "#commands/base.hypixel-command"; +const formatTimeWithSeconds = (time: number) => formatTime(time, { entries: 3 }); + interface DropperTableProps { stats: Dropper; submode: SubModeForMode; @@ -34,7 +36,7 @@ export const DropperTable = ({ stats, submode, t, time }: DropperTableProps) => 0}> - + diff --git a/apps/discord-bot/src/commands/arcade/modes/galaxy-wars.tsx b/apps/discord-bot/src/commands/arcade/modes/galaxy-wars.tsx index 0ae58b7fd..d6fb3bd6c 100644 --- a/apps/discord-bot/src/commands/arcade/modes/galaxy-wars.tsx +++ b/apps/discord-bot/src/commands/arcade/modes/galaxy-wars.tsx @@ -27,5 +27,8 @@ export const GalaxyWarsTable = ({ stats, t }: GalaxyWarsTableProps) => ( + + + ); diff --git a/apps/discord-bot/src/commands/arcade/modes/zombies.tsx b/apps/discord-bot/src/commands/arcade/modes/zombies.tsx index 78dc6a714..0a3dcc8e1 100644 --- a/apps/discord-bot/src/commands/arcade/modes/zombies.tsx +++ b/apps/discord-bot/src/commands/arcade/modes/zombies.tsx @@ -21,12 +21,13 @@ interface ZombiesMapColumnProps { const ZombiesMapColumn = ({ title, stats, t, time }: ZombiesMapColumnProps) => { const mapStat = stats.wins >= 1 ? - [t("stats.fastestWin"), stats.fastestWin ? formatTime(stats.fastestWin) : "N/A"] : + [t("stats.fastestWin"), stats.fastestWin ? formatTime(stats.fastestWin, { entries: 3 }) : "N/A"] : [t("stats.bestRound"), t(stats.bestRound)]; return ( + @@ -50,6 +51,8 @@ export const ZombiesTable = ({ stats, t, time }: ZombiesTableProps) => { + + @@ -89,7 +92,7 @@ export const ZombiesMapDifficultyTable = ({ stats, t, time }: ZombiesMapDifficul - + diff --git a/apps/discord-bot/src/commands/bedwars/bedwars.profile.tsx b/apps/discord-bot/src/commands/bedwars/bedwars.profile.tsx index e680c86bc..4f8909814 100644 --- a/apps/discord-bot/src/commands/bedwars/bedwars.profile.tsx +++ b/apps/discord-bot/src/commands/bedwars/bedwars.profile.tsx @@ -34,7 +34,11 @@ export const BedWarsProfile = ({ time, }: BedWarsProfileProps) => { const { bedwars } = player.stats; - const stats = bedwars[mode.api]; + const modeKey = mode.submode?.api ?? mode.api; + const formattedMode = mode.submode?.api === mode.api || !mode.submode ? + mode.formatted : + `${mode.formatted} ${mode.submode.formatted}`; + const stats = bedwars[modeKey]; const sidebar: SidebarItem[] = [ [t("stats.tokens"), t(bedwars.tokens), "§2"], @@ -64,7 +68,7 @@ export const BedWarsProfile = ({ name={player.prefixName} badge={badge} sidebar={sidebar} - title={`§l${FormattedGame.BEDWARS} §fStats §r(${mode.formatted})`} + title={`§l${FormattedGame.BEDWARS} §fStats §r(${formattedMode})`} description={`§7${t("stats.level")}: ${ bedwars.levelFormatted }\n${formatProgression({ diff --git a/apps/discord-bot/src/commands/copsandcrims/copsandcrims.profile.tsx b/apps/discord-bot/src/commands/copsandcrims/copsandcrims.profile.tsx index 2dc1e0cf7..0f78882ab 100644 --- a/apps/discord-bot/src/commands/copsandcrims/copsandcrims.profile.tsx +++ b/apps/discord-bot/src/commands/copsandcrims/copsandcrims.profile.tsx @@ -11,6 +11,8 @@ import { CopsAndCrimsModes, FormattedGame, type GameMode } from "@statsify/schem import { formatTime } from "@statsify/util"; import type { BaseProfileProps } from "#commands/base.hypixel-command"; +const formatTimeWithSeconds = (time: number) => formatTime(time, { entries: 3 }); + export interface CopsAndCrimsProfileProps extends BaseProfileProps { mode: GameMode; } @@ -106,7 +108,7 @@ export const CopsAndCrimsProfile = ({ diff --git a/apps/discord-bot/src/commands/duels/duels.profile.tsx b/apps/discord-bot/src/commands/duels/duels.profile.tsx index d45cfdd5a..d6f2cedc3 100644 --- a/apps/discord-bot/src/commands/duels/duels.profile.tsx +++ b/apps/discord-bot/src/commands/duels/duels.profile.tsx @@ -19,6 +19,8 @@ import { formatTime, prettify } from "@statsify/util"; import type { BaseProfileProps, ProfileTime } from "#commands/base.hypixel-command"; import type { DuelsModeIcons } from "./duels.command.js"; +const formatTimeWithSeconds = (time: number) => formatTime(time, { entries: 3 }); + export type DuelsProfileProps = Omit & { mode: GameMode; time: T; @@ -42,6 +44,7 @@ export const DuelsProfile = ({ const sidebar: SidebarItem[] = [ [t("stats.tokens"), t(duels.tokens), "§2"], [t("stats.pingRange"), `${t(duels.pingRange)}ms`, "§a"], + [t("stats.gamesPlayed"), t(duels.overall.wins + duels.overall.losses), "§e"], [t("stats.blocksPlaced"), t(duels.overall.blocksPlaced), "§9"], ]; @@ -61,7 +64,7 @@ export const DuelsProfile = ({ if (mode.api === "parkour") { sidebar.push( - [t("stats.bestTime"), duels.parkour.bestTime === 0 ? "N/A" : formatTime(duels.parkour.bestTime), "§d"], + [t("stats.bestTime"), duels.parkour.bestTime === 0 ? "N/A" : formatTimeWithSeconds(duels.parkour.bestTime), "§d"], [t("stats.checkpoints"), t(duels.parkour.checkpoints), "§5"] ); } diff --git a/apps/discord-bot/src/commands/general/general.profile.tsx b/apps/discord-bot/src/commands/general/general.profile.tsx index 60361afd2..338d420a4 100644 --- a/apps/discord-bot/src/commands/general/general.profile.tsx +++ b/apps/discord-bot/src/commands/general/general.profile.tsx @@ -111,6 +111,16 @@ export const GeneralProfile = ({ value={t(challenges.total)} color="§a" /> + + diff --git a/apps/discord-bot/src/commands/murdermystery/murdermystery.profile.tsx b/apps/discord-bot/src/commands/murdermystery/murdermystery.profile.tsx index 4251fa621..91cff1f5b 100644 --- a/apps/discord-bot/src/commands/murdermystery/murdermystery.profile.tsx +++ b/apps/discord-bot/src/commands/murdermystery/murdermystery.profile.tsx @@ -22,6 +22,8 @@ import { import { formatTime } from "@statsify/util"; import type { BaseProfileProps } from "#commands/base.hypixel-command"; +const formatTimeWithSeconds = (time: number) => formatTime(time, { entries: 3 }); + export interface MurderMysteryProfileProps extends BaseProfileProps { mode: GameMode; } @@ -199,7 +201,7 @@ export const MurderMysteryProfile = ({ title={t("stats.fastestMurdererWin")} value={ stats.fastestMurdererWin ? - formatTime(stats.fastestMurdererWin) : + formatTimeWithSeconds(stats.fastestMurdererWin) : "N/A" } color="§c" @@ -208,7 +210,7 @@ export const MurderMysteryProfile = ({ title={t("stats.fastestDetectiveWin")} value={ stats.fastestDetectiveWin ? - formatTime(stats.fastestDetectiveWin) : + formatTimeWithSeconds(stats.fastestDetectiveWin) : "N/A" } color="§b" diff --git a/apps/discord-bot/src/commands/parkour/parkour.profile.tsx b/apps/discord-bot/src/commands/parkour/parkour.profile.tsx index b5c9d185a..9d0bd739e 100644 --- a/apps/discord-bot/src/commands/parkour/parkour.profile.tsx +++ b/apps/discord-bot/src/commands/parkour/parkour.profile.tsx @@ -12,6 +12,8 @@ import { formatTime } from "@statsify/util"; import type { BaseProfileProps } from "#commands/base.hypixel-command"; import type { Image } from "skia-canvas"; +const formatTimeWithSeconds = (time: number) => formatTime(time, { entries: 3 }); + interface ParkourProfileProps extends BaseProfileProps { gameIcons: Record; } @@ -29,7 +31,7 @@ export const ParkourProfile = ({ const times: [GameId, any][] = Object.entries(parkour) .sort((a, b) => (a[1] || Number.MAX_VALUE) - (b[1] || Number.MAX_VALUE)) - .map(([field, time]) => [field as GameId, time ? formatTime(time) : "N/A"]); + .map(([field, time]) => [field as GameId, time ? formatTimeWithSeconds(time) : "N/A"]); return ( diff --git a/apps/discord-bot/src/commands/pit/pit.profile.tsx b/apps/discord-bot/src/commands/pit/pit.profile.tsx index 76d5a5d9a..d243ddfb6 100644 --- a/apps/discord-bot/src/commands/pit/pit.profile.tsx +++ b/apps/discord-bot/src/commands/pit/pit.profile.tsx @@ -33,6 +33,7 @@ export const PitProfile = ({ const sidebar: SidebarItem[] = [ [t("stats.gold"), t(pit.gold), "§6"], + [t("stats.totalExp"), t(pit.exp), "§b"], [t("stats.contracts"), t(pit.contractsCompleted), "§a"], [t("stats.renown"), t(pit.renown), "§e"], [t("stats.lifetimeRenown"), t(pit.lifetimeRenown), "§b"], @@ -54,6 +55,13 @@ export const PitProfile = ({ currentLevel: pit.levelFormatted, nextLevel: pit.nextLevelFormatted, showLevel: true, + })}\n${formatProgression({ + t, + label: t("stats.progression.gold"), + progression: pit.goldProgression, + currentLevel: pit.levelFormatted, + nextLevel: pit.nextLevelFormatted, + showLevel: false, })}`} sidebar={sidebar} badge={badge} diff --git a/apps/discord-bot/src/commands/quake/quake.profile.tsx b/apps/discord-bot/src/commands/quake/quake.profile.tsx index b47eca731..e903caf72 100644 --- a/apps/discord-bot/src/commands/quake/quake.profile.tsx +++ b/apps/discord-bot/src/commands/quake/quake.profile.tsx @@ -41,6 +41,7 @@ export const QuakeProfile = ({ [t("stats.godlikes"), t(quake.godlikes), "§3"], [t("stats.trigger"), `${quake.trigger}s`, "§b"], [t("stats.highestKillstreak"), t(quake.highestKillstreak), "§4"], + [t("stats.blocksTravelled"), t(quake[mode.api].blocksTravelled), "§b"], ]; return ( @@ -78,10 +79,17 @@ export const QuakeProfile = ({ value={t(stats.shotsFired)} color="§a" /> - + + + + diff --git a/apps/discord-bot/src/commands/rankings/rankings.command.tsx b/apps/discord-bot/src/commands/rankings/rankings.command.tsx index 15966236e..2bfdfa04d 100644 --- a/apps/discord-bot/src/commands/rankings/rankings.command.tsx +++ b/apps/discord-bot/src/commands/rankings/rankings.command.tsx @@ -347,7 +347,7 @@ export class RankingsCommand { return scrollingPagination( context, groups.map( - (group) => () => + (group, page) => () => render( , getTheme(user) ) diff --git a/apps/discord-bot/src/commands/rankings/rankings.profile.tsx b/apps/discord-bot/src/commands/rankings/rankings.profile.tsx index 26d4b7941..bdb9c314c 100644 --- a/apps/discord-bot/src/commands/rankings/rankings.profile.tsx +++ b/apps/discord-bot/src/commands/rankings/rankings.profile.tsx @@ -44,6 +44,8 @@ const formatStat = (stat: PostLeaderboardRankingsResponse, game?: string) => { export interface RankingsProfileProps extends Omit { data: PostLeaderboardRankingsResponse[]; game?: string; + page: number; + pageCount: number; } export const RankingsProfile = ({ @@ -56,6 +58,8 @@ export const RankingsProfile = ({ skin, game, badge, + page, + pageCount, }: RankingsProfileProps) => { const listTitles = ["Statistic", "Pos", "Value"]; if (!game) listTitles.unshift("Game"); @@ -102,6 +106,7 @@ export const RankingsProfile = ({ skin={skin} time="LIVE" title={`§l§bLeaderboard Positions §r(§l${formattedGame}§r)`} + description={`§7Page ${t(page + 1)} / ${t(pageCount)}`} badge={badge} /> {titles}, ...items]} /> diff --git a/apps/discord-bot/src/commands/tntgames/tntgames.profile.tsx b/apps/discord-bot/src/commands/tntgames/tntgames.profile.tsx index 7e1f9d466..e998f1005 100644 --- a/apps/discord-bot/src/commands/tntgames/tntgames.profile.tsx +++ b/apps/discord-bot/src/commands/tntgames/tntgames.profile.tsx @@ -11,6 +11,8 @@ import { FormattedGame, type GameMode, type TNTGamesModes } from "@statsify/sche import { formatTime, prettify } from "@statsify/util"; import type { BaseProfileProps } from "#commands/base.hypixel-command"; +const formatTimeWithSeconds = (time: number) => formatTime(time, { entries: 3 }); + export interface TNTGamesProfileProps extends BaseProfileProps { mode: GameMode; } @@ -31,6 +33,7 @@ export const TNTGamesProfile = ({ const sidebar: SidebarItem[] = [ [t("stats.coins"), t(tntgames.coins), "§6"], [t("stats.overallWins"), t(tntgames.wins), "§e"], + [t("stats.playtime"), formatTime(tntgames.playtime), "§a"], ]; let table; @@ -47,7 +50,7 @@ export const TNTGamesProfile = ({ [ [t("stats.wins"), t(tntgames.tntRun.wins)], [t("stats.wlr"), t(tntgames.tntRun.wlr)], - [t("stats.bestTime"), formatTime(tntgames.tntRun.record)], + [t("stats.bestTime"), formatTimeWithSeconds(tntgames.tntRun.record)], ] : [ [t("stats.wins"), t(tntgames.tntRun.wins)], @@ -104,7 +107,7 @@ export const TNTGamesProfile = ({ - + @@ -124,7 +127,7 @@ export const TNTGamesProfile = ({ - + diff --git a/apps/discord-bot/src/commands/warlords/warlords.profile.tsx b/apps/discord-bot/src/commands/warlords/warlords.profile.tsx index 82d410c24..4561f703c 100644 --- a/apps/discord-bot/src/commands/warlords/warlords.profile.tsx +++ b/apps/discord-bot/src/commands/warlords/warlords.profile.tsx @@ -42,7 +42,11 @@ export const WarlordsProfile = ({ sidebar.push([t("stats.class"), prettify(warlords.class), "§e"]); const clazz = warlords.class as "mage" | "warrior" | "paladin" | "shaman"; // Verify that the cast is correct and the class is a valid class - if (clazz in warlords && typeof warlords[clazz] === "object") sidebar.push([t("stats.spec"), prettify(warlords[clazz].specification), "§a"]); + if (clazz in warlords && typeof warlords[clazz] === "object") + sidebar.push( + [t("stats.spec"), prettify(warlords[clazz].specification), "§a"], + [t("stats.level"), t(warlords[clazz].level), "§a"] + ); } let table: JSX.Element; diff --git a/apps/discord-bot/src/commands/woolgames/capture-the-wool.table.tsx b/apps/discord-bot/src/commands/woolgames/capture-the-wool.table.tsx index 3514b8815..4fd0ac318 100644 --- a/apps/discord-bot/src/commands/woolgames/capture-the-wool.table.tsx +++ b/apps/discord-bot/src/commands/woolgames/capture-the-wool.table.tsx @@ -12,6 +12,8 @@ import type { CaptureTheWool } from "@statsify/schemas"; import type { LocalizeFunction } from "@statsify/discord"; import type { ProfileTime } from "#commands/base.hypixel-command"; +const formatTimeWithSeconds = (time: number) => formatTime(time, { entries: 3 }); + interface CaptureTheWoolTableProps { captureTheWool: CaptureTheWool; t: LocalizeFunction; @@ -54,21 +56,21 @@ export const CaptureTheWoolTable = ({ captureTheWool, t, time }: CaptureTheWoolT 0}> 0}> 0}> diff --git a/locales/en-US/default.json b/locales/en-US/default.json index b66f3e884..ca7a7b4ee 100644 --- a/locales/en-US/default.json +++ b/locales/en-US/default.json @@ -576,6 +576,7 @@ "blocksBroken": "Blocks Broken", "blocksPlaced": "Blocks Placed", "blocksRan": "Blocks Ran", + "blocksTravelled": "Blocks Travelled", "bombsDefused": "Bombs Defused", "bombsPlanted": "Bombs Planted", "bounty": "Bounty", @@ -629,6 +630,7 @@ "goldEarned": "Gold Earned", "goldPickedUp": "Gold Picked Up", "goldRate": "Gold Rate", + "goldRequirement": "Gold Requirement", "goldSpent": "Gold Spent", "goldTrophies": "Gold Trophies", "grandPrixTokens": "GP Tokens", @@ -728,10 +730,12 @@ "powerupActivations": "Power-Up Activations", "powerups": "Power-Ups", "prefix": "Prefix", + "prestigeGold": "Prestige Gold", "prevent": "Prevent", "progression": { "exp": "EXP Progress", "gexp": "GEXP Progress", + "gold": "Gold Progress", "goldTrophy": "Gold Trophy Progress", "kill": "Kill Progress", "score": "Score Progress", @@ -769,7 +773,9 @@ "title": "Title", "tokens": "Tokens", "total": "Total", + "totalKills": "Total Kills", "totalSlumberTickets": "Total Slumber Tickets", + "totalWins": "Total Wins", "totalTrophies": "Total Trophies", "trail": "Trail", "transfusion": "Transfusion", diff --git a/packages/schemas/src/player/gamemodes/arcade/mode.ts b/packages/schemas/src/player/gamemodes/arcade/mode.ts index e2dd5a1a2..d1bac2a7f 100644 --- a/packages/schemas/src/player/gamemodes/arcade/mode.ts +++ b/packages/schemas/src/player/gamemodes/arcade/mode.ts @@ -16,6 +16,8 @@ import { import { Field } from "#metadata"; import { add, deepAdd, deepSub, ratio, sub } from "@statsify/math"; +const formatTimeWithSeconds = (time: number) => formatTime(time, { entries: 3 }); + export class BlockingDead { @Field() public wins: number; @@ -733,7 +735,7 @@ export class Dropper { @Field() public flawlessGames: number; - @Field({ leaderboard: { formatter: formatTime, sort: "ASC" } }) + @Field({ leaderboard: { formatter: formatTimeWithSeconds, sort: "ASC" } }) public bestTime: number; @Field({ leaderboard: { name: "Maps:" } }) @@ -863,6 +865,9 @@ export class GalaxyWars { @Field() public rebelKills: number; + @Field() + public shotsFired: number; + public constructor(data: APIData) { this.wins = data.sw_game_wins; this.kills = data.sw_kills; @@ -870,6 +875,7 @@ export class GalaxyWars { this.kdr = ratio(this.kills, this.deaths); this.empireKills = data.sw_empire_kills; this.rebelKills = data.sw_rebel_kills; + this.shotsFired = data.sw_shots_fired; } } @@ -1370,19 +1376,22 @@ export class ZombiesMapDifficulty { @Field({ leaderboard: { sort: "ASC", - formatter: formatTime, + formatter: formatTimeWithSeconds, additionalFields: ["this.wins"], }, historical: { enabled: false }, }) public fastestWin: number; - @Field({ leaderboard: { enabled: false } }) + @Field({ leaderboard: { additionalFields: ["this.deaths", "this.kdr"] } }) public kills: number; - @Field({ leaderboard: { enabled: false } }) + @Field({ leaderboard: { additionalFields: ["this.kills", "this.kdr"] } }) public deaths: number; + @Field({ leaderboard: { additionalFields: ["this.kills", "this.deaths"] } }) + public kdr: number; + @Field({ leaderboard: { enabled: false } }) public bestRound: number; @@ -1399,6 +1408,7 @@ export class ZombiesMapDifficulty { this.fastestWin = (data[`fastest_time_30_zombies${mode}`] ?? 0) * 1000; this.kills = data[`zombie_kills_zombies${mode}`]; this.deaths = data[`deaths_zombies${mode}`]; + this.kdr = ratio(this.kills, this.deaths); this.bestRound = data[`best_round_zombies${mode}`]; this.doorsOpened = data[`doors_opened_zombies${mode}`]; this.totalRounds = data[`total_rounds_survived_zombies${mode}`]; diff --git a/packages/schemas/src/player/gamemodes/bedwars/index.ts b/packages/schemas/src/player/gamemodes/bedwars/index.ts index 69aa8947c..13d8c97d6 100644 --- a/packages/schemas/src/player/gamemodes/bedwars/index.ts +++ b/packages/schemas/src/player/gamemodes/bedwars/index.ts @@ -37,14 +37,63 @@ export const BEDWARS_MODES = new GameModes([ { api: "threes", hypixel: "BEDWARS_FOUR_THREE" }, { api: "fours", hypixel: "BEDWARS_FOUR_FOUR" }, { api: "4v4", hypixel: "BEDWARS_TWO_FOUR" }, - { api: "armed" }, + { + api: "armed", + submodes: [ + { api: "armed", formatted: "Overall" }, + { api: "armedDoubles", formatted: "Doubles" }, + { api: "armedFours", formatted: "Fours" }, + ], + }, { api: "castle", hypixel: "BEDWARS_CASTLE" }, - { api: "lucky" }, - { api: "rush" }, - { api: "swap" }, - { api: "ultimate" }, - { api: "underworld" }, - { api: "voidless" }, + { + api: "lucky", + submodes: [ + { api: "lucky", formatted: "Overall" }, + { api: "luckyDoubles", formatted: "Doubles" }, + { api: "luckyFours", formatted: "Fours" }, + ], + }, + { + api: "rush", + submodes: [ + { api: "rush", formatted: "Overall" }, + { api: "rushDoubles", formatted: "Doubles" }, + { api: "rushFours", formatted: "Fours" }, + ], + }, + { + api: "swap", + submodes: [ + { api: "swap", formatted: "Overall" }, + { api: "swapDoubles", formatted: "Doubles" }, + { api: "swapFours", formatted: "Fours" }, + ], + }, + { + api: "ultimate", + submodes: [ + { api: "ultimate", formatted: "Overall" }, + { api: "ultimateDoubles", formatted: "Doubles" }, + { api: "ultimateFours", formatted: "Fours" }, + ], + }, + { + api: "underworld", + submodes: [ + { api: "underworld", formatted: "Overall" }, + { api: "underworldDoubles", formatted: "Doubles" }, + { api: "underworldFours", formatted: "Fours" }, + ], + }, + { + api: "voidless", + submodes: [ + { api: "voidless", formatted: "Overall" }, + { api: "voidlessDoubles", formatted: "Doubles" }, + { api: "voidlessFours", formatted: "Fours" }, + ], + }, { api: "oneBlock", hypixel: "BEDWARS_EIGHT_ONE_ONEBLOCK" }, { api: "challenges" }, @@ -167,6 +216,48 @@ export class BedWars { @Field() public oneBlock: BedWarsMode; + @Field() + public armedDoubles: BedWarsMode; + + @Field() + public armedFours: BedWarsMode; + + @Field() + public luckyDoubles: BedWarsMode; + + @Field() + public luckyFours: BedWarsMode; + + @Field() + public rushDoubles: BedWarsMode; + + @Field() + public rushFours: BedWarsMode; + + @Field() + public swapDoubles: BedWarsMode; + + @Field() + public swapFours: BedWarsMode; + + @Field() + public ultimateDoubles: BedWarsMode; + + @Field() + public ultimateFours: BedWarsMode; + + @Field() + public underworldDoubles: BedWarsMode; + + @Field() + public underworldFours: BedWarsMode; + + @Field() + public voidlessDoubles: BedWarsMode; + + @Field() + public voidlessFours: BedWarsMode; + @Field() public challenges: BedWarsModeChallenges; @@ -250,6 +341,21 @@ export class BedWars { this.voidless = DreamsBedWarsMode.new(data, "voidless"); this.oneBlock = new BedWarsMode(data, "eight_one_oneblock"); + this.armedDoubles = new BedWarsMode(data, "eight_two_armed"); + this.armedFours = new BedWarsMode(data, "four_four_armed"); + this.luckyDoubles = new BedWarsMode(data, "eight_two_lucky"); + this.luckyFours = new BedWarsMode(data, "four_four_lucky"); + this.rushDoubles = new BedWarsMode(data, "eight_two_rush"); + this.rushFours = new BedWarsMode(data, "four_four_rush"); + this.swapDoubles = new BedWarsMode(data, "eight_two_swap"); + this.swapFours = new BedWarsMode(data, "four_four_swap"); + this.ultimateDoubles = new BedWarsMode(data, "eight_two_ultimate"); + this.ultimateFours = new BedWarsMode(data, "four_four_ultimate"); + this.underworldDoubles = new BedWarsMode(data, "eight_two_underworld"); + this.underworldFours = new BedWarsMode(data, "four_four_underworld"); + this.voidlessDoubles = new BedWarsMode(data, "eight_two_voidless"); + this.voidlessFours = new BedWarsMode(data, "four_four_voidless"); + this.core = deepSub(this.overall, this["4v4"]); BedWarsMode.applyRatios(this.core); diff --git a/packages/schemas/src/player/gamemodes/duels/mode.ts b/packages/schemas/src/player/gamemodes/duels/mode.ts index 9f218a630..bdda67cd6 100644 --- a/packages/schemas/src/player/gamemodes/duels/mode.ts +++ b/packages/schemas/src/player/gamemodes/duels/mode.ts @@ -12,6 +12,8 @@ import { Progression } from "#progression"; import { TitleRequirement, getTitleAndProgression } from "./util.js"; import { add, deepAdd, ratio } from "@statsify/math"; +const formatTimeWithSeconds = (time: number) => formatTime(time, { entries: 3 }); + export class BaseDuelsGameMode { @Field() public bestWinstreak: number; @@ -649,7 +651,7 @@ export class ParkourDuels extends SingleDuelsGameMode { @Field() public checkpoints: number; - @Field({ leaderboard: { formatter: formatTime, sort: "ASC" } }) + @Field({ leaderboard: { formatter: formatTimeWithSeconds, sort: "ASC" } }) public bestTime: number; public constructor(data: APIData) { diff --git a/packages/schemas/src/player/gamemodes/general/index.ts b/packages/schemas/src/player/gamemodes/general/index.ts index f3e6dd85e..e6920dd44 100644 --- a/packages/schemas/src/player/gamemodes/general/index.ts +++ b/packages/schemas/src/player/gamemodes/general/index.ts @@ -20,6 +20,12 @@ export class General { @Field() public achievementPoints: number; + @Field() + public totalWins: number; + + @Field() + public totalKills: number; + @Field() public giftsSent: number; diff --git a/packages/schemas/src/player/gamemodes/murdermystery/mode.ts b/packages/schemas/src/player/gamemodes/murdermystery/mode.ts index 3fe4ad413..a30e537b1 100644 --- a/packages/schemas/src/player/gamemodes/murdermystery/mode.ts +++ b/packages/schemas/src/player/gamemodes/murdermystery/mode.ts @@ -11,6 +11,8 @@ import { Field } from "#metadata"; import { Progression } from "#progression"; import { ratio } from "@statsify/math"; +const formatTimeWithSeconds = (time: number) => formatTime(time, { entries: 3 }); + export class BaseMurderMysteryMode { @Field() public wins: number; @@ -98,13 +100,13 @@ export class OverallMurderMysteryMode extends StandardMurderMysteryMode { export class ClassicMurderMysteryMode extends StandardMurderMysteryMode { @Field({ - leaderboard: { sort: "ASC", formatter: formatTime }, + leaderboard: { sort: "ASC", formatter: formatTimeWithSeconds }, historical: { enabled: false }, }) public fastestDetectiveWin: number; @Field({ - leaderboard: { sort: "ASC", formatter: formatTime }, + leaderboard: { sort: "ASC", formatter: formatTimeWithSeconds }, historical: { enabled: false }, }) public fastestMurdererWin: number; diff --git a/packages/schemas/src/player/gamemodes/parkour/index.ts b/packages/schemas/src/player/gamemodes/parkour/index.ts index 8936c32f9..ff57df798 100644 --- a/packages/schemas/src/player/gamemodes/parkour/index.ts +++ b/packages/schemas/src/player/gamemodes/parkour/index.ts @@ -14,7 +14,8 @@ export const PARKOUR_MODES = new GameModes([{ api: "overall" }] as const); export type ParkourModes = ExtractGameModes; -const fieldOptions = { sort: "ASC", formatter: formatTime, fieldName: "Time" }; +const formatTimeWithSeconds = (time: number) => formatTime(time, { entries: 3 }); +const fieldOptions = { sort: "ASC", formatter: formatTimeWithSeconds, fieldName: "Time" }; const historical = { enabled: false }; export class Parkour { diff --git a/packages/schemas/src/player/gamemodes/pit/index.ts b/packages/schemas/src/player/gamemodes/pit/index.ts index b647ec65c..2e19f934f 100644 --- a/packages/schemas/src/player/gamemodes/pit/index.ts +++ b/packages/schemas/src/player/gamemodes/pit/index.ts @@ -16,6 +16,7 @@ import { getLevel, getLevelFormatted, getPrestige, + getPrestigeGoldReq, getPrestigeReq, getRenownShopCost, } from "./util.js"; @@ -79,6 +80,15 @@ export class Pit { }) public goldEarned: number; + @Field({ leaderboard: { enabled: false }, historical: { enabled: false } }) + public prestigeGold: number; + + @Field({ leaderboard: { enabled: false }, historical: { enabled: false } }) + public goldRequirement: number; + + @Field({ leaderboard: { enabled: false }, historical: { enabled: false } }) + public goldProgression: Progression; + @Field({ historical: { enabled: false } }) public renown: number; @@ -162,11 +172,19 @@ export class Pit { this.trueLevel = prestige * 120 + level; const lastPrestigeReq = getPrestigeReq(prestige - 1); + const prestigeGoldReq = getPrestigeGoldReq(prestige); + + this.prestigeGold = profile[`cash_during_prestige_${prestige}`] ?? 0; + this.goldRequirement = prestigeGoldReq; this.progression = new Progression( this.exp - lastPrestigeReq, Math.min(getPrestigeReq(prestige) - lastPrestigeReq, 11_787_293_080) ); + this.goldProgression = new Progression( + this.prestigeGold, + Math.max(prestigeGoldReq, 0) + ); this.levelFormatted = getLevelFormatted(level, prestige); this.nextLevelFormatted = diff --git a/packages/schemas/src/player/gamemodes/pit/util.ts b/packages/schemas/src/player/gamemodes/pit/util.ts index e7f5dc1d8..b9d6e16c2 100644 --- a/packages/schemas/src/player/gamemodes/pit/util.ts +++ b/packages/schemas/src/player/gamemodes/pit/util.ts @@ -29,6 +29,16 @@ const PRESTIGE_XP_REQUIREMENTS = [ 11_787_293_080, ]; +const PRESTIGE_GOLD_REQUIREMENTS = [ + 10_000, 20_000, 20_000, 20_000, 30_000, 35_000, 40_000, 45_000, 50_000, + 60_000, 70_000, 80_000, 90_000, 100_000, 125_000, 150_000, 175_000, 200_000, + 250_000, 300_000, 350_000, 400_000, 500_000, 600_000, 700_000, 800_000, + 900_000, 1_000_000, 1_000_000, 1_000_000, 1_000_000, 1_000_000, 1_000_000, + 1_000_000, 1_000_000, 2_000_000, 2_000_000, 2_000_000, 2_000_000, 2_000_000, + 2_000_000, 2_000_000, 2_000_000, 2_000_000, 2_000_000, 2_000_000, 2_000_000, + 2_000_000, 2_000_000, 2_000_000, -1, +]; + const PRESTIGE_COLORS = [ "7", "9", @@ -82,6 +92,9 @@ export const getPrestige = (xp: number) => { export const getPrestigeReq = (prestige: number) => prestige > -1 ? PRESTIGE_XP_REQUIREMENTS[prestige] : 0; +export const getPrestigeGoldReq = (prestige: number) => + prestige > -1 ? PRESTIGE_GOLD_REQUIREMENTS[prestige] : 0; + export const getLevel = (pres: number, xp: number) => { let level = 120; if ( diff --git a/packages/schemas/src/player/gamemodes/quake/mode.ts b/packages/schemas/src/player/gamemodes/quake/mode.ts index f0bf2a5cd..44737571a 100644 --- a/packages/schemas/src/player/gamemodes/quake/mode.ts +++ b/packages/schemas/src/player/gamemodes/quake/mode.ts @@ -29,9 +29,12 @@ export class QuakeMode { @Field() public killstreaks: number; - @Field({ leaderboard: { enabled: false } }) + @Field() public shotsFired: number; + @Field({ leaderboard: { fieldName: "Blocks Travelled", name: "Blocks Travelled" } }) + public blocksTravelled: number; + @Field({ leaderboard: { fieldName: "2017+ Kills", name: "2017+ Kills" } }) public postUpdateKills: number; @@ -41,6 +44,9 @@ export class QuakeMode { @Field({ leaderboard: { enabled: false } }) public quakeShotAccuracy: number; + @Field({ leaderboard: { enabled: false } }) + public headshotAccuracy: number; + public constructor(data: APIData, mode: string) { mode = mode ? `_${mode}` : mode; @@ -50,6 +56,7 @@ export class QuakeMode { this.headshots = data[`headshots${mode}`]; this.killstreaks = data[`killstreaks${mode}`]; this.shotsFired = data[`shots_fired${mode}`]; + this.blocksTravelled = data[`distance_travelled${mode}`]; this.postUpdateKills = data[`kills_since_update_feb_2017${mode}`]; QuakeMode.applyRatios(this); } @@ -58,5 +65,6 @@ export class QuakeMode { data.kdr = ratio(data.kills, data.deaths); data.kwr = ratio(data.kills, data.wins); data.quakeShotAccuracy = ratio(data.postUpdateKills, data.shotsFired, 100); + data.headshotAccuracy = ratio(data.headshots, data.postUpdateKills, 100); } } diff --git a/packages/schemas/src/player/gamemodes/tntgames/index.ts b/packages/schemas/src/player/gamemodes/tntgames/index.ts index c8edc3e91..04f272373 100644 --- a/packages/schemas/src/player/gamemodes/tntgames/index.ts +++ b/packages/schemas/src/player/gamemodes/tntgames/index.ts @@ -6,10 +6,10 @@ * https://github.com/Statsify/statsify/blob/main/LICENSE */ +import { type APIData, formatTime } from "@statsify/util"; import { BowSpleef, PVPRun, TNTRun, TNTTag, Wizards } from "./mode.js"; import { type ExtractGameModes, GameModes } from "#game"; import { Field } from "#metadata"; -import type { APIData } from "@statsify/util"; export const TNT_GAMES_MODES = new GameModes([ { api: "overall" }, @@ -45,6 +45,9 @@ export class TNTGames { @Field() public wins: number; + @Field({ leaderboard: { formatter: formatTime }, historical: { enabled: false } }) + public playtime: number; + @Field({ leaderboard: { fieldName: "TNT Run", extraDisplay: "this.tntRun.naturalPrefix" }, }) @@ -65,6 +68,7 @@ export class TNTGames { public constructor(data: APIData, ap: APIData) { this.coins = data.coins; this.wins = data.wins; + this.playtime = (ap.tntgames_tnt_triathlon ?? 0) * 60_000; this.tntRun = new TNTRun(data, ap); this.pvpRun = new PVPRun(data); diff --git a/packages/schemas/src/player/gamemodes/tntgames/mode.ts b/packages/schemas/src/player/gamemodes/tntgames/mode.ts index abb824ba2..85782ce89 100644 --- a/packages/schemas/src/player/gamemodes/tntgames/mode.ts +++ b/packages/schemas/src/player/gamemodes/tntgames/mode.ts @@ -12,6 +12,8 @@ import { type GamePrefix, createPrefixProgression, cycleColors, defaultPrefix, g import { Progression } from "#progression"; import { ratio } from "@statsify/math"; +const formatTimeWithSeconds = (time: number) => formatTime(time, { entries: 3 }); + const tntgamesRainbow = (text: string) => cycleColors(text, ["c", "6", "e", "a", "b", "d", "5"]); // Prefixes for TNT Run, PVP Run and Bow Spleef @@ -95,7 +97,7 @@ export class PVPRun { @Field() public kdr: number; - @Field({ leaderboard: { formatter: formatTime }, historical: { enabled: false } }) + @Field({ leaderboard: { formatter: formatTimeWithSeconds }, historical: { enabled: false } }) public record: number; @Field() @@ -149,7 +151,7 @@ export class TNTRun { @Field() public wlr: number; - @Field({ leaderboard: { formatter: formatTime }, historical: { enabled: false } }) + @Field({ leaderboard: { formatter: formatTimeWithSeconds }, historical: { enabled: false } }) public record: number; @Field() @@ -413,4 +415,3 @@ export class Wizards { this.arcaneWizard = new WizardsClass(data, "arcane_wizard"); } } - diff --git a/packages/schemas/src/player/gamemodes/woolgames/capture-the-wool.ts b/packages/schemas/src/player/gamemodes/woolgames/capture-the-wool.ts index 477c45511..92cee381c 100644 --- a/packages/schemas/src/player/gamemodes/woolgames/capture-the-wool.ts +++ b/packages/schemas/src/player/gamemodes/woolgames/capture-the-wool.ts @@ -10,6 +10,8 @@ import { type APIData, formatTime } from "@statsify/util"; import { Field } from "#metadata"; import { ratio } from "@statsify/math"; +const formatTimeWithSeconds = (time: number) => formatTime(time, { entries: 3 }); + export class CaptureTheWool { @Field() public wins: number; @@ -44,7 +46,7 @@ export class CaptureTheWool { @Field({ leaderboard: { sort: "ASC", - formatter: formatTime, + formatter: formatTimeWithSeconds, additionalFields: ["this.wins"], }, historical: { enabled: false }, @@ -54,7 +56,7 @@ export class CaptureTheWool { @Field({ leaderboard: { sort: "ASC", - formatter: formatTime, + formatter: formatTimeWithSeconds, additionalFields: ["this.woolCaptured"], }, historical: { enabled: false }, @@ -62,7 +64,7 @@ export class CaptureTheWool { public fastestWoolCapture: number; @Field({ - leaderboard: { formatter: formatTime }, + leaderboard: { formatter: formatTimeWithSeconds }, historical: { enabled: false }, }) public longestGame: number; diff --git a/packages/schemas/src/player/stats.ts b/packages/schemas/src/player/stats.ts index 60fa3b0e6..b24b1821c 100644 --- a/packages/schemas/src/player/stats.ts +++ b/packages/schemas/src/player/stats.ts @@ -36,6 +36,7 @@ import { } from "./gamemodes/index.js"; import { Field } from "#metadata"; import { FormattedGame } from "#game"; +import { add } from "@statsify/math"; import type { APIData } from "@statsify/util"; export class PlayerStats { @@ -253,5 +254,56 @@ export class PlayerStats { this.walls = new Walls(stats.Walls ?? {}, legacy); this.warlords = new Warlords(stats.Battleground ?? {}); this.woolgames = new WoolGames(stats.WoolGames ?? {}, achievements); + + this.general.totalWins = add( + this.arcade.wins, + this.arenabrawl.overall.wins, + this.bedwars.overall.wins, + this.blitzsg.overall.wins, + this.buildbattle.overall.wins, + this.copsandcrims.overall.wins, + this.duels.overall.wins, + this.megawalls.overall.wins, + this.murdermystery.overall.wins, + this.paintball.wins, + this.quake.overall.wins, + this.skywars.overall.wins, + this.smashheroes.overall.wins, + this.speeduhc.overall.wins, + this.tntgames.wins, + this.uhc.overall.wins, + this.vampirez.overallWins, + this.walls.wins, + this.warlords.wins, + this.woolgames.wins + ); + + this.general.totalKills = add( + this.arenabrawl.overall.kills, + this.bedwars.overall.kills, + this.blitzsg.overall.kills, + this.copsandcrims.overall.kills, + this.duels.overall.kills, + this.megawalls.overall.kills, + this.murdermystery.overall.kills, + this.paintball.kills, + this.pit.kills, + this.quake.overall.kills, + this.skywars.overall.kills, + this.smashheroes.overall.kills, + this.speeduhc.overall.kills, + this.tntgames.pvpRun.kills, + this.tntgames.tntTag.kills, + this.tntgames.wizards.kills, + this.uhc.overall.kills, + this.vampirez.human.kills, + this.vampirez.vampire.kills, + this.vampirez.zombieKills, + this.walls.kills, + this.warlords.kills, + this.woolgames.woolwars.overall.kills, + this.woolgames.sheepwars.kills, + this.woolgames.captureTheWool.kills + ); } }