@rian8337/osu-difficulty-calculator 4.0.0-beta.92 → 4.0.0-beta.94

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.
@@ -1241,8 +1268,8 @@ class DroidFlashlight extends DroidSkill {
1241
1268
  this.totalObjects = totalObjects;
1242
1269
  this.reducedSectionCount = 0;
1243
1270
  this.reducedSectionBaseline = 1;
1244
- this.starsPerDouble = 1.06;
1245
- this.skillMultiplier = 0.024;
1271
+ this.starsPerDouble = 1;
1272
+ this.skillMultiplier = 0.058;
1246
1273
  this.currentFlashlightStrain = 0;
1247
1274
  }
1248
1275
  strainValueAt(current) {
@@ -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);
@@ -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) {
@@ -3419,9 +3455,10 @@ class DroidDifficultyCalculator extends DifficultyCalculator {
3419
3455
  attributes.sliderCount = playableBeatmap.hitObjects.sliders;
3420
3456
  attributes.spinnerCount = playableBeatmap.hitObjects.spinners;
3421
3457
  attributes.overallDifficulty = playableBeatmap.difficulty.od;
3422
- attributes.maximumScore =
3423
- beatmap.maxDroidScore(playableBeatmap.mods) +
3424
- 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);
3425
3462
  this.populateAimAttributes(attributes, skills, objects);
3426
3463
  this.populateTapAttributes(attributes, skills, objects);
3427
3464
  this.populateRhythmAttributes(attributes, skills);
@@ -3494,8 +3531,9 @@ class DroidDifficultyCalculator extends DifficultyCalculator {
3494
3531
  attributes.aimDifficultSliderCount = aim.countDifficultSliders();
3495
3532
  attributes.aimDifficultStrainCount =
3496
3533
  aim.countTopWeightedStrains(aimDifficultyValue);
3497
- const aimNoSliderTopWeightedSliderCount = aimNoSlider.countTopWeightedSliders(aimDifficultyValue);
3498
- const aimNoSliderDifficultStrainCount = aimNoSlider.countTopWeightedStrains(aimDifficultyValue);
3534
+ const aimNoSliderDifficultyValue = aimNoSlider.difficultyValue();
3535
+ const aimNoSliderTopWeightedSliderCount = aimNoSlider.countTopWeightedSliders(aimNoSliderDifficultyValue);
3536
+ const aimNoSliderDifficultStrainCount = aimNoSlider.countTopWeightedStrains(aimNoSliderDifficultyValue);
3499
3537
  attributes.aimTopWeightedSliderFactor =
3500
3538
  aimNoSliderTopWeightedSliderCount /
3501
3539
  Math.max(1, aimNoSliderDifficultStrainCount -
@@ -3530,7 +3568,8 @@ class DroidDifficultyCalculator extends DifficultyCalculator {
3530
3568
  }
3531
3569
  if (attributes.aimDifficulty > 0) {
3532
3570
  attributes.sliderFactor =
3533
- this.calculateAimDifficultyRating(aimNoSlider.difficultyValue()) / this.calculateAimDifficultyRating(aimDifficultyValue);
3571
+ this.calculateAimDifficultyRating(aimNoSliderDifficultyValue) /
3572
+ this.calculateAimDifficultyRating(aimDifficultyValue);
3534
3573
  }
3535
3574
  else {
3536
3575
  attributes.sliderFactor = 1;
@@ -3601,13 +3640,9 @@ class DroidDifficultyCalculator extends DifficultyCalculator {
3601
3640
  attributes.rhythmDifficulty = this.calculateDifficultyRating((_a = rhythm === null || rhythm === void 0 ? void 0 : rhythm.difficultyValue()) !== null && _a !== void 0 ? _a : 0);
3602
3641
  }
3603
3642
  populateFlashlightAttributes(attributes, skills) {
3643
+ var _a;
3604
3644
  const flashlight = skills.find((s) => s instanceof DroidFlashlight);
3605
- if (!flashlight) {
3606
- attributes.flashlightDifficulty = 0;
3607
- return;
3608
- }
3609
- attributes.flashlightDifficulty =
3610
- Math.sqrt(flashlight.difficultyValue()) * 0.18;
3645
+ attributes.flashlightDifficulty = this.calculateDifficultyRating((_a = flashlight === null || flashlight === void 0 ? void 0 : flashlight.difficultyValue()) !== null && _a !== void 0 ? _a : 0);
3611
3646
  }
3612
3647
  populateReadingAttributes(attributes, skills) {
3613
3648
  const reading = skills.find((s) => s instanceof DroidReading);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rian8337/osu-difficulty-calculator",
3
- "version": "4.0.0-beta.92",
3
+ "version": "4.0.0-beta.94",
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": "102e7baf7e9179caf6b0e81cafb53d6e3a561184"
41
+ "gitHead": "ab494480e1793b60e89b1e8b56c73b7a6995ff1f"
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.
@@ -1263,7 +1284,8 @@ declare abstract class StrainSkill extends Skill implements IHasPeakDifficulty {
1263
1284
  */
1264
1285
  get objectStrains(): readonly number[];
1265
1286
  protected readonly strainPeaks: number[];
1266
- get peaks(): readonly number[];
1287
+ private readonly strainPeakTimes;
1288
+ get peaks(): readonly TimedStrainPeak[];
1267
1289
  private readonly sectionLength;
1268
1290
  private currentStrain;
1269
1291
  private currentSectionPeak;
@@ -1531,7 +1553,13 @@ declare abstract class VariableLengthStrainSkill extends Skill implements IHasPe
1531
1553
  private currentSectionEnd;
1532
1554
  private totalLength;
1533
1555
  private readonly strainPeaks;
1534
- 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[];
1535
1563
  /**
1536
1564
  * Stores previous strains so that, if a difficult {@link DifficultyHitObject} is followed by an easier
1537
1565
  * {@link DifficultyHitObject}, the difficult one gets a full strain instead of being cut short.
@@ -1668,7 +1696,7 @@ declare class DroidFlashlight extends DroidSkill {
1668
1696
  private readonly totalObjects;
1669
1697
  protected readonly reducedSectionCount = 0;
1670
1698
  protected readonly reducedSectionBaseline = 1;
1671
- protected readonly starsPerDouble = 1.06;
1699
+ protected readonly starsPerDouble = 1;
1672
1700
  private readonly skillMultiplier;
1673
1701
  private currentFlashlightStrain;
1674
1702
  static difficultyToPerformance(difficulty: number): number;
@@ -1706,7 +1734,7 @@ declare abstract class HarmonicSkill extends Skill implements IHasPeakDifficulty
1706
1734
  * Values closer to 1 decay faster, whilst lower values give more weight to easier {@link DifficultyHitObject}s.
1707
1735
  */
1708
1736
  protected readonly decayExponent: number;
1709
- get peaks(): readonly number[];
1737
+ get peaks(): readonly TimedStrainPeak[];
1710
1738
  static difficultyToPerformance(difficulty: number): number;
1711
1739
  difficultyValue(): number;
1712
1740
  /**
@@ -1878,4 +1906,4 @@ declare class OsuSpeed extends OsuSkill {
1878
1906
  }
1879
1907
 
1880
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 };
1881
- export type { CacheableDifficultyAttributes, DifficultSlider, HighStrainSection, IDifficultyAttributes, IDroidDifficultyAttributes, IExtendedDroidDifficultyAttributes, IOsuDifficultyAttributes, PerformanceCalculationOptions, StrainPeaks };
1909
+ export type { CacheableDifficultyAttributes, DifficultSlider, HighStrainSection, IDifficultyAttributes, IDroidDifficultyAttributes, IExtendedDroidDifficultyAttributes, IOsuDifficultyAttributes, PerformanceCalculationOptions, StrainPeaks, TimedStrainPeak };