@rian8337/osu-difficulty-calculator 1.1.0 → 1.2.1
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.
|
@@ -153,7 +153,7 @@ class DroidPerformanceCalculator extends PerformanceCalculator_1.PerformanceCalc
|
|
|
153
153
|
(0.95 + (od > 0 ? odScaling : -odScaling)) *
|
|
154
154
|
Math.pow((this.computedAccuracy.value(objectCount) +
|
|
155
155
|
relevantAccuracy.value(this.stars.attributes.speedNoteCount)) /
|
|
156
|
-
2, (
|
|
156
|
+
2, (14 - Math.max(od, 2.5)) / 2);
|
|
157
157
|
// Scale the speed value with # of 50s to punish doubletapping.
|
|
158
158
|
this.tap *= Math.pow(0.98, Math.max(0, this.computedAccuracy.n50 - objectCount / 500));
|
|
159
159
|
}
|
package/dist/DroidStarRating.js
CHANGED
|
@@ -59,31 +59,15 @@ class DroidStarRating extends StarRating_1.StarRating {
|
|
|
59
59
|
const aimSkill = new DroidAim_1.DroidAim(this.mods, true);
|
|
60
60
|
const aimSkillWithoutSliders = new DroidAim_1.DroidAim(this.mods, false);
|
|
61
61
|
this.calculateSkills(aimSkill, aimSkillWithoutSliders);
|
|
62
|
-
this.
|
|
63
|
-
this.strainPeaks.aimWithoutSliders = aimSkillWithoutSliders.strainPeaks;
|
|
64
|
-
this.aim = this.starValue(aimSkill.difficultyValue());
|
|
65
|
-
if (this.aim) {
|
|
66
|
-
this.attributes.sliderFactor =
|
|
67
|
-
this.starValue(aimSkillWithoutSliders.difficultyValue()) /
|
|
68
|
-
this.aim;
|
|
69
|
-
}
|
|
62
|
+
this.postCalculateAim(aimSkill, aimSkillWithoutSliders);
|
|
70
63
|
}
|
|
71
64
|
/**
|
|
72
65
|
* Calculates the speed star rating of the beatmap and stores it in this instance.
|
|
73
66
|
*/
|
|
74
67
|
calculateTap() {
|
|
75
|
-
if (this.mods.some((m) => m instanceof osu_base_1.ModRelax)) {
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
68
|
const tapSkill = new DroidTap_1.DroidTap(this.mods, this.stats.od);
|
|
79
69
|
this.calculateSkills(tapSkill);
|
|
80
|
-
this.
|
|
81
|
-
this.tap = this.starValue(tapSkill.difficultyValue());
|
|
82
|
-
const objectStrains = this.objects.map((v) => v.tapStrain);
|
|
83
|
-
const maxStrain = Math.max(...objectStrains);
|
|
84
|
-
if (maxStrain) {
|
|
85
|
-
this.attributes.speedNoteCount = objectStrains.reduce((total, next) => total + 1 / (1 + Math.exp(-((next / maxStrain) * 12 - 6))), 0);
|
|
86
|
-
}
|
|
70
|
+
this.postCalculateTap(tapSkill);
|
|
87
71
|
}
|
|
88
72
|
/**
|
|
89
73
|
* Calculates the rhythm star rating of the beatmap and stores it in this instance.
|
|
@@ -91,7 +75,7 @@ class DroidStarRating extends StarRating_1.StarRating {
|
|
|
91
75
|
calculateRhythm() {
|
|
92
76
|
const rhythmSkill = new DroidRhythm_1.DroidRhythm(this.mods, this.stats.od);
|
|
93
77
|
this.calculateSkills(rhythmSkill);
|
|
94
|
-
this.
|
|
78
|
+
this.postCalculateRhythm(rhythmSkill);
|
|
95
79
|
}
|
|
96
80
|
/**
|
|
97
81
|
* Calculates the flashlight star rating of the beatmap and stores it in this instance.
|
|
@@ -99,8 +83,7 @@ class DroidStarRating extends StarRating_1.StarRating {
|
|
|
99
83
|
calculateFlashlight() {
|
|
100
84
|
const flashlightSkill = new DroidFlashlight_1.DroidFlashlight(this.mods);
|
|
101
85
|
this.calculateSkills(flashlightSkill);
|
|
102
|
-
this.
|
|
103
|
-
this.flashlight = this.starValue(flashlightSkill.difficultyValue());
|
|
86
|
+
this.postCalculateFlashlight(flashlightSkill);
|
|
104
87
|
}
|
|
105
88
|
calculateTotal() {
|
|
106
89
|
const aimPerformanceValue = this.basePerformanceValue(this.aim);
|
|
@@ -122,48 +105,21 @@ class DroidStarRating extends StarRating_1.StarRating {
|
|
|
122
105
|
calculateAll() {
|
|
123
106
|
const skills = this.createSkills();
|
|
124
107
|
const isRelax = this.mods.some((m) => m instanceof osu_base_1.ModRelax);
|
|
125
|
-
if (isRelax) {
|
|
126
|
-
// Remove tap and rhythm skill to prevent overhead. These values will be 0 anyways.
|
|
127
|
-
skills.splice(2, 2);
|
|
128
|
-
}
|
|
129
108
|
this.calculateSkills(...skills);
|
|
130
109
|
const aimSkill = skills[0];
|
|
131
110
|
const aimSkillWithoutSliders = skills[1];
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
111
|
+
const rhythmSkill = skills[2];
|
|
112
|
+
const tapSkill = skills[3];
|
|
113
|
+
const flashlightSkill = skills[4];
|
|
114
|
+
this.postCalculateAim(aimSkill, aimSkillWithoutSliders);
|
|
135
115
|
if (!isRelax) {
|
|
136
|
-
|
|
137
|
-
tapSkill = skills[3];
|
|
138
|
-
flashlightSkill = skills[4];
|
|
139
|
-
}
|
|
140
|
-
else {
|
|
141
|
-
flashlightSkill = skills[2];
|
|
116
|
+
this.postCalculateTap(tapSkill);
|
|
142
117
|
}
|
|
143
|
-
this.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (this.aim) {
|
|
147
|
-
this.attributes.sliderFactor =
|
|
148
|
-
this.starValue(aimSkillWithoutSliders.difficultyValue()) /
|
|
149
|
-
this.aim;
|
|
150
|
-
}
|
|
151
|
-
if (tapSkill) {
|
|
152
|
-
this.strainPeaks.speed = tapSkill.strainPeaks;
|
|
153
|
-
this.tap = this.starValue(tapSkill.difficultyValue());
|
|
154
|
-
const objectStrains = this.objects.map((v) => v.tapStrain);
|
|
155
|
-
const maxStrain = Math.max(...objectStrains);
|
|
156
|
-
if (maxStrain) {
|
|
157
|
-
this.attributes.speedNoteCount = objectStrains.reduce((total, next) => total +
|
|
158
|
-
1 / (1 + Math.exp(-((next / maxStrain) * 12 - 6))), 0);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
if (rhythmSkill) {
|
|
162
|
-
this.calculateSkills(rhythmSkill);
|
|
163
|
-
this.rhythm = this.starValue(rhythmSkill.difficultyValue());
|
|
118
|
+
this.calculateSpeedNoteCount();
|
|
119
|
+
if (!isRelax) {
|
|
120
|
+
this.postCalculateRhythm(rhythmSkill);
|
|
164
121
|
}
|
|
165
|
-
this.
|
|
166
|
-
this.flashlight = this.starValue(flashlightSkill.difficultyValue());
|
|
122
|
+
this.postCalculateFlashlight(flashlightSkill);
|
|
167
123
|
this.calculateTotal();
|
|
168
124
|
}
|
|
169
125
|
/**
|
|
@@ -194,5 +150,58 @@ class DroidStarRating extends StarRating_1.StarRating {
|
|
|
194
150
|
new DroidFlashlight_1.DroidFlashlight(this.mods),
|
|
195
151
|
];
|
|
196
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Called after aim skill calculation.
|
|
155
|
+
*
|
|
156
|
+
* @param aimSkill The aim skill that considers sliders.
|
|
157
|
+
* @param aimSkillWithoutSliders The aim skill that doesn't consider sliders.
|
|
158
|
+
*/
|
|
159
|
+
postCalculateAim(aimSkill, aimSkillWithoutSliders) {
|
|
160
|
+
this.strainPeaks.aimWithSliders = aimSkill.strainPeaks;
|
|
161
|
+
this.strainPeaks.aimWithoutSliders = aimSkillWithoutSliders.strainPeaks;
|
|
162
|
+
this.aim = this.starValue(aimSkill.difficultyValue());
|
|
163
|
+
if (this.aim) {
|
|
164
|
+
this.attributes.sliderFactor =
|
|
165
|
+
this.starValue(aimSkillWithoutSliders.difficultyValue()) /
|
|
166
|
+
this.aim;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Called after tap skill calculation.
|
|
171
|
+
*
|
|
172
|
+
* @param tapSkill The tap skill.
|
|
173
|
+
*/
|
|
174
|
+
postCalculateTap(tapSkill) {
|
|
175
|
+
this.strainPeaks.speed = tapSkill.strainPeaks;
|
|
176
|
+
this.tap = this.starValue(tapSkill.difficultyValue());
|
|
177
|
+
this.calculateSpeedNoteCount();
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Calculates the speed note count attribute.
|
|
181
|
+
*/
|
|
182
|
+
calculateSpeedNoteCount() {
|
|
183
|
+
const objectStrains = this.objects.map((v) => v.tapStrain);
|
|
184
|
+
const maxStrain = Math.max(...objectStrains);
|
|
185
|
+
if (maxStrain) {
|
|
186
|
+
this.attributes.speedNoteCount = objectStrains.reduce((total, next) => total + 1 / (1 + Math.exp(-((next / maxStrain) * 12 - 6))), 0);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Called after rhythm skill calculation.
|
|
191
|
+
*
|
|
192
|
+
* @param rhythmSkill The rhythm skill.
|
|
193
|
+
*/
|
|
194
|
+
postCalculateRhythm(rhythmSkill) {
|
|
195
|
+
this.rhythm = this.starValue(rhythmSkill.difficultyValue());
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Called after flashlight skill calculation.
|
|
199
|
+
*
|
|
200
|
+
* @param flashlightSkill The flashlight skill.
|
|
201
|
+
*/
|
|
202
|
+
postCalculateFlashlight(flashlightSkill) {
|
|
203
|
+
this.strainPeaks.flashlight = flashlightSkill.strainPeaks;
|
|
204
|
+
this.flashlight = this.starValue(flashlightSkill.difficultyValue());
|
|
205
|
+
}
|
|
197
206
|
}
|
|
198
207
|
exports.DroidStarRating = DroidStarRating;
|
|
@@ -62,8 +62,9 @@ class PerformanceCalculator {
|
|
|
62
62
|
nmiss: params.miss || 0,
|
|
63
63
|
});
|
|
64
64
|
}
|
|
65
|
+
this.effectiveMissCount = this.calculateEffectiveMissCount(combo, maxCombo);
|
|
65
66
|
if (this.stars.mods.some((m) => m instanceof osu_base_1.ModNoFail)) {
|
|
66
|
-
this.finalMultiplier *= Math.max(0.9, 1 - 0.02 * this.
|
|
67
|
+
this.finalMultiplier *= Math.max(0.9, 1 - 0.02 * this.effectiveMissCount);
|
|
67
68
|
}
|
|
68
69
|
if (this.stars.mods.some((m) => m instanceof osu_base_1.ModSpunOut)) {
|
|
69
70
|
this.finalMultiplier *=
|
|
@@ -71,11 +72,13 @@ class PerformanceCalculator {
|
|
|
71
72
|
Math.pow(this.stars.map.spinners / this.stars.objects.length, 0.85);
|
|
72
73
|
}
|
|
73
74
|
if (this.stars.mods.some((m) => m instanceof osu_base_1.ModRelax)) {
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
// As we're adding 100s and 50s to an approximated number of combo breaks, the result can be higher
|
|
76
|
+
// than total hits in specific scenarios (which breaks some calculations), so we need to clamp it.
|
|
77
|
+
this.effectiveMissCount = Math.min(this.effectiveMissCount +
|
|
78
|
+
this.computedAccuracy.n100 +
|
|
79
|
+
this.computedAccuracy.n50, this.stars.objects.length);
|
|
76
80
|
this.finalMultiplier *= 0.6;
|
|
77
81
|
}
|
|
78
|
-
this.effectiveMissCount = this.calculateEffectiveMissCount(combo, maxCombo);
|
|
79
82
|
this.mapStatistics = new osu_base_1.MapStats({
|
|
80
83
|
ar: baseAR,
|
|
81
84
|
od: baseOD,
|
|
@@ -119,7 +122,7 @@ class PerformanceCalculator {
|
|
|
119
122
|
comboBasedMissCount = Math.min(fullComboThreshold / Math.max(1, combo), this.stars.objects.length);
|
|
120
123
|
}
|
|
121
124
|
}
|
|
122
|
-
return Math.max(this.computedAccuracy.nmiss,
|
|
125
|
+
return Math.max(this.computedAccuracy.nmiss, comboBasedMissCount);
|
|
123
126
|
}
|
|
124
127
|
}
|
|
125
128
|
exports.PerformanceCalculator = PerformanceCalculator;
|
|
@@ -10,7 +10,7 @@ class DroidFlashlight extends DroidSkill_1.DroidSkill {
|
|
|
10
10
|
constructor() {
|
|
11
11
|
super(...arguments);
|
|
12
12
|
this.historyLength = 10;
|
|
13
|
-
this.skillMultiplier = 0.
|
|
13
|
+
this.skillMultiplier = 0.07;
|
|
14
14
|
this.strainDecayBase = 0.15;
|
|
15
15
|
this.reducedSectionCount = 10;
|
|
16
16
|
this.reducedSectionBaseline = 0.75;
|
|
@@ -24,22 +24,23 @@ class DroidFlashlight extends DroidSkill_1.DroidSkill {
|
|
|
24
24
|
let smallDistNerf = 1;
|
|
25
25
|
let cumulativeStrainTime = 0;
|
|
26
26
|
let result = 0;
|
|
27
|
+
let last = current;
|
|
27
28
|
for (let i = 0; i < this.previous.length; ++i) {
|
|
28
|
-
const
|
|
29
|
-
if (
|
|
30
|
-
|
|
29
|
+
const currentObject = this.previous[i];
|
|
30
|
+
if (!(currentObject.object instanceof osu_base_1.Spinner)) {
|
|
31
|
+
const jumpDistance = current.object.stackedPosition.subtract(currentObject.object.endPosition).length;
|
|
32
|
+
cumulativeStrainTime += last.strainTime;
|
|
33
|
+
// We want to nerf objects that can be easily seen within the Flashlight circle radius.
|
|
34
|
+
if (i === 0) {
|
|
35
|
+
smallDistNerf = Math.min(1, jumpDistance / 75);
|
|
36
|
+
}
|
|
37
|
+
// We also want to nerf stacks so that only the first object of the stack is accounted for.
|
|
38
|
+
const stackNerf = Math.min(1, currentObject.lazyJumpDistance / scalingFactor / 25);
|
|
39
|
+
result +=
|
|
40
|
+
(stackNerf * scalingFactor * jumpDistance) /
|
|
41
|
+
cumulativeStrainTime;
|
|
31
42
|
}
|
|
32
|
-
|
|
33
|
-
cumulativeStrainTime += previous.strainTime;
|
|
34
|
-
// We want to nerf objects that can be easily seen within the Flashlight circle radius.
|
|
35
|
-
if (i === 0) {
|
|
36
|
-
smallDistNerf = Math.min(1, jumpDistance / 75);
|
|
37
|
-
}
|
|
38
|
-
// We also want to nerf stacks so that only the first object of the stack is accounted for.
|
|
39
|
-
const stackNerf = Math.min(1, previous.lazyJumpDistance / scalingFactor / 25);
|
|
40
|
-
result +=
|
|
41
|
-
(Math.pow(0.8, i) * stackNerf * scalingFactor * jumpDistance) /
|
|
42
|
-
cumulativeStrainTime;
|
|
43
|
+
last = currentObject;
|
|
43
44
|
}
|
|
44
45
|
return Math.pow(smallDistNerf * result, 2);
|
|
45
46
|
}
|
package/dist/skills/DroidTap.js
CHANGED
|
@@ -37,10 +37,8 @@ class DroidTap extends DroidSkill_1.DroidSkill {
|
|
|
37
37
|
strainTime = osu_base_1.Interpolation.lerp(this.previous[0].strainTime, strainTime, strainTime / greatWindowFull);
|
|
38
38
|
}
|
|
39
39
|
// Cap deltatime to the OD 300 hitwindow.
|
|
40
|
-
//
|
|
41
|
-
strainTime /= osu_base_1.MathUtils.clamp(strainTime /
|
|
42
|
-
new osu_base_1.OsuHitWindow(this.hitWindow.overallDifficulty - 21.5).hitWindowFor300() /
|
|
43
|
-
0.35, 0.9, 1);
|
|
40
|
+
// 0.58 is derived from making sure 260 BPM 1/4 OD5 streams aren't nerfed harshly, whilst 0.92 limits the effect of the cap.
|
|
41
|
+
strainTime /= osu_base_1.MathUtils.clamp(strainTime / greatWindowFull / 0.58, 0.92, 1);
|
|
44
42
|
let speedBonus = 1;
|
|
45
43
|
if (strainTime < this.minSpeedBonus) {
|
|
46
44
|
speedBonus +=
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rian8337/osu-difficulty-calculator",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
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",
|
|
@@ -30,10 +30,10 @@
|
|
|
30
30
|
"url": "https://github.com/Rian8337/osu-droid-module/issues"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@rian8337/osu-base": "^1.
|
|
33
|
+
"@rian8337/osu-base": "^1.2.1"
|
|
34
34
|
},
|
|
35
35
|
"publishConfig": {
|
|
36
36
|
"access": "public"
|
|
37
37
|
},
|
|
38
|
-
"gitHead": "
|
|
38
|
+
"gitHead": "8c08cf8f2c3af4b9a306f9fe9ffedfe339b9b046"
|
|
39
39
|
}
|
package/typings/index.d.ts
CHANGED
|
@@ -382,6 +382,38 @@ declare module "@rian8337/osu-difficulty-calculator" {
|
|
|
382
382
|
* Creates skills to be calculated.
|
|
383
383
|
*/
|
|
384
384
|
protected override createSkills(): DroidSkill[];
|
|
385
|
+
/**
|
|
386
|
+
* Called after aim skill calculation.
|
|
387
|
+
*
|
|
388
|
+
* @param aimSkill The aim skill that considers sliders.
|
|
389
|
+
* @param aimSkillWithoutSliders The aim skill that doesn't consider sliders.
|
|
390
|
+
*/
|
|
391
|
+
private postCalculateAim(
|
|
392
|
+
aimSkill: DroidAim,
|
|
393
|
+
aimSkillWithoutSliders: DroidAim
|
|
394
|
+
): void;
|
|
395
|
+
/**
|
|
396
|
+
* Called after tap skill calculation.
|
|
397
|
+
*
|
|
398
|
+
* @param tapSkill The tap skill.
|
|
399
|
+
*/
|
|
400
|
+
private postCalculateTap(tapSkill: DroidTap): void;
|
|
401
|
+
/**
|
|
402
|
+
* Calculates the speed note count attribute.
|
|
403
|
+
*/
|
|
404
|
+
private calculateSpeedNoteCount(): void;
|
|
405
|
+
/**
|
|
406
|
+
* Called after rhythm skill calculation.
|
|
407
|
+
*
|
|
408
|
+
* @param rhythmSkill The rhythm skill.
|
|
409
|
+
*/
|
|
410
|
+
private postCalculateRhythm(rhythmSkill: DroidRhythm): void;
|
|
411
|
+
/**
|
|
412
|
+
* Called after flashlight skill calculation.
|
|
413
|
+
*
|
|
414
|
+
* @param flashlightSkill The flashlight skill.
|
|
415
|
+
*/
|
|
416
|
+
private postCalculateFlashlight(flashlightSkill: DroidFlashlight): void;
|
|
385
417
|
/**
|
|
386
418
|
* Calculates the base rating value of a difficulty.
|
|
387
419
|
*/
|