@rian8337/osu-difficulty-calculator 1.1.1 → 1.2.3

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.
@@ -153,7 +153,7 @@ class DroidPerformanceCalculator extends PerformanceCalculator_1.PerformanceCalc
153
153
  (0.95 + (od > 0 ? odScaling : -odScaling)) *
154
154
  Math.pow((this.computedAccuracy.value(objectCount) +
155
155
  relevantAccuracy.value(this.stars.attributes.speedNoteCount)) /
156
- 2, (12 - Math.max(od, 2.5)) / 2);
156
+ 2, (14 - Math.max(od, 2.5)) / 2);
157
157
  // Scale the speed value with # of 50s to punish doubletapping.
158
158
  this.tap *= Math.pow(0.98, Math.max(0, this.computedAccuracy.n50 - objectCount / 500));
159
159
  }
@@ -59,14 +59,7 @@ class DroidStarRating extends StarRating_1.StarRating {
59
59
  const aimSkill = new DroidAim_1.DroidAim(this.mods, true);
60
60
  const aimSkillWithoutSliders = new DroidAim_1.DroidAim(this.mods, false);
61
61
  this.calculateSkills(aimSkill, aimSkillWithoutSliders);
62
- this.strainPeaks.aimWithSliders = aimSkill.strainPeaks;
63
- this.strainPeaks.aimWithoutSliders = aimSkillWithoutSliders.strainPeaks;
64
- this.aim = this.starValue(aimSkill.difficultyValue());
65
- if (this.aim) {
66
- this.attributes.sliderFactor =
67
- this.starValue(aimSkillWithoutSliders.difficultyValue()) /
68
- this.aim;
69
- }
62
+ this.postCalculateAim(aimSkill, aimSkillWithoutSliders);
70
63
  }
