From 8b90449970cc2824610c5914e19c81660d3f28aa Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Thu, 26 Dec 2024 19:37:55 -0500 Subject: [PATCH] [+] Chusan rating calculation --- AquaNet/src/components/RatingCompSong.svelte | 48 ++-- AquaNet/src/libs/scoring.ts | 236 ++++++++++++------- AquaNet/src/pages/UserHome.svelte | 2 + 3 files changed, 171 insertions(+), 115 deletions(-) diff --git a/AquaNet/src/components/RatingCompSong.svelte b/AquaNet/src/components/RatingCompSong.svelte index 5e5d42e9..fed7071b 100644 --- a/AquaNet/src/components/RatingCompSong.svelte +++ b/AquaNet/src/components/RatingCompSong.svelte @@ -4,7 +4,7 @@ import { slide } from "svelte/transition"; import { DATA_HOST } from "../libs/config"; import { t } from "../libs/i18n"; - import { type GameName, getMult, roundFloor } from "../libs/scoring"; + import { type GameName, getMult, parseComposition, roundFloor } from "../libs/scoring"; import { coverNotFound } from "../libs/ui"; import type { MusicMeta } from "../libs/generalTypes"; import { tooltip } from "../libs/ui"; @@ -14,47 +14,35 @@ export let meta: MusicMeta export let game: GameName - let mapData = g.split(":").map(Number) - let mult = getMult(mapData[3], game) - let mapRank: number | undefined = meta?.notes?.[mapData[1] === 10 ? 0 : mapData[1]]?.lv - const rounding = useLocalStorage("rounding", true); - - let gameIndexMap = { - 'mai2': 3, - 'ongeki': 2, - 'chu3': 2 - }; - - let gameIndex = gameIndexMap[game as keyof typeof gameIndexMap]; - + // // mapData: [id, difficulty, score, rank] + // let mapData = g.split(":").map(Number) + // // mult: [score cutoff, rank multiplier, rank text] + // let mult = getMult(mapData[3], game) + // let mapRank: number | undefined = meta?.notes?.[mapData[1] === 10 ? 0 : mapData[1]]?.lv + const p = parseComposition(g, meta, game) + const rounding = useLocalStorage("rounding", true) +
- +
{meta?.name ?? t("UserHome.UnknownSong")}
- - { mapRank ?? '-' } + + { p.difficulty ?? '-' }
- - - {("" + getMult(mapData[gameIndex], game)[2]).replace("p", "+")} - - { - rounding.value ? - roundFloor(mapData[gameIndex], game, 1) : - (mapData[gameIndex] / 10000).toFixed(4) - }% + + {p.rank.replace("p", "+")} + + {rounding.value ? roundFloor(p.score, game, 1) : (p.score / 10000).toFixed(4)}% - {#if game === 'mai2'} - - { mapRank ? Math.floor(mapRank * mult[1] * (Math.min(100.5, mapData[3] / 10000) / 100)) : '-' } - + {#if p.ratingChange !== undefined} + { p.ratingChange.toFixed(1) } {/if}
diff --git a/AquaNet/src/libs/scoring.ts b/AquaNet/src/libs/scoring.ts index 91ded584..acee0e85 100644 --- a/AquaNet/src/libs/scoring.ts +++ b/AquaNet/src/libs/scoring.ts @@ -1,85 +1,151 @@ -export type GameName = 'mai2' | 'chu3' | 'ongeki' | 'wacca' - -const multTable = { - 'mai2': [ - [ 100.5, 22.4, 'SSSp' ], - [ 100.0, 21.6, 'SSS' ], - [ 99.5, 21.1, 'SSp' ], - [ 99, 20.8, 'SS' ], - [ 98, 20.3, 'Sp' ], - [ 97, 20, 'S' ], - [ 94, 16.8, 'AAA' ], - [ 90, 15.2, 'AA' ], - [ 80, 13.6, 'A' ], - [ 75, 12, 'BBB' ], - [ 70, 11.2, 'BB' ], - [ 60, 9.6, 'B' ], - [ 50, 8, 'C' ], - [ 40, 6.4, 'D' ], - [ 30, 4.8, 'D' ], - [ 20, 3.2, 'D' ], - [ 10, 1.6, 'D' ], - [ 0, 0, 'D' ] - ], - - // TODO: Fill in multipliers for Chunithm and Ongeki - 'chu3': [ - [ 100.75, 0, 'SSS' ], - [ 100.0, 0, 'SS' ], - [ 97.5, 0, 'S' ], - [ 95.0, 0, 'AAA' ], - [ 92.5, 0, 'AA' ], - [ 90.0, 0, 'A' ], - [ 80.0, 0, 'BBB' ], - [ 70.0, 0, 'BB' ], - [ 60.0, 0, 'B' ], - [ 50.0, 0, 'C' ], - [ 0.0, 0, 'D' ] - ], - - 'ongeki': [ - [ 100.75, 0, 'SSS+' ], - [ 100.0, 0, 'SSS' ], - [ 99.0, 0, 'SS' ], - [ 97.0, 0, 'S' ], - [ 94.0, 0, 'AAA' ], - [ 90.0, 0, 'AA' ], - [ 85.0, 0, 'A' ], - [ 80.0, 0, 'BBB' ], - [ 75.0, 0, 'BB' ], - [ 70.0, 0, 'B' ], - [ 50.0, 0, 'C' ], - [ 0.0, 0, 'D' ] - ], - - 'wacca': [ - [ 100.0, 0, 'AP' ], - [ 98.0, 0, 'SSS' ], - [ 95.0, 0, 'SS' ], - [ 90.0, 0, 'S' ], - [ 85.0, 0, 'AAA' ], - [ 80.0, 0, 'AA' ], - [ 70.0, 0, 'A' ], - [ 60.0, 0, 'B' ], - [ 1.0, 0, 'C' ], - [ 0.0, 0, 'D' ] - ] -} - -export function getMult(achievement: number, game: GameName) { - achievement /= 10000 - const mt = multTable[game] - for (let i = 0; i < mt.length; i++) { - if (achievement >= (mt[i][0] as number)) return mt[i] - } - return [ 0, 0, 0 ] -} - -export function roundFloor(achievement: number, game: GameName, digits = 2) { - // Round, but if the rounded number reaches the next rank, use floor instead - const mult = getMult(achievement, game); - achievement /= 10000 - const rounded = achievement.toFixed(digits); - if (getMult(+rounded * 10000, game)[2] === mult[2] && rounded !== '101.0') return rounded; - return (+rounded - Math.pow(10, -digits)).toFixed(digits); -} +import { DATA_HOST } from "./config" +import type { MusicMeta } from "./generalTypes" + +export type GameName = 'mai2' | 'chu3' | 'ongeki' | 'wacca' + +const multTable = { + 'mai2': [ + [ 100.5, 22.4, 'SSSp' ], + [ 100.0, 21.6, 'SSS' ], + [ 99.5, 21.1, 'SSp' ], + [ 99, 20.8, 'SS' ], + [ 98, 20.3, 'Sp' ], + [ 97, 20, 'S' ], + [ 94, 16.8, 'AAA' ], + [ 90, 15.2, 'AA' ], + [ 80, 13.6, 'A' ], + [ 75, 12, 'BBB' ], + [ 70, 11.2, 'BB' ], + [ 60, 9.6, 'B' ], + [ 50, 8, 'C' ], + [ 40, 6.4, 'D' ], + [ 30, 4.8, 'D' ], + [ 20, 3.2, 'D' ], + [ 10, 1.6, 'D' ], + [ 0, 0, 'D' ] + ], + + // TODO: Fill in multipliers for Chunithm and Ongeki + 'chu3': [ + [ 100.9, 215, 'SSS+' ], + [ 100.75, 200, 'SSS' ], + [ 100.0, 0, 'SS' ], + [ 97.5, 0, 'S' ], + [ 95.0, 0, 'AAA' ], + [ 92.5, 0, 'AA' ], + [ 90.0, 0, 'A' ], + [ 80.0, 0, 'BBB' ], + [ 70.0, 0, 'BB' ], + [ 60.0, 0, 'B' ], + [ 50.0, 0, 'C' ], + [ 0.0, 0, 'D' ] + ], + + 'ongeki': [ + [ 100.75, 0, 'SSS+' ], + [ 100.0, 0, 'SSS' ], + [ 99.0, 0, 'SS' ], + [ 97.0, 0, 'S' ], + [ 94.0, 0, 'AAA' ], + [ 90.0, 0, 'AA' ], + [ 85.0, 0, 'A' ], + [ 80.0, 0, 'BBB' ], + [ 75.0, 0, 'BB' ], + [ 70.0, 0, 'B' ], + [ 50.0, 0, 'C' ], + [ 0.0, 0, 'D' ] + ], + + 'wacca': [ + [ 100.0, 0, 'AP' ], + [ 98.0, 0, 'SSS' ], + [ 95.0, 0, 'SS' ], + [ 90.0, 0, 'S' ], + [ 85.0, 0, 'AAA' ], + [ 80.0, 0, 'AA' ], + [ 70.0, 0, 'A' ], + [ 60.0, 0, 'B' ], + [ 1.0, 0, 'C' ], + [ 0.0, 0, 'D' ] + ] +} + +export function getMult(achievement: number, game: GameName) { + achievement /= 10000 + const mt = multTable[game] + for (let i = 0; i < mt.length; i++) { + if (achievement >= (mt[i][0] as number)) return mt[i] + } + return [ 0, 0, 0 ] +} + +export function roundFloor(achievement: number, game: GameName, digits = 2) { + // Round, but if the rounded number reaches the next rank, use floor instead + const mult = getMult(achievement, game); + achievement /= 10000 + const rounded = achievement.toFixed(digits); + if (getMult(+rounded * 10000, game)[2] === mult[2] && rounded !== '101.0') return rounded; + return (+rounded - Math.pow(10, -digits)).toFixed(digits); +} + +export function chusanRating(lv: number, score: number) { + console.log(lv) + lv = lv * 100 + if (score >= 1009000) return lv + 215; // SSS+ + if (score >= 1007500) return lv + 200 + (score - 1007500) / 100; // SSS + if (score >= 1005000) return lv + 150 + (score - 1005000) / 50; // SS+ + if (score >= 1000000) return lv + 100 + (score - 1000000) / 100; // SS + if (score >= 975000) return lv + (score - 975000) / 250; // S+, S + if (score >= 925000) return lv - 300 + (score - 925000) * 3 / 500; // AA + if (score >= 900000) return lv - 500 + (score - 900000) * 4 / 500; // A + if (score >= 800000) return ((lv - 500) / 2 + (score - 800000) * ((lv - 500) / 2) / (100000)); // BBB + return 0; // C +} + +interface ParsedComposition { + musicId: number + diffId: number // ID of the difficulty + score: number + cutoff: number + mult: number + rank: string // e.g. 'SSS+' + difficulty?: number // Actual difficulty of the map + img: string + ratingChange?: number // Rating change after playing this map +} + + +export function parseComposition(item: string, meta: MusicMeta, game: GameName): ParsedComposition { + // Chuni & ongeki: musicId, difficultId, score + // Mai: musicId, level (difficultyId), romVersion, achievement (score) + const mapData = item.split(':').map(Number) + if (game === 'mai2') mapData.splice(2, 1) + const [ musicId, diffId, score ] = mapData + + // Get score multiplier + const tup = getMult(score, game) + const [ cutoff, mult ] = [ +tup[0], +tup[1] ] + const rank = tup[2] as string + + let diff = meta?.notes?.[mapData[1] === 10 ? 0 : mapData[1]]?.lv + + function calcDxChange() { + if (!diff) return + if (game === 'mai2') + return Math.floor(diff * +mult * (Math.min(100.5, mapData[3] / 10000) / 100)) + if (game === 'chu3') + return chusanRating(diff, score) / 100 + } + + return { + musicId, + diffId, + score, + cutoff, + mult, + rank, + difficulty: diff, + img: `${DATA_HOST}/d/${game}/music/00${mapData[0].toString().padStart(6, '0').substring(2)}.png`, + ratingChange: calcDxChange() + } +} diff --git a/AquaNet/src/pages/UserHome.svelte b/AquaNet/src/pages/UserHome.svelte index b3be7134..46102176 100644 --- a/AquaNet/src/pages/UserHome.svelte +++ b/AquaNet/src/pages/UserHome.svelte @@ -260,6 +260,8 @@ + +

{t('UserHome.RecentScores')}