@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.
- package/LICENSE +21 -0
- package/README.md +11 -0
- package/dist/DroidPerformanceCalculator.js +248 -0
- package/dist/DroidStarRating.js +175 -0
- package/dist/MapStars.js +50 -0
- package/dist/OsuPerformanceCalculator.js +217 -0
- package/dist/OsuStarRating.js +147 -0
- package/dist/base/DifficultyAttributes.js +2 -0
- package/dist/base/PerformanceCalculator.js +125 -0
- package/dist/base/Skill.js +29 -0
- package/dist/base/StarRating.js +130 -0
- package/dist/base/StrainSkill.js +75 -0
- package/dist/index.js +25 -0
- package/dist/preprocessing/DifficultyHitObject.js +89 -0
- package/dist/preprocessing/DifficultyHitObjectCreator.js +237 -0
- package/dist/skills/DroidAim.js +209 -0
- package/dist/skills/DroidFlashlight.js +59 -0
- package/dist/skills/DroidSkill.js +16 -0
- package/dist/skills/DroidTap.js +188 -0
- package/dist/skills/OsuAim.js +170 -0
- package/dist/skills/OsuFlashlight.js +60 -0
- package/dist/skills/OsuSkill.js +37 -0
- package/dist/skills/OsuSpeed.js +175 -0
- package/package.json +37 -0
- package/typings/index.d.ts +1052 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OsuSpeed = void 0;
|
|
4
|
+
const OsuSkill_1 = require("./OsuSkill");
|
|
5
|
+
const osu_base_1 = require("@rian8337/osu-base");
|
|
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 OsuSpeed extends OsuSkill_1.OsuSkill {
|
|
10
|
+
constructor(mods, greatWindow) {
|
|
11
|
+
super(mods);
|
|
12
|
+
/**
|
|
13
|
+
* Spacing threshold for a single hitobject spacing.
|
|
14
|
+
*/
|
|
15
|
+
this.SINGLE_SPACING_THRESHOLD = 125;
|
|
16
|
+
this.historyLength = 32;
|
|
17
|
+
this.skillMultiplier = 1375;
|
|
18
|
+
this.strainDecayBase = 0.3;
|
|
19
|
+
this.reducedSectionCount = 5;
|
|
20
|
+
this.reducedSectionBaseline = 0.75;
|
|
21
|
+
this.difficultyMultiplier = 1.04;
|
|
22
|
+
this.decayWeight = 0.9;
|
|
23
|
+
this.rhythmMultiplier = 0.75;
|
|
24
|
+
this.historyTimeMax = 5000; // 5 seconds of calculateRhythmBonus max.
|
|
25
|
+
this.currentSpeedStrain = 0;
|
|
26
|
+
this.currentRhythm = 0;
|
|
27
|
+
// ~200 1/4 BPM streams
|
|
28
|
+
this.minSpeedBonus = 75;
|
|
29
|
+
this.greatWindow = greatWindow;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* @param current The hitobject to calculate.
|
|
33
|
+
*/
|
|
34
|
+
strainValueOf(current) {
|
|
35
|
+
if (current.object instanceof osu_base_1.Spinner) {
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
let strainTime = current.strainTime;
|
|
39
|
+
const greatWindowFull = this.greatWindow * 2;
|
|
40
|
+
const speedWindowRatio = strainTime / greatWindowFull;
|
|
41
|
+
// Aim to nerf cheesy rhythms (very fast consecutive doubles with large deltatimes between).
|
|
42
|
+
if (this.previous[0] &&
|
|
43
|
+
strainTime < greatWindowFull &&
|
|
44
|
+
this.previous[0].strainTime > strainTime) {
|
|
45
|
+
strainTime = osu_base_1.Interpolation.lerp(this.previous[0].strainTime, strainTime, speedWindowRatio);
|
|
46
|
+
}
|
|
47
|
+
// Cap deltatime to the OD 300 hitwindow.
|
|
48
|
+
// 0.93 is derived from making sure 260 BPM 1/4 OD8 streams aren't nerfed harshly, whilst 0.92 limits the effect of the cap.
|
|
49
|
+
strainTime /= osu_base_1.MathUtils.clamp(strainTime / greatWindowFull / 0.93, 0.92, 1);
|
|
50
|
+
let speedBonus = 1;
|
|
51
|
+
if (strainTime < this.minSpeedBonus) {
|
|
52
|
+
speedBonus +=
|
|
53
|
+
0.75 * Math.pow((this.minSpeedBonus - strainTime) / 40, 2);
|
|
54
|
+
}
|
|
55
|
+
const travelDistance = this.previous[0]?.travelDistance ?? 0;
|
|
56
|
+
const distance = Math.min(this.SINGLE_SPACING_THRESHOLD, travelDistance + current.lazyJumpDistance);
|
|
57
|
+
return ((speedBonus +
|
|
58
|
+
speedBonus *
|
|
59
|
+
Math.pow(distance / this.SINGLE_SPACING_THRESHOLD, 3.5)) /
|
|
60
|
+
strainTime);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* @param current The hitobject to calculate.
|
|
64
|
+
*/
|
|
65
|
+
strainValueAt(current) {
|
|
66
|
+
this.currentSpeedStrain *= this.strainDecay(current.deltaTime);
|
|
67
|
+
this.currentSpeedStrain +=
|
|
68
|
+
this.strainValueOf(current) * this.skillMultiplier;
|
|
69
|
+
this.currentRhythm = this.calculateRhythmBonus(current);
|
|
70
|
+
return this.currentSpeedStrain * this.currentRhythm;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current object.
|
|
74
|
+
*/
|
|
75
|
+
calculateRhythmBonus(current) {
|
|
76
|
+
if (current.object instanceof osu_base_1.Spinner) {
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
let previousIslandSize = 0;
|
|
80
|
+
let rhythmComplexitySum = 0;
|
|
81
|
+
let islandSize = 1;
|
|
82
|
+
// Store the ratio of the current start of an island to buff for tighter rhythms.
|
|
83
|
+
let startRatio = 0;
|
|
84
|
+
let firstDeltaSwitch = false;
|
|
85
|
+
let rhythmStart = 0;
|
|
86
|
+
while (rhythmStart < this.previous.length - 2 &&
|
|
87
|
+
current.startTime - this.previous[rhythmStart].startTime <
|
|
88
|
+
this.historyTimeMax) {
|
|
89
|
+
++rhythmStart;
|
|
90
|
+
}
|
|
91
|
+
for (let i = rhythmStart; i > 0; --i) {
|
|
92
|
+
// Scale note 0 to 1 from history to now.
|
|
93
|
+
let currentHistoricalDecay = (this.historyTimeMax -
|
|
94
|
+
(current.startTime - this.previous[i - 1].startTime)) /
|
|
95
|
+
this.historyTimeMax;
|
|
96
|
+
// Either we're limited by time or limited by object count.
|
|
97
|
+
currentHistoricalDecay = Math.min(currentHistoricalDecay, (this.previous.length - i) / this.previous.length);
|
|
98
|
+
const currentDelta = this.previous[i - 1].strainTime;
|
|
99
|
+
const prevDelta = this.previous[i].strainTime;
|
|
100
|
+
const lastDelta = this.previous[i + 1].strainTime;
|
|
101
|
+
const currentRatio = 1 +
|
|
102
|
+
6 *
|
|
103
|
+
Math.min(0.5, Math.pow(Math.sin(Math.PI /
|
|
104
|
+
(Math.min(prevDelta, currentDelta) /
|
|
105
|
+
Math.max(prevDelta, currentDelta))), 2));
|
|
106
|
+
const windowPenalty = Math.min(1, Math.max(0, Math.abs(prevDelta - currentDelta) - this.greatWindow * 0.6) /
|
|
107
|
+
(this.greatWindow * 0.6));
|
|
108
|
+
let effectiveRatio = windowPenalty * currentRatio;
|
|
109
|
+
if (firstDeltaSwitch) {
|
|
110
|
+
if (prevDelta <= 1.25 * currentDelta &&
|
|
111
|
+
prevDelta * 1.25 >= currentDelta) {
|
|
112
|
+
// Island is still progressing, count size.
|
|
113
|
+
if (islandSize < 7) {
|
|
114
|
+
++islandSize;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
if (this.previous[i - 1].object instanceof osu_base_1.Slider) {
|
|
119
|
+
// BPM change is into slider, this is easy acc window.
|
|
120
|
+
effectiveRatio /= 8;
|
|
121
|
+
}
|
|
122
|
+
if (this.previous[i].object instanceof osu_base_1.Slider) {
|
|
123
|
+
// BPM change was from a slider, this is typically easier than circle -> circle.
|
|
124
|
+
effectiveRatio /= 4;
|
|
125
|
+
}
|
|
126
|
+
if (previousIslandSize === islandSize) {
|
|
127
|
+
// Repeated island size (ex: triplet -> triplet).
|
|
128
|
+
effectiveRatio /= 4;
|
|
129
|
+
}
|
|
130
|
+
if (previousIslandSize % 2 === islandSize % 2) {
|
|
131
|
+
// Repeated island polarity (2 -> 4, 3 -> 5).
|
|
132
|
+
effectiveRatio /= 2;
|
|
133
|
+
}
|
|
134
|
+
if (lastDelta > prevDelta + 10 &&
|
|
135
|
+
prevDelta > currentDelta + 10) {
|
|
136
|
+
// Previous increase happened a note ago.
|
|
137
|
+
// Albeit this is a 1/1 -> 1/2-1/4 type of transition, we don't want to buff this.
|
|
138
|
+
effectiveRatio /= 8;
|
|
139
|
+
}
|
|
140
|
+
rhythmComplexitySum +=
|
|
141
|
+
(((Math.sqrt(effectiveRatio * startRatio) *
|
|
142
|
+
currentHistoricalDecay *
|
|
143
|
+
Math.sqrt(4 + islandSize)) /
|
|
144
|
+
2) *
|
|
145
|
+
Math.sqrt(4 + previousIslandSize)) /
|
|
146
|
+
2;
|
|
147
|
+
startRatio = effectiveRatio;
|
|
148
|
+
previousIslandSize = islandSize;
|
|
149
|
+
if (prevDelta * 1.25 < currentDelta) {
|
|
150
|
+
// We're slowing down, stop counting.
|
|
151
|
+
// If we're speeding up, this stays as is and we keep counting island size.
|
|
152
|
+
firstDeltaSwitch = false;
|
|
153
|
+
}
|
|
154
|
+
islandSize = 1;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
else if (prevDelta > 1.25 * currentDelta) {
|
|
158
|
+
// We want to be speeding up.
|
|
159
|
+
// Begin counting island until we change speed again.
|
|
160
|
+
firstDeltaSwitch = true;
|
|
161
|
+
startRatio = effectiveRatio;
|
|
162
|
+
islandSize = 1;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return Math.sqrt(4 + rhythmComplexitySum * this.rhythmMultiplier) / 2;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* @param current The hitobject to save to.
|
|
169
|
+
*/
|
|
170
|
+
saveToHitObject(current) {
|
|
171
|
+
current.tapStrain = this.currentStrain;
|
|
172
|
+
current.rhythmMultiplier = this.currentRhythm;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
exports.OsuSpeed = OsuSpeed;
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rian8337/osu-difficulty-calculator",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A module for calculating osu!standard beatmap difficulty with respect to the current difficulty and performance algorithm.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"osu",
|
|
7
|
+
"osu-difficulty-calculator"
|
|
8
|
+
],
|
|
9
|
+
"author": "Rian8337 <52914632+Rian8337@users.noreply.github.com>",
|
|
10
|
+
"homepage": "https://github.com/Rian8337/osu-droid-module#readme",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"main": "dist/index.js",
|
|
13
|
+
"types": "typings/index.d.ts",
|
|
14
|
+
"files": [
|
|
15
|
+
"dist/**",
|
|
16
|
+
"typings/**"
|
|
17
|
+
],
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/Rian8337/osu-droid-module.git"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc",
|
|
24
|
+
"prepare": "npm run build",
|
|
25
|
+
"test": "jest -i"
|
|
26
|
+
},
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/Rian8337/osu-droid-module/issues"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@rian8337/osu-base": "^1.0.0"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"gitHead": "e4fdb9c1ed6f90e70651c1aedfdb964b61cb240d"
|
|
37
|
+
}
|