osu-pp-calculator
Version:
calculates performance points for given beatmap
170 lines (169 loc) • 6.97 kB
JavaScript
;
var vec2_1 = require('./lib/vec2');
var beatmap_1 = require('./beatmap');
var DifficultyType;
(function (DifficultyType) {
DifficultyType[DifficultyType["SPEED"] = 0] = "SPEED";
DifficultyType[DifficultyType["AIM"] = 1] = "AIM";
})(DifficultyType || (DifficultyType = {}));
;
// how much strains decay per interval (if the previous interval's peak
// strains after applying decay are still higher than the current one's,
// they will be used as the peak strains).
var DECAY_BASE = [0.3, 0.15];
// almost the normalized circle diameter (104px)
var DIAMETER_APPROX = 90;
// arbitrary tresholds to determine when a stream is spaced enough that is
// becomes hard to alternate.
var STREAM_INTERVAL = 110;
var SINGLETAP_INTERVAL = 125;
// used to keep speed and aim balanced between eachother
var WEIGHT_SCALING = [1400, 26.25];
// non-normalized diameter where the circlesize buff starts
var CS_BUFF_TRESHOLD = 30;
// diffcalc hit object
var DiffCalcHitObject = (function () {
function DiffCalcHitObject(hitObject, radius) {
this.hitObject = hitObject;
// strains start at 1
this.strains = [1.0, 1.0];
//this.hitObject = baseObject;
// strains start at 1
this.strains = [1, 1];
// positions are normalized on circle radius so that we can calc as
// if everything was the same circlesize
var scalingFactor = 52.0 / radius;
// cs buff (based on osuElements, pretty accurate but not 100% sure)
//
// some high cs data I've collected:
//
// cs5.85 on RoR:
// 1.822916667% aim stars increase
// 2.752293578% speed stars increase
// 4.799627961% pp increase
//
// cd6.5 on defenders
// 18.143683959% pp increase
// 4.62962963% aim stars increase
// 9.039548023% speed stars increase
if (radius < CS_BUFF_TRESHOLD) {
scalingFactor *=
1 + (CS_BUFF_TRESHOLD - radius) * 0.02;
}
this.normStart = new vec2_1.default(this.hitObject.position);
this.normStart.multiply(scalingFactor);
// ignoring slider lengths doesn't seem to affect star rating too
// much and speeds up the calculation exponentially
// actually, I believe this is how diff calc works now and slider
// lengths were dropped since osu!tp
this.normEnd = this.normStart.clone();
}
DiffCalcHitObject.prototype.calculateStrains = function (prev) {
this.calculateStrain(prev, DifficultyType.SPEED);
this.calculateStrain(prev, DifficultyType.AIM);
};
DiffCalcHitObject.prototype.calculateStrain = function (prev, diffType) {
var res = 0;
var timeElapsed = this.hitObject.startTime - prev.hitObject.startTime;
var decay = Math.pow(DECAY_BASE[diffType], timeElapsed / 1000.0);
var scaling = WEIGHT_SCALING[diffType];
if (this.hitObject.type == beatmap_1.HitObjectType.Circle ||
this.hitObject.type == beatmap_1.HitObjectType.Slider)
res = this.spacingWeight(this.normStart.distance(prev.normEnd), diffType) * scaling;
res /= Math.max(timeElapsed, 50);
this.strains[diffType] = prev.strains[diffType] * decay + res;
};
DiffCalcHitObject.prototype.spacingWeight = function (distance, diffType) {
if (diffType == DifficultyType.AIM)
return Math.pow(distance, 0.99);
if (distance > SINGLETAP_INTERVAL) {
return 2.5;
}
if (distance > STREAM_INTERVAL) {
return 1.6 + 0.9 *
(distance - STREAM_INTERVAL) /
(SINGLETAP_INTERVAL - STREAM_INTERVAL);
}
if (distance > DIAMETER_APPROX) {
return 1.2 + 0.4 * (distance - DIAMETER_APPROX) /
(STREAM_INTERVAL - DIAMETER_APPROX);
}
if (distance > DIAMETER_APPROX / 2.0) {
return 0.95 + 0.25 *
(distance - DIAMETER_APPROX / 2.0) /
(DIAMETER_APPROX / 2.0);
}
return 0.95;
};
return DiffCalcHitObject;
}());
;
var STAR_SCALING_FACTOR = 0.0675;
var EXTREME_SCALING_FACTOR = 0.5;
var PLAYFIELD_WIDTH = 512; // in osu!pixels
// strains are calculated by analyzing the map in chunks and then taking the
// peak strains in each chunk.
// this is the length of a strain interval in milliseconds.
var STRAIN_STEP = 400;
// max strains are weighted from highest to lowest, and this is how much the
// weight decays.
var DECAY_WEIGHT = 0.9;
var calculateDifficulty = function (objects, type) {
var highestStrains = [];
var intervalEnd = STRAIN_STEP;
var maxStrain = 0.0;
var prev;
for (var i = 0; i < objects.length; i++) {
var obj = objects[i];
// make previous peak strain decay until the current object
while (obj.hitObject.startTime > intervalEnd) {
highestStrains.push(maxStrain);
if (!prev) {
maxStrain = 0.0;
}
else {
var decay = Math.pow(DECAY_BASE[type], (intervalEnd - prev.hitObject.startTime) / 1000.0);
maxStrain = prev.strains[type] * decay;
}
intervalEnd += STRAIN_STEP;
}
// calculate max strain for this interval
maxStrain = Math.max(maxStrain, obj.strains[type]);
prev = obj;
}
highestStrains.push(maxStrain);
// sort strains from greatest to lowest
highestStrains.sort(function (a, b) { return b - a; });
return highestStrains.reduce(function (prev, curr, idx) { return prev + curr * Math.pow(DECAY_WEIGHT, idx); }, 0);
};
var DifficultyCalculator;
(function (DifficultyCalculator) {
function calculate(beatmap) {
if (beatmap.mode != 0)
throw new Error("This gamemode is not supported");
var circleRadius = (PLAYFIELD_WIDTH / 16) * (1 - 0.7 *
(beatmap.circleSize - 5) / 5);
var objects = [];
for (var i = 0; i < beatmap.hitObjects.length; i++) {
objects[i] = new DiffCalcHitObject(beatmap.hitObjects[i], circleRadius);
}
var prev = objects[0];
for (var i = 1; i < objects.length; i++) {
var o = objects[i];
o.calculateStrains(prev);
prev = o;
}
var aim = calculateDifficulty(objects, DifficultyType.AIM);
var speed = calculateDifficulty(objects, DifficultyType.SPEED);
aim = Math.sqrt(aim) * STAR_SCALING_FACTOR;
speed = Math.sqrt(speed) * STAR_SCALING_FACTOR;
var stars = aim + speed +
Math.abs(speed - aim) * EXTREME_SCALING_FACTOR;
return {
aim: aim, speed: speed, stars: stars
};
}
DifficultyCalculator.calculate = calculate;
})(DifficultyCalculator || (DifficultyCalculator = {}));
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = DifficultyCalculator;