71
64
  /**
72
65
  * Calculates the speed star rating of the beatmap and stores it in this instance.
@@ -74,13 +67,7 @@ class DroidStarRating extends StarRating_1.StarRating {
74
67
  calculateTap() {
75
68
  const tapSkill = new DroidTap_1.DroidTap(this.mods, this.stats.od);
76
69
  this.calculateSkills(tapSkill);
77
- this.strainPeaks.speed = tapSkill.strainPeaks;
78
- this.tap = this.starValue(tapSkill.difficultyValue());
79
- const objectStrains = this.objects.map((v) => v.tapStrain);
80
- const maxStrain = Math.max(...objectStrains);
81
- if (maxStrain) {
82
- this.attributes.speedNoteCount = objectStrains.reduce((total, next) => total + 1 / (1 + Math.exp(-((next / maxStrain) * 12 - 6))), 0);
83
- }
70
+ this.postCalculateTap(tapSkill);
84
71
  }
85
72
  /**
86
73
  * Calculates the rhythm star rating of the beatmap and stores it in this instance.
@@ -88,7 +75,7 @@ class DroidStarRating extends StarRating_1.StarRating {
88
75
  calculateRhythm() {
89
76
  const rhythmSkill = new DroidRhythm_1.DroidRhythm(this.mods, this.stats.od);
90
77
  this.calculateSkills(rhythmSkill);
91
- this.rhythm = this.starValue(rhythmSkill.difficultyValue());
78
+ this.postCalculateRhythm(rhythmSkill);
92
79
  }
93
80
  /**
94
81
  * Calculates the flashlight star rating of the beatmap and stores it in this instance.
@@ -96,8 +83,7 @@ class DroidStarRating extends StarRating_1.StarRating {
96
83
  calculateFlashlight() {
97
84
  const flashlightSkill = new DroidFlashlight_1.DroidFlashlight(this.mods);
98
85
  this.calculateSkills(flashlightSkill);
99
- this.strainPeaks.flashlight = flashlightSkill.strainPeaks;
100
- this.flashlight = this.starValue(flashlightSkill.difficultyValue());
86
+ this.postCalculateFlashlight(flashlightSkill);
101
87
  }
102
88
  calculateTotal() {
103
89
  const aimPerformanceValue = this.basePerformanceValue(this.aim);
@@ -125,28 +111,17 @@ class DroidStarRating extends StarRating_1.StarRating {
125
111
  const rhythmSkill = skills[2];
126
112
  const tapSkill = skills[3];
127
113
  const flashlightSkill = skills[4];
128
- this.strainPeaks.aimWithSliders = aimSkill.strainPeaks;
129
- this.strainPeaks.aimWithoutSliders = aimSkillWithoutSliders.strainPeaks;
130
- this.aim = this.starValue(aimSkill.difficultyValue());
131
- if (this.aim) {
132
- this.attributes.sliderFactor =
133
- this.starValue(aimSkillWithoutSliders.difficultyValue()) /
134
- this.aim;
135
- }
114
+ this.postCalculateAim(aimSkill, aimSkillWithoutSliders);
136
115
  if (!isRelax) {
137
- this.strainPeaks.speed = tapSkill.strainPeaks;
138
- this.tap = this.starValue(tapSkill.difficultyValue());
116
+ this.postCalculateTap(tapSkill);
139
117
  }
140
- const objectStrains = this.objects.map((v) => v.tapStrain);
141
- const maxStrain = Math.max(...objectStrains);
142
- if (maxStrain) {
143
- this.attributes.speedNoteCount = objectStrains.reduce((total, next) => total + 1 / (1 + Math.exp(-((next / maxStrain) * 12 - 6))), 0);
118
+ else {
119
+ this.calculateSpeedNoteCount();
144
120
  }
145
121
  if (!isRelax) {
146
- this.rhythm = this.starValue(rhythmSkill.difficultyValue());
122
+ this.postCalculateRhythm(rhythmSkill);
147
123
  }
148
- this.strainPeaks.flashlight = flashlightSkill.strainPeaks;
149
- this.flashlight = this.starValue(flashlightSkill.difficultyValue());
124
+ this.postCalculateFlashlight(flashlightSkill);
150
125
  this.calculateTotal();
151
126
  }
152
127
  /**
@@ -177,5 +152,58 @@ class DroidStarRating extends StarRating_1.StarRating {
177
152
  new DroidFlashlight_1.DroidFlashlight(this.mods),
178
153
  ];
179
154
  }
155
+ /**
156
+ * Called after aim skill calculation.
157
+ *
158
+ * @param aimSkill The aim skill that considers sliders.
159
+ * @param aimSkillWithoutSliders The aim skill that doesn't consider sliders.
160
+ */
161
+ postCalculateAim(aimSkill, aimSkillWithoutSliders) {
162
+ this.strainPeaks.aimWithSliders = aimSkill.strainPeaks;
163
+ this.strainPeaks.aimWithoutSliders = aimSkillWithoutSliders.strainPeaks;
164
+ this.aim = this.starValue(aimSkill.difficultyValue());
165
+ if (this.aim) {
166
+ this.attributes.sliderFactor =
167
+ this.starValue(aimSkillWithoutSliders.difficultyValue()) /
168
+ this.aim;
169
+ }
170
+ }
171
+ /**
172
+ * Called after tap skill calculation.
173
+ *
174
+ * @param tapSkill The tap skill.
175
+ */
176
+ postCalculateTap(tapSkill) {
177
+ this.strainPeaks.speed = tapSkill.strainPeaks;
178
+ this.tap = this.starValue(tapSkill.difficultyValue());
179
+ this.calculateSpeedNoteCount();
180
+ }
181
+ /**
182
+ * Calculates the speed note count attribute.
183
+ */
184
+ calculateSpeedNoteCount() {
185
+ const objectStrains = this.objects.map((v) => v.tapStrain);
186
+ const maxStrain = Math.max(...objectStrains);
187
+ if (maxStrain) {
188
+ this.attributes.speedNoteCount = objectStrains.reduce((total, next) => total + 1 / (1 + Math.exp(-((next / maxStrain) * 12 - 6))), 0);
189
+ }
190
+ }
191
+ /**
192
+ * Called after rhythm skill calculation.
193
+ *
194
+ * @param rhythmSkill The rhythm skill.
195
+ */
196
+ postCalculateRhythm(rhythmSkill) {
197
+ this.rhythm = this.starValue(rhythmSkill.difficultyValue());
198
+ }
199
+ /**
200
+ * Called after flashlight skill calculation.
201
+ *
202
+ * @param flashlightSkill The flashlight skill.
203
+ */
204
+ postCalculateFlashlight(flashlightSkill) {
205
+ this.strainPeaks.flashlight = flashlightSkill.strainPeaks;
206
+ this.flashlight = this.starValue(flashlightSkill.difficultyValue());
207
+ }
180
208
  }
