@rian8337/osu-droid-replay-analyzer 4.0.0-beta.75 → 4.0.0-beta.77

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -709,6 +709,224 @@ class RebalanceThreeFingerChecker {
709
709
  }
710
710
  }
711
711
 
712
+ /**
713
+ * Utility to check whether relevant sliders in a beatmap are cheesed for rebalance scores..
714
+ */
715
+ class RebalanceSliderCheeseChecker {
716
+ /**
717
+ * @param beatmap The beatmap to analyze.
718
+ * @param data The data of the replay.
719
+ * @param difficultyAttributes The difficulty attributes of the beatmap.
720
+ */
721
+ constructor(beatmap, data, difficultyAttributes) {
722
+ this.beatmap = beatmap;
723
+ this.data = data;
724
+ this.difficultyAttributes = difficultyAttributes;
725
+ this.hitWindow50 = difficultyAttributes.mods.has(osuBase.ModPrecise)
726
+ ? new osuBase.PreciseDroidHitWindow(beatmap.difficulty.od).mehWindow
727
+ : new osuBase.DroidHitWindow(beatmap.difficulty.od).mehWindow;
728
+ this.isHardRock = difficultyAttributes.mods.has(osuBase.ModHardRock);
729
+ }
730
+ /**
731
+ * Checks if relevant sliders in the given beatmap was cheesed.
732
+ */
733
+ check() {
734
+ if (this.difficultyAttributes.difficultSliders.length === 0 ||
735
+ (this.difficultyAttributes.sliderFactor === 1 &&
736
+ this.difficultyAttributes.flashlightSliderFactor === 1)) {
737
+ return {
738
+ aimPenalty: 1,
739
+ flashlightPenalty: 1,
740
+ visualPenalty: 1,
741
+ };
742
+ }
743
+ const cheesedDifficultyRatings = this.checkSliderCheesing();
744
+ return this.calculateSliderCheesePenalty(cheesedDifficultyRatings);
745
+ }
746
+ /**
747
+ * Checks for sliders that were cheesed.
748
+ */
749
+ checkSliderCheesing() {
750
+ const { objects } = this.beatmap.hitObjects;
751
+ const cheesedDifficultyRatings = [];
752
+ // Current loop indices are stored for efficiency.
753
+ const cursorLoopIndices = osuBase.Utils.initializeArray(this.data.cursorMovement.length, 0);
754
+ const acceptableRadius = objects[0].radius * 2;
755
+ // Sort difficult sliders by index so that cursor loop indices work properly.
756
+ for (const difficultSlider of this.difficultyAttributes.difficultSliders
757
+ .slice()
758
+ .sort((a, b) => a.index - b.index)) {
759
+ if (difficultSlider.index >= this.data.hitObjectData.length) {
760
+ continue;
761
+ }
762
+ const object = objects[difficultSlider.index];
763
+ const objectData = this.data.hitObjectData[difficultSlider.index];
764
+ // If a miss or slider break occurs, we disregard the check for that slider.
765
+ if (objectData.result === exports.HitResult.miss ||
766
+ -this.hitWindow50 > objectData.accuracy ||
767
+ objectData.accuracy >
768
+ Math.min(this.hitWindow50, object.duration)) {
769
+ continue;
770
+ }
771
+ const objectStartPosition = object.stackedPosition;
772
+ // These time boundaries should consider the delta time between the previous and next
773
+ // object as well as their hit accuracy. However, they are somewhat complicated to
774
+ // compute and the accuracy gain is small. As such, let's settle with 50 hit window.
775
+ const minTimeLimit = object.startTime - this.hitWindow50;
776
+ const maxTimeLimit = object.startTime + this.hitWindow50;
777
+ // Get the closest tap distance across all cursors.
778
+ const closestDistances = [];
779
+ const closestGroupIndices = [];
780
+ for (let i = 0; i < this.data.cursorMovement.length; ++i) {
781
+ const cursorGroups = this.data.cursorMovement[i].occurrenceGroups;
782
+ let closestDistance = Number.POSITIVE_INFINITY;
783
+ let closestIndex = cursorGroups.length;
784
+ for (let j = cursorLoopIndices[i]; j < cursorGroups.length; j = ++cursorLoopIndices[i]) {
785
+ const group = cursorGroups[j];
786
+ if (group.endTime < minTimeLimit) {
787
+ continue;
788
+ }
789
+ if (group.startTime > maxTimeLimit) {
790
+ break;
791
+ }
792
+ if (group.startTime >= minTimeLimit) {
793
+ const position = this.getCursorPosition(group.down);
794
+ const distance = position.getDistance(objectStartPosition);
795
+ if (closestDistance > distance) {
796
+ closestDistance = distance;
797
+ closestIndex = j;
798
+ }
799
+ if (closestDistance <= acceptableRadius / 2) {
800
+ break;
801
+ }
802
+ }
803
+ // Normally, we check if there are cursor presses within the group's active time.
804
+ // However, some funky workarounds are used throughout the game for replays, so
805
+ // for the time being we only check for cursor distances across the group.
806
+ const { allOccurrences } = group;
807
+ for (let k = 1; k < allOccurrences.length; ++k) {
808
+ const cursor = allOccurrences[k];
809
+ const prevCursor = allOccurrences[k - 1];
810
+ let distance = Number.POSITIVE_INFINITY;
811
+ const currentPosition = this.getCursorPosition(cursor);
812
+ const prevPosition = this.getCursorPosition(prevCursor);
813
+ switch (cursor.id) {
814
+ case exports.MovementType.up:
815
+ distance =
816
+ prevPosition.getDistance(objectStartPosition);
817
+ break;
818
+ case exports.MovementType.move:
819
+ for (let mSecPassed = Math.max(prevCursor.time, minTimeLimit); mSecPassed <=
820
+ Math.min(cursor.time, maxTimeLimit); ++mSecPassed) {
821
+ const t = (mSecPassed - prevCursor.time) /
822
+ (cursor.time - prevCursor.time);
823
+ const cursorPosition = osuBase.Interpolation.lerp(prevPosition, currentPosition, t);
824
+ distance =
825
+ cursorPosition.getDistance(objectStartPosition);
826
+ if (closestDistance > distance) {
827
+ closestDistance = distance;
828
+ closestIndex = j;
829
+ }
830
+ if (closestDistance <=
831
+ acceptableRadius / 2) {
832
+ break;
833
+ }
834
+ }
835
+ }
836
+ if (closestDistance > distance) {
837
+ closestDistance = distance;
838
+ closestIndex = j;
839
+ }
840
+ if (closestDistance <= acceptableRadius / 2) {
841
+ break;
842
+ }
843
+ }
844
+ }
845
+ closestDistances.push(closestDistance);
846
+ closestGroupIndices.push(closestIndex);
847
+ if (cursorLoopIndices[i] > 0) {
848
+ // Decrement the index. The previous group may also have a role on the next slider.
849
+ --cursorLoopIndices[i];
850
+ }
851
+ }
852
+ const cursorIndex = closestDistances.indexOf(Math.min(...closestDistances));
853
+ const closestDistance = closestDistances[cursorIndex];
854
+ if (closestDistance > acceptableRadius / 2) {
855
+ cheesedDifficultyRatings.push(difficultSlider.difficultyRating);
856
+ continue;
857
+ }
858
+ const group = this.data.cursorMovement[cursorIndex].occurrenceGroups[closestGroupIndices[cursorIndex]];
859
+ let isCheesed = false;
860
+ // Track cursor movement to see if it lands on every tick.
861
+ let occurrenceLoopIndex = 1;
862
+ const { allOccurrences } = group;
863
+ for (let i = 1; i < object.nestedHitObjects.length; ++i) {
864
+ if (isCheesed) {
865
+ break;
866
+ }
867
+ const tickWasHit = objectData.tickset[i - 1];
868
+ if (!tickWasHit) {
869
+ continue;
870
+ }
871
+ const nestedObject = object.nestedHitObjects[i];
872
+ const nestedPosition = nestedObject.stackedPosition;
873
+ while (occurrenceLoopIndex < allOccurrences.length &&
874
+ allOccurrences[occurrenceLoopIndex].time <
875
+ nestedObject.startTime) {
876
+ ++occurrenceLoopIndex;
877
+ }
878
+ if (occurrenceLoopIndex === allOccurrences.length) {
879
+ continue;
880
+ }
881
+ const cursor = allOccurrences[occurrenceLoopIndex];
882
+ const prevCursor = allOccurrences[occurrenceLoopIndex - 1];
883
+ const currentPosition = this.getCursorPosition(cursor);
884
+ const prevPosition = this.getCursorPosition(prevCursor);
885
+ switch (cursor.id) {
886
+ case exports.MovementType.move: {
887
+ // Interpolate cursor position during nested object time.
888
+ const t = (nestedObject.startTime - prevCursor.time) /
889
+ (cursor.time - prevCursor.time);
890
+ const cursorPosition = osuBase.Interpolation.lerp(prevPosition, currentPosition, t);
891
+ const distance = cursorPosition.getDistance(nestedPosition);
892
+ isCheesed = distance > acceptableRadius;
893
+ break;
894
+ }
895
+ case exports.MovementType.up:
896
+ isCheesed =
897
+ prevPosition.getDistance(nestedPosition) >
898
+ acceptableRadius;
899
+ }
900
+ }
901
+ if (isCheesed) {
902
+ cheesedDifficultyRatings.push(difficultSlider.difficultyRating);
903
+ }
904
+ }
905
+ return cheesedDifficultyRatings;
906
+ }
907
+ /**
908
+ * Calculates the slider cheese penalty.
909
+ */
910
+ calculateSliderCheesePenalty(cheesedDifficultyRatings) {
911
+ const summedDifficultyRating = Math.min(1, cheesedDifficultyRatings.reduce((a, v) => a + v, 0));
912
+ return {
913
+ aimPenalty: Math.max(this.difficultyAttributes.sliderFactor, Math.pow(1 -
914
+ summedDifficultyRating *
915
+ this.difficultyAttributes.sliderFactor, 2)),
916
+ flashlightPenalty: Math.max(this.difficultyAttributes.flashlightSliderFactor, Math.pow(1 -
917
+ summedDifficultyRating *
918
+ this.difficultyAttributes.flashlightSliderFactor, 2)),
919
+ visualPenalty: 1,
920
+ };
921
+ }
922
+ getCursorPosition(cursor) {
923
+ if (this.isHardRock) {
924
+ return new osuBase.Vector2(cursor.position.x, osuBase.Playfield.baseSize.y - cursor.position.y);
925
+ }
926
+ return cursor.position;
927
+ }
928
+ }
929
+
712
930
  /******************************************************************************
713
931
  Copyright (c) Microsoft Corporation.
714
932
 
@@ -742,7 +960,7 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
742
960
  };
743
961
 
744
962
  /**
745
- * Utility to check whether relevant sliders in a beatmap are cheesed.
963
+ * Utility to check whether relevant sliders in a beatmap are cheesed for live scores.
746
964
  */
