UNPKG

osumodcalculator

Version:

osu! calculator for converting values to DT & HT and other things

439 lines (406 loc) 12.2 kB
import { types } from "."; /** * Get hit windows for an Overall Difficulty (accuracy) value (standard) * * @includeExample src/examples/stats.ts:17-22 */ export function ToMsOd(od: number) { const rangeobj = { hitwindow_300: 79 - (od * 6) + 0.5, hitwindow_100: 139 - (od * 8) + 0.5, hitwindow_50: 199 - (od * 10) + 0.5, }; return rangeobj; } /** * convert approach rate to milliseconds * * @includeExample src/examples/stats.ts:25 */ export function ToMsAr(ar: number) { const ogtoms = ar > 5 ? 1200 - (((ar - 5) * 10) * 15) : 1800 - (((ar) * 10) * 12); return ogtoms; } /** * convert hit window timings into an Overall Difficulty (accuracy) value * * if a hitwindow is missing, either replace it with NaN, null or undefined * * @includeExample src/examples/stats.ts:28-34 */ export function FromMsOd(hitwindow300: number, hitwindow100?: number, hitwindow50?: number) { let od: string = 'NaN'; if (!isNaN(hitwindow300)) { od = ((79.5 - hitwindow300) / 6).toFixed(2); } else if (hitwindow100 && !isNaN(hitwindow100)) { od = ((139.5 - hitwindow100) / 8).toFixed(2); } else if (hitwindow50 && !isNaN(hitwindow50)) { od = ((199.5 - hitwindow50) / 10).toFixed(2); } if (+od > 11) { od = '11'; } return +od; } /** * calculate approach rate from milliseconds * * @includeExample src/examples/stats.ts:37 */ export function FromMsAr(ms: number) { let ar: number; if (ms < 300) { ar = 11; } else if (ms < 1200) { ar = Math.round((11 - (ms - 300) / 150) * 100) / 100; } else { ar = Math.round((5 - (ms - 1200) / 120) * 100) / 100; } return ar; } /** * calculate approach rate with Double Time applied * * @includeExample src/examples/stats.ts:30-34 */ export function ToDtAr(ar: number) { /* if (ar > 5) { ms = 200 + (11 - ar) * 100; } else { ms = 800 + (5 - ar) * 80; } */ const ogtoms = ar > 5 ? 1200 - ((ar - 5) * 10 * 15) : 1800 - ((ar * 10) * 12); const ms = ogtoms * (2 / 3); let newAR: number; if (ms < 300) { newAR = 11; } else if (ms < 1200) { newAR = Math.round((11 - (ms - 300) / 150) * 100) / 100; } else { newAR = Math.round((5 - (ms - 1200) / 120) * 100) / 100; } const arobj = { ar: newAR, ms: ms, }; return arobj; } /** * calculate approach rate with Half Time applied * * @includeExample src/examples/stats.ts:48-53 */ export function ToHtAr(ar: number) { let newAR: number; const ogtoms = ar > 5 ? 1200 - ((ar - 5) * 10 * 15) : 1800 - ((ar * 10) * 12); const ms = ogtoms * (4 / 3); if (ms < 300) { newAR = 11; } else if (ms < 1200) { newAR = Math.round((11 - (ms - 300) / 150) * 100) / 100; } else { newAR = Math.round((5 - (ms - 1200) / 120) * 100) / 100; } const arobj = { ar: newAR, ms: ms, }; return arobj; } /** * calculate Overall Difficulty (accuracy) with Double Time applied * * @includeExample src/examples/stats.ts:56-63 */ export function ToDtOd(od: number) { const range300 = ((79 - (od * 6) + 0.5) * 2 / 3) + 0.33; const odobj = { hitwindow_300: range300, hitwindow_100: ((139 - (od * 8) + 0.5) * 2 / 3) + 0.33, hitwindow_50: ((199 - (od * 10) + 0.5) * 2 / 3) + 0.33, od: +((79.5 - range300) / 6).toFixed(2) > 11 ? 11 : +((79.5 - range300) / 6) }; return odobj; } /** * calculate Overall Difficulty (accuracy) with Half Time applied * * @includeExample src/examples/stats.ts:66-73 */ export function ToHtOd(od: number) { const range300 = ((79 - (od * 6) + 0.5) * 4 / 3) + 0.66; const odobj = { hitwindow_300: range300, hitwindow_100: ((139 - (od * 8) + 0.5) * 4 / 3) + 0.66, hitwindow_50: ((199 - (od * 10) + 0.5) * 4 / 3) + 0.66, od: +((79.5 - range300) / 6).toFixed(2) > 11 ? 11 : +((79.5 - range300) / 6) }; return odobj; } /** * calculate values with hard rock applied * * @includeExample src/examples/stats.ts:76-87 */ export function toHR(cs: number, ar: number, od: number, hp: number) { const hrobj = { cs: cs * 1.3 > 10 ? 10 : cs * 1.3, ar: ar * 1.4 > 10 ? 10 : ar * 1.4, od: od * 1.4 > 10 ? 10 : od * 1.4, hp: hp * 1.4 > 10 ? 10 : hp * 1.4, }; return hrobj; } /** * calculate values with hard rock applied * * @includeExample src/examples/stats.ts:90-101 */ export function toEZ(cs: number, ar: number, od: number, hp: number) { const ezobj = { cs: cs / 2 > 10 ? 10 : cs / 2, ar: ar / 2 > 10 ? 10 : ar / 2, od: od / 2 > 10 ? 10 : od / 2, hp: hp / 2 > 10 ? 10 : hp / 2, }; return ezobj; } /** * get the radius of the circle (in pixels) * * @includeExample src/examples/stats.ts:104 */ export function csToRadius(cs: number) { return (0.00005556 * cs ** 2 - 4.483 * cs + 54.42); } /** * radius to circle size * * @includeExample src/examples/stats.ts:108-109 */ export function csFromRadius(radius: number) { return +((5000 / 8104533921) * radius ** 2 - (1808448550 / 8104533921) * radius + (8582285633270972 / 706821088118109)).toFixed(2); } /** * calculate stats with speed applied * * @param i - the level of speed to apply. * Using DT or HT will apply 1.5 and 0.75 speed, respectively. * * @includeExample src/examples/stats.ts:112-133 */ export function speed(i: 'DT' | 'HT' | number, stats: { ar: number, od: number, bpm: number, songLength: number, }) { if (!i) { i = 1.0; } if (typeof i == 'string') { switch (i) { case "DT": i = 1.5; break; case "HT": i = 0.75; break; } } const modAr = ToMsAr(stats.ar) / i; const modOd = ToMsOd(stats.od); modOd.hitwindow_300 /= i; modOd.hitwindow_100 /= i; modOd.hitwindow_50 /= i; const modBpm = stats.bpm * i; const modSl = stats.songLength / i; return { ar: FromMsAr(modAr), od: FromMsOd(modOd.hitwindow_300), bpm: modBpm, songLength: modSl, extra: { arMs: modAr, odMs: modOd, lengthReadable: secondsToReadable(modSl), } }; } /** * calculate values with mods applied * * if custom speed is unused then the speed from any given mods will be used (DT, HT etc.) * * @includeExample src/examples/stats.ts:136-177 */ export function modded(stats: { cs: number, ar: number, od: number, hp: number, bpm: number, songLength: number, }, mods: types.ApiMod[] | types.Mod[], customSpeed?: number, ): { cs: number, ar: number, od: number, hp: number, bpm: number, songLength: number, extra: { csRadius: number, arMs: number, odMs: { hitwindow_300: number, hitwindow_100: number, hitwindow_50: number, }, lengthReadable: string, }; } { if (mods.length == 0) { if (!customSpeed || customSpeed == 1) { return { cs: stats.cs, ar: stats.ar, od: stats.od, hp: stats.hp, bpm: stats.bpm, songLength: stats.songLength, extra: { csRadius: csToRadius(stats.cs), arMs: ToMsAr(stats.ar), odMs: ToMsOd(stats.od), lengthReadable: secondsToReadable(stats.songLength), } }; } const modAr = ToMsAr(stats.ar) / customSpeed; const modOd = ToMsOd(stats.od); modOd.hitwindow_300 /= customSpeed; modOd.hitwindow_100 /= customSpeed; modOd.hitwindow_50 /= customSpeed; const modBpm = stats.bpm * customSpeed; const modSl = stats.songLength / customSpeed; return { cs: stats.cs, ar: FromMsAr(modAr), od: FromMsOd(modOd.hitwindow_300), hp: stats.hp, bpm: modBpm, songLength: modSl, extra: { csRadius: csToRadius(stats.cs), arMs: modAr, odMs: modOd, lengthReadable: secondsToReadable(modSl), } }; } let modCs = stats.cs; let modArMs = ToMsAr(stats.ar); let modOdMs = ToMsOd(stats.od); let modHp = stats.hp; let modBpm = stats.bpm; let modSl = stats.songLength; if (typeof mods[0] == 'string') { mods = mods as types.Mod[]; if (mods.includes('HR')) { modCs *= 1.3; modArMs = ToMsAr(stats.ar * 1.4); modOdMs = ToMsOd(stats.od * 1.4); modHp *= 1.4; } else if (mods.includes('EZ')) { modCs /= 2; modArMs = ToMsAr(stats.ar / 2); modOdMs = ToMsOd(stats.od / 2); modHp /= 2; } if (!customSpeed) { if (mods.includes('DT') || mods.includes('NC')) customSpeed = 1.5; if (mods.includes('HT') || mods.includes('DC')) customSpeed = 0.75; } } else { mods = mods as types.ApiMod[]; const modacrs = mods.map(x => x.acronym); if (modacrs.includes('HR')) { modCs *= 1.3; modArMs = ToMsAr(stats.ar * 1.4); modOdMs = ToMsOd(stats.od * 1.4); modHp *= 1.4; } else if (modacrs.includes('EZ')) { modCs /= 2; modArMs = ToMsAr(stats.ar / 2); modOdMs = ToMsOd(stats.od / 2); modHp /= 2; } for (const mod of mods) { if (mod?.settings) { if (mod.settings?.circle_size) { modCs = mod.settings.circle_size; } if (mod.settings?.approach_rate) { modArMs = ToMsAr(mod.settings.approach_rate); } if (mod.settings?.overall_difficulty) { modOdMs = ToMsOd(mod.settings.overall_difficulty); } if (mod.settings?.drain_rate) { modHp = mod.settings.drain_rate; } if (mod?.settings?.speed_change) { customSpeed = mod?.settings?.speed_change; } } } if (!customSpeed) { if (modacrs.includes('DT') || modacrs.includes('NC')) customSpeed = 1.5; if (modacrs.includes('HT') || modacrs.includes('DC')) customSpeed = 0.75; } } if (customSpeed) { modArMs /= customSpeed; modOdMs.hitwindow_300 /= customSpeed; modOdMs.hitwindow_100 /= customSpeed; modOdMs.hitwindow_50 /= customSpeed; modBpm *= customSpeed; modSl /= customSpeed; } return { cs: modCs, ar: FromMsAr(modArMs), od: FromMsOd(modOdMs.hitwindow_300), hp: modHp, bpm: modBpm, songLength: modSl, extra: { csRadius: csToRadius(modCs), arMs: modArMs, odMs: modOdMs, lengthReadable: secondsToReadable(modSl), } }; } function secondsToReadable(inpt: number) { return inpt > 60 ? inpt % 60 < 10 ? Math.floor(inpt / 60) + ':0' + Math.floor(inpt % 60) : Math.floor(inpt / 60) + ':' + Math.floor(inpt % 60) : inpt % 60 < 10 ? Math.floor(inpt / 60) + ':0' + Math.floor(inpt % 60) : Math.floor(inpt / 60) + ':' + Math.floor(inpt % 60); }