@rian8337/osu-difficulty-calculator 1.0.0

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.
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DroidFlashlight = void 0;
4
+ const osu_base_1 = require("@rian8337/osu-base");
5
+ const DroidSkill_1 = require("./DroidSkill");
6
+ /**
7
+ * Represents the skill required to memorize and hit every object in a beatmap with the Flashlight mod enabled.
8
+ */
9
+ class DroidFlashlight extends DroidSkill_1.DroidSkill {
10
+ constructor() {
11
+ super(...arguments);
12
+ this.historyLength = 10;
13
+ this.skillMultiplier = 0.15;
14
+ this.strainDecayBase = 0.15;
15
+ this.reducedSectionCount = 10;
16
+ this.reducedSectionBaseline = 0.75;
17
+ this.starsPerDouble = 1.05;
18
+ }
19
+ strainValueOf(current) {
20
+ if (current.object instanceof osu_base_1.Spinner) {
21
+ return 0;
22
+ }
23
+ const scalingFactor = 52 / current.object.radius;
24
+ let smallDistNerf = 1;
25
+ let cumulativeStrainTime = 0;
26
+ let result = 0;
27
+ 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;
31
+ }
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
+ }
44
+ return Math.pow(smallDistNerf * result, 2);
45
+ }
46
+ /**
47
+ * @param current The hitobject to calculate.
48
+ */
49
+ strainValueAt(current) {
50
+ this.currentStrain *= this.strainDecay(current.deltaTime);
51
+ this.currentStrain +=
52
+ this.strainValueOf(current) * this.skillMultiplier;
53
+ return this.currentStrain;
54
+ }
55
+ saveToHitObject(current) {
56
+ current.flashlightStrain = this.currentStrain;
57
+ }
58
+ }
59
+ exports.DroidFlashlight = DroidFlashlight;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DroidSkill = void 0;
4
+ const StrainSkill_1 = require("../base/StrainSkill");
5
+ /**
6
+ * Used to processes strain values of difficulty hitobjects, keep track of strain levels caused by the processed objects
7
+ * and to calculate a final difficulty value representing the difficulty of hitting all the processed objects.
8
+ */
9
+ class DroidSkill extends StrainSkill_1.StrainSkill {
10
+ difficultyValue() {
11
+ // Math here preserves the property that two notes of equal difficulty x, we have their summed difficulty = x * starsPerDouble.
12
+ // This also applies to two sets of notes with equal difficulty.
13
+ return Math.pow(this.strainPeaks.reduce((a, v) => a + Math.pow(v, 1 / Math.log2(this.starsPerDouble)), 0), Math.log2(this.starsPerDouble));
14
+ }
15
+ }
16
+ exports.DroidSkill = DroidSkill;
@@ -0,0 +1,188 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DroidTap = void 0;
4
+ const osu_base_1 = require("@rian8337/osu-base");
5
+ const DroidSkill_1 = require("./DroidSkill");
6
+ /**
7
+ * Represents the skill required to press keys or tap with regards to keeping up with the speed at which objects need to be hit.
8
+ */
9
+ class DroidTap extends DroidSkill_1.DroidSkill {
10
+ constructor(mods, overallDifficulty) {
11
+ super(mods);
12
+ this.historyLength = 32;
13
+ this.skillMultiplier = 1375;
14
+ this.reducedSectionCount = 5;
15
+ this.reducedSectionBaseline = 0.75;
16
+ this.strainDecayBase = 0.3;
17
+ this.starsPerDouble = 1.1;
18
+ // ~200 1/4 BPM streams
19
+ this.minSpeedBonus = 75;
20
+ this.currentTapStrain = 0;
21
+ this.currentOriginalTapStrain = 0;
22
+ this.rhythmMultiplier = 0.75;
23
+ this.historyTimeMax = 5000; // 5 seconds of calculateRhythmBonus max.
24
+ this.currentRhythm = 1;
25
+ this.overallDifficulty = overallDifficulty;
26
+ this.hitWindow = new osu_base_1.OsuHitWindow(this.overallDifficulty);
27
+ }
28
+ /**
29
+ * @param current The hitobject to calculate.
30
+ */
31
+ strainValueOf(current) {
32
+ if (current.object instanceof osu_base_1.Spinner) {
33
+ return 0;
34
+ }
35
+ let strainTime = current.strainTime;
36
+ const greatWindowFull = this.hitWindow.hitWindowFor300() * 2;
37
+ // Aim to nerf cheesy rhythms (very fast consecutive doubles with large deltatimes between).
38
+ if (this.previous[0] &&
39
+ strainTime < greatWindowFull &&
40
+ this.previous[0].strainTime > strainTime) {
41
+ strainTime = osu_base_1.Interpolation.lerp(this.previous[0].strainTime, strainTime, strainTime / greatWindowFull);
42
+ }
43
+ // Cap deltatime to the OD 300 hitwindow.
44
+ // This equation is derived from making sure 260 BPM 1/4 OD7 streams aren't nerfed harshly.
45
+ strainTime /= osu_base_1.MathUtils.clamp(strainTime /
46
+ new osu_base_1.OsuHitWindow(this.overallDifficulty - 21.5).hitWindowFor300() /
47
+ 0.35, 0.9, 1);
48
+ let speedBonus = 1;
49
+ if (strainTime < this.minSpeedBonus) {
50
+ speedBonus +=
51
+ 0.75 * Math.pow((this.minSpeedBonus - strainTime) / 40, 2);
52
+ }
53
+ let originalSpeedBonus = 1;
54
+ if (current.strainTime < this.minSpeedBonus) {
55
+ originalSpeedBonus +=
56
+ 0.75 *
57
+ Math.pow((this.minSpeedBonus - current.strainTime) / 40, 2);
58
+ }
59
+ const decay = this.strainDecay(current.deltaTime);
60
+ this.currentRhythm = this.calculateRhythmBonus(current);
61
+ this.currentTapStrain *= decay;
62
+ this.currentTapStrain +=
63
+ this.tapStrainOf(speedBonus, strainTime) * this.skillMultiplier;
64
+ this.currentOriginalTapStrain *= decay;
65
+ this.currentOriginalTapStrain +=
66
+ this.tapStrainOf(originalSpeedBonus, current.strainTime) *
67
+ this.skillMultiplier;
68
+ return this.currentTapStrain * this.currentRhythm;
69
+ }
70
+ /**
71
+ * Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current object.
72
+ */
73
+ calculateRhythmBonus(current) {
74
+ if (current.object instanceof osu_base_1.Spinner) {
75
+ return 0;
76
+ }
77
+ let previousIslandSize = 0;
78
+ let rhythmComplexitySum = 0;
79
+ let islandSize = 1;
80
+ // Store the ratio of the current start of an island to buff for tighter rhythms.
81
+ let startRatio = 0;
82
+ let firstDeltaSwitch = false;
83
+ let rhythmStart = 0;
84
+ while (rhythmStart < this.previous.length - 2 &&
85
+ current.startTime - this.previous[rhythmStart].startTime <
86
+ this.historyTimeMax) {
87
+ ++rhythmStart;
88
+ }
89
+ for (let i = rhythmStart; i > 0; --i) {
90
+ // Scale note 0 to 1 from history to now.
91
+ let currentHistoricalDecay = (this.historyTimeMax -
92
+ (current.startTime - this.previous[i - 1].startTime)) /
93
+ this.historyTimeMax;
94
+ // Either we're limited by time or limited by object count.
95
+ currentHistoricalDecay = Math.min(currentHistoricalDecay, (this.previous.length - i) / this.previous.length);
96
+ const currentDelta = this.previous[i - 1].strainTime;
97
+ const prevDelta = this.previous[i].strainTime;
98
+ const lastDelta = this.previous[i + 1].strainTime;
99
+ const currentRatio = 1 +
100
+ 6 *
101
+ Math.min(0.5, Math.pow(Math.sin(Math.PI /
102
+ (Math.min(prevDelta, currentDelta) /
103
+ Math.max(prevDelta, currentDelta))), 2));
104
+ const windowPenalty = Math.min(1, Math.max(0, Math.abs(prevDelta - currentDelta) -
105
+ this.hitWindow.hitWindowFor300() * 0.6) /
106
+ (this.hitWindow.hitWindowFor300() * 0.6));
107
+ let effectiveRatio = windowPenalty * currentRatio;
108
+ if (firstDeltaSwitch) {
109
+ if (prevDelta <= 1.25 * currentDelta &&
110
+ prevDelta * 1.25 >= currentDelta) {
111
+ // Island is still progressing, count size.
112
+ if (islandSize < 7) {
113
+ ++islandSize;
114
+ }
115
+ }
116
+ else {
117
+ if (this.previous[i - 1].object instanceof osu_base_1.Slider) {
118
+ // BPM change is into slider, this is easy acc window.
119
+ effectiveRatio /= 8;
120
+ }
121
+ if (this.previous[i].object instanceof osu_base_1.Slider) {
122
+ // BPM change was from a slider, this is typically easier than circle -> circle.
123
+ effectiveRatio /= 4;
124
+ }
125
+ if (previousIslandSize === islandSize) {
126
+ // Repeated island size (ex: triplet -> triplet).
127
+ effectiveRatio /= 4;
128
+ }
129
+ if (previousIslandSize % 2 === islandSize % 2) {
130
+ // Repeated island polarity (2 -> 4, 3 -> 5).
131
+ effectiveRatio /= 2;
132
+ }
133
+ if (lastDelta > prevDelta + 10 &&
134
+ prevDelta > currentDelta + 10) {
135
+ // Previous increase happened a note ago.
136
+ // Albeit this is a 1/1 -> 1/2-1/4 type of transition, we don't want to buff this.
137
+ effectiveRatio /= 8;
138
+ }
139
+ rhythmComplexitySum +=
140
+ (((Math.sqrt(effectiveRatio * startRatio) *
141
+ currentHistoricalDecay *
142
+ Math.sqrt(4 + islandSize)) /
143
+ 2) *
144
+ Math.sqrt(4 + previousIslandSize)) /
145
+ 2;
146
+ startRatio = effectiveRatio;
147
+ previousIslandSize = islandSize;
148
+ if (prevDelta * 1.25 < currentDelta) {
149
+ // We're slowing down, stop counting.
150
+ // If we're speeding up, this stays as is and we keep counting island size.
151
+ firstDeltaSwitch = false;
152
+ }
153
+ islandSize = 1;
154
+ }
155
+ }
156
+ else if (prevDelta > 1.25 * currentDelta) {
157
+ // We want to be speeding up.
158
+ // Begin counting island until we change speed again.
159
+ firstDeltaSwitch = true;
160
+ startRatio = effectiveRatio;
161
+ islandSize = 1;
162
+ }
163
+ }
164
+ return Math.sqrt(4 + rhythmComplexitySum * this.rhythmMultiplier) / 2;
165
+ }
166
+ /**
167
+ * Calculates the tap strain of a hitobject given a specific speed bonus and strain time.
168
+ */
169
+ tapStrainOf(speedBonus, strainTime) {
170
+ return speedBonus / strainTime;
171
+ }
172
+ /**
173
+ * @param current The hitobject to calculate.
174
+ */
175
+ strainValueAt(current) {
176
+ return this.strainValueOf(current);
177
+ }
178
+ /**
179
+ * @param current The hitobject to save to.
180
+ */
181
+ saveToHitObject(current) {
182
+ current.tapStrain = this.currentStrain;
183
+ current.rhythmMultiplier = this.currentRhythm;
184
+ current.originalTapStrain =
185
+ this.currentOriginalTapStrain * this.currentRhythm;
186
+ }
187
+ }
188
+ exports.DroidTap = DroidTap;
@@ -0,0 +1,170 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OsuAim = void 0;
4
+ const OsuSkill_1 = require("./OsuSkill");
5
+ const osu_base_1 = require("@rian8337/osu-base");
6
+ /**
7
+ * Represents the skill required to correctly aim at every object in the map with a uniform CircleSize and normalized distances.
8
+ */
9
+ class OsuAim extends OsuSkill_1.OsuSkill {
10
+ constructor(mods, withSliders) {
11
+ super(mods);
12
+ this.skillMultiplier = 23.25;
13
+ this.strainDecayBase = 0.15;
14
+ this.reducedSectionCount = 10;
15
+ this.reducedSectionBaseline = 0.75;
16
+ this.difficultyMultiplier = 1.06;
17
+ this.decayWeight = 0.9;
18
+ this.wideAngleMultiplier = 1.5;
19
+ this.acuteAngleMultiplier = 2;
20
+ this.sliderMultiplier = 1.5;
21
+ this.velocityChangeMultiplier = 0.75;
22
+ this.withSliders = withSliders;
23
+ }
24
+ /**
25
+ * @param current The hitobject to calculate.
26
+ */
27
+ strainValueOf(current) {
28
+ if (current.object instanceof osu_base_1.Spinner ||
29
+ this.previous.length <= 1 ||
30
+ this.previous[0].object instanceof osu_base_1.Spinner) {
31
+ return 0;
32
+ }
33
+ const last = this.previous[0];
34
+ const lastLast = this.previous[1];
35
+ // Calculate the velocity to the current hitobject, which starts with a base distance / time assuming the last object is a hitcircle.
36
+ let currentVelocity = current.lazyJumpDistance / current.strainTime;
37
+ // But if the last object is a slider, then we extend the travel velocity through the slider into the current object.
38
+ if (last.object instanceof osu_base_1.Slider && this.withSliders) {
39
+ // Calculate the slider velocity from slider head to slider end.
40
+ const travelVelocity = last.travelDistance / last.travelTime;
41
+ // Calculate the movement velocity from slider end to current object.
42
+ const movementVelocity = current.minimumJumpDistance / current.minimumJumpTime;
43
+ // Take the larger total combined velocity.
44
+ currentVelocity = Math.max(currentVelocity, movementVelocity + travelVelocity);
45
+ }
46
+ // As above, do the same for the previous hitobject.
47
+ let prevVelocity = last.lazyJumpDistance / last.strainTime;
48
+ if (lastLast.object instanceof osu_base_1.Slider && this.withSliders) {
49
+ const travelVelocity = lastLast.travelDistance / lastLast.travelTime;
50
+ const movementVelocity = last.minimumJumpDistance / last.minimumJumpTime;
51
+ prevVelocity = Math.max(prevVelocity, movementVelocity + travelVelocity);
52
+ }
53
+ let wideAngleBonus = 0;
54
+ let acuteAngleBonus = 0;
55
+ let sliderBonus = 0;
56
+ let velocityChangeBonus = 0;
57
+ // Start strain with regular velocity.
58
+ let strain = currentVelocity;
59
+ if (Math.max(current.strainTime, last.strainTime) <
60
+ 1.25 * Math.min(current.strainTime, last.strainTime)) {
61
+ // If rhythms are the same.
62
+ if (current.angle !== null &&
63
+ last.angle !== null &&
64
+ lastLast.angle !== null) {
65
+ // Rewarding angles, take the smaller velocity as base.
66
+ const angleBonus = Math.min(currentVelocity, prevVelocity);
67
+ wideAngleBonus = this.calculateWideAngleBonus(current.angle);
68
+ acuteAngleBonus = this.calculateAcuteAngleBonus(current.angle);
69
+ // Only buff deltaTime exceeding 300 BPM 1/2.
70
+ if (current.strainTime > 100) {
71
+ acuteAngleBonus = 0;
72
+ }
73
+ else {
74
+ acuteAngleBonus *=
75
+ // Multiply by previous angle, we don't want to buff unless this is a wiggle type pattern.
76
+ this.calculateAcuteAngleBonus(last.angle) *
77
+ // The maximum velocity we buff is equal to 125 / strainTime.
78
+ Math.min(angleBonus, 125 / current.strainTime) *
79
+ // Scale buff from 300 BPM 1/2 to 400 BPM 1/2.
80
+ Math.pow(Math.sin((Math.PI / 2) *
81
+ Math.min(1, (100 - current.strainTime) / 25)), 2) *
82
+ // Buff distance exceeding 50 (radius) up to 100 (diameter).
83
+ Math.pow(Math.sin(((Math.PI / 2) *
84
+ (osu_base_1.MathUtils.clamp(current.lazyJumpDistance, 50, 100) -
85
+ 50)) /
86
+ 50), 2);
87
+ }
88
+ // Penalize wide angles if they're repeated, reducing the penalty as last.angle gets more acute.
89
+ wideAngleBonus *=
90
+ angleBonus *
91
+ (1 -
92
+ Math.min(wideAngleBonus, Math.pow(this.calculateWideAngleBonus(last.angle), 3)));
93
+ // Penalize acute angles if they're repeated, reducing the penalty as lastLast.angle gets more obtuse.
94
+ acuteAngleBonus *=
95
+ 0.5 +
96
+ 0.5 *
97
+ (1 -
98
+ Math.min(acuteAngleBonus, Math.pow(this.calculateAcuteAngleBonus(lastLast.angle), 3)));
99
+ }
100
+ }
101
+ if (Math.max(prevVelocity, currentVelocity)) {
102
+ // We want to use the average velocity over the whole object when awarding differences, not the individual jump and slider path velocities.
103
+ prevVelocity =
104
+ (last.lazyJumpDistance + lastLast.travelDistance) /
105
+ last.strainTime;
106
+ currentVelocity =
107
+ (current.lazyJumpDistance + last.travelDistance) /
108
+ current.strainTime;
109
+ // Scale with ratio of difference compared to half the max distance.
110
+ const distanceRatio = Math.pow(Math.sin(((Math.PI / 2) * Math.abs(prevVelocity - currentVelocity)) /
111
+ Math.max(prevVelocity, currentVelocity)), 2);
112
+ // Reward for % distance up to 125 / strainTime for overlaps where velocity is still changing.
113
+ const overlapVelocityBuff = Math.min(125 / Math.min(current.strainTime, last.strainTime), Math.abs(prevVelocity - currentVelocity));
114
+ // Reward for % distance slowed down compared to previous, paying attention to not award overlap.
115
+ const nonOverlapVelocityBuff = Math.abs(prevVelocity - currentVelocity) *
116
+ // Do not award overlap.
117
+ Math.pow(Math.sin((Math.PI / 2) *
118
+ Math.min(1, Math.min(current.lazyJumpDistance, last.lazyJumpDistance) / 100)), 2);
119
+ // Choose the largest bonus, multiplied by ratio.
120
+ velocityChangeBonus =
121
+ Math.max(overlapVelocityBuff, nonOverlapVelocityBuff) *
122
+ distanceRatio;
123
+ // Penalize for rhythm changes.
124
+ velocityChangeBonus *= Math.pow(Math.min(current.strainTime, last.strainTime) /
125
+ Math.max(current.strainTime, last.strainTime), 2);
126
+ }
127
+ if (last.travelTime) {
128
+ // Reward sliders based on velocity.
129
+ sliderBonus = last.travelDistance / last.travelTime;
130
+ }
131
+ // Add in acute angle bonus or wide angle bonus + velocity change bonus, whichever is larger.
132
+ strain += Math.max(acuteAngleBonus * this.acuteAngleMultiplier, wideAngleBonus * this.wideAngleMultiplier +
133
+ velocityChangeBonus * this.velocityChangeMultiplier);
134
+ // Add in additional slider velocity bonus.
135
+ if (this.withSliders) {
136
+ strain += sliderBonus * this.sliderMultiplier;
137
+ }
138
+ return strain;
139
+ }
140
+ /**
141
+ * @param current The hitobject to calculate.
142
+ */
143
+ strainValueAt(current) {
144
+ this.currentStrain *= this.strainDecay(current.deltaTime);
145
+ this.currentStrain +=
146
+ this.strainValueOf(current) * this.skillMultiplier;
147
+ return this.currentStrain;
148
+ }
149
+ /**
150
+ * @param current The hitobject to save to.
151
+ */
152
+ saveToHitObject(current) {
153
+ current.aimStrainWithSliders = this.currentStrain;
154
+ }
155
+ /**
156
+ * Calculates the bonus of wide angles.
157
+ */
158
+ calculateWideAngleBonus(angle) {
159
+ return Math.pow(Math.sin((3 / 4) *
160
+ (Math.min((5 / 6) * Math.PI, Math.max(Math.PI / 6, angle)) -
161
+ Math.PI / 6)), 2);
162
+ }
163
+ /**
164
+ * Calculates the bonus of acute angles.
165
+ */
166
+ calculateAcuteAngleBonus(angle) {
167
+ return 1 - this.calculateWideAngleBonus(angle);
168
+ }
169
+ }
170
+ exports.OsuAim = OsuAim;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OsuFlashlight = void 0;
4
+ const osu_base_1 = require("@rian8337/osu-base");
5
+ const OsuSkill_1 = require("./OsuSkill");
6
+ /**
7
+ * Represents the skill required to memorize and hit every object in a beatmap with the Flashlight mod enabled.
8
+ */
9
+ class OsuFlashlight extends OsuSkill_1.OsuSkill {
10
+ constructor() {
11
+ super(...arguments);
12
+ this.historyLength = 10;
13
+ this.skillMultiplier = 0.15;
14
+ this.strainDecayBase = 0.15;
15
+ this.reducedSectionCount = 10;
16
+ this.reducedSectionBaseline = 0.75;
17
+ this.difficultyMultiplier = 1.06;
18
+ this.decayWeight = 1;
19
+ }
20
+ strainValueOf(current) {
21
+ if (current.object instanceof osu_base_1.Spinner) {
22
+ return 0;
23
+ }
24
+ const scalingFactor = 52 / current.object.radius;
25
+ let smallDistNerf = 1;
26
+ let cumulativeStrainTime = 0;
27
+ let result = 0;
28
+ for (let i = 0; i < this.previous.length; ++i) {
29
+ const previous = this.previous[i];
30
+ if (previous.object instanceof osu_base_1.Spinner) {
31
+ continue;
32
+ }
33
+ const jumpDistance = current.object.stackedPosition.subtract(previous.object.endPosition).length;
34
+ cumulativeStrainTime += previous.strainTime;
35
+ // We want to nerf objects that can be easily seen within the Flashlight circle radius.
36
+ if (i === 0) {
37
+ smallDistNerf = Math.min(1, jumpDistance / 75);
38
+ }
39
+ // We also want to nerf stacks so that only the first object of the stack is accounted for.
40
+ const stackNerf = Math.min(1, previous.lazyJumpDistance / scalingFactor / 25);
41
+ result +=
42
+ (Math.pow(0.8, i) * stackNerf * scalingFactor * jumpDistance) /
43
+ cumulativeStrainTime;
44
+ }
45
+ return Math.pow(smallDistNerf * result, 2);
46
+ }
47
+ /**
48
+ * @param current The hitobject to calculate.
49
+ */
50
+ strainValueAt(current) {
51
+ this.currentStrain *= this.strainDecay(current.deltaTime);
52
+ this.currentStrain +=
53
+ this.strainValueOf(current) * this.skillMultiplier;
54
+ return this.currentStrain;
55
+ }
56
+ saveToHitObject(current) {
57
+ current.flashlightStrain = this.currentStrain;
58
+ }
59
+ }
60
+ exports.OsuFlashlight = OsuFlashlight;
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OsuSkill = void 0;
4
+ const osu_base_1 = require("@rian8337/osu-base");
5
+ const StrainSkill_1 = require("../base/StrainSkill");
6
+ /**
7
+ * Used to processes strain values of difficulty hitobjects, keep track of strain levels caused by the processed objects
8
+ * and to calculate a final difficulty value representing the difficulty of hitting all the processed objects.
9
+ */
10
+ class OsuSkill extends StrainSkill_1.StrainSkill {
11
+ difficultyValue() {
12
+ let difficulty = 0;
13
+ let weight = 1;
14
+ const sortedStrains = this.strainPeaks
15
+ .slice()
16
+ .sort((a, b) => {
17
+ return b - a;
18
+ });
19
+ // We are reducing the highest strains first to account for extreme difficulty spikes.
20
+ for (let i = 0; i < Math.min(sortedStrains.length, this.reducedSectionCount); ++i) {
21
+ const scale = Math.log10(osu_base_1.Interpolation.lerp(1, 10, osu_base_1.MathUtils.clamp(i / this.reducedSectionCount, 0, 1)));
22
+ sortedStrains[i] *= osu_base_1.Interpolation.lerp(this.reducedSectionBaseline, 1, scale);
23
+ }
24
+ // Difficulty is the weighted sum of the highest strains from every section.
25
+ // We're sorting from highest to lowest strain.
26
+ sortedStrains
27
+ .sort((a, b) => {
28
+ return b - a;
29
+ })
30
+ .forEach((strain) => {
31
+ difficulty += strain * weight;
32
+ weight *= this.decayWeight;
33
+ });
34
+ return difficulty * this.difficultyMultiplier;
35
+ }
36
+ }
37
+ exports.OsuSkill = OsuSkill;