@rian8337/osu-difficulty-calculator 4.0.0-beta.9 → 4.0.0-beta.90

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.
@@ -1,43 +1,13 @@
1
- import { MapStats, Mod, PlaceableHitObject, Modes, Beatmap, HitObject, Accuracy } from '@rian8337/osu-base';
2
-
3
- /**
4
- * An evaluator for calculating aim skill.
5
- *
6
- * This class should be considered an "evaluating" class and not persisted.
7
- */
8
- declare abstract class AimEvaluator {
9
- protected static readonly wideAngleMultiplier: number;
10
- protected static readonly acuteAngleMultiplier: number;
11
- protected static readonly sliderMultiplier: number;
12
- protected static readonly velocityChangeMultiplier: number;
13
- /**
14
- * Calculates the bonus of wide angles.
15
- */
16
- protected static calculateWideAngleBonus(angle: number): number;
17
- /**
18
- * Calculates the bonus of acute angles.
19
- */
20
- protected static calculateAcuteAngleBonus(angle: number): number;
21
- }
22
-
23
- /**
24
- * The base of calculation options.
25
- */
26
- interface CalculationOptions {
27
- /**
28
- * Custom map statistics to apply custom speed multiplier as well as old statistics.
29
- */
30
- stats?: MapStats;
31
- }
1
+ import { ModMap, SerializedMod, PlaceableHitObject, Vector2, Modes, PlayableBeatmap, Mod, Beatmap, DroidPlayableBeatmap, Accuracy, OsuPlayableBeatmap } from '@rian8337/osu-base';
32
2
 
33
3
  /**
34
4
  * Holds data that can be used to calculate performance points.
35
5
  */
