osu-pp-calculator
Version:
calculates performance points for given beatmap
163 lines (127 loc) • 4.13 kB
text/typescript
import Vec2 from './lib/vec2';
import BeatmapModifier from './mods';
const OD_0_MS: number = 79.5,
OD_10_MS: number = 19.5,
AR_0_MS: number = 1800,
AR_5_MS: number = 1200,
AR_10_MS: number = 450;
const OD_MS_STEP: number = 6,
AR_MS_STEP1: number = 120, // ar0-5
AR_MS_STEP2: number = 150; // ar5-10
export enum HitObjectType {
Circle,
Spinner,
Slider,
};
export interface HitObject {
position: Vec2;
startTime: number;
endTime: number;
type: HitObjectType;
};
export default class Beatmap {
mode: number;
hpDrainRate: number;
circleSize: number;
overallDifficulty: number;
approachRate: number;
sv: number;
combo: number;
tickRate: number;
hitObjects: HitObject[];
circleCount: number;
sliderCount: number;
spinnerCount: number;
applyMods(mods: number) {
if (!mods)
return;
// od
let overallDifficultyMultiplier: number = 1;
if (mods & BeatmapModifier.HardRock) {
overallDifficultyMultiplier *= 1.4;
}
if (mods & BeatmapModifier.Easy) {
overallDifficultyMultiplier *= 0.5;
}
this.overallDifficulty *= overallDifficultyMultiplier;
let overallDifficultyMillis = OD_0_MS - OD_MS_STEP * this.overallDifficulty;
// ar
let approachRateMultiplier: number = 1;
if (mods & BeatmapModifier.HardRock) {
approachRateMultiplier = 1.4;
}
if (mods & BeatmapModifier.Easy) {
approachRateMultiplier = 0.5;
}
this.approachRate *= approachRateMultiplier;
// convert AR into its milliseconds value
let approachRateMillis = this.approachRate <= 5
? (AR_0_MS - AR_MS_STEP1 * this.approachRate)
: (AR_5_MS - AR_MS_STEP2 * (this.approachRate - 5));
// cs
let circleSizeMultiplier = 1;
if (mods & BeatmapModifier.HardRock) {
circleSizeMultiplier = 1.3;
}
if (mods & BeatmapModifier.Easy) {
circleSizeMultiplier = 0.5;
}
// stats must be capped to 0-10 before HT/DT which bring them to a range
// of -4.42 to 11.08 for OD and -5 to 11 for AR
overallDifficultyMillis = Math.min(OD_0_MS, Math.max(OD_10_MS, overallDifficultyMillis));
approachRateMillis = Math.min(AR_0_MS, Math.max(AR_10_MS, approachRateMillis));
// playback speed
let speed: number = 1;
if (mods & BeatmapModifier.DoubleTime) {
speed *= 1.5;
}
if (mods & BeatmapModifier.HalfTime) {
speed *= 0.75;
}
const invSpeed = 1 / speed;
// apply speed-changing mods
overallDifficultyMillis *= invSpeed;
approachRateMillis *= invSpeed;
// convert OD and AR back into their stat form
this.overallDifficulty = (OD_0_MS - overallDifficultyMillis) / OD_MS_STEP;
this.approachRate = this.approachRate <= 5.0
? ((AR_0_MS - approachRateMillis) / AR_MS_STEP1)
: (5.0 + (AR_5_MS - approachRateMillis) / AR_MS_STEP2);
this.circleSize *= circleSizeMultiplier;
this.circleSize = Math.max(0.0, Math.min(10.0, this.circleSize));
if (!(mods & BeatmapModifier.DoubleTime || mods & BeatmapModifier.HalfTime)) {
// not speed-modifying
return;
}
// apply speed-changing mods
this.hitObjects.forEach(obj => {
obj.startTime *= invSpeed;
obj.endTime *= invSpeed;
});
}
static fromOsuParserObject (obj: any): Beatmap {
let beatmap = new Beatmap;
const types = {
'slider': HitObjectType.Slider,
'circle': HitObjectType.Circle,
'spinner': HitObjectType.Spinner,
};
beatmap.hitObjects = obj.hitObjects.map((ho: any) => ({
position: new Vec2(ho.position),
startTime: ho.startTime,
endTime: ho.endTime,
type: types[ho.objectName],
}));
beatmap.mode = obj.Mode;
beatmap.circleCount = obj.nbCircles
beatmap.sliderCount = obj.nbSliders;
beatmap.spinnerCount = obj.nbSpinners;
beatmap.hpDrainRate = obj.HPDrainRate;
beatmap.circleSize = obj.CircleSize;
beatmap.overallDifficulty = obj.OverallDifficulty;
beatmap.approachRate = obj.ApproachRate;
beatmap.combo = obj.maxCombo;
beatmap.tickRate = obj.SliderTickRate;
return beatmap;
}
};