@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 +343 -7
- package/package.json +5 -5
- package/typings/index.d.ts +95 -7
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
|
|
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 =
|
|
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.
|
|
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.
|
|
38
|
-
"@rian8337/osu-difficulty-calculator": "^4.0.0-beta.
|
|
39
|
-
"@rian8337/osu-rebalance-difficulty-calculator": "^4.0.0-beta.
|
|
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": "
|
|
49
|
+
"gitHead": "13ad454ea657dbd5821d0857c4da6dc7ab0fe965"
|
|
50
50
|
}
|
package/typings/index.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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 };
|