36
- interface DifficultyAttributes {
6
+ interface IDifficultyAttributes {
37
7
  /**
38
8
  * The mods which were applied to the beatmap.
39
9
  */
40
- mods: Mod[];
10
+ mods: ModMap;
41
11
  /**
42
12
  * The combined star rating of all skills.
43
13
  */
@@ -73,13 +43,7 @@ interface DifficultyAttributes {
73
43
  */
74
44
  clockRate: number;
75
45
  /**
76
- * The perceived approach rate inclusive of rate-adjusting mods (DT/HT/etc).
77
- *
78
- * Rate-adjusting mods don't directly affect the approach rate difficulty value, but have a perceived effect as a result of adjusting audio timing.
79
- */
80
- approachRate: number;
81
- /**
82
- * The perceived overall difficulty inclusive of rate-adjusting mods (DT/HT/etc), based on osu!standard judgement.
46
+ * The perceived overall difficulty **exclusive** of rate-adjusting mods (DT/HT/etc).
83
47
  *
84
48
  * Rate-adjusting mods don't directly affect the overall difficulty value, but have a perceived effect as a result of adjusting audio timing.
85
49
  */
@@ -96,22 +60,61 @@ interface DifficultyAttributes {
96
60
  * The number of spinners in the beatmap.
97
61
  */
98
62
  spinnerCount: number;
63
+ /**
64
+ * The number of sliders weighted by difficulty.
65
+ */
66
+ aimDifficultSliderCount: number;
67
+ /**
68
+ * The amount of strains that are considered difficult with respect to the aim skill.
69
+ */
70
+ aimDifficultStrainCount: number;
99
71
  }
100
72
 
101
73
  /**
102
- * Represents options for difficulty calculation.
74
+ * Represents difficulty attributes that can be cached.
103
75
  */
104
- interface DifficultyCalculationOptions extends CalculationOptions {
76
+ type CacheableDifficultyAttributes<T extends IDifficultyAttributes> = Omit<T, "mods" | "toCacheableAttributes"> & {
77
+ /**
78
+ * The mods which were applied to the beatmap.
79
+ */
80
+ mods: SerializedMod[];
81
+ };
82
+
83
+ /**
84
+ * Holds data that can be used to calculate performance points.
85
+ */
86
+ declare abstract class DifficultyAttributes implements IDifficultyAttributes {
87
+ mods: ModMap;
88
+ starRating: number;
89
+ maxCombo: number;
90
+ aimDifficulty: number;
91
+ flashlightDifficulty: number;
92
+ speedNoteCount: number;
93
+ sliderFactor: number;
94
+ clockRate: number;
95
+ overallDifficulty: number;
96
+ hitCircleCount: number;
97
+ sliderCount: number;
98
+ spinnerCount: number;
99
+ aimDifficultSliderCount: number;
100
+ aimDifficultStrainCount: number;
101
+ constructor(cacheableAttributes?: CacheableDifficultyAttributes<IDifficultyAttributes>);
105
102
  /**
106
- * The modifications to apply.
103
+ * Converts this `DifficultyAttributes` instance to an attribute structure that can be cached.
104
+ *
105
+ * @returns The cacheable attributes.
106
+ */
107
+ toCacheableAttributes(): CacheableDifficultyAttributes<this>;
108
+ /**
109
+ * Returns a string representation of the difficulty attributes.
107
110
  */
108
- mods?: Mod[];
111
+ toString(): string;
109
112
  }
110
113
 
111
114
  /**
112
- * Represents an osu!standard hit object with difficulty calculation values.
115
+ * Represents a hit object with difficulty calculation values.
113
116
  */
114
- declare class DifficultyHitObject {
117
+ declare abstract class DifficultyHitObject {
115
118
  /**
116
119
  * The underlying hitobject.
117
120
  */
@@ -121,19 +124,7 @@ declare class DifficultyHitObject {
121
124
  *
122
125
  * This is one less than the actual index of the hitobject in the beatmap.
123
126
  */
124
- index: number;
125
- /**
126
- * The preempt time of the hitobject.
127
- */
128
- baseTimePreempt: number;
129
- /**
130
- * Adjusted preempt time of the hitobject, taking speed multiplier into account.
131
- */
132
- timePreempt: number;
133
- /**
134
- * The fade in time of the hitobject.
135
- */
136
- timeFadeIn: number;
127
+ readonly index: number;
137
128
  /**
138
129
  * The aim strain generated by the hitobject if sliders are considered.
139
130
  */
@@ -142,41 +133,10 @@ declare class DifficultyHitObject {
142
133
  * The aim strain generated by the hitobject if sliders are not considered.
143
134
  */
144
135
  aimStrainWithoutSliders: number;
145
- /**
146
- * The tap strain generated by the hitobject.
147
- *
148
- * This is also used for osu!standard as opposed to "speed strain".
149
- */
150
- tapStrain: number;
151
- /**
152
- * The tap strain generated by the hitobject if `strainTime` isn't modified by
153
- * OD. This is used in three-finger detection.
154
- */
155
- originalTapStrain: number;
156
136
  /**
157
137
  * The rhythm multiplier generated by the hitobject. This is used to alter tap strain.
158
138
  */
159
139
  rhythmMultiplier: number;
160
- /**
161
- * The rhythm strain generated by the hitobject.
162
- */
163
- rhythmStrain: number;
164
- /**
165
- * The flashlight strain generated by the hitobject if sliders are considered.
166
- */
167
- flashlightStrainWithSliders: number;
168
- /**
169
- * The flashlight strain generated by the hitobject if sliders are not considered.
170
- */
171
- flashlightStrainWithoutSliders: number;
172
- /**
173
- * The visual strain generated by the hitobject if sliders are considered.
174
- */
175
- visualStrainWithSliders: number;
176
- /**
177
- * The visual strain generated by the hitobject if sliders are not considered.
178
- */
179
- visualStrainWithoutSliders: number;
180
140
  /**
181
141
  * The normalized distance from the "lazy" end position of the previous hitobject to the start position of this hitobject.
182
142
  *
@@ -208,6 +168,21 @@ declare class DifficultyHitObject {
208
168
  * The time taken to travel through `travelDistance`, with a minimum value of 25ms for sliders.
209
169
  */
210
170
  travelTime: number;
171
+ /**
172
+ * The position of the cursor at the point of completion of this hitobject if it was hit
173
+ * with as few movements as possible.
174
+ */
175
+ lazyEndPosition?: Vector2;
176
+ /**
177
+ * The distance travelled by the cursor upon completion of this hitobject if it was hit
178
+ * with as few movements as possible.
179
+ */
180
+ lazyTravelDistance: number;
181
+ /**
182
+ * The time taken by the cursor upon completion of this hitobject if it was hit with
183
+ * as few movements as possible.
184
+ */
185
+ lazyTravelTime: number;
211
186
  /**
212
187
  * Angle the player has to take to hit this hitobject.
213
188
  *
@@ -217,42 +192,66 @@ declare class DifficultyHitObject {
217
192
  /**
218
193
  * The amount of milliseconds elapsed between this hitobject and the last hitobject.
219
194
  */
220
- deltaTime: number;
195
+ readonly deltaTime: number;
221
196
  /**
222
197
  * The amount of milliseconds elapsed since the start time of the previous hitobject, with a minimum of 25ms.
223
198
  */
224
- strainTime: number;
199
+ readonly strainTime: number;
225
200
  /**
226
201
  * Adjusted start time of the hitobject, taking speed multiplier into account.
227
202
  */
228
- startTime: number;
203
+ readonly startTime: number;
229
204
  /**
230
205
  * Adjusted end time of the hitobject, taking speed multiplier into account.
231
206
  */
232
- endTime: number;
207
+ readonly endTime: number;
233
208
  /**
234
- * The note density of the hitobject.
209
+ * The full great window of the hitobject.
235
210
  */
236
- noteDensity: number;
211
+ readonly fullGreatWindow: number;
237
212
  /**
238
- * The overlapping factor of the hitobject.
239
- *
240
- * This is used to scale visual skill.
213
+ * Selective bonus for beatmaps with higher circle size.
241
214
  */
242
- overlappingFactor: number;
215
+ abstract get smallCircleBonus(): number;
243
216
  /**
244
- * Adjusted velocity of the hitobject, taking speed multiplier into account.
217
+ * Other hitobjects in the beatmap, including this hitobject.
245
218
  */
246
- velocity: number;
219
+ protected readonly hitObjects: readonly DifficultyHitObject[];
247
220
  /**
248
- * Other hitobjects in the beatmap, including this hitobject.
221
+ * The normalized radius of the hitobject.
249
222
  */
250
- private readonly hitObjects;
223
+ static readonly normalizedRadius: number;
224
+ /**
225
+ * The normalized diameter of the hitobject.
226
+ */
227
+ static get normalizedDiameter(): number;
228
+ protected abstract readonly mode: Modes;
229
+ protected readonly maximumSliderRadius: number;
230
+ protected readonly assumedSliderRadius: number;
231
+ /**
232
+ * The lowest possible delta time value.
233
+ */
234
+ static readonly minDeltaTime = 25;
235
+ private readonly lastObject;
236
+ private readonly lastDifficultyObject;
237
+ private readonly lastLastDifficultyObject;
251
238
  /**
239
+ * Note: You **must** call `computeProperties` at some point due to how TypeScript handles
240
+ * overridden properties (see [this](https://github.com/microsoft/TypeScript/issues/1617) GitHub issue).
241
+ *
252
242
  * @param object The underlying hitobject.
253
- * @param hitObjects All difficulty hitobjects in the processed beatmap.
243
+ * @param lastObject The hitobject before this hitobject.
244
+ * @param difficultyHitObjects All difficulty hitobjects in the processed beatmap.
245
+ * @param clockRate The clock rate of the beatmap.
246
+ * @param index The index of this hitobject in the list of all hitobjects.
247
+ */
248
+ constructor(object: PlaceableHitObject, lastObject: PlaceableHitObject | null, difficultyHitObjects: readonly DifficultyHitObject[], clockRate: number, index: number);
249
+ /**
250
+ * Computes the properties of this hitobject.
251
+ *
252
+ * @param clockRate The clock rate of the beatmap.
254
253
  */
255
- constructor(object: PlaceableHitObject, hitObjects: DifficultyHitObject[]);
254
+ computeProperties(clockRate: number): void;
256
255
  /**
257
256
  * Gets the difficulty hitobject at a specific index with respect to the current
258
257
  * difficulty hitobject's index.
@@ -263,7 +262,7 @@ declare class DifficultyHitObject {
263
262
  * @returns The difficulty hitobject at the index with respect to the current
264
263
  * difficulty hitobject's index, `null` if the index is out of range.
265
264
  */
266
- previous(backwardsIndex: number): DifficultyHitObject | null;
265
+ previous(backwardsIndex: number): this | null;
267
266
  /**
268
267
  * Gets the difficulty hitobject at a specific index with respect to the current
269
268
  * difficulty hitobject's index.
@@ -274,26 +273,25 @@ declare class DifficultyHitObject {
274
273
  * @returns The difficulty hitobject at the index with respect to the current
275
274
  * difficulty hitobject's index, `null` if the index is out of range.
276
275
  */
277
- next(forwardsIndex: number): DifficultyHitObject | null;
276
+ next(forwardsIndex: number): this | null;
278
277
  /**
279
278
  * Calculates the opacity of the hitobject at a given time.
280
279
  *
281
280
  * @param time The time to calculate the hitobject's opacity at.
282
- * @param isHidden Whether Hidden mod is used.
283
- * @param mode The gamemode to calculate the opacity for.
281
+ * @param mods The mods used.
284
282
  * @returns The opacity of the hitobject at the given time.
285
283
  */
286
- opacityAt(time: number, isHidden: boolean, mode: Modes): number;
284
+ opacityAt(time: number, mods: ModMap): number;
287
285
  /**
288
- * Determines whether this hitobject is considered overlapping with the hitobject before it.
289
- *
290
- * Keep in mind that "overlapping" in this case is overlapping to the point where both hitobjects
291
- * can be hit with just a single tap in osu!droid.
286
+ * How possible is it to doubletap this object together with the next one and get perfect
287
+ * judgement in range from 0 to 1.
292
288
  *
293
- * @param considerDistance Whether to consider the distance between both hitobjects.
294
- * @returns Whether the hitobject is considered overlapping.
289
+ * A value closer to 1 indicates a higher possibility.
295
290
  */
296
- isOverlapping(considerDistance: boolean): boolean;
291
+ getDoubletapness(nextObj: this | null): number;
292
+ protected setDistances(clockRate: number): void;
293
+ private calculateSliderCursorPosition;
294
+ private getEndCursorPosition;
297
295
  }
298
296
 
299
297
  /**
@@ -305,8 +303,8 @@ declare abstract class Skill {
305
303
  /**
306
304
  * The mods that this skill processes.
307
305
  */
308
- protected readonly mods: Mod[];
309
- constructor(mods: Mod[]);
306
+ protected readonly mods: ModMap;
307
+ constructor(mods: ModMap);
310
308
  /**
311
309
  * Processes a hitobject.
312
310
  *
@@ -320,18 +318,32 @@ declare abstract class Skill {
320
318
  }
321
319
 
322
320
  /**
323
- * Used to processes strain values of difficulty hitobjects, keep track of strain levels caused by the processed objects
324
- * and to calculate a final difficulty value representing the difficulty of hitting all the processed objects.
321
+ * Represents the strain peaks of various calculated difficulties.
325
322
  */
326
- declare abstract class StrainSkill extends Skill {
323
+ interface StrainPeaks {
324
+ /**
325
+ * The strain peaks of aim difficulty if sliders are considered.
326
+ */
327
+ aimWithSliders: readonly number[];
327
328
  /**
328
- * The strain of currently calculated hitobject.
329
+ * The strain peaks of aim difficulty if sliders are not considered.
330
+ */
331
+ aimWithoutSliders: readonly number[];
332
+ /**
333
+ * The strain peaks of speed difficulty.
329
334
  */
330
- protected currentStrain: number;
335
+ speed: readonly number[];
331
336
  /**
332
- * The current section's strain peak.
337
+ * The strain peaks of flashlight difficulty.
333
338
  */
334
- protected currentSectionPeak: number;
339
+ flashlight: readonly number[];
340
+ }
341
+
342
+ /**
343
+ * Used to processes strain values of difficulty hitobjects, keep track of strain levels caused by the processed objects
344
+ * and to calculate a final difficulty value representing the difficulty of hitting all the processed objects.
345
+ */
346
+ declare abstract class StrainSkill extends Skill {
335
347
  /**
336
348
  * Strain peaks are stored here.
337
349
  */
@@ -345,247 +357,250 @@ declare abstract class StrainSkill extends Skill {
345
357
  * The baseline multiplier applied to the section with the biggest strain.
346
358
  */
347
359
  protected abstract readonly reducedSectionBaseline: number;
348
- /**
349
- * Strain values are multiplied by this number for the given skill. Used to balance the value of different skills between each other.
350
- */
351
- protected abstract readonly skillMultiplier: number;
352
360
  /**
353
361
  * Determines how quickly strain decays for the given skill.
354
362
  *
355
363
  * For example, a value of 0.15 indicates that strain decays to 15% of its original value in one second.
356
364
  */
357
365
  protected abstract readonly strainDecayBase: number;
366
+ protected readonly _objectStrains: number[];
367
+ protected difficulty: number;
368
+ /**
369
+ * The strains of hitobjects.
370
+ */
371
+ get objectStrains(): readonly number[];
358
372
  private readonly sectionLength;
373
+ private currentStrain;
374
+ private currentSectionPeak;
359
375
  private currentSectionEnd;
360
- private isFirstObject;
361
376
  /**
362
- * Calculates the strain value of a hitobject and stores the value in it. This value is affected by previously processed objects.
377
+ * Converts a difficulty value to a performance value.
363
378
  *
364
- * @param current The hitobject to process.
379
+ * @param difficulty The difficulty value to convert.
380
+ * @returns The performance value.
365
381
  */
382
+ static difficultyToPerformance(difficulty: number): number;
366
383
  process(current: DifficultyHitObject): void;
367
384
  /**
368
385
  * Saves the current peak strain level to the list of strain peaks, which will be used to calculate an overall difficulty.
369
386
  */
370
387
  saveCurrentPeak(): void;
388
+ /**
389
+ * Returns the number of strains weighed against the top strain.
390
+ *
391
+ * The result is scaled by clock rate as it affects the total number of strains.
392
+ */
393
+ countTopWeightedStrains(): number;
371
394
  /**
372
395
  * Calculates strain decay for a specified time frame.
373
396
  *
374
397
  * @param ms The time frame to calculate.
375
398
  */
376
399
  protected strainDecay(ms: number): number;
400
+ /**
401
+ * Calculates the starting time of a strain section at an object.
402
+ *
403
+ * @param current The object at which the strain section starts.
404
+ * @returns The start time of the strain section.
405
+ */
406
+ protected calculateCurrentSectionStart(current: DifficultyHitObject): number;
377
407
  /**
378
408
  * Calculates the strain value at a hitobject.
409
+ *
410
+ * @param current The hitobject to calculate.
379
411
  */
380
412
  protected abstract strainValueAt(current: DifficultyHitObject): number;
381
413
  /**
382
414
  * Saves the current strain to a hitobject.
383
415
  */
384
416
  protected abstract saveToHitObject(current: DifficultyHitObject): void;
417
+ /**
418
+ * Retrieves the peak strain at a point in time.
419
+ *
420
+ * @param time The time to retrieve the peak strain at.
421
+ * @param current The current hit object.
422
+ * @returns The peak strain.
423
+ */
424
+ protected abstract calculateInitialStrain(time: number, current: DifficultyHitObject): number;
385
425
  /**
386
426
  * Sets the initial strain level for a new section.
387
427
  *
388
- * @param offset The beginning of the new section in milliseconds, adjusted by speed multiplier.
428
+ * @param time The beginning of the new section in milliseconds.
389
429
  * @param current The current hitobject.
390
430
  */
391
431
  private startNewSectionFrom;
392
432
  }
393
433
 
394
434
  /**
395
- * Represents the strain peaks of various calculated difficulties.
396
- */
397
- interface StrainPeaks {
398
- /**
399
- * The strain peaks of aim difficulty if sliders are considered.
400
- */
401
- aimWithSliders: number[];
402
- /**
403
- * The strain peaks of aim difficulty if sliders are not considered.
404
- */
405
- aimWithoutSliders: number[];
406
- /**
407
- * The strain peaks of speed difficulty.
408
- */
409
- speed: number[];
410
- /**
411
- * The strain peaks of flashlight difficulty.
412
- */
413
- flashlight: number[];
414
- }
415
-
416
- /**
417
- * The base of difficulty calculators.
435
+ * The base of a difficulty calculator.
418
436
  */
419
- declare abstract class DifficultyCalculator {
437
+ declare abstract class DifficultyCalculator<TBeatmap extends PlayableBeatmap, THitObject extends DifficultyHitObject, TAttributes extends DifficultyAttributes> {
420
438
  /**
421
- * The calculated beatmap.
439
+ * `Mod`s that adjust the difficulty of a beatmap.
422
440
  */
423
- readonly beatmap: Beatmap;
441
+ protected readonly difficultyAdjustmentMods: (typeof Mod)[];
424
442
  /**
425
- * The difficulty objects of the beatmap.
426
- */
427
- readonly objects: DifficultyHitObject[];
428
- /**
429
- * The modifications applied.
430
- */
431
- mods: Mod[];
432
- /**
433
- * The total star rating of the beatmap.
434
- */
435
- total: number;
436
- /**
437
- * The map statistics of the beatmap after modifications are applied.
438
- */
439
- stats: MapStats;
440
- /**
441
- * The strain peaks of various calculated difficulties.
442
- */
443
- readonly strainPeaks: StrainPeaks;
444
- /**
445
- * Holds data that can be used to calculate performance points.
446
- */
447
- abstract readonly attributes: DifficultyAttributes;
448
- protected abstract readonly difficultyMultiplier: number;
449
- protected abstract readonly mode: Modes;
450
- /**
451
- * Constructs a new instance of the calculator.
443
+ * Retains `Mod`s that adjust a beatmap's difficulty from the specified mods.
452
444
  *
453
- * @param beatmap The beatmap to calculate. This beatmap will be deep-cloned to prevent reference changes.
445
+ * @param mods The mods to retain the difficulty adjustment mods from.
446
+ * @returns The retained difficulty adjustment mods.
454
447
  */
455
- constructor(beatmap: Beatmap);
448
+ abstract retainDifficultyAdjustmentMods(mods: Mod[]): Mod[];
456
449
  /**
457
- * Calculates the star rating of the specified beatmap.
458
- *
459
- * The beatmap is analyzed in chunks of `sectionLength` duration.
460
- * For each chunk the highest hitobject strains are added to
461
- * a list which is then collapsed into a weighted sum, much
462
- * like scores are weighted on a user's profile.
463
- *
464
- * For subsequent chunks, the initial max strain is calculated
465
- * by decaying the previous hitobject's strain until the
466
- * beginning of the new chunk.
450
+ * Calculates the difficulty of a `Beatmap` with specific `Mod`s.
467
451
  *
468
- * @param options Options for the difficulty calculation.
469
- * @returns The current instance.
452
+ * @param beatmap The `Beatmap` whose difficulty is to be calculated.
453
+ * @param mods The `Mod`s to apply to the beatmap. Defaults to No Mod.
454
+ * @returns A `DifficultyAttributes` object describing the difficulty of the `Beatmap`.
470
455
  */
471
- calculate(options?: DifficultyCalculationOptions): this;
456
+ calculate(beatmap: Beatmap, mods?: ModMap): TAttributes;
472
457
  /**
473
- * Generates difficulty hitobjects for this calculator.
458
+ * Obtains the strain peaks of a `PlayableBeatmap`.
459
+ *
460
+ * @param beatmap The `PlayableBeatmap` whose strain peaks are to be calculated.
461
+ * @returns The strain peaks of the `PlayableBeatmap`.
474
462
  */
475
- generateDifficultyHitObjects(): void;
463
+ calculateStrainPeaks(beatmap: TBeatmap): StrainPeaks;
476
464
  /**
477
- * Performs some pre-processing before proceeding with difficulty calculation.
465
+ * Obtains the strain peaks of a `Beatmap` with specific `Mod`s.
466
+ *
467
+ * @param beatmap The `Beatmap` whose strain peaks are to be calculated.
468
+ * @param mods The `Mod`s to apply to the beatmap. Defaults to No Mod.
469
+ * @returns The strain peaks of the `Beatmap`.
478
470
  */
479
- protected preProcess(): void;
471
+ calculateStrainPeaks(beatmap: Beatmap, mods?: ModMap): StrainPeaks;
480
472
  /**
481
- * Calculates the skills provided.
473
+ * Creates the `Skill`s to calculate the difficulty of a `PlayableBeatmap`.
482
474
  *
483
- * @param skills The skills to calculate.
475
+ * @param beatmap The `PlayableBeatmap` whose difficulty will be calculated.
476
+ * @return The `Skill`s.
484
477
  */
485
- protected calculateSkills(...skills: StrainSkill[]): void;
478
+ protected abstract createSkills(beatmap: TBeatmap): Skill[];
486
479
  /**
487
- * Calculates the total star rating of the beatmap and stores it in this instance.
480
+ * Creates the `Skill`s to obtain the strain peaks of a `PlayableBeatmap`.
481
+ *
482
+ * @param beatmap
488
483
  */
489
- abstract calculateTotal(): void;
484
+ protected abstract createStrainPeakSkills(beatmap: TBeatmap): StrainSkill[];
490
485
  /**
491
- * Calculates every star rating of the beatmap and stores it in this instance.
486
+ * Creates difficulty hitobjects for this calculator.
487
+ *
488
+ * @param beatmap The beatmap to generate difficulty hitobjects from.
489
+ * @returns The generated difficulty hitobjects.
492
490
  */
493
- abstract calculateAll(): void;
491
+ protected abstract createDifficultyHitObjects(beatmap: TBeatmap): THitObject[];
494
492
  /**
495
- * Returns a string representative of the class.
493
+ * Creates a `DifficultyAttributes` object to describe a `PlayableBeatmap`'s difficulty.
494
+ *
495
+ * @param beatmap The `PlayableBeatmap` whose difficulty was calculated.
496
+ * @param skills The `Skill`s which processed the `PlayableBeatmap`.
497
+ * @param objects The `DifficultyHitObject`s which were processed.
498
+ * @returns The `DifficultyAttributes` object.
496
499
  */
497
- abstract toString(): string;
500
+ protected abstract createDifficultyAttributes(beatmap: TBeatmap, skills: Skill[], objects: THitObject[]): TAttributes;
498
501
  /**
499
- * Creates skills to be calculated.
502
+ * Constructs a `PlayableBeatmap` from a `Beatmap` with specific `Mod`s.
503
+ *
504
+ * @param beatmap The `Beatmap` to create a `PlayableBeatmap` from.
505
+ * @param mods The `Mod`s to apply to the `Beatmap`.
506
+ * @returns The `PlayableBeatmap`.
500
507
  */
501
- protected abstract createSkills(): StrainSkill[];
508
+ protected abstract createPlayableBeatmap(beatmap: Beatmap, mods?: ModMap): TBeatmap;
509
+ }
510
+
511
+ /**
512
+ * Represents a slider that is considered difficult.
513
+ *
514
+ * This structure is a part of difficulty attributes and can be cached.
515
+ */
516
+ interface DifficultSlider {
502
517
  /**
503
- * Populates the stored difficulty attributes with necessary data.
518
+ * The index of the slider in the beatmap.
504
519
  */
505
- protected populateDifficultyAttributes(): void;
520
+ readonly index: number;
506
521
  /**
507
- * Calculates the star rating value of a difficulty.
522
+ * The difficulty rating of this slider compared to other sliders, based on the velocity of the slider.
508
523
  *
509
- * @param difficulty The difficulty to calculate.
510
- */
511
- protected starValue(difficulty: number): number;
512
- /**
513
- * Calculates the base performance value of a difficulty rating.
524
+ * A value closer to 1 indicates that this slider is more difficult compared to most sliders.
514
525
  *
515
- * @param rating The difficulty rating.
526
+ * A value closer to 0 indicates that this slider is easier compared to most sliders.
516
527
  */
517
- protected basePerformanceValue(rating: number): number;
528
+ readonly difficultyRating: number;
518
529
  }
519
530
 
520
531
  /**
521
- * A converter used to convert normal hitobjects into difficulty hitobjects.
532
+ * Represents an osu!droid hit object with difficulty calculation values.
522
533
  */
523
- declare class DifficultyHitObjectCreator {
534
+ declare class DroidDifficultyHitObject extends DifficultyHitObject {
535
+ /**
536
+ * The tap strain generated by the hitobject.
537
+ */
538
+ tapStrain: number;
524
539
  /**
525
- * The threshold for small circle buff for osu!droid.
540
+ * The tap strain generated by the hitobject if `strainTime` isn't modified by
541
+ * OD. This is used in three-finger detection.
526
542
  */
527
- private readonly DROID_CIRCLESIZE_BUFF_THRESHOLD;
543
+ originalTapStrain: number;
528
544
  /**
529
- * The threshold for small circle buff for osu!standard.
545
+ * The rhythm strain generated by the hitobject.
530
546
  */
531
- private readonly PC_CIRCLESIZE_BUFF_THRESHOLD;
547
+ rhythmStrain: number;
532
548
  /**
533
- * The gamemode this creator is creating for.
549
+ * The flashlight strain generated by the hitobject if sliders are considered.
534
550
  */
535
- private mode;
551
+ flashlightStrainWithSliders: number;
536
552
  /**
537
- * The base normalized radius of hitobjects.
553
+ * The flashlight strain generated by the hitobject if sliders are not considered.
538
554
  */
539
- private readonly normalizedRadius;
540
- private maximumSliderRadius;
541
- private readonly assumedSliderRadius;
542
- private readonly minDeltaTime;
555
+ flashlightStrainWithoutSliders: number;
543
556
  /**
544
- * Generates difficulty hitobjects for difficulty calculation.
557
+ * The reading difficulty generated by the hitobject.
545
558
  */
546
- generateDifficultyObjects(params: {
547
- objects: readonly HitObject[];
548
- circleSize: number;
549
- mods: Mod[];
550
- speedMultiplier: number;
551
- mode: Modes;
552
- preempt?: number;
553
- }): DifficultyHitObject[];
559
+ readingDifficulty: number;
554
560
  /**
555
- * Calculates a slider's cursor position.
561
+ * The note density of the hitobject.
556
562
  */
557
- private calculateSliderCursorPosition;
563
+ noteDensity: number;
558
564
  /**
559
- * Gets the scaling factor of a radius.
565
+ * The overlapping factor of the hitobject.
560
566
  *
561
- * @param radius The radius to get the scaling factor from.
567
+ * This is used to scale visual skill.
562
568
  */
563
- private getScalingFactor;
569
+ overlappingFactor: number;
564
570
  /**
565
- * Returns the end cursor position of a hitobject.
571
+ * Adjusted preempt time of the hitobject, taking speed multiplier into account.
566
572
  */
567
- private getEndCursorPosition;
568
- private applyToOverlappingFactor;
569
- }
570
-
571
- /**
572
- * Represents a slider that is considered difficult.
573
- *
574
- * This structure is a part of difficulty attributes and can be cached.
575
- */
576
- interface DifficultSlider {
573
+ readonly timePreempt: number;
574
+ private readonly radiusBuffThreshold;
575
+ protected readonly mode = Modes.droid;
576
+ protected readonly maximumSliderRadius: number;
577
+ get smallCircleBonus(): number;
577
578
  /**
578
- * The index of the slider in the beatmap.
579
+ * Note: You **must** call `computeProperties` at some point due to how TypeScript handles
580
+ * overridden properties (see [this](https://github.com/microsoft/TypeScript/issues/1617) GitHub issue).
581
+ *
582
+ * @param object The underlying hitobject.
583
+ * @param lastObject The hitobject before this hitobject.
584
+ * @param difficultyHitObjects All difficulty hitobjects in the processed beatmap.
585
+ * @param clockRate The clock rate of the beatmap.
579
586
  */
580
- readonly index: number;
587
+ constructor(object: PlaceableHitObject, lastObject: PlaceableHitObject | null, difficultyHitObjects: readonly DifficultyHitObject[], clockRate: number, index: number);
588
+ opacityAt(time: number, mods: ModMap): number;
589
+ previous(backwardsIndex: number): this | null;
590
+ next(forwardsIndex: number): this | null;
581
591
  /**
582
- * The difficulty rating of this slider compared to other sliders, based on the velocity of the slider.
592
+ * Determines whether this hitobject is considered overlapping with the hitobject before it.
583
593
  *
584
- * A value closer to 1 indicates that this slider is more difficult compared to most sliders.
594
+ * Keep in mind that "overlapping" in this case is overlapping to the point where both hitobjects
595
+ * can be hit with just a single tap in osu!droid.
585
596
  *
586
- * A value closer to 0 indicates that this slider is easier compared to most sliders.
597
+ * In the case of sliders, it is considered overlapping if all nested hitobjects can be hit with
598
+ * one aim motion.
599
+ *
600
+ * @param considerDistance Whether to consider the distance between both hitobjects.
601
+ * @returns Whether the hitobject is considered overlapping.
587
602
  */
588
- readonly difficultyRating: number;
603
+ isOverlapping(considerDistance: boolean): boolean;
589
604
  }
590
605
 
591
606
  /**
@@ -597,41 +612,56 @@ declare abstract class DroidSkill extends StrainSkill {
597
612
  * The bonus multiplier that is given for a sequence of notes of equal difficulty.
598
613
  */
599
614
  protected abstract readonly starsPerDouble: number;
615
+ process(current: DifficultyHitObject): void;
600
616
  difficultyValue(): number;
617
+ /**
618
+ * Gets the strain of a hitobject.
619
+ *
620
+ * @param current The hitobject to get the strain from.
621
+ * @returns The strain of the hitobject.
622
+ */
623
+ protected abstract getObjectStrain(current: DifficultyHitObject): number;
624
+ protected calculateCurrentSectionStart(current: DifficultyHitObject): number;
601
625
  }
602
626
 
603
627
  /**
604
628
  * Represents the skill required to correctly aim at every object in the map with a uniform CircleSize and normalized distances.
605
629
  */
606
630
  declare class DroidAim extends DroidSkill {
607
- protected readonly skillMultiplier: number;
608
- protected readonly strainDecayBase: number;
609
- protected readonly reducedSectionCount: number;
610
- protected readonly reducedSectionBaseline: number;
611
- protected readonly starsPerDouble: number;
612
- private readonly withSliders;
613
- constructor(mods: Mod[], withSliders: boolean);
614
- /**
615
- * @param current The hitobject to calculate.
616
- */
617
- protected strainValueAt(current: DifficultyHitObject): number;
631
+ protected readonly strainDecayBase = 0.15;
632
+ protected readonly reducedSectionCount = 10;
633
+ protected readonly reducedSectionBaseline = 0.75;
634
+ protected readonly starsPerDouble = 1.05;
635
+ private readonly skillMultiplier;
636
+ private currentAimStrain;
637
+ private readonly sliderStrains;
638
+ private maxSliderStrain;
639
+ readonly withSliders: boolean;
640
+ constructor(mods: ModMap, withSliders: boolean);
641
+ static difficultyToPerformance(difficulty: number): number;
642
+ /**
643
+ * Obtains the amount of sliders that are considered difficult in terms of relative strain.
644
+ */
645
+ countDifficultSliders(): number;
646
+ protected strainValueAt(current: DroidDifficultyHitObject): number;
647
+ protected calculateInitialStrain(time: number, current: DroidDifficultyHitObject): number;
648
+ protected getObjectStrain(): number;
618
649
  /**
619
650
  * @param current The hitobject to save to.
620
651
  */
621
- protected saveToHitObject(current: DifficultyHitObject): void;
652
+ protected saveToHitObject(current: DroidDifficultyHitObject): void;
622
653
  }
623
654
 
624
655
  /**
625
656
  * An evaluator for calculating osu!droid Aim skill.
626
657
  */
627
- declare abstract class DroidAimEvaluator extends AimEvaluator {
628
- protected static readonly wideAngleMultiplier: number;
629
- protected static readonly sliderMultiplier: number;
630
- protected static readonly velocityChangeMultiplier: number;
631
- /**
632
- * Spacing threshold for a single hitobject spacing.
633
- */
634
- private static readonly SINGLE_SPACING_THRESHOLD;
658
+ declare abstract class DroidAimEvaluator {
659
+ private static readonly wideAngleMultiplier;
660
+ private static readonly acuteAngleMultiplier;
661
+ private static readonly sliderMultiplier;
662
+ private static readonly velocityChangeMultiplier;
663
+ private static readonly wiggleMultiplier;
664
+ private static readonly singleSpacingThreshold;
635
665
  private static readonly minSpeedBonus;
636
666
  /**
637
667
  * Evaluates the difficulty of aiming the current object, based on:
@@ -644,21 +674,23 @@ declare abstract class DroidAimEvaluator extends AimEvaluator {
644
674
  * @param current The current object.
645
675
  * @param withSliders Whether to take slider difficulty into account.
646
676
  */
647
- static evaluateDifficultyOf(current: DifficultyHitObject, withSliders: boolean): number;
677
+ static evaluateDifficultyOf(current: DroidDifficultyHitObject, withSliders: boolean): number;
648
678
  /**
649
- * Calculates the aim strain of a hitobject.
679
+ * Calculates the snap aim strain of a hitobject.
650
680
  */
651
- private static aimStrainOf;
681
+ private static snapAimStrainOf;
652
682
  /**
653
- * Calculates the movement strain of a hitobject.
683
+ * Calculates the flow aim strain of a hitobject.
654
684
  */
655
- private static movementStrainOf;
685
+ private static flowAimStrainOf;
686
+ private static calculateWideAngleBonus;
687
+ private static calculateAcuteAngleBonus;
656
688
  }
657
689
 
658
690
  /**
659
691
  * Holds data that can be used to calculate osu!droid performance points.
660
692
  */
661
- interface DroidDifficultyAttributes extends DifficultyAttributes {
693
+ interface IDroidDifficultyAttributes extends IDifficultyAttributes {
662
694
  /**
663
695
  * The difficulty corresponding to the tap skill.
664
696
  */
@@ -668,211 +700,171 @@ interface DroidDifficultyAttributes extends DifficultyAttributes {
668
700
  */
669
701
  rhythmDifficulty: number;
670
702
  /**
671
- * The difficulty corresponding to the visual skill.
672
- */
673
- visualDifficulty: number;
674
- }
675
-
676
- /**
677
- * Represents a beatmap section at which the strains of objects are considerably high.
678
- */
679
- interface HighStrainSection {
680
- /**
681
- * The index of the first object in this section with respect to the full beatmap.
682
- */
683
- readonly firstObjectIndex: number;
684
- /**
685
- * The index of the last object in this section with respect to the full beatmap.
703
+ * The difficulty corresponding to the reading skill.
686
704
  */
687
- readonly lastObjectIndex: number;
705
+ readingDifficulty: number;
688
706
  /**
689
- * The summed strain of this section.
707
+ * The amount of strains that are considered difficult with respect to the tap skill.
690
708
  */
691
- readonly sumStrain: number;
692
- }
693
-
694
- /**
695
- * Holds data that can be used to calculate osu!droid performance points as well
696
- * as doing some analysis using the replay of a score.
697
- */
698
- interface ExtendedDroidDifficultyAttributes extends DroidDifficultyAttributes {
699
- /**
700
- * Possible sections at which the player can use three fingers on.
701
- */
702
- possibleThreeFingeredSections: HighStrainSection[];
709
+ tapDifficultStrainCount: number;
703
710
  /**
704
- * Sliders that are considered difficult.
711
+ * The amount of strains that are considered difficult with respect to the flashlight skill.
705
712
  */
706
- difficultSliders: DifficultSlider[];
713
+ flashlightDifficultStrainCount: number;
707
714
  /**
708
- * The number of clickable objects weighted by difficulty.
709
- *
710
- * Related to aim difficulty.
715
+ * The amount of notes that are considered difficult with respect to the reading skill.
711
716
  */
712
- aimNoteCount: number;
717
+ readingDifficultNoteCount: number;
713
718
  /**
714
- * Describes how much of flashlight difficulty is contributed to by hitcircles or sliders.
715
- *
716
- * A value closer to 1 indicates most of flashlight difficulty is contributed by hitcircles.
717
- *
718
- * A value closer to 0 indicates most of flashlight difficulty is contributed by sliders.
719
+ * The average delta time of speed objects.
719
720
  */
720
- flashlightSliderFactor: number;
721
+ averageSpeedDeltaTime: number;
721
722
  /**
722
- * Describes how much of visual difficulty is contributed to by hitcircles or sliders.
723
+ * Describes how much of tap difficulty is contributed by notes that are "vibroable".
723
724
  *
724
- * A value closer to 1 indicates most of visual difficulty is contributed by hitcircles.
725
+ * A value closer to 1 indicates most of tap difficulty is contributed by notes that are not "vibroable".
725
726
  *
726
- * A value closer to 0 indicates most of visual difficulty is contributed by sliders.
727
+ * A value closer to 0 indicates most of tap difficulty is contributed by notes that are "vibroable".
727
728
  */
728
- visualSliderFactor: number;
729
+ vibroFactor: number;
729
730
  }
730
731
 
731
732
  /**
732
- * A difficulty calculator for osu!droid gamemode.
733
+ * Holds data that can be used to calculate osu!droid performance points.
733
734
  */
734
- declare class DroidDifficultyCalculator extends DifficultyCalculator {
735
- /**
736
- * The aim star rating of the beatmap.
737
- */
738
- aim: number;
739
- /**
740
- * The tap star rating of the beatmap.
741
- */
742
- tap: number;
743
- /**
744
- * The rhythm star rating of the beatmap.
745
- */
746
- rhythm: number;
747
- /**
748
- * The flashlight star rating of the beatmap.
749
- */
750
- flashlight: number;
751
- /**
752
- * The visual star rating of the beatmap.
753
- */
754
- visual: number;
755
- /**
756
- * The strain threshold to start detecting for possible three-fingered section.
757
- *
758
- * Increasing this number will result in less sections being flagged.
759
- */
760
- static readonly threeFingerStrainThreshold: number;
761
- readonly attributes: ExtendedDroidDifficultyAttributes;
762
- protected readonly difficultyMultiplier: number;
763
- protected readonly mode: Modes;
764
- /**
765
- * Calculates the aim star rating of the beatmap and stores it in this instance.
766
- */
767
- calculateAim(): void;
768
- /**
769
- * Calculates the tap star rating of the beatmap and stores it in this instance.
770
- */
771
- calculateTap(): void;
772
- /**
773
- * Calculates the rhythm star rating of the beatmap and stores it in this instance.
774
- */
775
- calculateRhythm(): void;
776
- /**
777
- * Calculates the flashlight star rating of the beatmap and stores it in this instance.
778
- */
779
- calculateFlashlight(): void;
780
- /**
781
- * Calculates the visual star rating of the beatmap and stores it in this instance.
782
- */
783
- calculateVisual(): void;
784
- calculateTotal(): void;
785
- calculateAll(): void;
735
+ declare class DroidDifficultyAttributes extends DifficultyAttributes implements IDroidDifficultyAttributes {
736
+ tapDifficulty: number;
737
+ rhythmDifficulty: number;
738
+ readingDifficulty: number;
739
+ tapDifficultStrainCount: number;
740
+ flashlightDifficultStrainCount: number;
741
+ readingDifficultNoteCount: number;
742
+ averageSpeedDeltaTime: number;
743
+ vibroFactor: number;
744
+ constructor(cacheableAttributes?: CacheableDifficultyAttributes<IDroidDifficultyAttributes>);
786
745
  toString(): string;
787
- protected preProcess(): void;
788
- protected createSkills(): DroidSkill[];
746
+ }
747
+
748
+ /**
749
+ * Represents a beatmap section at which the strains of objects are considerably high.
750
+ */
751
+ interface HighStrainSection {
789
752
  /**
790
- * Called after aim skill calculation.
791
- *
792
- * @param aimSkill The aim skill that considers sliders.
793
- * @param aimSkillWithoutSliders The aim skill that doesn't consider sliders.
753
+ * The index of the first object in this section with respect to the full beatmap.
794
754
  */
795
- private postCalculateAim;
755
+ readonly firstObjectIndex: number;
796
756
  /**
797
- * Calculates aim-related attributes.
757
+ * The index of the last object in this section with respect to the full beatmap.
798
758
  */
799
- private calculateAimAttributes;
759
+ readonly lastObjectIndex: number;
800
760
  /**
801
- * Called after tap skill calculation.
802
- *
803
- * @param tapSkill The tap skill.
761
+ * The summed strain of this section.
804
762
  */
805
- private postCalculateTap;
763
+ readonly sumStrain: number;
764
+ }
765
+
766
+ /**
767
+ * Holds data that can be used to calculate osu!droid performance points as well
768
+ * as doing some analysis using the replay of a score.
769
+ */
770
+ interface IExtendedDroidDifficultyAttributes extends IDroidDifficultyAttributes {
806
771
  /**
807
- * Calculates speed-related attributes.
772
+ * The mode of the difficulty calculation.
808
773
  */
809
- private calculateSpeedAttributes;
774
+ mode: "live";
810
775
  /**
811
- * Calculates the sum of strains for possible three-fingered sections.
812
- *
813
- * @param firstObjectIndex The index of the first object in the section.
814
- * @param lastObjectIndex The index of the last object in the section.
815
- * @returns The summed strain of the section.
776
+ * Possible sections at which the player can use three fingers on.
816
777
  */
817
- private calculateThreeFingerSummedStrain;
778
+ possibleThreeFingeredSections: HighStrainSection[];
818
779
  /**
819
- * Called after rhythm skill calculation.
820
- *
821
- * @param rhythmSkill The rhythm skill.
780
+ * Sliders that are considered difficult.
822
781
  */
823
- private postCalculateRhythm;
782
+ difficultSliders: DifficultSlider[];
824
783
  /**
825
- * Called after flashlight skill calculation.
784
+ * Describes how much of flashlight difficulty is contributed to by hitcircles or sliders.
826
785
  *
827
- * @param flashlightSkill The flashlight skill that considers sliders.
828
- * @param flashlightSkillWithoutSliders The flashlight skill that doesn't consider sliders.
829
- */
830
- private postCalculateFlashlight;
831
- /**
832
- * Called after visual skill calculation.
786
+ * A value closer to 1 indicates most of flashlight difficulty is contributed by hitcircles.
833
787
  *
834
- * @param visualSkillWithSliders The visual skill that considers sliders.
835
- * @param visualSkillWithoutSliders The visual skill that doesn't consider sliders.
788
+ * A value closer to 0 indicates most of flashlight difficulty is contributed by sliders.
836
789
  */
837
- private postCalculateVisual;
790
+ flashlightSliderFactor: number;
838
791
  }
839
792
 
840
793
  /**
841
- * Represents the skill required to memorize and hit every object in a beatmap with the Flashlight mod enabled.
794
+ * Holds data that can be used to calculate osu!droid performance points as well
795
+ * as doing some analysis using the replay of a score.
842
796
  */
843
- declare class DroidFlashlight extends DroidSkill {
844
- protected readonly skillMultiplier: number;
845
- protected readonly strainDecayBase: number;
846
- protected readonly reducedSectionCount: number;
847
- protected readonly reducedSectionBaseline: number;
848
- protected readonly starsPerDouble: number;
849
- private readonly isHidden;
850
- private readonly withSliders;
851
- constructor(mods: Mod[], withSliders: boolean);
797
+ declare class ExtendedDroidDifficultyAttributes extends DroidDifficultyAttributes implements IExtendedDroidDifficultyAttributes {
798
+ mode: "live";
799
+ possibleThreeFingeredSections: HighStrainSection[];
800
+ difficultSliders: DifficultSlider[];
801
+ flashlightSliderFactor: number;
802
+ constructor(cacheableAttributes?: CacheableDifficultyAttributes<IExtendedDroidDifficultyAttributes>);
803
+ }
804
+
805
+ /**
806
+ * A difficulty calculator for osu!droid gamemode.
807
+ */
808
+ declare class DroidDifficultyCalculator extends DifficultyCalculator<DroidPlayableBeatmap, DroidDifficultyHitObject, ExtendedDroidDifficultyAttributes> {
852
809
  /**
853
- * @param current The hitobject to calculate.
810
+ * The strain threshold to start detecting for possible three-fingered section.
811
+ *
812
+ * Increasing this number will result in less sections being flagged.
854
813
  */
855
- protected strainValueAt(current: DifficultyHitObject): number;
856
- protected saveToHitObject(current: DifficultyHitObject): void;
814
+ static readonly threeFingerStrainThreshold = 175;
815
+ private readonly difficultyMultiplier;
816
+ constructor();
817
+ retainDifficultyAdjustmentMods(mods: Mod[]): Mod[];
818
+ protected createDifficultyAttributes(beatmap: DroidPlayableBeatmap, skills: Skill[], objects: DroidDifficultyHitObject[]): ExtendedDroidDifficultyAttributes;
819
+ protected createPlayableBeatmap(beatmap: Beatmap, mods?: ModMap): DroidPlayableBeatmap;
820
+ protected createDifficultyHitObjects(beatmap: DroidPlayableBeatmap): DroidDifficultyHitObject[];
821
+ protected createSkills(beatmap: DroidPlayableBeatmap): Skill[];
822
+ protected createStrainPeakSkills(beatmap: DroidPlayableBeatmap): StrainSkill[];
823
+ private populateAimAttributes;
824
+ private populateTapAttributes;
825
+ private populateRhythmAttributes;
826
+ private populateFlashlightAttributes;
827
+ private populateReadingAttributes;
828
+ /**
829
+ * Calculates the base rating of a `Skill`.
830
+ *
831
+ * @param skill The `Skill` to calculate the rating of.
832
+ * @returns The rating of the `Skill`.
833
+ */
834
+ private calculateRating;
857
835
  }
858
836
 
859
837
  /**
860
- * An evaluator for calculating flashlight skill.
861
- *
862
- * This class should be considered an "evaluating" class and not persisted.
838
+ * Represents the skill required to memorize and hit every object in a beatmap with the Flashlight mod enabled.
863
839
  */
864
- declare abstract class FlashlightEvaluator {
865
- protected static readonly maxOpacityBonus: number;
866
- protected static readonly hiddenBonus: number;
867
- protected static readonly minVelocity: number;
868
- protected static readonly sliderMultiplier: number;
869
- protected static readonly minAngleMultiplier: number;
840
+ declare class DroidFlashlight extends DroidSkill {
841
+ protected readonly strainDecayBase = 0.15;
842
+ protected readonly reducedSectionCount = 0;
843
+ protected readonly reducedSectionBaseline = 1;
844
+ protected readonly starsPerDouble = 1.06;
845
+ private readonly skillMultiplier;
846
+ private currentFlashlightStrain;
847
+ readonly withSliders: boolean;
848
+ constructor(mods: ModMap, withSliders: boolean);
849
+ static difficultyToPerformance(difficulty: number): number;
850
+ protected strainValueAt(current: DroidDifficultyHitObject): number;
851
+ protected calculateInitialStrain(time: number, current: DifficultyHitObject): number;
852
+ protected getObjectStrain(): number;
853
+ protected saveToHitObject(current: DroidDifficultyHitObject): void;
854
+ difficultyValue(): number;
870
855
  }
871
856
 
872
857
  /**
873
858
  * An evaluator for calculating osu!droid Flashlight skill.
874
859
  */
875
- declare abstract class DroidFlashlightEvaluator extends FlashlightEvaluator {
860
+ declare abstract class DroidFlashlightEvaluator {
861
+ private static readonly maxOpacityBonus;
862
+ private static readonly hiddenBonus;
863
+ private static readonly traceableCircleBonus;
864
+ private static readonly traceableObjectBonus;
865
+ private static readonly minVelocity;
866
+ private static readonly sliderMultiplier;
867
+ private static readonly minAngleMultiplier;
876
868
  /**
877
869
  * Evaluates the difficulty of memorizing and hitting the current object, based on:
878
870
  *
@@ -883,10 +875,10 @@ declare abstract class DroidFlashlightEvaluator extends FlashlightEvaluator {
883
875
  * - and whether Hidden mod is enabled.
884
876
  *
885
877
  * @param current The current object.
886
- * @param isHiddenMod Whether the Hidden mod is enabled.
878
+ * @param mods The mods used.
887
879
  * @param withSliders Whether to take slider difficulty into account.
888
880
  */
889
- static evaluateDifficultyOf(current: DifficultyHitObject, isHiddenMod: boolean, withSliders: boolean): number;
881
+ static evaluateDifficultyOf(current: DroidDifficultyHitObject, mods: ModMap, withSliders: boolean): number;
890
882
  }
891
883
 
892
884
  /**
@@ -905,6 +897,14 @@ interface PerformanceCalculationOptions {
905
897
  * The amount of misses achieved in the score.
906
898
  */
907
899
  miss?: number;
900
+ /**
901
+ * The amount of slider ends dropped in the score.
902
+ */
903
+ sliderEndsDropped?: number;
904
+ /**
905
+ * The amount of slider ticks missed in the score.
906
+ */
907
+ sliderTicksMissed?: number;
908
908
  /**
909
909
  * The tap penalty to apply for penalized scores. Only used when using `DroidPerformanceCalculator`.
910
910
  */
@@ -926,7 +926,7 @@ interface PerformanceCalculationOptions {
926
926
  /**
927
927
  * The base class of performance calculators.
928
928
  */
929
- declare abstract class PerformanceCalculator {
929
+ declare abstract class PerformanceCalculator<T extends IDifficultyAttributes> {
930
930
  /**
931
931
  * The overall performance value.
932
932
  */
@@ -935,32 +935,39 @@ declare abstract class PerformanceCalculator {
935
935
  * The calculated accuracy.
936
936
  */
937
937
  computedAccuracy: Accuracy;
938
+ /**
939
+ * The calculated maximum combo.
940
+ */
941
+ combo: number;
938
942
  /**
939
943
  * The difficulty attributes that is being calculated.
940
944
  */
941
- abstract readonly difficultyAttributes: DifficultyAttributes;
945
+ readonly difficultyAttributes: T | CacheableDifficultyAttributes<T>;
942
946
  /**
943
- * Penalty for combo breaks.
947
+ * The mods that were used.
944
948
  */
945
- protected comboPenalty: number;
949
+ protected readonly mods: ModMap;
950
+ private _sliderEndsDropped;
946
951
  /**
947
- * The global multiplier to be applied to the final performance value.
948
- *
949
- * This is being adjusted to keep the final value scaled around what it used to be when changing things.
952
+ * The amount of slider ends dropped in the score.
950
953
  */
951
- protected abstract finalMultiplier: number;
954
+ protected get sliderEndsDropped(): number;
955
+ private _sliderTicksMissed;
952
956
  /**
953
- * The gamemode to calculate for.
957
+ * The amount of slider ticks missed in the score.
958
+ *
959
+ * This is used to calculate the slider accuracy.
954
960
  */
955
- protected abstract readonly mode: Modes;
961
+ protected get sliderTicksMissed(): number;
962
+ private _usingClassicSliderAccuracy;
956
963
  /**
957
- * The amount of misses that are filtered out from sliderbreaks.
964
+ * Whether this score uses classic slider accuracy.
958
965
  */
959
- protected effectiveMissCount: number;
966
+ protected get usingClassicSliderAccuracy(): boolean;
960
967
  /**
961
- * Nerf factor used for nerfing beatmaps with very likely dropped sliderends.
968
+ * @param difficultyAttributes The difficulty attributes to calculate.
962
969
  */
963
- protected sliderNerfFactor: number;
970
+ constructor(difficultyAttributes: T | CacheableDifficultyAttributes<T>);
964
971
  /**
965
972
  * Calculates the performance points of the beatmap.
966
973
  *
@@ -977,10 +984,6 @@ declare abstract class PerformanceCalculator {
977
984
  * performance value of the beatmap and stores them in this instance.
978
985
  */
979
986
  protected abstract calculateValues(): void;
980
- /**
981
- * Calculates the total performance value of the beatmap and stores it in this instance.
982
- */
983
- protected abstract calculateTotalValue(): void;
984
987
  /**
985
988
  * The total hits that can be done in the beatmap.
986
989
  */
@@ -990,9 +993,9 @@ declare abstract class PerformanceCalculator {
990
993
  */
991
994
  protected get totalSuccessfulHits(): number;
992
995
  /**
993
- * Calculates the base performance value of a star rating.
996
+ * The total of imperfect hits (100s, 50s, misses).
994
997
  */
995
- protected baseValue(stars: number): number;
998
+ protected get totalImperfectHits(): number;
996
999
  /**
997
1000
  * Processes given options for usage in performance calculation.
998
1001
  *
@@ -1000,15 +1003,18 @@ declare abstract class PerformanceCalculator {
1000
1003
  */
1001
1004
  protected handleOptions(options?: PerformanceCalculationOptions): void;
1002
1005
  /**
1003
- * Calculates the amount of misses + sliderbreaks from combo.
1006
+ * Determines whether an attribute is a cacheable attribute.
1007
+ *
1008
+ * @param attributes The attributes to check.
1009
+ * @returns Whether the attributes are cacheable.
1004
1010
  */
1005
- private calculateEffectiveMissCount;
1011
+ private isCacheableAttribute;
1006
1012
  }
1007
1013
 
1008
1014
  /**
1009
1015
  * A performance points calculator that calculates performance points for osu!droid gamemode.
1010
1016
  */
1011
- declare class DroidPerformanceCalculator extends PerformanceCalculator {
1017
+ declare class DroidPerformanceCalculator extends PerformanceCalculator<IDroidDifficultyAttributes> {
1012
1018
  /**
1013
1019
  * The aim performance value.
1014
1020
  */
@@ -1026,9 +1032,9 @@ declare class DroidPerformanceCalculator extends PerformanceCalculator {
1026
1032
  */
1027
1033
  flashlight: number;
1028
1034
  /**
1029
- * The visual performance value.
1035
+ * The reading performance value.
1030
1036
  */
1031
- visual: number;
1037
+ reading: number;
1032
1038
  /**
1033
1039
  * The penalty used to penalize the tap performance value.
1034
1040
  *
@@ -1056,58 +1062,17 @@ declare class DroidPerformanceCalculator extends PerformanceCalculator {
1056
1062
  */
1057
1063
  get flashlightSliderCheesePenalty(): number;
1058
1064
  /**
1059
- * The penalty used to penalize the visual performance value.
1060
- *
1061
- * Can be properly obtained by analyzing the replay associated with the score.
1065
+ * The amount of misses, including slider breaks.
1062
1066
  */
1063
- get visualSliderCheesePenalty(): number;
1064
- readonly difficultyAttributes: DroidDifficultyAttributes;
1065
- protected finalMultiplier: number;
1066
- protected readonly mode: Modes;
1067
+ get effectiveMissCount(): number;
1068
+ static readonly finalMultiplier = 1.24;
1067
1069
  private _aimSliderCheesePenalty;
1068
1070
  private _flashlightSliderCheesePenalty;
1069
- private _visualSliderCheesePenalty;
1070
1071
  private _tapPenalty;
1072
+ private _effectiveMissCount;
1071
1073
  private _deviation;
1072
1074
  private _tapDeviation;
1073
- /**
1074
- * @param difficultyAttributes The difficulty attributes to calculate.
1075
- */
1076
- constructor(difficultyAttributes: DroidDifficultyAttributes);
1077
- /**
1078
- * Applies a tap penalty value to this calculator.
1079
- *
1080
- * The tap and total performance value will be recalculated afterwards.
1081
- *
1082
- * @param value The tap penalty value. Must be greater than or equal to 1.
1083
- */
1084
- applyTapPenalty(value: number): void;
1085
- /**
1086
- * Applies an aim slider cheese penalty value to this calculator.
1087
- *
1088
- * The aim and total performance value will be recalculated afterwards.
1089
- *
1090
- * @param value The slider cheese penalty value. Must be between than 0 and 1.
1091
- */
1092
- applyAimSliderCheesePenalty(value: number): void;
1093
- /**
1094
- * Applies a flashlight slider cheese penalty value to this calculator.
1095
- *
1096
- * The flashlight and total performance value will be recalculated afterwards.
1097
- *
1098
- * @param value The slider cheese penalty value. Must be between 0 and 1.
1099
- */
1100
- applyFlashlightSliderCheesePenalty(value: number): void;
1101
- /**
1102
- * Applies a visual slider cheese penalty value to this calculator.
1103
- *
1104
- * The visual and total performance value will be recalculated afterwards.
1105
- *
1106
- * @param value The slider cheese penalty value. Must be between 0 and 1.
1107
- */
1108
- applyVisualSliderCheesePenalty(value: number): void;
1109
1075
  protected calculateValues(): void;
1110
- protected calculateTotalValue(): void;
1111
1076
  protected handleOptions(options?: PerformanceCalculationOptions): void;
1112
1077
  /**
1113
1078
  * Calculates the aim performance value of the beatmap.
@@ -1126,11 +1091,32 @@ declare class DroidPerformanceCalculator extends PerformanceCalculator {
1126
1091
  */
1127
1092
  private calculateFlashlightValue;
1128
1093
  /**
1129
- * Calculates the visual performance value of the beatmap.
1094
+ * Calculates the reading performance value of the beatmap.
1095
+ */
1096
+ private calculateReadingValue;
1097
+ /**
1098
+ * Calculates a strain-based miss penalty.
1099
+ *
1100
+ * Strain-based miss penalty assumes that a player will miss on the hardest parts of a map,
1101
+ * so we use the amount of relatively difficult sections to adjust miss penalty
1102
+ * to make it more punishing on maps with lower amount of hard sections.
1103
+ */
1104
+ private calculateStrainBasedMissPenalty;
1105
+ /**
1106
+ * The object-based proportional miss penalty.
1130
1107
  */
1131
- private calculateVisualValue;
1108
+ private get proportionalMissPenalty();
1132
1109
  /**
1133
- * Estimates the player's tap deviation based on the OD, number of circles and sliders,
1110
+ * Calculates the object-based length scaling based on the deviation of a player for a full
1111
+ * combo in this beatmap, taking retries into account.
1112
+ *
1113
+ * @param objectCount The amount of objects to be considered. Defaults to the amount of
1114
+ * objects in this beatmap.
1115
+ * @param punishForMemorization Whether to punish the deviation for memorization. Defaults to `false`.
1116
+ */
1117
+ private calculateDeviationBasedLengthScaling;
1118
+ /**
1119
+ * Estimates the player's deviation based on the OD, number of circles and sliders,
1134
1120
  * and number of 300s, 100s, 50s, and misses, assuming the player's mean hit error is 0.
1135
1121
  *
1136
1122
  * The estimation is consistent in that two SS scores on the same map
@@ -1151,281 +1137,286 @@ declare class DroidPerformanceCalculator extends PerformanceCalculator {
1151
1137
  * This is fine though, since this method is only used to scale tap pp.
1152
1138
  */
1153
1139
  private calculateTapDeviation;
1140
+ /**
1141
+ * Calculates a multiplier for tap to account for improper tapping based on the deviation and tap difficulty.
1142
+ *
1143
+ * [Graph](https://www.desmos.com/calculator/z5l9ebrwpi)
1144
+ */
1145
+ private calculateTapHighDeviationNerf;
1146
+ private getHitWindow;
1154
1147
  toString(): string;
1155
1148
  }
1156
1149
 
1157
1150
  /**
1158
- * Represents the skill required to properly follow a beatmap's rhythm.
1151
+ * Represents the skill required to read every object in the map.
1159
1152
  */
1160
- declare class DroidRhythm extends DroidSkill {
1161
- protected readonly skillMultiplier: number;
1162
- protected readonly reducedSectionCount: number;
1163
- protected readonly reducedSectionBaseline: number;
1164
- protected readonly strainDecayBase: number;
1165
- protected readonly starsPerDouble: number;
1166
- private currentRhythm;
1167
- private readonly hitWindow;
1168
- constructor(mods: Mod[], overallDifficulty: number);
1169
- protected strainValueAt(current: DifficultyHitObject): number;
1170
- protected saveToHitObject(current: DifficultyHitObject): void;
1153
+ declare class DroidReading extends Skill {
1154
+ private readonly clockRate;
1155
+ private readonly hitObjects;
1156
+ private readonly noteDifficulties;
1157
+ private readonly strainDecayBase;
1158
+ private readonly skillMultiplier;
1159
+ private currentNoteDifficulty;
1160
+ private difficulty;
1161
+ private noteWeightSum;
1162
+ constructor(mods: ModMap, clockRate: number, hitObjects: readonly PlaceableHitObject[]);
1163
+ /**
1164
+ * Converts a difficulty value to a performance value.
1165
+ *
1166
+ * @param difficulty The difficulty value to convert.
1167
+ * @returns The performance value.
1168
+ */
1169
+ static difficultyToPerformance(difficulty: number): number;
1170
+ process(current: DroidDifficultyHitObject): void;
1171
+ difficultyValue(): number;
1172
+ /**
1173
+ * Returns the number of relevant objects weighted against the top note.
1174
+ */
1175
+ countTopWeightedNotes(): number;
1176
+ private strainDecay;
1171
1177
  }
1172
1178
 
1173
1179
  /**
1174
- * An evaluator for calculating rhythm skill.
1175
- *
1176
- * This class should be considered an "evaluating" class and not persisted.
1180
+ * An evaluator for calculating osu!droid reading skill.
1181
+ */
1182
+ declare abstract class DroidReadingEvaluator {
1183
+ private static readonly emptyModMap;
1184
+ private static readonly readingWindowSize;
1185
+ private static readonly distanceInfluenceThreshold;
1186
+ private static readonly hiddenMultiplier;
1187
+ private static readonly densityMultiplier;
1188
+ private static readonly densityDifficultyBase;
1189
+ private static readonly preemptBalancingFactor;
1190
+ private static readonly preemptStartingPoint;
1191
+ static evaluateDifficultyOf(current: DroidDifficultyHitObject, clockRate: number, mods: ModMap): number;
1192
+ /**
1193
+ * Retrieves a list of objects that are visible at the point in time the current object needs to be hit.
1194
+ *
1195
+ * @param current The current object.
1196
+ */
1197
+ private static retrievePastVisibleObjects;
1198
+ /**
1199
+ * Calculates the density of objects visible at the point in time the current object needs to be hit.
1200
+ *
1201
+ * @param current The current object.
1202
+ */
1203
+ private static calculateCurrentVisibleObjectsDensity;
1204
+ /**
1205
+ * Returns the time an object spends invisible with the Hidden mod at the current approach rate.
1206
+ *
1207
+ * @param current The current object.
1208
+ */
1209
+ private static getDurationSpentInvisible;
1210
+ /**
1211
+ * Calculates a factor of how often the current object's angle has been repeated in a certain time frame.
1212
+ * It does this by checking the difference in angle between current and past objects and sums them up
1213
+ * based on a range of similarity.
1214
+ *
1215
+ * @param current The current object.
1216
+ */
1217
+ private static getConstantAngleNerfFactor;
1218
+ private static getTimeNerfFactor;
1219
+ }
1220
+
1221
+ /**
1222
+ * Represents the skill required to properly follow a beatmap's rhythm.
1177
1223
  */
1178
- declare abstract class RhythmEvaluator {
1179
- protected static readonly rhythmMultiplier: number;
1180
- protected static readonly historyTimeMax: number;
1224
+ declare class DroidRhythm extends DroidSkill {
1225
+ protected readonly reducedSectionCount = 5;
1226
+ protected readonly reducedSectionBaseline = 0.75;
1227
+ protected readonly strainDecayBase = 0.3;
1228
+ protected readonly starsPerDouble = 1.75;
1229
+ private readonly useSliderAccuracy;
1230
+ private currentRhythmStrain;
1231
+ private currentRhythmMultiplier;
1232
+ constructor(mods: ModMap);
1233
+ protected strainValueAt(current: DroidDifficultyHitObject): number;
1234
+ protected calculateInitialStrain(time: number, current: DroidDifficultyHitObject): number;
1235
+ protected getObjectStrain(): number;
1236
+ protected saveToHitObject(current: DroidDifficultyHitObject): void;
1181
1237
  }
1182
1238
 
1183
1239
  /**
1184
1240
  * An evaluator for calculating osu!droid Rhythm skill.
1185
1241
  */
1186
- declare abstract class DroidRhythmEvaluator extends RhythmEvaluator {
1242
+ declare abstract class DroidRhythmEvaluator {
1243
+ private static readonly historyTimeMax;
1244
+ private static readonly historyObjectsMax;
1245
+ private static readonly rhythmOverallMultiplier;
1246
+ private static readonly rhythmRatioMultiplier;
1187
1247
  /**
1188
1248
  * Calculates a rhythm multiplier for the difficulty of the tap associated
1189
1249
  * with historic data of the current object.
1190
1250
  *
1191
1251
  * @param current The current object.
1192
- * @param greatWindow The great hit window of the current object.
1252
+ * @param useSliderAccuracy Whether to use slider accuracy.
1193
1253
  */
1194
- static evaluateDifficultyOf(current: DifficultyHitObject, greatWindow: number): number;
1254
+ static evaluateDifficultyOf(current: DroidDifficultyHitObject, useSliderAccuracy: boolean): number;
1195
1255
  }
1196
1256
 
1197
1257
  /**
1198
1258
  * Represents the skill required to press keys or tap with regards to keeping up with the speed at which objects need to be hit.
1199
1259
  */
1200
1260
  declare class DroidTap extends DroidSkill {
1201
- protected readonly skillMultiplier: number;
1202
- protected readonly reducedSectionCount: number;
1203
- protected readonly reducedSectionBaseline: number;
1204
- protected readonly strainDecayBase: number;
1205
- protected readonly starsPerDouble: number;
1261
+ protected readonly reducedSectionCount = 10;
1262
+ protected readonly reducedSectionBaseline = 0.75;
1263
+ protected readonly strainDecayBase = 0.3;
1264
+ protected readonly starsPerDouble = 1.1;
1206
1265
  private currentTapStrain;
1207
- private currentOriginalTapStrain;
1208
- private readonly greatWindow;
1209
- constructor(mods: Mod[], overallDifficulty: number);
1266
+ private currentRhythmMultiplier;
1267
+ private readonly skillMultiplier;
1268
+ private readonly _objectDeltaTimes;
1269
+ private maxStrain;
1210
1270
  /**
1211
- * @param current The hitobject to calculate.
1271
+ * The delta time of hitobjects.
1212
1272
  */
1213
- protected strainValueAt(current: DifficultyHitObject): number;
1273
+ get objectDeltaTimes(): readonly number[];
1274
+ readonly considerCheesability: boolean;
1275
+ readonly strainTimeCap?: number;
1276
+ constructor(mods: ModMap, considerCheesability: boolean, strainTimeCap?: number);
1277
+ /**
1278
+ * The amount of notes that are relevant to the difficulty.
1279
+ */
1280
+ relevantNoteCount(): number;
1281
+ /**
1282
+ * The delta time relevant to the difficulty.
1283
+ */
1284
+ relevantDeltaTime(): number;
1285
+ protected strainValueAt(current: DroidDifficultyHitObject): number;
1286
+ protected calculateInitialStrain(time: number, current: DroidDifficultyHitObject): number;
1287
+ protected getObjectStrain(): number;
1214
1288
  /**
1215
1289
  * @param current The hitobject to save to.
1216
1290
  */
1217
- protected saveToHitObject(current: DifficultyHitObject): void;
1218
- }
1219
-
1220
- /**
1221
- * An evaluator for calculating speed or tap skill.
1222
- *
1223
- * This class should be considered an "evaluating" class and not persisted.
1224
- */
1225
- declare abstract class SpeedEvaluator {
1226
- protected static readonly minSpeedBonus: number;
1291
+ protected saveToHitObject(current: DroidDifficultyHitObject): void;
1227
1292
  }
1228
1293
 
1229
1294
  /**
1230
1295
  * An evaluator for calculating osu!droid tap skill.
1231
1296
  */
1232
- declare abstract class DroidTapEvaluator extends SpeedEvaluator {
1297
+ declare abstract class DroidTapEvaluator {
1298
+ private static readonly minSpeedBonus;
1233
1299
  /**
1234
1300
  * Evaluates the difficulty of tapping the current object, based on:
1235
1301
  *
1236
1302
  * - time between pressing the previous and current object,
1237
1303
  * - distance between those objects,
1238
- * - and how easily they can be cheesed.
1304
+ * - how easily they can be cheesed,
1305
+ * - and the strain time cap.
1239
1306
  *
1240
1307
  * @param current The current object.
1241
- * @param greatWindow The great hit window of the current object.
1242
1308
  * @param considerCheesability Whether to consider cheesability.
1309
+ * @param strainTimeCap The strain time to cap the object's strain time to.
1243
1310
  */
1244
- static evaluateDifficultyOf(current: DifficultyHitObject, greatWindow: number, considerCheesability: boolean): number;
1245
- }
1246
-
1247
- /**
1248
- * Represents the skill required to read every object in the map.
1249
- */
1250
- declare class DroidVisual extends DroidSkill {
1251
- protected readonly starsPerDouble: number;
1252
- protected readonly reducedSectionCount: number;
1253
- protected readonly reducedSectionBaseline: number;
1254
- protected readonly skillMultiplier: number;
1255
- protected readonly strainDecayBase: number;
1256
- private readonly isHidden;
1257
- private readonly withSliders;
1258
- constructor(mods: Mod[], withSliders: boolean);
1259
- protected strainValueAt(current: DifficultyHitObject): number;
1260
- protected saveToHitObject(current: DifficultyHitObject): void;
1261
- }
1262
-
1263
- /**
1264
- * An evaluator for calculating osu!droid Visual skill.
1265
- */
1266
- declare abstract class DroidVisualEvaluator {
1267
- /**
1268
- * Evaluates the difficulty of reading the current object, based on:
1269
- *
1270
- * - note density of the current object,
1271
- * - overlapping factor of the current object,
1272
- * - the preempt time of the current object,
1273
- * - the visual opacity of the current object,
1274
- * - the velocity of the current object if it's a slider,
1275
- * - past objects' velocity if they are sliders,
1276
- * - and whether the Hidden mod is enabled.
1277
- *
1278
- * @param current The current object.
1279
- * @param isHiddenMod Whether the Hidden mod is enabled.
1280
- * @param withSliders Whether to take slider difficulty into account.
1281
- */
1282
- static evaluateDifficultyOf(current: DifficultyHitObject, isHiddenMod: boolean, withSliders: boolean): number;
1311
+ static evaluateDifficultyOf(current: DroidDifficultyHitObject, considerCheesability: boolean, strainTimeCap?: number): number;
1283
1312
  }
1284
1313
 
1285
1314
  /**
1286
- * Used to processes strain values of difficulty hitobjects, keep track of strain levels caused by the processed objects
1287
- * and to calculate a final difficulty value representing the difficulty of hitting all the processed objects.
1315
+ * Holds data that can be used to calculate osu!standard performance points.
1288
1316
  */
1289
- declare abstract class OsuSkill extends StrainSkill {
1317
+ interface IOsuDifficultyAttributes extends IDifficultyAttributes {
1290
1318
  /**
1291
- * The default multiplier applied to the final difficulty value after all other calculations.
1319
+ * The perceived approach rate **exclusive** of rate-adjusting mods (DT/HT/etc).
1292
1320
  *
1293
- * May be overridden via {@link difficultyMultiplier}.
1294
- */
1295
- static readonly defaultDifficultyMultiplier: number;
1296
- /**
1297
- * The final multiplier to be applied to the final difficulty value after all other calculations.
1321
+ * Rate-adjusting mods don't directly affect the approach rate difficulty value, but have a perceived effect as a result of adjusting audio timing.
1298
1322
  */
1299
- protected readonly difficultyMultiplier: number;
1323
+ approachRate: number;
1300
1324
  /**
1301
- * The weight by which each strain value decays.
1325
+ * The health drain rate of the beatmap.
1302
1326
  */
1303
- protected abstract readonly decayWeight: number;
1304
- difficultyValue(): number;
1305
- }
1306
-
1307
- /**
1308
- * Holds data that can be used to calculate osu!standard performance points.
1309
- */
1310
- interface OsuDifficultyAttributes extends DifficultyAttributes {
1327
+ drainRate: number;
1311
1328
  /**
1312
1329
  * The difficulty corresponding to the speed skill.
1313
1330
  */
1314
1331
  speedDifficulty: number;
1315
- }
1316
-
1317
- /**
1318
- * A difficulty calculator for osu!standard gamemode.
1319
- */
1320
- declare class OsuDifficultyCalculator extends DifficultyCalculator {
1321
1332
  /**
1322
- * The aim star rating of the beatmap.
1333
+ * The amount of strains that are considered difficult with respect to the speed skill.
1323
1334
  */
1324
- aim: number;
1325
- /**
1326
- * The speed star rating of the beatmap.
1327
- */
1328
- speed: number;
1329
- /**
1330
- * The flashlight star rating of the beatmap.
1331
- */
1332
- flashlight: number;
1333
- readonly attributes: OsuDifficultyAttributes;
1334
- protected readonly difficultyMultiplier: number;
1335
- protected readonly mode: Modes;
1336
- /**
1337
- * Calculates the aim star rating of the beatmap and stores it in this instance.
1338
- */
1339
- calculateAim(): void;
1340
- /**
1341
- * Calculates the speed star rating of the beatmap and stores it in this instance.
1342
- */
1343
- calculateSpeed(): void;
1344
- /**
1345
- * Calculates the flashlight star rating of the beatmap and stores it in this instance.
1346
- */
1347
- calculateFlashlight(): void;
1348
- calculateTotal(): void;
1349
- calculateAll(): void;
1350
- toString(): string;
1351
- protected preProcess(): void;
1352
- protected createSkills(): OsuSkill[];
1335
+ speedDifficultStrainCount: number;
1353
1336
  /**
1354
- * Called after aim skill calculation.
1337
+ * Describes how much of {@link aimDifficultStrainCount} is contributed to by circles or sliders.
1355
1338
  *
1356
- * @param aimSkill The aim skill that considers sliders.
1357
- * @param aimSkillWithoutSliders The aim skill that doesn't consider sliders.
1358
- */
1359
- private postCalculateAim;
1360
- /**
1361
- * Called after speed skill calculation.
1339
+ * A value closer to 0 indicates most of {@link aimDifficultStrainCount} is contributed by circles.
1362
1340
  *
1363
- * @param speedSkill The speed skill.
1341
+ * A value closer to infinity indicates most of {@link aimDifficultStrainCount} is contributed by sliders.
1364
1342
  */
1365
- private postCalculateSpeed;
1343
+ aimTopWeightedSliderFactor: number;
1366
1344
  /**
1367
- * Calculates speed-related attributes.
1368
- */
1369
- private calculateSpeedAttributes;
1370
- /**
1371
- * Called after flashlight skill calculation.
1345
+ * Describes how much of {@link speedDifficultStrainCount} is contributed to by circles or sliders.
1372
1346
  *
1373
- * @param flashlightSkill The flashlight skill.
1347
+ * A value closer to 0 indicates most of {@link speedDifficultStrainCount} is contributed by circles.
1348
+ *
1349
+ * A value closer to infinity indicates most of {@link speedDifficultStrainCount} is contributed by sliders.
1374
1350
  */
1375
- private postCalculateFlashlight;
1351
+ speedTopWeightedSliderFactor: number;
1376
1352
  }
1377
1353
 
1378
1354
  /**
1379
- * A difficulty calculator that calculates for both osu!droid and osu!standard gamemode.
1355
+ * Represents an osu!standard hit object with difficulty calculation values.
1380
1356
  */
1381
- declare class MapStars {
1357
+ declare class OsuDifficultyHitObject extends DifficultyHitObject {
1382
1358
  /**
1383
- * The osu!droid difficulty calculator of the beatmap.
1359
+ * The speed strain generated by the hitobject.
1384
1360
  */
1385
- readonly droid: DroidDifficultyCalculator;
1361
+ speedStrain: number;
1386
1362
  /**
1387
- * The osu!standard difficulty calculator of the beatmap.
1363
+ * The flashlight strain generated by this hitobject.
1388
1364
  */
1389
- readonly osu: OsuDifficultyCalculator;
1390
- /**
1391
- * Constructs this instance and calculates the given beatmap's osu!droid and osu!standard difficulty.
1392
- *
1393
- * @param beatmap The beatmap to calculate.
1394
- * @param options Options for the difficulty calculation.
1395
- */
1396
- constructor(beatmap: Beatmap, options?: DifficultyCalculationOptions);
1365
+ flashlightStrain: number;
1366
+ get smallCircleBonus(): number;
1367
+ protected readonly mode = Modes.osu;
1368
+ }
1369
+
1370
+ /**
1371
+ * Used to processes strain values of difficulty hitobjects, keep track of strain levels caused by the processed objects
1372
+ * and to calculate a final difficulty value representing the difficulty of hitting all the processed objects.
1373
+ */
1374
+ declare abstract class OsuSkill extends StrainSkill {
1397
1375
  /**
1398
- * Returns a string representative of the class.
1376
+ * The weight by which each strain value decays.
1399
1377
  */
1400
- toString(): string;
1378
+ protected abstract readonly decayWeight: number;
1379
+ difficultyValue(): number;
1401
1380
  }
1402
1381
 
1403
1382
  /**
1404
1383
  * Represents the skill required to correctly aim at every object in the map with a uniform CircleSize and normalized distances.
1405
1384
  */
1406
1385
  declare class OsuAim extends OsuSkill {
1407
- protected readonly skillMultiplier: number;
1408
- protected readonly strainDecayBase: number;
1409
- protected readonly reducedSectionCount: number;
1410
- protected readonly reducedSectionBaseline: number;
1411
- protected readonly difficultyMultiplier: number;
1412
- protected readonly decayWeight: number;
1413
- private readonly withSliders;
1414
- constructor(mods: Mod[], withSliders: boolean);
1386
+ protected readonly strainDecayBase = 0.15;
1387
+ protected readonly reducedSectionCount = 10;
1388
+ protected readonly reducedSectionBaseline = 0.75;
1389
+ protected readonly decayWeight = 0.9;
1390
+ private currentAimStrain;
1391
+ private readonly skillMultiplier;
1392
+ private readonly sliderStrains;
1393
+ readonly withSliders: boolean;
1394
+ constructor(mods: ModMap, withSliders: boolean);
1415
1395
  /**
1416
- * @param current The hitobject to calculate.
1396
+ * Obtains the amount of sliders that are considered difficult in terms of relative strain.
1417
1397
  */
1418
- protected strainValueAt(current: DifficultyHitObject): number;
1398
+ countDifficultSliders(): number;
1399
+ /**
1400
+ * Obtains the amount of sliders that are considered difficult in terms of relative strain, weighted by consistency.
1401
+ */
1402
+ countTopWeightedSliders(): number;
1403
+ protected strainValueAt(current: OsuDifficultyHitObject): number;
1404
+ protected calculateInitialStrain(time: number, current: OsuDifficultyHitObject): number;
1419
1405
  /**
1420
1406
  * @param current The hitobject to save to.
1421
1407
  */
1422
- protected saveToHitObject(current: DifficultyHitObject): void;
1408
+ protected saveToHitObject(current: OsuDifficultyHitObject): void;
1423
1409
  }
1424
1410
 
1425
1411
  /**
1426
1412
  * An evaluator for calculating osu!standard Aim skill.
1427
1413
  */
1428
- declare abstract class OsuAimEvaluator extends AimEvaluator {
1414
+ declare abstract class OsuAimEvaluator {
1415
+ private static readonly wideAngleMultiplier;
1416
+ private static readonly acuteAngleMultiplier;
1417
+ private static readonly sliderMultiplier;
1418
+ private static readonly velocityChangeMultiplier;
1419
+ private static readonly wiggleMultiplier;
1429
1420
  /**
1430
1421
  * Evaluates the difficulty of aiming the current object, based on:
1431
1422
  *
@@ -1437,31 +1428,69 @@ declare abstract class OsuAimEvaluator extends AimEvaluator {
1437
1428
  * @param current The current object.
1438
1429
  * @param withSliders Whether to take slider difficulty into account.
1439
1430
  */
1440
- static evaluateDifficultyOf(current: DifficultyHitObject, withSliders: boolean): number;
1431
+ static evaluateDifficultyOf(current: OsuDifficultyHitObject, withSliders: boolean): number;
1432
+ private static calculateWideAngleBonus;
1433
+ private static calculateAcuteAngleBonus;
1434
+ }
1435
+
1436
+ /**
1437
+ * Holds data that can be used to calculate osu!standard performance points.
1438
+ */
1439
+ declare class OsuDifficultyAttributes extends DifficultyAttributes implements IOsuDifficultyAttributes {
1440
+ approachRate: number;
1441
+ drainRate: number;
1442
+ speedDifficulty: number;
1443
+ speedDifficultStrainCount: number;
1444
+ aimTopWeightedSliderFactor: number;
1445
+ speedTopWeightedSliderFactor: number;
1446
+ constructor(cacheableAttributes?: CacheableDifficultyAttributes<IOsuDifficultyAttributes>);
1447
+ toString(): string;
1448
+ }
1449
+
1450
+ /**
1451
+ * A difficulty calculator for osu!standard gamemode.
1452
+ */
1453
+ declare class OsuDifficultyCalculator extends DifficultyCalculator<OsuPlayableBeatmap, OsuDifficultyHitObject, OsuDifficultyAttributes> {
1454
+ private readonly starRatingMultiplier;
1455
+ constructor();
1456
+ retainDifficultyAdjustmentMods(mods: Mod[]): Mod[];
1457
+ protected createDifficultyAttributes(beatmap: OsuPlayableBeatmap, skills: Skill[]): OsuDifficultyAttributes;
1458
+ protected createPlayableBeatmap(beatmap: Beatmap, mods?: ModMap): OsuPlayableBeatmap;
1459
+ protected createDifficultyHitObjects(beatmap: OsuPlayableBeatmap): OsuDifficultyHitObject[];
1460
+ protected createSkills(beatmap: OsuPlayableBeatmap): OsuSkill[];
1461
+ protected createStrainPeakSkills(beatmap: OsuPlayableBeatmap): StrainSkill[];
1462
+ private calculateMechanicalDifficultyRating;
1463
+ private calculateStarRating;
1464
+ static calculateRateAdjustedApproachRate(approachRate: number, clockRate: number): number;
1465
+ static calculateRateAdjustedOverallDifficulty(overallDifficulty: number, clockRate: number): number;
1441
1466
  }
1442
1467
 
1443
1468
  /**
1444
1469
  * Represents the skill required to memorize and hit every object in a beatmap with the Flashlight mod enabled.
1445
1470
  */
1446
1471
  declare class OsuFlashlight extends OsuSkill {
1447
- protected readonly skillMultiplier: number;
1448
- protected readonly strainDecayBase: number;
1449
- protected readonly reducedSectionCount: number;
1450
- protected readonly reducedSectionBaseline: number;
1451
- protected readonly decayWeight: number;
1452
- private readonly isHidden;
1453
- constructor(mods: Mod[]);
1454
- /**
1455
- * @param current The hitobject to calculate.
1456
- */
1457
- protected strainValueAt(current: DifficultyHitObject): number;
1458
- protected saveToHitObject(current: DifficultyHitObject): void;
1472
+ protected readonly strainDecayBase = 0.15;
1473
+ protected readonly reducedSectionCount = 0;
1474
+ protected readonly reducedSectionBaseline = 1;
1475
+ protected readonly decayWeight = 1;
1476
+ private currentFlashlightStrain;
1477
+ private readonly skillMultiplier;
1478
+ static difficultyToPerformance(difficulty: number): number;
1479
+ difficultyValue(): number;
1480
+ protected strainValueAt(current: OsuDifficultyHitObject): number;
1481
+ protected calculateInitialStrain(time: number, current: OsuDifficultyHitObject): number;
1482
+ protected saveToHitObject(current: OsuDifficultyHitObject): void;
1459
1483
  }
1460
1484
 
1461
1485
  /**
1462
1486
  * An evaluator for calculating osu!standard Flashlight skill.
1463
1487
  */
1464
- declare abstract class OsuFlashlightEvaluator extends FlashlightEvaluator {
1488
+ declare abstract class OsuFlashlightEvaluator {
1489
+ private static readonly maxOpacityBonus;
1490
+ private static readonly hiddenBonus;
1491
+ private static readonly minVelocity;
1492
+ private static readonly sliderMultiplier;
1493
+ private static readonly minAngleMultiplier;
1465
1494
  /**
1466
1495
  * Evaluates the difficulty of memorizing and hitting the current object, based on:
1467
1496
  *
@@ -1472,15 +1501,15 @@ declare abstract class OsuFlashlightEvaluator extends FlashlightEvaluator {
1472
1501
  * - and whether Hidden mod is enabled.
1473
1502
  *
1474
1503
  * @param current The current object.
1475
- * @param isHiddenMod Whether the Hidden mod is enabled.
1504
+ * @param mods The mods used.
1476
1505
  */
1477
- static evaluateDifficultyOf(current: DifficultyHitObject, isHiddenMod: boolean): number;
1506
+ static evaluateDifficultyOf(current: OsuDifficultyHitObject, mods: ModMap): number;
1478
1507
  }
1479
1508
 
1480
1509
  /**
1481
1510
  * A performance points calculator that calculates performance points for osu!standard gamemode.
1482
1511
  */
1483
- declare class OsuPerformanceCalculator extends PerformanceCalculator {
1512
+ declare class OsuPerformanceCalculator extends PerformanceCalculator<IOsuDifficultyAttributes> {
1484
1513
  /**
1485
1514
  * The aim performance value.
1486
1515
  */
@@ -1497,15 +1526,19 @@ declare class OsuPerformanceCalculator extends PerformanceCalculator {
1497
1526
  * The flashlight performance value.
1498
1527
  */
1499
1528
  flashlight: number;
1500
- readonly difficultyAttributes: OsuDifficultyAttributes;
1501
- protected finalMultiplier: number;
1502
- protected readonly mode: Modes;
1503
1529
  /**
1504
- * @param difficultyAttributes The difficulty attributes to calculate.
1505
- */
1506
- constructor(difficultyAttributes: OsuDifficultyAttributes);
1530
+ * The amount of misses, including slider breaks.
1531
+ */
1532
+ get effectiveMissCount(): number;
1533
+ static readonly finalMultiplier = 1.14;
1534
+ private greatWindow;
1535
+ private okWindow;
1536
+ private mehWindow;
1537
+ private approachRate;
1538
+ private overallDifficulty;
1539
+ private _effectiveMissCount;
1540
+ private speedDeviation;
1507
1541
  protected calculateValues(): void;
1508
- protected calculateTotalValue(): void;
1509
1542
  /**
1510
1543
  * Calculates the aim performance value of the beatmap.
1511
1544
  */
@@ -1522,55 +1555,105 @@ declare class OsuPerformanceCalculator extends PerformanceCalculator {
1522
1555
  * Calculates the flashlight performance value of the beatmap.
1523
1556
  */
1524
1557
  private calculateFlashlightValue;
1558
+ /**
1559
+ * Calculates a strain-based miss penalty.
1560
+ *
1561
+ * Strain-based miss penalty assumes that a player will miss on the hardest parts of a map,
1562
+ * so we use the amount of relatively difficult sections to adjust miss penalty
1563
+ * to make it more punishing on maps with lower amount of hard sections.
1564
+ */
1565
+ private calculateMissPenalty;
1566
+ /**
1567
+ * Estimates a player's deviation on speed notes using {@link calculateDeviation}, assuming worst-case.
1568
+ *
1569
+ * Treats all speed notes as hit circles.
1570
+ */
1571
+ private calculateSpeedDeviation;
1572
+ /**
1573
+ * Estimates the player's tap deviation based on the OD, given number of greats, oks, mehs and misses,
1574
+ * assuming the player's mean hit error is 0. The estimation is consistent in that two SS scores on the
1575
+ * same map with the same settings will always return the same deviation.
1576
+ *
1577
+ * Misses are ignored because they are usually due to misaiming.
1578
+ *
1579
+ * Greats and oks are assumed to follow a normal distribution, whereas mehs are assumed to follow a uniform distribution.
1580
+ */
1581
+ private calculateDeviation;
1582
+ /**
1583
+ * Calculates multiplier for speed to account for improper tapping based on the deviation and speed difficulty.
1584
+ *
1585
+ * [Graph](https://www.desmos.com/calculator/dmogdhzofn)
1586
+ */
1587
+ private calculateSpeedHighDeviationNerf;
1588
+ private calculateEstimatedSliderBreaks;
1589
+ /**
1590
+ * Calculates the amount of misses + sliderbreaks from combo.
1591
+ */
1592
+ private calculateComboBasedEstimatedMissCount;
1525
1593
  toString(): string;
1526
1594
  }
1527
1595
 
1528
1596
  /**
1529
1597
  * An evaluator for calculating osu!standard Rhythm skill.
1530
1598
  */
1531
- declare abstract class OsuRhythmEvaluator extends RhythmEvaluator {
1599
+ declare abstract class OsuRhythmEvaluator {
1600
+ private static readonly historyTimeMax;
1601
+ private static readonly historyObjectsMax;
1602
+ private static readonly rhythmOverallMultiplier;
1603
+ private static readonly rhythmRatioMultiplier;
1532
1604
  /**
1533
1605
  * Calculates a rhythm multiplier for the difficulty of the tap associated
1534
1606
  * with historic data of the current object.
1535
1607
  *
1536
1608
  * @param current The current object.
1537
- * @param greatWindow The great hit window of the current object.
1538
1609
  */
1539
- static evaluateDifficultyOf(current: DifficultyHitObject, greatWindow: number): number;
1610
+ static evaluateDifficultyOf(current: OsuDifficultyHitObject): number;
1540
1611
  }
1541
1612
 
1542
1613
  /**
1543
1614
  * Represents the skill required to press keys or tap with regards to keeping up with the speed at which objects need to be hit.
1544
1615
  */
1545
1616
  declare class OsuSpeed extends OsuSkill {
1546
- protected readonly skillMultiplier: number;
1547
- protected readonly strainDecayBase: number;
1548
- protected readonly reducedSectionCount: number;
1549
- protected readonly reducedSectionBaseline: number;
1550
- protected readonly difficultyMultiplier: number;
1551
- protected readonly decayWeight: number;
1617
+ protected readonly strainDecayBase = 0.3;
1618
+ protected readonly reducedSectionCount = 5;
1619
+ protected readonly reducedSectionBaseline = 0.75;
1620
+ protected readonly decayWeight = 0.9;
1552
1621
  private currentSpeedStrain;
1553
1622
  private currentRhythm;
1554
- private readonly greatWindow;
1555
- constructor(mods: Mod[], greatWindow: number);
1623
+ private readonly skillMultiplier;
1624
+ private readonly sliderStrains;
1625
+ private maxStrain;
1626
+ /**
1627
+ * The amount of notes that are relevant to the difficulty.
1628
+ */
1629
+ relevantNoteCount(): number;
1630
+ /**
1631
+ * Obtains the amount of sliders that are considered difficult in terms of relative strain, weighted by consistency.
1632
+ */
1633
+ countTopWeightedSliders(): number;
1556
1634
  /**
1557
1635
  * @param current The hitobject to calculate.
1558
1636
  */
1559
- protected strainValueAt(current: DifficultyHitObject): number;
1637
+ protected strainValueAt(current: OsuDifficultyHitObject): number;
1638
+ protected calculateInitialStrain(time: number, current: OsuDifficultyHitObject): number;
1560
1639
  /**
1561
1640
  * @param current The hitobject to save to.
1562
1641
  */
1563
- protected saveToHitObject(current: DifficultyHitObject): void;
1642
+ protected saveToHitObject(current: OsuDifficultyHitObject): void;
1564
1643
  }
1565
1644
 
1566
1645
  /**
1567
1646
  * An evaluator for calculating osu!standard speed skill.
1568
1647
  */
1569
- declare abstract class OsuSpeedEvaluator extends SpeedEvaluator {
1648
+ declare abstract class OsuSpeedEvaluator {
1570
1649
  /**
1571
1650
  * Spacing threshold for a single hitobject spacing.
1651
+ *
1652
+ * About 1.25 circles distance between hitobject centers.
1572
1653
  */
1573
1654
  private static readonly SINGLE_SPACING_THRESHOLD;
1655
+ private static readonly minSpeedBonus;
1656
+ private static readonly DISTANCE_MULTIPLIER;
1574
1657
  /**
1575
1658
  * Evaluates the difficulty of tapping the current object, based on:
1576
1659
  *
@@ -1579,9 +1662,10 @@ declare abstract class OsuSpeedEvaluator extends SpeedEvaluator {
1579
1662
  * - and how easily they can be cheesed.
1580
1663
  *
1581
1664
  * @param current The current object.
1582
- * @param greatWindow The great hit window of the current object.
1665
+ * @param mods The mods applied.
1583
1666
  */
1584
- static evaluateDifficultyOf(current: DifficultyHitObject, greatWindow: number): number;
1667
+ static evaluateDifficultyOf(current: OsuDifficultyHitObject, mods: ModMap): number;
1585
1668
  }
1586
1669
 
1587
- export { AimEvaluator, CalculationOptions, DifficultSlider, DifficultyAttributes, DifficultyCalculationOptions, DifficultyCalculator, DifficultyHitObject, DifficultyHitObjectCreator, DroidAim, DroidAimEvaluator, DroidDifficultyAttributes, DroidDifficultyCalculator, DroidFlashlight, DroidFlashlightEvaluator, DroidPerformanceCalculator, DroidRhythm, DroidRhythmEvaluator, DroidTap, DroidTapEvaluator, DroidVisual, DroidVisualEvaluator, ExtendedDroidDifficultyAttributes, FlashlightEvaluator, HighStrainSection, MapStars, OsuAim, OsuAimEvaluator, OsuDifficultyAttributes, OsuDifficultyCalculator, OsuFlashlight, OsuFlashlightEvaluator, OsuPerformanceCalculator, OsuRhythmEvaluator, OsuSpeed, OsuSpeedEvaluator, PerformanceCalculationOptions, PerformanceCalculator, RhythmEvaluator, SpeedEvaluator, StrainPeaks };
1670
+ export { DifficultyAttributes, DifficultyCalculator, DifficultyHitObject, DroidAim, DroidAimEvaluator, DroidDifficultyAttributes, DroidDifficultyCalculator, DroidDifficultyHitObject, DroidFlashlight, DroidFlashlightEvaluator, DroidPerformanceCalculator, DroidReading, DroidReadingEvaluator, DroidRhythm, DroidRhythmEvaluator, DroidTap, DroidTapEvaluator, ExtendedDroidDifficultyAttributes, OsuAim, OsuAimEvaluator, OsuDifficultyAttributes, OsuDifficultyCalculator, OsuDifficultyHitObject, OsuFlashlight, OsuFlashlightEvaluator, OsuPerformanceCalculator, OsuRhythmEvaluator, OsuSpeed, OsuSpeedEvaluator, PerformanceCalculator };
1671
+ export type { CacheableDifficultyAttributes, DifficultSlider, HighStrainSection, IDifficultyAttributes, IDroidDifficultyAttributes, IExtendedDroidDifficultyAttributes, IOsuDifficultyAttributes, PerformanceCalculationOptions, StrainPeaks };