@rian8337/osu-droid-replay-analyzer 3.2.0 → 4.0.0-beta.0
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 +145 -332
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/typings/index.d.ts +85 -89
package/dist/index.js
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
-
|
|
5
3
|
var osuBase = require('@rian8337/osu-base');
|
|
6
4
|
var osuDifficultyCalculator = require('@rian8337/osu-difficulty-calculator');
|
|
7
5
|
var osuRebalanceDifficultyCalculator = require('@rian8337/osu-rebalance-difficulty-calculator');
|
|
8
6
|
var unzipper = require('unzipper');
|
|
9
7
|
var javaDeserialization = require('java-deserialization');
|
|
10
|
-
var
|
|
8
|
+
var node_stream = require('node:stream');
|
|
11
9
|
|
|
12
|
-
function
|
|
13
|
-
if (e && e.__esModule) return e;
|
|
10
|
+
function _interopNamespaceDefault(e) {
|
|
14
11
|
var n = Object.create(null);
|
|
15
12
|
if (e) {
|
|
16
13
|
Object.keys(e).forEach(function (k) {
|
|
@@ -23,11 +20,11 @@ function _interopNamespace(e) {
|
|
|
23
20
|
}
|
|
24
21
|
});
|
|
25
22
|
}
|
|
26
|
-
n
|
|
23
|
+
n.default = e;
|
|
27
24
|
return Object.freeze(n);
|
|
28
25
|
}
|
|
29
26
|
|
|
30
|
-
var javaDeserialization__namespace = /*#__PURE__*/
|
|
27
|
+
var javaDeserialization__namespace = /*#__PURE__*/_interopNamespaceDefault(javaDeserialization);
|
|
31
28
|
|
|
32
29
|
/**
|
|
33
30
|
* Movement types of a cursor in an osu!droid replay.
|
|
@@ -365,104 +362,22 @@ class ReplayData {
|
|
|
365
362
|
}
|
|
366
363
|
}
|
|
367
364
|
|
|
368
|
-
/**
|
|
369
|
-
* A beatmap section generator that generates beatmap section based on aim/speed strain.
|
|
370
|
-
*/
|
|
371
|
-
class BeatmapSectionGenerator {
|
|
372
|
-
/**
|
|
373
|
-
* Generates `BeatmapSection`s for the specified beatmap.
|
|
374
|
-
*
|
|
375
|
-
* @param calculator The difficulty calculator to generate for.
|
|
376
|
-
* @param minSectionObjectCount The maximum delta time allowed between two beatmap sections.
|
|
377
|
-
* Increasing this number decreases the amount of beatmap sections in general. Note that this value does not account for the speed multiplier of
|
|
378
|
-
* the play, similar to the way replay object data is stored.
|
|
379
|
-
* @param maxSectionDeltaTime The minimum object count required to make a beatmap section. Increasing this number decreases the amount of beatmap sections.
|
|
380
|
-
*/
|
|
381
|
-
static generateSections(calculator, minSectionObjectCount, maxSectionDeltaTime) {
|
|
382
|
-
const beatmapSections = [];
|
|
383
|
-
let firstObjectIndex = 0;
|
|
384
|
-
for (let i = 0; i < calculator.objects.length - 1; ++i) {
|
|
385
|
-
const current = calculator.objects[i];
|
|
386
|
-
const next = calculator.objects[i + 1];
|
|
387
|
-
const realDeltaTime = next.object.startTime - current.object.endTime;
|
|
388
|
-
if (realDeltaTime >= maxSectionDeltaTime) {
|
|
389
|
-
// Ignore sections that don't meet object count requirement.
|
|
390
|
-
if (i - firstObjectIndex < minSectionObjectCount) {
|
|
391
|
-
firstObjectIndex = i + 1;
|
|
392
|
-
continue;
|
|
393
|
-
}
|
|
394
|
-
beatmapSections.push({
|
|
395
|
-
firstObjectIndex,
|
|
396
|
-
lastObjectIndex: i,
|
|
397
|
-
});
|
|
398
|
-
firstObjectIndex = i + 1;
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
// Don't forget to manually add the last beatmap section, which would otherwise be ignored.
|
|
402
|
-
if (calculator.objects.length - firstObjectIndex >
|
|
403
|
-
minSectionObjectCount) {
|
|
404
|
-
beatmapSections.push({
|
|
405
|
-
firstObjectIndex,
|
|
406
|
-
lastObjectIndex: calculator.objects.length - 1,
|
|
407
|
-
});
|
|
408
|
-
}
|
|
409
|
-
return beatmapSections;
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
/**
|
|
414
|
-
* Represents a section of a beatmap.
|
|
415
|
-
*/
|
|
416
|
-
class BeatmapSection {
|
|
417
|
-
/**
|
|
418
|
-
* The index of the first `DifficultyHitObject` of this beatmap section.
|
|
419
|
-
*/
|
|
420
|
-
firstObjectIndex;
|
|
421
|
-
/**
|
|
422
|
-
* The index of the last `DifficultyHitObject` of this beatmap section.
|
|
423
|
-
*/
|
|
424
|
-
lastObjectIndex;
|
|
425
|
-
/**
|
|
426
|
-
* @param firstObjectIndex The index of the first `DifficultyHitObject` of this beatmap section.
|
|
427
|
-
* @param lastObjectIndex The index of the last `DifficultyHitObject` of this beatmap section.
|
|
428
|
-
*/
|
|
429
|
-
constructor(firstObjectIndex, lastObjectIndex) {
|
|
430
|
-
this.firstObjectIndex = firstObjectIndex;
|
|
431
|
-
this.lastObjectIndex = lastObjectIndex;
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
/**
|
|
436
|
-
* A section of a beatmap with extra information used for detecting three-finger usage.
|
|
437
|
-
*/
|
|
438
|
-
class ThreeFingerBeatmapSection extends BeatmapSection {
|
|
439
|
-
/**
|
|
440
|
-
* Whether or not this beatmap section is dragged.
|
|
441
|
-
*/
|
|
442
|
-
isDragged;
|
|
443
|
-
/**
|
|
444
|
-
* The index of the cursor that is dragging this section.
|
|
445
|
-
*/
|
|
446
|
-
dragFingerIndex;
|
|
447
|
-
constructor(values) {
|
|
448
|
-
super(values.firstObjectIndex, values.lastObjectIndex);
|
|
449
|
-
this.isDragged = values.isDragged;
|
|
450
|
-
this.dragFingerIndex = values.dragFingerIndex;
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
365
|
/**
|
|
455
366
|
* Utility to check whether or not a beatmap is three-fingered.
|
|
456
367
|
*/
|
|
457
368
|
class ThreeFingerChecker {
|
|
458
369
|
/**
|
|
459
|
-
* The
|
|
370
|
+
* The beatmap that is being analyzed.
|
|
460
371
|
*/
|
|
461
|
-
|
|
372
|
+
beatmap;
|
|
462
373
|
/**
|
|
463
374
|
* The data of the replay.
|
|
464
375
|
*/
|
|
465
376
|
data;
|
|
377
|
+
/**
|
|
378
|
+
* The difficulty attributes of the beatmap.
|
|
379
|
+
*/
|
|
380
|
+
difficultyAttributes;
|
|
466
381
|
/**
|
|
467
382
|
* The strain threshold to start detecting for 3-fingered section.
|
|
468
383
|
*
|
|
@@ -500,22 +415,7 @@ class ThreeFingerChecker {
|
|
|
500
415
|
*/
|
|
501
416
|
threeFingerRatioThreshold = 0.01;
|
|
502
417
|
/**
|
|
503
|
-
*
|
|
504
|
-
*
|
|
505
|
-
* Increasing this number decreases the amount of beatmap sections in general.
|
|
506
|
-
*
|
|
507
|
-
* Note that this value does not account for the speed multiplier of
|
|
508
|
-
* the play, similar to the way replay object data is stored.
|
|
509
|
-
*/
|
|
510
|
-
maxSectionDeltaTime = 2000;
|
|
511
|
-
/**
|
|
512
|
-
* The minimum object count required to make a beatmap section.
|
|
513
|
-
*
|
|
514
|
-
* Increasing this number decreases the amount of beatmap sections.
|
|
515
|
-
*/
|
|
516
|
-
minSectionObjectCount = 5;
|
|
517
|
-
/**
|
|
518
|
-
* The sections of the beatmap that was cut based on `maxSectionDeltaTime` and `minSectionObjectCount`.
|
|
418
|
+
* Extended sections of the beatmap for drag detection.
|
|
519
419
|
*/
|
|
520
420
|
beatmapSections = [];
|
|
521
421
|
/**
|
|
@@ -543,38 +443,40 @@ class ThreeFingerChecker {
|
|
|
543
443
|
* Each index represents the cursor index.
|
|
544
444
|
*/
|
|
545
445
|
downCursorInstances = [];
|
|
546
|
-
/**
|
|
547
|
-
* All cursor occurrences in the replay.
|
|
548
|
-
*
|
|
549
|
-
* Each index represents the cursor index.
|
|
550
|
-
*/
|
|
551
|
-
allCursorInstances;
|
|
552
446
|
/**
|
|
553
447
|
* Nerf factors from all sections that were three-fingered.
|
|
554
448
|
*/
|
|
555
449
|
nerfFactors = [];
|
|
556
450
|
/**
|
|
557
|
-
*
|
|
451
|
+
* Whether this score uses the Precise mod.
|
|
452
|
+
*/
|
|
453
|
+
isPrecise;
|
|
454
|
+
/**
|
|
455
|
+
* @param beatmap The beatmap to analyze.
|
|
558
456
|
* @param data The data of the replay.
|
|
457
|
+
* @param difficultyAttributes The difficulty attributes of the beatmap.
|
|
559
458
|
*/
|
|
560
|
-
constructor(
|
|
561
|
-
this.
|
|
459
|
+
constructor(beatmap, data, difficultyAttributes) {
|
|
460
|
+
this.beatmap = beatmap;
|
|
562
461
|
this.data = data;
|
|
563
|
-
this.
|
|
462
|
+
this.difficultyAttributes = difficultyAttributes;
|
|
564
463
|
const stats = new osuBase.MapStats({
|
|
565
|
-
od: this.
|
|
566
|
-
mods: this.
|
|
464
|
+
od: this.beatmap.difficulty.od,
|
|
465
|
+
mods: this.difficultyAttributes.mods.filter((m) => m.isApplicableToDroid() &&
|
|
567
466
|
!osuBase.ModUtil.speedChangingMods.some((v) => v.acronym === m.acronym)),
|
|
568
467
|
}).calculate();
|
|
468
|
+
this.isPrecise = this.difficultyAttributes.mods.some((m) => m instanceof osuBase.ModPrecise);
|
|
569
469
|
this.hitWindow = new osuBase.DroidHitWindow(stats.od);
|
|
570
|
-
|
|
571
|
-
|
|
470
|
+
this.strainNoteCount =
|
|
471
|
+
this.difficultyAttributes.possibleThreeFingeredSections.reduce((a, v) => a + v.lastObjectIndex - v.firstObjectIndex + 1, 0);
|
|
572
472
|
}
|
|
573
473
|
/**
|
|
574
474
|
* Checks whether a beatmap is eligible to be detected for 3-finger.
|
|
475
|
+
*
|
|
476
|
+
* @param difficultyAttributes The difficulty attributes of the beatmap.
|
|
575
477
|
*/
|
|
576
|
-
static isEligibleToDetect(
|
|
577
|
-
return
|
|
478
|
+
static isEligibleToDetect(difficultyAttributes) {
|
|
479
|
+
return difficultyAttributes.possibleThreeFingeredSections.length > 0;
|
|
578
480
|
}
|
|
579
481
|
/**
|
|
580
482
|
* Checks if the given beatmap is 3-fingered and also returns the final penalty.
|
|
@@ -597,7 +499,6 @@ class ThreeFingerChecker {
|
|
|
597
499
|
}
|
|
598
500
|
this.getBeatmapSections();
|
|
599
501
|
this.detectDragPlay();
|
|
600
|
-
this.getDetailedBeatmapSections();
|
|
601
502
|
this.preventAccidentalTaps();
|
|
602
503
|
if (this.downCursorInstances.filter((v) => v.length > 0).length <= 3) {
|
|
603
504
|
return { is3Finger: false, penalty: 1 };
|
|
@@ -613,50 +514,43 @@ class ThreeFingerChecker {
|
|
|
613
514
|
* start of the hitobject before it and do not end right at the first hitobject after it.
|
|
614
515
|
*/
|
|
615
516
|
getAccurateBreakPoints() {
|
|
616
|
-
const objects = this.
|
|
517
|
+
const { objects } = this.beatmap.hitObjects;
|
|
617
518
|
const objectData = this.data.hitObjectData;
|
|
618
|
-
const
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
let timeBefore = objects[beforeIndex].object.endTime;
|
|
519
|
+
for (const breakPoint of this.beatmap.events.breaks) {
|
|
520
|
+
const beforeIndex = osuBase.MathUtils.clamp(objects.findIndex((o) => o.endTime >= breakPoint.startTime) - 1, 0, objects.length - 2);
|
|
521
|
+
let timeBefore = objects[beforeIndex].endTime;
|
|
622
522
|
// For sliders and spinners, automatically set hit window length to be as lenient as possible.
|
|
623
|
-
let beforeIndexHitWindowLength = this.hitWindow.hitWindowFor50(isPrecise);
|
|
523
|
+
let beforeIndexHitWindowLength = this.hitWindow.hitWindowFor50(this.isPrecise);
|
|
624
524
|
switch (objectData[beforeIndex].result) {
|
|
625
525
|
case exports.HitResult.great:
|
|
626
|
-
beforeIndexHitWindowLength =
|
|
627
|
-
this.hitWindow.hitWindowFor300(isPrecise);
|
|
526
|
+
beforeIndexHitWindowLength = this.hitWindow.hitWindowFor300(this.isPrecise);
|
|
628
527
|
break;
|
|
629
528
|
case exports.HitResult.good:
|
|
630
|
-
beforeIndexHitWindowLength =
|
|
631
|
-
this.hitWindow.hitWindowFor100(isPrecise);
|
|
529
|
+
beforeIndexHitWindowLength = this.hitWindow.hitWindowFor100(this.isPrecise);
|
|
632
530
|
break;
|
|
633
531
|
default:
|
|
634
|
-
beforeIndexHitWindowLength =
|
|
635
|
-
this.hitWindow.hitWindowFor50(isPrecise);
|
|
532
|
+
beforeIndexHitWindowLength = this.hitWindow.hitWindowFor50(this.isPrecise);
|
|
636
533
|
}
|
|
637
534
|
timeBefore += beforeIndexHitWindowLength;
|
|
638
535
|
const afterIndex = beforeIndex + 1;
|
|
639
|
-
let timeAfter = objects[afterIndex].
|
|
536
|
+
let timeAfter = objects[afterIndex].startTime;
|
|
640
537
|
// For sliders and spinners, automatically set hit window length to be as lenient as possible.
|
|
641
|
-
let afterIndexHitWindowLength = this.hitWindow.hitWindowFor50(isPrecise);
|
|
538
|
+
let afterIndexHitWindowLength = this.hitWindow.hitWindowFor50(this.isPrecise);
|
|
642
539
|
switch (objectData[afterIndex].result) {
|
|
643
540
|
case exports.HitResult.great:
|
|
644
|
-
afterIndexHitWindowLength =
|
|
645
|
-
this.hitWindow.hitWindowFor300(isPrecise);
|
|
541
|
+
afterIndexHitWindowLength = this.hitWindow.hitWindowFor300(this.isPrecise);
|
|
646
542
|
break;
|
|
647
543
|
case exports.HitResult.good:
|
|
648
|
-
afterIndexHitWindowLength =
|
|
649
|
-
this.hitWindow.hitWindowFor100(isPrecise);
|
|
544
|
+
afterIndexHitWindowLength = this.hitWindow.hitWindowFor100(this.isPrecise);
|
|
650
545
|
break;
|
|
651
546
|
default:
|
|
652
|
-
afterIndexHitWindowLength =
|
|
653
|
-
this.hitWindow.hitWindowFor50(isPrecise);
|
|
547
|
+
afterIndexHitWindowLength = this.hitWindow.hitWindowFor50(this.isPrecise);
|
|
654
548
|
}
|
|
655
549
|
timeAfter += afterIndexHitWindowLength;
|
|
656
|
-
this.breakPointAccurateTimes.push({
|
|
550
|
+
this.breakPointAccurateTimes.push(new osuBase.BreakPoint({
|
|
657
551
|
startTime: timeBefore,
|
|
658
552
|
endTime: timeAfter,
|
|
659
|
-
});
|
|
553
|
+
}));
|
|
660
554
|
}
|
|
661
555
|
}
|
|
662
556
|
/**
|
|
@@ -665,50 +559,43 @@ class ThreeFingerChecker {
|
|
|
665
559
|
* This also filters cursors that are in break period or happen before start/after end of the beatmap.
|
|
666
560
|
*/
|
|
667
561
|
filterCursorInstances() {
|
|
668
|
-
const objects = this.
|
|
562
|
+
const { objects } = this.beatmap.hitObjects;
|
|
669
563
|
const objectData = this.data.hitObjectData;
|
|
670
564
|
const firstObjectResult = objectData[0].result;
|
|
671
565
|
const lastObjectResult = objectData.at(-1).result;
|
|
672
|
-
const isPrecise = this.calculator.mods.some((m) => m instanceof osuBase.ModPrecise);
|
|
673
566
|
// For sliders, automatically set hit window length to be as lenient as possible.
|
|
674
|
-
let firstObjectHitWindow = this.hitWindow.hitWindowFor50(isPrecise);
|
|
675
|
-
if (objects[0]
|
|
567
|
+
let firstObjectHitWindow = this.hitWindow.hitWindowFor50(this.isPrecise);
|
|
568
|
+
if (objects[0] instanceof osuBase.Circle) {
|
|
676
569
|
switch (firstObjectResult) {
|
|
677
570
|
case exports.HitResult.great:
|
|
678
|
-
firstObjectHitWindow =
|
|
679
|
-
this.hitWindow.hitWindowFor300(isPrecise);
|
|
571
|
+
firstObjectHitWindow = this.hitWindow.hitWindowFor300(this.isPrecise);
|
|
680
572
|
break;
|
|
681
573
|
case exports.HitResult.good:
|
|
682
|
-
firstObjectHitWindow =
|
|
683
|
-
this.hitWindow.hitWindowFor100(isPrecise);
|
|
574
|
+
firstObjectHitWindow = this.hitWindow.hitWindowFor100(this.isPrecise);
|
|
684
575
|
break;
|
|
685
576
|
default:
|
|
686
|
-
firstObjectHitWindow =
|
|
687
|
-
this.hitWindow.hitWindowFor50(isPrecise);
|
|
577
|
+
firstObjectHitWindow = this.hitWindow.hitWindowFor50(this.isPrecise);
|
|
688
578
|
}
|
|
689
579
|
}
|
|
690
580
|
// For sliders, automatically set hit window length to be as lenient as possible.
|
|
691
|
-
let lastObjectHitWindow = this.hitWindow.hitWindowFor50(isPrecise);
|
|
692
|
-
if (objects.at(-1)
|
|
581
|
+
let lastObjectHitWindow = this.hitWindow.hitWindowFor50(this.isPrecise);
|
|
582
|
+
if (objects.at(-1) instanceof osuBase.Circle) {
|
|
693
583
|
switch (lastObjectResult) {
|
|
694
584
|
case exports.HitResult.great:
|
|
695
|
-
lastObjectHitWindow =
|
|
696
|
-
this.hitWindow.hitWindowFor300(isPrecise);
|
|
585
|
+
lastObjectHitWindow = this.hitWindow.hitWindowFor300(this.isPrecise);
|
|
697
586
|
break;
|
|
698
587
|
case exports.HitResult.good:
|
|
699
|
-
lastObjectHitWindow =
|
|
700
|
-
this.hitWindow.hitWindowFor100(isPrecise);
|
|
588
|
+
lastObjectHitWindow = this.hitWindow.hitWindowFor100(this.isPrecise);
|
|
701
589
|
break;
|
|
702
590
|
default:
|
|
703
|
-
lastObjectHitWindow =
|
|
704
|
-
this.hitWindow.hitWindowFor50(isPrecise);
|
|
591
|
+
lastObjectHitWindow = this.hitWindow.hitWindowFor50(this.isPrecise);
|
|
705
592
|
}
|
|
706
593
|
}
|
|
707
594
|
// These hit time uses hit window length as threshold.
|
|
708
595
|
// This is because cursors aren't recorded exactly at hit time,
|
|
709
596
|
// probably due to the game's behavior.
|
|
710
|
-
const firstObjectHitTime = objects[0].
|
|
711
|
-
const lastObjectHitTime = objects.at(-1).
|
|
597
|
+
const firstObjectHitTime = objects[0].startTime - firstObjectHitWindow;
|
|
598
|
+
const lastObjectHitTime = objects.at(-1).startTime + lastObjectHitWindow;
|
|
712
599
|
for (let i = 0; i < this.data.cursorMovement.length; ++i) {
|
|
713
600
|
const cursorInstance = this.data.cursorMovement[i];
|
|
714
601
|
const validOccurrences = [];
|
|
@@ -732,14 +619,13 @@ class ThreeFingerChecker {
|
|
|
732
619
|
* detect dragged sections and improve detection speed.
|
|
733
620
|
*/
|
|
734
621
|
getBeatmapSections() {
|
|
735
|
-
const
|
|
736
|
-
|
|
737
|
-
this.beatmapSections.push(
|
|
738
|
-
|
|
739
|
-
lastObjectIndex: beatmapSection.lastObjectIndex,
|
|
622
|
+
for (const section of this.difficultyAttributes
|
|
623
|
+
.possibleThreeFingeredSections) {
|
|
624
|
+
this.beatmapSections.push({
|
|
625
|
+
...section,
|
|
740
626
|
isDragged: false,
|
|
741
627
|
dragFingerIndex: -1,
|
|
742
|
-
})
|
|
628
|
+
});
|
|
743
629
|
}
|
|
744
630
|
}
|
|
745
631
|
/**
|
|
@@ -760,48 +646,41 @@ class ThreeFingerChecker {
|
|
|
760
646
|
* @param section The section to check.
|
|
761
647
|
*/
|
|
762
648
|
checkDrag(section) {
|
|
763
|
-
const objects = this.
|
|
649
|
+
const { objects } = this.beatmap.hitObjects;
|
|
764
650
|
const objectData = this.data.hitObjectData;
|
|
765
|
-
const isPrecise = this.calculator.mods.some((m) => m instanceof osuBase.ModPrecise);
|
|
766
651
|
const firstObject = objects[section.firstObjectIndex];
|
|
767
652
|
const lastObject = objects[section.lastObjectIndex];
|
|
768
|
-
let firstObjectMinHitTime = firstObject.
|
|
769
|
-
if (firstObject
|
|
653
|
+
let firstObjectMinHitTime = firstObject.startTime;
|
|
654
|
+
if (firstObject instanceof osuBase.Circle) {
|
|
770
655
|
switch (objectData[section.firstObjectIndex].result) {
|
|
771
656
|
case exports.HitResult.great:
|
|
772
|
-
firstObjectMinHitTime -=
|
|
773
|
-
this.hitWindow.hitWindowFor300(isPrecise);
|
|
657
|
+
firstObjectMinHitTime -= this.hitWindow.hitWindowFor300(this.isPrecise);
|
|
774
658
|
break;
|
|
775
659
|
case exports.HitResult.good:
|
|
776
|
-
firstObjectMinHitTime -=
|
|
777
|
-
this.hitWindow.hitWindowFor100(isPrecise);
|
|
660
|
+
firstObjectMinHitTime -= this.hitWindow.hitWindowFor100(this.isPrecise);
|
|
778
661
|
break;
|
|
779
662
|
default:
|
|
780
|
-
firstObjectMinHitTime -=
|
|
781
|
-
this.hitWindow.hitWindowFor50(isPrecise);
|
|
663
|
+
firstObjectMinHitTime -= this.hitWindow.hitWindowFor50(this.isPrecise);
|
|
782
664
|
}
|
|
783
665
|
}
|
|
784
666
|
else {
|
|
785
|
-
firstObjectMinHitTime -= this.hitWindow.hitWindowFor50(isPrecise);
|
|
667
|
+
firstObjectMinHitTime -= this.hitWindow.hitWindowFor50(this.isPrecise);
|
|
786
668
|
}
|
|
787
|
-
let lastObjectMaxHitTime = lastObject.
|
|
788
|
-
if (lastObject
|
|
669
|
+
let lastObjectMaxHitTime = lastObject.startTime;
|
|
670
|
+
if (lastObject instanceof osuBase.Circle) {
|
|
789
671
|
switch (objectData[section.lastObjectIndex].result) {
|
|
790
672
|
case exports.HitResult.great:
|
|
791
|
-
lastObjectMaxHitTime +=
|
|
792
|
-
this.hitWindow.hitWindowFor300(isPrecise);
|
|
673
|
+
lastObjectMaxHitTime += this.hitWindow.hitWindowFor300(this.isPrecise);
|
|
793
674
|
break;
|
|
794
675
|
case exports.HitResult.good:
|
|
795
|
-
lastObjectMaxHitTime +=
|
|
796
|
-
this.hitWindow.hitWindowFor100(isPrecise);
|
|
676
|
+
lastObjectMaxHitTime += this.hitWindow.hitWindowFor100(this.isPrecise);
|
|
797
677
|
break;
|
|
798
678
|
default:
|
|
799
|
-
lastObjectMaxHitTime +=
|
|
800
|
-
this.hitWindow.hitWindowFor50(isPrecise);
|
|
679
|
+
lastObjectMaxHitTime += this.hitWindow.hitWindowFor50(this.isPrecise);
|
|
801
680
|
}
|
|
802
681
|
}
|
|
803
682
|
else {
|
|
804
|
-
lastObjectMaxHitTime += this.hitWindow.hitWindowFor50(isPrecise);
|
|
683
|
+
lastObjectMaxHitTime += this.hitWindow.hitWindowFor50(this.isPrecise);
|
|
805
684
|
}
|
|
806
685
|
// Since there may be more than 1 cursor instance index,
|
|
807
686
|
// we check which cursor instance follows hitobjects all over.
|
|
@@ -834,22 +713,21 @@ class ThreeFingerChecker {
|
|
|
834
713
|
* @param cursorIndexes The indexes of the cursor instance that has at least an occurrence in the section.
|
|
835
714
|
*/
|
|
836
715
|
findDragIndex(sectionObjects, sectionReplayObjectData, cursorIndexes) {
|
|
837
|
-
const
|
|
838
|
-
const hitWindow50 = this.hitWindow.hitWindowFor50(isPrecise);
|
|
716
|
+
const hitWindow50 = this.hitWindow.hitWindowFor50(this.isPrecise);
|
|
839
717
|
for (let i = 0; i < sectionObjects.length && cursorIndexes.every((v) => v !== -1); ++i) {
|
|
840
718
|
const object = sectionObjects[i];
|
|
841
719
|
const objectData = sectionReplayObjectData[i];
|
|
842
|
-
if (object
|
|
720
|
+
if (object instanceof osuBase.Spinner ||
|
|
843
721
|
objectData.result === exports.HitResult.miss) {
|
|
844
722
|
continue;
|
|
845
723
|
}
|
|
846
724
|
// Exclude sliderbreaks.
|
|
847
|
-
if (object
|
|
725
|
+
if (object instanceof osuBase.Slider &&
|
|
848
726
|
objectData.accuracy === Math.floor(hitWindow50) + 13) {
|
|
849
727
|
continue;
|
|
850
728
|
}
|
|
851
|
-
const objectPosition = object.
|
|
852
|
-
const hitTime = object.
|
|
729
|
+
const objectPosition = object.getStackedPosition(osuBase.Modes.droid);
|
|
730
|
+
const hitTime = object.startTime + objectData.accuracy;
|
|
853
731
|
// Observe the cursor position at the object's hit time.
|
|
854
732
|
for (let j = 0; j < cursorIndexes.length; ++j) {
|
|
855
733
|
if (cursorIndexes[j] === -1) {
|
|
@@ -864,30 +742,28 @@ class ThreeFingerChecker {
|
|
|
864
742
|
for (let k = 1; k < cursors.length; ++k) {
|
|
865
743
|
const cursor = cursors[k];
|
|
866
744
|
const prevCursor = cursors[k - 1];
|
|
867
|
-
if (prevCursor.time <
|
|
868
|
-
object.object.startTime - hitWindow50) {
|
|
745
|
+
if (prevCursor.time < object.startTime - hitWindow50) {
|
|
869
746
|
continue;
|
|
870
747
|
}
|
|
871
|
-
if (prevCursor.time >
|
|
872
|
-
object.object.startTime + hitWindow50) {
|
|
748
|
+
if (prevCursor.time > object.startTime + hitWindow50) {
|
|
873
749
|
break;
|
|
874
750
|
}
|
|
875
751
|
let isInObject = false;
|
|
876
752
|
switch (cursor.id) {
|
|
877
753
|
case exports.MovementType.up:
|
|
878
754
|
isInObject =
|
|
879
|
-
prevCursor.position.getDistance(objectPosition) <= object.
|
|
755
|
+
prevCursor.position.getDistance(objectPosition) <= object.getRadius(osuBase.Modes.droid);
|
|
880
756
|
break;
|
|
881
757
|
case exports.MovementType.move:
|
|
882
758
|
// Interpolate movement.
|
|
883
759
|
for (let mSecPassed = prevCursor.time; !isInObject &&
|
|
884
760
|
mSecPassed <=
|
|
885
|
-
Math.min(cursor.time, object.
|
|
761
|
+
Math.min(cursor.time, object.startTime + hitWindow50); ++mSecPassed) {
|
|
886
762
|
const t = (mSecPassed - prevCursor.time) /
|
|
887
763
|
(cursor.time - prevCursor.time);
|
|
888
764
|
const cursorPosition = new osuBase.Vector2(osuBase.Interpolation.lerp(prevCursor.position.x, cursor.position.x, t), osuBase.Interpolation.lerp(prevCursor.position.y, cursor.position.y, t));
|
|
889
765
|
isInObject =
|
|
890
|
-
objectPosition.getDistance(cursorPosition) <= object.
|
|
766
|
+
objectPosition.getDistance(cursorPosition) <= object.getRadius(osuBase.Modes.droid);
|
|
891
767
|
}
|
|
892
768
|
}
|
|
893
769
|
if (!isInObject) {
|
|
@@ -898,51 +774,6 @@ class ThreeFingerChecker {
|
|
|
898
774
|
}
|
|
899
775
|
return cursorIndexes.find((v) => v !== -1) ?? -1;
|
|
900
776
|
}
|
|
901
|
-
/**
|
|
902
|
-
* Redivides the beatmap into sections.
|
|
903
|
-
*
|
|
904
|
-
* The result will be used to detect for three-fingered
|
|
905
|
-
* sections.
|
|
906
|
-
*/
|
|
907
|
-
getDetailedBeatmapSections() {
|
|
908
|
-
const objects = this.calculator.objects;
|
|
909
|
-
const newBeatmapSections = [];
|
|
910
|
-
for (const beatmapSection of this.beatmapSections) {
|
|
911
|
-
let inSpeedSection = false;
|
|
912
|
-
let newFirstObjectIndex = beatmapSection.firstObjectIndex;
|
|
913
|
-
for (let i = beatmapSection.firstObjectIndex; i <= beatmapSection.lastObjectIndex; ++i) {
|
|
914
|
-
if (!inSpeedSection &&
|
|
915
|
-
objects[i].originalTapStrain >=
|
|
916
|
-
ThreeFingerChecker.strainThreshold) {
|
|
917
|
-
inSpeedSection = true;
|
|
918
|
-
newFirstObjectIndex = i;
|
|
919
|
-
continue;
|
|
920
|
-
}
|
|
921
|
-
if (inSpeedSection &&
|
|
922
|
-
objects[i].originalTapStrain <
|
|
923
|
-
ThreeFingerChecker.strainThreshold) {
|
|
924
|
-
inSpeedSection = false;
|
|
925
|
-
newBeatmapSections.push({
|
|
926
|
-
firstObjectIndex: newFirstObjectIndex,
|
|
927
|
-
lastObjectIndex: i,
|
|
928
|
-
isDragged: beatmapSection.isDragged,
|
|
929
|
-
dragFingerIndex: beatmapSection.dragFingerIndex,
|
|
930
|
-
});
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
// Don't forget to manually add the last beatmap section, which would otherwise be ignored.
|
|
934
|
-
if (inSpeedSection) {
|
|
935
|
-
newBeatmapSections.push({
|
|
936
|
-
firstObjectIndex: newFirstObjectIndex,
|
|
937
|
-
lastObjectIndex: beatmapSection.lastObjectIndex,
|
|
938
|
-
isDragged: beatmapSection.isDragged,
|
|
939
|
-
dragFingerIndex: beatmapSection.dragFingerIndex,
|
|
940
|
-
});
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
this.beatmapSections.length = 0;
|
|
944
|
-
this.beatmapSections.push(...newBeatmapSections);
|
|
945
|
-
}
|
|
946
777
|
/**
|
|
947
778
|
* Attempts to prevent accidental taps from being flagged.
|
|
948
779
|
*
|
|
@@ -955,7 +786,7 @@ class ThreeFingerChecker {
|
|
|
955
786
|
if (filledCursorAmount <= 3) {
|
|
956
787
|
return;
|
|
957
788
|
}
|
|
958
|
-
const objects = this.
|
|
789
|
+
const { objects } = this.beatmap.hitObjects;
|
|
959
790
|
const totalCursorAmount = this.downCursorInstances.reduce((acc, value) => acc + value.length, 0);
|
|
960
791
|
for (let i = 0; i < this.downCursorInstances.length; ++i) {
|
|
961
792
|
if (filledCursorAmount <= 3) {
|
|
@@ -979,23 +810,22 @@ class ThreeFingerChecker {
|
|
|
979
810
|
* This check will ignore all objects with speed strain below `strainThreshold`.
|
|
980
811
|
*/
|
|
981
812
|
calculateNerfFactors() {
|
|
982
|
-
const objects = this.
|
|
813
|
+
const { objects } = this.beatmap.hitObjects;
|
|
983
814
|
const objectData = this.data.hitObjectData;
|
|
984
|
-
const isPrecise = this.data.convertedMods.some((m) => m instanceof osuBase.ModPrecise);
|
|
985
815
|
// We only filter cursor instances that are above the strain threshold.
|
|
986
816
|
// This minimalizes the amount of cursor instances to analyze.
|
|
987
817
|
for (const beatmapSection of this.beatmapSections) {
|
|
988
818
|
const dragIndex = beatmapSection.dragFingerIndex;
|
|
989
|
-
const startTime = objects[beatmapSection.firstObjectIndex].
|
|
819
|
+
const startTime = objects[beatmapSection.firstObjectIndex].startTime +
|
|
990
820
|
(objectData[beatmapSection.firstObjectIndex].result !==
|
|
991
821
|
exports.HitResult.miss
|
|
992
822
|
? objectData[beatmapSection.firstObjectIndex].accuracy
|
|
993
|
-
: -this.hitWindow.hitWindowFor50(isPrecise));
|
|
994
|
-
const endTime = objects[beatmapSection.lastObjectIndex].
|
|
823
|
+
: -this.hitWindow.hitWindowFor50(this.isPrecise));
|
|
824
|
+
const endTime = objects[beatmapSection.lastObjectIndex].endTime +
|
|
995
825
|
(objectData[beatmapSection.lastObjectIndex].result !==
|
|
996
826
|
exports.HitResult.miss
|
|
997
827
|
? objectData[beatmapSection.lastObjectIndex].accuracy
|
|
998
|
-
: this.hitWindow.hitWindowFor50(isPrecise));
|
|
828
|
+
: this.hitWindow.hitWindowFor50(this.isPrecise));
|
|
999
829
|
const cursorAmounts = [];
|
|
1000
830
|
const cursorVectorTimes = [];
|
|
1001
831
|
for (let i = 0; i < this.downCursorInstances.length; ++i) {
|
|
@@ -1071,11 +901,6 @@ class ThreeFingerChecker {
|
|
|
1071
901
|
const objectCount = beatmapSection.lastObjectIndex -
|
|
1072
902
|
beatmapSection.firstObjectIndex +
|
|
1073
903
|
1;
|
|
1074
|
-
const strainFactor = Math.pow(objects
|
|
1075
|
-
.slice(beatmapSection.firstObjectIndex, beatmapSection.lastObjectIndex)
|
|
1076
|
-
.reduce((acc, value) => acc +
|
|
1077
|
-
value.originalTapStrain /
|
|
1078
|
-
ThreeFingerChecker.strainThreshold, 0), 0.75);
|
|
1079
904
|
// We can ignore the first 3 (2 for drag) filled cursor instances
|
|
1080
905
|
// since they are guaranteed not 3 finger.
|
|
1081
906
|
const threeFingerCursorAmounts = cursorAmounts
|
|
@@ -1097,7 +922,7 @@ class ThreeFingerChecker {
|
|
|
1097
922
|
// Length factor applies more penalty if there are more 3-fingered object.
|
|
1098
923
|
const lengthFactor = 1 + Math.pow(objectCount / this.strainNoteCount, 1.2);
|
|
1099
924
|
this.nerfFactors.push({
|
|
1100
|
-
strainFactor: Math.max(1,
|
|
925
|
+
strainFactor: Math.max(1, beatmapSection.sumStrain),
|
|
1101
926
|
fingerFactor,
|
|
1102
927
|
lengthFactor,
|
|
1103
928
|
});
|
|
@@ -1797,48 +1622,46 @@ class TwoHandChecker {
|
|
|
1797
1622
|
*/
|
|
1798
1623
|
class SliderCheeseChecker {
|
|
1799
1624
|
/**
|
|
1800
|
-
* The
|
|
1625
|
+
* The beatmap that is being analyzed.
|
|
1801
1626
|
*/
|
|
1802
|
-
|
|
1627
|
+
beatmap;
|
|
1803
1628
|
/**
|
|
1804
1629
|
* The data of the replay.
|
|
1805
1630
|
*/
|
|
1806
1631
|
data;
|
|
1807
1632
|
/**
|
|
1808
|
-
* The
|
|
1633
|
+
* The difficulty attributes of the beatmap.
|
|
1809
1634
|
*/
|
|
1810
|
-
|
|
1635
|
+
difficultyAttributes;
|
|
1811
1636
|
/**
|
|
1812
1637
|
* The 50 osu!droid hit window of the analyzed beatmap.
|
|
1813
1638
|
*/
|
|
1814
1639
|
hitWindow50;
|
|
1815
1640
|
/**
|
|
1816
|
-
* The
|
|
1641
|
+
* The difficulty ratings of sliders that were cheesed.
|
|
1817
1642
|
*/
|
|
1818
|
-
|
|
1643
|
+
cheesedDifficultyRatings = [];
|
|
1819
1644
|
/**
|
|
1820
|
-
* @param
|
|
1645
|
+
* @param beatmap The beatmap to analyze.
|
|
1821
1646
|
* @param data The data of the replay.
|
|
1647
|
+
* @param difficultyAttributes The difficulty attributes of the beatmap.
|
|
1822
1648
|
*/
|
|
1823
|
-
constructor(
|
|
1824
|
-
this.
|
|
1649
|
+
constructor(beatmap, data, difficultyAttributes) {
|
|
1650
|
+
this.beatmap = beatmap;
|
|
1825
1651
|
this.data = data;
|
|
1652
|
+
this.difficultyAttributes = difficultyAttributes;
|
|
1826
1653
|
const stats = new osuBase.MapStats({
|
|
1827
|
-
od: this.
|
|
1828
|
-
mods: this.
|
|
1654
|
+
od: this.beatmap.difficulty.od,
|
|
1655
|
+
mods: this.difficultyAttributes.mods.filter((m) => m.isApplicableToDroid() &&
|
|
1829
1656
|
!osuBase.ModUtil.speedChangingMods.some((v) => v.acronym === m.acronym)),
|
|
1830
1657
|
}).calculate();
|
|
1831
|
-
this.
|
|
1832
|
-
this.hitWindow50 = this.hitWindow.hitWindowFor50(calculator.mods.some((m) => m instanceof osuBase.ModPrecise));
|
|
1658
|
+
this.hitWindow50 = new osuBase.DroidHitWindow(stats.od).hitWindowFor50(this.difficultyAttributes.mods.some((m) => m instanceof osuBase.ModPrecise));
|
|
1833
1659
|
}
|
|
1834
1660
|
/**
|
|
1835
1661
|
* Checks if relevant sliders in the given beatmap was cheesed.
|
|
1836
|
-
*
|
|
1837
|
-
* Returns a number that can be passed to a `DroidPerformanceCalculator`
|
|
1838
|
-
* to alter the aim performance value.
|
|
1839
1662
|
*/
|
|
1840
1663
|
check() {
|
|
1841
|
-
if (this.
|
|
1664
|
+
if (this.difficultyAttributes.difficultSliders.length === 0) {
|
|
1842
1665
|
return {
|
|
1843
1666
|
aimPenalty: 1,
|
|
1844
1667
|
flashlightPenalty: 1,
|
|
@@ -1854,17 +1677,15 @@ class SliderCheeseChecker {
|
|
|
1854
1677
|
checkSliderCheesing() {
|
|
1855
1678
|
// Current loop indexes are stored for efficiency.
|
|
1856
1679
|
const cursorLoopIndexes = osuBase.Utils.initializeArray(10, 0);
|
|
1857
|
-
const acceptableRadius = this.
|
|
1858
|
-
for (
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
const { object } = diffObject;
|
|
1862
|
-
if (diffObject.travelDistance === 0 ||
|
|
1863
|
-
!(object instanceof osuBase.Slider)) {
|
|
1680
|
+
const acceptableRadius = this.beatmap.hitObjects.objects[0].getRadius(osuBase.Modes.droid) * 2;
|
|
1681
|
+
for (const difficultSlider of this.difficultyAttributes
|
|
1682
|
+
.difficultSliders) {
|
|
1683
|
+
if (difficultSlider.index >= this.data.hitObjectData.length) {
|
|
1864
1684
|
continue;
|
|
1865
1685
|
}
|
|
1686
|
+
const object = (this.beatmap.hitObjects.objects[difficultSlider.index]);
|
|
1866
1687
|
const objectStartPosition = object.getStackedPosition(osuBase.Modes.droid);
|
|
1867
|
-
const objectData = this.data.hitObjectData[
|
|
1688
|
+
const objectData = this.data.hitObjectData[difficultSlider.index];
|
|
1868
1689
|
// If a slider break occurs, we disregard the check for that slider.
|
|
1869
1690
|
if (objectData.accuracy === Math.floor(this.hitWindow50) + 13) {
|
|
1870
1691
|
continue;
|
|
@@ -1938,7 +1759,7 @@ class SliderCheeseChecker {
|
|
|
1938
1759
|
}
|
|
1939
1760
|
}
|
|
1940
1761
|
if (isCheesed) {
|
|
1941
|
-
this.
|
|
1762
|
+
this.cheesedDifficultyRatings.push(difficultSlider.difficultyRating);
|
|
1942
1763
|
}
|
|
1943
1764
|
}
|
|
1944
1765
|
}
|
|
@@ -1946,32 +1767,16 @@ class SliderCheeseChecker {
|
|
|
1946
1767
|
* Calculates the slider cheese penalty.
|
|
1947
1768
|
*/
|
|
1948
1769
|
calculateSliderCheesePenalty() {
|
|
1949
|
-
|
|
1950
|
-
return {
|
|
1951
|
-
aimPenalty: 1,
|
|
1952
|
-
flashlightPenalty: 1,
|
|
1953
|
-
visualPenalty: 1,
|
|
1954
|
-
};
|
|
1955
|
-
}
|
|
1956
|
-
let calculator;
|
|
1957
|
-
if (this.calculator instanceof osuDifficultyCalculator.DroidDifficultyCalculator) {
|
|
1958
|
-
calculator = new osuDifficultyCalculator.DroidDifficultyCalculator(this.calculator.beatmap);
|
|
1959
|
-
}
|
|
1960
|
-
else {
|
|
1961
|
-
calculator = new osuRebalanceDifficultyCalculator.DroidDifficultyCalculator(this.calculator.beatmap);
|
|
1962
|
-
}
|
|
1963
|
-
Object.assign(calculator.attributes, this.calculator.attributes);
|
|
1964
|
-
calculator.mods = this.calculator.mods;
|
|
1965
|
-
calculator.stats = this.calculator.stats;
|
|
1966
|
-
calculator.generateDifficultyHitObjects();
|
|
1967
|
-
for (const index of this.cheesedObjectIndexes) {
|
|
1968
|
-
calculator.objects[index].travelDistance = 0;
|
|
1969
|
-
}
|
|
1970
|
-
calculator.calculateAll();
|
|
1770
|
+
const summedDifficultyRating = this.cheesedDifficultyRatings.reduce((a, v) => a + v, 0);
|
|
1971
1771
|
return {
|
|
1972
|
-
aimPenalty:
|
|
1973
|
-
|
|
1974
|
-
|
|
1772
|
+
aimPenalty: 1 -
|
|
1773
|
+
summedDifficultyRating * this.difficultyAttributes.sliderFactor,
|
|
1774
|
+
flashlightPenalty: 1 -
|
|
1775
|
+
summedDifficultyRating *
|
|
1776
|
+
this.difficultyAttributes.flashlightSliderFactor,
|
|
1777
|
+
visualPenalty: 1 -
|
|
1778
|
+
summedDifficultyRating *
|
|
1779
|
+
this.difficultyAttributes.visualSliderFactor,
|
|
1975
1780
|
};
|
|
1976
1781
|
}
|
|
1977
1782
|
}
|
|
@@ -2008,6 +1813,10 @@ class ReplayAnalyzer {
|
|
|
2008
1813
|
* The beatmap that is being analyzed. `DroidDifficultyCalculator` or `RebalanceDroidDifficultyCalculator` is required for three finger or two hand analyzing.
|
|
2009
1814
|
*/
|
|
2010
1815
|
beatmap;
|
|
1816
|
+
/**
|
|
1817
|
+
* The difficulty attributes of the beatmap.
|
|
1818
|
+
*/
|
|
1819
|
+
difficultyAttributes;
|
|
2011
1820
|
/**
|
|
2012
1821
|
* The results of the analyzer. `null` when initialized.
|
|
2013
1822
|
*/
|
|
@@ -2053,6 +1862,10 @@ class ReplayAnalyzer {
|
|
|
2053
1862
|
constructor(values) {
|
|
2054
1863
|
this.scoreID = values.scoreID;
|
|
2055
1864
|
this.beatmap = values.map;
|
|
1865
|
+
this.difficultyAttributes = values.difficultyAttributes;
|
|
1866
|
+
if (this.beatmap && !(this.beatmap instanceof osuBase.Beatmap)) {
|
|
1867
|
+
this.difficultyAttributes = this.beatmap.attributes;
|
|
1868
|
+
}
|
|
2056
1869
|
}
|
|
2057
1870
|
/**
|
|
2058
1871
|
* Analyzes a replay.
|
|
@@ -2094,10 +1907,10 @@ class ReplayAnalyzer {
|
|
|
2094
1907
|
*/
|
|
2095
1908
|
decompress() {
|
|
2096
1909
|
return new Promise((resolve, reject) => {
|
|
2097
|
-
const stream
|
|
2098
|
-
stream
|
|
2099
|
-
stream
|
|
2100
|
-
stream
|
|
1910
|
+
const stream = new node_stream.Readable();
|
|
1911
|
+
stream.push(this.originalODR);
|
|
1912
|
+
stream.push(null);
|
|
1913
|
+
stream
|
|
2101
1914
|
.pipe(unzipper.Parse())
|
|
2102
1915
|
.on("entry", async (entry) => {
|
|
2103
1916
|
const fileName = entry.path;
|
|
@@ -2489,15 +2302,15 @@ class ReplayAnalyzer {
|
|
|
2489
2302
|
/**
|
|
2490
2303
|
* Checks if a play is using 3 fingers.
|
|
2491
2304
|
*
|
|
2492
|
-
* Requires `analyze()` to be called first and `map` to be defined
|
|
2305
|
+
* Requires `analyze()` to be called first and `map` and `difficultyAttributes` to be defined.
|
|
2493
2306
|
*/
|
|
2494
2307
|
checkFor3Finger() {
|
|
2495
|
-
if (!
|
|
2496
|
-
this.beatmap instanceof osuRebalanceDifficultyCalculator.DroidDifficultyCalculator) ||
|
|
2497
|
-
!this.data) {
|
|
2308
|
+
if (!this.beatmap || !this.data || !this.difficultyAttributes) {
|
|
2498
2309
|
return;
|
|
2499
2310
|
}
|
|
2500
|
-
const threeFingerChecker = new ThreeFingerChecker(this.beatmap
|
|
2311
|
+
const threeFingerChecker = new ThreeFingerChecker(this.beatmap instanceof osuBase.Beatmap
|
|
2312
|
+
? this.beatmap
|
|
2313
|
+
: this.beatmap.beatmap, this.data, this.difficultyAttributes);
|
|
2501
2314
|
const result = threeFingerChecker.check();
|
|
2502
2315
|
this.is3Finger = result.is3Finger;
|
|
2503
2316
|
this.tapPenalty = result.penalty;
|
|
@@ -2523,15 +2336,15 @@ class ReplayAnalyzer {
|
|
|
2523
2336
|
/**
|
|
2524
2337
|
* Checks if a play has cheesed sliders.
|
|
2525
2338
|
*
|
|
2526
|
-
* Requires `analyze()` to be called first and `map` to be defined
|
|
2339
|
+
* Requires `analyze()` to be called first and `map` and `difficultyAttributes` to be defined.
|
|
2527
2340
|
*/
|
|
2528
2341
|
checkForSliderCheesing() {
|
|
2529
|
-
if (!
|
|
2530
|
-
this.beatmap instanceof osuRebalanceDifficultyCalculator.DroidDifficultyCalculator) ||
|
|
2531
|
-
!this.data) {
|
|
2342
|
+
if (!this.beatmap || !this.data || !this.difficultyAttributes) {
|
|
2532
2343
|
return;
|
|
2533
2344
|
}
|
|
2534
|
-
const sliderCheeseChecker = new SliderCheeseChecker(this.beatmap
|
|
2345
|
+
const sliderCheeseChecker = new SliderCheeseChecker(this.beatmap instanceof osuBase.Beatmap
|
|
2346
|
+
? this.beatmap
|
|
2347
|
+
: this.beatmap.beatmap, this.data, this.difficultyAttributes);
|
|
2535
2348
|
this.sliderCheesePenalty = sliderCheeseChecker.check();
|
|
2536
2349
|
this.hasBeenCheckedForSliderCheesing = true;
|
|
2537
2350
|
}
|