181
209
  exports.DroidStarRating = DroidStarRating;
@@ -35,15 +35,8 @@ class OsuStarRating extends StarRating_1.StarRating {
35
35
  calculateAim() {
36
36
  const aimSkill = new OsuAim_1.OsuAim(this.mods, true);
37
37
  const aimSkillWithoutSliders = new OsuAim_1.OsuAim(this.mods, false);
38
- this.calculateSkills(aimSkill);
39
- this.strainPeaks.aimWithSliders = aimSkill.strainPeaks;
40
- this.strainPeaks.aimWithoutSliders = aimSkillWithoutSliders.strainPeaks;
41
- this.aim = this.starValue(aimSkill.difficultyValue());
42
- if (this.aim) {
43
- this.attributes.sliderFactor =
44
- this.starValue(aimSkillWithoutSliders.difficultyValue()) /
45
- this.aim;
46
- }
38
+ this.calculateSkills(aimSkill, aimSkillWithoutSliders);
39
+ this.postCalculateAim(aimSkill, aimSkillWithoutSliders);
47
40
  }
48
41
  /**
49
42
  * Calculates the speed star rating of the beatmap and stores it in this instance.
@@ -54,8 +47,7 @@ class OsuStarRating extends StarRating_1.StarRating {
54
47
  }
55
48
  const speedSkill = new OsuSpeed_1.OsuSpeed(this.mods, new osu_base_1.OsuHitWindow(this.stats.od).hitWindowFor300());
56
49
  this.calculateSkills(speedSkill);
57
- this.strainPeaks.speed = speedSkill.strainPeaks;
58
- this.speed = this.starValue(speedSkill.difficultyValue());
50
+ this.postCalculateSpeed(speedSkill);
59
51
  }
60
52
  /**
61
53
  * Calculates the flashlight star rating of the beatmap and stores it in this instance.
@@ -63,8 +55,7 @@ class OsuStarRating extends StarRating_1.StarRating {
63
55
  calculateFlashlight() {
64
56
  const flashlightSkill = new OsuFlashlight_1.OsuFlashlight(this.mods);
65
57
  this.calculateSkills(flashlightSkill);
66
- this.strainPeaks.flashlight = flashlightSkill.strainPeaks;
67
- this.flashlight = this.starValue(flashlightSkill.difficultyValue());
58
+ this.postCalculateFlashlight(flashlightSkill);
68
59
  }
69
60
  calculateTotal() {
70
61
  const aimPerformanceValue = this.basePerformanceValue(this.aim);
@@ -87,10 +78,6 @@ class OsuStarRating extends StarRating_1.StarRating {
87
78
  calculateAll() {
88
79
  const skills = this.createSkills();
89
80
  const isRelax = this.mods.some((m) => m instanceof osu_base_1.ModRelax);
90
- if (isRelax) {
91
- // Remove speed skill to prevent overhead
92
- skills.splice(2, 1);
93
- }
94
81
  this.calculateSkills(...skills);
95
82
  const aimSkill = skills[0];
96
83
  const aimSkillWithoutSliders = skills[1];
@@ -103,20 +90,11 @@ class OsuStarRating extends StarRating_1.StarRating {
103
90
  speedSkill = skills[2];
104
91
  flashlightSkill = skills[3];
105
92
  }
106
- this.strainPeaks.aimWithSliders = aimSkill.strainPeaks;
107
- this.strainPeaks.aimWithoutSliders = aimSkillWithoutSliders.strainPeaks;
108
- this.aim = this.starValue(aimSkill.difficultyValue());
109
- if (this.aim) {
110
- this.attributes.sliderFactor =
111
- this.starValue(aimSkillWithoutSliders.difficultyValue()) /
112
- this.aim;
113
- }
93
+ this.postCalculateAim(aimSkill, aimSkillWithoutSliders);
114
94
  if (speedSkill) {
115
- this.strainPeaks.speed = speedSkill.strainPeaks;
116
- this.speed = this.starValue(speedSkill.difficultyValue());
95
+ this.postCalculateSpeed(speedSkill);
117
96
  }
118
- this.strainPeaks.flashlight = flashlightSkill.strainPeaks;
119
- this.flashlight = this.starValue(flashlightSkill.difficultyValue());
97
+ this.postCalculateFlashlight(flashlightSkill);
120
98
  this.calculateTotal();
121
99
  }
122
100
  /**
@@ -143,5 +121,39 @@ class OsuStarRating extends StarRating_1.StarRating {
143
121
  new OsuFlashlight_1.OsuFlashlight(this.mods),
144
122
  ];
145
123
  }
124
+ /**
125
+ * Called after aim skill calculation.
126
+ *
127
+ * @param aimSkill The aim skill that considers sliders.
128
+ * @param aimSkillWithoutSliders The aim skill that doesn't consider sliders.
129
+ */
130
+ postCalculateAim(aimSkill, aimSkillWithoutSliders) {
131
+ this.strainPeaks.aimWithSliders = aimSkill.strainPeaks;
132
+ this.strainPeaks.aimWithoutSliders = aimSkillWithoutSliders.strainPeaks;
133
+ this.aim = this.starValue(aimSkill.difficultyValue());
134
+ if (this.aim) {
135
+ this.attributes.sliderFactor =
136
+ this.starValue(aimSkillWithoutSliders.difficultyValue()) /
137
+ this.aim;
138
+ }
139
+ }
140
+ /**
141
+ * Called after speed skill calculation.
142
+ *
143
+ * @param speedSkill The speed skill.
144
+ */
145
+ postCalculateSpeed(speedSkill) {
146
+ this.strainPeaks.speed = speedSkill.strainPeaks;
147
+ this.speed = this.starValue(speedSkill.difficultyValue());
148
+ }
149
+ /**
150
+ * Called after flashlight skill calculation.
151
+ *
152
+ * @param flashlightSkill The flashlight skill.
153
+ */
154
+ postCalculateFlashlight(flashlightSkill) {
155
+ this.strainPeaks.flashlight = flashlightSkill.strainPeaks;
156
+ this.flashlight = this.starValue(flashlightSkill.difficultyValue());
157
+ }
146
158
  }
