@rian8337/osu-base 2.1.0 → 2.2.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/dist/index.js +161 -54
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/typings/index.d.ts +72 -10
package/dist/index.js
CHANGED
|
@@ -1299,12 +1299,21 @@ class DifficultyControlPoint extends ControlPoint {
|
|
|
1299
1299
|
* The slider speed multiplier of the control point.
|
|
1300
1300
|
*/
|
|
1301
1301
|
speedMultiplier;
|
|
1302
|
+
/**
|
|
1303
|
+
* Whether or not slider ticks should be generated at this control point.
|
|
1304
|
+
*
|
|
1305
|
+
* This exists for backwards compatibility with maps that abuse NaN slider velocity behavior on osu!stable (e.g. /b/2628991).
|
|
1306
|
+
*/
|
|
1307
|
+
generateTicks;
|
|
1308
|
+
// Generate ticks can be made required in 3.0.
|
|
1302
1309
|
constructor(values) {
|
|
1303
1310
|
super(values);
|
|
1304
1311
|
this.speedMultiplier = values.speedMultiplier;
|
|
1312
|
+
this.generateTicks = values.generateTicks ?? true;
|
|
1305
1313
|
}
|
|
1306
1314
|
isRedundant(existing) {
|
|
1307
|
-
return this.speedMultiplier === existing.speedMultiplier
|
|
1315
|
+
return (this.speedMultiplier === existing.speedMultiplier &&
|
|
1316
|
+
this.generateTicks === existing.generateTicks);
|
|
1308
1317
|
}
|
|
1309
1318
|
toString() {
|
|
1310
1319
|
return ("{ time: " +
|
|
@@ -1716,27 +1725,42 @@ class Beatmap {
|
|
|
1716
1725
|
* @param stats The statistics used for calculation.
|
|
1717
1726
|
*/
|
|
1718
1727
|
maxDroidScore(stats) {
|
|
1719
|
-
let scoreMultiplier = 1;
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
if (speedMultiplier > 1) {
|
|
1724
|
-
scoreSpeedMultiplier += (speedMultiplier - 1) * 0.24;
|
|
1725
|
-
}
|
|
1726
|
-
else if (speedMultiplier < 1) {
|
|
1727
|
-
scoreSpeedMultiplier = Math.pow(0.3, (1 - speedMultiplier) * 4);
|
|
1728
|
-
}
|
|
1729
|
-
scoreMultiplier =
|
|
1730
|
-
stats.mods.reduce((a, v) => a * v.scoreMultiplier, 1) *
|
|
1731
|
-
scoreSpeedMultiplier;
|
|
1728
|
+
let scoreMultiplier = stats.mods.reduce((a, v) => a * v.droidScoreMultiplier, 1);
|
|
1729
|
+
const { speedMultiplier } = stats;
|
|
1730
|
+
if (speedMultiplier >= 1) {
|
|
1731
|
+
scoreMultiplier *= 1 + (speedMultiplier - 1) * 0.24;
|
|
1732
1732
|
}
|
|
1733
1733
|
else {
|
|
1734
|
-
scoreMultiplier
|
|
1734
|
+
scoreMultiplier *= Math.pow(0.3, (1 - speedMultiplier) * 4);
|
|
1735
1735
|
}
|
|
1736
|
-
|
|
1736
|
+
const difficultyMultiplier = 1 +
|
|
1737
1737
|
this.difficulty.od / 10 +
|
|
1738
1738
|
this.difficulty.hp / 10 +
|
|
1739
|
-
(this.difficulty.cs - 3) / 4
|
|
1739
|
+
(this.difficulty.cs - 3) / 4;
|
|
1740
|
+
let combo = 0;
|
|
1741
|
+
let score = 0;
|
|
1742
|
+
for (const object of this.hitObjects.objects) {
|
|
1743
|
+
if (!(object instanceof Slider)) {
|
|
1744
|
+
score += Math.floor(300 + (300 * combo * difficultyMultiplier) / 25);
|
|
1745
|
+
++combo;
|
|
1746
|
+
continue;
|
|
1747
|
+
}
|
|
1748
|
+
const ticksPerSpan = object.ticks;
|
|
1749
|
+
const totalTicks = ticksPerSpan * (object.repeats + 1);
|
|
1750
|
+
// Apply slider head.
|
|
1751
|
+
score += 30;
|
|
1752
|
+
++combo;
|
|
1753
|
+
// Apply slider repeats.
|
|
1754
|
+
score += 30 * object.repeats;
|
|
1755
|
+
combo += object.repeats;
|
|
1756
|
+
// Apply slider ticks.
|
|
1757
|
+
score += 10 * totalTicks;
|
|
1758
|
+
combo += totalTicks;
|
|
1759
|
+
// Apply slider end.
|
|
1760
|
+
score += Math.floor(300 + (300 * combo * difficultyMultiplier) / 25);
|
|
1761
|
+
++combo;
|
|
1762
|
+
}
|
|
1763
|
+
return Math.floor(score * scoreMultiplier);
|
|
1740
1764
|
}
|
|
1741
1765
|
/**
|
|
1742
1766
|
* Calculates the osu!standard maximum score of the beatmap without taking spinner bonus into account.
|
|
@@ -1746,6 +1770,7 @@ class Beatmap {
|
|
|
1746
1770
|
maxOsuScore(mods = []) {
|
|
1747
1771
|
const accumulatedDiffPoints = this.difficulty.cs + this.difficulty.hp + this.difficulty.od;
|
|
1748
1772
|
let difficultyMultiplier = 2;
|
|
1773
|
+
const scoreMultiplier = mods.reduce((a, v) => a * v.pcScoreMultiplier, 1);
|
|
1749
1774
|
switch (true) {
|
|
1750
1775
|
case accumulatedDiffPoints <= 5:
|
|
1751
1776
|
difficultyMultiplier = 2;
|
|
@@ -1763,15 +1788,6 @@ class Beatmap {
|
|
|
1763
1788
|
difficultyMultiplier = 6;
|
|
1764
1789
|
break;
|
|
1765
1790
|
}
|
|
1766
|
-
return this.maxScore(difficultyMultiplier, mods.reduce((a, v) => a * v.scoreMultiplier, 1));
|
|
1767
|
-
}
|
|
1768
|
-
/**
|
|
1769
|
-
* Calculates the maximum score with a given difficulty and score multiplier.
|
|
1770
|
-
*
|
|
1771
|
-
* @param difficultyMultiplier The difficulty multiplier.
|
|
1772
|
-
* @param scoreMultiplier The score multiplier.
|
|
1773
|
-
*/
|
|
1774
|
-
maxScore(difficultyMultiplier, scoreMultiplier) {
|
|
1775
1791
|
let combo = 0;
|
|
1776
1792
|
let score = 0;
|
|
1777
1793
|
for (const object of this.hitObjects.objects) {
|
|
@@ -1782,11 +1798,18 @@ class Beatmap {
|
|
|
1782
1798
|
++combo;
|
|
1783
1799
|
continue;
|
|
1784
1800
|
}
|
|
1785
|
-
const
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1801
|
+
const ticksPerSpan = object.ticks;
|
|
1802
|
+
const totalTicks = ticksPerSpan * (object.repeats + 1);
|
|
1803
|
+
// Apply slider head.
|
|
1804
|
+
score += 30;
|
|
1805
|
+
++combo;
|
|
1806
|
+
// Apply slider repeats.
|
|
1807
|
+
score += 30 * object.repeats;
|
|
1808
|
+
combo += object.repeats;
|
|
1809
|
+
// Apply slider ticks.
|
|
1810
|
+
score += 10 * totalTicks;
|
|
1811
|
+
combo += totalTicks;
|
|
1812
|
+
// Apply slider end.
|
|
1790
1813
|
score += Math.floor(300 +
|
|
1791
1814
|
(300 * combo * difficultyMultiplier * scoreMultiplier) / 25);
|
|
1792
1815
|
++combo;
|
|
@@ -1995,6 +2018,7 @@ class OsuHitWindow extends HitWindow {
|
|
|
1995
2018
|
}
|
|
1996
2019
|
}
|
|
1997
2020
|
|
|
2021
|
+
// TODO: separate droid/PC mod implementations
|
|
1998
2022
|
/**
|
|
1999
2023
|
* Represents a mod.
|
|
2000
2024
|
*/
|
|
@@ -2010,6 +2034,8 @@ class ModDoubleTime extends Mod {
|
|
|
2010
2034
|
name = "DoubleTime";
|
|
2011
2035
|
droidRanked = true;
|
|
2012
2036
|
pcRanked = true;
|
|
2037
|
+
droidScoreMultiplier = 1.12;
|
|
2038
|
+
pcScoreMultiplier = 1.12;
|
|
2013
2039
|
bitwise = 1 << 6;
|
|
2014
2040
|
droidString = "d";
|
|
2015
2041
|
droidOnly = false;
|
|
@@ -2024,6 +2050,8 @@ class ModHalfTime extends Mod {
|
|
|
2024
2050
|
name = "HalfTime";
|
|
2025
2051
|
droidRanked = true;
|
|
2026
2052
|
pcRanked = true;
|
|
2053
|
+
droidScoreMultiplier = 0.3;
|
|
2054
|
+
pcScoreMultiplier = 0.3;
|
|
2027
2055
|
bitwise = 1 << 8;
|
|
2028
2056
|
droidString = "t";
|
|
2029
2057
|
droidOnly = false;
|
|
@@ -2038,6 +2066,8 @@ class ModNightCore extends Mod {
|
|
|
2038
2066
|
name = "NightCore";
|
|
2039
2067
|
droidRanked = true;
|
|
2040
2068
|
pcRanked = true;
|
|
2069
|
+
droidScoreMultiplier = 1.12;
|
|
2070
|
+
pcScoreMultiplier = 1.12;
|
|
2041
2071
|
bitwise = 1 << 9;
|
|
2042
2072
|
droidString = "c";
|
|
2043
2073
|
droidOnly = false;
|
|
@@ -2053,6 +2083,8 @@ class ModHardRock extends Mod {
|
|
|
2053
2083
|
bitwise = 1 << 4;
|
|
2054
2084
|
droidRanked = true;
|
|
2055
2085
|
pcRanked = true;
|
|
2086
|
+
droidScoreMultiplier = 1.06;
|
|
2087
|
+
pcScoreMultiplier = 1.06;
|
|
2056
2088
|
droidString = "r";
|
|
2057
2089
|
droidOnly = false;
|
|
2058
2090
|
}
|
|
@@ -2066,6 +2098,8 @@ class ModEasy extends Mod {
|
|
|
2066
2098
|
name = "Easy";
|
|
2067
2099
|
droidRanked = true;
|
|
2068
2100
|
pcRanked = true;
|
|
2101
|
+
droidScoreMultiplier = 0.5;
|
|
2102
|
+
pcScoreMultiplier = 0.5;
|
|
2069
2103
|
bitwise = 1 << 1;
|
|
2070
2104
|
droidString = "e";
|
|
2071
2105
|
droidOnly = false;
|
|
@@ -2080,6 +2114,8 @@ class ModPrecise extends Mod {
|
|
|
2080
2114
|
name = "Precise";
|
|
2081
2115
|
droidRanked = true;
|
|
2082
2116
|
pcRanked = false;
|
|
2117
|
+
droidScoreMultiplier = 1.06;
|
|
2118
|
+
pcScoreMultiplier = 1.06;
|
|
2083
2119
|
bitwise = Number.NaN;
|
|
2084
2120
|
droidString = "s";
|
|
2085
2121
|
droidOnly = true;
|
|
@@ -2094,6 +2130,8 @@ class ModSmallCircle extends Mod {
|
|
|
2094
2130
|
name = "SmallCircle";
|
|
2095
2131
|
droidRanked = false;
|
|
2096
2132
|
pcRanked = false;
|
|
2133
|
+
droidScoreMultiplier = 1.06;
|
|
2134
|
+
pcScoreMultiplier = 1;
|
|
2097
2135
|
bitwise = Number.NaN;
|
|
2098
2136
|
droidString = "m";
|
|
2099
2137
|
droidOnly = true;
|
|
@@ -2108,6 +2146,8 @@ class ModReallyEasy extends Mod {
|
|
|
2108
2146
|
name = "ReallyEasy";
|
|
2109
2147
|
droidRanked = false;
|
|
2110
2148
|
pcRanked = false;
|
|
2149
|
+
droidScoreMultiplier = 0.4;
|
|
2150
|
+
pcScoreMultiplier = 0.4;
|
|
2111
2151
|
bitwise = Number.NaN;
|
|
2112
2152
|
droidString = "l";
|
|
2113
2153
|
droidOnly = true;
|
|
@@ -2122,6 +2162,8 @@ class ModAuto extends Mod {
|
|
|
2122
2162
|
name = "Autoplay";
|
|
2123
2163
|
droidRanked = false;
|
|
2124
2164
|
pcRanked = false;
|
|
2165
|
+
droidScoreMultiplier = 1;
|
|
2166
|
+
pcScoreMultiplier = 1;
|
|
2125
2167
|
bitwise = 1 << 11;
|
|
2126
2168
|
droidString = "a";
|
|
2127
2169
|
droidOnly = false;
|
|
@@ -2136,6 +2178,8 @@ class ModAutopilot extends Mod {
|
|
|
2136
2178
|
name = "Autopilot";
|
|
2137
2179
|
droidRanked = false;
|
|
2138
2180
|
pcRanked = false;
|
|
2181
|
+
droidScoreMultiplier = 0.001;
|
|
2182
|
+
pcScoreMultiplier = 0;
|
|
2139
2183
|
bitwise = 1 << 13;
|
|
2140
2184
|
droidString = "p";
|
|
2141
2185
|
droidOnly = false;
|
|
@@ -2150,6 +2194,8 @@ class ModFlashlight extends Mod {
|
|
|
2150
2194
|
name = "Flashlight";
|
|
2151
2195
|
droidRanked = false;
|
|
2152
2196
|
pcRanked = true;
|
|
2197
|
+
droidScoreMultiplier = 1.12;
|
|
2198
|
+
pcScoreMultiplier = 1.12;
|
|
2153
2199
|
bitwise = 1 << 10;
|
|
2154
2200
|
droidString = "i";
|
|
2155
2201
|
droidOnly = false;
|
|
@@ -2167,6 +2213,8 @@ class ModHidden extends Mod {
|
|
|
2167
2213
|
bitwise = 1 << 3;
|
|
2168
2214
|
droidRanked = true;
|
|
2169
2215
|
pcRanked = true;
|
|
2216
|
+
droidScoreMultiplier = 1.06;
|
|
2217
|
+
pcScoreMultiplier = 1.06;
|
|
2170
2218
|
droidString = "h";
|
|
2171
2219
|
droidOnly = false;
|
|
2172
2220
|
}
|
|
@@ -2180,6 +2228,8 @@ class ModNoFail extends Mod {
|
|
|
2180
2228
|
name = "NoFail";
|
|
2181
2229
|
droidRanked = true;
|
|
2182
2230
|
pcRanked = true;
|
|
2231
|
+
droidScoreMultiplier = 0.5;
|
|
2232
|
+
pcScoreMultiplier = 0.5;
|
|
2183
2233
|
bitwise = 1 << 0;
|
|
2184
2234
|
droidString = "n";
|
|
2185
2235
|
droidOnly = false;
|
|
@@ -2194,6 +2244,8 @@ class ModPerfect extends Mod {
|
|
|
2194
2244
|
name = "Perfect";
|
|
2195
2245
|
droidRanked = false;
|
|
2196
2246
|
pcRanked = true;
|
|
2247
|
+
droidScoreMultiplier = 1;
|
|
2248
|
+
pcScoreMultiplier = 1;
|
|
2197
2249
|
bitwise = 1 << 14;
|
|
2198
2250
|
droidString = "f";
|
|
2199
2251
|
droidOnly = false;
|
|
@@ -2208,6 +2260,8 @@ class ModRelax extends Mod {
|
|
|
2208
2260
|
name = "Relax";
|
|
2209
2261
|
droidRanked = false;
|
|
2210
2262
|
pcRanked = false;
|
|
2263
|
+
droidScoreMultiplier = 0.001;
|
|
2264
|
+
pcScoreMultiplier = 0;
|
|
2211
2265
|
bitwise = 1 << 7;
|
|
2212
2266
|
droidString = "x";
|
|
2213
2267
|
droidOnly = false;
|
|
@@ -2222,6 +2276,8 @@ class ModScoreV2 extends Mod {
|
|
|
2222
2276
|
name = "ScoreV2";
|
|
2223
2277
|
droidRanked = false;
|
|
2224
2278
|
pcRanked = false;
|
|
2279
|
+
droidScoreMultiplier = 1;
|
|
2280
|
+
pcScoreMultiplier = 1;
|
|
2225
2281
|
bitwise = 1 << 29;
|
|
2226
2282
|
droidString = "v";
|
|
2227
2283
|
droidOnly = false;
|
|
@@ -2236,6 +2292,8 @@ class ModSpunOut extends Mod {
|
|
|
2236
2292
|
name = "SpunOut";
|
|
2237
2293
|
droidRanked = false;
|
|
2238
2294
|
pcRanked = true;
|
|
2295
|
+
droidScoreMultiplier = 0.9;
|
|
2296
|
+
pcScoreMultiplier = 0.9;
|
|
2239
2297
|
bitwise = 1 << 12;
|
|
2240
2298
|
droidString = "";
|
|
2241
2299
|
droidOnly = false;
|
|
@@ -2250,6 +2308,8 @@ class ModSuddenDeath extends Mod {
|
|
|
2250
2308
|
name = "Sudden Death";
|
|
2251
2309
|
droidRanked = false;
|
|
2252
2310
|
pcRanked = true;
|
|
2311
|
+
droidScoreMultiplier = 1;
|
|
2312
|
+
pcScoreMultiplier = 1;
|
|
2253
2313
|
bitwise = 1 << 5;
|
|
2254
2314
|
droidString = "u";
|
|
2255
2315
|
droidOnly = false;
|
|
@@ -2264,6 +2324,8 @@ class ModTouchDevice extends Mod {
|
|
|
2264
2324
|
name = "TouchDevice";
|
|
2265
2325
|
droidRanked = true;
|
|
2266
2326
|
pcRanked = true;
|
|
2327
|
+
droidScoreMultiplier = 1;
|
|
2328
|
+
pcScoreMultiplier = 1;
|
|
2267
2329
|
bitwise = 1 << 2;
|
|
2268
2330
|
droidString = "";
|
|
2269
2331
|
droidOnly = false;
|
|
@@ -3304,6 +3366,9 @@ class Decoder {
|
|
|
3304
3366
|
for (let line of str.split("\n")) {
|
|
3305
3367
|
this.currentLine = line;
|
|
3306
3368
|
++this.line;
|
|
3369
|
+
if (this.shouldSkipLine(line)) {
|
|
3370
|
+
continue;
|
|
3371
|
+
}
|
|
3307
3372
|
if (this.section !== BeatmapSection.metadata) {
|
|
3308
3373
|
// Comments should not be stripped from metadata lines, as the song metadata may contain "//" as valid data.
|
|
3309
3374
|
const index = line.indexOf("//");
|
|
@@ -3341,6 +3406,15 @@ class Decoder {
|
|
|
3341
3406
|
}
|
|
3342
3407
|
return this;
|
|
3343
3408
|
}
|
|
3409
|
+
/**
|
|
3410
|
+
* Determines whether a line should be skipped.
|
|
3411
|
+
*
|
|
3412
|
+
* @param line The line to determine.
|
|
3413
|
+
* @returns Whether the line should be skipped.
|
|
3414
|
+
*/
|
|
3415
|
+
shouldSkipLine(line) {
|
|
3416
|
+
return !line || line.trimStart().startsWith("//");
|
|
3417
|
+
}
|
|
3344
3418
|
/**
|
|
3345
3419
|
* Internal decoder function for decoding a line.
|
|
3346
3420
|
*
|
|
@@ -3420,29 +3494,43 @@ class SectionDecoder {
|
|
|
3420
3494
|
* @param str The string to parse.
|
|
3421
3495
|
* @param min The minimum threshold. Defaults to `-ParserConstants.MAX_PARSE_VALUE`.
|
|
3422
3496
|
* @param max The maximum threshold. Defaults to `ParserConstants.MAX_PARSE_VALUE`.
|
|
3497
|
+
* @param allowNaN Whether to allow NaN.
|
|
3423
3498
|
* @returns The parsed integer.
|
|
3424
3499
|
*/
|
|
3425
|
-
tryParseInt(str, min = -ParserConstants.MAX_PARSE_VALUE, max = ParserConstants.MAX_PARSE_VALUE) {
|
|
3500
|
+
tryParseInt(str, min = -ParserConstants.MAX_PARSE_VALUE, max = ParserConstants.MAX_PARSE_VALUE, allowNaN = false) {
|
|
3426
3501
|
const num = parseInt(str);
|
|
3427
|
-
if (
|
|
3428
|
-
throw new RangeError(
|
|
3502
|
+
if (num < min) {
|
|
3503
|
+
throw new RangeError("Value is too low");
|
|
3504
|
+
}
|
|
3505
|
+
if (num > max) {
|
|
3506
|
+
throw new RangeError("Value is too high");
|
|
3507
|
+
}
|
|
3508
|
+
if (!allowNaN && Number.isNaN(num)) {
|
|
3509
|
+
throw new RangeError("Not a number");
|
|
3429
3510
|
}
|
|
3430
3511
|
return num;
|
|
3431
3512
|
}
|
|
3432
3513
|
/**
|
|
3433
3514
|
* Attempts to parse a string into a float.
|
|
3434
3515
|
*
|
|
3435
|
-
* Throws an exception when the resulting value is
|
|
3516
|
+
* Throws an exception when the resulting value is too low or too high.
|
|
3436
3517
|
*
|
|
3437
3518
|
* @param str The string to parse.
|
|
3438
3519
|
* @param min The minimum threshold. Defaults to `-ParserConstants.MAX_PARSE_VALUE`.
|
|
3439
3520
|
* @param max The maximum threshold. Defaults to `ParserConstants.MAX_PARSE_VALUE`.
|
|
3521
|
+
* @param allowNaN Whether to allow NaN.
|
|
3440
3522
|
* @returns The parsed float.
|
|
3441
3523
|
*/
|
|
3442
|
-
tryParseFloat(str, min = -ParserConstants.MAX_PARSE_VALUE, max = ParserConstants.MAX_PARSE_VALUE) {
|
|
3524
|
+
tryParseFloat(str, min = -ParserConstants.MAX_PARSE_VALUE, max = ParserConstants.MAX_PARSE_VALUE, allowNaN = false) {
|
|
3443
3525
|
const num = parseFloat(str);
|
|
3444
|
-
if (
|
|
3445
|
-
throw new RangeError(
|
|
3526
|
+
if (num < min) {
|
|
3527
|
+
throw new RangeError("Value is too low");
|
|
3528
|
+
}
|
|
3529
|
+
if (num > max) {
|
|
3530
|
+
throw new RangeError("Value is too high");
|
|
3531
|
+
}
|
|
3532
|
+
if (!allowNaN && Number.isNaN(num)) {
|
|
3533
|
+
throw new RangeError("Not a number");
|
|
3446
3534
|
}
|
|
3447
3535
|
return num;
|
|
3448
3536
|
}
|
|
@@ -3502,8 +3590,8 @@ class BeatmapHitObjectsDecoder extends SectionDecoder {
|
|
|
3502
3590
|
}
|
|
3503
3591
|
const repetitions = Math.max(0, this.tryParseInt(this.setPosition(s[6]), -ParserConstants.MAX_PARSE_VALUE, ParserConstants.MAX_REPETITIONS_VALUE));
|
|
3504
3592
|
const distance = Math.max(0, this.tryParseFloat(this.setPosition(s[7])));
|
|
3505
|
-
const
|
|
3506
|
-
const
|
|
3593
|
+
const difficultyControlPoint = this.target.controlPoints.difficulty.controlPointAt(time);
|
|
3594
|
+
const timingControlPoint = this.target.controlPoints.timing.controlPointAt(time);
|
|
3507
3595
|
const points = [new Vector2(0, 0)];
|
|
3508
3596
|
const pointSplit = this.setPosition(s[5]).split("|");
|
|
3509
3597
|
let pathType = pointSplit.shift();
|
|
@@ -3573,6 +3661,23 @@ class BeatmapHitObjectsDecoder extends SectionDecoder {
|
|
|
3573
3661
|
comboOffset += this.extraComboOffset;
|
|
3574
3662
|
this.forceNewCombo = false;
|
|
3575
3663
|
this.extraComboOffset = 0;
|
|
3664
|
+
let tickDistanceMultiplier = Number.POSITIVE_INFINITY;
|
|
3665
|
+
if (difficultyControlPoint.generateTicks) {
|
|
3666
|
+
if (this.isNumberValid(timingControlPoint.msPerBeat, ParserConstants.MIN_MSPERBEAT_VALUE, ParserConstants.MAX_MSPERBEAT_VALUE)) {
|
|
3667
|
+
// Prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance.
|
|
3668
|
+
// This results in more (or less) ticks being generated in <v8 maps for the same time duration.
|
|
3669
|
+
//
|
|
3670
|
+
// This additional check is used in case BPM goes very low or very high.
|
|
3671
|
+
// When lazer is final, this should be revisited.
|
|
3672
|
+
tickDistanceMultiplier =
|
|
3673
|
+
this.target.formatVersion < 8
|
|
3674
|
+
? 1 / difficultyControlPoint.speedMultiplier
|
|
3675
|
+
: 1;
|
|
3676
|
+
}
|
|
3677
|
+
else {
|
|
3678
|
+
tickDistanceMultiplier = 0;
|
|
3679
|
+
}
|
|
3680
|
+
}
|
|
3576
3681
|
object = new Slider({
|
|
3577
3682
|
position: position,
|
|
3578
3683
|
startTime: time,
|
|
@@ -3582,20 +3687,11 @@ class BeatmapHitObjectsDecoder extends SectionDecoder {
|
|
|
3582
3687
|
nodeSamples: nodeSamples,
|
|
3583
3688
|
repetitions: repetitions,
|
|
3584
3689
|
path: path,
|
|
3585
|
-
speedMultiplier: MathUtils.clamp(
|
|
3586
|
-
msPerBeat:
|
|
3690
|
+
speedMultiplier: MathUtils.clamp(difficultyControlPoint.speedMultiplier, ParserConstants.MIN_SPEEDMULTIPLIER_VALUE, ParserConstants.MAX_SPEEDMULTIPLIER_VALUE),
|
|
3691
|
+
msPerBeat: timingControlPoint.msPerBeat,
|
|
3587
3692
|
mapSliderVelocity: this.target.difficulty.sliderMultiplier,
|
|
3588
3693
|
mapTickRate: this.target.difficulty.sliderTickRate,
|
|
3589
|
-
|
|
3590
|
-
// This results in more (or less) ticks being generated in <v8 maps for the same time duration.
|
|
3591
|
-
//
|
|
3592
|
-
// This additional check is used in case BPM goes very low or very high.
|
|
3593
|
-
// When lazer is final, this should be revisited.
|
|
3594
|
-
tickDistanceMultiplier: this.isNumberValid(msPerBeatTimingPoint.msPerBeat, ParserConstants.MIN_MSPERBEAT_VALUE, ParserConstants.MAX_MSPERBEAT_VALUE)
|
|
3595
|
-
? this.target.formatVersion < 8
|
|
3596
|
-
? 1 / speedMultiplierTimingPoint.speedMultiplier
|
|
3597
|
-
: 1
|
|
3598
|
-
: 0,
|
|
3694
|
+
tickDistanceMultiplier: tickDistanceMultiplier,
|
|
3599
3695
|
});
|
|
3600
3696
|
}
|
|
3601
3697
|
else if (type & exports.objectTypes.spinner) {
|
|
@@ -4143,7 +4239,9 @@ class BeatmapControlPointsDecoder extends SectionDecoder {
|
|
|
4143
4239
|
throw new Error("Ignoring malformed timing point");
|
|
4144
4240
|
}
|
|
4145
4241
|
const time = this.target.getOffsetTime(this.tryParseFloat(this.setPosition(s[0])));
|
|
4146
|
-
|
|
4242
|
+
// msPerBeat is allowed to be NaN to handle an edge case in which some
|
|
4243
|
+
// beatmaps use NaN slider velocity to disable slider tick generation.
|
|
4244
|
+
const msPerBeat = this.tryParseFloat(this.setPosition(s[1]), undefined, undefined, true);
|
|
4147
4245
|
let timeSignature = 4;
|
|
4148
4246
|
if (s.length >= 3) {
|
|
4149
4247
|
timeSignature = this.tryParseInt(this.setPosition(s[2]));
|
|
@@ -4170,7 +4268,14 @@ class BeatmapControlPointsDecoder extends SectionDecoder {
|
|
|
4170
4268
|
kiaiMode = !!(effectBitFlags & EffectFlags.kiai);
|
|
4171
4269
|
omitFirstBarSignature = !!(effectBitFlags & EffectFlags.omitFirstBarLine);
|
|
4172
4270
|
}
|
|
4173
|
-
|
|
4271
|
+
let timingChange = true;
|
|
4272
|
+
if (s.length >= 7) {
|
|
4273
|
+
timingChange = s[6] === "1";
|
|
4274
|
+
}
|
|
4275
|
+
if (timingChange) {
|
|
4276
|
+
if (Number.isNaN(msPerBeat)) {
|
|
4277
|
+
throw new Error("Beat length cannot be NaN in a timing control point");
|
|
4278
|
+
}
|
|
4174
4279
|
this.target.controlPoints.timing.add(new TimingControlPoint({
|
|
4175
4280
|
time: time,
|
|
4176
4281
|
msPerBeat: msPerBeat,
|
|
@@ -4179,7 +4284,9 @@ class BeatmapControlPointsDecoder extends SectionDecoder {
|
|
|
4179
4284
|
}
|
|
4180
4285
|
this.target.controlPoints.difficulty.add(new DifficultyControlPoint({
|
|
4181
4286
|
time: time,
|
|
4287
|
+
// If msPerBeat is NaN, speedMultiplier should still be 1 because all comparisons against NaN are false.
|
|
4182
4288
|
speedMultiplier: msPerBeat < 0 ? 100 / -msPerBeat : 1,
|
|
4289
|
+
generateTicks: !Number.isNaN(msPerBeat),
|
|
4183
4290
|
}));
|
|
4184
4291
|
this.target.controlPoints.effect.add(new EffectControlPoint({
|
|
4185
4292
|
time: time,
|