@rian8337/osu-difficulty-calculator 4.0.0-beta.51 → 4.0.0-beta.52

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
@@ -287,10 +287,10 @@ class DifficultyHitObject {
287
287
  * Calculates the opacity of the hitobject at a given time.
288
288
  *
289
289
  * @param time The time to calculate the hitobject's opacity at.
290
- * @param isHidden Whether Hidden mod is used.
290
+ * @param mods The mods used.
291
291
  * @returns The opacity of the hitobject at the given time.
292
292
  */
293
- opacityAt(time, isHidden) {
293
+ opacityAt(time, mods) {
294
294
  if (time > this.object.startTime) {
295
295
  // Consider a hitobject as being invisible when its start time is passed.
296
296
  // In reality the hitobject will be visible beyond its start time up until its hittable window has passed,
@@ -299,7 +299,7 @@ class DifficultyHitObject {
299
299
  }
300
300
  const fadeInStartTime = this.object.startTime - this.object.timePreempt;
301
301
  const fadeInDuration = this.object.timeFadeIn;
302
- if (isHidden) {
302
+ if (mods.some((m) => m instanceof osuBase.ModHidden)) {
303
303
  const fadeOutStartTime = fadeInStartTime + fadeInDuration;
304
304
  const fadeOutDuration = this.object.timePreempt * osuBase.ModHidden.fadeOutDurationMultiplier;
305
305
  return Math.min(osuBase.MathUtils.clamp((time - fadeInStartTime) / fadeInDuration, 0, 1), 1 -
@@ -582,6 +582,14 @@ class DroidDifficultyHitObject extends DifficultyHitObject {
582
582
  super.computeProperties(clockRate, hitObjects);
583
583
  this.setVisuals(clockRate, hitObjects);
584
584
  }
585
+ opacityAt(time, mods) {
586
+ // Traceable hides the primary piece of a hit circle (that is, its body), so consider it as fully invisible.
587
+ if (this.object instanceof osuBase.Circle &&
588
+ mods.some((m) => m instanceof osuBase.ModTraceable)) {
589
+ return 0;
590
+ }
591
+ return super.opacityAt(time, mods);
592
+ }
585
593
  /**
586
594
  * Determines whether this hitobject is considered overlapping with the hitobject before it.
587
595
  *
@@ -1219,10 +1227,10 @@ class DroidFlashlightEvaluator {
1219
1227
  * - and whether Hidden mod is enabled.
1220
1228
  *
1221
1229
  * @param current The current object.
1222
- * @param isHiddenMod Whether the Hidden mod is enabled.
1230
+ * @param mods The mods used.
1223
1231
  * @param withSliders Whether to take slider difficulty into account.
1224
1232
  */
1225
- static evaluateDifficultyOf(current, isHiddenMod, withSliders) {
1233
+ static evaluateDifficultyOf(current, mods, withSliders) {
1226
1234
  if (current.object instanceof osuBase.Spinner ||
1227
1235
  // Exclude overlapping objects that can be tapped at once.
1228
1236
  current.isOverlapping(true)) {
@@ -1253,7 +1261,7 @@ class DroidFlashlightEvaluator {
1253
1261
  const opacityBonus = 1 +
1254
1262
  this.maxOpacityBonus *
1255
1263
  (1 -
1256
- current.opacityAt(currentObject.object.startTime, isHiddenMod));
1264
+ current.opacityAt(currentObject.object.startTime, mods));
1257
1265
  result +=
1258
1266
  (stackNerf * opacityBonus * scalingFactor * jumpDistance) /
1259
1267
  cumulativeStrainTime;
@@ -1268,9 +1276,20 @@ class DroidFlashlightEvaluator {
1268
1276
  }
1269
1277
  result = Math.pow(smallDistNerf * result, 2);
1270
1278
  // Additional bonus for Hidden due to there being no approach circles.
1271
- if (isHiddenMod) {
1279
+ if (mods.some((m) => m instanceof osuBase.ModHidden)) {
1272
1280
  result *= 1 + this.hiddenBonus;
1273
1281
  }
1282
+ else if (mods.some((m) => m instanceof osuBase.ModTraceable)) {
1283
+ // Additional bonus for Traceable due to there being no primary or secondary object pieces.
1284
+ if (current.object instanceof osuBase.Circle) {
1285
+ // Additional bonus for hit circles due to there being no circle piece, which is the primary piece.
1286
+ result *= 1 + this.traceableCircleBonus;
1287
+ }
1288
+ else {
1289
+ // The rest of the objects only hide secondary pieces.
1290
+ result *= 1 + this.traceableObjectBonus;
1291
+ }
1292
+ }
1274
1293
  // Nerf patterns with repeated angles.
1275
1294
  result *=
1276
1295
  this.minAngleMultiplier +
@@ -1293,6 +1312,8 @@ class DroidFlashlightEvaluator {
1293
1312
  }
1294
1313
  DroidFlashlightEvaluator.maxOpacityBonus = 0.4;
1295
1314
  DroidFlashlightEvaluator.hiddenBonus = 0.2;
1315
+ DroidFlashlightEvaluator.traceableCircleBonus = 0.15;
1316
+ DroidFlashlightEvaluator.traceableObjectBonus = 0.1;
1296
1317
  DroidFlashlightEvaluator.minVelocity = 0.5;
1297
1318
  DroidFlashlightEvaluator.sliderMultiplier = 1.3;
1298
1319
  DroidFlashlightEvaluator.minAngleMultiplier = 0.2;
@@ -1309,13 +1330,12 @@ class DroidFlashlight extends DroidSkill {
1309
1330
  this.starsPerDouble = 1.06;
1310
1331
  this.skillMultiplier = 0.02;
1311
1332
  this.currentFlashlightStrain = 0;
1312
- this.isHidden = mods.some((m) => m instanceof osuBase.ModHidden);
1313
1333
  this.withSliders = withSliders;
1314
1334
  }
1315
1335
  strainValueAt(current) {
1316
1336
  this.currentFlashlightStrain *= this.strainDecay(current.deltaTime);
1317
1337
  this.currentFlashlightStrain +=
1318
- DroidFlashlightEvaluator.evaluateDifficultyOf(current, this.isHidden, this.withSliders) * this.skillMultiplier;
1338
+ DroidFlashlightEvaluator.evaluateDifficultyOf(current, this.mods, this.withSliders) * this.skillMultiplier;
1319
1339
  return this.currentFlashlightStrain;
1320
1340
  }
1321
1341
  calculateInitialStrain(time, current) {
@@ -1582,22 +1602,31 @@ class DroidVisualEvaluator {
1582
1602
  * - and whether the Hidden mod is enabled.
1583
1603
  *
1584
1604
  * @param current The current object.
1585
- * @param isHiddenMod Whether the Hidden mod is enabled.
1605
+ * @param mods The mods used.
1586
1606
  * @param withSliders Whether to take slider difficulty into account.
1587
1607
  */
1588
- static evaluateDifficultyOf(current, isHiddenMod, withSliders) {
1608
+ static evaluateDifficultyOf(current, mods, withSliders) {
1589
1609
  if (current.object instanceof osuBase.Spinner ||
1590
1610
  // Exclude overlapping objects that can be tapped at once.
1591
1611
  current.isOverlapping(true) ||
1592
1612
  current.index === 0) {
1593
1613
  return 0;
1594
1614
  }
1595
- // Start with base density and give global bonus for Hidden.
1615
+ // Start with base density and give global bonus for Hidden and Traceable.
1596
1616
  // Add density caps for sanity.
1597
1617
  let strain;
1598
- if (isHiddenMod) {
1618
+ if (mods.some((m) => m instanceof osuBase.ModHidden)) {
1599
1619
  strain = Math.min(30, Math.pow(current.noteDensity, 3));
1600
1620
  }
1621
+ else if (mods.some((m) => m instanceof osuBase.ModTraceable)) {
1622
+ // Give more bonus for hit circles due to there being no circle piece.
1623
+ if (current.object instanceof osuBase.Circle) {
1624
+ strain = Math.min(25, Math.pow(current.noteDensity, 2.5));
1625
+ }
1626
+ else {
1627
+ strain = Math.min(22.5, Math.pow(current.noteDensity, 2.25));
1628
+ }
1629
+ }
1601
1630
  else {
1602
1631
  strain = Math.min(20, Math.pow(current.noteDensity, 2));
1603
1632
  }
@@ -1615,9 +1644,7 @@ class DroidVisualEvaluator {
1615
1644
  break;
1616
1645
  }
1617
1646
  strain +=
1618
- (1 -
1619
- current.opacityAt(previous.object.startTime, isHiddenMod)) /
1620
- 4;
1647
+ (1 - current.opacityAt(previous.object.startTime, mods)) / 4;
1621
1648
  }
1622
1649
  if (current.timePreempt < 400) {
1623
1650
  // Give bonus for AR higher than 10.33.
@@ -1677,13 +1704,12 @@ class DroidVisual extends DroidSkill {
1677
1704
  this.currentVisualStrain = 0;
1678
1705
  this.currentRhythmMultiplier = 1;
1679
1706
  this.skillMultiplier = 10;
1680
- this.isHidden = mods.some((m) => m instanceof osuBase.ModHidden);
1681
1707
  this.withSliders = withSliders;
1682
1708
  }
1683
1709
  strainValueAt(current) {
1684
1710
  this.currentVisualStrain *= this.strainDecay(current.deltaTime);
1685
1711
  this.currentVisualStrain +=
1686
- DroidVisualEvaluator.evaluateDifficultyOf(current, this.isHidden, this.withSliders) * this.skillMultiplier;
1712
+ DroidVisualEvaluator.evaluateDifficultyOf(current, this.mods, this.withSliders) * this.skillMultiplier;
1687
1713
  this.currentRhythmMultiplier = current.rhythmMultiplier;
1688
1714
  return this.currentVisualStrain * this.currentRhythmMultiplier;
1689
1715
  }
@@ -3377,9 +3403,9 @@ class OsuFlashlightEvaluator {
3377
3403
  * - and whether Hidden mod is enabled.
3378
3404
  *
3379
3405
  * @param current The current object.
3380
- * @param isHiddenMod Whether the Hidden mod is enabled.
3406
+ * @param mods The mods used.
3381
3407
  */
3382
- static evaluateDifficultyOf(current, isHiddenMod) {
3408
+ static evaluateDifficultyOf(current, mods) {
3383
3409
  if (current.object instanceof osuBase.Spinner) {
3384
3410
  return 0;
3385
3411
  }
@@ -3406,7 +3432,7 @@ class OsuFlashlightEvaluator {
3406
3432
  const opacityBonus = 1 +
3407
3433
  this.maxOpacityBonus *
3408
3434
  (1 -
3409
- current.opacityAt(currentObject.object.startTime, isHiddenMod));
3435
+ current.opacityAt(currentObject.object.startTime, mods));
3410
3436
  result +=
3411
3437
  (stackNerf * opacityBonus * scalingFactor * jumpDistance) /
3412
3438
  cumulativeStrainTime;
@@ -3421,7 +3447,7 @@ class OsuFlashlightEvaluator {
3421
3447
  }
3422
3448
  result = Math.pow(smallDistNerf * result, 2);
3423
3449
  // Additional bonus for Hidden due to there being no approach circles.
3424
- if (isHiddenMod) {
3450
+ if (mods.some((m) => m instanceof osuBase.ModHidden)) {
3425
3451
  result *= 1 + this.hiddenBonus;
3426
3452
  }
3427
3453
  // Nerf patterns with repeated angles.
@@ -3454,15 +3480,14 @@ OsuFlashlightEvaluator.minAngleMultiplier = 0.2;
3454
3480
  * Represents the skill required to memorize and hit every object in a beatmap with the Flashlight mod enabled.
3455
3481
  */
3456
3482
  class OsuFlashlight extends OsuSkill {
3457
- constructor(mods) {
3458
- super(mods);
3483
+ constructor() {
3484
+ super(...arguments);
3459
3485
  this.strainDecayBase = 0.15;
3460
3486
  this.reducedSectionCount = 0;
3461
3487
  this.reducedSectionBaseline = 1;
3462
3488
  this.decayWeight = 1;
3463
3489
  this.currentFlashlightStrain = 0;
3464
3490
  this.skillMultiplier = 0.05512;
3465
- this.isHidden = mods.some((m) => m instanceof osuBase.ModHidden);
3466
3491
  }
3467
3492
  difficultyValue() {
3468
3493
  return this.strainPeaks.reduce((a, b) => a + b, 0);
@@ -3470,7 +3495,8 @@ class OsuFlashlight extends OsuSkill {
3470
3495
  strainValueAt(current) {
3471
3496
  this.currentFlashlightStrain *= this.strainDecay(current.deltaTime);
3472
3497
  this.currentFlashlightStrain +=
3473
- OsuFlashlightEvaluator.evaluateDifficultyOf(current, this.isHidden) * this.skillMultiplier;
3498
+ OsuFlashlightEvaluator.evaluateDifficultyOf(current, this.mods) *
3499
+ this.skillMultiplier;
3474
3500
  return this.currentFlashlightStrain;
3475
3501
  }
3476
3502
  calculateInitialStrain(time, current) {
@@ -3805,7 +3831,7 @@ class OsuPerformanceCalculator extends PerformanceCalculator {
3805
3831
  aimValue *= 1 + arFactor * lengthBonus;
3806
3832
  }
3807
3833
  // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
3808
- if (this.mods.some((m) => m instanceof osuBase.ModHidden)) {
3834
+ if (this.mods.some((m) => m instanceof osuBase.ModHidden || m instanceof osuBase.ModTraceable)) {
3809
3835
  aimValue *= 1 + 0.04 * (12 - calculatedAR);
3810
3836
  }
3811
3837
  // Scale the aim value with slider factor to nerf very likely dropped sliderends.
@@ -3840,7 +3866,7 @@ class OsuPerformanceCalculator extends PerformanceCalculator {
3840
3866
  // Buff for longer maps with high AR.
3841
3867
  speedValue *= 1 + 0.3 * (calculatedAR - 10.33) * lengthBonus;
3842
3868
  }
3843
- if (this.mods.some((m) => m instanceof osuBase.ModHidden)) {
3869
+ if (this.mods.some((m) => m instanceof osuBase.ModHidden || m instanceof osuBase.ModTraceable)) {
3844
3870
  speedValue *= 1 + 0.04 * (12 - calculatedAR);
3845
3871
  }
3846
3872
  // Calculate accuracy assuming the worst case scenario.
@@ -3890,7 +3916,7 @@ class OsuPerformanceCalculator extends PerformanceCalculator {
3890
3916
  2.83;
3891
3917
  // Bonus for many hitcircles - it's harder to keep good accuracy up for longer
3892
3918
  accuracyValue *= Math.min(1.15, Math.pow(ncircles / 1000, 0.3));
3893
- if (this.mods.some((m) => m instanceof osuBase.ModHidden)) {
3919
+ if (this.mods.some((m) => m instanceof osuBase.ModHidden || m instanceof osuBase.ModTraceable)) {
3894
3920
  accuracyValue *= 1.08;
3895
3921
  }
3896
3922
  if (this.mods.some((m) => m instanceof osuBase.ModFlashlight)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rian8337/osu-difficulty-calculator",
3
- "version": "4.0.0-beta.51",
3
+ "version": "4.0.0-beta.52",
4
4
  "description": "A module for calculating osu!standard beatmap difficulty and performance value with respect to the current difficulty and performance algorithm.",
5
5
  "keywords": [
6
6
  "osu",
@@ -33,10 +33,10 @@
33
33
  "url": "https://github.com/Rian8337/osu-droid-module/issues"
34
34
  },
35
35
  "dependencies": {
36
- "@rian8337/osu-base": "^4.0.0-beta.51"
36
+ "@rian8337/osu-base": "^4.0.0-beta.52"
37
37
  },
38
38
  "publishConfig": {
39
39
  "access": "public"
40
40
  },
41
- "gitHead": "6527e0c37f4d439e68fbecffa453fe1ff79a96f6"
41
+ "gitHead": "c7cd2019227c891e7897245ee61d1e24d7a5fb9c"
42
42
  }
@@ -242,10 +242,10 @@ declare abstract class DifficultyHitObject {
242
242
  * Calculates the opacity of the hitobject at a given time.
243
243
  *
244
244
  * @param time The time to calculate the hitobject's opacity at.
245
- * @param isHidden Whether Hidden mod is used.
245
+ * @param mods The mods used.
246
246
  * @returns The opacity of the hitobject at the given time.
247
247
  */
248
- opacityAt(time: number, isHidden: boolean): number;
248
+ opacityAt(time: number, mods: Mod[]): number;
249
249
  /**
250
250
  * How possible is it to doubletap this object together with the next one and get perfect
251
251
  * judgement in range from 0 to 1.
@@ -610,6 +610,7 @@ declare class DroidDifficultyHitObject extends DifficultyHitObject {
610
610
  */
611
611
  constructor(object: PlaceableHitObject, lastObject: PlaceableHitObject | null, lastLastObject: PlaceableHitObject | null, difficultyHitObjects: readonly DifficultyHitObject[], clockRate: number);
612
612
  computeProperties(clockRate: number, hitObjects: readonly PlaceableHitObject[]): void;
613
+ opacityAt(time: number, mods: Mod[]): number;
613
614
  /**
614
615
  * Determines whether this hitobject is considered overlapping with the hitobject before it.
615
616
  *
@@ -913,7 +914,6 @@ declare class DroidFlashlight extends DroidSkill {
913
914
  protected readonly reducedSectionBaseline = 1;
914
915
  protected readonly starsPerDouble = 1.06;
915
916
  private readonly skillMultiplier;
916
- private readonly isHidden;
917
917
  private currentFlashlightStrain;
918
918
  readonly withSliders: boolean;
919
919
  constructor(mods: Mod[], withSliders: boolean);
@@ -930,6 +930,8 @@ declare class DroidFlashlight extends DroidSkill {
930
930
  declare abstract class DroidFlashlightEvaluator {
931
931
  private static readonly maxOpacityBonus;
932
932
  private static readonly hiddenBonus;
933
+ private static readonly traceableCircleBonus;
934
+ private static readonly traceableObjectBonus;
933
935
  private static readonly minVelocity;
934
936
  private static readonly sliderMultiplier;
935
937
  private static readonly minAngleMultiplier;
@@ -943,10 +945,10 @@ declare abstract class DroidFlashlightEvaluator {
943
945
  * - and whether Hidden mod is enabled.
944
946
  *
945
947
  * @param current The current object.
946
- * @param isHiddenMod Whether the Hidden mod is enabled.
948
+ * @param mods The mods used.
947
949
  * @param withSliders Whether to take slider difficulty into account.
948
950
  */
949
- static evaluateDifficultyOf(current: DroidDifficultyHitObject, isHiddenMod: boolean, withSliders: boolean): number;
951
+ static evaluateDifficultyOf(current: DroidDifficultyHitObject, mods: Mod[], withSliders: boolean): number;
950
952
  }
951
953
 
952
954
  /**
@@ -1347,7 +1349,6 @@ declare class DroidVisual extends DroidSkill {
1347
1349
  protected readonly reducedSectionCount = 10;
1348
1350
  protected readonly reducedSectionBaseline = 0.75;
1349
1351
  protected readonly strainDecayBase = 0.1;
1350
- private readonly isHidden;
1351
1352
  private currentVisualStrain;
1352
1353
  private currentRhythmMultiplier;
1353
1354
  private readonly skillMultiplier;
@@ -1375,10 +1376,10 @@ declare abstract class DroidVisualEvaluator {
1375
1376
  * - and whether the Hidden mod is enabled.
1376
1377
  *
1377
1378
  * @param current The current object.
1378
- * @param isHiddenMod Whether the Hidden mod is enabled.
1379
+ * @param mods The mods used.
1379
1380
  * @param withSliders Whether to take slider difficulty into account.
1380
1381
  */
1381
- static evaluateDifficultyOf(current: DroidDifficultyHitObject, isHiddenMod: boolean, withSliders: boolean): number;
1382
+ static evaluateDifficultyOf(current: DroidDifficultyHitObject, mods: Mod[], withSliders: boolean): number;
1382
1383
  }
1383
1384
 
1384
1385
  /**
@@ -1549,8 +1550,6 @@ declare class OsuFlashlight extends OsuSkill {
1549
1550
  protected readonly decayWeight = 1;
1550
1551
  private currentFlashlightStrain;
1551
1552
  private readonly skillMultiplier;
1552
- private readonly isHidden;
1553
- constructor(mods: Mod[]);
1554
1553
  difficultyValue(): number;
1555
1554
  protected strainValueAt(current: OsuDifficultyHitObject): number;
1556
1555
  protected calculateInitialStrain(time: number, current: OsuDifficultyHitObject): number;
@@ -1576,9 +1575,9 @@ declare abstract class OsuFlashlightEvaluator {
1576
1575
  * - and whether Hidden mod is enabled.
1577
1576
  *
1578
1577
  * @param current The current object.
1579
- * @param isHiddenMod Whether the Hidden mod is enabled.
1578
+ * @param mods The mods used.
1580
1579
  */
1581
- static evaluateDifficultyOf(current: OsuDifficultyHitObject, isHiddenMod: boolean): number;
1580
+ static evaluateDifficultyOf(current: OsuDifficultyHitObject, mods: Mod[]): number;
1582
1581
  }
1583
1582
 
1584
1583
  /**