@rian8337/osu-base 4.0.0-beta.31 → 4.0.0-beta.39

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.
Files changed (3) hide show
  1. package/dist/index.js +430 -185
  2. package/package.json +2 -2
  3. package/typings/index.d.ts +224 -113
package/dist/index.js CHANGED
@@ -194,6 +194,17 @@ exports.AnimationLoopType = void 0;
194
194
  AnimationLoopType["loopOnce"] = "LoopOnce";
195
195
  })(exports.AnimationLoopType || (exports.AnimationLoopType = {}));
196
196
 
197
+ /**
198
+ * Represents available sample banks.
199
+ */
200
+ exports.SampleBank = void 0;
201
+ (function (SampleBank) {
202
+ SampleBank[SampleBank["none"] = 0] = "none";
203
+ SampleBank[SampleBank["normal"] = 1] = "normal";
204
+ SampleBank[SampleBank["soft"] = 2] = "soft";
205
+ SampleBank[SampleBank["drum"] = 3] = "drum";
206
+ })(exports.SampleBank || (exports.SampleBank = {}));
207
+
197
208
  /**
198
209
  * Represents a gameplay hit sample.
199
210
  */
@@ -207,7 +218,30 @@ class HitSampleInfo {
207
218
  * Represents a pre-determined gameplay hit sample that can be loaded from banks.
208
219
  */
209
220
  class BankHitSampleInfo extends HitSampleInfo {
210
- constructor(name, bank, customSampleBank = 0, volume = 0, isLayered = false) {
221
+ get lookupNames() {
222
+ const names = [];
223
+ let prefix;
224
+ switch (this.bank) {
225
+ case exports.SampleBank.none:
226
+ prefix = "";
227
+ break;
228
+ case exports.SampleBank.normal:
229
+ prefix = "normal";
230
+ break;
231
+ case exports.SampleBank.soft:
232
+ prefix = "soft";
233
+ break;
234
+ case exports.SampleBank.drum:
235
+ prefix = "drum";
236
+ break;
237
+ }
238
+ if (this.customSampleBank >= 2) {
239
+ names.push(`${prefix}-${this.name}${this.customSampleBank}`);
240
+ }
241
+ names.push(`${prefix}-${this.name}`, this.name);
242
+ return names;
243
+ }
244
+ constructor(name, bank = exports.SampleBank.none, customSampleBank = 0, volume = 0, isLayered = false) {
211
245
  super(volume);
212
246
  this.name = name;
213
247
  this.bank = bank;
@@ -241,17 +275,6 @@ exports.ObjectTypes = void 0;
241
275
  ObjectTypes[ObjectTypes["comboOffset"] = 112] = "comboOffset";
242
276
  })(exports.ObjectTypes || (exports.ObjectTypes = {}));
243
277
 
244
- /**
245
- * Represents available sample banks.
246
- */
247
- exports.SampleBank = void 0;
248
- (function (SampleBank) {
249
- SampleBank[SampleBank["none"] = 0] = "none";
250
- SampleBank[SampleBank["normal"] = 1] = "normal";
251
- SampleBank[SampleBank["soft"] = 2] = "soft";
252
- SampleBank[SampleBank["drum"] = 3] = "drum";
253
- })(exports.SampleBank || (exports.SampleBank = {}));
254
-
255
278
  /**
256
279
  * Represents a two-dimensional vector.
257
280
  */
@@ -604,6 +627,106 @@ CircleSizeCalculator.brokenGamefieldRoundingAllowance = 1.00041;
604
627
  */
605
628
  CircleSizeCalculator.assumedDroidHeight = 681;
606
629
 
630
+ /**
631
+ * Represents a hit window.
632
+ */
633
+ class HitWindow {
634
+ /**
635
+ * @param overallDifficulty The overall difficulty of this `HitWindow`. Defaults to 5.
636
+ */
637
+ constructor(overallDifficulty = 5) {
638
+ this.overallDifficulty = overallDifficulty;
639
+ }
640
+ }
641
+ /**
642
+ * A fixed miss hit window regardless of difficulty settings.
643
+ */
644
+ HitWindow.missWindow = 400;
645
+
646
+ /**
647
+ * Represents the hit window of osu!droid _without_ the Precise mod.
648
+ */
649
+ class DroidHitWindow extends HitWindow {
650
+ /**
651
+ * Calculates the overall difficulty value of a great (300) hit window.
652
+ *
653
+ * @param value The value of the hit window, in milliseconds.
654
+ * @returns The overall difficulty value.
655
+ */
656
+ static greatWindowToOD(value) {
657
+ return 5 - (value - 75) / 5;
658
+ }
659
+ /**
660
+ * Calculates the overall difficulty value of a good (100) hit window.
661
+ *
662
+ * @param value The value of the hit window, in milliseconds.
663
+ * @returns The overall difficulty value.
664
+ */
665
+ static okWindowToOD(value) {
666
+ return 5 - (value - 150) / 10;
667
+ }
668
+ /**
669
+ * Calculates the overall difficulty value of a meh (50) hit window.
670
+ *
671
+ * @param value The value of the hit window, in milliseconds.
672
+ * @returns The overall difficulty value.
673
+ */
674
+ static mehWindowToOD(value) {
675
+ return 5 - (value - 250) / 10;
676
+ }
677
+ get greatWindow() {
678
+ return 75 + 5 * (5 - this.overallDifficulty);
679
+ }
680
+ get okWindow() {
681
+ return 150 + 10 * (5 - this.overallDifficulty);
682
+ }
683
+ get mehWindow() {
684
+ return 250 + 10 * (5 - this.overallDifficulty);
685
+ }
686
+ }
687
+
688
+ /**
689
+ * Represents the hit window of osu!standard.
690
+ */
691
+ class OsuHitWindow extends HitWindow {
692
+ /**
693
+ * Calculates the overall difficulty value of a great (300) hit window.
694
+ *
695
+ * @param value The value of the hit window, in milliseconds.
696
+ * @returns The overall difficulty value.
697
+ */
698
+ static greatWindowToOD(value) {
699
+ return (80 - value) / 6;
700
+ }
701
+ /**
702
+ * Calculates the overall difficulty value of a good (100) hit window.
703
+ *
704
+ * @param value The value of the hit window, in milliseconds.
705
+ * @returns The overall difficulty value.
706
+ */
707
+ static okWindowToOD(value) {
708
+ return (140 - value) / 8;
709
+ }
710
+ /**
711
+ * Calculates the overall difficulty value of a meh hit window.
712
+ *
713
+ * @param value The value of the hit window, in milliseconds.
714
+ * @returns The overall difficulty value.
715
+ */
716
+ static mehWindowToOD(value) {
717
+ return (200 - value) / 10;
718
+ }
719
+ get greatWindow() {
720
+ return 80 - 6 * this.overallDifficulty;
721
+ }
722
+ get okWindow() {
723
+ return 140 - 8 * this.overallDifficulty;
724
+ }
725
+ get mehWindow() {
726
+ return 200 - 10 * this.overallDifficulty;
727
+ }
728
+ }
729
+
607
730
  /**
608
731
  * Represents a hitobject in a beatmap.
609
732
  */
@@ -614,9 +737,6 @@ class HitObject {
614
737
  get position() {
615
738
  return this._position;
616
739
  }
617
- /**
618
- * The position of the hitobject in osu!pixels.
619
- */
620
740
  set position(value) {
621
741
  this._position = value;
622
742
  }
@@ -639,14 +759,17 @@ class HitObject {
639
759
  return this.endTime - this.startTime;
640
760
  }
641
761
  /**
642
- * The stack height of this hitobject.
762
+ * Whether this hitobject is in kiai time.
643
763
  */
644
- get stackHeight() {
645
- return this._stackHeight;
764
+ get kiai() {
765
+ return this._kiai;
646
766
  }
647
767
  /**
648
768
  * The stack height of this hitobject.
649
769
  */
770
+ get stackHeight() {
771
+ return this._stackHeight;
772
+ }
650
773
  set stackHeight(value) {
651
774
  this._stackHeight = value;
652
775
  }
@@ -656,9 +779,6 @@ class HitObject {
656
779
  get scale() {
657
780
  return this._scale;
658
781
  }
659
- /**
660
- * The osu!standard scale of this hitobject.
661
- */
662
782
  set scale(value) {
663
783
  this._scale = value;
664
784
  }
@@ -697,13 +817,12 @@ class HitObject {
697
817
  * Any samples which may be used by this hitobject that are non-standard.
698
818
  */
699
819
  this.auxiliarySamples = [];
820
+ this._kiai = false;
700
821
  /**
701
- * The stack height of this hitobject.
822
+ * The hit window of this hitobject.
702
823
  */
824
+ this.hitWindow = null;
703
825
  this._stackHeight = 0;
704
- /**
705
- * The osu!standard scale of this hitobject.
706
- */
707
826
  this._scale = 1;
708
827
  /**
709
828
  * The time at which the approach circle of this hitobject should appear before this hitobject starts.
@@ -727,6 +846,12 @@ class HitObject {
727
846
  * @param mode The gamemode to apply defaults for.
728
847
  */
729
848
  applyDefaults(controlPoints, difficulty, mode) {
849
+ var _a;
850
+ this._kiai = controlPoints.effect.controlPointAt(this.startTime + HitObject.controlPointLeniency).isKiai;
851
+ (_a = this.hitWindow) !== null && _a !== void 0 ? _a : (this.hitWindow = this.createHitWindow(mode));
852
+ if (this.hitWindow) {
853
+ this.hitWindow.overallDifficulty = difficulty.od;
854
+ }
730
855
  this.timePreempt = BeatmapDifficulty.difficultyRange(difficulty.ar, HitObject.preemptMax, HitObject.preemptMid, HitObject.preemptMin);
731
856
  // Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR.
732
857
  // This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above.
@@ -809,6 +934,24 @@ class HitObject {
809
934
  }
810
935
  return new BankHitSampleInfo(sampleName, exports.SampleBank.none);
811
936
  }
937
+ /**
938
+ * Creates the hit window of this hitobject.
939
+ *
940
+ * A `null` return means that this hitobject has no hit window and timing errors should not be displayed to the user.
941
+ *
942
+ * This will only be called if this hitobject's hit window has not been set externally.
943
+ *
944
+ * @param mode The gamemode to create the hit window for.
945
+ * @returns The created hit window.
946
+ */
947
+ createHitWindow(mode) {
948
+ switch (mode) {
949
+ case exports.Modes.droid:
950
+ return new DroidHitWindow();
951
+ case exports.Modes.osu:
952
+ return new OsuHitWindow();
953
+ }
954
+ }
812
955
  /**
813
956
  * Evaluates the stacked position of the specified position.
814
957
  *
@@ -867,22 +1010,51 @@ class SliderHead extends SliderNestedHitObject {
867
1010
  }
868
1011
  }
869
1012
 
1013
+ /**
1014
+ * An empty `HitWindow` that does not have any hit windows.
1015
+ *
1016
+ * No time values are provided (meaning instantaneous hit or miss).
1017
+ */
1018
+ class EmptyHitWindow extends HitWindow {
1019
+ constructor() {
1020
+ super(0);
1021
+ }
1022
+ get greatWindow() {
1023
+ return 0;
1024
+ }
1025
+ get okWindow() {
1026
+ return 0;
1027
+ }
1028
+ get mehWindow() {
1029
+ return 0;
1030
+ }
1031
+ }
1032
+
870
1033
  /**
871
1034
  * Represents a slider repeat.
872
1035
  */
873
1036
  class SliderRepeat extends SliderNestedHitObject {
1037
+ createHitWindow() {
1038
+ return new EmptyHitWindow();
1039
+ }
874
1040
  }
875
1041
 
876
1042
  /**
877
1043
  * Represents a slider tick in a slider.
878
1044
  */
879
1045
  class SliderTick extends SliderNestedHitObject {
1046
+ createHitWindow() {
1047
+ return new EmptyHitWindow();
1048
+ }
880
1049
  }
881
1050
 
882
1051
  /**
883
1052
  * Represents the tail of a slider.
884
1053
  */
885
1054
  class SliderTail extends SliderNestedHitObject {
1055
+ createHitWindow() {
1056
+ return new EmptyHitWindow();
1057
+ }
886
1058
  }
887
1059
 
888
1060
  /**
@@ -929,6 +1101,84 @@ class Cached {
929
1101
  }
930
1102
  }
931
1103
 
1104
+ /**
1105
+ * Represents a gameplay hit sample that is meant to be played sequentially at specific times.
1106
+ */
1107
+ class SequenceHitSampleInfo {
1108
+ /**
1109
+ * Whether this `SequenceHitSampleInfo` contains no `TimedHitSampleInfo`s.
1110
+ */
1111
+ get isEmpty() {
1112
+ return this.samples.length === 0;
1113
+ }
1114
+ constructor(samples) {
1115
+ this.samples = samples;
1116
+ }
1117
+ /**
1118
+ * Obtains the `TimedHitSampleInfo` to play at a given time.
1119
+ *
1120
+ * @param time The time, in milliseconds.
1121
+ * @return The `TimedHitSampleInfo` to play at the given time,
1122
+ * or `null` if no `TimedHitSampleInfo`s should be played.
1123
+ */
1124
+ sampleAt(time) {
1125
+ if (this.isEmpty || time < this.samples[0].time) {
1126
+ return null;
1127
+ }
1128
+ const lastSample = this.samples[this.samples.length - 1];
1129
+ if (time >= lastSample.time) {
1130
+ return lastSample;
1131
+ }
1132
+ let l = 0;
1133
+ let r = this.samples.length - 2;
1134
+ while (l <= r) {
1135
+ const pivot = l + ((r - l) >> 1);
1136
+ const sample = this.samples[pivot];
1137
+ if (sample.time < time) {
1138
+ l = pivot + 1;
1139
+ }
1140
+ else if (sample.time > time) {
1141
+ r = pivot - 1;
1142
+ }
1143
+ else {
1144
+ return sample;
1145
+ }
1146
+ }
1147
+ // l will be the first sample with time > sample.time, but we want the one before it
1148
+ return this.samples[l - 1];
1149
+ }
1150
+ }
1151
+
1152
+ /**
1153
+ * A `HitSampleInfo` that has a time associated with it.
1154
+ */
1155
+ class TimedHitSampleInfo {
1156
+ constructor(time, sample) {
1157
+ this.time = time;
1158
+ this.sample = sample;
1159
+ }
1160
+ }
1161
+
1162
+ /**
1163
+ * Represents a custom gameplay hit sample that can be loaded from files.
1164
+ */
1165
+ class FileHitSampleInfo extends HitSampleInfo {
1166
+ get lookupNames() {
1167
+ const names = [];
1168
+ names.push(this.filename);
1169
+ // Fallback to file name without extension.
1170
+ const extensionIndex = this.filename.lastIndexOf(".");
1171
+ if (extensionIndex !== -1) {
1172
+ names.push(this.filename.substring(0, extensionIndex));
1173
+ }
1174
+ return names;
1175
+ }
1176
+ constructor(filename, volume = 0) {
1177
+ super(volume);
1178
+ this.filename = filename;
1179
+ }
1180
+ }
1181
+
932
1182
  /**
933
1183
  * Represents a slider in a beatmap.
934
1184
  */
@@ -1109,22 +1359,11 @@ class Slider extends HitObject {
1109
1359
  ? (scoringDistance / difficulty.sliderTickRate) *
1110
1360
  this.tickDistanceMultiplier
1111
1361
  : Number.POSITIVE_INFINITY;
1112
- this.createNestedHitObjects(mode);
1362
+ this.createNestedHitObjects(mode, controlPoints);
1113
1363
  this.nestedHitObjects.forEach((v) => v.applyDefaults(controlPoints, difficulty, mode));
1114
1364
  }
1115
1365
  applySamples(controlPoints) {
1116
1366
  super.applySamples(controlPoints);
1117
- // Create sliding samples
1118
- this.auxiliarySamples.length = 0;
1119
- const bankSamples = this.samples.filter((v) => v instanceof BankHitSampleInfo);
1120
- const normalSample = bankSamples.find((v) => v.name === BankHitSampleInfo.HIT_NORMAL);
1121
- if (normalSample) {
1122
- this.auxiliarySamples.push(new BankHitSampleInfo("sliderslide", normalSample.bank, normalSample.customSampleBank, normalSample.volume, normalSample.isLayered));
1123
- }
1124
- const whistleSample = bankSamples.find((v) => v.name === BankHitSampleInfo.HIT_WHISTLE);
1125
- if (whistleSample) {
1126
- this.auxiliarySamples.push(new BankHitSampleInfo("sliderwhistle", whistleSample.bank, whistleSample.customSampleBank, whistleSample.volume, whistleSample.isLayered));
1127
- }
1128
1367
  this.nodeSamples.forEach((nodeSample, i) => {
1129
1368
  const time = this.startTime +
1130
1369
  i * this.spanDuration +
@@ -1132,6 +1371,9 @@ class Slider extends HitObject {
1132
1371
  const nodeSamplePoint = controlPoints.sample.controlPointAt(time);
1133
1372
  this.nodeSamples[i] = nodeSample.map((v) => nodeSamplePoint.applyTo(v));
1134
1373
  });
1374
+ // Create sliding samples
1375
+ this.createSlidingSamples(controlPoints);
1376
+ this.updateNestedSamples(controlPoints);
1135
1377
  }
1136
1378
  /**
1137
1379
  * Computes the position on this slider relative to how much of the slider has been completed.
@@ -1164,7 +1406,10 @@ class Slider extends HitObject {
1164
1406
  spanAt(progress) {
1165
1407
  return Math.floor(progress * this.spanCount);
1166
1408
  }
1167
- createNestedHitObjects(mode) {
1409
+ createHitWindow() {
1410
+ return new EmptyHitWindow();
1411
+ }
1412
+ createNestedHitObjects(mode, controlPoints) {
1168
1413
  this.nestedHitObjects.length = 0;
1169
1414
  this._head = new SliderHead({
1170
1415
  position: this.position,
@@ -1250,15 +1495,62 @@ class Slider extends HitObject {
1250
1495
  }
1251
1496
  this.nestedHitObjects.push(this.tail);
1252
1497
  this.nestedHitObjects.sort((a, b) => a.startTime - b.startTime);
1253
- this.updateNestedSamples();
1498
+ this.updateNestedSamples(controlPoints);
1254
1499
  }
1255
1500
  updateNestedPositions() {
1256
1501
  this.endPositionCache.invalidate();
1257
1502
  this.head.position = this.position;
1258
1503
  this.tail.position = this.endPosition;
1259
1504
  }
1260
- updateNestedSamples() {
1505
+ createSlidingSamples(controlPoints) {
1506
+ this.auxiliarySamples.length = 0;
1507
+ const bankSamples = this.samples.filter((v) => v instanceof BankHitSampleInfo);
1508
+ const normalSample = bankSamples.find((v) => v.name === BankHitSampleInfo.HIT_NORMAL);
1509
+ const whistleSample = bankSamples.find((v) => v.name === BankHitSampleInfo.HIT_WHISTLE);
1510
+ if (!normalSample && !whistleSample) {
1511
+ return;
1512
+ }
1513
+ const samplePoints = controlPoints.sample.between(this.startTime + Slider.controlPointLeniency, this.endTime + Slider.controlPointLeniency);
1514
+ if (normalSample) {
1515
+ this.auxiliarySamples.push(new SequenceHitSampleInfo(samplePoints.map((s) => new TimedHitSampleInfo(s.time, s.applyTo(Slider.baseNormalSlideSample)))));
1516
+ }
1517
+ if (whistleSample) {
1518
+ this.auxiliarySamples.push(new SequenceHitSampleInfo(samplePoints.map((s) => new TimedHitSampleInfo(s.time, s.applyTo(Slider.baseWhistleSlideSample)))));
1519
+ }
1520
+ }
1521
+ updateNestedSamples(controlPoints) {
1261
1522
  var _a;
1523
+ // Ensure that the list of node samples is at least as long as the number of nodes.
1524
+ while (this.nodeSamples.length < this.repeatCount + 2) {
1525
+ this.nodeSamples.push(this.samples.map((s) => {
1526
+ if (s instanceof BankHitSampleInfo) {
1527
+ return new BankHitSampleInfo(s.name, s.bank, s.customSampleBank, s.volume, s.isLayered);
1528
+ }
1529
+ else if (s instanceof FileHitSampleInfo) {
1530
+ return new FileHitSampleInfo(s.filename, s.volume);
1531
+ }
1532
+ else {
1533
+ throw new TypeError("Unknown type of hit sample info.");
1534
+ }
1535
+ }));
1536
+ }
1537
+ for (const nestedObject of this.nestedHitObjects) {
1538
+ nestedObject.samples.length = 0;
1539
+ if (nestedObject instanceof SliderHead) {
1540
+ nestedObject.samples.push(...this.nodeSamples[0]);
1541
+ }
1542
+ else if (nestedObject instanceof SliderRepeat) {
1543
+ nestedObject.samples.push(...this.nodeSamples[nestedObject.spanIndex + 1]);
1544
+ }
1545
+ else if (nestedObject instanceof SliderTail) {
1546
+ nestedObject.samples.push(...this.nodeSamples[this.spanCount]);
1547
+ }
1548
+ else {
1549
+ const time = nestedObject.startTime + Slider.controlPointLeniency;
1550
+ const tickSamplePoint = controlPoints.sample.controlPointAt(time);
1551
+ nestedObject.samples.push(tickSamplePoint.applyTo(Slider.baseTickSample));
1552
+ }
1553
+ }
1262
1554
  const bankSamples = this.samples.filter((v) => v instanceof BankHitSampleInfo);
1263
1555
  const normalSample = (_a = bankSamples.find((v) => v.name === BankHitSampleInfo.HIT_NORMAL)) !== null && _a !== void 0 ? _a : bankSamples.at(0);
1264
1556
  const sampleList = [];
@@ -1286,6 +1578,9 @@ class Slider extends HitObject {
1286
1578
  return `Position: [${this.position.x}, ${this.position.y}], distance: ${this.path.expectedDistance}, repeat count: ${this.repeatCount}, slider ticks: ${this.nestedHitObjects.filter((v) => v instanceof SliderTick).length}`;
1287
1579
  }
1288
1580
  }
1581
+ Slider.baseNormalSlideSample = new BankHitSampleInfo("sliderslide");
1582
+ Slider.baseWhistleSlideSample = new BankHitSampleInfo("sliderwhistle");
1583
+ Slider.baseTickSample = new BankHitSampleInfo("slidertick");
1289
1584
  Slider.legacyLastTickOffset = 36;
1290
1585
 
1291
1586
  /**
@@ -1636,6 +1931,27 @@ class ControlPointManager {
1636
1931
  clear() {
1637
1932
  this._points.length = 0;
1638
1933
  }
1934
+ /**
1935
+ * Gets all control points between two times.
1936
+ *
1937
+ * @param start The start time, in milliseconds.
1938
+ * @param end The end time, in milliseconds.
1939
+ * @return An array of control points between the two times. If `start` is greater than `end`, the control point at
1940
+ * `start` will be returned.
1941
+ */
1942
+ between(startTime, endTime) {
1943
+ if (this._points.length === 0) {
1944
+ return [this.defaultControlPoint];
1945
+ }
1946
+ if (startTime > endTime) {
1947
+ return [this.controlPointAt(startTime)];
1948
+ }
1949
+ // Subtract 1 from start index as the binary search from findInsertionIndex would return the next control point
1950
+ const startIndex = Math.max(0, this.findInsertionIndex(startTime) - 1);
1951
+ // End index does not matter as slice range is exclusive
1952
+ const endIndex = MathUtils.clamp(this.findInsertionIndex(endTime), startIndex + 1, this._points.length);
1953
+ return this._points.slice(startIndex, endIndex);
1954
+ }
1639
1955
  /**
1640
1956
  * Binary searches one of the control point lists to find the active control point at the given time.
1641
1957
  *
@@ -1803,16 +2119,6 @@ class EffectControlPointManager extends ControlPointManager {
1803
2119
  }
1804
2120
  }
1805
2121
 
1806
- /**
1807
- * Represents a custom gameplay hit sample that can be loaded from files.
1808
- */
1809
- class FileHitSampleInfo extends HitSampleInfo {
1810
- constructor(filename, volume = 0) {
1811
- super(volume);
1812
- this.filename = filename;
1813
- }
1814
- }
1815
-
1816
2122
  /**
1817
2123
  * Represents a control point that handles sample sounds.
1818
2124
  */
@@ -1959,12 +2265,10 @@ class Spinner extends HitObject {
1959
2265
  }
1960
2266
  applySamples(controlPoints) {
1961
2267
  super.applySamples(controlPoints);
2268
+ const samplePoints = controlPoints.sample.between(this.startTime + HitObject.controlPointLeniency, this.endTime + HitObject.controlPointLeniency);
1962
2269
  this.auxiliarySamples.length = 0;
1963
- const bankSample = this.samples.find((v) => v instanceof BankHitSampleInfo);
1964
- if (bankSample) {
1965
- this.auxiliarySamples.push(new BankHitSampleInfo("spinnerspin", bankSample.bank, bankSample.customSampleBank, bankSample.volume, bankSample.isLayered));
1966
- }
1967
- this.auxiliarySamples.push(this.createHitSampleInfo("spinnerbonus"));
2270
+ this.auxiliarySamples.push(new SequenceHitSampleInfo(samplePoints.map((s) => new TimedHitSampleInfo(s.time, s.applyTo(Spinner.baseSpinnerSpinSample)))));
2271
+ this.auxiliarySamples.push(new SequenceHitSampleInfo(samplePoints.map((s) => new TimedHitSampleInfo(s.time, s.applyTo(Spinner.baseSpinnerBonusSample)))));
1968
2272
  }
1969
2273
  getStackedPosition() {
1970
2274
  return this.position;
@@ -1972,10 +2276,15 @@ class Spinner extends HitObject {
1972
2276
  getStackedEndPosition() {
1973
2277
  return this.position;
1974
2278
  }
2279
+ createHitWindow() {
2280
+ return new EmptyHitWindow();
2281
+ }
1975
2282
  toString() {
1976
2283
  return `Position: [${this._position.x}, ${this._position.y}], duration: ${this.duration}`;
1977
2284
  }
1978
2285
  }
2286
+ Spinner.baseSpinnerSpinSample = new BankHitSampleInfo("spinnerspin");
2287
+ Spinner.baseSpinnerBonusSample = new BankHitSampleInfo("spinnerbonus");
1979
2288
 
1980
2289
  /**
1981
2290
  * Contains information about hit objects of a beatmap.
@@ -7102,130 +7411,6 @@ ErrorFunction.ervInvImpGd = [
7102
7411
  0.3999688121938621e-6, 0.1618092908879045e-8, 0.2315586083102596e-11,
7103
7412
  ];
7104
7413
 
7105
- class HitWindow {
7106
- /**
7107
- * @param overallDifficulty The overall difficulty of this hit window.
7108
- */
7109
- constructor(overallDifficulty) {
7110
- this.overallDifficulty = overallDifficulty;
7111
- }
7112
- }
7113
- /**
7114
- * Represents the hit window of osu!droid.
7115
- */
7116
- class DroidHitWindow extends HitWindow {
7117
- /**
7118
- * Calculates the overall difficulty value of a great hit window.
7119
- *
7120
- * @param value The value of the hit window, in milliseconds.
7121
- * @param isPrecise Whether to calculate for Precise mod.
7122
- * @returns The overall difficulty value.
7123
- */
7124
- static hitWindow300ToOD(value, isPrecise) {
7125
- if (isPrecise) {
7126
- return 5 - (value - 55) / 6;
7127
- }
7128
- else {
7129
- return 5 - (value - 75) / 5;
7130
- }
7131
- }
7132
- /**
7133
- * Calculates the overall difficulty value of a good hit window.
7134
- *
7135
- * @param value The value of the hit window, in milliseconds.
7136
- * @param isPrecise Whether to calculate for Precise mod.
7137
- * @returns The overall difficulty value.
7138
- */
7139
- static hitWindow100ToOD(value, isPrecise) {
7140
- if (isPrecise) {
7141
- return 5 - (value - 120) / 8;
7142
- }
7143
- else {
7144
- return 5 - (value - 150) / 10;
7145
- }
7146
- }
7147
- /**
7148
- * Calculates the overall difficulty value of a meh hit window.
7149
- *
7150
- * @param value The value of the hit window, in milliseconds.
7151
- * @param isPrecise Whether to calculate for Precise mod.
7152
- * @returns The overall difficulty value.
7153
- */
7154
- static hitWindow50ToOD(value, isPrecise) {
7155
- if (isPrecise) {
7156
- return 5 - (value - 180) / 10;
7157
- }
7158
- else {
7159
- return 5 - (value - 250) / 10;
7160
- }
7161
- }
7162
- hitWindowFor300(isPrecise) {
7163
- if (isPrecise) {
7164
- return 55 + 6 * (5 - this.overallDifficulty);
7165
- }
7166
- else {
7167
- return 75 + 5 * (5 - this.overallDifficulty);
7168
- }
7169
- }
7170
- hitWindowFor100(isPrecise) {
7171
- if (isPrecise) {
7172
- return 120 + 8 * (5 - this.overallDifficulty);
7173
- }
7174
- else {
7175
- return 150 + 10 * (5 - this.overallDifficulty);
7176
- }
7177
- }
7178
- hitWindowFor50(isPrecise) {
7179
- if (isPrecise) {
7180
- return 180 + 10 * (5 - this.overallDifficulty);
7181
- }
7182
- else {
7183
- return 250 + 10 * (5 - this.overallDifficulty);
7184
- }
7185
- }
7186
- }
7187
- /**
7188
- * Represents the hit window of osu!standard.
7189
- */
7190
- class OsuHitWindow extends HitWindow {
7191
- /**
7192
- * Calculates the overall difficulty value of a great hit window.
7193
- *
7194
- * @param value The value of the hit window, in milliseconds.
7195
- * @returns The overall difficulty value.
7196
- */
7197
- static hitWindow300ToOD(value) {
7198
- return (80 - value) / 6;
7199
- }
7200
- /**
7201
- * Calculates the overall difficulty value of a good hit window.
7202
- *
7203
- * @param value The value of the hit window, in milliseconds.
7204
- * @returns The overall difficulty value.
7205
- */
7206
- static hitWindow100ToOD(value) {
7207
- return (140 - value) / 8;
7208
- }
7209
- /**
7210
- * Calculates the overall difficulty value of a meh hit window.
7211
- *
7212
- * @param value The value of the hit window, in milliseconds.
7213
- * @returns The overall difficulty value.
7214
- */
7215
- static hitWindow50ToOD(value) {
7216
- return (200 - value) / 10;
7217
- }
7218
- hitWindowFor300() {
7219
- return 80 - 6 * this.overallDifficulty;
7220
- }
7221
- hitWindowFor100() {
7222
- return 140 - 8 * this.overallDifficulty;
7223
- }
7224
- hitWindowFor50() {
7225
- return 200 - 10 * this.overallDifficulty;
7226
- }
7227
- }
7228
-
7229
7414
  /**
7230
7415
  * Holds interpolation methods for numbers and vectors.
7231
7416
  */
@@ -7994,6 +8179,48 @@ class ModPerfect extends Mod {
7994
8179
  }
7995
8180
  }
7996
8181
 
8182
+ /**
8183
+ * Represents the hit window of osu!droid _with_ the Precise mod.
8184
+ */
8185
+ class PreciseDroidHitWindow extends HitWindow {
8186
+ /**
8187
+ * Calculates the overall difficulty value of a great (300) hit window.
8188
+ *
8189
+ * @param value The value of the hit window, in milliseconds.
8190
+ * @returns The overall difficulty value.
8191
+ */
8192
+ static greatWindowToOD(value) {
8193
+ return 5 - (value - 55) / 6;
8194
+ }
8195
+ /**
8196
+ * Calculates the overall difficulty value of a good (100) hit window.
8197
+ *
8198
+ * @param value The value of the hit window, in milliseconds.
8199
+ * @returns The overall difficulty value.
8200
+ */
8201
+ static okWindowToOD(value) {
8202
+ return 5 - (value - 120) / 8;
8203
+ }
8204
+ /**
8205
+ * Calculates the overall difficulty value of a meh (50) hit window.
8206
+ *
8207
+ * @param value The value of the hit window, in milliseconds.
8208
+ * @returns The overall difficulty value.
8209
+ */
8210
+ static mehWindowToOD(value) {
8211
+ return 5 - (value - 180) / 10;
8212
+ }
8213
+ get greatWindow() {
8214
+ return 55 + 6 * (5 - this.overallDifficulty);
8215
+ }
8216
+ get okWindow() {
8217
+ return 120 + 8 * (5 - this.overallDifficulty);
8218
+ }
8219
+ get mehWindow() {
8220
+ return 180 + 10 * (5 - this.overallDifficulty);
8221
+ }
8222
+ }
8223
+
7997
8224
  /**
7998
8225
  * Represents the Precise mod.
7999
8226
  */
@@ -8007,6 +8234,16 @@ class ModPrecise extends Mod {
8007
8234
  this.droidString = "s";
8008
8235
  this.isDroidLegacyMod = false;
8009
8236
  }
8237
+ applyToHitObject(_, hitObject) {
8238
+ var _a, _b;
8239
+ if (hitObject instanceof Slider) {
8240
+ // For sliders, the hit window is enforced in the head - everything else is an instant hit or miss.
8241
+ hitObject.head.hitWindow = new PreciseDroidHitWindow((_a = hitObject.head.hitWindow) === null || _a === void 0 ? void 0 : _a.overallDifficulty);
8242
+ }
8243
+ else {
8244
+ hitObject.hitWindow = new PreciseDroidHitWindow((_b = hitObject.hitWindow) === null || _b === void 0 ? void 0 : _b.overallDifficulty);
8245
+ }
8246
+ }
8010
8247
  }
8011
8248
 
8012
8249
  /**
@@ -8320,16 +8557,19 @@ class ModUtil {
8320
8557
  difficulty.ar = BeatmapDifficulty.inverseDifficultyRange(preempt, HitObject.preemptMax, HitObject.preemptMid, HitObject.preemptMin);
8321
8558
  switch (mode) {
8322
8559
  case exports.Modes.droid: {
8323
- const isPrecise = mods.some((m) => m instanceof ModPrecise);
8324
- const hitWindow = new DroidHitWindow(difficulty.od);
8325
- const greatWindow = hitWindow.hitWindowFor300(isPrecise) / rate;
8326
- difficulty.od = DroidHitWindow.hitWindow300ToOD(greatWindow, isPrecise);
8560
+ if (mods.some((m) => m instanceof ModPrecise)) {
8561
+ const hitWindow = new PreciseDroidHitWindow(difficulty.od);
8562
+ difficulty.od = PreciseDroidHitWindow.greatWindowToOD(hitWindow.greatWindow / rate);
8563
+ }
8564
+ else {
8565
+ const hitWindow = new DroidHitWindow(difficulty.od);
8566
+ difficulty.od = DroidHitWindow.greatWindowToOD(hitWindow.greatWindow / rate);
8567
+ }
8327
8568
  break;
8328
8569
  }
8329
8570
  case exports.Modes.osu: {
8330
8571
  const hitWindow = new OsuHitWindow(difficulty.od);
8331
- const greatWindow = hitWindow.hitWindowFor300() / rate;
8332
- difficulty.od = OsuHitWindow.hitWindow300ToOD(greatWindow);
8572
+ difficulty.od = OsuHitWindow.greatWindowToOD(hitWindow.greatWindow / rate);
8333
8573
  break;
8334
8574
  }
8335
8575
  }
@@ -8533,10 +8773,12 @@ exports.DroidAPIRequestBuilder = DroidAPIRequestBuilder;
8533
8773
  exports.DroidHitWindow = DroidHitWindow;
8534
8774
  exports.EffectControlPoint = EffectControlPoint;
8535
8775
  exports.EffectControlPointManager = EffectControlPointManager;
8776
+ exports.EmptyHitWindow = EmptyHitWindow;
8536
8777
  exports.ErrorFunction = ErrorFunction;
8537
8778
  exports.FileHitSampleInfo = FileHitSampleInfo;
8538
8779
  exports.HitObject = HitObject;
8539
8780
  exports.HitSampleInfo = HitSampleInfo;
8781
+ exports.HitWindow = HitWindow;
8540
8782
  exports.Interpolation = Interpolation;
8541
8783
  exports.MapInfo = MapInfo;
8542
8784
  exports.MathUtils = MathUtils;
@@ -8569,11 +8811,13 @@ exports.OsuHitWindow = OsuHitWindow;
8569
8811
  exports.PathApproximator = PathApproximator;
8570
8812
  exports.Playfield = Playfield;
8571
8813
  exports.Polynomial = Polynomial;
8814
+ exports.PreciseDroidHitWindow = PreciseDroidHitWindow;
8572
8815
  exports.Precision = Precision;
8573
8816
  exports.RGBColor = RGBColor;
8574
8817
  exports.SampleBankInfo = SampleBankInfo;
8575
8818
  exports.SampleControlPoint = SampleControlPoint;
8576
8819
  exports.SampleControlPointManager = SampleControlPointManager;
8820
+ exports.SequenceHitSampleInfo = SequenceHitSampleInfo;
8577
8821
  exports.Slider = Slider;
8578
8822
  exports.SliderHead = SliderHead;
8579
8823
  exports.SliderNestedHitObject = SliderNestedHitObject;
@@ -8590,6 +8834,7 @@ exports.StoryboardEncoder = StoryboardEncoder;
8590
8834
  exports.StoryboardLayer = StoryboardLayer;
8591
8835
  exports.StoryboardSample = StoryboardSample;
8592
8836
  exports.StoryboardSprite = StoryboardSprite;
8837
+ exports.TimedHitSampleInfo = TimedHitSampleInfo;
8593
8838
  exports.TimingControlPoint = TimingControlPoint;
8594
8839
  exports.TimingControlPointManager = TimingControlPointManager;
8595
8840
  exports.Utils = Utils;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rian8337/osu-base",
3
- "version": "4.0.0-beta.31",
3
+ "version": "4.0.0-beta.39",
4
4
  "description": "Base module for all osu! related modules.",
5
5
  "keywords": [
6
6
  "osu",
@@ -42,5 +42,5 @@
42
42
  "publishConfig": {
43
43
  "access": "public"
44
44
  },
45
- "gitHead": "39d3f8d4e23493a0ee772603b022a826951a2f42"
45
+ "gitHead": "6039ff691d968464d4b111e0b5a88775fb055281"
46
46
  }
@@ -104,6 +104,10 @@ declare abstract class HitSampleInfo {
104
104
  * If this is 0, the control point's volume should be used instead.
105
105
  */
106
106
  readonly volume: number;
107
+ /**
108
+ * All possible filenames that can be used as an audio source, returned in order of preference (highest first).
109
+ */
110
+ abstract get lookupNames(): string[];
107
111
  constructor(volume?: number);
108
112
  }
109
113
 
@@ -136,7 +140,8 @@ declare class BankHitSampleInfo extends HitSampleInfo {
136
140
  * but can be disabled using the layered skin config option.
137
141
  */
138
142
  readonly isLayered: boolean;
139
- constructor(name: string, bank: SampleBank, customSampleBank?: number, volume?: number, isLayered?: boolean);
143
+ get lookupNames(): string[];
144
+ constructor(name: string, bank?: SampleBank, customSampleBank?: number, volume?: number, isLayered?: boolean);
140
145
  }
141
146
 
142
147
  /**
@@ -384,6 +389,36 @@ declare class Vector2 {
384
389
  toString(): string;
385
390
  }
386
391
 
392
+ /**
393
+ * Represents a hit window.
394
+ */
395
+ declare abstract class HitWindow {
396
+ /**
397
+ * A fixed miss hit window regardless of difficulty settings.
398
+ */
399
+ static readonly missWindow = 400;
400
+ /**
401
+ * The overall difficulty of this `HitWindow`.
402
+ */
403
+ overallDifficulty: number;
404
+ /**
405
+ * @param overallDifficulty The overall difficulty of this `HitWindow`. Defaults to 5.
406
+ */
407
+ constructor(overallDifficulty?: number);
408
+ /**
409
+ * The great (300) window of this `HitWindow`.
410
+ */
411
+ abstract get greatWindow(): number;
412
+ /**
413
+ * The ok (100) window of this `HitWindow`.
414
+ */
415
+ abstract get okWindow(): number;
416
+ /**
417
+ * The meh (50) window of this `HitWindow`.
418
+ */
419
+ abstract get mehWindow(): number;
420
+ }
421
+
387
422
  /**
388
423
  * Represents a control point in a beatmap.
389
424
  */
@@ -461,6 +496,15 @@ declare abstract class ControlPointManager<T extends ControlPoint> {
461
496
  * Clears all control points of this type.
462
497
  */
463
498
  clear(): void;
499
+ /**
500
+ * Gets all control points between two times.
501
+ *
502
+ * @param start The start time, in milliseconds.
503
+ * @param end The end time, in milliseconds.
504
+ * @return An array of control points between the two times. If `start` is greater than `end`, the control point at
505
+ * `start` will be returned.
506
+ */
507
+ between(startTime: number, endTime: number): T[];
464
508
  /**
465
509
  * Binary searches one of the control point lists to find the active control point at the given time.
466
510
  *
@@ -645,6 +689,44 @@ declare class BeatmapControlPoints {
645
689
  clear(): void;
646
690
  }
647
691
 
692
+ /**
693
+ * A `HitSampleInfo` that has a time associated with it.
694
+ */
695
+ declare class TimedHitSampleInfo<TSampleInfo extends HitSampleInfo = HitSampleInfo> {
696
+ /**
697
+ * The time at which the `HitSampleInfo` should be played.
698
+ */
699
+ readonly time: number;
700
+ /**
701
+ * The `HitSampleInfo` to play.
702
+ */
703
+ readonly sample: TSampleInfo;
704
+ constructor(time: number, sample: TSampleInfo);
705
+ }
706
+
707
+ /**
708
+ * Represents a gameplay hit sample that is meant to be played sequentially at specific times.
709
+ */
710
+ declare class SequenceHitSampleInfo {
711
+ /**
712
+ * The `TimedHitSampleInfo`s to play.
713
+ */
714
+ readonly samples: readonly TimedHitSampleInfo[];
715
+ /**
716
+ * Whether this `SequenceHitSampleInfo` contains no `TimedHitSampleInfo`s.
717
+ */
718
+ get isEmpty(): boolean;
719
+ constructor(samples: readonly TimedHitSampleInfo[]);
720
+ /**
721
+ * Obtains the `TimedHitSampleInfo` to play at a given time.
722
+ *
723
+ * @param time The time, in milliseconds.
724
+ * @return The `TimedHitSampleInfo` to play at the given time,
725
+ * or `null` if no `TimedHitSampleInfo`s should be played.
726
+ */
727
+ sampleAt(time: number): TimedHitSampleInfo | null;
728
+ }
729
+
648
730
  /**
649
731
  * Represents a hitobject in a beatmap.
650
732
  */
@@ -677,17 +759,11 @@ declare abstract class HitObject {
677
759
  * The bitwise type of the hitobject (circle/slider/spinner).
678
760
  */
679
761
  readonly type: ObjectTypes;
680
- /**
681
- * The position of the hitobject in osu!pixels.
682
- */
683
762
  protected _position: Vector2;
684
763
  /**
685
764
  * The position of the hitobject in osu!pixels.
686
765
  */
687
766
  get position(): Vector2;
688
- /**
689
- * The position of the hitobject in osu!pixels.
690
- */
691
767
  set position(value: Vector2);
692
768
  /**
693
769
  * The end position of the hitobject in osu!pixels.
@@ -719,30 +795,27 @@ declare abstract class HitObject {
719
795
  /**
720
796
  * Any samples which may be used by this hitobject that are non-standard.
721
797
  */
722
- auxiliarySamples: HitSampleInfo[];
798
+ auxiliarySamples: SequenceHitSampleInfo[];
799
+ private _kiai;
723
800
  /**
724
- * The stack height of this hitobject.
801
+ * Whether this hitobject is in kiai time.
725
802
  */
726
- protected _stackHeight: number;
803
+ get kiai(): boolean;
727
804
  /**
728
- * The stack height of this hitobject.
805
+ * The hit window of this hitobject.
729
806
  */
730
- get stackHeight(): number;
807
+ hitWindow: HitWindow | null;
808
+ protected _stackHeight: number;
731
809
  /**
732
810
  * The stack height of this hitobject.
733
811
  */
812
+ get stackHeight(): number;
734
813
  set stackHeight(value: number);
735
- /**
736
- * The osu!standard scale of this hitobject.
737
- */
738
814
  protected _scale: number;
739
815
  /**
740
816
  * The osu!standard scale of this hitobject.
741
817
  */
742
818
  get scale(): number;
743
- /**
744
- * The osu!standard scale of this hitobject.
745
- */
746
819
  set scale(value: number);
747
820
  /**
748
821
  * The time at which the approach circle of this hitobject should appear before this hitobject starts.
@@ -815,6 +888,17 @@ declare abstract class HitObject {
815
888
  * @returns The created hit sample info.
816
889
  */
817
890
  protected createHitSampleInfo(sampleName: string): BankHitSampleInfo;
891
+ /**
892
+ * Creates the hit window of this hitobject.
893
+ *
894
+ * A `null` return means that this hitobject has no hit window and timing errors should not be displayed to the user.
895
+ *
896
+ * This will only be called if this hitobject's hit window has not been set externally.
897
+ *
898
+ * @param mode The gamemode to create the hit window for.
899
+ * @returns The created hit window.
900
+ */
901
+ protected createHitWindow(mode: Modes): HitWindow | null;
818
902
  /**
819
903
  * Returns the string representative of the class.
820
904
  */
@@ -1556,12 +1640,16 @@ declare class SliderHead extends SliderNestedHitObject {
1556
1640
  * Represents the tail of a slider.
1557
1641
  */
1558
1642
  declare class SliderTail extends SliderNestedHitObject {
1643
+ protected createHitWindow(): HitWindow | null;
1559
1644
  }
1560
1645
 
1561
1646
  /**
1562
1647
  * Represents a slider in a beatmap.
1563
1648
  */
1564
1649
  declare class Slider extends HitObject {
1650
+ private static readonly baseNormalSlideSample;
1651
+ private static readonly baseWhistleSlideSample;
1652
+ private static readonly baseTickSample;
1565
1653
  get position(): Vector2;
1566
1654
  set position(value: Vector2);
1567
1655
  get endTime(): number;
@@ -1721,8 +1809,10 @@ declare class Slider extends HitObject {
1721
1809
  * @returns `[0, spanCount)` where 0 is the first run.
1722
1810
  */
1723
1811
  spanAt(progress: number): number;
1812
+ protected createHitWindow(): HitWindow | null;
1724
1813
  private createNestedHitObjects;
1725
1814
  private updateNestedPositions;
1815
+ private createSlidingSamples;
1726
1816
  private updateNestedSamples;
1727
1817
  toString(): string;
1728
1818
  }
@@ -1734,6 +1824,8 @@ declare class Slider extends HitObject {
1734
1824
  * position of a spinner is always at 256x192.
1735
1825
  */
1736
1826
  declare class Spinner extends HitObject {
1827
+ private static readonly baseSpinnerSpinSample;
1828
+ private static readonly baseSpinnerBonusSample;
1737
1829
  private _endTime;
1738
1830
  get endTime(): number;
1739
1831
  constructor(values: {
@@ -1744,6 +1836,7 @@ declare class Spinner extends HitObject {
1744
1836
  applySamples(controlPoints: BeatmapControlPoints): void;
1745
1837
  getStackedPosition(): Vector2;
1746
1838
  getStackedEndPosition(): Vector2;
1839
+ protected createHitWindow(): HitWindow | null;
1747
1840
  toString(): string;
1748
1841
  }
1749
1842
 
@@ -2946,6 +3039,48 @@ declare class DroidAPIRequestBuilder extends APIRequestBuilder<DroidAPIEndpoint>
2946
3039
  buildURL(): string;
2947
3040
  }
2948
3041
 
3042
+ /**
3043
+ * Represents the hit window of osu!droid _without_ the Precise mod.
3044
+ */
3045
+ declare class DroidHitWindow extends HitWindow {
3046
+ /**
3047
+ * Calculates the overall difficulty value of a great (300) hit window.
3048
+ *
3049
+ * @param value The value of the hit window, in milliseconds.
3050
+ * @returns The overall difficulty value.
3051
+ */
3052
+ static greatWindowToOD(value: number): number;
3053
+ /**
3054
+ * Calculates the overall difficulty value of a good (100) hit window.
3055
+ *
3056
+ * @param value The value of the hit window, in milliseconds.
3057
+ * @returns The overall difficulty value.
3058
+ */
3059
+ static okWindowToOD(value: number): number;
3060
+ /**
3061
+ * Calculates the overall difficulty value of a meh (50) hit window.
3062
+ *
3063
+ * @param value The value of the hit window, in milliseconds.
3064
+ * @returns The overall difficulty value.
3065
+ */
3066
+ static mehWindowToOD(value: number): number;
3067
+ get greatWindow(): number;
3068
+ get okWindow(): number;
3069
+ get mehWindow(): number;
3070
+ }
3071
+
3072
+ /**
3073
+ * An empty `HitWindow` that does not have any hit windows.
3074
+ *
3075
+ * No time values are provided (meaning instantaneous hit or miss).
3076
+ */
3077
+ declare class EmptyHitWindow extends HitWindow {
3078
+ constructor();
3079
+ get greatWindow(): number;
3080
+ get okWindow(): number;
3081
+ get mehWindow(): number;
3082
+ }
3083
+
2949
3084
  /**
2950
3085
  * A Math utility class containing all methods related to the error function.
2951
3086
  *
@@ -3228,102 +3363,10 @@ declare class FileHitSampleInfo extends HitSampleInfo {
3228
3363
  * The name of the file to load the sample from.
3229
3364
  */
3230
3365
  readonly filename: string;
3366
+ get lookupNames(): string[];
3231
3367
  constructor(filename: string, volume?: number);
3232
3368
  }
3233
3369
 
3234
- declare abstract class HitWindow {
3235
- /**
3236
- * The overall difficulty of this hit window.
3237
- */
3238
- readonly overallDifficulty: number;
3239
- /**
3240
- * @param overallDifficulty The overall difficulty of this hit window.
3241
- */
3242
- constructor(overallDifficulty: number);
3243
- /**
3244
- * Gets the hit window for 300 (great) hit result.
3245
- *
3246
- * @param isPrecise Whether to calculate for Precise mod.
3247
- * @returns The hit window in milliseconds.
3248
- */
3249
- abstract hitWindowFor300(isPrecise?: boolean): number;
3250
- /**
3251
- * Gets the hit window for 100 (good) hit result.
3252
- *
3253
- * @param isPrecise Whether to calculate for Precise mod.
3254
- * @returns The hit window in milliseconds.
3255
- */
3256
- abstract hitWindowFor100(isPrecise?: boolean): number;
3257
- /**
3258
- * Gets the hit window for 50 (meh) hit result.
3259
- *
3260
- * @param isPrecise Whether to calculate for Precise mod.
3261
- * @returns The hit window in milliseconds.
3262
- */
3263
- abstract hitWindowFor50(isPrecise?: boolean): number;
3264
- }
3265
- /**
3266
- * Represents the hit window of osu!droid.
3267
- */
3268
- declare class DroidHitWindow extends HitWindow {
3269
- /**
3270
- * Calculates the overall difficulty value of a great hit window.
3271
- *
3272
- * @param value The value of the hit window, in milliseconds.
3273
- * @param isPrecise Whether to calculate for Precise mod.
3274
- * @returns The overall difficulty value.
3275
- */
3276
- static hitWindow300ToOD(value: number, isPrecise?: boolean): number;
3277
- /**
3278
- * Calculates the overall difficulty value of a good hit window.
3279
- *
3280
- * @param value The value of the hit window, in milliseconds.
3281
- * @param isPrecise Whether to calculate for Precise mod.
3282
- * @returns The overall difficulty value.
3283
- */
3284
- static hitWindow100ToOD(value: number, isPrecise?: boolean): number;
3285
- /**
3286
- * Calculates the overall difficulty value of a meh hit window.
3287
- *
3288
- * @param value The value of the hit window, in milliseconds.
3289
- * @param isPrecise Whether to calculate for Precise mod.
3290
- * @returns The overall difficulty value.
3291
- */
3292
- static hitWindow50ToOD(value: number, isPrecise?: boolean): number;
3293
- hitWindowFor300(isPrecise?: boolean): number;
3294
- hitWindowFor100(isPrecise?: boolean): number;
3295
- hitWindowFor50(isPrecise?: boolean): number;
3296
- }
3297
- /**
3298
- * Represents the hit window of osu!standard.
3299
- */
3300
- declare class OsuHitWindow extends HitWindow {
3301
- /**
3302
- * Calculates the overall difficulty value of a great hit window.
3303
- *
3304
- * @param value The value of the hit window, in milliseconds.
3305
- * @returns The overall difficulty value.
3306
- */
3307
- static hitWindow300ToOD(value: number): number;
3308
- /**
3309
- * Calculates the overall difficulty value of a good hit window.
3310
- *
3311
- * @param value The value of the hit window, in milliseconds.
3312
- * @returns The overall difficulty value.
3313
- */
3314
- static hitWindow100ToOD(value: number): number;
3315
- /**
3316
- * Calculates the overall difficulty value of a meh hit window.
3317
- *
3318
- * @param value The value of the hit window, in milliseconds.
3319
- * @returns The overall difficulty value.
3320
- */
3321
- static hitWindow50ToOD(value: number): number;
3322
- hitWindowFor300(): number;
3323
- hitWindowFor100(): number;
3324
- hitWindowFor50(): number;
3325
- }
3326
-
3327
3370
  /**
3328
3371
  * Represents available hitsound types.
3329
3372
  */
@@ -3930,13 +3973,14 @@ declare class ModPerfect extends Mod implements IModApplicableToDroid, IModAppli
3930
3973
  /**
3931
3974
  * Represents the Precise mod.
3932
3975
  */
3933
- declare class ModPrecise extends Mod implements IModApplicableToDroid {
3976
+ declare class ModPrecise extends Mod implements IModApplicableToDroid, IModApplicableToHitObject {
3934
3977
  readonly acronym = "PR";
3935
3978
  readonly name = "Precise";
3936
3979
  readonly droidRanked = true;
3937
3980
  readonly droidScoreMultiplier = 1.06;
3938
3981
  readonly droidString = "s";
3939
3982
  readonly isDroidLegacyMod = false;
3983
+ applyToHitObject(_: Modes, hitObject: HitObject): void;
3940
3984
  }
3941
3985
 
3942
3986
  /**
@@ -4206,6 +4250,36 @@ declare class OsuAPIRequestBuilder extends APIRequestBuilder<OsuAPIEndpoint> {
4206
4250
  protected readonly APIkeyParam: string;
4207
4251
  }
4208
4252
 
4253
+ /**
4254
+ * Represents the hit window of osu!standard.
4255
+ */
4256
+ declare class OsuHitWindow extends HitWindow {
4257
+ /**
4258
+ * Calculates the overall difficulty value of a great (300) hit window.
4259
+ *
4260
+ * @param value The value of the hit window, in milliseconds.
4261
+ * @returns The overall difficulty value.
4262
+ */
4263
+ static greatWindowToOD(value: number): number;
4264
+ /**
4265
+ * Calculates the overall difficulty value of a good (100) hit window.
4266
+ *
4267
+ * @param value The value of the hit window, in milliseconds.
4268
+ * @returns The overall difficulty value.
4269
+ */
4270
+ static okWindowToOD(value: number): number;
4271
+ /**
4272
+ * Calculates the overall difficulty value of a meh hit window.
4273
+ *
4274
+ * @param value The value of the hit window, in milliseconds.
4275
+ * @returns The overall difficulty value.
4276
+ */
4277
+ static mehWindowToOD(value: number): number;
4278
+ get greatWindow(): number;
4279
+ get okWindow(): number;
4280
+ get mehWindow(): number;
4281
+ }
4282
+
4209
4283
  /**
4210
4284
  * Path approximator for sliders.
4211
4285
  */
@@ -4328,6 +4402,36 @@ declare abstract class Polynomial {
4328
4402
  static evaluate(z: number, coefficients: readonly number[]): number;
4329
4403
  }
4330
4404
 
4405
+ /**
4406
+ * Represents the hit window of osu!droid _with_ the Precise mod.
4407
+ */
4408
+ declare class PreciseDroidHitWindow extends HitWindow {
4409
+ /**
4410
+ * Calculates the overall difficulty value of a great (300) hit window.
4411
+ *
4412
+ * @param value The value of the hit window, in milliseconds.
4413
+ * @returns The overall difficulty value.
4414
+ */
4415
+ static greatWindowToOD(value: number): number;
4416
+ /**
4417
+ * Calculates the overall difficulty value of a good (100) hit window.
4418
+ *
4419
+ * @param value The value of the hit window, in milliseconds.
4420
+ * @returns The overall difficulty value.
4421
+ */
4422
+ static okWindowToOD(value: number): number;
4423
+ /**
4424
+ * Calculates the overall difficulty value of a meh (50) hit window.
4425
+ *
4426
+ * @param value The value of the hit window, in milliseconds.
4427
+ * @returns The overall difficulty value.
4428
+ */
4429
+ static mehWindowToOD(value: number): number;
4430
+ get greatWindow(): number;
4431
+ get okWindow(): number;
4432
+ get mehWindow(): number;
4433
+ }
4434
+
4331
4435
  /**
4332
4436
  * Precision utilities.
4333
4437
  */
@@ -4401,16 +4505,23 @@ declare class SampleBankInfo {
4401
4505
  constructor(bankInfo?: SampleBankInfo);
4402
4506
  }
4403
4507
 
4508
+ /**
4509
+ * Possible ranks that a score can have.
4510
+ */
4511
+ type ScoreRank = "XH" | "SH" | "X" | "S" | "A" | "B" | "C" | "D";
4512
+
4404
4513
  /**
4405
4514
  * Represents a slider repeat.
4406
4515
  */
4407
4516
  declare class SliderRepeat extends SliderNestedHitObject {
4517
+ protected createHitWindow(): HitWindow | null;
4408
4518
  }
4409
4519
 
4410
4520
  /**
4411
4521
  * Represents a slider tick in a slider.
4412
4522
  */
4413
4523
  declare class SliderTick extends SliderNestedHitObject {
4524
+ protected createHitWindow(): HitWindow | null;
4414
4525
  }
4415
4526
 
4416
4527
  /**
@@ -4596,4 +4707,4 @@ declare abstract class ZeroCrossingBracketing {
4596
4707
  static expandReduce(f: (x: number) => number, bounds: RootBounds, expansionFactor?: number, expansionMaxIterations?: number, reduceSubdivisions?: number): boolean;
4597
4708
  }
4598
4709
 
4599
- export { Accuracy, Anchor, AnimationLoopType, BankHitSampleInfo, Beatmap, BeatmapBackground, BeatmapColor, BeatmapControlPoints, BeatmapConverter, BeatmapCountdown, BeatmapDecoder, BeatmapDifficulty, BeatmapEditor, BeatmapEncoder, BeatmapEvents, BeatmapGeneral, BeatmapGenre, BeatmapHitObjects, BeatmapLanguage, BeatmapMetadata, BeatmapOverlayPosition, BeatmapProcessor, BeatmapVideo, BlendingEquation, BlendingParameters, BlendingType, BreakPoint, Brent, Circle, CircleSizeCalculator, Command, CommandLoop, CommandTimeline, CommandTimelineGroup, type CommandTimelineSelector, CommandTrigger, ControlPointManager, DifficultyControlPoint, DifficultyControlPointManager, type DroidAPIEndpoint, DroidAPIRequestBuilder, DroidHitWindow, Easing, EditorGridSize, EffectControlPoint, EffectControlPointManager, ErrorFunction, FileHitSampleInfo, GameMode, HitObject, HitSampleInfo, HitSoundType, type ICommandTimeline, type IModApplicableToBeatmap, type IModApplicableToDifficulty, type IModApplicableToDifficultyWithSettings, type IModApplicableToDroid, type IModApplicableToHitObject, type IModApplicableToOsu, type If, Interpolation, MapInfo, MathUtils, Mod, ModAuto, ModAutopilot, ModDifficultyAdjust, ModDoubleTime, ModEasy, ModFlashlight, ModHalfTime, ModHardRock, ModHidden, ModNightCore, ModNoFail, type ModParseOptions, ModPerfect, ModPrecise, ModReallyEasy, ModRelax, ModScoreV2, ModSmallCircle, ModSpeedUp, ModSpunOut, ModSuddenDeath, ModTouchDevice, ModUtil, Modes, NormalDistribution, ObjectTypes, type OmitType, type OsuAPIEndpoint, OsuAPIRequestBuilder, type OsuAPIResponse, OsuHitWindow, PathApproximator, PathType, type PlaceableHitObject, type PlayableBeatmapOptions, Playfield, Polynomial, Precision, RGBColor, RankedStatus, type RequestResponse, type RootBounds, SampleBank, SampleBankInfo, SampleControlPoint, SampleControlPointManager, Slider, SliderHead, SliderNestedHitObject, SliderPath, SliderRepeat, SliderTail, SliderTick, Spinner, Storyboard, StoryboardAnimation, StoryboardCommandType, StoryboardDecoder, StoryboardElement, StoryboardEncoder, StoryboardEventType, StoryboardLayer, StoryboardLayerType, StoryboardParameterCommandType, StoryboardSample, StoryboardSprite, TimingControlPoint, TimingControlPointManager, Utils, Vector2, ZeroCrossingBracketing };
4710
+ export { Accuracy, Anchor, AnimationLoopType, BankHitSampleInfo, Beatmap, BeatmapBackground, BeatmapColor, BeatmapControlPoints, BeatmapConverter, BeatmapCountdown, BeatmapDecoder, BeatmapDifficulty, BeatmapEditor, BeatmapEncoder, BeatmapEvents, BeatmapGeneral, BeatmapGenre, BeatmapHitObjects, BeatmapLanguage, BeatmapMetadata, BeatmapOverlayPosition, BeatmapProcessor, BeatmapVideo, BlendingEquation, BlendingParameters, BlendingType, BreakPoint, Brent, Circle, CircleSizeCalculator, Command, CommandLoop, CommandTimeline, CommandTimelineGroup, type CommandTimelineSelector, CommandTrigger, ControlPointManager, DifficultyControlPoint, DifficultyControlPointManager, type DroidAPIEndpoint, DroidAPIRequestBuilder, DroidHitWindow, Easing, EditorGridSize, EffectControlPoint, EffectControlPointManager, EmptyHitWindow, ErrorFunction, FileHitSampleInfo, GameMode, HitObject, HitSampleInfo, HitSoundType, HitWindow, type ICommandTimeline, type IModApplicableToBeatmap, type IModApplicableToDifficulty, type IModApplicableToDifficultyWithSettings, type IModApplicableToDroid, type IModApplicableToHitObject, type IModApplicableToOsu, type If, Interpolation, MapInfo, MathUtils, Mod, ModAuto, ModAutopilot, ModDifficultyAdjust, ModDoubleTime, ModEasy, ModFlashlight, ModHalfTime, ModHardRock, ModHidden, ModNightCore, ModNoFail, type ModParseOptions, ModPerfect, ModPrecise, ModReallyEasy, ModRelax, ModScoreV2, ModSmallCircle, ModSpeedUp, ModSpunOut, ModSuddenDeath, ModTouchDevice, ModUtil, Modes, NormalDistribution, ObjectTypes, type OmitType, type OsuAPIEndpoint, OsuAPIRequestBuilder, type OsuAPIResponse, OsuHitWindow, PathApproximator, PathType, type PlaceableHitObject, type PlayableBeatmapOptions, Playfield, Polynomial, PreciseDroidHitWindow, Precision, RGBColor, RankedStatus, type RequestResponse, type RootBounds, SampleBank, SampleBankInfo, SampleControlPoint, SampleControlPointManager, type ScoreRank, SequenceHitSampleInfo, Slider, SliderHead, SliderNestedHitObject, SliderPath, SliderRepeat, SliderTail, SliderTick, Spinner, Storyboard, StoryboardAnimation, StoryboardCommandType, StoryboardDecoder, StoryboardElement, StoryboardEncoder, StoryboardEventType, StoryboardLayer, StoryboardLayerType, StoryboardParameterCommandType, StoryboardSample, StoryboardSprite, TimedHitSampleInfo, TimingControlPoint, TimingControlPointManager, Utils, Vector2, ZeroCrossingBracketing };