@rian8337/osu-difficulty-calculator 4.0.0-beta.91 → 4.0.0-beta.93

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -223,8 +223,17 @@ class Skill {
223
223
  get objectDifficulties() {
224
224
  return this._objectDifficulties;
225
225
  }
226
+ /**
227
+ * The start times of {@link DifficultyHitObject}s, populated by {@link Skill.process}.
228
+ *
229
+ * Indices correspond to {@link objectDifficulties}.
230
+ */
231
+ get objectTimes() {
232
+ return this._objectTimes;
233
+ }
226
234
  constructor(mods) {
227
235
  this._objectDifficulties = [];
236
+ this._objectTimes = [];
228
237
  this.mods = mods;
229
238
  }
230
239
  /**
@@ -240,6 +249,7 @@ class Skill {
240
249
  const difficultyValue = this.processInternal(current);
241
250
  this.saveToHitObject(current, difficultyValue);
242
251
  this._objectDifficulties.push(difficultyValue);
252
+ this._objectTimes.push(current.startTime);
243
253
  }
244
254
  /**
245
255
  * Saves the calculated difficulty to a {@link DifficultyHitObject}.
@@ -276,6 +286,12 @@ class VariableLengthStrainSkill extends Skill {
276
286
  this.currentSectionEnd = 0;
277
287
  this.totalLength = 0;
278
288
  this.strainPeaks = [];
289
+ /**
290
+ * The peaks of this skill in chronological order, used for graphing purposes.
291
+ *
292
+ * Unlike {@link strainPeaks}, this is never trimmed, as it does not contribute to {@link difficultyValue}.
293
+ */
294
+ this.chronologicalPeaks = [];
279
295
  /**
280
296
  * Stores previous strains so that, if a difficult {@link DifficultyHitObject} is followed by an easier
281
297
  * {@link DifficultyHitObject}, the difficult one gets a full strain instead of being cut short.
@@ -294,7 +310,10 @@ class VariableLengthStrainSkill extends Skill {
294
310
  return 11 / (1 - this.decayWeight);
295
311
  }
296
312
  get peaks() {
297
- return this.currentStrainPeaks.map((s) => s.value);
313
+ return this.chronologicalPeaks.concat({
314
+ time: this.currentSectionEnd,
315
+ value: this.currentSectionPeak,
316
+ });
298
317
  }
299
318
  static difficultyToPerformance(difficulty) {
300
319
  return 4 * Math.pow(difficulty, 3);
@@ -442,6 +461,10 @@ class VariableLengthStrainSkill extends Skill {
442
461
  */
443
462
  saveCurrentPeak(sectionLength) {
444
463
  this.addStrainPeakInPlace(new StrainPeak(this.currentSectionPeak, sectionLength));
464
+ this.chronologicalPeaks.push({
465
+ time: this.currentSectionBegin + sectionLength,
466
+ value: this.currentSectionPeak,
467
+ });
445
468
  this.totalLength += sectionLength;
446
469
  // Remove from the front of our strain peaks if there is any which are too deep to contribute to difficulty.
447
470
  // `maxStoredSections` dictates for us how many sections will preserve at least 99.999% of difficulty.
@@ -1095,6 +1118,7 @@ class StrainSkill extends Skill {
1095
1118
  this.reducedSectionBaseline = 0.75;
1096
1119
  this._objectStrains = [];
1097
1120
  this.strainPeaks = [];
1121
+ this.strainPeakTimes = [];
1098
1122
  this.sectionLength = 400;
1099
1123
  this.currentStrain = 0;
1100
1124
  this.currentSectionPeak = 0;
@@ -1107,7 +1131,9 @@ class StrainSkill extends Skill {
1107
1131
  return this._objectStrains;
1108
1132
  }
1109
1133
  get peaks() {
1110
- return this.currentStrainPeaks;
1134
+ return this.strainPeaks
1135
+ .map((value, i) => ({ time: this.strainPeakTimes[i], value }))
1136
+ .concat({ time: this.currentSectionEnd, value: this.currentSectionPeak });
1111
1137
  }
1112
1138
  /**
1113
1139
  * Converts a difficulty value to a performance value.
@@ -1175,6 +1201,7 @@ class StrainSkill extends Skill {
1175
1201
  */
1176
1202
  saveCurrentPeak() {
1177
1203
  this.strainPeaks.push(this.currentSectionPeak);
1204
+ this.strainPeakTimes.push(this.currentSectionEnd);
1178
1205
  }
1179
1206
  /**
1180
1207
  * Sets the initial strain level for a new section.
@@ -1278,7 +1305,7 @@ class DroidFlashlight extends DroidSkill {
1278
1305
  return sum;
1279
1306
  }
1280
1307
  calculateAdjustedDifficulty(current) {
1281
- let difficulty = Math.pow(DroidFlashlightEvaluator.evaluateDifficultyOf(current, this.mods), 0.8);
1308
+ let difficulty = Math.pow(DroidFlashlightEvaluator.evaluateDifficultyOf(current, this.mods), 0.9);
1282
1309
  if (this.mods.has(osuBase.ModRelax)) {
1283
1310
  difficulty *= 0.7;
1284
1311
  }
@@ -1322,7 +1349,10 @@ class HarmonicSkill extends Skill {
1322
1349
  return this._noteWeightSum;
1323
1350
  }
1324
1351
  get peaks() {
1325
- return this.objectDifficulties;
1352
+ return this.objectDifficulties.map((value, i) => ({
1353
+ time: this.objectTimes[i],
1354
+ value,
1355
+ }));
1326
1356
  }
1327
1357
  static difficultyToPerformance(difficulty) {
1328
1358
  return 4 * Math.pow(difficulty, 3);
@@ -1901,7 +1931,7 @@ class DroidPerformanceCalculator extends PerformanceCalculator {
1901
1931
  * The reading performance value.
1902
1932
  */
1903
1933
  this.reading = 0;
1904
- this._aimSliderCheesePenalty = 1;
1934
+ this._sliderCheesePenalty = 1;
1905
1935
  this._tapPenalty = 1;
1906
1936
  this._effectiveMissCount = 0;
1907
1937
  this._deviation = 0;
@@ -1933,8 +1963,8 @@ class DroidPerformanceCalculator extends PerformanceCalculator {
1933
1963
  *
1934
1964
  * Can be properly obtained by analyzing the replay associated with the score.
1935
1965
  */
1936
- get aimSliderCheesePenalty() {
1937
- return this._aimSliderCheesePenalty;
1966
+ get sliderCheesePenalty() {
1967
+ return this._sliderCheesePenalty;
1938
1968
  }
1939
1969
  /**
1940
1970
  * The total score achieved in the score.
@@ -1994,7 +2024,7 @@ class DroidPerformanceCalculator extends PerformanceCalculator {
1994
2024
  handleOptions(options) {
1995
2025
  var _a, _b;
1996
2026
  this._tapPenalty = osuBase.MathUtils.clamp((_a = options === null || options === void 0 ? void 0 : options.tapPenalty) !== null && _a !== void 0 ? _a : 1, 0, 1);
1997
- this._aimSliderCheesePenalty = osuBase.MathUtils.clamp((_b = options === null || options === void 0 ? void 0 : options.aimSliderCheesePenalty) !== null && _b !== void 0 ? _b : 1, 0, 1);
2027
+ this._sliderCheesePenalty = osuBase.MathUtils.clamp((_b = options === null || options === void 0 ? void 0 : options.sliderCheesePenalty) !== null && _b !== void 0 ? _b : 1, 0, 1);
1998
2028
  this._totalScore =
1999
2029
  (options === null || options === void 0 ? void 0 : options.totalScore) !== undefined
2000
2030
  ? osuBase.MathUtils.clamp(options.totalScore, 0, this.difficultyAttributes.maximumScore)
@@ -2036,7 +2066,7 @@ class DroidPerformanceCalculator extends PerformanceCalculator {
2036
2066
  // Scale the aim value with estimated full combo deviation.
2037
2067
  aimValue *= this.calculateDeviationBasedLengthScaling();
2038
2068
  // Scale the aim value with slider cheese penalty.
2039
- aimValue *= this._aimSliderCheesePenalty;
2069
+ aimValue *= this._sliderCheesePenalty;
2040
2070
  // Scale the aim value with deviation.
2041
2071
  aimValue *=
2042
2072
  1.025 *
@@ -3161,9 +3191,15 @@ class DroidRhythmEvaluator {
3161
3191
  // Scale down the difficulty if the object is doubletappable.
3162
3192
  effectiveRatio *=
3163
3193
  1 - prevObject.getDoubletapness(currentObject) * 0.75;
3164
- rhythmComplexitySum +=
3165
- Math.sqrt(effectiveRatio * startRatio) *
3166
- currentHistoricalDecay;
3194
+ if (island.deltaCount > 1) {
3195
+ rhythmComplexitySum +=
3196
+ Math.sqrt(effectiveRatio * startRatio) *
3197
+ currentHistoricalDecay;
3198
+ }
3199
+ else {
3200
+ // Constant difficulty for single-note islands.
3201
+ rhythmComplexitySum += 0.7 * currentHistoricalDecay;
3202
+ }
3167
3203
  startRatio = effectiveRatio;
3168
3204
  previousIsland = island;
3169
3205
  if (prevDelta + deltaDifferenceEpsilon < currentDelta) {
@@ -3348,15 +3384,12 @@ class ExtendedDroidDifficultyAttributes extends DroidDifficultyAttributes {
3348
3384
  this.mode = "live";
3349
3385
  this.possibleThreeFingeredSections = [];
3350
3386
  this.difficultSliders = [];
3351
- this.flashlightSliderFactor = 1;
3352
3387
  if (!cacheableAttributes) {
3353
3388
  return;
3354
3389
  }
3355
3390
  this.possibleThreeFingeredSections =
3356
3391
  cacheableAttributes.possibleThreeFingeredSections;
3357
3392
  this.difficultSliders = cacheableAttributes.difficultSliders;
3358
- this.flashlightSliderFactor =
3359
- cacheableAttributes.flashlightSliderFactor;
3360
3393
  }
3361
3394
  }
3362
3395
 
@@ -3422,9 +3455,10 @@ class DroidDifficultyCalculator extends DifficultyCalculator {
3422
3455
  attributes.sliderCount = playableBeatmap.hitObjects.sliders;
3423
3456
  attributes.spinnerCount = playableBeatmap.hitObjects.spinners;
3424
3457
  attributes.overallDifficulty = playableBeatmap.difficulty.od;
3425
- attributes.maximumScore =
3426
- beatmap.maxDroidScore(playableBeatmap.mods) +
3427
- DroidScoreUtils.calculateMaximumSpinnerBonus(playableBeatmap);
3458
+ // Cap at 32-bit signed integer since that's the maximum score that can be submitted
3459
+ // to the game's leaderboards.
3460
+ attributes.maximumScore = Math.min(beatmap.maxDroidScore(playableBeatmap.mods) +
3461
+ DroidScoreUtils.calculateMaximumSpinnerBonus(playableBeatmap), 2147483647);
3428
3462
  this.populateAimAttributes(attributes, skills, objects);
3429
3463
  this.populateTapAttributes(attributes, skills, objects);
3430
3464
  this.populateRhythmAttributes(attributes, skills);
@@ -3497,8 +3531,9 @@ class DroidDifficultyCalculator extends DifficultyCalculator {
3497
3531
  attributes.aimDifficultSliderCount = aim.countDifficultSliders();
3498
3532
  attributes.aimDifficultStrainCount =
3499
3533
  aim.countTopWeightedStrains(aimDifficultyValue);
3500
- const aimNoSliderTopWeightedSliderCount = aimNoSlider.countTopWeightedSliders(aimDifficultyValue);
3501
- const aimNoSliderDifficultStrainCount = aimNoSlider.countTopWeightedStrains(aimDifficultyValue);
3534
+ const aimNoSliderDifficultyValue = aimNoSlider.difficultyValue();
3535
+ const aimNoSliderTopWeightedSliderCount = aimNoSlider.countTopWeightedSliders(aimNoSliderDifficultyValue);
3536
+ const aimNoSliderDifficultStrainCount = aimNoSlider.countTopWeightedStrains(aimNoSliderDifficultyValue);
3502
3537
  attributes.aimTopWeightedSliderFactor =
3503
3538
  aimNoSliderTopWeightedSliderCount /
3504
3539
  Math.max(1, aimNoSliderDifficultStrainCount -
@@ -3533,7 +3568,8 @@ class DroidDifficultyCalculator extends DifficultyCalculator {
3533
3568
  }
3534
3569
  if (attributes.aimDifficulty > 0) {
3535
3570
  attributes.sliderFactor =
3536
- this.calculateAimDifficultyRating(aimNoSlider.difficultyValue()) / this.calculateAimDifficultyRating(aimDifficultyValue);
3571
+ this.calculateAimDifficultyRating(aimNoSliderDifficultyValue) /
3572
+ this.calculateAimDifficultyRating(aimDifficultyValue);
3537
3573
  }
3538
3574
  else {
3539
3575
  attributes.sliderFactor = 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rian8337/osu-difficulty-calculator",
3
- "version": "4.0.0-beta.91",
3
+ "version": "4.0.0-beta.93",
4
4
  "description": "A module for calculating osu!standard beatmap difficulty and performance value with respect to the current difficulty and performance algorithm.",
5
5
  "keywords": [
6
6
  "osu",
@@ -38,5 +38,5 @@
38
38
  "publishConfig": {
39
39
  "access": "public"
40
40
  },
41
- "gitHead": "21fc1a8f1ddf89952521bf670a84dcfdd0495ed7"
41
+ "gitHead": "9eee7ed61799bd3d1a9510e0491e7433932ba190"
42
42
  }
@@ -336,6 +336,20 @@ declare abstract class DifficultyAttributes implements IDifficultyAttributes {
336
336
  toString(): string;
337
337
  }
338
338
 
339
+ /**
340
+ * Represents a strain peak at a specific point in time.
341
+ */
342
+ interface TimedStrainPeak {
343
+ /**
344
+ * The time at which this peak occurs, in milliseconds.
345
+ */
346
+ readonly time: number;
347
+ /**
348
+ * The strain value of this peak.
349
+ */
350
+ readonly value: number;
351
+ }
352
+
339
353
  /**
340
354
  * Represents the strain peaks of various calculated difficulties.
341
355
  */
@@ -343,19 +357,19 @@ interface StrainPeaks {
343
357
  /**
344
358
  * The strain peaks of aim difficulty if sliders are considered.
345
359
  */
346
- aimWithSliders: readonly number[];
360
+ aimWithSliders: readonly TimedStrainPeak[];
347
361
  /**
348
362
  * The strain peaks of aim difficulty if sliders are not considered.
349
363
  */
350
- aimWithoutSliders: readonly number[];
364
+ aimWithoutSliders: readonly TimedStrainPeak[];
351
365
  /**
352
366
  * The strain peaks of speed difficulty.
353
367
  */
354
- speed: readonly number[];
368
+ speed: readonly TimedStrainPeak[];
355
369
  /**
356
370
  * The strain peaks of flashlight difficulty.
357
371
  */
358
- flashlight: readonly number[];
372
+ flashlight: readonly TimedStrainPeak[];
359
373
  }
360
374
 
361
375
  /**
@@ -363,9 +377,9 @@ interface StrainPeaks {
363
377
  */
364
378
  interface IHasPeakDifficulty {
365
379
  /**
366
- * The peak difficulties calculated by this `Skill`.
380
+ * The peak difficulties calculated by this `Skill`, in chronological order.
367
381
  */
368
- get peaks(): readonly number[];
382
+ get peaks(): readonly TimedStrainPeak[];
369
383
  }
370
384
 
371
385
  /**
@@ -377,10 +391,17 @@ declare abstract class Skill {
377
391
  */
378
392
  protected readonly mods: ModMap;
379
393
  private _objectDifficulties;
394
+ private _objectTimes;
380
395
  /**
381
396
  * The difficulties of {@link DifficultyHitObject}s, populated by {@link Skill.process}.
382
397
  */
383
398
  protected get objectDifficulties(): readonly number[];
399
+ /**
400
+ * The start times of {@link DifficultyHitObject}s, populated by {@link Skill.process}.
401
+ *
402
+ * Indices correspond to {@link objectDifficulties}.
403
+ */
404
+ protected get objectTimes(): readonly number[];
384
405
  constructor(mods: ModMap);
385
406
  /**
386
407
  * Calculates the strain value of a hitobject and stores the value in it.
@@ -520,7 +541,7 @@ interface PerformanceCalculationOptions {
520
541
  /**
521
542
  * The aim slider cheese penalty to apply for penalized scores. Only used when using `DroidPerformanceCalculator`.
522
543
  */
523
- aimSliderCheesePenalty?: number;
544
+ sliderCheesePenalty?: number;
524
545
  /**
525
546
  * The total score achieved in the score.
526
547
  */
@@ -775,14 +796,6 @@ interface IExtendedDroidDifficultyAttributes extends IDroidDifficultyAttributes
775
796
  * Sliders that are considered difficult.
776
797
  */
777
798
  difficultSliders: DifficultSlider[];
778
- /**
779
- * Describes how much of flashlight difficulty is contributed to by hitcircles or sliders.
780
- *
781
- * A value closer to 1 indicates most of flashlight difficulty is contributed by hitcircles.
782
- *
783
- * A value closer to 0 indicates most of flashlight difficulty is contributed by sliders.
784
- */
785
- flashlightSliderFactor: number;
786
799
  }
787
800
 
788
801
  /**
@@ -793,7 +806,6 @@ declare class ExtendedDroidDifficultyAttributes extends DroidDifficultyAttribute
793
806
  mode: "live";
794
807
  possibleThreeFingeredSections: HighStrainSection[];
795
808
  difficultSliders: DifficultSlider[];
796
- flashlightSliderFactor: number;
797
809
  constructor(cacheableAttributes?: CacheableDifficultyAttributes<IExtendedDroidDifficultyAttributes>);
798
810
  }
799
811
 
@@ -861,7 +873,7 @@ declare class DroidPerformanceCalculator extends PerformanceCalculator<IDroidDif
861
873
  *
862
874
  * Can be properly obtained by analyzing the replay associated with the score.
863
875
  */
864
- get aimSliderCheesePenalty(): number;
876
+ get sliderCheesePenalty(): number;
865
877
  /**
866
878
  * The total score achieved in the score.
867
879
  */
@@ -872,7 +884,7 @@ declare class DroidPerformanceCalculator extends PerformanceCalculator<IDroidDif
872
884
  get effectiveMissCount(): number;
873
885
  static readonly finalMultiplier = 1.24;
874
886
  static readonly normExponent = 1.1;
875
- private _aimSliderCheesePenalty;
887
+ private _sliderCheesePenalty;
876
888
  private _tapPenalty;
877
889
  private _effectiveMissCount;
878
890
  private _deviation;
@@ -1272,7 +1284,8 @@ declare abstract class StrainSkill extends Skill implements IHasPeakDifficulty {
1272
1284
  */
1273
1285
  get objectStrains(): readonly number[];
1274
1286
  protected readonly strainPeaks: number[];
1275
- get peaks(): readonly number[];
1287
+ private readonly strainPeakTimes;
1288
+ get peaks(): readonly TimedStrainPeak[];
1276
1289
  private readonly sectionLength;
1277
1290
  private currentStrain;
1278
1291
  private currentSectionPeak;
@@ -1540,7 +1553,13 @@ declare abstract class VariableLengthStrainSkill extends Skill implements IHasPe
1540
1553
  private currentSectionEnd;
1541
1554
  private totalLength;
1542
1555
  private readonly strainPeaks;
1543
- get peaks(): readonly number[];
1556
+ /**
1557
+ * The peaks of this skill in chronological order, used for graphing purposes.
1558
+ *
1559
+ * Unlike {@link strainPeaks}, this is never trimmed, as it does not contribute to {@link difficultyValue}.
1560
+ */
1561
+ private readonly chronologicalPeaks;
1562
+ get peaks(): readonly TimedStrainPeak[];
1544
1563
  /**
1545
1564
  * Stores previous strains so that, if a difficult {@link DifficultyHitObject} is followed by an easier
1546
1565
  * {@link DifficultyHitObject}, the difficult one gets a full strain instead of being cut short.
@@ -1715,7 +1734,7 @@ declare abstract class HarmonicSkill extends Skill implements IHasPeakDifficulty
1715
1734
  * Values closer to 1 decay faster, whilst lower values give more weight to easier {@link DifficultyHitObject}s.
1716
1735
  */
1717
1736
  protected readonly decayExponent: number;
1718
- get peaks(): readonly number[];
1737
+ get peaks(): readonly TimedStrainPeak[];
1719
1738
  static difficultyToPerformance(difficulty: number): number;
1720
1739
  difficultyValue(): number;
1721
1740
  /**
@@ -1887,4 +1906,4 @@ declare class OsuSpeed extends OsuSkill {
1887
1906
  }
1888
1907
 
1889
1908
  export { DifficultyAttributes, DifficultyCalculator, DifficultyHitObject, DroidAgilityEvaluator, DroidAim, DroidDifficultyAttributes, DroidDifficultyCalculator, DroidDifficultyHitObject, DroidFlashlight, DroidFlashlightEvaluator, DroidFlowAimEvaluator, DroidPerformanceCalculator, DroidReading, DroidReadingEvaluator, DroidRhythm, DroidRhythmEvaluator, DroidSnapAimEvaluator, DroidTap, DroidTapEvaluator, ExtendedDroidDifficultyAttributes, OsuAim, OsuAimEvaluator, OsuDifficultyAttributes, OsuDifficultyCalculator, OsuDifficultyHitObject, OsuFlashlight, OsuFlashlightEvaluator, OsuPerformanceCalculator, OsuRhythmEvaluator, OsuSpeed, OsuSpeedEvaluator, PerformanceCalculator };
1890
- export type { CacheableDifficultyAttributes, DifficultSlider, HighStrainSection, IDifficultyAttributes, IDroidDifficultyAttributes, IExtendedDroidDifficultyAttributes, IOsuDifficultyAttributes, PerformanceCalculationOptions, StrainPeaks };
1909
+ export type { CacheableDifficultyAttributes, DifficultSlider, HighStrainSection, IDifficultyAttributes, IDroidDifficultyAttributes, IExtendedDroidDifficultyAttributes, IOsuDifficultyAttributes, PerformanceCalculationOptions, StrainPeaks, TimedStrainPeak };