147
159
  exports.OsuStarRating = OsuStarRating;
@@ -122,7 +122,7 @@ class PerformanceCalculator {
122
122
  comboBasedMissCount = Math.min(fullComboThreshold / Math.max(1, combo), this.stars.objects.length);
123
123
  }
124
124
  }
125
- return Math.max(this.computedAccuracy.nmiss, Math.floor(comboBasedMissCount));
125
+ return Math.max(this.computedAccuracy.nmiss, comboBasedMissCount);
126
126
  }
127
127
  }
128
128
  exports.PerformanceCalculator = PerformanceCalculator;
@@ -10,7 +10,7 @@ class DroidFlashlight extends DroidSkill_1.DroidSkill {
10
10
  constructor() {
11
11
  super(...arguments);
12
12
  this.historyLength = 10;
13
- this.skillMultiplier = 0.15;
13
+ this.skillMultiplier = 0.07;
14
14
  this.strainDecayBase = 0.15;
15
15
  this.reducedSectionCount = 10;
16
16
  this.reducedSectionBaseline = 0.75;
@@ -24,22 +24,23 @@ class DroidFlashlight extends DroidSkill_1.DroidSkill {
24
24
  let smallDistNerf = 1;
25
25
  let cumulativeStrainTime = 0;
26
26
  let result = 0;
27
+ let last = current;
27
28
  for (let i = 0; i < this.previous.length; ++i) {
28
- const previous = this.previous[i];
29
- if (previous.object instanceof osu_base_1.Spinner) {
30
- continue;
29
+ const currentObject = this.previous[i];
30
+ if (!(currentObject.object instanceof osu_base_1.Spinner)) {
31
+ const jumpDistance = current.object.stackedPosition.subtract(currentObject.object.endPosition).length;
32
+ cumulativeStrainTime += last.strainTime;
33
+ // We want to nerf objects that can be easily seen within the Flashlight circle radius.
34
+ if (i === 0) {
35
+ smallDistNerf = Math.min(1, jumpDistance / 75);
36
+ }
37
+ // We also want to nerf stacks so that only the first object of the stack is accounted for.
38
+ const stackNerf = Math.min(1, currentObject.lazyJumpDistance / scalingFactor / 25);
39
+ result +=
40
+ (stackNerf * scalingFactor * jumpDistance) /
41
+ cumulativeStrainTime;
31
42
  }
32
- const jumpDistance = current.object.stackedPosition.subtract(previous.object.endPosition).length;
33
- cumulativeStrainTime += previous.strainTime;
34
- // We want to nerf objects that can be easily seen within the Flashlight circle radius.
35
- if (i === 0) {
36
- smallDistNerf = Math.min(1, jumpDistance / 75);
37
- }
38
- // We also want to nerf stacks so that only the first object of the stack is accounted for.
39
- const stackNerf = Math.min(1, previous.lazyJumpDistance / scalingFactor / 25);
40
- result +=
41
- (Math.pow(0.8, i) * stackNerf * scalingFactor * jumpDistance) /
42
- cumulativeStrainTime;
43
+ last = currentObject;
43
44
  }
44
45
  return Math.pow(smallDistNerf * result, 2);
45
46
  }
@@ -37,10 +37,8 @@ class DroidTap extends DroidSkill_1.DroidSkill {
37
37
  strainTime = osu_base_1.Interpolation.lerp(this.previous[0].strainTime, strainTime, strainTime / greatWindowFull);
38
38
  }
39
39
  // Cap deltatime to the OD 300 hitwindow.
40
- // This equation is derived from making sure 260 BPM 1/4 OD7 streams aren't nerfed harshly.
41
- strainTime /= osu_base_1.MathUtils.clamp(strainTime /
42
- new osu_base_1.OsuHitWindow(this.hitWindow.overallDifficulty - 21.5).hitWindowFor300() /
43
- 0.35, 0.9, 1);
40
+ // 0.58 is derived from making sure 260 BPM 1/4 OD5 streams aren't nerfed harshly, whilst 0.92 limits the effect of the cap.
41
+ strainTime /= osu_base_1.MathUtils.clamp(strainTime / greatWindowFull / 0.58, 0.92, 1);
44
42
  let speedBonus = 1;
45
43
  if (strainTime < this.minSpeedBonus) {
46
44
  speedBonus +=
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rian8337/osu-difficulty-calculator",
3
- "version": "1.1.1",
3
+ "version": "1.2.3",
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",
@@ -30,10 +30,10 @@
30
30
  "url": "https://github.com/Rian8337/osu-droid-module/issues"
31
31
  },
32
32
  "dependencies": {
33
- "@rian8337/osu-base": "^1.0.5"
33
+ "@rian8337/osu-base": "^1.2.1"
34
34
  },
35
35
  "publishConfig": {
36
36
  "access": "public"
37
37
  },
38
- "gitHead": "595c6aac626f4346807f6da868c86324fed52422"
38
+ "gitHead": "e2d1f9bef01c9b12954a78683c3b8dcddabc7b1d"
39
39
  }
@@ -383,15 +383,37 @@ declare module "@rian8337/osu-difficulty-calculator" {
383
383
  */
384
384
  protected override createSkills(): DroidSkill[];
385
385
  /**
386
- * Calculates the base rating value of a difficulty.
386
+ * Called after aim skill calculation.
387
+ *
388
+ * @param aimSkill The aim skill that considers sliders.
389
+ * @param aimSkillWithoutSliders The aim skill that doesn't consider sliders.
387
390
  */
388
- private baseRatingValue(difficulty: number): number;
391
+ private postCalculateAim(
392
+ aimSkill: DroidAim,
393
+ aimSkillWithoutSliders: DroidAim
394
+ ): void;
389
395
  /**
390
- * Calculates the base performance value of a difficulty rating.
396
+ * Called after tap skill calculation.
391
397
  *
392
- * @param rating The difficulty rating.
398
+ * @param tapSkill The tap skill.
393
399
  */
394
- private basePerformanceValue(rating: number): number;
400
+ private postCalculateTap(tapSkill: DroidTap): void;
401
+ /**
402
+ * Calculates the speed note count attribute.
403
+ */
404
+ private calculateSpeedNoteCount(): void;
405
+ /**
406
+ * Called after rhythm skill calculation.
407
+ *
408
+ * @param rhythmSkill The rhythm skill.
409
+ */
410
+ private postCalculateRhythm(rhythmSkill: DroidRhythm): void;
411
+ /**
412
+ * Called after flashlight skill calculation.
413
+ *
414
+ * @param flashlightSkill The flashlight skill.
415
+ */
416
+ private postCalculateFlashlight(flashlightSkill: DroidFlashlight): void;
395
417
  }
396
418
 
397
419
  /**
@@ -684,17 +706,27 @@ declare module "@rian8337/osu-difficulty-calculator" {
684
706
  */
685
707
  protected override createSkills(): OsuSkill[];
686
708
  /**
687
- * Calculates the base performance value of a difficulty rating.
709
+ * Called after aim skill calculation.
688
710
  *
689
- * @param rating The difficulty rating.
711
+ * @param aimSkill The aim skill that considers sliders.
712
+ * @param aimSkillWithoutSliders The aim skill that doesn't consider sliders.
713
+ */
714
+ private postCalculateAim(
715
+ aimSkill: OsuAim,
716
+ aimSkillWithoutSliders: OsuAim
717
+ ): void;
718
+ /**
719
+ * Called after speed skill calculation.
720
+ *
721
+ * @param speedSkill The speed skill.
690
722
  */
691
- private basePerformanceValue(rating: number): number;
723
+ private postCalculateSpeed(speedSkill: OsuSpeed): void;
692
724
  /**
693
- * Calculates the star rating value of a difficulty.
725
+ * Called after flashlight skill calculation.
694
726
  *
695
- * @param difficulty The difficulty to calculate.
727
+ * @param flashlightSkill The flashlight skill.
696
728
  */
697
- private starValue(difficulty: number): number;
729
+ private postCalculateFlashlight(flashlightSkill: OsuFlashlight): void;
698
730
  }
699
731
 
700
732
  //#endregion
@@ -1029,6 +1061,18 @@ declare module "@rian8337/osu-difficulty-calculator" {
1029
1061
  * Creates skills to be calculated.
1030
1062
  */
1031
1063
  protected abstract createSkills(): Skill[];
1064
+ /**
1065
+ * Calculates the star rating value of a difficulty.
1066
+ *
1067
+ * @param difficulty The difficulty to calculate.
1068
+ */
1069
+ protected starValue(difficulty: number): number;
1070
+ /**
1071
+ * Calculates the base performance value of a difficulty rating.
1072
+ *
1073
+ * @param rating The difficulty rating.
1074
+ */
1075
+ protected basePerformanceValue(rating: number): number;
1032
1076
  }
1033
1077
 
1034
1078
  /**