@rian8337/osu-base 4.0.0-beta.25 → 4.0.0-beta.27

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 +446 -506
  2. package/package.json +2 -2
  3. package/typings/index.d.ts +162 -174
package/dist/index.js CHANGED
@@ -390,6 +390,42 @@ class BeatmapDifficulty {
390
390
  set ar(value) {
391
391
  this._ar = value;
392
392
  }
393
+ /**
394
+ * Maps a difficulty value [0, 10] to a two-piece linear range of values.
395
+ *
396
+ * @param difficulty The difficulty value to be mapped.
397
+ * @param min Minimum of the resulting range which will be achieved by a difficulty value of 0.
398
+ * @param mid Midpoint of the resulting range which will be achieved by a difficulty value of 5.
399
+ * @param max Maximum of the resulting range which will be achieved by a difficulty value of 10.
400
+ */
401
+ static difficultyRange(difficulty, min, mid, max) {
402
+ switch (true) {
403
+ case difficulty > 5:
404
+ return mid + ((max - mid) * (difficulty - 5)) / 5;
405
+ case difficulty < 5:
406
+ return mid + ((mid - min) * (difficulty - 5)) / 5;
407
+ default:
408
+ return mid;
409
+ }
410
+ }
411
+ /**
412
+ * Inverse function to `difficultyRange`. Maps a value returned by the function back to the
413
+ * difficulty that produced it.
414
+ *
415
+ * @param difficultyValue The difficulty-dependent value to be unmapped.
416
+ * @param diff0 Minimum of the resulting range which will be achieved by a difficulty value of 0.
417
+ * @param diff5 Midpoint of the resulting range which will be achieved by a difficulty value of 5.
418
+ * @param diff10 Maximum of the resulting range which will be achieved by a difficulty value of 10.
419
+ * @return The value to which the difficulty value maps in the specified range.
420
+ */
421
+ static inverseDifficultyRange(difficultyValue, diff0, diff5, diff10) {
422
+ if (Math.sign(difficultyValue - diff5) == Math.sign(diff10 - diff0)) {
423
+ return ((difficultyValue - diff5) / (diff10 - diff5)) * 5 + 5;
424
+ }
425
+ else {
426
+ return ((difficultyValue - diff5) / (diff5 - diff0)) * 5 + 5;
427
+ }
428
+ }
393
429
  constructor(shallowCopy) {
394
430
  /**
395
431
  * The circle size of the beatmap.
@@ -568,477 +604,6 @@ CircleSizeCalculator.brokenGamefieldRoundingAllowance = 1.00041;
568
604
  */
569
605
  CircleSizeCalculator.assumedDroidHeight = 681;
570
606
 
571
- /**
572
- * Represents a mod.
573
- */
574
- class Mod {
575
- /**
576
- * Whether this mod can be applied to osu!droid.
577
- */
578
- isApplicableToDroid() {
579
- return "droidRanked" in this;
580
- }
581
- /**
582
- * Whether this mod can be applied to osu!standard.
583
- */
584
- isApplicableToOsu() {
585
- return "pcRanked" in this;
586
- }
587
- /**
588
- * Whether this mod can be applied to a beatmap.
589
- */
590
- isApplicableToBeatmap() {
591
- return "applyToBeatmap" in this;
592
- }
593
- /**
594
- * Whether this mod can be applied to a beatmap difficulty.
595
- */
596
- isApplicableToDifficulty() {
597
- return "applyToDifficulty" in this;
598
- }
599
- /**
600
- * Whether this mod can be applied to a beatmap difficulty relative to other mods and settings.
601
- */
602
- isApplicableToDifficultyWithSettings() {
603
- return "applyToDifficultyWithSettings" in this;
604
- }
605
- /**
606
- * Whether this mod can be applied to a hitobject.
607
- */
608
- isApplicableToHitObject() {
609
- return "applyToHitObject" in this;
610
- }
611
- }
612
-
613
- /**
614
- * Represents the difficulty adjust (DA) mod.
615
- *
616
- * This is not a real mod in osu! but is used to force difficulty values in the game.
617
- */
618
- class ModDifficultyAdjust extends Mod {
619
- constructor(values) {
620
- super();
621
- this.acronym = "DA";
622
- this.name = "Difficulty Adjust";
623
- this.droidRanked = false;
624
- this.droidScoreMultiplier = 1;
625
- this.droidString = "";
626
- this.isDroidLegacyMod = false;
627
- this.pcRanked = false;
628
- this.pcScoreMultiplier = 1;
629
- this.bitwise = 0;
630
- this.cs = values === null || values === void 0 ? void 0 : values.cs;
631
- this.ar = values === null || values === void 0 ? void 0 : values.ar;
632
- this.od = values === null || values === void 0 ? void 0 : values.od;
633
- this.hp = values === null || values === void 0 ? void 0 : values.hp;
634
- }
635
- applyToDifficulty(mode, difficulty) {
636
- if (this.cs !== undefined) {
637
- difficulty.cs = this.cs;
638
- }
639
- if (this.ar !== undefined) {
640
- difficulty.ar = this.ar;
641
- }
642
- if (this.od !== undefined) {
643
- difficulty.od = this.od;
644
- }
645
- if (this.hp !== undefined) {
646
- difficulty.hp = this.hp;
647
- }
648
- }
649
- }
650
-
651
- /**
652
- * Represents the DoubleTime mod.
653
- */
654
- class ModDoubleTime extends Mod {
655
- constructor() {
656
- super(...arguments);
657
- this.acronym = "DT";
658
- this.name = "DoubleTime";
659
- this.droidRanked = true;
660
- this.droidScoreMultiplier = 1.12;
661
- this.droidString = "d";
662
- this.isDroidLegacyMod = false;
663
- this.pcRanked = true;
664
- this.pcScoreMultiplier = 1.12;
665
- this.bitwise = 1 << 6;
666
- }
667
- }
668
-
669
- /**
670
- * Represents the HalfTime mod.
671
- */
672
- class ModHalfTime extends Mod {
673
- constructor() {
674
- super(...arguments);
675
- this.acronym = "HT";
676
- this.name = "HalfTime";
677
- this.droidRanked = true;
678
- this.droidScoreMultiplier = 0.3;
679
- this.droidString = "t";
680
- this.isDroidLegacyMod = false;
681
- this.pcRanked = true;
682
- this.pcScoreMultiplier = 0.3;
683
- this.bitwise = 1 << 8;
684
- }
685
- }
686
-
687
- /**
688
- * Represents the NightCore mod.
689
- */
690
- class ModNightCore extends Mod {
691
- constructor() {
692
- super(...arguments);
693
- this.acronym = "NC";
694
- this.name = "NightCore";
695
- this.droidRanked = true;
696
- this.droidString = "c";
697
- this.isDroidLegacyMod = false;
698
- this.droidScoreMultiplier = 1.12;
699
- this.pcRanked = true;
700
- this.pcScoreMultiplier = 1.12;
701
- this.bitwise = 1 << 9;
702
- }
703
- }
704
-
705
- /**
706
- * Represents the Precise mod.
707
- */
708
- class ModPrecise extends Mod {
709
- constructor() {
710
- super(...arguments);
711
- this.acronym = "PR";
712
- this.name = "Precise";
713
- this.droidRanked = true;
714
- this.droidScoreMultiplier = 1.06;
715
- this.droidString = "s";
716
- this.isDroidLegacyMod = false;
717
- }
718
- }
719
-
720
- /**
721
- * Represents the SpeedUp mod.
722
- */
723
- class ModSpeedUp extends Mod {
724
- constructor() {
725
- super(...arguments);
726
- this.acronym = "SU";
727
- this.name = "Speed Up";
728
- this.droidRanked = false;
729
- this.droidScoreMultiplier = 1.06;
730
- this.droidString = "b";
731
- this.isDroidLegacyMod = true;
732
- }
733
- }
734
-
735
- class HitWindow {
736
- /**
737
- * @param overallDifficulty The overall difficulty of this hit window.
738
- */
739
- constructor(overallDifficulty) {
740
- this.overallDifficulty = overallDifficulty;
741
- }
742
- }
743
- /**
744
- * Represents the hit window of osu!droid.
745
- */
746
- class DroidHitWindow extends HitWindow {
747
- /**
748
- * Calculates the overall difficulty value of a great hit window.
749
- *
750
- * @param value The value of the hit window, in milliseconds.
751
- * @param isPrecise Whether to calculate for Precise mod.
752
- * @returns The overall difficulty value.
753
- */
754
- static hitWindow300ToOD(value, isPrecise) {
755
- if (isPrecise) {
756
- return 5 - (value - 55) / 6;
757
- }
758
- else {
759
- return 5 - (value - 75) / 5;
760
- }
761
- }
762
- /**
763
- * Calculates the overall difficulty value of a good hit window.
764
- *
765
- * @param value The value of the hit window, in milliseconds.
766
- * @param isPrecise Whether to calculate for Precise mod.
767
- * @returns The overall difficulty value.
768
- */
769
- static hitWindow100ToOD(value, isPrecise) {
770
- if (isPrecise) {
771
- return 5 - (value - 120) / 8;
772
- }
773
- else {
774
- return 5 - (value - 150) / 10;
775
- }
776
- }
777
- /**
778
- * Calculates the overall difficulty value of a meh hit window.
779
- *
780
- * @param value The value of the hit window, in milliseconds.
781
- * @param isPrecise Whether to calculate for Precise mod.
782
- * @returns The overall difficulty value.
783
- */
784
- static hitWindow50ToOD(value, isPrecise) {
785
- if (isPrecise) {
786
- return 5 - (value - 180) / 10;
787
- }
788
- else {
789
- return 5 - (value - 250) / 10;
790
- }
791
- }
792
- hitWindowFor300(isPrecise) {
793
- if (isPrecise) {
794
- return 55 + 6 * (5 - this.overallDifficulty);
795
- }
796
- else {
797
- return 75 + 5 * (5 - this.overallDifficulty);
798
- }
799
- }
800
- hitWindowFor100(isPrecise) {
801
- if (isPrecise) {
802
- return 120 + 8 * (5 - this.overallDifficulty);
803
- }
804
- else {
805
- return 150 + 10 * (5 - this.overallDifficulty);
806
- }
807
- }
808
- hitWindowFor50(isPrecise) {
809
- if (isPrecise) {
810
- return 180 + 10 * (5 - this.overallDifficulty);
811
- }
812
- else {
813
- return 250 + 10 * (5 - this.overallDifficulty);
814
- }
815
- }
816
- }
817
- /**
818
- * Represents the hit window of osu!standard.
819
- */
820
- class OsuHitWindow extends HitWindow {
821
- /**
822
- * Calculates the overall difficulty value of a great hit window.
823
- *
824
- * @param value The value of the hit window, in milliseconds.
825
- * @returns The overall difficulty value.
826
- */
827
- static hitWindow300ToOD(value) {
828
- return (80 - value) / 6;
829
- }
830
- /**
831
- * Calculates the overall difficulty value of a good hit window.
832
- *
833
- * @param value The value of the hit window, in milliseconds.
834
- * @returns The overall difficulty value.
835
- */
836
- static hitWindow100ToOD(value) {
837
- return (140 - value) / 8;
838
- }
839
- /**
840
- * Calculates the overall difficulty value of a meh hit window.
841
- *
842
- * @param value The value of the hit window, in milliseconds.
843
- * @returns The overall difficulty value.
844
- */
845
- static hitWindow50ToOD(value) {
846
- return (200 - value) / 10;
847
- }
848
- hitWindowFor300() {
849
- return 80 - 6 * this.overallDifficulty;
850
- }
851
- hitWindowFor100() {
852
- return 140 - 8 * this.overallDifficulty;
853
- }
854
- hitWindowFor50() {
855
- return 200 - 10 * this.overallDifficulty;
856
- }
857
- }
858
-
859
- /**
860
- * Calculates the osu!droid difficulty statistics of a beatmap.
861
- *
862
- * This provides functionality to apply speed-changing mods to the difficulty statistics.
863
- *
864
- * @param options The options for the difficulty statistics calculator.
865
- * @returns The difficulty statistics of the beatmap.
866
- */
867
- function calculateDroidDifficultyStatistics(options) {
868
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
869
- const overallSpeedMultiplier = calculateSpeedMultiplierFromMods((_a = options.mods) !== null && _a !== void 0 ? _a : [], options.oldStatistics) * ((_b = options.customSpeedMultiplier) !== null && _b !== void 0 ? _b : 1);
870
- const difficulty = new BeatmapDifficulty();
871
- difficulty.cs = (_c = options.circleSize) !== null && _c !== void 0 ? _c : difficulty.cs;
872
- difficulty.ar = (_d = options.approachRate) !== null && _d !== void 0 ? _d : difficulty.ar;
873
- difficulty.od = (_e = options.overallDifficulty) !== null && _e !== void 0 ? _e : difficulty.od;
874
- difficulty.hp = (_f = options.healthDrain) !== null && _f !== void 0 ? _f : difficulty.hp;
875
- const difficultyAdjustMod = (_g = options.mods) === null || _g === void 0 ? void 0 : _g.find((mod) => mod instanceof ModDifficultyAdjust);
876
- (_h = options.mods) === null || _h === void 0 ? void 0 : _h.forEach((mod) => {
877
- if (mod.isApplicableToDifficulty()) {
878
- mod.applyToDifficulty(exports.Modes.droid, difficulty);
879
- }
880
- });
881
- // Special handling for difficulty adjust mod where difficulty statistics are forced.
882
- difficultyAdjustMod === null || difficultyAdjustMod === void 0 ? void 0 : difficultyAdjustMod.applyToDifficulty(exports.Modes.droid, difficulty);
883
- (_j = options.mods) === null || _j === void 0 ? void 0 : _j.forEach((mod, _, arr) => {
884
- var _a;
885
- if (mod.isApplicableToDifficultyWithSettings()) {
886
- mod.applyToDifficultyWithSettings(exports.Modes.droid, difficulty, arr, (_a = options.customSpeedMultiplier) !== null && _a !== void 0 ? _a : 1);
887
- }
888
- });
889
- if (options.circleSize !== undefined &&
890
- options.convertCircleSize !== false) {
891
- const scale = CircleSizeCalculator.droidCSToDroidScale(difficulty.cs);
892
- const radius = CircleSizeCalculator.droidScaleToStandardRadius(scale);
893
- difficulty.cs = CircleSizeCalculator.standardRadiusToStandardCS(radius, true);
894
- }
895
- // Apply speed-changing mods
896
- if (options.approachRate !== undefined &&
897
- (difficultyAdjustMod === null || difficultyAdjustMod === void 0 ? void 0 : difficultyAdjustMod.ar) === undefined) {
898
- const approachRateMilliseconds = convertApproachRateToMilliseconds(difficulty.ar) /
899
- overallSpeedMultiplier;
900
- difficulty.ar = convertApproachRateMilliseconds(approachRateMilliseconds);
901
- }
902
- if (options.overallDifficulty !== undefined) {
903
- const isPrecise = (_l = (_k = options.mods) === null || _k === void 0 ? void 0 : _k.some((mod) => mod instanceof ModPrecise)) !== null && _l !== void 0 ? _l : false;
904
- let hitWindowGreat = new DroidHitWindow(difficulty.od).hitWindowFor300(isPrecise);
905
- if ((difficultyAdjustMod === null || difficultyAdjustMod === void 0 ? void 0 : difficultyAdjustMod.od) === undefined) {
906
- hitWindowGreat /= overallSpeedMultiplier;
907
- }
908
- difficulty.od =
909
- options.convertOverallDifficulty !== false
910
- ? OsuHitWindow.hitWindow300ToOD(hitWindowGreat)
911
- : DroidHitWindow.hitWindow300ToOD(hitWindowGreat, isPrecise);
912
- }
913
- return {
914
- circleSize: (options.circleSize !== undefined
915
- ? difficulty.cs
916
- : undefined),
917
- approachRate: (options.approachRate !== undefined
918
- ? difficulty.ar
919
- : undefined),
920
- overallDifficulty: (options.overallDifficulty !== undefined
921
- ? difficulty.od
922
- : undefined),
923
- healthDrain: (options.healthDrain !== undefined
924
- ? difficulty.hp
925
- : undefined),
926
- overallSpeedMultiplier: overallSpeedMultiplier,
927
- };
928
- }
929
- /**
930
- * Calculates the osu!standard difficulty statistics of a beatmap.
931
- *
932
- * This provides functionality to apply speed-changing mods to the difficulty statistics.
933
- *
934
- * @param options The options for the difficulty statistics calculator.
935
- * @returns The difficulty statistics of the beatmap.
936
- */
937
- function calculateOsuDifficultyStatistics(options) {
938
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
939
- const overallSpeedMultiplier = calculateSpeedMultiplierFromMods((_a = options.mods) !== null && _a !== void 0 ? _a : []) *
940
- ((_b = options.customSpeedMultiplier) !== null && _b !== void 0 ? _b : 1);
941
- const difficulty = new BeatmapDifficulty();
942
- difficulty.cs = (_c = options.circleSize) !== null && _c !== void 0 ? _c : difficulty.cs;
943
- difficulty.ar = (_d = options.approachRate) !== null && _d !== void 0 ? _d : difficulty.ar;
944
- difficulty.od = (_e = options.overallDifficulty) !== null && _e !== void 0 ? _e : difficulty.od;
945
- difficulty.hp = (_f = options.healthDrain) !== null && _f !== void 0 ? _f : difficulty.hp;
946
- const difficultyAdjustMod = (_g = options.mods) === null || _g === void 0 ? void 0 : _g.find((mod) => mod instanceof ModDifficultyAdjust);
947
- (_h = options.mods) === null || _h === void 0 ? void 0 : _h.forEach((mod) => {
948
- if (mod.isApplicableToDifficulty()) {
949
- mod.applyToDifficulty(exports.Modes.osu, difficulty);
950
- }
951
- });
952
- // Special handling for difficulty adjust mod where difficulty statistics are forced.
953
- difficultyAdjustMod === null || difficultyAdjustMod === void 0 ? void 0 : difficultyAdjustMod.applyToDifficulty(exports.Modes.osu, difficulty);
954
- (_j = options.mods) === null || _j === void 0 ? void 0 : _j.forEach((mod, _, arr) => {
955
- var _a;
956
- if (mod.isApplicableToDifficultyWithSettings()) {
957
- mod.applyToDifficultyWithSettings(exports.Modes.osu, difficulty, arr, (_a = options.customSpeedMultiplier) !== null && _a !== void 0 ? _a : 1);
958
- }
959
- });
960
- // Apply speed-changing mods
961
- if (options.approachRate !== undefined &&
962
- (difficultyAdjustMod === null || difficultyAdjustMod === void 0 ? void 0 : difficultyAdjustMod.ar) === undefined) {
963
- const approachRateMilliseconds = convertApproachRateToMilliseconds(difficulty.ar) /
964
- overallSpeedMultiplier;
965
- difficulty.ar = convertApproachRateMilliseconds(approachRateMilliseconds);
966
- }
967
- if (options.overallDifficulty !== undefined &&
968
- (difficultyAdjustMod === null || difficultyAdjustMod === void 0 ? void 0 : difficultyAdjustMod.od) === undefined) {
969
- const hitWindowGreat = new OsuHitWindow(difficulty.od).hitWindowFor300() /
970
- overallSpeedMultiplier;
971
- difficulty.od = OsuHitWindow.hitWindow300ToOD(hitWindowGreat);
972
- }
973
- return {
974
- circleSize: (options.circleSize !== undefined
975
- ? difficulty.cs
976
- : undefined),
977
- approachRate: (options.approachRate !== undefined
978
- ? difficulty.ar
979
- : undefined),
980
- overallDifficulty: (options.overallDifficulty !== undefined
981
- ? difficulty.od
982
- : undefined),
983
- healthDrain: (options.healthDrain !== undefined
984
- ? difficulty.hp
985
- : undefined),
986
- overallSpeedMultiplier: overallSpeedMultiplier,
987
- };
988
- }
989
- /**
990
- * Calculates the speed multiplier obtained from mods.
991
- *
992
- * @param mods The mods to calculate the speed multiplier from.
993
- * @param oldStatistics Whether to calculate for old statistics for osu!droid (1.6.7 and older). Defaults to `false`.
994
- * @returns The speed multiplier obtained from the mods.
995
- */
996
- function calculateSpeedMultiplierFromMods(mods, oldStatistics) {
997
- let speedMultiplier = 1;
998
- for (const mod of mods) {
999
- switch (true) {
1000
- case mod instanceof ModDoubleTime:
1001
- speedMultiplier *= 1.5;
1002
- break;
1003
- case mod instanceof ModHalfTime:
1004
- speedMultiplier *= 0.75;
1005
- break;
1006
- case mod instanceof ModNightCore:
1007
- speedMultiplier *= oldStatistics ? 1.39 : 1.5;
1008
- break;
1009
- case mod instanceof ModSpeedUp:
1010
- speedMultiplier *= 1.25;
1011
- break;
1012
- }
1013
- }
1014
- return speedMultiplier;
1015
- }
1016
- const AR0_MS = 1800;
1017
- const AR5_MS = 1200;
1018
- const AR10_MS = 450;
1019
- const AR_MS_STEP1 = (AR0_MS - AR5_MS) / 5;
1020
- const AR_MS_STEP2 = (AR5_MS - AR10_MS) / 5;
1021
- /**
1022
- * Converts an approach rate value to its milliseconds counterpart.
1023
- *
1024
- * @param ar The approach rate to convert.
1025
- * @returns The converted approach rate in milliseconds.
1026
- */
1027
- function convertApproachRateToMilliseconds(ar) {
1028
- return ar < 5 ? AR0_MS - AR_MS_STEP1 * ar : AR5_MS - AR_MS_STEP2 * (ar - 5);
1029
- }
1030
- /**
1031
- * Converts an approach rate value in milliseconds to its approach rate value counterpart.
1032
- *
1033
- * @param ms The approach rate in milliseconds to convert.
1034
- * @returns The converted approach rate value.
1035
- */
1036
- function convertApproachRateMilliseconds(ms) {
1037
- return ms > AR5_MS
1038
- ? (AR0_MS - ms) / AR_MS_STEP1
1039
- : 5 + (AR5_MS - ms) / AR_MS_STEP2;
1040
- }
1041
-
1042
607
  /**
1043
608
  * Represents a hitobject in a beatmap.
1044
609
  */
@@ -1162,17 +727,18 @@ class HitObject {
1162
727
  * @param mode The gamemode to apply defaults for.
1163
728
  */
1164
729
  applyDefaults(controlPoints, difficulty, mode) {
1165
- this.timePreempt = convertApproachRateToMilliseconds(difficulty.ar);
730
+ this.timePreempt = BeatmapDifficulty.difficultyRange(difficulty.ar, HitObject.preemptMax, HitObject.preemptMid, HitObject.preemptMin);
1166
731
  // 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.
1167
732
  // 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.
1168
733
  // Note that this doesn't exactly match the AR>10 visuals as they're classically known, but it feels good.
1169
734
  // This adjustment is necessary for AR>10, otherwise timePreempt can become smaller leading to hit circles not fully fading in.
1170
- this.timeFadeIn = 400 * Math.min(1, this.timePreempt / AR10_MS);
735
+ this.timeFadeIn =
736
+ 400 * Math.min(1, this.timePreempt / HitObject.preemptMin);
1171
737
  switch (mode) {
1172
738
  case exports.Modes.droid: {
1173
- const cs = calculateDroidDifficultyStatistics({
1174
- circleSize: difficulty.cs,
1175
- }).circleSize;
739
+ const droidScale = CircleSizeCalculator.droidCSToDroidScale(difficulty.cs);
740
+ const radius = CircleSizeCalculator.droidScaleToStandardRadius(droidScale);
741
+ const cs = CircleSizeCalculator.standardRadiusToStandardCS(radius, true);
1176
742
  this.scale = CircleSizeCalculator.standardCSToStandardScale(cs, true);
1177
743
  break;
1178
744
  }
@@ -1261,6 +827,18 @@ class HitObject {
1261
827
  * The base radius of all hitobjects.
1262
828
  */
1263
829
  HitObject.baseRadius = 64;
830
+ /**
831
+ * Maximum preempt time at AR=0.
832
+ */
833
+ HitObject.preemptMax = 1800;
834
+ /**
835
+ * Median preempt time at AR=5.
836
+ */
837
+ HitObject.preemptMid = 1200;
838
+ /**
839
+ * Minimum preempt time at AR=10.
840
+ */
841
+ HitObject.preemptMin = 450;
1264
842
  /**
1265
843
  * A small adjustment to the start time of control points to account for rounding/precision errors.
1266
844
  */
@@ -2588,8 +2166,9 @@ class BeatmapProcessor {
2588
2166
  for (let i = 0; i < objects.length - 1; ++i) {
2589
2167
  const current = objects[i];
2590
2168
  const next = objects[i + 1];
2591
- if (next.startTime - current.startTime <
2592
- 2000 * this.beatmap.general.stackLeniency &&
2169
+ if (current instanceof Circle &&
2170
+ next.startTime - current.startTime <
2171
+ 2000 * this.beatmap.general.stackLeniency &&
2593
2172
  next.position.getDistance(current.position) <
2594
2173
  Math.sqrt(convertedScale)) {
2595
2174
  next.stackHeight = current.stackHeight + 1;
@@ -3037,9 +2616,6 @@ class Beatmap {
3037
2616
  mod.applyToDifficulty(mode, converted.difficulty);
3038
2617
  }
3039
2618
  });
3040
- // Special handling for difficulty adjust mod where difficulty statistics are forced.
3041
- const difficultyAdjustMod = mods.find((m) => m instanceof ModDifficultyAdjust);
3042
- difficultyAdjustMod === null || difficultyAdjustMod === void 0 ? void 0 : difficultyAdjustMod.applyToDifficulty(mode, converted.difficulty);
3043
2619
  mods.forEach((mod) => {
3044
2620
  if (mod.isApplicableToDifficultyWithSettings()) {
3045
2621
  mod.applyToDifficultyWithSettings(mode, converted.difficulty, mods, customSpeedMultiplier);
@@ -3054,6 +2630,13 @@ class Beatmap {
3054
2630
  }
3055
2631
  }
3056
2632
  });
2633
+ mods.forEach((mod) => {
2634
+ if (mod.isApplicableToHitObjectWithSettings()) {
2635
+ for (const hitObject of converted.hitObjects.objects) {
2636
+ mod.applyToHitObjectWithSettings(mode, hitObject, mods, customSpeedMultiplier);
2637
+ }
2638
+ }
2639
+ });
3057
2640
  new BeatmapProcessor(converted).postProcess(mode);
3058
2641
  mods.forEach((mod) => {
3059
2642
  if (mod.isApplicableToBeatmap()) {
@@ -5658,7 +5241,7 @@ class BeatmapBaseEncoder extends BaseEncoder {
5658
5241
  */
5659
5242
  class BeatmapColorEncoder extends BeatmapBaseEncoder {
5660
5243
  encodeInternal() {
5661
- const colors = this.map.colors;
5244
+ const { colors } = this.map;
5662
5245
  if (colors.combo.length === 0 &&
5663
5246
  !colors.sliderBorder &&
5664
5247
  !colors.sliderTrackOverride) {
@@ -5778,7 +5361,7 @@ class BeatmapDifficultyEncoder extends BeatmapBaseEncoder {
5778
5361
  if (this.encodeSections) {
5779
5362
  this.writeLine("[Difficulty]");
5780
5363
  }
5781
- const difficulty = this.map.difficulty;
5364
+ const { difficulty } = this.map;
5782
5365
  this.writeLine(`HPDrainRate: ${difficulty.hp}`);
5783
5366
  this.writeLine(`CircleSize: ${difficulty.cs}`);
5784
5367
  this.writeLine(`OverallDifficulty: ${difficulty.od}`);
@@ -5796,7 +5379,7 @@ class BeatmapEditorEncoder extends BeatmapBaseEncoder {
5796
5379
  if (this.encodeSections) {
5797
5380
  this.writeLine("[Editor]");
5798
5381
  }
5799
- const editor = this.map.editor;
5382
+ const { editor } = this.map;
5800
5383
  if (editor.bookmarks.length > 0) {
5801
5384
  this.writeLine(editor.bookmarks.join());
5802
5385
  }
@@ -6010,7 +5593,7 @@ class StoryboardEncoder extends Encoder {
6010
5593
  reset() {
6011
5594
  this.finalResult = "";
6012
5595
  this.encoders = [
6013
- // The variable decoder is put first as variable need to be on top of a .osb file.
5596
+ // The variable decoder is put first as variables need to be on top of a .osb file.
6014
5597
  new StoryboardVariablesEncoder(this.target, this.encodeSections),
6015
5598
  new StoryboardEventsEncoder(this.target, this.encodeSections),
6016
5599
  ];
@@ -6026,7 +5609,7 @@ class BeatmapEventsEncoder extends BeatmapBaseEncoder {
6026
5609
  this.writeLine("[Events]");
6027
5610
  }
6028
5611
  this.writeLine("//Background and Video Events");
6029
- const events = this.map.events;
5612
+ const { events } = this.map;
6030
5613
  if (events.background) {
6031
5614
  this.writeLine(`0,0,"${events.background.filename}",${events.background.offset.x},${events.background.offset.y}`);
6032
5615
  }
@@ -6059,7 +5642,7 @@ class BeatmapGeneralEncoder extends BeatmapBaseEncoder {
6059
5642
  if (this.encodeSections) {
6060
5643
  this.writeLine("[General]");
6061
5644
  }
6062
- const general = this.map.general;
5645
+ const { general } = this.map;
6063
5646
  if (general.audioFilename) {
6064
5647
  this.writeLine(`AudioFilename: ${general.audioFilename}`);
6065
5648
  }
@@ -6196,7 +5779,7 @@ class BeatmapMetadataEncoder extends BeatmapBaseEncoder {
6196
5779
  if (this.encodeSections) {
6197
5780
  this.writeLine("[Metadata]");
6198
5781
  }
6199
- const metadata = this.map.metadata;
5782
+ const { metadata } = this.map;
6200
5783
  this.writeLine(`Title: ${metadata.title}`);
6201
5784
  if (metadata.titleUnicode) {
6202
5785
  this.writeLine(`TitleUnicode: ${metadata.titleUnicode}`);
@@ -6659,7 +6242,7 @@ class APIRequestBuilder {
6659
6242
  * @param value The value to add for the parameter.
6660
6243
  */
6661
6244
  addParameter(param, value) {
6662
- this.params.set(param, value);
6245
+ this.params.set(param, value.toString());
6663
6246
  return this;
6664
6247
  }
6665
6248
  /**
@@ -7509,13 +7092,137 @@ ErrorFunction.ervInvImpGn = [
7509
7092
  0.13588013010892486e-14, -0.3488903933999489e-21,
7510
7093
  ];
7511
7094
  /**
7512
- * Polynomial coefficients for a denominator of erfInvImp
7513
- * calculation for erf^-1(z) in the interval [0.75, 1] with x greater than 44.
7095
+ * Polynomial coefficients for a denominator of erfInvImp
7096
+ * calculation for erf^-1(z) in the interval [0.75, 1] with x greater than 44.
7097
+ */
7098
+ ErrorFunction.ervInvImpGd = [
7099
+ 1, 0.08457462340018994, 0.002820929847262647, 0.4682929219408942e-4,
7100
+ 0.3999688121938621e-6, 0.1618092908879045e-8, 0.2315586083102596e-11,
7101
+ ];
7102
+
7103
+ class HitWindow {
7104
+ /**
7105
+ * @param overallDifficulty The overall difficulty of this hit window.
7106
+ */
7107
+ constructor(overallDifficulty) {
7108
+ this.overallDifficulty = overallDifficulty;
7109
+ }
7110
+ }
7111
+ /**
7112
+ * Represents the hit window of osu!droid.
7113
+ */
7114
+ class DroidHitWindow extends HitWindow {
7115
+ /**
7116
+ * Calculates the overall difficulty value of a great hit window.
7117
+ *
7118
+ * @param value The value of the hit window, in milliseconds.
7119
+ * @param isPrecise Whether to calculate for Precise mod.
7120
+ * @returns The overall difficulty value.
7121
+ */
7122
+ static hitWindow300ToOD(value, isPrecise) {
7123
+ if (isPrecise) {
7124
+ return 5 - (value - 55) / 6;
7125
+ }
7126
+ else {
7127
+ return 5 - (value - 75) / 5;
7128
+ }
7129
+ }
7130
+ /**
7131
+ * Calculates the overall difficulty value of a good hit window.
7132
+ *
7133
+ * @param value The value of the hit window, in milliseconds.
7134
+ * @param isPrecise Whether to calculate for Precise mod.
7135
+ * @returns The overall difficulty value.
7136
+ */
7137
+ static hitWindow100ToOD(value, isPrecise) {
7138
+ if (isPrecise) {
7139
+ return 5 - (value - 120) / 8;
7140
+ }
7141
+ else {
7142
+ return 5 - (value - 150) / 10;
7143
+ }
7144
+ }
7145
+ /**
7146
+ * Calculates the overall difficulty value of a meh hit window.
7147
+ *
7148
+ * @param value The value of the hit window, in milliseconds.
7149
+ * @param isPrecise Whether to calculate for Precise mod.
7150
+ * @returns The overall difficulty value.
7151
+ */
7152
+ static hitWindow50ToOD(value, isPrecise) {
7153
+ if (isPrecise) {
7154
+ return 5 - (value - 180) / 10;
7155
+ }
7156
+ else {
7157
+ return 5 - (value - 250) / 10;
7158
+ }
7159
+ }
7160
+ hitWindowFor300(isPrecise) {
7161
+ if (isPrecise) {
7162
+ return 55 + 6 * (5 - this.overallDifficulty);
7163
+ }
7164
+ else {
7165
+ return 75 + 5 * (5 - this.overallDifficulty);
7166
+ }
7167
+ }
7168
+ hitWindowFor100(isPrecise) {
7169
+ if (isPrecise) {
7170
+ return 120 + 8 * (5 - this.overallDifficulty);
7171
+ }
7172
+ else {
7173
+ return 150 + 10 * (5 - this.overallDifficulty);
7174
+ }
7175
+ }
7176
+ hitWindowFor50(isPrecise) {
7177
+ if (isPrecise) {
7178
+ return 180 + 10 * (5 - this.overallDifficulty);
7179
+ }
7180
+ else {
7181
+ return 250 + 10 * (5 - this.overallDifficulty);
7182
+ }
7183
+ }
7184
+ }
7185
+ /**
7186
+ * Represents the hit window of osu!standard.
7514
7187
  */
7515
- ErrorFunction.ervInvImpGd = [
7516
- 1, 0.08457462340018994, 0.002820929847262647, 0.4682929219408942e-4,
7517
- 0.3999688121938621e-6, 0.1618092908879045e-8, 0.2315586083102596e-11,
7518
- ];
7188
+ class OsuHitWindow extends HitWindow {
7189
+ /**
7190
+ * Calculates the overall difficulty value of a great hit window.
7191
+ *
7192
+ * @param value The value of the hit window, in milliseconds.
7193
+ * @returns The overall difficulty value.
7194
+ */
7195
+ static hitWindow300ToOD(value) {
7196
+ return (80 - value) / 6;
7197
+ }
7198
+ /**
7199
+ * Calculates the overall difficulty value of a good hit window.
7200
+ *
7201
+ * @param value The value of the hit window, in milliseconds.
7202
+ * @returns The overall difficulty value.
7203
+ */
7204
+ static hitWindow100ToOD(value) {
7205
+ return (140 - value) / 8;
7206
+ }
7207
+ /**
7208
+ * Calculates the overall difficulty value of a meh hit window.
7209
+ *
7210
+ * @param value The value of the hit window, in milliseconds.
7211
+ * @returns The overall difficulty value.
7212
+ */
7213
+ static hitWindow50ToOD(value) {
7214
+ return (200 - value) / 10;
7215
+ }
7216
+ hitWindowFor300() {
7217
+ return 80 - 6 * this.overallDifficulty;
7218
+ }
7219
+ hitWindowFor100() {
7220
+ return 140 - 8 * this.overallDifficulty;
7221
+ }
7222
+ hitWindowFor50() {
7223
+ return 200 - 10 * this.overallDifficulty;
7224
+ }
7225
+ }
7519
7226
 
7520
7227
  /**
7521
7228
  * Holds interpolation methods for numbers and vectors.
@@ -7779,12 +7486,13 @@ class MapInfo {
7779
7486
  });
7780
7487
  }
7781
7488
  /**
7782
- * Constructs a `MapInfo` from an osu! API response.
7489
+ * Constructs a `MapInfo` from an osu! API response and (optionally) a parsed `Beatmap`.
7783
7490
  *
7784
7491
  * @param mapinfo The osu! API response.
7785
- * @returns A `MapInfo` instance representing the osu! API response.
7492
+ * @param parsedBeatmap The parsed `Beatmap`.
7493
+ * @returns A `MapInfo` instance representing the osu! API response and parsed `Beatmap`.
7786
7494
  */
7787
- static from(mapinfo) {
7495
+ static from(mapinfo, parsedBeatmap) {
7788
7496
  const map = new MapInfo();
7789
7497
  const parseDate = (str) => {
7790
7498
  const t = str.split(/[- :]/).map((e) => parseInt(e));
@@ -7842,6 +7550,9 @@ class MapInfo {
7842
7550
  map.videoAvailable = !!parseInt(mapinfo.video);
7843
7551
  map.downloadAvailable = !parseInt(mapinfo.download_unavailable);
7844
7552
  map.audioAvailable = !parseInt(mapinfo.audio_unavailable);
7553
+ if (parsedBeatmap) {
7554
+ map.cachedBeatmap = parsedBeatmap;
7555
+ }
7845
7556
  return map;
7846
7557
  }
7847
7558
  /**
@@ -7939,6 +7650,60 @@ class MapInfo {
7939
7650
  }
7940
7651
  }
7941
7652
 
7653
+ /**
7654
+ * Represents a mod.
7655
+ */
7656
+ class Mod {
7657
+ /**
7658
+ * Whether this `Mod` can be applied to osu!droid.
7659
+ */
7660
+ isApplicableToDroid() {
7661
+ return "droidRanked" in this;
7662
+ }
7663
+ /**
7664
+ * Whether this `Mod` can be applied to osu!standard.
7665
+ */
7666
+ isApplicableToOsu() {
7667
+ return "pcRanked" in this;
7668
+ }
7669
+ /**
7670
+ * Whether this `Mod` can be applied to a `Beatmap`.
7671
+ */
7672
+ isApplicableToBeatmap() {
7673
+ return "applyToBeatmap" in this;
7674
+ }
7675
+ /**
7676
+ * Whether this `Mod` can be applied to a `BeatmapDifficulty`.
7677
+ */
7678
+ isApplicableToDifficulty() {
7679
+ return "applyToDifficulty" in this;
7680
+ }
7681
+ /**
7682
+ * Whether this `Mod` can be applied to a `BeatmapDifficulty` relative to other `Mod`s and settings.
7683
+ */
7684
+ isApplicableToDifficultyWithSettings() {
7685
+ return "applyToDifficultyWithSettings" in this;
7686
+ }
7687
+ /**
7688
+ * Whether this `Mod` can be applied to a `HitObject`.
7689
+ */
7690
+ isApplicableToHitObject() {
7691
+ return "applyToHitObject" in this;
7692
+ }
7693
+ /**
7694
+ * Whether this `Mod` can be applied to a `HitObject` relative to other `Mod`s and settings.
7695
+ */
7696
+ isApplicableToHitObjectWithSettings() {
7697
+ return "applyToHitObjectWithSettings" in this;
7698
+ }
7699
+ /**
7700
+ * Whether this `Mod`s can be applied to a track's playback rate.
7701
+ */
7702
+ isApplicableToTrackRate() {
7703
+ return "applyToRate" in this;
7704
+ }
7705
+ }
7706
+
7942
7707
  /**
7943
7708
  * Represents the Auto mod.
7944
7709
  */
@@ -7975,6 +7740,27 @@ class ModAutopilot extends Mod {
7975
7740
  }
7976
7741
  }
7977
7742
 
7743
+ /**
7744
+ * Represents the DoubleTime mod.
7745
+ */
7746
+ class ModDoubleTime extends Mod {
7747
+ constructor() {
7748
+ super(...arguments);
7749
+ this.acronym = "DT";
7750
+ this.name = "DoubleTime";
7751
+ this.droidRanked = true;
7752
+ this.droidScoreMultiplier = 1.12;
7753
+ this.droidString = "d";
7754
+ this.isDroidLegacyMod = false;
7755
+ this.pcRanked = true;
7756
+ this.pcScoreMultiplier = 1.12;
7757
+ this.bitwise = 1 << 6;
7758
+ }
7759
+ applyToRate(rate) {
7760
+ return rate * 1.5;
7761
+ }
7762
+ }
7763
+
7978
7764
  /**
7979
7765
  * Represents the Easy mod.
7980
7766
  */
@@ -8025,6 +7811,27 @@ class ModFlashlight extends Mod {
8025
7811
  }
8026
7812
  }
8027
7813
 
7814
+ /**
7815
+ * Represents the HalfTime mod.
7816
+ */
7817
+ class ModHalfTime extends Mod {
7818
+ constructor() {
7819
+ super(...arguments);
7820
+ this.acronym = "HT";
7821
+ this.name = "HalfTime";
7822
+ this.droidRanked = true;
7823
+ this.droidScoreMultiplier = 0.3;
7824
+ this.droidString = "t";
7825
+ this.isDroidLegacyMod = false;
7826
+ this.pcRanked = true;
7827
+ this.pcScoreMultiplier = 0.3;
7828
+ this.bitwise = 1 << 8;
7829
+ }
7830
+ applyToRate(rate) {
7831
+ return rate * 0.75;
7832
+ }
7833
+ }
7834
+
8028
7835
  /**
8029
7836
  * Represents the osu! playfield.
8030
7837
  */
@@ -8125,6 +7932,27 @@ class ModHidden extends Mod {
8125
7932
  ModHidden.fadeInDurationMultiplier = 0.4;
8126
7933
  ModHidden.fadeOutDurationMultiplier = 0.3;
8127
7934
 
7935
+ /**
7936
+ * Represents the NightCore mod.
7937
+ */
7938
+ class ModNightCore extends Mod {
7939
+ constructor() {
7940
+ super(...arguments);
7941
+ this.acronym = "NC";
7942
+ this.name = "NightCore";
7943
+ this.droidRanked = true;
7944
+ this.droidString = "c";
7945
+ this.isDroidLegacyMod = false;
7946
+ this.droidScoreMultiplier = 1.12;
7947
+ this.pcRanked = true;
7948
+ this.pcScoreMultiplier = 1.12;
7949
+ this.bitwise = 1 << 9;
7950
+ }
7951
+ applyToRate(rate, oldStatistics) {
7952
+ return rate * (oldStatistics ? 1.39 : 1.5);
7953
+ }
7954
+ }
7955
+
8128
7956
  /**
8129
7957
  * Represents the NoFail mod.
8130
7958
  */
@@ -8161,6 +7989,21 @@ class ModPerfect extends Mod {
8161
7989
  }
8162
7990
  }
8163
7991
 
7992
+ /**
7993
+ * Represents the Precise mod.
7994
+ */
7995
+ class ModPrecise extends Mod {
7996
+ constructor() {
7997
+ super(...arguments);
7998
+ this.acronym = "PR";
7999
+ this.name = "Precise";
8000
+ this.droidRanked = true;
8001
+ this.droidScoreMultiplier = 1.06;
8002
+ this.droidString = "s";
8003
+ this.isDroidLegacyMod = false;
8004
+ }
8005
+ }
8006
+
8164
8007
  /**
8165
8008
  * Represents the ReallyEasy mod.
8166
8009
  */
@@ -8268,6 +8111,25 @@ class ModSmallCircle extends Mod {
8268
8111
  }
8269
8112
  }
8270
8113
 
8114
+ /**
8115
+ * Represents the SpeedUp mod.
8116
+ */
8117
+ class ModSpeedUp extends Mod {
8118
+ constructor() {
8119
+ super(...arguments);
8120
+ this.acronym = "SU";
8121
+ this.name = "Speed Up";
8122
+ this.droidRanked = false;
8123
+ this.droidScoreMultiplier = 1.06;
8124
+ this.droidString = "b";
8125
+ this.isDroidLegacyMod = true;
8126
+ this.trackRateMultiplier = 1.25;
8127
+ }
8128
+ applyToRate(rate) {
8129
+ return rate * 1.25;
8130
+ }
8131
+ }
8132
+
8271
8133
  /**
8272
8134
  * Represents the SpunOut mod.
8273
8135
  */
@@ -8420,6 +8282,42 @@ class ModUtil {
8420
8282
  .slice()
8421
8283
  .filter((m) => !this.speedChangingMods.some((v) => m.acronym === v.acronym));
8422
8284
  }
8285
+ /**
8286
+ * Applies the selected `Mod`s to a `BeatmapDifficulty`.
8287
+ *
8288
+ * @param difficulty The `BeatmapDifficulty` to apply the `Mod`s to.
8289
+ * @param mode The game mode to apply the `Mod`s for.
8290
+ * @param mods The selected `Mod`s.
8291
+ * @param customSpeedMultiplier The custom speed multiplier to apply.
8292
+ */
8293
+ static applyModsToBeatmapDifficulty(difficulty, mode, mods, customSpeedMultiplier = 1) {
8294
+ for (const mod of mods) {
8295
+ if (mod.isApplicableToDifficulty()) {
8296
+ mod.applyToDifficulty(mode, difficulty);
8297
+ }
8298
+ }
8299
+ for (const mod of mods) {
8300
+ if (mod.isApplicableToDifficultyWithSettings()) {
8301
+ mod.applyToDifficultyWithSettings(mode, difficulty, mods, customSpeedMultiplier);
8302
+ }
8303
+ }
8304
+ }
8305
+ /**
8306
+ * Calculates the rate for the track with the selected `Mod`s.
8307
+ *
8308
+ * @param mods The list of selected `Mod`s.
8309
+ * @param oldStatistics Whether to enforce old statistics. Some `Mod`s behave differently with this flag.
8310
+ * @returns The rate with `Mod`s.
8311
+ */
8312
+ static calculateRateWithMods(mods, oldStatistics) {
8313
+ let rate = 1;
8314
+ for (const mod of mods) {
8315
+ if (mod.isApplicableToTrackRate()) {
8316
+ rate = mod.applyToRate(rate, oldStatistics);
8317
+ }
8318
+ }
8319
+ return rate;
8320
+ }
8423
8321
  /**
8424
8322
  * Processes parsing options.
8425
8323
  *
@@ -8492,6 +8390,56 @@ ModUtil.speedChangingMods = [
8492
8390
  */
8493
8391
  ModUtil.mapChangingMods = _a.speedChangingMods.concat(new ModEasy(), new ModHardRock(), new ModReallyEasy(), new ModSmallCircle());
8494
8392
 
8393
+ /**
8394
+ * Represents the difficulty adjust (DA) mod.
8395
+ *
8396
+ * This is not a real mod in osu! but is used to force difficulty values in the game.
8397
+ */
8398
+ class ModDifficultyAdjust extends Mod {
8399
+ constructor(values) {
8400
+ super();
8401
+ this.acronym = "DA";
8402
+ this.name = "Difficulty Adjust";
8403
+ this.droidRanked = false;
8404
+ this.droidScoreMultiplier = 1;
8405
+ this.droidString = "";
8406
+ this.isDroidLegacyMod = false;
8407
+ this.pcRanked = false;
8408
+ this.pcScoreMultiplier = 1;
8409
+ this.bitwise = 0;
8410
+ this.cs = values === null || values === void 0 ? void 0 : values.cs;
8411
+ this.ar = values === null || values === void 0 ? void 0 : values.ar;
8412
+ this.od = values === null || values === void 0 ? void 0 : values.od;
8413
+ this.hp = values === null || values === void 0 ? void 0 : values.hp;
8414
+ }
8415
+ applyToDifficultyWithSettings(mode, difficulty, mods, customSpeedMultiplier) {
8416
+ var _a, _b, _c, _d;
8417
+ difficulty.cs = (_a = this.cs) !== null && _a !== void 0 ? _a : difficulty.cs;
8418
+ difficulty.ar = (_b = this.ar) !== null && _b !== void 0 ? _b : difficulty.ar;
8419
+ difficulty.od = (_c = this.od) !== null && _c !== void 0 ? _c : difficulty.od;
8420
+ difficulty.hp = (_d = this.hp) !== null && _d !== void 0 ? _d : difficulty.hp;
8421
+ // Special case for force AR, where the AR value is kept constant with respect to game time.
8422
+ // This makes the player perceive the AR as is under all speed multipliers.
8423
+ if (this.ar !== undefined) {
8424
+ const preempt = BeatmapDifficulty.difficultyRange(this.ar, HitObject.preemptMax, HitObject.preemptMid, HitObject.preemptMin);
8425
+ const trackRate = this.calculateTrackRate(mods, customSpeedMultiplier);
8426
+ difficulty.ar = BeatmapDifficulty.inverseDifficultyRange(preempt * trackRate, HitObject.preemptMax, HitObject.preemptMid, HitObject.preemptMin);
8427
+ }
8428
+ }
8429
+ applyToHitObjectWithSettings(mode, hitObject, mods, customSpeedMultiplier) {
8430
+ // Special case for force AR, where the AR value is kept constant with respect to game time.
8431
+ // This makes the player perceive the fade in animation as is under all speed multipliers.
8432
+ if (this.ar === undefined) {
8433
+ return;
8434
+ }
8435
+ const trackRate = this.calculateTrackRate(mods, customSpeedMultiplier);
8436
+ hitObject.timeFadeIn *= trackRate;
8437
+ }
8438
+ calculateTrackRate(mods, customSpeedMultiplier) {
8439
+ return ModUtil.calculateRateWithMods(mods) * customSpeedMultiplier;
8440
+ }
8441
+ }
8442
+
8495
8443
  /**
8496
8444
  * Continuous Univariate Normal distribution, also known as Gaussian distribution.
8497
8445
  *
@@ -8519,9 +8467,6 @@ class NormalDistribution {
8519
8467
 
8520
8468
  dotenv.config();
8521
8469
 
8522
- exports.AR0_MS = AR0_MS;
8523
- exports.AR10_MS = AR10_MS;
8524
- exports.AR5_MS = AR5_MS;
8525
8470
  exports.Accuracy = Accuracy;
8526
8471
  exports.BankHitSampleInfo = BankHitSampleInfo;
8527
8472
  exports.Beatmap = Beatmap;
@@ -8618,9 +8563,4 @@ exports.TimingControlPointManager = TimingControlPointManager;
8618
8563
  exports.Utils = Utils;
8619
8564
  exports.Vector2 = Vector2;
8620
8565
  exports.ZeroCrossingBracketing = ZeroCrossingBracketing;
8621
- exports.calculateDroidDifficultyStatistics = calculateDroidDifficultyStatistics;
8622
- exports.calculateOsuDifficultyStatistics = calculateOsuDifficultyStatistics;
8623
- exports.calculateSpeedMultiplierFromMods = calculateSpeedMultiplierFromMods;
8624
- exports.convertApproachRateMilliseconds = convertApproachRateMilliseconds;
8625
- exports.convertApproachRateToMilliseconds = convertApproachRateToMilliseconds;
8626
8566
  //# sourceMappingURL=index.js.map