@rian8337/osu-difficulty-calculator 4.0.0-beta.95 → 4.0.0-beta.97
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +133 -20
- package/package.json +3 -3
- package/typings/index.d.ts +38 -1
package/dist/index.js
CHANGED
|
@@ -60,6 +60,118 @@ class DifficultyCalculator {
|
|
|
60
60
|
flashlight: skills[3].peaks,
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Calculates the difficulty of a `Beatmap` with specific `Mod`s and returns a set of
|
|
65
|
+
* `TimedDifficultyAttributes` representing the difficulty at every relevant time value in the `Beatmap`.
|
|
66
|
+
*
|
|
67
|
+
* @param beatmap The `Beatmap` whose difficulty is to be calculated.
|
|
68
|
+
* @param mods The `Mod`s to apply to the `Beatmap`.
|
|
69
|
+
* @return The set of `TimedDifficultyAttributes`.
|
|
70
|
+
*/
|
|
71
|
+
calculateTimed(beatmap, mods) {
|
|
72
|
+
if (beatmap.hitObjects.objects.length === 0) {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
const playableBeatmap = this.createPlayableBeatmap(beatmap, mods);
|
|
76
|
+
const attributes = osuBase.Utils.initializeArray(beatmap.hitObjects.objects.length);
|
|
77
|
+
const skills = this.createSkills(playableBeatmap);
|
|
78
|
+
const progressiveBeatmap = new ProgressiveCalculationBeatmap(playableBeatmap);
|
|
79
|
+
const { objects } = playableBeatmap.hitObjects;
|
|
80
|
+
const difficultyObjects = this.createDifficultyHitObjects(playableBeatmap);
|
|
81
|
+
let currentIndex = 0;
|
|
82
|
+
for (let i = 0; i < objects.length; ++i) {
|
|
83
|
+
const obj = objects[i];
|
|
84
|
+
progressiveBeatmap.hitObjects.add(obj);
|
|
85
|
+
while (currentIndex < difficultyObjects.length && difficultyObjects[currentIndex].object.endTime <= obj.endTime) {
|
|
86
|
+
for (const skill of skills) {
|
|
87
|
+
skill.process(difficultyObjects[currentIndex]);
|
|
88
|
+
}
|
|
89
|
+
++currentIndex;
|
|
90
|
+
}
|
|
91
|
+
attributes[i] = {
|
|
92
|
+
time: obj.endTime,
|
|
93
|
+
attributes: this.createDifficultyAttributes(beatmap, playableBeatmap, skills, difficultyObjects.slice(0, currentIndex)),
|
|
94
|
+
sliderCount: progressiveBeatmap.hitObjects.sliders,
|
|
95
|
+
sliderTickCount: progressiveBeatmap.hitObjects.sliderTicks,
|
|
96
|
+
sliderRepeatCount: progressiveBeatmap.hitObjects.sliderRepeatPoints
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return attributes;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* A {@link PlayableBeatmap} for timed difficulty calculation.
|
|
104
|
+
*/
|
|
105
|
+
class ProgressiveCalculationBeatmap extends osuBase.PlayableBeatmap {
|
|
106
|
+
get maxCombo() {
|
|
107
|
+
return this.hitObjects.maxCombo;
|
|
108
|
+
}
|
|
109
|
+
constructor(baseBeatmap) {
|
|
110
|
+
super(baseBeatmap, baseBeatmap.mods);
|
|
111
|
+
this.hitObjects = new ProgressiveCalculationHitObjects();
|
|
112
|
+
this.baseHitWindow = baseBeatmap.hitWindow;
|
|
113
|
+
}
|
|
114
|
+
createHitWindow() {
|
|
115
|
+
return this.baseHitWindow;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
class ProgressiveCalculationHitObjects extends osuBase.BeatmapHitObjects {
|
|
119
|
+
constructor() {
|
|
120
|
+
super(...arguments);
|
|
121
|
+
this.maxCombo = 0;
|
|
122
|
+
// We store these locally since the super class's getters iterate through all objects, which is inefficient for progressive calculation.
|
|
123
|
+
this.sliderTickCount = 0;
|
|
124
|
+
this.sliderRepeatCount = 0;
|
|
125
|
+
}
|
|
126
|
+
get sliderTicks() {
|
|
127
|
+
return this.sliderTickCount;
|
|
128
|
+
}
|
|
129
|
+
get sliderRepeatPoints() {
|
|
130
|
+
return this.sliderRepeatCount;
|
|
131
|
+
}
|
|
132
|
+
add(...objects) {
|
|
133
|
+
super.add(...objects);
|
|
134
|
+
for (const obj of objects) {
|
|
135
|
+
if (obj instanceof osuBase.Slider) {
|
|
136
|
+
this.maxCombo += obj.nestedHitObjects.length;
|
|
137
|
+
// Similarly, we loop through the nested hit objects since `Slider.ticks` also loops
|
|
138
|
+
// through the nested hit objects, which is inefficient for progressive calculation.
|
|
139
|
+
for (const nestedObj of obj.nestedHitObjects) {
|
|
140
|
+
if (nestedObj instanceof osuBase.SliderTick) {
|
|
141
|
+
++this.sliderTickCount;
|
|
142
|
+
}
|
|
143
|
+
else if (nestedObj instanceof osuBase.SliderRepeat) {
|
|
144
|
+
++this.sliderRepeatCount;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
++this.maxCombo;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
removeAt(index) {
|
|
154
|
+
const obj = super.removeAt(index);
|
|
155
|
+
if (obj !== null) {
|
|
156
|
+
if (obj instanceof osuBase.Slider) {
|
|
157
|
+
this.maxCombo -= obj.nestedHitObjects.length;
|
|
158
|
+
// Similarly, we loop through the nested hit objects since `Slider.ticks` also loops
|
|
159
|
+
// through the nested hit objects, which is inefficient for progressive calculation.
|
|
160
|
+
for (const nestedObj of obj.nestedHitObjects) {
|
|
161
|
+
if (nestedObj instanceof osuBase.SliderTick) {
|
|
162
|
+
--this.sliderTickCount;
|
|
163
|
+
}
|
|
164
|
+
else if (nestedObj instanceof osuBase.SliderRepeat) {
|
|
165
|
+
--this.sliderRepeatCount;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
--this.maxCombo;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return obj;
|
|
174
|
+
}
|
|
63
175
|
}
|
|
64
176
|
|
|
65
177
|
/**
|
|
@@ -151,11 +263,11 @@ class PerformanceCalculator {
|
|
|
151
263
|
* @param options Options for performance calculation.
|
|
152
264
|
*/
|
|
153
265
|
handleOptions(options) {
|
|
154
|
-
var _a, _b;
|
|
266
|
+
var _a, _b, _c;
|
|
155
267
|
if ((options === null || options === void 0 ? void 0 : options.accPercent) instanceof osuBase.Accuracy) {
|
|
156
268
|
// Copy into new instance to not modify the original
|
|
157
269
|
this.computedAccuracy = new osuBase.Accuracy(options.accPercent);
|
|
158
|
-
if (this.computedAccuracy.
|
|
270
|
+
if (!this.computedAccuracy.isN300Resolved) {
|
|
159
271
|
this.computedAccuracy.n300 = Math.max(0, this.totalHits -
|
|
160
272
|
this.computedAccuracy.n100 -
|
|
161
273
|
this.computedAccuracy.n50 -
|
|
@@ -165,16 +277,15 @@ class PerformanceCalculator {
|
|
|
165
277
|
this.computedAccuracy.nmiss = Math.max(0, this.totalHits - this.totalSuccessfulHits);
|
|
166
278
|
}
|
|
167
279
|
}
|
|
280
|
+
else if ((options === null || options === void 0 ? void 0 : options.accPercent) !== undefined) {
|
|
281
|
+
this.computedAccuracy = osuBase.Accuracy.fromPercent(options.accPercent, this.totalHits, (_a = options.miss) !== null && _a !== void 0 ? _a : 0);
|
|
282
|
+
}
|
|
168
283
|
else {
|
|
169
|
-
this.computedAccuracy =
|
|
170
|
-
percent: options === null || options === void 0 ? void 0 : options.accPercent,
|
|
171
|
-
nobjects: this.totalHits,
|
|
172
|
-
nmiss: (_a = options === null || options === void 0 ? void 0 : options.miss) !== null && _a !== void 0 ? _a : 0,
|
|
173
|
-
});
|
|
284
|
+
this.computedAccuracy = osuBase.Accuracy.fromHitCounts({ nmiss: (_b = options === null || options === void 0 ? void 0 : options.miss) !== null && _b !== void 0 ? _b : 0 }, this.totalHits);
|
|
174
285
|
}
|
|
175
286
|
const maxCombo = this.difficultyAttributes.maxCombo;
|
|
176
287
|
const miss = this.computedAccuracy.nmiss;
|
|
177
|
-
this.combo = (
|
|
288
|
+
this.combo = (_c = options === null || options === void 0 ? void 0 : options.combo) !== null && _c !== void 0 ? _c : maxCombo - miss;
|
|
178
289
|
if ((options === null || options === void 0 ? void 0 : options.sliderEndsDropped) !== undefined &&
|
|
179
290
|
options.sliderTicksMissed !== undefined) {
|
|
180
291
|
this._usingClassicSliderAccuracy = false;
|
|
@@ -3389,15 +3500,16 @@ class DroidScoreUtils {
|
|
|
3389
3500
|
/**
|
|
3390
3501
|
* Calculates the maximum possible spinner bonus for a given beatmap.
|
|
3391
3502
|
*
|
|
3392
|
-
* @param beatmap The
|
|
3503
|
+
* @param beatmap The `Beatmap` to calculate the maximum spinner bonus for.
|
|
3504
|
+
* @param playableBeatmap The `DroidPlayableBeatmap` to calculate the maximum spinner bonus for.
|
|
3393
3505
|
* @returns The maximum spinner bonus.
|
|
3394
3506
|
*/
|
|
3395
|
-
static calculateMaximumSpinnerBonus(beatmap) {
|
|
3396
|
-
const { hitObjects } =
|
|
3507
|
+
static calculateMaximumSpinnerBonus(beatmap, playableBeatmap) {
|
|
3508
|
+
const { hitObjects } = playableBeatmap;
|
|
3397
3509
|
if (hitObjects.spinners === 0) {
|
|
3398
3510
|
return 0;
|
|
3399
3511
|
}
|
|
3400
|
-
const scoreMultiplier = osuBase.
|
|
3512
|
+
const scoreMultiplier = new osuBase.DroidScoreMultiplierCalculator(beatmap.difficulty).calculateFor(playableBeatmap.mods.values());
|
|
3401
3513
|
let bonus = 0;
|
|
3402
3514
|
// In osu!droid, there is no time-based limit to spinner RPM, since the limit is
|
|
3403
3515
|
// π/2 rad/*frame* and not rad/second.
|
|
@@ -3406,7 +3518,7 @@ class DroidScoreUtils {
|
|
|
3406
3518
|
// the actual maximum spinner bonus.
|
|
3407
3519
|
const maximumRotationsPerSecond = (Math.PI / 2) * 120;
|
|
3408
3520
|
// Taken from https://github.com/osudroid/osu-droid/blob/45ae4d66ce275382c5de037245ceca8704b3ae75/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java#L1640.
|
|
3409
|
-
const minimumRotationsPerSecond = 2 + (2 *
|
|
3521
|
+
const minimumRotationsPerSecond = 2 + (2 * playableBeatmap.difficulty.od) / 10;
|
|
3410
3522
|
for (const obj of hitObjects.objects) {
|
|
3411
3523
|
if (!(obj instanceof osuBase.Spinner)) {
|
|
3412
3524
|
continue;
|
|
@@ -3449,8 +3561,9 @@ class DroidDifficultyCalculator extends DifficultyCalculator {
|
|
|
3449
3561
|
attributes.overallDifficulty = playableBeatmap.difficulty.od;
|
|
3450
3562
|
// Cap at 32-bit signed integer since that's the maximum score that can be submitted
|
|
3451
3563
|
// to the game's leaderboards.
|
|
3452
|
-
attributes.maximumScore =
|
|
3453
|
-
|
|
3564
|
+
attributes.maximumScore =
|
|
3565
|
+
beatmap.maxDroidScore(playableBeatmap.mods) +
|
|
3566
|
+
DroidScoreUtils.calculateMaximumSpinnerBonus(beatmap, playableBeatmap);
|
|
3454
3567
|
this.populateAimAttributes(attributes, skills, objects);
|
|
3455
3568
|
this.populateTapAttributes(attributes, skills, objects);
|
|
3456
3569
|
this.populateRhythmAttributes(attributes, skills);
|
|
@@ -5025,7 +5138,7 @@ class OsuPerformanceCalculator extends PerformanceCalculator {
|
|
|
5025
5138
|
1.3 +
|
|
5026
5139
|
this.totalHits *
|
|
5027
5140
|
(0.0016 / (1 + 2 * this._effectiveMissCount)) *
|
|
5028
|
-
Math.pow(this.computedAccuracy.value
|
|
5141
|
+
Math.pow(this.computedAccuracy.value, 16) *
|
|
5029
5142
|
(1 -
|
|
5030
5143
|
0.003 *
|
|
5031
5144
|
Math.pow(this.difficultyAttributes.drainRate, 2));
|
|
@@ -5036,7 +5149,7 @@ class OsuPerformanceCalculator extends PerformanceCalculator {
|
|
|
5036
5149
|
this.calculateTraceableBonus(this.difficultyAttributes.sliderFactor);
|
|
5037
5150
|
}
|
|
5038
5151
|
// Scale the aim value with accuracy.
|
|
5039
|
-
aimValue *= this.computedAccuracy.value
|
|
5152
|
+
aimValue *= this.computedAccuracy.value;
|
|
5040
5153
|
return aimValue;
|
|
5041
5154
|
}
|
|
5042
5155
|
/**
|
|
@@ -5085,7 +5198,7 @@ class OsuPerformanceCalculator extends PerformanceCalculator {
|
|
|
5085
5198
|
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
|
|
5086
5199
|
let accuracyValue = Math.pow(1.52163, this.overallDifficulty) *
|
|
5087
5200
|
// It is possible to reach a negative accuracy with this formula. Cap it at zero - zero points.
|
|
5088
|
-
Math.pow(realAccuracy.n300 < 0 ? 0 : realAccuracy.value
|
|
5201
|
+
Math.pow(realAccuracy.n300 < 0 ? 0 : realAccuracy.value, 24) *
|
|
5089
5202
|
2.83;
|
|
5090
5203
|
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer.
|
|
5091
5204
|
accuracyValue *= Math.pow(ncircles / 1000, ncircles < 1000 ? 0.3 : 0.1);
|
|
@@ -5119,7 +5232,7 @@ class OsuPerformanceCalculator extends PerformanceCalculator {
|
|
|
5119
5232
|
Math.pow(this.effectiveMissCount / this.totalHits, 0.775), Math.pow(this.effectiveMissCount, 0.875));
|
|
5120
5233
|
}
|
|
5121
5234
|
// Scale the flashlight value with accuracy slightly.
|
|
5122
|
-
flashlightValue *= 0.5 + this.computedAccuracy.value
|
|
5235
|
+
flashlightValue *= 0.5 + this.computedAccuracy.value / 2;
|
|
5123
5236
|
return flashlightValue;
|
|
5124
5237
|
}
|
|
5125
5238
|
/**
|
|
@@ -5132,7 +5245,7 @@ class OsuPerformanceCalculator extends PerformanceCalculator {
|
|
|
5132
5245
|
readingValue *= this.calculateMissPenalty(this.effectiveMissCount + aimEstimatedSliderBreaks, this.difficultyAttributes.readingDifficultNoteCount);
|
|
5133
5246
|
}
|
|
5134
5247
|
// Scale the reading value with accuracy _harshly_.
|
|
5135
|
-
readingValue *= Math.pow(this.computedAccuracy.value
|
|
5248
|
+
readingValue *= Math.pow(this.computedAccuracy.value, 3);
|
|
5136
5249
|
return readingValue;
|
|
5137
5250
|
}
|
|
5138
5251
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rian8337/osu-difficulty-calculator",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.97",
|
|
4
4
|
"description": "A module for calculating osu!standard beatmap difficulty and performance value with respect to the current difficulty and performance algorithm.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"osu",
|
|
@@ -33,10 +33,10 @@
|
|
|
33
33
|
"url": "https://github.com/Rian8337/osu-droid-module/issues"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@rian8337/osu-base": "4.0.0-beta.
|
|
36
|
+
"@rian8337/osu-base": "4.0.0-beta.97"
|
|
37
37
|
},
|
|
38
38
|
"publishConfig": {
|
|
39
39
|
"access": "public"
|
|
40
40
|
},
|
|
41
|
-
"gitHead": "
|
|
41
|
+
"gitHead": "0d77ee41c1b0eca8e46c118e942417483c4a9452"
|
|
42
42
|
}
|
package/typings/index.d.ts
CHANGED
|
@@ -445,6 +445,34 @@ declare abstract class Skill {
|
|
|
445
445
|
protected abstract processInternal(current: DifficultyHitObject): number;
|
|
446
446
|
}
|
|
447
447
|
|
|
448
|
+
/**
|
|
449
|
+
* Wraps a `DifficultyAttributes` object and adds a time value for which the attribute is valid.
|
|
450
|
+
*
|
|
451
|
+
* Output by `DifficultyCalculator.calculateTimed` methods.
|
|
452
|
+
*/
|
|
453
|
+
interface TimedDifficultyAttributes<TAttributes extends DifficultyAttributes> {
|
|
454
|
+
/**
|
|
455
|
+
* The non-clock-adjusted time value at which the attributes take effect.
|
|
456
|
+
*/
|
|
457
|
+
readonly time: number;
|
|
458
|
+
/**
|
|
459
|
+
* The attributes.
|
|
460
|
+
*/
|
|
461
|
+
readonly attributes: TAttributes;
|
|
462
|
+
/**
|
|
463
|
+
* The number of sliders in the beatmap up to this point.
|
|
464
|
+
*/
|
|
465
|
+
readonly sliderCount: number;
|
|
466
|
+
/**
|
|
467
|
+
* The number of slider ticks in the beatmap up to this point.
|
|
468
|
+
*/
|
|
469
|
+
readonly sliderTickCount: number;
|
|
470
|
+
/**
|
|
471
|
+
* The number of slider repeats in the beatmap up to this point.
|
|
472
|
+
*/
|
|
473
|
+
readonly sliderRepeatCount: number;
|
|
474
|
+
}
|
|
475
|
+
|
|
448
476
|
/**
|
|
449
477
|
* The base of a difficulty calculator.
|
|
450
478
|
*/
|
|
@@ -483,6 +511,15 @@ declare abstract class DifficultyCalculator<TBeatmap extends PlayableBeatmap, TH
|
|
|
483
511
|
* @returns The strain peaks of the `Beatmap`.
|
|
484
512
|
*/
|
|
485
513
|
calculateStrainPeaks(beatmap: Beatmap, mods?: ModMap): StrainPeaks;
|
|
514
|
+
/**
|
|
515
|
+
* Calculates the difficulty of a `Beatmap` with specific `Mod`s and returns a set of
|
|
516
|
+
* `TimedDifficultyAttributes` representing the difficulty at every relevant time value in the `Beatmap`.
|
|
517
|
+
*
|
|
518
|
+
* @param beatmap The `Beatmap` whose difficulty is to be calculated.
|
|
519
|
+
* @param mods The `Mod`s to apply to the `Beatmap`.
|
|
520
|
+
* @return The set of `TimedDifficultyAttributes`.
|
|
521
|
+
*/
|
|
522
|
+
calculateTimed(beatmap: Beatmap, mods?: ModMap): TimedDifficultyAttributes<TAttributes>[];
|
|
486
523
|
/**
|
|
487
524
|
* Creates the `Skill`s to calculate the difficulty of a `PlayableBeatmap`.
|
|
488
525
|
*
|
|
@@ -2053,4 +2090,4 @@ declare class OsuSpeed extends HarmonicSkill {
|
|
|
2053
2090
|
}
|
|
2054
2091
|
|
|
2055
2092
|
export { DifficultyAttributes, DifficultyCalculator, DifficultyHitObject, DroidAgilityEvaluator, DroidAim, DroidDifficultyAttributes, DroidDifficultyCalculator, DroidDifficultyHitObject, DroidFlashlight, DroidFlashlightEvaluator, DroidFlowAimEvaluator, DroidPerformanceCalculator, DroidReading, DroidReadingEvaluator, DroidRhythm, DroidRhythmEvaluator, DroidSnapAimEvaluator, DroidTap, DroidTapEvaluator, ExtendedDroidDifficultyAttributes, OsuAgilityEvaluator, OsuAim, OsuDifficultyAttributes, OsuDifficultyCalculator, OsuDifficultyHitObject, OsuFlashlight, OsuFlashlightEvaluator, OsuFlowAimEvaluator, OsuPerformanceCalculator, OsuReading, OsuReadingEvaluator, OsuRhythmEvaluator, OsuSnapAimEvaluator, OsuSpeed, OsuSpeedEvaluator, PerformanceCalculator };
|
|
2056
|
-
export type { CacheableDifficultyAttributes, DifficultSlider, HighStrainSection, IDifficultyAttributes, IDroidDifficultyAttributes, IExtendedDroidDifficultyAttributes, IOsuDifficultyAttributes, PerformanceCalculationOptions, StrainPeaks, TimedStrainPeak };
|
|
2093
|
+
export type { CacheableDifficultyAttributes, DifficultSlider, HighStrainSection, IDifficultyAttributes, IDroidDifficultyAttributes, IExtendedDroidDifficultyAttributes, IOsuDifficultyAttributes, PerformanceCalculationOptions, StrainPeaks, TimedDifficultyAttributes, TimedStrainPeak };
|