@rian8337/osu-difficulty-calculator 1.0.0
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/LICENSE +21 -0
- package/README.md +11 -0
- package/dist/DroidPerformanceCalculator.js +248 -0
- package/dist/DroidStarRating.js +175 -0
- package/dist/MapStars.js +50 -0
- package/dist/OsuPerformanceCalculator.js +217 -0
- package/dist/OsuStarRating.js +147 -0
- package/dist/base/DifficultyAttributes.js +2 -0
- package/dist/base/PerformanceCalculator.js +125 -0
- package/dist/base/Skill.js +29 -0
- package/dist/base/StarRating.js +130 -0
- package/dist/base/StrainSkill.js +75 -0
- package/dist/index.js +25 -0
- package/dist/preprocessing/DifficultyHitObject.js +89 -0
- package/dist/preprocessing/DifficultyHitObjectCreator.js +237 -0
- package/dist/skills/DroidAim.js +209 -0
- package/dist/skills/DroidFlashlight.js +59 -0
- package/dist/skills/DroidSkill.js +16 -0
- package/dist/skills/DroidTap.js +188 -0
- package/dist/skills/OsuAim.js +170 -0
- package/dist/skills/OsuFlashlight.js +60 -0
- package/dist/skills/OsuSkill.js +37 -0
- package/dist/skills/OsuSpeed.js +175 -0
- package/package.json +37 -0
- package/typings/index.d.ts +1052 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Rian8337
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DroidPerformanceCalculator = void 0;
|
|
4
|
+
const DroidStarRating_1 = require("./DroidStarRating");
|
|
5
|
+
const PerformanceCalculator_1 = require("./base/PerformanceCalculator");
|
|
6
|
+
const osu_base_1 = require("@rian8337/osu-base");
|
|
7
|
+
/**
|
|
8
|
+
* A performance points calculator that calculates performance points for osu!droid gamemode.
|
|
9
|
+
*/
|
|
10
|
+
class DroidPerformanceCalculator extends PerformanceCalculator_1.PerformanceCalculator {
|
|
11
|
+
constructor() {
|
|
12
|
+
super(...arguments);
|
|
13
|
+
this.stars = new DroidStarRating_1.DroidStarRating();
|
|
14
|
+
this.finalMultiplier = 1.42;
|
|
15
|
+
/**
|
|
16
|
+
* The aim performance value.
|
|
17
|
+
*/
|
|
18
|
+
this.aim = 0;
|
|
19
|
+
/**
|
|
20
|
+
* The tap performance value.
|
|
21
|
+
*/
|
|
22
|
+
this.tap = 0;
|
|
23
|
+
/**
|
|
24
|
+
* The accuracy performance value.
|
|
25
|
+
*/
|
|
26
|
+
this.accuracy = 0;
|
|
27
|
+
/**
|
|
28
|
+
* The flashlight performance value.
|
|
29
|
+
*/
|
|
30
|
+
this.flashlight = 0;
|
|
31
|
+
this.aggregatedRhythmMultiplier = 0;
|
|
32
|
+
}
|
|
33
|
+
calculate(params) {
|
|
34
|
+
this.handleParams(params, osu_base_1.modes.droid);
|
|
35
|
+
this.calculateAverageRhythmMultiplier();
|
|
36
|
+
this.calculateAimValue();
|
|
37
|
+
this.calculateTapValue();
|
|
38
|
+
this.calculateAccuracyValue();
|
|
39
|
+
this.calculateFlashlightValue();
|
|
40
|
+
// Apply tap penalty for penalized plays.
|
|
41
|
+
this.tap /= params.tapPenalty ?? 1;
|
|
42
|
+
this.total =
|
|
43
|
+
Math.pow(Math.pow(this.aim, 1.1) +
|
|
44
|
+
Math.pow(this.tap, 1.1) +
|
|
45
|
+
Math.pow(this.accuracy, 1.1) +
|
|
46
|
+
Math.pow(this.flashlight, 1.1), 1 / 1.1) * this.finalMultiplier;
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Calculates the average rhythm multiplier of the beatmap.
|
|
51
|
+
*/
|
|
52
|
+
calculateAverageRhythmMultiplier() {
|
|
53
|
+
// The first object doesn't have any rhythm multiplier, so we begin with the second object
|
|
54
|
+
const rhythmMultipliers = this.stars.objects
|
|
55
|
+
.map((v) => v.rhythmMultiplier)
|
|
56
|
+
.slice(1);
|
|
57
|
+
this.aggregatedRhythmMultiplier = Math.max(1, rhythmMultipliers.reduce((total, value) => total + value, 0) /
|
|
58
|
+
Math.max(500, rhythmMultipliers.length));
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Calculates the aim performance value of the beatmap.
|
|
62
|
+
*/
|
|
63
|
+
calculateAimValue() {
|
|
64
|
+
// Global variables
|
|
65
|
+
const objectCount = this.stars.objects.length;
|
|
66
|
+
const calculatedAR = this.mapStatistics.ar;
|
|
67
|
+
this.aim = this.baseValue(Math.pow(this.stars.aim, 0.8));
|
|
68
|
+
if (this.effectiveMissCount > 0) {
|
|
69
|
+
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
|
|
70
|
+
this.aim *=
|
|
71
|
+
0.97 *
|
|
72
|
+
Math.pow(1 - Math.pow(this.effectiveMissCount / objectCount, 0.775), this.effectiveMissCount);
|
|
73
|
+
}
|
|
74
|
+
// Combo scaling
|
|
75
|
+
this.aim *= this.comboPenalty;
|
|
76
|
+
// We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
|
|
77
|
+
let hiddenBonus = 1;
|
|
78
|
+
if (this.stars.mods.some((m) => m instanceof osu_base_1.ModHidden)) {
|
|
79
|
+
// The bonus starts decreasing twice as fast
|
|
80
|
+
// beyond AR10 and reaches 1 at AR11.
|
|
81
|
+
if (calculatedAR > 10) {
|
|
82
|
+
hiddenBonus += Math.max(0, 0.08 * (11 - calculatedAR));
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
hiddenBonus += 0.04 * (12 - calculatedAR);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
this.aim *= hiddenBonus;
|
|
89
|
+
// AR scaling
|
|
90
|
+
let arFactor = 0;
|
|
91
|
+
if (calculatedAR > 10.33) {
|
|
92
|
+
arFactor += 0.3 * (calculatedAR - 10.33);
|
|
93
|
+
}
|
|
94
|
+
else if (calculatedAR < 8) {
|
|
95
|
+
arFactor += 0.1 * (8 - calculatedAR);
|
|
96
|
+
}
|
|
97
|
+
// Buff for longer maps with high AR.
|
|
98
|
+
this.aim *=
|
|
99
|
+
1 +
|
|
100
|
+
arFactor *
|
|
101
|
+
(1.650668 +
|
|
102
|
+
(0.4845796 - 1.650668) /
|
|
103
|
+
(1 + Math.pow(objectCount / 817.9306, 1.147469)));
|
|
104
|
+
// Scale the aim value with slider factor to nerf very likely dropped sliderends.
|
|
105
|
+
this.aim *= this.sliderNerfFactor;
|
|
106
|
+
// Scale the aim value with accuracy.
|
|
107
|
+
this.aim *= this.computedAccuracy.value(objectCount);
|
|
108
|
+
// It is also important to consider accuracy difficulty when doing that.
|
|
109
|
+
const odScaling = Math.pow(this.mapStatistics.od, 2) / 2500;
|
|
110
|
+
this.aim *=
|
|
111
|
+
0.98 + (this.mapStatistics.od >= 0 ? odScaling : -odScaling);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Calculates the tap performance value of the beatmap.
|
|
115
|
+
*/
|
|
116
|
+
calculateTapValue() {
|
|
117
|
+
// Global variables
|
|
118
|
+
const objectCount = this.stars.objects.length;
|
|
119
|
+
const calculatedAR = this.mapStatistics.ar;
|
|
120
|
+
this.tap = this.baseValue(this.stars.tap);
|
|
121
|
+
if (this.effectiveMissCount > 0) {
|
|
122
|
+
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
|
|
123
|
+
this.tap *=
|
|
124
|
+
0.97 *
|
|
125
|
+
Math.pow(1 - Math.pow(this.effectiveMissCount / objectCount, 0.775), Math.pow(this.effectiveMissCount, 0.875));
|
|
126
|
+
}
|
|
127
|
+
// Combo scaling
|
|
128
|
+
this.tap *= this.comboPenalty;
|
|
129
|
+
// AR scaling
|
|
130
|
+
if (calculatedAR > 10.33) {
|
|
131
|
+
// Buff for longer maps with high AR.
|
|
132
|
+
this.tap *=
|
|
133
|
+
1 +
|
|
134
|
+
0.3 *
|
|
135
|
+
(calculatedAR - 10.33) *
|
|
136
|
+
(1.650668 +
|
|
137
|
+
(0.4845796 - 1.650668) /
|
|
138
|
+
(1 + Math.pow(objectCount / 817.9306, 1.147469)));
|
|
139
|
+
}
|
|
140
|
+
// Calculate accuracy assuming the worst case scenario.
|
|
141
|
+
const countGreat = this.computedAccuracy.n300;
|
|
142
|
+
const countOk = this.computedAccuracy.n100;
|
|
143
|
+
const countMeh = this.computedAccuracy.n50;
|
|
144
|
+
const relevantTotalDiff = objectCount - this.stars.attributes.speedNoteCount;
|
|
145
|
+
const relevantAccuracy = new osu_base_1.Accuracy({
|
|
146
|
+
n300: Math.max(0, countGreat - relevantTotalDiff),
|
|
147
|
+
n100: Math.max(0, countOk - Math.max(0, relevantTotalDiff - countGreat)),
|
|
148
|
+
n50: Math.max(0, countMeh - Math.max(0, relevantTotalDiff - countGreat - countOk)),
|
|
149
|
+
nmiss: this.effectiveMissCount,
|
|
150
|
+
});
|
|
151
|
+
// Scale the speed value with accuracy and OD.
|
|
152
|
+
const od = this.mapStatistics.od;
|
|
153
|
+
const odScaling = Math.pow(od, 2) / 750;
|
|
154
|
+
this.tap *=
|
|
155
|
+
(0.95 + (od > 0 ? odScaling : -odScaling)) *
|
|
156
|
+
Math.pow((this.computedAccuracy.value(objectCount) +
|
|
157
|
+
relevantAccuracy.value(this.stars.attributes.speedNoteCount)) /
|
|
158
|
+
2, (12 - Math.max(od, 2.5)) / 2);
|
|
159
|
+
// Scale the speed value with # of 50s to punish doubletapping.
|
|
160
|
+
this.tap *= Math.pow(0.98, Math.max(0, this.computedAccuracy.n50 - objectCount / 500));
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Calculates the accuracy performance value of the beatmap.
|
|
164
|
+
*/
|
|
165
|
+
calculateAccuracyValue() {
|
|
166
|
+
if (this.stars.mods.some((m) => m instanceof osu_base_1.ModRelax)) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
// Global variables
|
|
170
|
+
const ncircles = this.stars.mods.some((m) => m instanceof osu_base_1.ModScoreV2)
|
|
171
|
+
? this.stars.objects.length - this.stars.map.spinners
|
|
172
|
+
: this.stars.map.circles;
|
|
173
|
+
if (ncircles === 0) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const realAccuracy = new osu_base_1.Accuracy({
|
|
177
|
+
...this.computedAccuracy,
|
|
178
|
+
n300: this.computedAccuracy.n300 -
|
|
179
|
+
(this.stars.objects.length - ncircles),
|
|
180
|
+
});
|
|
181
|
+
// Lots of arbitrary values from testing.
|
|
182
|
+
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
|
|
183
|
+
this.accuracy =
|
|
184
|
+
Math.pow(1.4, this.mapStatistics.od) *
|
|
185
|
+
Math.pow(realAccuracy.value(ncircles), 12) *
|
|
186
|
+
10;
|
|
187
|
+
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer
|
|
188
|
+
this.accuracy *= Math.min(1.15, Math.pow(ncircles / 1000, 0.3));
|
|
189
|
+
// Scale the accuracy value with rhythm complexity.
|
|
190
|
+
this.accuracy *= Math.min(1, Math.pow(Math.exp(this.aggregatedRhythmMultiplier - 0.5), 0.85) / 2);
|
|
191
|
+
if (this.stars.mods.some((m) => m instanceof osu_base_1.ModHidden)) {
|
|
192
|
+
this.accuracy *= 1.08;
|
|
193
|
+
}
|
|
194
|
+
if (this.stars.mods.some((m) => m instanceof osu_base_1.ModFlashlight)) {
|
|
195
|
+
this.accuracy *= 1.02;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Calculates the flashlight performance value of the beatmap.
|
|
200
|
+
*/
|
|
201
|
+
calculateFlashlightValue() {
|
|
202
|
+
if (!this.stars.mods.some((m) => m instanceof osu_base_1.ModFlashlight)) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
// Global variables
|
|
206
|
+
const objectCount = this.stars.objects.length;
|
|
207
|
+
this.flashlight =
|
|
208
|
+
Math.pow(Math.pow(this.stars.flashlight, 0.8), 2) * 25;
|
|
209
|
+
// Add an additional bonus for HDFL.
|
|
210
|
+
if (this.stars.mods.some((m) => m instanceof osu_base_1.ModHidden)) {
|
|
211
|
+
this.flashlight *= 1.3;
|
|
212
|
+
}
|
|
213
|
+
// Combo scaling
|
|
214
|
+
this.flashlight *= this.comboPenalty;
|
|
215
|
+
if (this.effectiveMissCount > 0) {
|
|
216
|
+
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
|
|
217
|
+
this.flashlight *=
|
|
218
|
+
0.97 *
|
|
219
|
+
Math.pow(1 - Math.pow(this.effectiveMissCount / objectCount, 0.775), Math.pow(this.effectiveMissCount, 0.875));
|
|
220
|
+
}
|
|
221
|
+
// Account for shorter maps having a higher ratio of 0 combo/100 combo flashlight radius.
|
|
222
|
+
this.flashlight *=
|
|
223
|
+
0.7 +
|
|
224
|
+
0.1 * Math.min(1, objectCount / 200) +
|
|
225
|
+
(objectCount > 200
|
|
226
|
+
? 0.2 * Math.min(1, (objectCount - 200) / 200)
|
|
227
|
+
: 0);
|
|
228
|
+
// Scale the flashlight value with accuracy slightly.
|
|
229
|
+
this.flashlight *= 0.5 + this.computedAccuracy.value(objectCount) / 2;
|
|
230
|
+
// It is also important to consider accuracy difficulty when doing that.
|
|
231
|
+
const odScaling = Math.pow(this.mapStatistics.od, 2) / 2500;
|
|
232
|
+
this.flashlight *=
|
|
233
|
+
0.98 + (this.mapStatistics.od >= 0 ? odScaling : -odScaling);
|
|
234
|
+
}
|
|
235
|
+
toString() {
|
|
236
|
+
return (this.total.toFixed(2) +
|
|
237
|
+
" pp (" +
|
|
238
|
+
this.aim.toFixed(2) +
|
|
239
|
+
" aim, " +
|
|
240
|
+
this.tap.toFixed(2) +
|
|
241
|
+
" tap, " +
|
|
242
|
+
this.accuracy.toFixed(2) +
|
|
243
|
+
" acc, " +
|
|
244
|
+
this.flashlight.toFixed(2) +
|
|
245
|
+
" flashlight)");
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
exports.DroidPerformanceCalculator = DroidPerformanceCalculator;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DroidStarRating = void 0;
|
|
4
|
+
const DroidAim_1 = require("./skills/DroidAim");
|
|
5
|
+
const DroidTap_1 = require("./skills/DroidTap");
|
|
6
|
+
const StarRating_1 = require("./base/StarRating");
|
|
7
|
+
const DroidFlashlight_1 = require("./skills/DroidFlashlight");
|
|
8
|
+
const osu_base_1 = require("@rian8337/osu-base");
|
|
9
|
+
/**
|
|
10
|
+
* Difficulty calculator for osu!droid gamemode.
|
|
11
|
+
*/
|
|
12
|
+
class DroidStarRating extends StarRating_1.StarRating {
|
|
13
|
+
constructor() {
|
|
14
|
+
super(...arguments);
|
|
15
|
+
/**
|
|
16
|
+
* The aim star rating of the beatmap.
|
|
17
|
+
*/
|
|
18
|
+
this.aim = 0;
|
|
19
|
+
/**
|
|
20
|
+
* The tap star rating of the beatmap.
|
|
21
|
+
*/
|
|
22
|
+
this.tap = 0;
|
|
23
|
+
/**
|
|
24
|
+
* The flashlight star rating of the beatmap.
|
|
25
|
+
*/
|
|
26
|
+
this.flashlight = 0;
|
|
27
|
+
this.difficultyMultiplier = 0.18;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Calculates the star rating of the specified beatmap.
|
|
31
|
+
*
|
|
32
|
+
* The beatmap is analyzed in chunks of `sectionLength` duration.
|
|
33
|
+
* For each chunk the highest hitobject strains are added to
|
|
34
|
+
* a list which is then collapsed into a weighted sum, much
|
|
35
|
+
* like scores are weighted on a user's profile.
|
|
36
|
+
*
|
|
37
|
+
* For subsequent chunks, the initial max strain is calculated
|
|
38
|
+
* by decaying the previous hitobject's strain until the
|
|
39
|
+
* beginning of the new chunk.
|
|
40
|
+
*
|
|
41
|
+
* The first object doesn't generate a strain
|
|
42
|
+
* so we begin calculating from the second object.
|
|
43
|
+
*
|
|
44
|
+
* Also don't forget to manually add the peak strain for the last
|
|
45
|
+
* section which would otherwise be ignored.
|
|
46
|
+
*/
|
|
47
|
+
calculate(params) {
|
|
48
|
+
return super.calculate(params, osu_base_1.modes.droid);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Calculates the aim star rating of the beatmap and stores it in this instance.
|
|
52
|
+
*/
|
|
53
|
+
calculateAim() {
|
|
54
|
+
const aimSkill = new DroidAim_1.DroidAim(this.mods, true);
|
|
55
|
+
const aimSkillWithoutSliders = new DroidAim_1.DroidAim(this.mods, false);
|
|
56
|
+
this.calculateSkills(aimSkill, aimSkillWithoutSliders);
|
|
57
|
+
this.strainPeaks.aimWithSliders = aimSkill.strainPeaks;
|
|
58
|
+
this.strainPeaks.aimWithoutSliders = aimSkillWithoutSliders.strainPeaks;
|
|
59
|
+
this.aim = this.starValue(aimSkill.difficultyValue());
|
|
60
|
+
if (this.aim) {
|
|
61
|
+
this.attributes.sliderFactor =
|
|
62
|
+
this.starValue(aimSkillWithoutSliders.difficultyValue()) /
|
|
63
|
+
this.aim;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Calculates the speed star rating of the beatmap and stores it in this instance.
|
|
68
|
+
*/
|
|
69
|
+
calculateTap() {
|
|
70
|
+
if (this.mods.some((m) => m instanceof osu_base_1.ModRelax)) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const tapSkill = new DroidTap_1.DroidTap(this.mods, this.stats.od);
|
|
74
|
+
this.calculateSkills(tapSkill);
|
|
75
|
+
this.strainPeaks.speed = tapSkill.strainPeaks;
|
|
76
|
+
this.tap = this.starValue(tapSkill.difficultyValue());
|
|
77
|
+
const objectStrains = this.objects.map((v) => v.tapStrain);
|
|
78
|
+
const maxStrain = Math.max(...objectStrains);
|
|
79
|
+
if (maxStrain) {
|
|
80
|
+
this.attributes.speedNoteCount = objectStrains.reduce((total, next) => total + 1 / (1 + Math.exp(-((next / maxStrain) * 12 - 6))), 0);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Calculates the flashlight star rating of the beatmap and stores it in this instance.
|
|
85
|
+
*/
|
|
86
|
+
calculateFlashlight() {
|
|
87
|
+
const flashlightSkill = new DroidFlashlight_1.DroidFlashlight(this.mods);
|
|
88
|
+
this.calculateSkills(flashlightSkill);
|
|
89
|
+
this.strainPeaks.flashlight = flashlightSkill.strainPeaks;
|
|
90
|
+
this.flashlight = this.starValue(flashlightSkill.difficultyValue());
|
|
91
|
+
}
|
|
92
|
+
calculateTotal() {
|
|
93
|
+
const aimPerformanceValue = this.basePerformanceValue(this.aim);
|
|
94
|
+
const speedPerformanceValue = this.basePerformanceValue(this.tap);
|
|
95
|
+
const flashlightPerformanceValue = this.mods.some((m) => m instanceof osu_base_1.ModFlashlight)
|
|
96
|
+
? Math.pow(this.flashlight, 2) * 25
|
|
97
|
+
: 0;
|
|
98
|
+
const basePerformanceValue = Math.pow(Math.pow(aimPerformanceValue, 1.1) +
|
|
99
|
+
Math.pow(speedPerformanceValue, 1.1) +
|
|
100
|
+
Math.pow(flashlightPerformanceValue, 1.1), 1 / 1.1);
|
|
101
|
+
if (basePerformanceValue > 1e-5) {
|
|
102
|
+
this.total =
|
|
103
|
+
Math.cbrt(1.12) *
|
|
104
|
+
0.027 *
|
|
105
|
+
(Math.cbrt((100000 / Math.pow(2, 1 / 1.1)) * basePerformanceValue) +
|
|
106
|
+
4);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
calculateAll() {
|
|
110
|
+
const skills = this.createSkills();
|
|
111
|
+
const isRelax = this.mods.some((m) => m instanceof osu_base_1.ModRelax);
|
|
112
|
+
if (isRelax) {
|
|
113
|
+
// Remove speed skill to prevent overhead
|
|
114
|
+
skills.splice(2, 1);
|
|
115
|
+
}
|
|
116
|
+
this.calculateSkills(...skills);
|
|
117
|
+
const aimSkill = skills[0];
|
|
118
|
+
const aimSkillWithoutSliders = skills[1];
|
|
119
|
+
let tapSkill;
|
|
120
|
+
let flashlightSkill;
|
|
121
|
+
if (!isRelax) {
|
|
122
|
+
tapSkill = skills[2];
|
|
123
|
+
flashlightSkill = skills[3];
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
flashlightSkill = skills[2];
|
|
127
|
+
}
|
|
128
|
+
this.strainPeaks.aimWithSliders = aimSkill.strainPeaks;
|
|
129
|
+
this.strainPeaks.aimWithoutSliders = aimSkillWithoutSliders.strainPeaks;
|
|
130
|
+
this.aim = this.starValue(aimSkill.difficultyValue());
|
|
131
|
+
if (this.aim) {
|
|
132
|
+
this.attributes.sliderFactor =
|
|
133
|
+
this.starValue(aimSkillWithoutSliders.difficultyValue()) /
|
|
134
|
+
this.aim;
|
|
135
|
+
}
|
|
136
|
+
if (tapSkill) {
|
|
137
|
+
this.strainPeaks.speed = tapSkill.strainPeaks;
|
|
138
|
+
this.tap = this.starValue(tapSkill.difficultyValue());
|
|
139
|
+
const objectStrains = this.objects.map((v) => v.tapStrain);
|
|
140
|
+
const maxStrain = Math.max(...objectStrains);
|
|
141
|
+
if (maxStrain) {
|
|
142
|
+
this.attributes.speedNoteCount = objectStrains.reduce((total, next) => total +
|
|
143
|
+
1 / (1 + Math.exp(-((next / maxStrain) * 12 - 6))), 0);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
this.strainPeaks.flashlight = flashlightSkill.strainPeaks;
|
|
147
|
+
this.flashlight = this.starValue(flashlightSkill.difficultyValue());
|
|
148
|
+
this.calculateTotal();
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Returns a string representative of the class.
|
|
152
|
+
*/
|
|
153
|
+
toString() {
|
|
154
|
+
return (this.total.toFixed(2) +
|
|
155
|
+
" stars (" +
|
|
156
|
+
this.aim.toFixed(2) +
|
|
157
|
+
" aim, " +
|
|
158
|
+
this.tap.toFixed(2) +
|
|
159
|
+
" tap, " +
|
|
160
|
+
this.flashlight.toFixed(2) +
|
|
161
|
+
" flashlight)");
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Creates skills to be calculated.
|
|
165
|
+
*/
|
|
166
|
+
createSkills() {
|
|
167
|
+
return [
|
|
168
|
+
new DroidAim_1.DroidAim(this.mods, true),
|
|
169
|
+
new DroidAim_1.DroidAim(this.mods, false),
|
|
170
|
+
new DroidTap_1.DroidTap(this.mods, this.stats.od),
|
|
171
|
+
new DroidFlashlight_1.DroidFlashlight(this.mods),
|
|
172
|
+
];
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
exports.DroidStarRating = DroidStarRating;
|
package/dist/MapStars.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MapStars = void 0;
|
|
4
|
+
const osu_base_1 = require("@rian8337/osu-base");
|
|
5
|
+
const DroidStarRating_1 = require("./DroidStarRating");
|
|
6
|
+
const OsuStarRating_1 = require("./OsuStarRating");
|
|
7
|
+
/**
|
|
8
|
+
* A star rating calculator that configures which mode to calculate difficulty for and what mods are applied.
|
|
9
|
+
*/
|
|
10
|
+
class MapStars {
|
|
11
|
+
constructor() {
|
|
12
|
+
/**
|
|
13
|
+
* The osu!droid star rating of the beatmap.
|
|
14
|
+
*/
|
|
15
|
+
this.droidStars = new DroidStarRating_1.DroidStarRating();
|
|
16
|
+
/**
|
|
17
|
+
* The osu!standard star rating of the beatmap.
|
|
18
|
+
*/
|
|
19
|
+
this.pcStars = new OsuStarRating_1.OsuStarRating();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Calculates the star rating of a beatmap.
|
|
23
|
+
*/
|
|
24
|
+
calculate(params) {
|
|
25
|
+
const mod = params.mods ?? [];
|
|
26
|
+
const stats = new osu_base_1.MapStats({
|
|
27
|
+
speedMultiplier: params.stats?.speedMultiplier ?? 1,
|
|
28
|
+
isForceAR: params.stats?.isForceAR ?? false,
|
|
29
|
+
oldStatistics: params.stats?.oldStatistics ?? false,
|
|
30
|
+
});
|
|
31
|
+
this.droidStars.calculate({
|
|
32
|
+
map: params.map,
|
|
33
|
+
mods: mod,
|
|
34
|
+
stats,
|
|
35
|
+
});
|
|
36
|
+
this.pcStars.calculate({
|
|
37
|
+
map: params.map,
|
|
38
|
+
mods: mod,
|
|
39
|
+
stats,
|
|
40
|
+
});
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Returns a string representative of the class.
|
|
45
|
+
*/
|
|
46
|
+
toString() {
|
|
47
|
+
return `${this.droidStars.toString()}\n${this.pcStars.toString()}`;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
exports.MapStars = MapStars;
|