747
965
  class SliderCheeseChecker {
748
966
  /**
@@ -963,7 +1181,7 @@ class SliderCheeseChecker {
963
1181
  }
964
1182
 
965
1183
  /**
966
- * Utility to check whether or not a beatmap is three-fingered for rebalance scores.
1184
+ * Utility to check whether or not a beatmap is three-fingered for live scores.
967
1185
  */
968
1186
  class ThreeFingerChecker {
969
1187
  /**
@@ -1026,9 +1244,6 @@ class ThreeFingerChecker {
1026
1244
  }
1027
1245
  this.getAccurateBreakPoints();
1028
1246
  this.filterCursorInstances();
1029
- if (this.downCursorInstances.filter((v) => v.length > 0).length <= 3) {
1030
- return { is3Finger: false, penalty: 1 };
1031
- }
1032
1247
  this.getBeatmapSections();
1033
1248
  this.calculateNerfFactors();
1034
1249
  const finalPenalty = this.calculateFinalPenalty();
@@ -2129,7 +2344,7 @@ class ReplayAnalyzer {
2129
2344
  /**
2130
2345
  * Gets hit error information of the replay.
2131
2346
  *
2132
- * `analyze()` must be called before calling this.
2347
+ * `analyze()` must be called before calling this, and `beatmap` must be defined.
2133
2348
  */
2134
2349
  calculateHitError() {
2135
2350
  var _a, _b;
@@ -2180,6 +2395,124 @@ class ReplayAnalyzer {
2180
2395
  unstableRate: osuBase.MathUtils.calculateStandardDeviation(accuracies) * 10,
2181
2396
  };
2182
2397
  }
2398
+ /**
2399
+ * Obtains the amount of slider ticks and ends hit in the replay.
2400
+ *
2401
+ * This requires `analyze()` to be called first and `beatmap` to be defined.
2402
+ *
2403
+ * @returns Slider hit information or `null` if the replay has not been analyzed or the beatmap is not defined.
2404
+ */
2405
+ obtainSliderHitInformation() {
2406
+ const { data, beatmap } = this;
2407
+ if (!data || !beatmap) {
2408
+ return null;
2409
+ }
2410
+ const sliderInformation = {
2411
+ tick: { obtained: 0, total: beatmap.hitObjects.sliderTicks },
2412
+ end: { obtained: 0, total: beatmap.hitObjects.sliders },
2413
+ };
2414
+ for (let i = 0; i < data.hitObjectData.length; ++i) {
2415
+ const object = beatmap.hitObjects.objects[i];
2416
+ const objectData = data.hitObjectData[i];
2417
+ if (objectData.result === exports.HitResult.miss ||
2418
+ !(object instanceof osuBase.Slider)) {
2419
+ continue;
2420
+ }
2421
+ // Exclude the head circle.
2422
+ for (let j = 1; j < object.nestedHitObjects.length; ++j) {
2423
+ const nested = object.nestedHitObjects[j];
2424
+ if (!objectData.tickset[j - 1]) {
2425
+ continue;
2426
+ }
2427
+ if (nested instanceof osuBase.SliderTick) {
2428
+ ++sliderInformation.tick.obtained;
2429
+ }
2430
+ else if (nested instanceof osuBase.SliderTail) {
2431
+ ++sliderInformation.end.obtained;
2432
+ }
2433
+ }
2434
+ }
2435
+ return sliderInformation;
2436
+ }
2437
+ /**
2438
+ * Simulates a hit window for the replay.
2439
+ *
2440
+ * This does not account for required spins in a spinner.
2441
+ *
2442
+ * Requires `analyze()` to be called first and `beatmap` to be defined.
2443
+ *
2444
+ * @param hitWindow The hit window to simulate.
2445
+ * @returns The accuracy of the replay based on the hit window, or `null` if the replay has not been analyzed or the beatmap is not defined.
2446
+ */
2447
+ simulateHitWindow(hitWindow) {
2448
+ const { data, beatmap } = this;
2449
+ if (!data || !beatmap) {
2450
+ return null;
2451
+ }
2452
+ const accuracy = new osuBase.Accuracy({ n300: 0, n100: 0, n50: 0, nmiss: 0 });
2453
+ for (let i = 0; i < data.hitObjectData.length; ++i) {
2454
+ const object = beatmap.hitObjects.objects[i];
2455
+ const objectData = data.hitObjectData[i];
2456
+ const hitAccuracy = Math.abs(objectData.accuracy);
2457
+ let { result } = objectData;
2458
+ if (object instanceof osuBase.Circle) {
2459
+ if (hitAccuracy <= hitWindow.greatWindow) {
2460
+ result = exports.HitResult.great;
2461
+ }
2462
+ else if (hitAccuracy <= hitWindow.okWindow) {
2463
+ result = exports.HitResult.good;
2464
+ }
2465
+ else if (hitAccuracy <= hitWindow.mehWindow) {
2466
+ result = exports.HitResult.meh;
2467
+ }
2468
+ else {
2469
+ result = exports.HitResult.miss;
2470
+ }
2471
+ }
2472
+ else if (object instanceof osuBase.Slider) {
2473
+ if (hitAccuracy <=
2474
+ Math.min(hitWindow.mehWindow, object.duration)) {
2475
+ let ticksObtained = 1;
2476
+ for (let j = 1; j < object.nestedHitObjects.length; ++j) {
2477
+ if (objectData.tickset[j - 1]) {
2478
+ ++ticksObtained;
2479
+ }
2480
+ }
2481
+ if (ticksObtained === object.nestedHitObjects.length) {
2482
+ result = exports.HitResult.great;
2483
+ }
2484
+ else if (ticksObtained >=
2485
+ Math.trunc(object.nestedHitObjects.length / 2)) {
2486
+ result = exports.HitResult.good;
2487
+ }
2488
+ else if (ticksObtained > 0) {
2489
+ result = exports.HitResult.meh;
2490
+ }
2491
+ else {
2492
+ result = exports.HitResult.miss;
2493
+ }
2494
+ }
2495
+ else {
2496
+ result = exports.HitResult.miss;
2497
+ }
2498
+ }
2499
+ switch (result) {
2500
+ case exports.HitResult.miss:
2501
+ ++accuracy.nmiss;
2502
+ break;
2503
+ case exports.HitResult.meh:
2504
+ ++accuracy.n50;
2505
+ break;
2506
+ case exports.HitResult.good:
2507
+ ++accuracy.n100;
2508
+ break;
2509
+ case exports.HitResult.great:
2510
+ ++accuracy.n300;
2511
+ break;
2512
+ }
2513
+ }
2514
+ return accuracy;
2515
+ }
2183
2516
  /**
2184
2517
  * Checks if a play is using 3 fingers.
2185
2518
  *
@@ -2227,7 +2560,9 @@ class ReplayAnalyzer {
2227
2560
  return;
2228
2561
  }
2229
2562
  (_a = this.playableBeatmap) !== null && _a !== void 0 ? _a : (this.playableBeatmap = this.constructPlayableBeatmap());
2230
- const sliderCheeseChecker = new SliderCheeseChecker(this.playableBeatmap, this.data, this.difficultyAttributes);
2563
+ const sliderCheeseChecker = this.difficultyAttributes.mode === "rebalance"
2564
+ ? new RebalanceSliderCheeseChecker(this.playableBeatmap, this.data, this.difficultyAttributes)
2565
+ : new SliderCheeseChecker(this.playableBeatmap, this.data, this.difficultyAttributes);
2231
2566
  this.sliderCheesePenalty = sliderCheeseChecker.check();
2232
2567
  this.hasBeenCheckedForSliderCheesing = true;
2233
2568
  }
@@ -2610,6 +2945,7 @@ class ReplayObjectData {
2610
2945
  exports.CursorData = CursorData;
2611
2946
  exports.CursorOccurrence = CursorOccurrence;
2612
2947
  exports.CursorOccurrenceGroup = CursorOccurrenceGroup;
2948
+ exports.RebalanceSliderCheeseChecker = RebalanceSliderCheeseChecker;
2613
2949
  exports.RebalanceThreeFingerChecker = RebalanceThreeFingerChecker;
2614
2950
  exports.ReplayAnalyzer = ReplayAnalyzer;
2615
2951
  exports.ReplayData = ReplayData;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rian8337/osu-droid-replay-analyzer",
3
- "version": "4.0.0-beta.75",
3
+ "version": "4.0.0-beta.77",
4
4
  "description": "A replay analyzer for analyzing osu!droid replay files.",
5
5
  "keywords": [
6
6
  "osu",
@@ -34,9 +34,9 @@
34
34
  "url": "https://github.com/Rian8337/osu-droid-module/issues"
35
35
  },
36
36
  "dependencies": {
37
- "@rian8337/osu-base": "^4.0.0-beta.74",
38
- "@rian8337/osu-difficulty-calculator": "^4.0.0-beta.75",
39
- "@rian8337/osu-rebalance-difficulty-calculator": "^4.0.0-beta.75",
37
+ "@rian8337/osu-base": "^4.0.0-beta.77",
38
+ "@rian8337/osu-difficulty-calculator": "^4.0.0-beta.77",
39
+ "@rian8337/osu-rebalance-difficulty-calculator": "^4.0.0-beta.77",
40
40
  "java-deserialization": "^0.1.0",
41
41
  "unzipper": "^0.10.11"
42
42
  },
@@ -46,5 +46,5 @@
46
46
  "publishConfig": {
47
47
  "access": "public"
48
48
  },
49
- "gitHead": "1e8a43d98546e5071bebe45b3ca40dd496aa7b21"
49
+ "gitHead": "13ad454ea657dbd5821d0857c4da6dc7ab0fe965"
50
50
  }
@@ -1,4 +1,4 @@
1
- import { Vector2, Accuracy, ScoreRank, ModMap, DroidPlayableBeatmap, Beatmap } from '@rian8337/osu-base';
1
+ import { Vector2, Accuracy, ScoreRank, ModMap, DroidPlayableBeatmap, Beatmap, HitWindow } from '@rian8337/osu-base';
2
2
  import { IExtendedDroidDifficultyAttributes as IExtendedDroidDifficultyAttributes$1 } from '@rian8337/osu-difficulty-calculator';
3
3
  import { IExtendedDroidDifficultyAttributes } from '@rian8337/osu-rebalance-difficulty-calculator';
4
4
 
@@ -726,6 +726,75 @@ interface SliderCheeseInformation {
726
726
  visualPenalty: number;
727
727
  }
728
728
 
729
+ /**
730
+ * Utility to check whether relevant sliders in a beatmap are cheesed for rebalance scores..
731
+ */
732
+ declare class RebalanceSliderCheeseChecker {
733
+ /**
734
+ * The beatmap that is being analyzed.
735
+ */
736
+ readonly beatmap: DroidPlayableBeatmap;
737
+ /**
738
+ * The data of the replay.
739
+ */
740
+ readonly data: ReplayData;
741
+ /**
742
+ * The difficulty attributes of the beatmap.
743
+ */
744
+ readonly difficultyAttributes: IExtendedDroidDifficultyAttributes;
745
+ /**
746
+ * The 50 osu!droid hit window of the analyzed beatmap.
747
+ */
748
+ private readonly hitWindow50;
749
+ private readonly isHardRock;
750
+ /**
751
+ * @param beatmap The beatmap to analyze.
752
+ * @param data The data of the replay.
753
+ * @param difficultyAttributes The difficulty attributes of the beatmap.
754
+ */
755
+ constructor(beatmap: DroidPlayableBeatmap, data: ReplayData, difficultyAttributes: IExtendedDroidDifficultyAttributes);
756
+ /**
757
+ * Checks if relevant sliders in the given beatmap was cheesed.
758
+ */
759
+ check(): SliderCheeseInformation;
760
+ /**
761
+ * Checks for sliders that were cheesed.
762
+ */
763
+ private checkSliderCheesing;
764
+ /**
765
+ * Calculates the slider cheese penalty.
766
+ */
767
+ private calculateSliderCheesePenalty;
768
+ private getCursorPosition;
769
+ }
770
+
771
+ /**
772
+ * Represents hit information about sliders in a beatmap.
773
+ */
774
+ interface SliderHitInformation {
775
+ /**
776
+ * Hit information of slider ticks.
777
+ */
778
+ readonly tick: SliderNestedHitObjectInformation;
779
+ /**
780
+ * hit information of slider ends.
781
+ */
782
+ readonly end: SliderNestedHitObjectInformation;
783
+ }
784
+ /**
785
+ * Hit information of a specific nested hit object.
786
+ */
787
+ interface SliderNestedHitObjectInformation {
788
+ /**
789
+ * The amount of the nested hit objects that were obtained.
790
+ */
791
+ obtained: number;
792
+ /**
793
+ * The amount of the nested hit objects in the beatmap.
794
+ */
795
+ readonly total: number;
796
+ }
797
+
729
798
  interface HitErrorInformation {
730
799
  negativeAvg: number;
731
800
  positiveAvg: number;
@@ -822,9 +891,28 @@ declare class ReplayAnalyzer {
822
891
  /**
823
892
  * Gets hit error information of the replay.
824
893
  *
825
- * `analyze()` must be called before calling this.
894
+ * `analyze()` must be called before calling this, and `beatmap` must be defined.
826
895
  */
827
896
  calculateHitError(): HitErrorInformation | null;
897
+ /**
898
+ * Obtains the amount of slider ticks and ends hit in the replay.
899
+ *
900
+ * This requires `analyze()` to be called first and `beatmap` to be defined.
901
+ *
902
+ * @returns Slider hit information or `null` if the replay has not been analyzed or the beatmap is not defined.
903
+ */
904
+ obtainSliderHitInformation(): SliderHitInformation | null;
905
+ /**
906
+ * Simulates a hit window for the replay.
907
+ *
908
+ * This does not account for required spins in a spinner.
909
+ *
910
+ * Requires `analyze()` to be called first and `beatmap` to be defined.
911
+ *
912
+ * @param hitWindow The hit window to simulate.
913
+ * @returns The accuracy of the replay based on the hit window, or `null` if the replay has not been analyzed or the beatmap is not defined.
914
+ */
915
+ simulateHitWindow(hitWindow: HitWindow): Accuracy | null;
828
916
  /**
829
917
  * Checks if a play is using 3 fingers.
830
918
  *
@@ -873,7 +961,7 @@ declare class ReplayAnalyzer {
873
961
  }
874
962
 
875
963
  /**
876
- * Utility to check whether relevant sliders in a beatmap are cheesed.
964
+ * Utility to check whether relevant sliders in a beatmap are cheesed for live scores.
877
965
  */
878
966
  declare class SliderCheeseChecker {
879
967
  /**
@@ -887,7 +975,7 @@ declare class SliderCheeseChecker {
887
975
  /**
888
976
  * The difficulty attributes of the beatmap.
889
977
  */
890
- readonly difficultyAttributes: IExtendedDroidDifficultyAttributes$1 | IExtendedDroidDifficultyAttributes;
978
+ readonly difficultyAttributes: IExtendedDroidDifficultyAttributes$1;
891
979
  /**
892
980
  * The 50 osu!droid hit window of the analyzed beatmap.
893
981
  */
@@ -898,7 +986,7 @@ declare class SliderCheeseChecker {
898
986
  * @param data The data of the replay.
899
987
  * @param difficultyAttributes The difficulty attributes of the beatmap.
900
988
  */
901
- constructor(beatmap: DroidPlayableBeatmap, data: ReplayData, difficultyAttributes: IExtendedDroidDifficultyAttributes$1 | IExtendedDroidDifficultyAttributes);
989
+ constructor(beatmap: DroidPlayableBeatmap, data: ReplayData, difficultyAttributes: IExtendedDroidDifficultyAttributes$1);
902
990
  /**
903
991
  * Checks if relevant sliders in the given beatmap was cheesed.
904
992
  */
@@ -915,7 +1003,7 @@ declare class SliderCheeseChecker {
915
1003
  }
916
1004
 
917
1005
  /**
918
- * Utility to check whether or not a beatmap is three-fingered for rebalance scores.
1006
+ * Utility to check whether or not a beatmap is three-fingered for live scores.
919
1007
  */
920
1008
  declare class ThreeFingerChecker {
921
1009
  /**
@@ -1115,4 +1203,4 @@ declare class TwoHandChecker {
1115
1203
  private getCursorPosition;
1116
1204
  }
1117
1205
 
1118
- export { CursorData, type CursorInformation, CursorOccurrence, CursorOccurrenceGroup, type ExportedReplayJSON, type ExportedReplayJSONDataV1, type ExportedReplayJSONDataV2, type ExportedReplayJSONDataV3, type ExportedReplayJSONV1, type ExportedReplayJSONV2, type ExportedReplayJSONV3, type HitErrorInformation, HitResult, MovementType, RebalanceThreeFingerChecker, ReplayAnalyzer, ReplayData, ReplayObjectData, ReplayV3Data, SliderCheeseChecker, type SliderCheeseInformation, ThreeFingerChecker, type ThreeFingerInformation, TwoHandChecker, type TwoHandInformation };
1206
+ export { CursorData, type CursorInformation, CursorOccurrence, CursorOccurrenceGroup, type ExportedReplayJSON, type ExportedReplayJSONDataV1, type ExportedReplayJSONDataV2, type ExportedReplayJSONDataV3, type ExportedReplayJSONV1, type ExportedReplayJSONV2, type ExportedReplayJSONV3, type HitErrorInformation, HitResult, MovementType, RebalanceSliderCheeseChecker, RebalanceThreeFingerChecker, ReplayAnalyzer, ReplayData, ReplayObjectData, ReplayV3Data, SliderCheeseChecker, type SliderCheeseInformation, type SliderHitInformation, type SliderNestedHitObjectInformation, ThreeFingerChecker, type ThreeFingerInformation, TwoHandChecker, type TwoHandInformation };