@rian8337/osu-base 2.1.1 → 3.0.0-beta.1
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 +185 -108
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/typings/index.d.ts +112 -98
package/dist/index.js
CHANGED
|
@@ -311,7 +311,9 @@ class APIRequestBuilder {
|
|
|
311
311
|
})
|
|
312
312
|
.on("complete", async (response) => {
|
|
313
313
|
++this.fetchAttempts;
|
|
314
|
-
|
|
314
|
+
const { statusCode } = response;
|
|
315
|
+
if ((statusCode === 500 || statusCode === 503) &&
|
|
316
|
+
this.fetchAttempts < 5) {
|
|
315
317
|
console.error(`Request to ${url} failed; ${this.fetchAttempts} attempts so far; retrying`);
|
|
316
318
|
await Utils.sleep(0.2);
|
|
317
319
|
return resolve(this.sendRequest());
|
|
@@ -390,7 +392,7 @@ exports.objectTypes = void 0;
|
|
|
390
392
|
})(exports.objectTypes || (exports.objectTypes = {}));
|
|
391
393
|
|
|
392
394
|
/**
|
|
393
|
-
*
|
|
395
|
+
* Represents a two-dimensional vector.
|
|
394
396
|
*/
|
|
395
397
|
class Vector2 {
|
|
396
398
|
/**
|
|
@@ -1299,12 +1301,20 @@ class DifficultyControlPoint extends ControlPoint {
|
|
|
1299
1301
|
* The slider speed multiplier of the control point.
|
|
1300
1302
|
*/
|
|
1301
1303
|
speedMultiplier;
|
|
1304
|
+
/**
|
|
1305
|
+
* Whether or not slider ticks should be generated at this control point.
|
|
1306
|
+
*
|
|
1307
|
+
* This exists for backwards compatibility with maps that abuse NaN slider velocity behavior on osu!stable (e.g. /b/2628991).
|
|
1308
|
+
*/
|
|
1309
|
+
generateTicks;
|
|
1302
1310
|
constructor(values) {
|
|
1303
1311
|
super(values);
|
|
1304
1312
|
this.speedMultiplier = values.speedMultiplier;
|
|
1313
|
+
this.generateTicks = values.generateTicks;
|
|
1305
1314
|
}
|
|
1306
1315
|
isRedundant(existing) {
|
|
1307
|
-
return this.speedMultiplier === existing.speedMultiplier
|
|
1316
|
+
return (this.speedMultiplier === existing.speedMultiplier &&
|
|
1317
|
+
this.generateTicks === existing.generateTicks);
|
|
1308
1318
|
}
|
|
1309
1319
|
toString() {
|
|
1310
1320
|
return ("{ time: " +
|
|
@@ -1323,6 +1333,7 @@ class DifficultyControlPointManager extends ControlPointManager {
|
|
|
1323
1333
|
defaultControlPoint = new DifficultyControlPoint({
|
|
1324
1334
|
time: 0,
|
|
1325
1335
|
speedMultiplier: 1,
|
|
1336
|
+
generateTicks: true,
|
|
1326
1337
|
});
|
|
1327
1338
|
controlPointAt(time) {
|
|
1328
1339
|
return this.binarySearchWithFallback(time);
|
|
@@ -1717,26 +1728,46 @@ class Beatmap {
|
|
|
1717
1728
|
*/
|
|
1718
1729
|
maxDroidScore(stats) {
|
|
1719
1730
|
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);
|
|
1731
|
+
for (const mod of stats.mods) {
|
|
1732
|
+
if (mod.isApplicableToDroid()) {
|
|
1733
|
+
scoreMultiplier *= mod.droidScoreMultiplier;
|
|
1728
1734
|
}
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1735
|
+
}
|
|
1736
|
+
const { speedMultiplier } = stats;
|
|
1737
|
+
if (speedMultiplier >= 1) {
|
|
1738
|
+
scoreMultiplier *= 1 + (speedMultiplier - 1) * 0.24;
|
|
1732
1739
|
}
|
|
1733
1740
|
else {
|
|
1734
|
-
scoreMultiplier
|
|
1741
|
+
scoreMultiplier *= Math.pow(0.3, (1 - speedMultiplier) * 4);
|
|
1735
1742
|
}
|
|
1736
|
-
|
|
1743
|
+
const difficultyMultiplier = 1 +
|
|
1737
1744
|
this.difficulty.od / 10 +
|
|
1738
1745
|
this.difficulty.hp / 10 +
|
|
1739
|
-
(this.difficulty.cs - 3) / 4
|
|
1746
|
+
(this.difficulty.cs - 3) / 4;
|
|
1747
|
+
let combo = 0;
|
|
1748
|
+
let score = 0;
|
|
1749
|
+
for (const object of this.hitObjects.objects) {
|
|
1750
|
+
if (!(object instanceof Slider)) {
|
|
1751
|
+
score += Math.floor(300 + (300 * combo * difficultyMultiplier) / 25);
|
|
1752
|
+
++combo;
|
|
1753
|
+
continue;
|
|
1754
|
+
}
|
|
1755
|
+
const ticksPerSpan = object.ticks;
|
|
1756
|
+
const totalTicks = ticksPerSpan * (object.repeats + 1);
|
|
1757
|
+
// Apply slider head.
|
|
1758
|
+
score += 30;
|
|
1759
|
+
++combo;
|
|
1760
|
+
// Apply slider repeats.
|
|
1761
|
+
score += 30 * object.repeats;
|
|
1762
|
+
combo += object.repeats;
|
|
1763
|
+
// Apply slider ticks.
|
|
1764
|
+
score += 10 * totalTicks;
|
|
1765
|
+
combo += totalTicks;
|
|
1766
|
+
// Apply slider end.
|
|
1767
|
+
score += Math.floor(300 + (300 * combo * difficultyMultiplier) / 25);
|
|
1768
|
+
++combo;
|
|
1769
|
+
}
|
|
1770
|
+
return Math.floor(score * scoreMultiplier);
|
|
1740
1771
|
}
|
|
1741
1772
|
/**
|
|
1742
1773
|
* Calculates the osu!standard maximum score of the beatmap without taking spinner bonus into account.
|
|
@@ -1746,6 +1777,12 @@ class Beatmap {
|
|
|
1746
1777
|
maxOsuScore(mods = []) {
|
|
1747
1778
|
const accumulatedDiffPoints = this.difficulty.cs + this.difficulty.hp + this.difficulty.od;
|
|
1748
1779
|
let difficultyMultiplier = 2;
|
|
1780
|
+
let scoreMultiplier = 1;
|
|
1781
|
+
for (const mod of mods) {
|
|
1782
|
+
if (mod.isApplicableToOsu()) {
|
|
1783
|
+
scoreMultiplier *= mod.pcScoreMultiplier;
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1749
1786
|
switch (true) {
|
|
1750
1787
|
case accumulatedDiffPoints <= 5:
|
|
1751
1788
|
difficultyMultiplier = 2;
|
|
@@ -1763,15 +1800,6 @@ class Beatmap {
|
|
|
1763
1800
|
difficultyMultiplier = 6;
|
|
1764
1801
|
break;
|
|
1765
1802
|
}
|
|
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
1803
|
let combo = 0;
|
|
1776
1804
|
let score = 0;
|
|
1777
1805
|
for (const object of this.hitObjects.objects) {
|
|
@@ -1782,11 +1810,18 @@ class Beatmap {
|
|
|
1782
1810
|
++combo;
|
|
1783
1811
|
continue;
|
|
1784
1812
|
}
|
|
1785
|
-
const
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1813
|
+
const ticksPerSpan = object.ticks;
|
|
1814
|
+
const totalTicks = ticksPerSpan * (object.repeats + 1);
|
|
1815
|
+
// Apply slider head.
|
|
1816
|
+
score += 30;
|
|
1817
|
+
++combo;
|
|
1818
|
+
// Apply slider repeats.
|
|
1819
|
+
score += 30 * object.repeats;
|
|
1820
|
+
combo += object.repeats;
|
|
1821
|
+
// Apply slider ticks.
|
|
1822
|
+
score += 10 * totalTicks;
|
|
1823
|
+
combo += totalTicks;
|
|
1824
|
+
// Apply slider end.
|
|
1790
1825
|
score += Math.floor(300 +
|
|
1791
1826
|
(300 * combo * difficultyMultiplier * scoreMultiplier) / 25);
|
|
1792
1827
|
++combo;
|
|
@@ -1999,160 +2034,163 @@ class OsuHitWindow extends HitWindow {
|
|
|
1999
2034
|
* Represents a mod.
|
|
2000
2035
|
*/
|
|
2001
2036
|
class Mod {
|
|
2037
|
+
/**
|
|
2038
|
+
* Whether this mod can be applied to osu!droid.
|
|
2039
|
+
*/
|
|
2040
|
+
isApplicableToDroid() {
|
|
2041
|
+
return "droidRanked" in this;
|
|
2042
|
+
}
|
|
2043
|
+
/**
|
|
2044
|
+
* Whether this mod can be applied to osu!standard.
|
|
2045
|
+
*/
|
|
2046
|
+
isApplicableToOsu() {
|
|
2047
|
+
return "pcRanked" in this;
|
|
2048
|
+
}
|
|
2002
2049
|
}
|
|
2003
2050
|
|
|
2004
2051
|
/**
|
|
2005
2052
|
* Represents the DoubleTime mod.
|
|
2006
2053
|
*/
|
|
2007
2054
|
class ModDoubleTime extends Mod {
|
|
2008
|
-
scoreMultiplier = 1.12;
|
|
2009
2055
|
acronym = "DT";
|
|
2010
2056
|
name = "DoubleTime";
|
|
2011
2057
|
droidRanked = true;
|
|
2012
2058
|
pcRanked = true;
|
|
2059
|
+
droidScoreMultiplier = 1.12;
|
|
2060
|
+
pcScoreMultiplier = 1.12;
|
|
2013
2061
|
bitwise = 1 << 6;
|
|
2014
2062
|
droidString = "d";
|
|
2015
|
-
droidOnly = false;
|
|
2016
2063
|
}
|
|
2017
2064
|
|
|
2018
2065
|
/**
|
|
2019
2066
|
* Represents the HalfTime mod.
|
|
2020
2067
|
*/
|
|
2021
2068
|
class ModHalfTime extends Mod {
|
|
2022
|
-
scoreMultiplier = 0.3;
|
|
2023
2069
|
acronym = "HT";
|
|
2024
2070
|
name = "HalfTime";
|
|
2025
2071
|
droidRanked = true;
|
|
2026
2072
|
pcRanked = true;
|
|
2073
|
+
droidScoreMultiplier = 0.3;
|
|
2074
|
+
pcScoreMultiplier = 0.3;
|
|
2027
2075
|
bitwise = 1 << 8;
|
|
2028
2076
|
droidString = "t";
|
|
2029
|
-
droidOnly = false;
|
|
2030
2077
|
}
|
|
2031
2078
|
|
|
2032
2079
|
/**
|
|
2033
2080
|
* Represents the NightCore mod.
|
|
2034
2081
|
*/
|
|
2035
2082
|
class ModNightCore extends Mod {
|
|
2036
|
-
scoreMultiplier = 1.12;
|
|
2037
2083
|
acronym = "NC";
|
|
2038
2084
|
name = "NightCore";
|
|
2039
2085
|
droidRanked = true;
|
|
2040
2086
|
pcRanked = true;
|
|
2087
|
+
droidScoreMultiplier = 1.12;
|
|
2088
|
+
pcScoreMultiplier = 1.12;
|
|
2041
2089
|
bitwise = 1 << 9;
|
|
2042
2090
|
droidString = "c";
|
|
2043
|
-
droidOnly = false;
|
|
2044
2091
|
}
|
|
2045
2092
|
|
|
2046
2093
|
/**
|
|
2047
2094
|
* Represents the HardRock mod.
|
|
2048
2095
|
*/
|
|
2049
2096
|
class ModHardRock extends Mod {
|
|
2050
|
-
scoreMultiplier = 1.06;
|
|
2051
2097
|
acronym = "HR";
|
|
2052
2098
|
name = "HardRock";
|
|
2053
2099
|
bitwise = 1 << 4;
|
|
2054
2100
|
droidRanked = true;
|
|
2055
2101
|
pcRanked = true;
|
|
2102
|
+
droidScoreMultiplier = 1.06;
|
|
2103
|
+
pcScoreMultiplier = 1.06;
|
|
2056
2104
|
droidString = "r";
|
|
2057
|
-
droidOnly = false;
|
|
2058
2105
|
}
|
|
2059
2106
|
|
|
2060
2107
|
/**
|
|
2061
2108
|
* Represents the Easy mod.
|
|
2062
2109
|
*/
|
|
2063
2110
|
class ModEasy extends Mod {
|
|
2064
|
-
scoreMultiplier = 0.5;
|
|
2065
2111
|
acronym = "EZ";
|
|
2066
2112
|
name = "Easy";
|
|
2067
2113
|
droidRanked = true;
|
|
2068
2114
|
pcRanked = true;
|
|
2115
|
+
droidScoreMultiplier = 0.5;
|
|
2116
|
+
pcScoreMultiplier = 0.5;
|
|
2069
2117
|
bitwise = 1 << 1;
|
|
2070
2118
|
droidString = "e";
|
|
2071
|
-
droidOnly = false;
|
|
2072
2119
|
}
|
|
2073
2120
|
|
|
2074
2121
|
/**
|
|
2075
2122
|
* Represents the Precise mod.
|
|
2076
2123
|
*/
|
|
2077
2124
|
class ModPrecise extends Mod {
|
|
2078
|
-
scoreMultiplier = 1.06;
|
|
2079
2125
|
acronym = "PR";
|
|
2080
2126
|
name = "Precise";
|
|
2081
2127
|
droidRanked = true;
|
|
2082
|
-
|
|
2083
|
-
bitwise = Number.NaN;
|
|
2128
|
+
droidScoreMultiplier = 1.06;
|
|
2084
2129
|
droidString = "s";
|
|
2085
|
-
droidOnly = true;
|
|
2086
2130
|
}
|
|
2087
2131
|
|
|
2088
2132
|
/**
|
|
2089
2133
|
* Represents the SmallCircle mod.
|
|
2090
2134
|
*/
|
|
2091
2135
|
class ModSmallCircle extends Mod {
|
|
2092
|
-
scoreMultiplier = 1.06;
|
|
2093
2136
|
acronym = "SC";
|
|
2094
2137
|
name = "SmallCircle";
|
|
2095
2138
|
droidRanked = false;
|
|
2096
|
-
|
|
2097
|
-
bitwise = Number.NaN;
|
|
2139
|
+
droidScoreMultiplier = 1.06;
|
|
2098
2140
|
droidString = "m";
|
|
2099
|
-
droidOnly = true;
|
|
2100
2141
|
}
|
|
2101
2142
|
|
|
2102
2143
|
/**
|
|
2103
2144
|
* Represents the ReallyEasy mod.
|
|
2104
2145
|
*/
|
|
2105
2146
|
class ModReallyEasy extends Mod {
|
|
2106
|
-
scoreMultiplier = 0.4;
|
|
2107
2147
|
acronym = "RE";
|
|
2108
2148
|
name = "ReallyEasy";
|
|
2109
2149
|
droidRanked = false;
|
|
2110
|
-
|
|
2111
|
-
bitwise = Number.NaN;
|
|
2150
|
+
droidScoreMultiplier = 0.4;
|
|
2112
2151
|
droidString = "l";
|
|
2113
|
-
droidOnly = true;
|
|
2114
2152
|
}
|
|
2115
2153
|
|
|
2116
2154
|
/**
|
|
2117
2155
|
* Represents the Auto mod.
|
|
2118
2156
|
*/
|
|
2119
2157
|
class ModAuto extends Mod {
|
|
2120
|
-
scoreMultiplier = 0;
|
|
2121
2158
|
acronym = "AT";
|
|
2122
2159
|
name = "Autoplay";
|
|
2123
2160
|
droidRanked = false;
|
|
2124
2161
|
pcRanked = false;
|
|
2162
|
+
droidScoreMultiplier = 1;
|
|
2163
|
+
pcScoreMultiplier = 1;
|
|
2125
2164
|
bitwise = 1 << 11;
|
|
2126
2165
|
droidString = "a";
|
|
2127
|
-
droidOnly = false;
|
|
2128
2166
|
}
|
|
2129
2167
|
|
|
2130
2168
|
/**
|
|
2131
2169
|
* Represents the Autopilot mod.
|
|
2132
2170
|
*/
|
|
2133
2171
|
class ModAutopilot extends Mod {
|
|
2134
|
-
scoreMultiplier = 0;
|
|
2135
2172
|
acronym = "AP";
|
|
2136
2173
|
name = "Autopilot";
|
|
2137
2174
|
droidRanked = false;
|
|
2138
2175
|
pcRanked = false;
|
|
2176
|
+
droidScoreMultiplier = 0.001;
|
|
2177
|
+
pcScoreMultiplier = 0;
|
|
2139
2178
|
bitwise = 1 << 13;
|
|
2140
2179
|
droidString = "p";
|
|
2141
|
-
droidOnly = false;
|
|
2142
2180
|
}
|
|
2143
2181
|
|
|
2144
2182
|
/**
|
|
2145
2183
|
* Represents the Flashlight mod.
|
|
2146
2184
|
*/
|
|
2147
2185
|
class ModFlashlight extends Mod {
|
|
2148
|
-
scoreMultiplier = 1.12;
|
|
2149
2186
|
acronym = "FL";
|
|
2150
2187
|
name = "Flashlight";
|
|
2151
2188
|
droidRanked = false;
|
|
2152
2189
|
pcRanked = true;
|
|
2190
|
+
droidScoreMultiplier = 1.12;
|
|
2191
|
+
pcScoreMultiplier = 1.12;
|
|
2153
2192
|
bitwise = 1 << 10;
|
|
2154
2193
|
droidString = "i";
|
|
2155
|
-
droidOnly = false;
|
|
2156
2194
|
}
|
|
2157
2195
|
|
|
2158
2196
|
/**
|
|
@@ -2161,112 +2199,106 @@ class ModFlashlight extends Mod {
|
|
|
2161
2199
|
class ModHidden extends Mod {
|
|
2162
2200
|
static fadeInDurationMultiplier = 0.4;
|
|
2163
2201
|
static fadeOutDurationMultiplier = 0.3;
|
|
2164
|
-
scoreMultiplier = 1.06;
|
|
2165
2202
|
acronym = "HD";
|
|
2166
2203
|
name = "Hidden";
|
|
2167
2204
|
bitwise = 1 << 3;
|
|
2168
2205
|
droidRanked = true;
|
|
2169
2206
|
pcRanked = true;
|
|
2207
|
+
droidScoreMultiplier = 1.06;
|
|
2208
|
+
pcScoreMultiplier = 1.06;
|
|
2170
2209
|
droidString = "h";
|
|
2171
|
-
droidOnly = false;
|
|
2172
2210
|
}
|
|
2173
2211
|
|
|
2174
2212
|
/**
|
|
2175
2213
|
* Represents the NoFail mod.
|
|
2176
2214
|
*/
|
|
2177
2215
|
class ModNoFail extends Mod {
|
|
2178
|
-
scoreMultiplier = 0.5;
|
|
2179
2216
|
acronym = "NF";
|
|
2180
2217
|
name = "NoFail";
|
|
2181
2218
|
droidRanked = true;
|
|
2182
2219
|
pcRanked = true;
|
|
2220
|
+
droidScoreMultiplier = 0.5;
|
|
2221
|
+
pcScoreMultiplier = 0.5;
|
|
2183
2222
|
bitwise = 1 << 0;
|
|
2184
2223
|
droidString = "n";
|
|
2185
|
-
droidOnly = false;
|
|
2186
2224
|
}
|
|
2187
2225
|
|
|
2188
2226
|
/**
|
|
2189
2227
|
* Represents the Perfect mod.
|
|
2190
2228
|
*/
|
|
2191
2229
|
class ModPerfect extends Mod {
|
|
2192
|
-
scoreMultiplier = 1;
|
|
2193
2230
|
acronym = "PF";
|
|
2194
2231
|
name = "Perfect";
|
|
2195
2232
|
droidRanked = false;
|
|
2196
2233
|
pcRanked = true;
|
|
2234
|
+
droidScoreMultiplier = 1;
|
|
2235
|
+
pcScoreMultiplier = 1;
|
|
2197
2236
|
bitwise = 1 << 14;
|
|
2198
2237
|
droidString = "f";
|
|
2199
|
-
droidOnly = false;
|
|
2200
2238
|
}
|
|
2201
2239
|
|
|
2202
2240
|
/**
|
|
2203
2241
|
* Represents the Relax mod.
|
|
2204
2242
|
*/
|
|
2205
2243
|
class ModRelax extends Mod {
|
|
2206
|
-
scoreMultiplier = 0;
|
|
2207
2244
|
acronym = "RX";
|
|
2208
2245
|
name = "Relax";
|
|
2209
2246
|
droidRanked = false;
|
|
2210
2247
|
pcRanked = false;
|
|
2248
|
+
droidScoreMultiplier = 0.001;
|
|
2249
|
+
pcScoreMultiplier = 0;
|
|
2211
2250
|
bitwise = 1 << 7;
|
|
2212
2251
|
droidString = "x";
|
|
2213
|
-
droidOnly = false;
|
|
2214
2252
|
}
|
|
2215
2253
|
|
|
2216
2254
|
/**
|
|
2217
2255
|
* Represents the ScoreV2 mod.
|
|
2218
2256
|
*/
|
|
2219
2257
|
class ModScoreV2 extends Mod {
|
|
2220
|
-
scoreMultiplier = 1;
|
|
2221
2258
|
acronym = "V2";
|
|
2222
2259
|
name = "ScoreV2";
|
|
2223
2260
|
droidRanked = false;
|
|
2224
2261
|
pcRanked = false;
|
|
2262
|
+
droidScoreMultiplier = 1;
|
|
2263
|
+
pcScoreMultiplier = 1;
|
|
2225
2264
|
bitwise = 1 << 29;
|
|
2226
2265
|
droidString = "v";
|
|
2227
|
-
droidOnly = false;
|
|
2228
2266
|
}
|
|
2229
2267
|
|
|
2230
2268
|
/**
|
|
2231
2269
|
* Represents the SpunOut mod.
|
|
2232
2270
|
*/
|
|
2233
2271
|
class ModSpunOut extends Mod {
|
|
2234
|
-
scoreMultiplier = 0.9;
|
|
2235
2272
|
acronym = "SO";
|
|
2236
2273
|
name = "SpunOut";
|
|
2237
|
-
droidRanked = false;
|
|
2238
2274
|
pcRanked = true;
|
|
2275
|
+
pcScoreMultiplier = 0.9;
|
|
2239
2276
|
bitwise = 1 << 12;
|
|
2240
|
-
droidString = "";
|
|
2241
|
-
droidOnly = false;
|
|
2242
2277
|
}
|
|
2243
2278
|
|
|
2244
2279
|
/**
|
|
2245
2280
|
* Represents the SuddenDeath mod.
|
|
2246
2281
|
*/
|
|
2247
2282
|
class ModSuddenDeath extends Mod {
|
|
2248
|
-
scoreMultiplier = 1;
|
|
2249
2283
|
acronym = "SD";
|
|
2250
2284
|
name = "Sudden Death";
|
|
2251
2285
|
droidRanked = false;
|
|
2252
2286
|
pcRanked = true;
|
|
2287
|
+
droidScoreMultiplier = 1;
|
|
2288
|
+
pcScoreMultiplier = 1;
|
|
2253
2289
|
bitwise = 1 << 5;
|
|
2254
2290
|
droidString = "u";
|
|
2255
|
-
droidOnly = false;
|
|
2256
2291
|
}
|
|
2257
2292
|
|
|
2258
2293
|
/**
|
|
2259
2294
|
* Represents the TouchDevice mod.
|
|
2260
2295
|
*/
|
|
2261
2296
|
class ModTouchDevice extends Mod {
|
|
2262
|
-
scoreMultiplier = 1;
|
|
2263
2297
|
acronym = "TD";
|
|
2264
2298
|
name = "TouchDevice";
|
|
2265
|
-
droidRanked = true;
|
|
2266
2299
|
pcRanked = true;
|
|
2300
|
+
pcScoreMultiplier = 1;
|
|
2267
2301
|
bitwise = 1 << 2;
|
|
2268
|
-
droidString = "";
|
|
2269
|
-
droidOnly = false;
|
|
2270
2302
|
}
|
|
2271
2303
|
|
|
2272
2304
|
/**
|
|
@@ -2331,7 +2363,8 @@ class ModUtil {
|
|
|
2331
2363
|
* @param options Options for parsing behavior.
|
|
2332
2364
|
*/
|
|
2333
2365
|
static droidStringToMods(str, options) {
|
|
2334
|
-
return this.processParsingOptions(this.allMods.filter((m) => m.
|
|
2366
|
+
return this.processParsingOptions(this.allMods.filter((m) => m.isApplicableToDroid() &&
|
|
2367
|
+
str.toLowerCase().includes(m.droidString)), options);
|
|
2335
2368
|
}
|
|
2336
2369
|
/**
|
|
2337
2370
|
* Gets a list of mods from a PC modbits.
|
|
@@ -2340,7 +2373,7 @@ class ModUtil {
|
|
|
2340
2373
|
* @param options Options for parsing behavior.
|
|
2341
2374
|
*/
|
|
2342
2375
|
static pcModbitsToMods(modbits, options) {
|
|
2343
|
-
return this.processParsingOptions(this.allMods.filter((m) => m.bitwise & modbits), options);
|
|
2376
|
+
return this.processParsingOptions(this.allMods.filter((m) => m.isApplicableToOsu() && (m.bitwise & modbits)), options);
|
|
2344
2377
|
}
|
|
2345
2378
|
/**
|
|
2346
2379
|
* Gets a list of mods from a PC mod string, such as "HDHR".
|
|
@@ -2381,11 +2414,10 @@ class ModUtil {
|
|
|
2381
2414
|
*/
|
|
2382
2415
|
static checkIncompatibleMods(mods) {
|
|
2383
2416
|
for (const incompatibleMod of this.incompatibleMods) {
|
|
2384
|
-
const fulfilledMods = mods.filter((m) => incompatibleMod.
|
|
2417
|
+
const fulfilledMods = mods.filter((m) => incompatibleMod.some((v) => m.acronym === v.acronym));
|
|
2385
2418
|
if (fulfilledMods.length > 1) {
|
|
2386
|
-
mods = mods.filter((m) =>
|
|
2387
|
-
.
|
|
2388
|
-
.includes(m.acronym));
|
|
2419
|
+
mods = mods.filter((m) => incompatibleMod
|
|
2420
|
+
.every((v) => m.acronym !== v.acronym));
|
|
2389
2421
|
// Keep the first selected mod
|
|
2390
2422
|
mods.push(fulfilledMods[0]);
|
|
2391
2423
|
}
|
|
@@ -3304,6 +3336,9 @@ class Decoder {
|
|
|
3304
3336
|
for (let line of str.split("\n")) {
|
|
3305
3337
|
this.currentLine = line;
|
|
3306
3338
|
++this.line;
|
|
3339
|
+
if (this.shouldSkipLine(line)) {
|
|
3340
|
+
continue;
|
|
3341
|
+
}
|
|
3307
3342
|
if (this.section !== BeatmapSection.metadata) {
|
|
3308
3343
|
// Comments should not be stripped from metadata lines, as the song metadata may contain "//" as valid data.
|
|
3309
3344
|
const index = line.indexOf("//");
|
|
@@ -3341,6 +3376,15 @@ class Decoder {
|
|
|
3341
3376
|
}
|
|
3342
3377
|
return this;
|
|
3343
3378
|
}
|
|
3379
|
+
/**
|
|
3380
|
+
* Determines whether a line should be skipped.
|
|
3381
|
+
*
|
|
3382
|
+
* @param line The line to determine.
|
|
3383
|
+
* @returns Whether the line should be skipped.
|
|
3384
|
+
*/
|
|
3385
|
+
shouldSkipLine(line) {
|
|
3386
|
+
return !line || line.trimStart().startsWith("//");
|
|
3387
|
+
}
|
|
3344
3388
|
/**
|
|
3345
3389
|
* Internal decoder function for decoding a line.
|
|
3346
3390
|
*
|
|
@@ -3420,29 +3464,43 @@ class SectionDecoder {
|
|
|
3420
3464
|
* @param str The string to parse.
|
|
3421
3465
|
* @param min The minimum threshold. Defaults to `-ParserConstants.MAX_PARSE_VALUE`.
|
|
3422
3466
|
* @param max The maximum threshold. Defaults to `ParserConstants.MAX_PARSE_VALUE`.
|
|
3467
|
+
* @param allowNaN Whether to allow NaN.
|
|
3423
3468
|
* @returns The parsed integer.
|
|
3424
3469
|
*/
|
|
3425
|
-
tryParseInt(str, min = -ParserConstants.MAX_PARSE_VALUE, max = ParserConstants.MAX_PARSE_VALUE) {
|
|
3470
|
+
tryParseInt(str, min = -ParserConstants.MAX_PARSE_VALUE, max = ParserConstants.MAX_PARSE_VALUE, allowNaN = false) {
|
|
3426
3471
|
const num = parseInt(str);
|
|
3427
|
-
if (
|
|
3428
|
-
throw new RangeError(
|
|
3472
|
+
if (num < min) {
|
|
3473
|
+
throw new RangeError("Value is too low");
|
|
3474
|
+
}
|
|
3475
|
+
if (num > max) {
|
|
3476
|
+
throw new RangeError("Value is too high");
|
|
3477
|
+
}
|
|
3478
|
+
if (!allowNaN && Number.isNaN(num)) {
|
|
3479
|
+
throw new RangeError("Not a number");
|
|
3429
3480
|
}
|
|
3430
3481
|
return num;
|
|
3431
3482
|
}
|
|
3432
3483
|
/**
|
|
3433
3484
|
* Attempts to parse a string into a float.
|
|
3434
3485
|
*
|
|
3435
|
-
* Throws an exception when the resulting value is
|
|
3486
|
+
* Throws an exception when the resulting value is too low or too high.
|
|
3436
3487
|
*
|
|
3437
3488
|
* @param str The string to parse.
|
|
3438
3489
|
* @param min The minimum threshold. Defaults to `-ParserConstants.MAX_PARSE_VALUE`.
|
|
3439
3490
|
* @param max The maximum threshold. Defaults to `ParserConstants.MAX_PARSE_VALUE`.
|
|
3491
|
+
* @param allowNaN Whether to allow NaN.
|
|
3440
3492
|
* @returns The parsed float.
|
|
3441
3493
|
*/
|
|
3442
|
-
tryParseFloat(str, min = -ParserConstants.MAX_PARSE_VALUE, max = ParserConstants.MAX_PARSE_VALUE) {
|
|
3494
|
+
tryParseFloat(str, min = -ParserConstants.MAX_PARSE_VALUE, max = ParserConstants.MAX_PARSE_VALUE, allowNaN = false) {
|
|
3443
3495
|
const num = parseFloat(str);
|
|
3444
|
-
if (
|
|
3445
|
-
throw new RangeError(
|
|
3496
|
+
if (num < min) {
|
|
3497
|
+
throw new RangeError("Value is too low");
|
|
3498
|
+
}
|
|
3499
|
+
if (num > max) {
|
|
3500
|
+
throw new RangeError("Value is too high");
|
|
3501
|
+
}
|
|
3502
|
+
if (!allowNaN && Number.isNaN(num)) {
|
|
3503
|
+
throw new RangeError("Not a number");
|
|
3446
3504
|
}
|
|
3447
3505
|
return num;
|
|
3448
3506
|
}
|
|
@@ -3502,8 +3560,8 @@ class BeatmapHitObjectsDecoder extends SectionDecoder {
|
|
|
3502
3560
|
}
|
|
3503
3561
|
const repetitions = Math.max(0, this.tryParseInt(this.setPosition(s[6]), -ParserConstants.MAX_PARSE_VALUE, ParserConstants.MAX_REPETITIONS_VALUE));
|
|
3504
3562
|
const distance = Math.max(0, this.tryParseFloat(this.setPosition(s[7])));
|
|
3505
|
-
const
|
|
3506
|
-
const
|
|
3563
|
+
const difficultyControlPoint = this.target.controlPoints.difficulty.controlPointAt(time);
|
|
3564
|
+
const timingControlPoint = this.target.controlPoints.timing.controlPointAt(time);
|
|
3507
3565
|
const points = [new Vector2(0, 0)];
|
|
3508
3566
|
const pointSplit = this.setPosition(s[5]).split("|");
|
|
3509
3567
|
let pathType = pointSplit.shift();
|
|
@@ -3573,6 +3631,23 @@ class BeatmapHitObjectsDecoder extends SectionDecoder {
|
|
|
3573
3631
|
comboOffset += this.extraComboOffset;
|
|
3574
3632
|
this.forceNewCombo = false;
|
|
3575
3633
|
this.extraComboOffset = 0;
|
|
3634
|
+
let tickDistanceMultiplier = Number.POSITIVE_INFINITY;
|
|
3635
|
+
if (difficultyControlPoint.generateTicks) {
|
|
3636
|
+
if (this.isNumberValid(timingControlPoint.msPerBeat, ParserConstants.MIN_MSPERBEAT_VALUE, ParserConstants.MAX_MSPERBEAT_VALUE)) {
|
|
3637
|
+
// Prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance.
|
|
3638
|
+
// This results in more (or less) ticks being generated in <v8 maps for the same time duration.
|
|
3639
|
+
//
|
|
3640
|
+
// This additional check is used in case BPM goes very low or very high.
|
|
3641
|
+
// When lazer is final, this should be revisited.
|
|
3642
|
+
tickDistanceMultiplier =
|
|
3643
|
+
this.target.formatVersion < 8
|
|
3644
|
+
? 1 / difficultyControlPoint.speedMultiplier
|
|
3645
|
+
: 1;
|
|
3646
|
+
}
|
|
3647
|
+
else {
|
|
3648
|
+
tickDistanceMultiplier = 0;
|
|
3649
|
+
}
|
|
3650
|
+
}
|
|
3576
3651
|
object = new Slider({
|
|
3577
3652
|
position: position,
|
|
3578
3653
|
startTime: time,
|
|
@@ -3582,20 +3657,11 @@ class BeatmapHitObjectsDecoder extends SectionDecoder {
|
|
|
3582
3657
|
nodeSamples: nodeSamples,
|
|
3583
3658
|
repetitions: repetitions,
|
|
3584
3659
|
path: path,
|
|
3585
|
-
speedMultiplier: MathUtils.clamp(
|
|
3586
|
-
msPerBeat:
|
|
3660
|
+
speedMultiplier: MathUtils.clamp(difficultyControlPoint.speedMultiplier, ParserConstants.MIN_SPEEDMULTIPLIER_VALUE, ParserConstants.MAX_SPEEDMULTIPLIER_VALUE),
|
|
3661
|
+
msPerBeat: timingControlPoint.msPerBeat,
|
|
3587
3662
|
mapSliderVelocity: this.target.difficulty.sliderMultiplier,
|
|
3588
3663
|
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,
|
|
3664
|
+
tickDistanceMultiplier: tickDistanceMultiplier,
|
|
3599
3665
|
});
|
|
3600
3666
|
}
|
|
3601
3667
|
else if (type & exports.objectTypes.spinner) {
|
|
@@ -4143,7 +4209,9 @@ class BeatmapControlPointsDecoder extends SectionDecoder {
|
|
|
4143
4209
|
throw new Error("Ignoring malformed timing point");
|
|
4144
4210
|
}
|
|
4145
4211
|
const time = this.target.getOffsetTime(this.tryParseFloat(this.setPosition(s[0])));
|
|
4146
|
-
|
|
4212
|
+
// msPerBeat is allowed to be NaN to handle an edge case in which some
|
|
4213
|
+
// beatmaps use NaN slider velocity to disable slider tick generation.
|
|
4214
|
+
const msPerBeat = this.tryParseFloat(this.setPosition(s[1]), undefined, undefined, true);
|
|
4147
4215
|
let timeSignature = 4;
|
|
4148
4216
|
if (s.length >= 3) {
|
|
4149
4217
|
timeSignature = this.tryParseInt(this.setPosition(s[2]));
|
|
@@ -4170,7 +4238,14 @@ class BeatmapControlPointsDecoder extends SectionDecoder {
|
|
|
4170
4238
|
kiaiMode = !!(effectBitFlags & EffectFlags.kiai);
|
|
4171
4239
|
omitFirstBarSignature = !!(effectBitFlags & EffectFlags.omitFirstBarLine);
|
|
4172
4240
|
}
|
|
4173
|
-
|
|
4241
|
+
let timingChange = true;
|
|
4242
|
+
if (s.length >= 7) {
|
|
4243
|
+
timingChange = s[6] === "1";
|
|
4244
|
+
}
|
|
4245
|
+
if (timingChange) {
|
|
4246
|
+
if (Number.isNaN(msPerBeat)) {
|
|
4247
|
+
throw new Error("Beat length cannot be NaN in a timing control point");
|
|
4248
|
+
}
|
|
4174
4249
|
this.target.controlPoints.timing.add(new TimingControlPoint({
|
|
4175
4250
|
time: time,
|
|
4176
4251
|
msPerBeat: msPerBeat,
|
|
@@ -4179,7 +4254,9 @@ class BeatmapControlPointsDecoder extends SectionDecoder {
|
|
|
4179
4254
|
}
|
|
4180
4255
|
this.target.controlPoints.difficulty.add(new DifficultyControlPoint({
|
|
4181
4256
|
time: time,
|
|
4257
|
+
// If msPerBeat is NaN, speedMultiplier should still be 1 because all comparisons against NaN are false.
|
|
4182
4258
|
speedMultiplier: msPerBeat < 0 ? 100 / -msPerBeat : 1,
|
|
4259
|
+
generateTicks: !Number.isNaN(msPerBeat),
|
|
4183
4260
|
}));
|
|
4184
4261
|
this.target.controlPoints.effect.add(new EffectControlPoint({
|
|
4185
4262
|
time: time,
|