@rian8337/osu-difficulty-calculator 4.0.0-beta.23 → 4.0.0-beta.25
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 +246 -182
- package/package.json +3 -3
- package/typings/index.d.ts +29 -26
package/dist/index.js
CHANGED
|
@@ -44,7 +44,6 @@ class DifficultyCalculator {
|
|
|
44
44
|
* @param beatmap The beatmap to calculate. This beatmap will be deep-cloned to prevent reference changes.
|
|
45
45
|
*/
|
|
46
46
|
constructor(beatmap) {
|
|
47
|
-
var _a;
|
|
48
47
|
/**
|
|
49
48
|
* The difficulty objects of the beatmap.
|
|
50
49
|
*/
|
|
@@ -65,7 +64,7 @@ class DifficultyCalculator {
|
|
|
65
64
|
this.beatmap = beatmap;
|
|
66
65
|
this.difficultyStatistics = {
|
|
67
66
|
circleSize: beatmap.difficulty.cs,
|
|
68
|
-
approachRate:
|
|
67
|
+
approachRate: beatmap.difficulty.ar,
|
|
69
68
|
overallDifficulty: beatmap.difficulty.od,
|
|
70
69
|
healthDrain: beatmap.difficulty.hp,
|
|
71
70
|
overallSpeedMultiplier: 1,
|
|
@@ -89,14 +88,14 @@ class DifficultyCalculator {
|
|
|
89
88
|
calculate(options) {
|
|
90
89
|
var _a;
|
|
91
90
|
this.mods = (_a = options === null || options === void 0 ? void 0 : options.mods) !== null && _a !== void 0 ? _a : [];
|
|
92
|
-
const
|
|
91
|
+
const playableBeatmap = this.beatmap.createPlayableBeatmap({
|
|
93
92
|
mode: this.mode,
|
|
94
93
|
mods: this.mods,
|
|
95
94
|
customSpeedMultiplier: options === null || options === void 0 ? void 0 : options.customSpeedMultiplier,
|
|
96
95
|
});
|
|
97
96
|
this.difficultyStatistics = Object.seal(this.computeDifficultyStatistics(options));
|
|
98
97
|
this.populateDifficultyAttributes();
|
|
99
|
-
this.objects.push(...this.generateDifficultyHitObjects(
|
|
98
|
+
this.objects.push(...this.generateDifficultyHitObjects(playableBeatmap));
|
|
100
99
|
this.calculateAll();
|
|
101
100
|
return this;
|
|
102
101
|
}
|
|
@@ -159,8 +158,9 @@ class DifficultyHitObject {
|
|
|
159
158
|
* @param lastLastObject The hitobject before the last hitobject.
|
|
160
159
|
* @param difficultyHitObjects All difficulty hitobjects in the processed beatmap.
|
|
161
160
|
* @param clockRate The clock rate of the beatmap.
|
|
161
|
+
* @param greatWindow The great window of the hitobject.
|
|
162
162
|
*/
|
|
163
|
-
constructor(object, lastObject, lastLastObject, difficultyHitObjects, clockRate) {
|
|
163
|
+
constructor(object, lastObject, lastLastObject, difficultyHitObjects, clockRate, greatWindow) {
|
|
164
164
|
/**
|
|
165
165
|
* The aim strain generated by the hitobject if sliders are considered.
|
|
166
166
|
*/
|
|
@@ -213,18 +213,18 @@ class DifficultyHitObject {
|
|
|
213
213
|
this.normalizedRadius = 50;
|
|
214
214
|
this.maximumSliderRadius = this.normalizedRadius * 2.4;
|
|
215
215
|
this.assumedSliderRadius = this.normalizedRadius * 1.8;
|
|
216
|
-
this.minDeltaTime = 25;
|
|
217
216
|
this.object = object;
|
|
218
217
|
this.lastObject = lastObject;
|
|
219
218
|
this.lastLastObject = lastLastObject;
|
|
220
219
|
this.hitObjects = difficultyHitObjects;
|
|
220
|
+
this.fullGreatWindow = greatWindow * 2;
|
|
221
221
|
this.index = difficultyHitObjects.length - 1;
|
|
222
222
|
// Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects.
|
|
223
223
|
this.startTime = object.startTime / clockRate;
|
|
224
224
|
this.endTime = object.endTime / clockRate;
|
|
225
225
|
if (lastObject) {
|
|
226
226
|
this.deltaTime = this.startTime - lastObject.startTime / clockRate;
|
|
227
|
-
this.strainTime = Math.max(this.deltaTime,
|
|
227
|
+
this.strainTime = Math.max(this.deltaTime, DifficultyHitObject.minDeltaTime);
|
|
228
228
|
}
|
|
229
229
|
else {
|
|
230
230
|
this.deltaTime = 0;
|
|
@@ -295,6 +295,24 @@ class DifficultyHitObject {
|
|
|
295
295
|
}
|
|
296
296
|
return osuBase.MathUtils.clamp((time - fadeInStartTime) / fadeInDuration, 0, 1);
|
|
297
297
|
}
|
|
298
|
+
/**
|
|
299
|
+
* How possible is it to doubletap this object together with the next one and get perfect
|
|
300
|
+
* judgement in range from 0 to 1.
|
|
301
|
+
*
|
|
302
|
+
* A value closer to 1 indicates a higher possibility.
|
|
303
|
+
*/
|
|
304
|
+
get doubletapness() {
|
|
305
|
+
const next = this.next(0);
|
|
306
|
+
if (!next) {
|
|
307
|
+
return 0;
|
|
308
|
+
}
|
|
309
|
+
const currentDeltaTime = Math.max(1, this.deltaTime);
|
|
310
|
+
const nextDeltaTime = Math.max(1, next.deltaTime);
|
|
311
|
+
const deltaDifference = Math.abs(nextDeltaTime - currentDeltaTime);
|
|
312
|
+
const speedRatio = currentDeltaTime / Math.max(currentDeltaTime, deltaDifference);
|
|
313
|
+
const windowRatio = Math.pow(Math.min(1, currentDeltaTime / this.fullGreatWindow), 2);
|
|
314
|
+
return 1 - Math.pow(speedRatio, 1 - windowRatio);
|
|
315
|
+
}
|
|
298
316
|
setDistances(clockRate) {
|
|
299
317
|
if (this.object instanceof osuBase.Slider) {
|
|
300
318
|
this.calculateSliderCursorPosition(this.object);
|
|
@@ -306,7 +324,7 @@ class DifficultyHitObject {
|
|
|
306
324
|
else {
|
|
307
325
|
this.travelDistance *= Math.pow(1 + this.object.repeatCount / 2.5, 1 / 2.5);
|
|
308
326
|
}
|
|
309
|
-
this.travelTime = Math.max(this.object.lazyTravelTime / clockRate,
|
|
327
|
+
this.travelTime = Math.max(this.object.lazyTravelTime / clockRate, DifficultyHitObject.minDeltaTime);
|
|
310
328
|
}
|
|
311
329
|
// We don't need to calculate either angle or distance when one of the last->curr objects is a spinner.
|
|
312
330
|
if (!this.lastObject ||
|
|
@@ -324,8 +342,8 @@ class DifficultyHitObject {
|
|
|
324
342
|
this.minimumJumpTime = this.strainTime;
|
|
325
343
|
this.minimumJumpDistance = this.lazyJumpDistance;
|
|
326
344
|
if (this.lastObject instanceof osuBase.Slider) {
|
|
327
|
-
const lastTravelTime = Math.max(this.lastObject.lazyTravelTime / clockRate,
|
|
328
|
-
this.minimumJumpTime = Math.max(this.strainTime - lastTravelTime,
|
|
345
|
+
const lastTravelTime = Math.max(this.lastObject.lazyTravelTime / clockRate, DifficultyHitObject.minDeltaTime);
|
|
346
|
+
this.minimumJumpTime = Math.max(this.strainTime - lastTravelTime, DifficultyHitObject.minDeltaTime);
|
|
329
347
|
// There are two types of slider-to-object patterns to consider in order to better approximate the real movement a player will take to jump between the hitobjects.
|
|
330
348
|
//
|
|
331
349
|
// 1. The anti-flow pattern, where players cut the slider short in order to move to the next hitobject.
|
|
@@ -440,6 +458,10 @@ class DifficultyHitObject {
|
|
|
440
458
|
return pos;
|
|
441
459
|
}
|
|
442
460
|
}
|
|
461
|
+
/**
|
|
462
|
+
* The lowest possible delta time value.
|
|
463
|
+
*/
|
|
464
|
+
DifficultyHitObject.minDeltaTime = 25;
|
|
443
465
|
|
|
444
466
|
/**
|
|
445
467
|
* An evaluator for calculating osu!droid Aim skill.
|
|
@@ -692,6 +714,7 @@ class DroidSkill extends StrainSkill {
|
|
|
692
714
|
constructor() {
|
|
693
715
|
super(...arguments);
|
|
694
716
|
this._objectStrains = [];
|
|
717
|
+
this.difficulty = 0;
|
|
695
718
|
}
|
|
696
719
|
/**
|
|
697
720
|
* The strains of hitobjects.
|
|
@@ -705,14 +728,14 @@ class DroidSkill extends StrainSkill {
|
|
|
705
728
|
* The result is scaled by clock rate as it affects the total number of strains.
|
|
706
729
|
*/
|
|
707
730
|
countDifficultStrains() {
|
|
708
|
-
if (this.
|
|
709
|
-
return 0;
|
|
710
|
-
}
|
|
711
|
-
const maxStrain = Math.max(...this._objectStrains);
|
|
712
|
-
if (maxStrain === 0) {
|
|
731
|
+
if (this.difficulty === 0) {
|
|
713
732
|
return 0;
|
|
714
733
|
}
|
|
715
|
-
|
|
734
|
+
// This is what the top strain is if all strain values were identical.
|
|
735
|
+
const consistentTopStrain = this.difficulty / 10;
|
|
736
|
+
// Use a weighted sum of all strains.
|
|
737
|
+
return this._objectStrains.reduce((total, next) => total +
|
|
738
|
+
1.1 / (1 + Math.exp(-10 * (next / consistentTopStrain - 0.88))), 0);
|
|
716
739
|
}
|
|
717
740
|
process(current) {
|
|
718
741
|
super.process(current);
|
|
@@ -730,12 +753,12 @@ class DroidSkill extends StrainSkill {
|
|
|
730
753
|
}
|
|
731
754
|
// Math here preserves the property that two notes of equal difficulty x, we have their summed difficulty = x * starsPerDouble.
|
|
732
755
|
// This also applies to two sets of notes with equal difficulty.
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
756
|
+
this.difficulty = 0;
|
|
757
|
+
for (const strain of strains) {
|
|
758
|
+
this.difficulty += Math.pow(strain, 1 / Math.log2(this.starsPerDouble));
|
|
759
|
+
}
|
|
760
|
+
this.difficulty = Math.pow(this.difficulty, Math.log2(this.starsPerDouble));
|
|
761
|
+
return this.difficulty;
|
|
739
762
|
}
|
|
740
763
|
calculateCurrentSectionStart(current) {
|
|
741
764
|
return current.startTime;
|
|
@@ -811,27 +834,16 @@ class DroidTapEvaluator extends SpeedEvaluator {
|
|
|
811
834
|
* @param considerCheesability Whether to consider cheesability.
|
|
812
835
|
* @param strainTimeCap The strain time to cap the object's strain time to.
|
|
813
836
|
*/
|
|
814
|
-
static evaluateDifficultyOf(current,
|
|
837
|
+
static evaluateDifficultyOf(current, considerCheesability, strainTimeCap) {
|
|
815
838
|
if (current.object instanceof osuBase.Spinner ||
|
|
816
839
|
// Exclude overlapping objects that can be tapped at once.
|
|
817
840
|
current.isOverlapping(false)) {
|
|
818
841
|
return 0;
|
|
819
842
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
if (next) {
|
|
825
|
-
const greatWindowFull = greatWindow * 2;
|
|
826
|
-
const currentDeltaTime = Math.max(1, current.deltaTime);
|
|
827
|
-
const nextDeltaTime = Math.max(1, next.deltaTime);
|
|
828
|
-
const deltaDifference = Math.abs(nextDeltaTime - currentDeltaTime);
|
|
829
|
-
const speedRatio = currentDeltaTime /
|
|
830
|
-
Math.max(currentDeltaTime, deltaDifference);
|
|
831
|
-
const windowRatio = Math.pow(Math.min(1, currentDeltaTime / greatWindowFull), 2);
|
|
832
|
-
doubletapness = Math.pow(speedRatio, 1 - windowRatio);
|
|
833
|
-
}
|
|
834
|
-
}
|
|
843
|
+
// Nerf doubletappable doubles.
|
|
844
|
+
const doubletapness = considerCheesability
|
|
845
|
+
? 1 - current.doubletapness
|
|
846
|
+
: 1;
|
|
835
847
|
const strainTime = strainTimeCap !== undefined
|
|
836
848
|
? // We cap the strain time to 50 here as the chance of vibro is higher in any BPM higher than 300.
|
|
837
849
|
Math.max(50, strainTimeCap, current.strainTime)
|
|
@@ -856,7 +868,7 @@ class DroidTap extends DroidSkill {
|
|
|
856
868
|
get objectDeltaTimes() {
|
|
857
869
|
return this._objectDeltaTimes;
|
|
858
870
|
}
|
|
859
|
-
constructor(mods,
|
|
871
|
+
constructor(mods, considerCheesability, strainTimeCap) {
|
|
860
872
|
super(mods);
|
|
861
873
|
this.reducedSectionCount = 10;
|
|
862
874
|
this.reducedSectionBaseline = 0.75;
|
|
@@ -866,7 +878,6 @@ class DroidTap extends DroidSkill {
|
|
|
866
878
|
this.currentRhythmMultiplier = 0;
|
|
867
879
|
this.skillMultiplier = 1375;
|
|
868
880
|
this._objectDeltaTimes = [];
|
|
869
|
-
this.greatWindow = new osuBase.OsuHitWindow(overallDifficulty).hitWindowFor300();
|
|
870
881
|
this.considerCheesability = considerCheesability;
|
|
871
882
|
this.strainTimeCap = strainTimeCap;
|
|
872
883
|
}
|
|
@@ -905,7 +916,7 @@ class DroidTap extends DroidSkill {
|
|
|
905
916
|
strainValueAt(current) {
|
|
906
917
|
this.currentTapStrain *= this.strainDecay(current.strainTime);
|
|
907
918
|
this.currentTapStrain +=
|
|
908
|
-
DroidTapEvaluator.evaluateDifficultyOf(current, this.
|
|
919
|
+
DroidTapEvaluator.evaluateDifficultyOf(current, this.considerCheesability, this.strainTimeCap) * this.skillMultiplier;
|
|
909
920
|
this.currentRhythmMultiplier = current.rhythmMultiplier;
|
|
910
921
|
this._objectDeltaTimes.push(current.deltaTime);
|
|
911
922
|
return this.currentTapStrain * current.rhythmMultiplier;
|
|
@@ -1089,6 +1100,46 @@ class RhythmEvaluator {
|
|
|
1089
1100
|
RhythmEvaluator.rhythmMultiplier = 0.75;
|
|
1090
1101
|
RhythmEvaluator.historyTimeMax = 5000; // 5 seconds of calculateRhythmBonus max.
|
|
1091
1102
|
|
|
1103
|
+
class Island {
|
|
1104
|
+
constructor(firstDelta, epsilon) {
|
|
1105
|
+
this.deltas = [];
|
|
1106
|
+
if (epsilon === undefined) {
|
|
1107
|
+
this.deltaDifferenceEpsilon = firstDelta;
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
this.deltaDifferenceEpsilon = epsilon;
|
|
1111
|
+
this.addDelta(firstDelta);
|
|
1112
|
+
}
|
|
1113
|
+
addDelta(delta) {
|
|
1114
|
+
// Convert to integer
|
|
1115
|
+
delta = Math.trunc(delta);
|
|
1116
|
+
const existingDelta = this.deltas.find((v) => Math.abs(v - delta) >= this.deltaDifferenceEpsilon);
|
|
1117
|
+
this.deltas.push(existingDelta !== null && existingDelta !== void 0 ? existingDelta : delta);
|
|
1118
|
+
}
|
|
1119
|
+
get averageDelta() {
|
|
1120
|
+
return this.deltas.length > 0
|
|
1121
|
+
? Math.max(this.deltas.reduce((a, b) => a + b) / this.deltas.length, DifficultyHitObject.minDeltaTime)
|
|
1122
|
+
: 0;
|
|
1123
|
+
}
|
|
1124
|
+
isSimilarPolarity(other) {
|
|
1125
|
+
// Consider islands to be of similar polarity only if they're having the same
|
|
1126
|
+
// average delta (we don't want to consider 3 singletaps similar to a triple)
|
|
1127
|
+
return (Math.abs(this.averageDelta - other.averageDelta) <
|
|
1128
|
+
this.deltaDifferenceEpsilon &&
|
|
1129
|
+
this.deltas.length % 2 === other.deltas.length % 2);
|
|
1130
|
+
}
|
|
1131
|
+
equals(other) {
|
|
1132
|
+
if (this.deltas.length !== other.deltas.length) {
|
|
1133
|
+
return false;
|
|
1134
|
+
}
|
|
1135
|
+
for (let i = 0; i < this.deltas.length; ++i) {
|
|
1136
|
+
if (this.deltas[i] !== other.deltas[i]) {
|
|
1137
|
+
return false;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
return true;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1092
1143
|
/**
|
|
1093
1144
|
* An evaluator for calculating osu!droid Rhythm skill.
|
|
1094
1145
|
*/
|
|
@@ -1098,22 +1149,23 @@ class DroidRhythmEvaluator extends RhythmEvaluator {
|
|
|
1098
1149
|
* with historic data of the current object.
|
|
1099
1150
|
*
|
|
1100
1151
|
* @param current The current object.
|
|
1101
|
-
* @param greatWindow The great hit window of the current object.
|
|
1102
1152
|
*/
|
|
1103
|
-
static evaluateDifficultyOf(current
|
|
1153
|
+
static evaluateDifficultyOf(current) {
|
|
1104
1154
|
if (current.object instanceof osuBase.Spinner ||
|
|
1105
1155
|
// Exclude overlapping objects that can be tapped at once.
|
|
1106
1156
|
current.isOverlapping(false)) {
|
|
1107
1157
|
return 1;
|
|
1108
1158
|
}
|
|
1109
|
-
|
|
1159
|
+
const deltaDifferenceEpsilon = current.fullGreatWindow * 0.3;
|
|
1110
1160
|
let rhythmComplexitySum = 0;
|
|
1111
|
-
let
|
|
1161
|
+
let island = new Island(deltaDifferenceEpsilon);
|
|
1162
|
+
let previousIsland = new Island(deltaDifferenceEpsilon);
|
|
1163
|
+
const islandCounts = new Map();
|
|
1112
1164
|
// Store the ratio of the current start of an island to buff for tighter rhythms.
|
|
1113
1165
|
let startRatio = 0;
|
|
1114
1166
|
let firstDeltaSwitch = false;
|
|
1115
1167
|
let rhythmStart = 0;
|
|
1116
|
-
const historicalNoteCount = Math.min(current.index,
|
|
1168
|
+
const historicalNoteCount = Math.min(current.index, this.historyObjectsMax);
|
|
1117
1169
|
// Exclude overlapping objects that can be tapped at once.
|
|
1118
1170
|
const validPrevious = [];
|
|
1119
1171
|
for (let i = 0; i < historicalNoteCount; ++i) {
|
|
@@ -1137,105 +1189,125 @@ class DroidRhythmEvaluator extends RhythmEvaluator {
|
|
|
1137
1189
|
this.historyTimeMax;
|
|
1138
1190
|
// Either we're limited by time or limited by object count.
|
|
1139
1191
|
currentHistoricalDecay = Math.min(currentHistoricalDecay, (validPrevious.length - i) / validPrevious.length);
|
|
1140
|
-
const
|
|
1141
|
-
const
|
|
1142
|
-
const
|
|
1192
|
+
const currentObject = validPrevious[i - 1];
|
|
1193
|
+
const prevObject = validPrevious[i];
|
|
1194
|
+
const lastObject = validPrevious[i + 1];
|
|
1195
|
+
const currentDelta = currentObject.strainTime;
|
|
1196
|
+
const prevDelta = prevObject.strainTime;
|
|
1197
|
+
const lastDelta = lastObject.strainTime;
|
|
1143
1198
|
const currentRatio = 1 +
|
|
1144
|
-
|
|
1199
|
+
10 *
|
|
1145
1200
|
Math.min(0.5, Math.pow(Math.sin(Math.PI /
|
|
1146
1201
|
(Math.min(prevDelta, currentDelta) /
|
|
1147
1202
|
Math.max(prevDelta, currentDelta))), 2));
|
|
1148
|
-
const windowPenalty =
|
|
1149
|
-
|
|
1203
|
+
const windowPenalty = osuBase.MathUtils.clamp((Math.abs(prevDelta - currentDelta) - deltaDifferenceEpsilon) /
|
|
1204
|
+
deltaDifferenceEpsilon, 0, 1);
|
|
1150
1205
|
let effectiveRatio = windowPenalty * currentRatio;
|
|
1151
1206
|
if (firstDeltaSwitch) {
|
|
1152
|
-
if (prevDelta
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
++islandSize;
|
|
1207
|
+
if (Math.abs(prevDelta - currentDelta) <= deltaDifferenceEpsilon) {
|
|
1208
|
+
if (island.deltas.length < this.maxIslandSize) {
|
|
1209
|
+
// Island is still progressing.
|
|
1210
|
+
island.addDelta(currentDelta);
|
|
1157
1211
|
}
|
|
1158
1212
|
}
|
|
1159
1213
|
else {
|
|
1160
|
-
|
|
1161
|
-
|
|
1214
|
+
// BPM change is into slider, this is easy acc window.
|
|
1215
|
+
if (currentObject.object instanceof osuBase.Slider) {
|
|
1162
1216
|
effectiveRatio /= 8;
|
|
1163
1217
|
}
|
|
1164
|
-
|
|
1165
|
-
|
|
1218
|
+
// BPM change was from a slider, this is typically easier than circle -> circle.
|
|
1219
|
+
// Unintentional side effect is that bursts with kicksliders at the ends might
|
|
1220
|
+
// have lower difficulty than bursts without sliders.
|
|
1221
|
+
if (prevObject.object instanceof osuBase.Slider) {
|
|
1166
1222
|
effectiveRatio /= 4;
|
|
1167
1223
|
}
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
effectiveRatio
|
|
1224
|
+
// Repeated island polarity (2 -> 4, 3 -> 5).
|
|
1225
|
+
if (island.isSimilarPolarity(previousIsland)) {
|
|
1226
|
+
effectiveRatio *= 0.3;
|
|
1171
1227
|
}
|
|
1172
|
-
|
|
1173
|
-
|
|
1228
|
+
// Previous increase happened a note ago.
|
|
1229
|
+
// Albeit this is a 1/1 -> 1/2-1/4 type of transition, we don't want to buff this.
|
|
1230
|
+
if (lastDelta > prevDelta + deltaDifferenceEpsilon &&
|
|
1231
|
+
prevDelta > currentDelta + deltaDifferenceEpsilon) {
|
|
1232
|
+
effectiveRatio /= 8;
|
|
1233
|
+
}
|
|
1234
|
+
// Singletaps are easier to control.
|
|
1235
|
+
if (island.deltas.length === 1) {
|
|
1174
1236
|
effectiveRatio /= 2;
|
|
1175
1237
|
}
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1238
|
+
let islandFound = false;
|
|
1239
|
+
for (const [currentIsland, count] of islandCounts) {
|
|
1240
|
+
if (!island.equals(currentIsland)) {
|
|
1241
|
+
continue;
|
|
1242
|
+
}
|
|
1243
|
+
islandFound = true;
|
|
1244
|
+
let islandCount = count;
|
|
1245
|
+
if (previousIsland.equals(island)) {
|
|
1246
|
+
// Only add island to island counts if they're going one after another.
|
|
1247
|
+
++islandCount;
|
|
1248
|
+
islandCounts.set(currentIsland, islandCount);
|
|
1249
|
+
}
|
|
1250
|
+
// Repeated island (ex: triplet -> triplet).
|
|
1251
|
+
// Graph: https://www.desmos.com/calculator/pj7an56zwf
|
|
1252
|
+
effectiveRatio *= Math.min(1 / islandCount, Math.pow(1 / islandCount, 4 /
|
|
1253
|
+
(1 +
|
|
1254
|
+
Math.exp(10 - 0.165 * island.averageDelta))));
|
|
1255
|
+
break;
|
|
1181
1256
|
}
|
|
1257
|
+
if (!islandFound) {
|
|
1258
|
+
islandCounts.set(island, 1);
|
|
1259
|
+
}
|
|
1260
|
+
// Scale down the difficulty if the object is doubletappable.
|
|
1261
|
+
effectiveRatio *= 1 - prevObject.doubletapness * 0.75;
|
|
1182
1262
|
rhythmComplexitySum +=
|
|
1183
|
-
|
|
1184
|
-
currentHistoricalDecay
|
|
1185
|
-
Math.sqrt(4 + islandSize)) /
|
|
1186
|
-
2) *
|
|
1187
|
-
Math.sqrt(4 + previousIslandSize)) /
|
|
1188
|
-
2;
|
|
1263
|
+
Math.sqrt(effectiveRatio * startRatio) *
|
|
1264
|
+
currentHistoricalDecay;
|
|
1189
1265
|
startRatio = effectiveRatio;
|
|
1190
|
-
|
|
1191
|
-
if (prevDelta
|
|
1266
|
+
previousIsland = island;
|
|
1267
|
+
if (prevDelta + deltaDifferenceEpsilon < currentDelta) {
|
|
1192
1268
|
// We're slowing down, stop counting.
|
|
1193
1269
|
// If we're speeding up, this stays as is and we keep counting island size.
|
|
1194
1270
|
firstDeltaSwitch = false;
|
|
1195
1271
|
}
|
|
1196
|
-
|
|
1272
|
+
island = new Island(Math.trunc(currentDelta), deltaDifferenceEpsilon);
|
|
1197
1273
|
}
|
|
1198
1274
|
}
|
|
1199
|
-
else if (prevDelta >
|
|
1200
|
-
// We
|
|
1275
|
+
else if (prevDelta > deltaDifferenceEpsilon + currentDelta) {
|
|
1276
|
+
// We're speeding up.
|
|
1201
1277
|
// Begin counting island until we change speed again.
|
|
1202
1278
|
firstDeltaSwitch = true;
|
|
1279
|
+
// Reduce ratio if we're starting after a slider.
|
|
1280
|
+
if (prevObject.object instanceof osuBase.Slider) {
|
|
1281
|
+
effectiveRatio *= 0.3;
|
|
1282
|
+
}
|
|
1203
1283
|
startRatio = effectiveRatio;
|
|
1204
|
-
|
|
1284
|
+
island = new Island(Math.trunc(currentDelta), deltaDifferenceEpsilon);
|
|
1205
1285
|
}
|
|
1206
1286
|
}
|
|
1207
|
-
|
|
1208
|
-
const next = current.next(0);
|
|
1209
|
-
let doubletapness = 1;
|
|
1210
|
-
if (next) {
|
|
1211
|
-
const currentDeltaTime = Math.max(1, current.deltaTime);
|
|
1212
|
-
const nextDeltaTime = Math.max(1, next.deltaTime);
|
|
1213
|
-
const deltaDifference = Math.abs(nextDeltaTime - currentDeltaTime);
|
|
1214
|
-
const speedRatio = currentDeltaTime / Math.max(currentDeltaTime, deltaDifference);
|
|
1215
|
-
const windowRatio = Math.pow(Math.min(1, currentDeltaTime / (greatWindow * 2)), 2);
|
|
1216
|
-
doubletapness = Math.pow(speedRatio, 1 - windowRatio);
|
|
1217
|
-
}
|
|
1218
|
-
return (Math.sqrt(4 + rhythmComplexitySum * this.rhythmMultiplier * doubletapness) / 2);
|
|
1287
|
+
return Math.sqrt(4 + rhythmComplexitySum * this.rhythmMultiplier) / 2;
|
|
1219
1288
|
}
|
|
1220
1289
|
}
|
|
1290
|
+
DroidRhythmEvaluator.rhythmMultiplier = 1.2;
|
|
1291
|
+
DroidRhythmEvaluator.historyTimeMax = 4000;
|
|
1292
|
+
DroidRhythmEvaluator.maxIslandSize = 7;
|
|
1293
|
+
DroidRhythmEvaluator.historyObjectsMax = 24;
|
|
1221
1294
|
|
|
1222
1295
|
/**
|
|
1223
1296
|
* Represents the skill required to properly follow a beatmap's rhythm.
|
|
1224
1297
|
*/
|
|
1225
1298
|
class DroidRhythm extends DroidSkill {
|
|
1226
|
-
constructor(
|
|
1227
|
-
super(
|
|
1299
|
+
constructor() {
|
|
1300
|
+
super(...arguments);
|
|
1228
1301
|
this.reducedSectionCount = 5;
|
|
1229
1302
|
this.reducedSectionBaseline = 0.75;
|
|
1230
1303
|
this.strainDecayBase = 0.3;
|
|
1231
1304
|
this.starsPerDouble = 1.75;
|
|
1232
1305
|
this.currentRhythmStrain = 0;
|
|
1233
1306
|
this.currentRhythmMultiplier = 1;
|
|
1234
|
-
this.hitWindow = new osuBase.OsuHitWindow(overallDifficulty);
|
|
1235
1307
|
}
|
|
1236
1308
|
strainValueAt(current) {
|
|
1237
1309
|
this.currentRhythmMultiplier =
|
|
1238
|
-
DroidRhythmEvaluator.evaluateDifficultyOf(current
|
|
1310
|
+
DroidRhythmEvaluator.evaluateDifficultyOf(current);
|
|
1239
1311
|
this.currentRhythmStrain *= this.strainDecay(current.deltaTime);
|
|
1240
1312
|
this.currentRhythmStrain += this.currentRhythmMultiplier - 1;
|
|
1241
1313
|
return this.currentRhythmStrain;
|
|
@@ -1318,11 +1390,12 @@ class DroidVisualEvaluator {
|
|
|
1318
1390
|
// Invert the scaling factor to determine the true travel distance independent of circle size.
|
|
1319
1391
|
const pixelTravelDistance = current.object.lazyTravelDistance / scalingFactor;
|
|
1320
1392
|
const currentVelocity = pixelTravelDistance / current.travelTime;
|
|
1393
|
+
const spanTravelDistance = pixelTravelDistance / current.object.spanCount;
|
|
1321
1394
|
strain +=
|
|
1322
1395
|
// Reward sliders based on velocity, while also avoiding overbuffing extremely fast sliders.
|
|
1323
1396
|
Math.min(6, currentVelocity * 1.5) *
|
|
1324
1397
|
// Longer sliders require more reading.
|
|
1325
|
-
(
|
|
1398
|
+
(spanTravelDistance / 100);
|
|
1326
1399
|
let cumulativeStrainTime = 0;
|
|
1327
1400
|
// Reward for velocity changes based on last few sliders.
|
|
1328
1401
|
for (let i = 0; i < Math.min(current.index, 4); ++i) {
|
|
@@ -1334,14 +1407,15 @@ class DroidVisualEvaluator {
|
|
|
1334
1407
|
continue;
|
|
1335
1408
|
}
|
|
1336
1409
|
// Invert the scaling factor to determine the true travel distance independent of circle size.
|
|
1337
|
-
const
|
|
1338
|
-
const lastVelocity =
|
|
1410
|
+
const lastPixelTravelDistance = last.object.lazyTravelDistance / scalingFactor;
|
|
1411
|
+
const lastVelocity = lastPixelTravelDistance / last.travelTime;
|
|
1412
|
+
const lastSpanTravelDistance = lastPixelTravelDistance / last.object.spanCount;
|
|
1339
1413
|
strain +=
|
|
1340
1414
|
// Reward past sliders based on velocity changes, while also
|
|
1341
1415
|
// avoiding overbuffing extremely fast velocity changes.
|
|
1342
1416
|
Math.min(10, 2.5 * Math.abs(currentVelocity - lastVelocity)) *
|
|
1343
1417
|
// Longer sliders require more reading.
|
|
1344
|
-
(
|
|
1418
|
+
(lastSpanTravelDistance / 125) *
|
|
1345
1419
|
// Avoid overbuffing past sliders.
|
|
1346
1420
|
Math.min(1, 300 / cumulativeStrainTime);
|
|
1347
1421
|
}
|
|
@@ -1417,10 +1491,11 @@ class DroidDifficultyHitObject extends DifficultyHitObject {
|
|
|
1417
1491
|
* @param lastLastObject The hitobject before the last hitobject.
|
|
1418
1492
|
* @param difficultyHitObjects All difficulty hitobjects in the processed beatmap.
|
|
1419
1493
|
* @param clockRate The clock rate of the beatmap.
|
|
1494
|
+
* @param greatWindow The great window of the hitobject.
|
|
1420
1495
|
* @param isForceAR Whether force AR is enabled.
|
|
1421
1496
|
*/
|
|
1422
|
-
constructor(object, lastObject, lastLastObject, difficultyHitObjects, clockRate, isForceAR) {
|
|
1423
|
-
super(object, lastObject, lastLastObject, difficultyHitObjects, clockRate);
|
|
1497
|
+
constructor(object, lastObject, lastLastObject, difficultyHitObjects, clockRate, greatWindow, isForceAR) {
|
|
1498
|
+
super(object, lastObject, lastLastObject, difficultyHitObjects, clockRate, greatWindow);
|
|
1424
1499
|
/**
|
|
1425
1500
|
* The tap strain generated by the hitobject.
|
|
1426
1501
|
*/
|
|
@@ -1559,7 +1634,9 @@ class DroidDifficultyHitObject extends DifficultyHitObject {
|
|
|
1559
1634
|
Math.max(0, 1 - distance / (2.5 * this.object.radius)) *
|
|
1560
1635
|
(7.5 /
|
|
1561
1636
|
(1 +
|
|
1562
|
-
Math.exp(0.15 *
|
|
1637
|
+
Math.exp(0.15 *
|
|
1638
|
+
(Math.max(deltaTime, DifficultyHitObject.minDeltaTime) -
|
|
1639
|
+
75))));
|
|
1563
1640
|
}
|
|
1564
1641
|
}
|
|
1565
1642
|
|
|
@@ -1671,11 +1748,10 @@ class DroidDifficultyCalculator extends DifficultyCalculator {
|
|
|
1671
1748
|
* Calculates the tap star rating of the beatmap and stores it in this instance.
|
|
1672
1749
|
*/
|
|
1673
1750
|
calculateTap() {
|
|
1674
|
-
const
|
|
1675
|
-
const
|
|
1676
|
-
const tapSkillNoCheese = new DroidTap(this.mods, od, false);
|
|
1751
|
+
const tapSkillCheese = new DroidTap(this.mods, true);
|
|
1752
|
+
const tapSkillNoCheese = new DroidTap(this.mods, false);
|
|
1677
1753
|
this.calculateSkills(tapSkillCheese, tapSkillNoCheese);
|
|
1678
|
-
const tapSkillVibro = new DroidTap(this.mods,
|
|
1754
|
+
const tapSkillVibro = new DroidTap(this.mods, true, tapSkillCheese.relevantDeltaTime());
|
|
1679
1755
|
this.calculateSkills(tapSkillVibro);
|
|
1680
1756
|
this.postCalculateTap(tapSkillCheese, tapSkillVibro);
|
|
1681
1757
|
}
|
|
@@ -1683,7 +1759,7 @@ class DroidDifficultyCalculator extends DifficultyCalculator {
|
|
|
1683
1759
|
* Calculates the rhythm star rating of the beatmap and stores it in this instance.
|
|
1684
1760
|
*/
|
|
1685
1761
|
calculateRhythm() {
|
|
1686
|
-
const rhythmSkill = new DroidRhythm(this.mods
|
|
1762
|
+
const rhythmSkill = new DroidRhythm(this.mods);
|
|
1687
1763
|
this.calculateSkills(rhythmSkill);
|
|
1688
1764
|
this.postCalculateRhythm(rhythmSkill);
|
|
1689
1765
|
}
|
|
@@ -1743,7 +1819,7 @@ class DroidDifficultyCalculator extends DifficultyCalculator {
|
|
|
1743
1819
|
const flashlightSkillWithoutSliders = skills[6];
|
|
1744
1820
|
const visualSkill = skills[7];
|
|
1745
1821
|
const visualSkillWithoutSliders = skills[8];
|
|
1746
|
-
const tapSkillVibro = new DroidTap(this.mods,
|
|
1822
|
+
const tapSkillVibro = new DroidTap(this.mods, true, tapSkillCheese.relevantDeltaTime());
|
|
1747
1823
|
this.calculateSkills(tapSkillVibro);
|
|
1748
1824
|
this.postCalculateAim(aimSkill, aimSkillWithoutSliders);
|
|
1749
1825
|
this.postCalculateTap(tapSkillCheese, tapSkillVibro);
|
|
@@ -1771,19 +1847,19 @@ class DroidDifficultyCalculator extends DifficultyCalculator {
|
|
|
1771
1847
|
const difficultyObjects = [];
|
|
1772
1848
|
const { objects } = convertedBeatmap.hitObjects;
|
|
1773
1849
|
const difficultyAdjustMod = this.mods.find((m) => m instanceof osuBase.ModDifficultyAdjust);
|
|
1850
|
+
const greatWindow = new osuBase.OsuHitWindow(this.difficultyStatistics.overallDifficulty).hitWindowFor300();
|
|
1774
1851
|
for (let i = 0; i < objects.length; ++i) {
|
|
1775
|
-
const difficultyObject = new DroidDifficultyHitObject(objects[i], (_a = objects[i - 1]) !== null && _a !== void 0 ? _a : null, (_b = objects[i - 2]) !== null && _b !== void 0 ? _b : null, difficultyObjects, this.difficultyStatistics.overallSpeedMultiplier, (difficultyAdjustMod === null || difficultyAdjustMod === void 0 ? void 0 : difficultyAdjustMod.ar) !== undefined);
|
|
1852
|
+
const difficultyObject = new DroidDifficultyHitObject(objects[i], (_a = objects[i - 1]) !== null && _a !== void 0 ? _a : null, (_b = objects[i - 2]) !== null && _b !== void 0 ? _b : null, difficultyObjects, this.difficultyStatistics.overallSpeedMultiplier, greatWindow, (difficultyAdjustMod === null || difficultyAdjustMod === void 0 ? void 0 : difficultyAdjustMod.ar) !== undefined);
|
|
1776
1853
|
difficultyObject.computeProperties(this.difficultyStatistics.overallSpeedMultiplier, objects);
|
|
1777
1854
|
difficultyObjects.push(difficultyObject);
|
|
1778
1855
|
}
|
|
1779
1856
|
return difficultyObjects;
|
|
1780
1857
|
}
|
|
1781
1858
|
computeDifficultyStatistics(options) {
|
|
1782
|
-
var _a;
|
|
1783
1859
|
const { difficulty } = this.beatmap;
|
|
1784
1860
|
return osuBase.calculateDroidDifficultyStatistics({
|
|
1785
1861
|
circleSize: difficulty.cs,
|
|
1786
|
-
approachRate:
|
|
1862
|
+
approachRate: difficulty.ar,
|
|
1787
1863
|
overallDifficulty: difficulty.od,
|
|
1788
1864
|
healthDrain: difficulty.hp,
|
|
1789
1865
|
mods: this.mods,
|
|
@@ -1792,16 +1868,15 @@ class DroidDifficultyCalculator extends DifficultyCalculator {
|
|
|
1792
1868
|
});
|
|
1793
1869
|
}
|
|
1794
1870
|
createSkills() {
|
|
1795
|
-
const od = this.difficultyStatistics.overallDifficulty;
|
|
1796
1871
|
return [
|
|
1797
1872
|
new DroidAim(this.mods, true),
|
|
1798
1873
|
new DroidAim(this.mods, false),
|
|
1799
1874
|
// Tap skill depends on rhythm skill, so we put it first
|
|
1800
|
-
new DroidRhythm(this.mods
|
|
1875
|
+
new DroidRhythm(this.mods),
|
|
1801
1876
|
// Cheesability tap
|
|
1802
|
-
new DroidTap(this.mods,
|
|
1877
|
+
new DroidTap(this.mods, true),
|
|
1803
1878
|
// Non-cheesability tap
|
|
1804
|
-
new DroidTap(this.mods,
|
|
1879
|
+
new DroidTap(this.mods, false),
|
|
1805
1880
|
new DroidFlashlight(this.mods, true),
|
|
1806
1881
|
new DroidFlashlight(this.mods, false),
|
|
1807
1882
|
new DroidVisual(this.mods, true),
|
|
@@ -1817,7 +1892,9 @@ class DroidDifficultyCalculator extends DifficultyCalculator {
|
|
|
1817
1892
|
postCalculateAim(aimSkill, aimSkillWithoutSliders) {
|
|
1818
1893
|
this.strainPeaks.aimWithSliders = aimSkill.strainPeaks;
|
|
1819
1894
|
this.strainPeaks.aimWithoutSliders = aimSkillWithoutSliders.strainPeaks;
|
|
1820
|
-
this.attributes.aimDifficulty = this.
|
|
1895
|
+
this.attributes.aimDifficulty = this.mods.some((m) => m instanceof osuBase.ModAutopilot)
|
|
1896
|
+
? 0
|
|
1897
|
+
: this.starValue(aimSkill.difficultyValue());
|
|
1821
1898
|
if (this.aim) {
|
|
1822
1899
|
this.attributes.sliderFactor =
|
|
1823
1900
|
this.starValue(aimSkillWithoutSliders.difficultyValue()) /
|
|
@@ -1834,6 +1911,7 @@ class DroidDifficultyCalculator extends DifficultyCalculator {
|
|
|
1834
1911
|
* Calculates aim-related attributes.
|
|
1835
1912
|
*/
|
|
1836
1913
|
calculateAimAttributes() {
|
|
1914
|
+
this.attributes.difficultSliders = [];
|
|
1837
1915
|
const topDifficultSliders = [];
|
|
1838
1916
|
for (let i = 0; i < this.objects.length; ++i) {
|
|
1839
1917
|
const object = this.objects[i];
|
|
@@ -1954,10 +2032,12 @@ class DroidDifficultyCalculator extends DifficultyCalculator {
|
|
|
1954
2032
|
this.attributes.flashlightSliderFactor =
|
|
1955
2033
|
this.starValue(flashlightSkillWithoutSliders.difficultyValue()) / this.flashlight;
|
|
1956
2034
|
}
|
|
2035
|
+
if (this.mods.some((m) => m instanceof osuBase.ModAutopilot)) {
|
|
2036
|
+
this.attributes.flashlightDifficulty *= 0.3;
|
|
2037
|
+
}
|
|
1957
2038
|
if (this.mods.some((m) => m instanceof osuBase.ModRelax)) {
|
|
1958
2039
|
this.attributes.flashlightDifficulty *= 0.7;
|
|
1959
2040
|
}
|
|
1960
|
-
this.attributes.flashlightDifficulty = this.flashlight;
|
|
1961
2041
|
this.attributes.flashlightDifficultStrainCount =
|
|
1962
2042
|
flashlightSkill.countDifficultStrains();
|
|
1963
2043
|
}
|
|
@@ -1976,6 +2056,9 @@ class DroidDifficultyCalculator extends DifficultyCalculator {
|
|
|
1976
2056
|
this.starValue(visualSkillWithoutSliders.difficultyValue()) /
|
|
1977
2057
|
this.visual;
|
|
1978
2058
|
}
|
|
2059
|
+
if (this.mods.some((m) => m instanceof osuBase.ModAutopilot)) {
|
|
2060
|
+
this.attributes.visualDifficulty *= 0.8;
|
|
2061
|
+
}
|
|
1979
2062
|
this.attributes.visualDifficultStrainCount =
|
|
1980
2063
|
visualSkillWithSliders.countDifficultStrains();
|
|
1981
2064
|
}
|
|
@@ -2357,8 +2440,8 @@ class DroidPerformanceCalculator extends PerformanceCalculator {
|
|
|
2357
2440
|
aimValue *= this._aimSliderCheesePenalty;
|
|
2358
2441
|
// Scale the aim value with deviation.
|
|
2359
2442
|
aimValue *=
|
|
2360
|
-
1.
|
|
2361
|
-
Math.
|
|
2443
|
+
1.025 *
|
|
2444
|
+
Math.pow(osuBase.ErrorFunction.erf(25 / (Math.SQRT2 * this._deviation)), 0.475);
|
|
2362
2445
|
// OD 7 SS stays the same.
|
|
2363
2446
|
aimValue *= 0.98 + Math.pow(7, 2) / 2500;
|
|
2364
2447
|
return aimValue;
|
|
@@ -2370,8 +2453,9 @@ class DroidPerformanceCalculator extends PerformanceCalculator {
|
|
|
2370
2453
|
let tapValue = this.baseValue(this.difficultyAttributes.tapDifficulty);
|
|
2371
2454
|
tapValue *= this.calculateStrainBasedMissPenalty(this.difficultyAttributes.tapDifficultStrainCount);
|
|
2372
2455
|
// Scale the tap value with estimated full combo deviation.
|
|
2373
|
-
//
|
|
2374
|
-
|
|
2456
|
+
// Consider notes that are difficult to tap with respect to other notes, but
|
|
2457
|
+
// also cap the note count to prevent buffing filler patterns.
|
|
2458
|
+
tapValue *= this.calculateDeviationBasedLengthScaling(Math.min(this.difficultyAttributes.speedNoteCount, this.totalHits / 1.45));
|
|
2375
2459
|
// Normalize the deviation to 300 BPM.
|
|
2376
2460
|
const normalizedDeviation = this.tapDeviation *
|
|
2377
2461
|
Math.max(1, 50 / this.difficultyAttributes.averageSpeedDeltaTime);
|
|
@@ -2386,8 +2470,8 @@ class DroidPerformanceCalculator extends PerformanceCalculator {
|
|
|
2386
2470
|
((2 * 300) / averageBPM))));
|
|
2387
2471
|
// Scale the tap value with tap deviation.
|
|
2388
2472
|
tapValue *=
|
|
2389
|
-
1.
|
|
2390
|
-
Math.pow(osuBase.ErrorFunction.erf(20 / (Math.SQRT2 * adjustedDeviation)), 0.
|
|
2473
|
+
1.05 *
|
|
2474
|
+
Math.pow(osuBase.ErrorFunction.erf(20 / (Math.SQRT2 * adjustedDeviation)), 0.6);
|
|
2391
2475
|
// Additional scaling for tap value based on average BPM and how "vibroable" the beatmap is.
|
|
2392
2476
|
// Higher BPMs require more precise tapping. When the deviation is too high,
|
|
2393
2477
|
// it can be assumed that the player taps invariant to rhythm.
|
|
@@ -2412,7 +2496,7 @@ class DroidPerformanceCalculator extends PerformanceCalculator {
|
|
|
2412
2496
|
this.totalSuccessfulHits === 0) {
|
|
2413
2497
|
return 0;
|
|
2414
2498
|
}
|
|
2415
|
-
let accuracyValue =
|
|
2499
|
+
let accuracyValue = 650 * Math.exp(-0.1 * this._deviation);
|
|
2416
2500
|
const ncircles = this.difficultyAttributes.mods.some((m) => m instanceof osuBase.ModScoreV2)
|
|
2417
2501
|
? this.totalHits - this.difficultyAttributes.spinnerCount
|
|
2418
2502
|
: this.difficultyAttributes.hitCircleCount;
|
|
@@ -2465,8 +2549,8 @@ class DroidPerformanceCalculator extends PerformanceCalculator {
|
|
|
2465
2549
|
visualValue *= this._visualSliderCheesePenalty;
|
|
2466
2550
|
// Scale the visual value with deviation.
|
|
2467
2551
|
visualValue *=
|
|
2468
|
-
1.
|
|
2469
|
-
Math.pow(osuBase.ErrorFunction.erf(25 / (Math.SQRT2 * this._deviation)), 0.
|
|
2552
|
+
1.05 *
|
|
2553
|
+
Math.pow(osuBase.ErrorFunction.erf(25 / (Math.SQRT2 * this._deviation)), 0.775);
|
|
2470
2554
|
// OD 5 SS stays the same.
|
|
2471
2555
|
visualValue *= 0.98 + Math.pow(5, 2) / 2500;
|
|
2472
2556
|
return visualValue;
|
|
@@ -2482,8 +2566,9 @@ class DroidPerformanceCalculator extends PerformanceCalculator {
|
|
|
2482
2566
|
if (this.effectiveMissCount === 0) {
|
|
2483
2567
|
return 1;
|
|
2484
2568
|
}
|
|
2485
|
-
return (0.
|
|
2486
|
-
(this.effectiveMissCount /
|
|
2569
|
+
return (0.96 /
|
|
2570
|
+
(this.effectiveMissCount /
|
|
2571
|
+
(4 * Math.pow(Math.log(difficultStrainCount), 0.94)) +
|
|
2487
2572
|
1));
|
|
2488
2573
|
}
|
|
2489
2574
|
/**
|
|
@@ -2910,30 +2995,19 @@ class OsuSpeedEvaluator extends SpeedEvaluator {
|
|
|
2910
2995
|
* - and how easily they can be cheesed.
|
|
2911
2996
|
*
|
|
2912
2997
|
* @param current The current object.
|
|
2913
|
-
* @param greatWindow The great hit window of the current object.
|
|
2914
2998
|
*/
|
|
2915
|
-
static evaluateDifficultyOf(current
|
|
2999
|
+
static evaluateDifficultyOf(current) {
|
|
2916
3000
|
var _a;
|
|
2917
3001
|
if (current.object instanceof osuBase.Spinner) {
|
|
2918
3002
|
return 0;
|
|
2919
3003
|
}
|
|
2920
3004
|
const prev = current.previous(0);
|
|
2921
3005
|
let strainTime = current.strainTime;
|
|
2922
|
-
const greatWindowFull = greatWindow * 2;
|
|
2923
3006
|
// Nerf doubletappable doubles.
|
|
2924
|
-
const
|
|
2925
|
-
let doubletapness = 1;
|
|
2926
|
-
if (next) {
|
|
2927
|
-
const currentDeltaTime = Math.max(1, current.deltaTime);
|
|
2928
|
-
const nextDeltaTime = Math.max(1, next.deltaTime);
|
|
2929
|
-
const deltaDifference = Math.abs(nextDeltaTime - currentDeltaTime);
|
|
2930
|
-
const speedRatio = currentDeltaTime / Math.max(currentDeltaTime, deltaDifference);
|
|
2931
|
-
const windowRatio = Math.pow(Math.min(1, currentDeltaTime / greatWindowFull), 2);
|
|
2932
|
-
doubletapness = Math.pow(speedRatio, 1 - windowRatio);
|
|
2933
|
-
}
|
|
3007
|
+
const doubletapness = 1 - current.doubletapness;
|
|
2934
3008
|
// Cap deltatime to the OD 300 hitwindow.
|
|
2935
3009
|
// 0.93 is derived from making sure 260 BPM 1/4 OD8 streams aren't nerfed harshly, whilst 0.92 limits the effect of the cap.
|
|
2936
|
-
strainTime /= osuBase.MathUtils.clamp(strainTime /
|
|
3010
|
+
strainTime /= osuBase.MathUtils.clamp(strainTime / current.fullGreatWindow / 0.93, 0.92, 1);
|
|
2937
3011
|
let speedBonus = 1;
|
|
2938
3012
|
if (strainTime < this.minSpeedBonus) {
|
|
2939
3013
|
speedBonus +=
|
|
@@ -2964,7 +3038,7 @@ class OsuRhythmEvaluator extends RhythmEvaluator {
|
|
|
2964
3038
|
* @param current The current object.
|
|
2965
3039
|
* @param greatWindow The great hit window of the current object.
|
|
2966
3040
|
*/
|
|
2967
|
-
static evaluateDifficultyOf(current
|
|
3041
|
+
static evaluateDifficultyOf(current) {
|
|
2968
3042
|
if (current.object instanceof osuBase.Spinner) {
|
|
2969
3043
|
return 0;
|
|
2970
3044
|
}
|
|
@@ -2999,8 +3073,9 @@ class OsuRhythmEvaluator extends RhythmEvaluator {
|
|
|
2999
3073
|
Math.min(0.5, Math.pow(Math.sin(Math.PI /
|
|
3000
3074
|
(Math.min(prevDelta, currentDelta) /
|
|
3001
3075
|
Math.max(prevDelta, currentDelta))), 2));
|
|
3002
|
-
const windowPenalty = Math.min(1, Math.max(0, Math.abs(prevDelta - currentDelta) -
|
|
3003
|
-
|
|
3076
|
+
const windowPenalty = Math.min(1, Math.max(0, Math.abs(prevDelta - currentDelta) -
|
|
3077
|
+
current.fullGreatWindow * 0.3) /
|
|
3078
|
+
(current.fullGreatWindow * 0.3));
|
|
3004
3079
|
let effectiveRatio = windowPenalty * currentRatio;
|
|
3005
3080
|
if (firstDeltaSwitch) {
|
|
3006
3081
|
if (prevDelta <= 1.25 * currentDelta &&
|
|
@@ -3066,8 +3141,8 @@ class OsuRhythmEvaluator extends RhythmEvaluator {
|
|
|
3066
3141
|
* Represents the skill required to press keys or tap with regards to keeping up with the speed at which objects need to be hit.
|
|
3067
3142
|
*/
|
|
3068
3143
|
class OsuSpeed extends OsuSkill {
|
|
3069
|
-
constructor(
|
|
3070
|
-
super(
|
|
3144
|
+
constructor() {
|
|
3145
|
+
super(...arguments);
|
|
3071
3146
|
this.strainDecayBase = 0.3;
|
|
3072
3147
|
this.reducedSectionCount = 5;
|
|
3073
3148
|
this.reducedSectionBaseline = 0.75;
|
|
@@ -3076,7 +3151,6 @@ class OsuSpeed extends OsuSkill {
|
|
|
3076
3151
|
this.currentSpeedStrain = 0;
|
|
3077
3152
|
this.currentRhythm = 0;
|
|
3078
3153
|
this.skillMultiplier = 1375;
|
|
3079
|
-
this.greatWindow = new osuBase.OsuHitWindow(overallDifficulty).hitWindowFor300();
|
|
3080
3154
|
}
|
|
3081
3155
|
/**
|
|
3082
3156
|
* @param current The hitobject to calculate.
|
|
@@ -3084,9 +3158,9 @@ class OsuSpeed extends OsuSkill {
|
|
|
3084
3158
|
strainValueAt(current) {
|
|
3085
3159
|
this.currentSpeedStrain *= this.strainDecay(current.strainTime);
|
|
3086
3160
|
this.currentSpeedStrain +=
|
|
3087
|
-
OsuSpeedEvaluator.evaluateDifficultyOf(current
|
|
3161
|
+
OsuSpeedEvaluator.evaluateDifficultyOf(current) *
|
|
3088
3162
|
this.skillMultiplier;
|
|
3089
|
-
this.currentRhythm = OsuRhythmEvaluator.evaluateDifficultyOf(current
|
|
3163
|
+
this.currentRhythm = OsuRhythmEvaluator.evaluateDifficultyOf(current);
|
|
3090
3164
|
return this.currentSpeedStrain * this.currentRhythm;
|
|
3091
3165
|
}
|
|
3092
3166
|
calculateInitialStrain(time, current) {
|
|
@@ -3220,29 +3294,8 @@ class OsuFlashlight extends OsuSkill {
|
|
|
3220
3294
|
* Represents an osu!standard hit object with difficulty calculation values.
|
|
3221
3295
|
*/
|
|
3222
3296
|
class OsuDifficultyHitObject extends DifficultyHitObject {
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
// We will scale distances by this factor, so we can assume a uniform CircleSize among beatmaps.
|
|
3226
|
-
let scalingFactor = this.normalizedRadius / radius;
|
|
3227
|
-
// High circle size (small CS) bonus
|
|
3228
|
-
if (radius < this.radiusBuffThreshold) {
|
|
3229
|
-
scalingFactor *=
|
|
3230
|
-
1 + Math.min(this.radiusBuffThreshold - radius, 5) / 50;
|
|
3231
|
-
}
|
|
3232
|
-
return scalingFactor;
|
|
3233
|
-
}
|
|
3234
|
-
/**
|
|
3235
|
-
* Note: You **must** call `computeProperties` at some point due to how TypeScript handles
|
|
3236
|
-
* overridden properties (see [this](https://github.com/microsoft/TypeScript/issues/1617) GitHub issue).
|
|
3237
|
-
*
|
|
3238
|
-
* @param object The underlying hitobject.
|
|
3239
|
-
* @param lastObject The hitobject before this hitobject.
|
|
3240
|
-
* @param lastLastObject The hitobject before the last hitobject.
|
|
3241
|
-
* @param difficultyHitObjects All difficulty hitobjects in the processed beatmap.
|
|
3242
|
-
* @param clockRate The clock rate of the beatmap.
|
|
3243
|
-
*/
|
|
3244
|
-
constructor(object, lastObject, lastLastObject, difficultyHitObjects, clockRate) {
|
|
3245
|
-
super(object, lastObject, lastLastObject, difficultyHitObjects, clockRate);
|
|
3297
|
+
constructor() {
|
|
3298
|
+
super(...arguments);
|
|
3246
3299
|
/**
|
|
3247
3300
|
* The speed strain generated by the hitobject.
|
|
3248
3301
|
*/
|
|
@@ -3254,6 +3307,17 @@ class OsuDifficultyHitObject extends DifficultyHitObject {
|
|
|
3254
3307
|
this.radiusBuffThreshold = 30;
|
|
3255
3308
|
this.mode = osuBase.Modes.osu;
|
|
3256
3309
|
}
|
|
3310
|
+
get scalingFactor() {
|
|
3311
|
+
const radius = this.object.radius;
|
|
3312
|
+
// We will scale distances by this factor, so we can assume a uniform CircleSize among beatmaps.
|
|
3313
|
+
let scalingFactor = this.normalizedRadius / radius;
|
|
3314
|
+
// High circle size (small CS) bonus
|
|
3315
|
+
if (radius < this.radiusBuffThreshold) {
|
|
3316
|
+
scalingFactor *=
|
|
3317
|
+
1 + Math.min(this.radiusBuffThreshold - radius, 5) / 50;
|
|
3318
|
+
}
|
|
3319
|
+
return scalingFactor;
|
|
3320
|
+
}
|
|
3257
3321
|
}
|
|
3258
3322
|
|
|
3259
3323
|
/**
|
|
@@ -3319,7 +3383,7 @@ class OsuDifficultyCalculator extends DifficultyCalculator {
|
|
|
3319
3383
|
this.attributes.speedDifficulty = 0;
|
|
3320
3384
|
return;
|
|
3321
3385
|
}
|
|
3322
|
-
const speedSkill = new OsuSpeed(this.mods
|
|
3386
|
+
const speedSkill = new OsuSpeed(this.mods);
|
|
3323
3387
|
this.calculateSkills(speedSkill);
|
|
3324
3388
|
this.postCalculateSpeed(speedSkill);
|
|
3325
3389
|
}
|
|
@@ -3387,19 +3451,19 @@ class OsuDifficultyCalculator extends DifficultyCalculator {
|
|
|
3387
3451
|
var _a, _b;
|
|
3388
3452
|
const difficultyObjects = [];
|
|
3389
3453
|
const { objects } = convertedBeatmap.hitObjects;
|
|
3454
|
+
const greatWindow = new osuBase.OsuHitWindow(this.difficultyStatistics.overallDifficulty).hitWindowFor300();
|
|
3390
3455
|
for (let i = 0; i < objects.length; ++i) {
|
|
3391
|
-
const difficultyObject = new OsuDifficultyHitObject(objects[i], (_a = objects[i - 1]) !== null && _a !== void 0 ? _a : null, (_b = objects[i - 2]) !== null && _b !== void 0 ? _b : null, difficultyObjects, this.difficultyStatistics.overallSpeedMultiplier);
|
|
3456
|
+
const difficultyObject = new OsuDifficultyHitObject(objects[i], (_a = objects[i - 1]) !== null && _a !== void 0 ? _a : null, (_b = objects[i - 2]) !== null && _b !== void 0 ? _b : null, difficultyObjects, this.difficultyStatistics.overallSpeedMultiplier, greatWindow);
|
|
3392
3457
|
difficultyObject.computeProperties(this.difficultyStatistics.overallSpeedMultiplier, objects);
|
|
3393
3458
|
difficultyObjects.push(difficultyObject);
|
|
3394
3459
|
}
|
|
3395
3460
|
return difficultyObjects;
|
|
3396
3461
|
}
|
|
3397
3462
|
computeDifficultyStatistics(options) {
|
|
3398
|
-
var _a;
|
|
3399
3463
|
const { difficulty } = this.beatmap;
|
|
3400
3464
|
return osuBase.calculateOsuDifficultyStatistics({
|
|
3401
3465
|
circleSize: difficulty.cs,
|
|
3402
|
-
approachRate:
|
|
3466
|
+
approachRate: difficulty.ar,
|
|
3403
3467
|
overallDifficulty: difficulty.od,
|
|
3404
3468
|
healthDrain: difficulty.hp,
|
|
3405
3469
|
mods: options === null || options === void 0 ? void 0 : options.mods,
|
|
@@ -3410,7 +3474,7 @@ class OsuDifficultyCalculator extends DifficultyCalculator {
|
|
|
3410
3474
|
return [
|
|
3411
3475
|
new OsuAim(this.mods, true),
|
|
3412
3476
|
new OsuAim(this.mods, false),
|
|
3413
|
-
new OsuSpeed(this.mods
|
|
3477
|
+
new OsuSpeed(this.mods),
|
|
3414
3478
|
new OsuFlashlight(this.mods),
|
|
3415
3479
|
];
|
|
3416
3480
|
}
|
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.25",
|
|
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.25"
|
|
37
37
|
},
|
|
38
38
|
"publishConfig": {
|
|
39
39
|
"access": "public"
|
|
40
40
|
},
|
|
41
|
-
"gitHead": "
|
|
41
|
+
"gitHead": "2d6271ea24c787caad1d24c3d20dbeeaf450359e"
|
|
42
42
|
}
|
package/typings/index.d.ts
CHANGED
|
@@ -191,6 +191,10 @@ declare abstract class DifficultyHitObject {
|
|
|
191
191
|
* Adjusted end time of the hitobject, taking speed multiplier into account.
|
|
192
192
|
*/
|
|
193
193
|
readonly endTime: number;
|
|
194
|
+
/**
|
|
195
|
+
* The full great window of the hitobject.
|
|
196
|
+
*/
|
|
197
|
+
readonly fullGreatWindow: number;
|
|
194
198
|
/**
|
|
195
199
|
* Other hitobjects in the beatmap, including this hitobject.
|
|
196
200
|
*/
|
|
@@ -199,7 +203,10 @@ declare abstract class DifficultyHitObject {
|
|
|
199
203
|
protected readonly normalizedRadius = 50;
|
|
200
204
|
protected readonly maximumSliderRadius: number;
|
|
201
205
|
protected readonly assumedSliderRadius: number;
|
|
202
|
-
|
|
206
|
+
/**
|
|
207
|
+
* The lowest possible delta time value.
|
|
208
|
+
*/
|
|
209
|
+
static readonly minDeltaTime = 25;
|
|
203
210
|
private readonly lastObject;
|
|
204
211
|
private readonly lastLastObject;
|
|
205
212
|
/**
|
|
@@ -211,8 +218,9 @@ declare abstract class DifficultyHitObject {
|
|
|
211
218
|
* @param lastLastObject The hitobject before the last hitobject.
|
|
212
219
|
* @param difficultyHitObjects All difficulty hitobjects in the processed beatmap.
|
|
213
220
|
* @param clockRate The clock rate of the beatmap.
|
|
221
|
+
* @param greatWindow The great window of the hitobject.
|
|
214
222
|
*/
|
|
215
|
-
|
|
223
|
+
constructor(object: PlaceableHitObject, lastObject: PlaceableHitObject | null, lastLastObject: PlaceableHitObject | null, difficultyHitObjects: readonly DifficultyHitObject[], clockRate: number, greatWindow: number);
|
|
216
224
|
/**
|
|
217
225
|
* Computes the properties of this hitobject.
|
|
218
226
|
*
|
|
@@ -250,6 +258,13 @@ declare abstract class DifficultyHitObject {
|
|
|
250
258
|
* @returns The opacity of the hitobject at the given time.
|
|
251
259
|
*/
|
|
252
260
|
opacityAt(time: number, isHidden: boolean): number;
|
|
261
|
+
/**
|
|
262
|
+
* How possible is it to doubletap this object together with the next one and get perfect
|
|
263
|
+
* judgement in range from 0 to 1.
|
|
264
|
+
*
|
|
265
|
+
* A value closer to 1 indicates a higher possibility.
|
|
266
|
+
*/
|
|
267
|
+
get doubletapness(): number;
|
|
253
268
|
protected abstract get scalingFactor(): number;
|
|
254
269
|
protected setDistances(clockRate: number): void;
|
|
255
270
|
private calculateSliderCursorPosition;
|
|
@@ -521,6 +536,7 @@ declare abstract class DroidSkill extends StrainSkill {
|
|
|
521
536
|
* The strains of hitobjects.
|
|
522
537
|
*/
|
|
523
538
|
get objectStrains(): readonly number[];
|
|
539
|
+
private difficulty;
|
|
524
540
|
/**
|
|
525
541
|
* Returns the number of strains weighed against the top strain.
|
|
526
542
|
*
|
|
@@ -599,9 +615,10 @@ declare class DroidDifficultyHitObject extends DifficultyHitObject {
|
|
|
599
615
|
* @param lastLastObject The hitobject before the last hitobject.
|
|
600
616
|
* @param difficultyHitObjects All difficulty hitobjects in the processed beatmap.
|
|
601
617
|
* @param clockRate The clock rate of the beatmap.
|
|
618
|
+
* @param greatWindow The great window of the hitobject.
|
|
602
619
|
* @param isForceAR Whether force AR is enabled.
|
|
603
620
|
*/
|
|
604
|
-
constructor(object: PlaceableHitObject, lastObject: PlaceableHitObject | null, lastLastObject: PlaceableHitObject | null, difficultyHitObjects: readonly DifficultyHitObject[], clockRate: number, isForceAR: boolean);
|
|
621
|
+
constructor(object: PlaceableHitObject, lastObject: PlaceableHitObject | null, lastLastObject: PlaceableHitObject | null, difficultyHitObjects: readonly DifficultyHitObject[], clockRate: number, greatWindow: number, isForceAR: boolean);
|
|
605
622
|
computeProperties(clockRate: number, hitObjects: readonly PlaceableHitObject[]): void;
|
|
606
623
|
/**
|
|
607
624
|
* Determines whether this hitobject is considered overlapping with the hitobject before it.
|
|
@@ -1243,8 +1260,6 @@ declare class DroidRhythm extends DroidSkill {
|
|
|
1243
1260
|
protected readonly starsPerDouble = 1.75;
|
|
1244
1261
|
private currentRhythmStrain;
|
|
1245
1262
|
private currentRhythmMultiplier;
|
|
1246
|
-
private readonly hitWindow;
|
|
1247
|
-
constructor(mods: Mod[], overallDifficulty: number);
|
|
1248
1263
|
protected strainValueAt(current: DroidDifficultyHitObject): number;
|
|
1249
1264
|
protected calculateInitialStrain(time: number, current: DroidDifficultyHitObject): number;
|
|
1250
1265
|
protected getObjectStrain(): number;
|
|
@@ -1265,14 +1280,17 @@ declare abstract class RhythmEvaluator {
|
|
|
1265
1280
|
* An evaluator for calculating osu!droid Rhythm skill.
|
|
1266
1281
|
*/
|
|
1267
1282
|
declare abstract class DroidRhythmEvaluator extends RhythmEvaluator {
|
|
1283
|
+
protected static readonly rhythmMultiplier = 1.2;
|
|
1284
|
+
protected static readonly historyTimeMax = 4000;
|
|
1285
|
+
private static readonly maxIslandSize;
|
|
1286
|
+
private static readonly historyObjectsMax;
|
|
1268
1287
|
/**
|
|
1269
1288
|
* Calculates a rhythm multiplier for the difficulty of the tap associated
|
|
1270
1289
|
* with historic data of the current object.
|
|
1271
1290
|
*
|
|
1272
1291
|
* @param current The current object.
|
|
1273
|
-
* @param greatWindow The great hit window of the current object.
|
|
1274
1292
|
*/
|
|
1275
|
-
static evaluateDifficultyOf(current: DroidDifficultyHitObject
|
|
1293
|
+
static evaluateDifficultyOf(current: DroidDifficultyHitObject): number;
|
|
1276
1294
|
}
|
|
1277
1295
|
|
|
1278
1296
|
/**
|
|
@@ -1286,7 +1304,6 @@ declare class DroidTap extends DroidSkill {
|
|
|
1286
1304
|
private currentTapStrain;
|
|
1287
1305
|
private currentRhythmMultiplier;
|
|
1288
1306
|
private readonly skillMultiplier;
|
|
1289
|
-
private readonly greatWindow;
|
|
1290
1307
|
private readonly considerCheesability;
|
|
1291
1308
|
private readonly strainTimeCap?;
|
|
1292
1309
|
private readonly _objectDeltaTimes;
|
|
@@ -1294,7 +1311,7 @@ declare class DroidTap extends DroidSkill {
|
|
|
1294
1311
|
* The delta time of hitobjects.
|
|
1295
1312
|
*/
|
|
1296
1313
|
get objectDeltaTimes(): readonly number[];
|
|
1297
|
-
constructor(mods: Mod[],
|
|
1314
|
+
constructor(mods: Mod[], considerCheesability: boolean, strainTimeCap?: number);
|
|
1298
1315
|
/**
|
|
1299
1316
|
* The amount of notes that are relevant to the difficulty.
|
|
1300
1317
|
*/
|
|
@@ -1338,7 +1355,7 @@ declare abstract class DroidTapEvaluator extends SpeedEvaluator {
|
|
|
1338
1355
|
* @param considerCheesability Whether to consider cheesability.
|
|
1339
1356
|
* @param strainTimeCap The strain time to cap the object's strain time to.
|
|
1340
1357
|
*/
|
|
1341
|
-
static evaluateDifficultyOf(current: DroidDifficultyHitObject,
|
|
1358
|
+
static evaluateDifficultyOf(current: DroidDifficultyHitObject, considerCheesability: boolean, strainTimeCap?: number): number;
|
|
1342
1359
|
}
|
|
1343
1360
|
|
|
1344
1361
|
/**
|
|
@@ -1420,17 +1437,6 @@ declare class OsuDifficultyHitObject extends DifficultyHitObject {
|
|
|
1420
1437
|
private readonly radiusBuffThreshold;
|
|
1421
1438
|
protected readonly mode = Modes.osu;
|
|
1422
1439
|
protected get scalingFactor(): number;
|
|
1423
|
-
/**
|
|
1424
|
-
* Note: You **must** call `computeProperties` at some point due to how TypeScript handles
|
|
1425
|
-
* overridden properties (see [this](https://github.com/microsoft/TypeScript/issues/1617) GitHub issue).
|
|
1426
|
-
*
|
|
1427
|
-
* @param object The underlying hitobject.
|
|
1428
|
-
* @param lastObject The hitobject before this hitobject.
|
|
1429
|
-
* @param lastLastObject The hitobject before the last hitobject.
|
|
1430
|
-
* @param difficultyHitObjects All difficulty hitobjects in the processed beatmap.
|
|
1431
|
-
* @param clockRate The clock rate of the beatmap.
|
|
1432
|
-
*/
|
|
1433
|
-
constructor(object: PlaceableHitObject, lastObject: PlaceableHitObject | null, lastLastObject: PlaceableHitObject | null, difficultyHitObjects: readonly DifficultyHitObject[], clockRate: number);
|
|
1434
1440
|
}
|
|
1435
1441
|
|
|
1436
1442
|
/**
|
|
@@ -1634,7 +1640,7 @@ declare abstract class OsuRhythmEvaluator extends RhythmEvaluator {
|
|
|
1634
1640
|
* @param current The current object.
|
|
1635
1641
|
* @param greatWindow The great hit window of the current object.
|
|
1636
1642
|
*/
|
|
1637
|
-
static evaluateDifficultyOf(current: OsuDifficultyHitObject
|
|
1643
|
+
static evaluateDifficultyOf(current: OsuDifficultyHitObject): number;
|
|
1638
1644
|
}
|
|
1639
1645
|
|
|
1640
1646
|
/**
|
|
@@ -1649,8 +1655,6 @@ declare class OsuSpeed extends OsuSkill {
|
|
|
1649
1655
|
private currentSpeedStrain;
|
|
1650
1656
|
private currentRhythm;
|
|
1651
1657
|
private readonly skillMultiplier;
|
|
1652
|
-
private readonly greatWindow;
|
|
1653
|
-
constructor(mods: Mod[], overallDifficulty: number);
|
|
1654
1658
|
/**
|
|
1655
1659
|
* @param current The hitobject to calculate.
|
|
1656
1660
|
*/
|
|
@@ -1678,9 +1682,8 @@ declare abstract class OsuSpeedEvaluator extends SpeedEvaluator {
|
|
|
1678
1682
|
* - and how easily they can be cheesed.
|
|
1679
1683
|
*
|
|
1680
1684
|
* @param current The current object.
|
|
1681
|
-
* @param greatWindow The great hit window of the current object.
|
|
1682
1685
|
*/
|
|
1683
|
-
static evaluateDifficultyOf(current: OsuDifficultyHitObject
|
|
1686
|
+
static evaluateDifficultyOf(current: OsuDifficultyHitObject): number;
|
|
1684
1687
|
}
|
|
1685
1688
|
|
|
1686
1689
|
export { AimEvaluator, type CacheableDifficultyAttributes, type DifficultSlider, type DifficultyAttributes, type DifficultyCalculationOptions, DifficultyCalculator, DifficultyHitObject, DroidAim, DroidAimEvaluator, type DroidDifficultyAttributes, type DroidDifficultyCalculationOptions, DroidDifficultyCalculator, DroidDifficultyHitObject, DroidFlashlight, DroidFlashlightEvaluator, DroidPerformanceCalculator, DroidRhythm, DroidRhythmEvaluator, DroidTap, DroidTapEvaluator, DroidVisual, DroidVisualEvaluator, type ExtendedDroidDifficultyAttributes, FlashlightEvaluator, type HighStrainSection, OsuAim, OsuAimEvaluator, type OsuDifficultyAttributes, OsuDifficultyCalculator, OsuDifficultyHitObject, OsuFlashlight, OsuFlashlightEvaluator, OsuPerformanceCalculator, OsuRhythmEvaluator, OsuSpeed, OsuSpeedEvaluator, type PerformanceCalculationOptions, PerformanceCalculator, RhythmEvaluator, SpeedEvaluator, type StrainPeaks };
|