@rian8337/osu-droid-replay-analyzer 3.2.1 → 4.0.0-beta.1
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 +180 -341
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/typings/index.d.ts +87 -93
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,110 +362,26 @@ 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;
|
|
466
377
|
/**
|
|
467
|
-
* The
|
|
468
|
-
|
|
469
|
-
|
|
378
|
+
* The difficulty attributes of the beatmap.
|
|
379
|
+
*/
|
|
380
|
+
difficultyAttributes;
|
|
381
|
+
/**
|
|
382
|
+
* The true scale of objects.
|
|
470
383
|
*/
|
|
471
|
-
|
|
384
|
+
trueScale;
|
|
472
385
|
/**
|
|
473
386
|
* The distance threshold between cursors to assume that two cursors are
|
|
474
387
|
* actually pressed with 1 finger in osu!pixels.
|
|
@@ -500,22 +413,7 @@ class ThreeFingerChecker {
|
|
|
500
413
|
*/
|
|
501
414
|
threeFingerRatioThreshold = 0.01;
|
|
502
415
|
/**
|
|
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`.
|
|
416
|
+
* Extended sections of the beatmap for drag detection.
|
|
519
417
|
*/
|
|
520
418
|
beatmapSections = [];
|
|
521
419
|
/**
|
|
@@ -543,38 +441,45 @@ class ThreeFingerChecker {
|
|
|
543
441
|
* Each index represents the cursor index.
|
|
544
442
|
*/
|
|
545
443
|
downCursorInstances = [];
|
|
546
|
-
/**
|
|
547
|
-
* All cursor occurrences in the replay.
|
|
548
|
-
*
|
|
549
|
-
* Each index represents the cursor index.
|
|
550
|
-
*/
|
|
551
|
-
allCursorInstances;
|
|
552
444
|
/**
|
|
553
445
|
* Nerf factors from all sections that were three-fingered.
|
|
554
446
|
*/
|
|
555
447
|
nerfFactors = [];
|
|
556
448
|
/**
|
|
557
|
-
*
|
|
449
|
+
* Whether this score uses the Precise mod.
|
|
450
|
+
*/
|
|
451
|
+
isPrecise;
|
|
452
|
+
/**
|
|
453
|
+
* @param beatmap The beatmap to analyze.
|
|
558
454
|
* @param data The data of the replay.
|
|
455
|
+
* @param difficultyAttributes The difficulty attributes of the beatmap.
|
|
559
456
|
*/
|
|
560
|
-
constructor(
|
|
561
|
-
this.
|
|
457
|
+
constructor(beatmap, data, difficultyAttributes) {
|
|
458
|
+
this.beatmap = beatmap;
|
|
562
459
|
this.data = data;
|
|
563
|
-
this.
|
|
460
|
+
this.difficultyAttributes = difficultyAttributes;
|
|
564
461
|
const stats = new osuBase.MapStats({
|
|
565
|
-
od: this.
|
|
566
|
-
mods: this.
|
|
462
|
+
od: this.beatmap.difficulty.od,
|
|
463
|
+
mods: this.difficultyAttributes.mods.filter((m) => m.isApplicableToDroid() &&
|
|
567
464
|
!osuBase.ModUtil.speedChangingMods.some((v) => v.acronym === m.acronym)),
|
|
568
465
|
}).calculate();
|
|
466
|
+
this.isPrecise = this.difficultyAttributes.mods.some((m) => m instanceof osuBase.ModPrecise);
|
|
569
467
|
this.hitWindow = new osuBase.DroidHitWindow(stats.od);
|
|
570
|
-
|
|
571
|
-
|
|
468
|
+
this.strainNoteCount =
|
|
469
|
+
this.difficultyAttributes.possibleThreeFingeredSections.reduce((a, v) => a + v.lastObjectIndex - v.firstObjectIndex + 1, 0);
|
|
470
|
+
const circleSize = new osuBase.MapStats({
|
|
471
|
+
cs: this.beatmap.difficulty.cs,
|
|
472
|
+
mods: this.difficultyAttributes.mods,
|
|
473
|
+
}).calculate({ mode: osuBase.Modes.droid }).cs;
|
|
474
|
+
this.trueScale = (1 - (0.7 * (circleSize - 5)) / 5) / 2;
|
|
572
475
|
}
|
|
573
476
|
/**
|
|
574
477
|
* Checks whether a beatmap is eligible to be detected for 3-finger.
|
|
478
|
+
*
|
|
479
|
+
* @param difficultyAttributes The difficulty attributes of the beatmap.
|
|
575
480
|
*/
|
|
576
|
-
static isEligibleToDetect(
|
|
577
|
-
return
|
|
481
|
+
static isEligibleToDetect(difficultyAttributes) {
|
|
482
|
+
return difficultyAttributes.possibleThreeFingeredSections.length > 0;
|
|
578
483
|
}
|
|
579
484
|
/**
|
|
580
485
|
* Checks if the given beatmap is 3-fingered and also returns the final penalty.
|
|
@@ -597,7 +502,6 @@ class ThreeFingerChecker {
|
|
|
597
502
|
}
|
|
598
503
|
this.getBeatmapSections();
|
|
599
504
|
this.detectDragPlay();
|
|
600
|
-
this.getDetailedBeatmapSections();
|
|
601
505
|
this.preventAccidentalTaps();
|
|
602
506
|
if (this.downCursorInstances.filter((v) => v.length > 0).length <= 3) {
|
|
603
507
|
return { is3Finger: false, penalty: 1 };
|
|
@@ -613,50 +517,43 @@ class ThreeFingerChecker {
|
|
|
613
517
|
* start of the hitobject before it and do not end right at the first hitobject after it.
|
|
614
518
|
*/
|
|
615
519
|
getAccurateBreakPoints() {
|
|
616
|
-
const objects = this.
|
|
520
|
+
const { objects } = this.beatmap.hitObjects;
|
|
617
521
|
const objectData = this.data.hitObjectData;
|
|
618
|
-
const
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
let timeBefore = objects[beforeIndex].object.endTime;
|
|
522
|
+
for (const breakPoint of this.beatmap.events.breaks) {
|
|
523
|
+
const beforeIndex = osuBase.MathUtils.clamp(objects.findIndex((o) => o.endTime >= breakPoint.startTime) - 1, 0, objects.length - 2);
|
|
524
|
+
let timeBefore = objects[beforeIndex].endTime;
|
|
622
525
|
// For sliders and spinners, automatically set hit window length to be as lenient as possible.
|
|
623
|
-
let beforeIndexHitWindowLength = this.hitWindow.hitWindowFor50(isPrecise);
|
|
526
|
+
let beforeIndexHitWindowLength = this.hitWindow.hitWindowFor50(this.isPrecise);
|
|
624
527
|
switch (objectData[beforeIndex].result) {
|
|
625
528
|
case exports.HitResult.great:
|
|
626
|
-
beforeIndexHitWindowLength =
|
|
627
|
-
this.hitWindow.hitWindowFor300(isPrecise);
|
|
529
|
+
beforeIndexHitWindowLength = this.hitWindow.hitWindowFor300(this.isPrecise);
|
|
628
530
|
break;
|
|
629
531
|
case exports.HitResult.good:
|
|
630
|
-
beforeIndexHitWindowLength =
|
|
631
|
-
this.hitWindow.hitWindowFor100(isPrecise);
|
|
532
|
+
beforeIndexHitWindowLength = this.hitWindow.hitWindowFor100(this.isPrecise);
|
|
632
533
|
break;
|
|
633
534
|
default:
|
|
634
|
-
beforeIndexHitWindowLength =
|
|
635
|
-
this.hitWindow.hitWindowFor50(isPrecise);
|
|
535
|
+
beforeIndexHitWindowLength = this.hitWindow.hitWindowFor50(this.isPrecise);
|
|
636
536
|
}
|
|
637
537
|
timeBefore += beforeIndexHitWindowLength;
|
|
638
538
|
const afterIndex = beforeIndex + 1;
|
|
639
|
-
let timeAfter = objects[afterIndex].
|
|
539
|
+
let timeAfter = objects[afterIndex].startTime;
|
|
640
540
|
// For sliders and spinners, automatically set hit window length to be as lenient as possible.
|
|
641
|
-
let afterIndexHitWindowLength = this.hitWindow.hitWindowFor50(isPrecise);
|
|
541
|
+
let afterIndexHitWindowLength = this.hitWindow.hitWindowFor50(this.isPrecise);
|
|
642
542
|
switch (objectData[afterIndex].result) {
|
|
643
543
|
case exports.HitResult.great:
|
|
644
|
-
afterIndexHitWindowLength =
|
|
645
|
-
this.hitWindow.hitWindowFor300(isPrecise);
|
|
544
|
+
afterIndexHitWindowLength = this.hitWindow.hitWindowFor300(this.isPrecise);
|
|
646
545
|
break;
|
|
647
546
|
case exports.HitResult.good:
|
|
648
|
-
afterIndexHitWindowLength =
|
|
649
|
-
this.hitWindow.hitWindowFor100(isPrecise);
|
|
547
|
+
afterIndexHitWindowLength = this.hitWindow.hitWindowFor100(this.isPrecise);
|
|
650
548
|
break;
|
|
651
549
|
default:
|
|
652
|
-
afterIndexHitWindowLength =
|
|
653
|
-
this.hitWindow.hitWindowFor50(isPrecise);
|
|
550
|
+
afterIndexHitWindowLength = this.hitWindow.hitWindowFor50(this.isPrecise);
|
|
654
551
|
}
|
|
655
552
|
timeAfter += afterIndexHitWindowLength;
|
|
656
|
-
this.breakPointAccurateTimes.push({
|
|
553
|
+
this.breakPointAccurateTimes.push(new osuBase.BreakPoint({
|
|
657
554
|
startTime: timeBefore,
|
|
658
555
|
endTime: timeAfter,
|
|
659
|
-
});
|
|
556
|
+
}));
|
|
660
557
|
}
|
|
661
558
|
}
|
|
662
559
|
/**
|
|
@@ -665,59 +562,54 @@ class ThreeFingerChecker {
|
|
|
665
562
|
* This also filters cursors that are in break period or happen before start/after end of the beatmap.
|
|
666
563
|
*/
|
|
667
564
|
filterCursorInstances() {
|
|
668
|
-
const objects = this.
|
|
565
|
+
const { objects } = this.beatmap.hitObjects;
|
|
669
566
|
const objectData = this.data.hitObjectData;
|
|
670
567
|
const firstObjectResult = objectData[0].result;
|
|
671
568
|
const lastObjectResult = objectData.at(-1).result;
|
|
672
|
-
const isPrecise = this.calculator.mods.some((m) => m instanceof osuBase.ModPrecise);
|
|
673
569
|
// For sliders, automatically set hit window length to be as lenient as possible.
|
|
674
|
-
let firstObjectHitWindow = this.hitWindow.hitWindowFor50(isPrecise);
|
|
675
|
-
if (objects[0]
|
|
570
|
+
let firstObjectHitWindow = this.hitWindow.hitWindowFor50(this.isPrecise);
|
|
571
|
+
if (objects[0] instanceof osuBase.Circle) {
|
|
676
572
|
switch (firstObjectResult) {
|
|
677
573
|
case exports.HitResult.great:
|
|
678
|
-
firstObjectHitWindow =
|
|
679
|
-
this.hitWindow.hitWindowFor300(isPrecise);
|
|
574
|
+
firstObjectHitWindow = this.hitWindow.hitWindowFor300(this.isPrecise);
|
|
680
575
|
break;
|
|
681
576
|
case exports.HitResult.good:
|
|
682
|
-
firstObjectHitWindow =
|
|
683
|
-
this.hitWindow.hitWindowFor100(isPrecise);
|
|
577
|
+
firstObjectHitWindow = this.hitWindow.hitWindowFor100(this.isPrecise);
|
|
684
578
|
break;
|
|
685
579
|
default:
|
|
686
|
-
firstObjectHitWindow =
|
|
687
|
-
this.hitWindow.hitWindowFor50(isPrecise);
|
|
580
|
+
firstObjectHitWindow = this.hitWindow.hitWindowFor50(this.isPrecise);
|
|
688
581
|
}
|
|
689
582
|
}
|
|
690
583
|
// 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)
|
|
584
|
+
let lastObjectHitWindow = this.hitWindow.hitWindowFor50(this.isPrecise);
|
|
585
|
+
if (objects.at(-1) instanceof osuBase.Circle) {
|
|
693
586
|
switch (lastObjectResult) {
|
|
694
587
|
case exports.HitResult.great:
|
|
695
|
-
lastObjectHitWindow =
|
|
696
|
-
this.hitWindow.hitWindowFor300(isPrecise);
|
|
588
|
+
lastObjectHitWindow = this.hitWindow.hitWindowFor300(this.isPrecise);
|
|
697
589
|
break;
|
|
698
590
|
case exports.HitResult.good:
|
|
699
|
-
lastObjectHitWindow =
|
|
700
|
-
this.hitWindow.hitWindowFor100(isPrecise);
|
|
591
|
+
lastObjectHitWindow = this.hitWindow.hitWindowFor100(this.isPrecise);
|
|
701
592
|
break;
|
|
702
593
|
default:
|
|
703
|
-
lastObjectHitWindow =
|
|
704
|
-
this.hitWindow.hitWindowFor50(isPrecise);
|
|
594
|
+
lastObjectHitWindow = this.hitWindow.hitWindowFor50(this.isPrecise);
|
|
705
595
|
}
|
|
706
596
|
}
|
|
707
597
|
// These hit time uses hit window length as threshold.
|
|
708
598
|
// This is because cursors aren't recorded exactly at hit time,
|
|
709
599
|
// probably due to the game's behavior.
|
|
710
|
-
const firstObjectHitTime = objects[0].
|
|
711
|
-
const lastObjectHitTime = objects.at(-1).
|
|
600
|
+
const firstObjectHitTime = objects[0].startTime - firstObjectHitWindow;
|
|
601
|
+
const lastObjectHitTime = objects.at(-1).startTime + lastObjectHitWindow;
|
|
712
602
|
for (let i = 0; i < this.data.cursorMovement.length; ++i) {
|
|
713
603
|
const cursorInstance = this.data.cursorMovement[i];
|
|
714
604
|
const validOccurrences = [];
|
|
715
605
|
for (let j = 0; j < cursorInstance.occurrenceGroups.length; ++j) {
|
|
716
606
|
const group = cursorInstance.occurrenceGroups[j];
|
|
717
|
-
if (group.startTime < firstObjectHitTime
|
|
718
|
-
group.endTime > lastObjectHitTime) {
|
|
607
|
+
if (group.startTime < firstObjectHitTime) {
|
|
719
608
|
continue;
|
|
720
609
|
}
|
|
610
|
+
if (group.startTime > lastObjectHitTime) {
|
|
611
|
+
break;
|
|
612
|
+
}
|
|
721
613
|
if (this.breakPointAccurateTimes.some((v) => group.startTime >= v.startTime &&
|
|
722
614
|
group.endTime <= v.endTime)) {
|
|
723
615
|
continue;
|
|
@@ -732,14 +624,13 @@ class ThreeFingerChecker {
|
|
|
732
624
|
* detect dragged sections and improve detection speed.
|
|
733
625
|
*/
|
|
734
626
|
getBeatmapSections() {
|
|
735
|
-
const
|
|
736
|
-
|
|
737
|
-
this.beatmapSections.push(
|
|
738
|
-
|
|
739
|
-
lastObjectIndex: beatmapSection.lastObjectIndex,
|
|
627
|
+
for (const section of this.difficultyAttributes
|
|
628
|
+
.possibleThreeFingeredSections) {
|
|
629
|
+
this.beatmapSections.push({
|
|
630
|
+
...section,
|
|
740
631
|
isDragged: false,
|
|
741
632
|
dragFingerIndex: -1,
|
|
742
|
-
})
|
|
633
|
+
});
|
|
743
634
|
}
|
|
744
635
|
}
|
|
745
636
|
/**
|
|
@@ -760,48 +651,41 @@ class ThreeFingerChecker {
|
|
|
760
651
|
* @param section The section to check.
|
|
761
652
|
*/
|
|
762
653
|
checkDrag(section) {
|
|
763
|
-
const objects = this.
|
|
654
|
+
const { objects } = this.beatmap.hitObjects;
|
|
764
655
|
const objectData = this.data.hitObjectData;
|
|
765
|
-
const isPrecise = this.calculator.mods.some((m) => m instanceof osuBase.ModPrecise);
|
|
766
656
|
const firstObject = objects[section.firstObjectIndex];
|
|
767
657
|
const lastObject = objects[section.lastObjectIndex];
|
|
768
|
-
let firstObjectMinHitTime = firstObject.
|
|
769
|
-
if (firstObject
|
|
658
|
+
let firstObjectMinHitTime = firstObject.startTime;
|
|
659
|
+
if (firstObject instanceof osuBase.Circle) {
|
|
770
660
|
switch (objectData[section.firstObjectIndex].result) {
|
|
771
661
|
case exports.HitResult.great:
|
|
772
|
-
firstObjectMinHitTime -=
|
|
773
|
-
this.hitWindow.hitWindowFor300(isPrecise);
|
|
662
|
+
firstObjectMinHitTime -= this.hitWindow.hitWindowFor300(this.isPrecise);
|
|
774
663
|
break;
|
|
775
664
|
case exports.HitResult.good:
|
|
776
|
-
firstObjectMinHitTime -=
|
|
777
|
-
this.hitWindow.hitWindowFor100(isPrecise);
|
|
665
|
+
firstObjectMinHitTime -= this.hitWindow.hitWindowFor100(this.isPrecise);
|
|
778
666
|
break;
|
|
779
667
|
default:
|
|
780
|
-
firstObjectMinHitTime -=
|
|
781
|
-
this.hitWindow.hitWindowFor50(isPrecise);
|
|
668
|
+
firstObjectMinHitTime -= this.hitWindow.hitWindowFor50(this.isPrecise);
|
|
782
669
|
}
|
|
783
670
|
}
|
|
784
671
|
else {
|
|
785
|
-
firstObjectMinHitTime -= this.hitWindow.hitWindowFor50(isPrecise);
|
|
672
|
+
firstObjectMinHitTime -= this.hitWindow.hitWindowFor50(this.isPrecise);
|
|
786
673
|
}
|
|
787
|
-
let lastObjectMaxHitTime = lastObject.
|
|
788
|
-
if (lastObject
|
|
674
|
+
let lastObjectMaxHitTime = lastObject.startTime;
|
|
675
|
+
if (lastObject instanceof osuBase.Circle) {
|
|
789
676
|
switch (objectData[section.lastObjectIndex].result) {
|
|
790
677
|
case exports.HitResult.great:
|
|
791
|
-
lastObjectMaxHitTime +=
|
|
792
|
-
this.hitWindow.hitWindowFor300(isPrecise);
|
|
678
|
+
lastObjectMaxHitTime += this.hitWindow.hitWindowFor300(this.isPrecise);
|
|
793
679
|
break;
|
|
794
680
|
case exports.HitResult.good:
|
|
795
|
-
lastObjectMaxHitTime +=
|
|
796
|
-
this.hitWindow.hitWindowFor100(isPrecise);
|
|
681
|
+
lastObjectMaxHitTime += this.hitWindow.hitWindowFor100(this.isPrecise);
|
|
797
682
|
break;
|
|
798
683
|
default:
|
|
799
|
-
lastObjectMaxHitTime +=
|
|
800
|
-
this.hitWindow.hitWindowFor50(isPrecise);
|
|
684
|
+
lastObjectMaxHitTime += this.hitWindow.hitWindowFor50(this.isPrecise);
|
|
801
685
|
}
|
|
802
686
|
}
|
|
803
687
|
else {
|
|
804
|
-
lastObjectMaxHitTime += this.hitWindow.hitWindowFor50(isPrecise);
|
|
688
|
+
lastObjectMaxHitTime += this.hitWindow.hitWindowFor50(this.isPrecise);
|
|
805
689
|
}
|
|
806
690
|
// Since there may be more than 1 cursor instance index,
|
|
807
691
|
// we check which cursor instance follows hitobjects all over.
|
|
@@ -834,22 +718,26 @@ class ThreeFingerChecker {
|
|
|
834
718
|
* @param cursorIndexes The indexes of the cursor instance that has at least an occurrence in the section.
|
|
835
719
|
*/
|
|
836
720
|
findDragIndex(sectionObjects, sectionReplayObjectData, cursorIndexes) {
|
|
837
|
-
const
|
|
838
|
-
const hitWindow50 = this.hitWindow.hitWindowFor50(isPrecise);
|
|
721
|
+
const hitWindow50 = this.hitWindow.hitWindowFor50(this.isPrecise);
|
|
839
722
|
for (let i = 0; i < sectionObjects.length && cursorIndexes.every((v) => v !== -1); ++i) {
|
|
840
|
-
|
|
723
|
+
let object = sectionObjects[i];
|
|
841
724
|
const objectData = sectionReplayObjectData[i];
|
|
842
|
-
if (object
|
|
725
|
+
if (object instanceof osuBase.Spinner ||
|
|
843
726
|
objectData.result === exports.HitResult.miss) {
|
|
844
727
|
continue;
|
|
845
728
|
}
|
|
846
729
|
// Exclude sliderbreaks.
|
|
847
|
-
if (object
|
|
730
|
+
if (object instanceof osuBase.Slider &&
|
|
848
731
|
objectData.accuracy === Math.floor(hitWindow50) + 13) {
|
|
849
732
|
continue;
|
|
850
733
|
}
|
|
851
|
-
|
|
852
|
-
|
|
734
|
+
if (object.droidScale !== this.trueScale) {
|
|
735
|
+
// Deep copy the instance so that we can assign scale.
|
|
736
|
+
object = osuBase.Utils.deepCopy(object);
|
|
737
|
+
object.droidScale = this.trueScale;
|
|
738
|
+
}
|
|
739
|
+
const objectPosition = object.getStackedPosition(osuBase.Modes.droid);
|
|
740
|
+
const hitTime = object.startTime + objectData.accuracy;
|
|
853
741
|
// Observe the cursor position at the object's hit time.
|
|
854
742
|
for (let j = 0; j < cursorIndexes.length; ++j) {
|
|
855
743
|
if (cursorIndexes[j] === -1) {
|
|
@@ -864,30 +752,28 @@ class ThreeFingerChecker {
|
|
|
864
752
|
for (let k = 1; k < cursors.length; ++k) {
|
|
865
753
|
const cursor = cursors[k];
|
|
866
754
|
const prevCursor = cursors[k - 1];
|
|
867
|
-
if (prevCursor.time <
|
|
868
|
-
object.object.startTime - hitWindow50) {
|
|
755
|
+
if (prevCursor.time < object.startTime - hitWindow50) {
|
|
869
756
|
continue;
|
|
870
757
|
}
|
|
871
|
-
if (prevCursor.time >
|
|
872
|
-
object.object.startTime + hitWindow50) {
|
|
758
|
+
if (prevCursor.time > object.startTime + hitWindow50) {
|
|
873
759
|
break;
|
|
874
760
|
}
|
|
875
761
|
let isInObject = false;
|
|
876
762
|
switch (cursor.id) {
|
|
877
763
|
case exports.MovementType.up:
|
|
878
764
|
isInObject =
|
|
879
|
-
prevCursor.position.getDistance(objectPosition) <= object.
|
|
765
|
+
prevCursor.position.getDistance(objectPosition) <= object.getRadius(osuBase.Modes.droid);
|
|
880
766
|
break;
|
|
881
767
|
case exports.MovementType.move:
|
|
882
768
|
// Interpolate movement.
|
|
883
769
|
for (let mSecPassed = prevCursor.time; !isInObject &&
|
|
884
770
|
mSecPassed <=
|
|
885
|
-
Math.min(cursor.time, object.
|
|
771
|
+
Math.min(cursor.time, object.startTime + hitWindow50); ++mSecPassed) {
|
|
886
772
|
const t = (mSecPassed - prevCursor.time) /
|
|
887
773
|
(cursor.time - prevCursor.time);
|
|
888
774
|
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
775
|
isInObject =
|
|
890
|
-
objectPosition.getDistance(cursorPosition) <= object.
|
|
776
|
+
objectPosition.getDistance(cursorPosition) <= object.getRadius(osuBase.Modes.droid);
|
|
891
777
|
}
|
|
892
778
|
}
|
|
893
779
|
if (!isInObject) {
|
|
@@ -898,51 +784,6 @@ class ThreeFingerChecker {
|
|
|
898
784
|
}
|
|
899
785
|
return cursorIndexes.find((v) => v !== -1) ?? -1;
|
|
900
786
|
}
|
|
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
787
|
/**
|
|
947
788
|
* Attempts to prevent accidental taps from being flagged.
|
|
948
789
|
*
|
|
@@ -955,7 +796,7 @@ class ThreeFingerChecker {
|
|
|
955
796
|
if (filledCursorAmount <= 3) {
|
|
956
797
|
return;
|
|
957
798
|
}
|
|
958
|
-
const objects = this.
|
|
799
|
+
const { objects } = this.beatmap.hitObjects;
|
|
959
800
|
const totalCursorAmount = this.downCursorInstances.reduce((acc, value) => acc + value.length, 0);
|
|
960
801
|
for (let i = 0; i < this.downCursorInstances.length; ++i) {
|
|
961
802
|
if (filledCursorAmount <= 3) {
|
|
@@ -979,23 +820,22 @@ class ThreeFingerChecker {
|
|
|
979
820
|
* This check will ignore all objects with speed strain below `strainThreshold`.
|
|
980
821
|
*/
|
|
981
822
|
calculateNerfFactors() {
|
|
982
|
-
const objects = this.
|
|
823
|
+
const { objects } = this.beatmap.hitObjects;
|
|
983
824
|
const objectData = this.data.hitObjectData;
|
|
984
|
-
const isPrecise = this.data.convertedMods.some((m) => m instanceof osuBase.ModPrecise);
|
|
985
825
|
// We only filter cursor instances that are above the strain threshold.
|
|
986
826
|
// This minimalizes the amount of cursor instances to analyze.
|
|
987
827
|
for (const beatmapSection of this.beatmapSections) {
|
|
988
828
|
const dragIndex = beatmapSection.dragFingerIndex;
|
|
989
|
-
const startTime = objects[beatmapSection.firstObjectIndex].
|
|
829
|
+
const startTime = objects[beatmapSection.firstObjectIndex].startTime +
|
|
990
830
|
(objectData[beatmapSection.firstObjectIndex].result !==
|
|
991
831
|
exports.HitResult.miss
|
|
992
832
|
? objectData[beatmapSection.firstObjectIndex].accuracy
|
|
993
|
-
: -this.hitWindow.hitWindowFor50(isPrecise));
|
|
994
|
-
const endTime = objects[beatmapSection.lastObjectIndex].
|
|
833
|
+
: -this.hitWindow.hitWindowFor50(this.isPrecise));
|
|
834
|
+
const endTime = objects[beatmapSection.lastObjectIndex].endTime +
|
|
995
835
|
(objectData[beatmapSection.lastObjectIndex].result !==
|
|
996
836
|
exports.HitResult.miss
|
|
997
837
|
? objectData[beatmapSection.lastObjectIndex].accuracy
|
|
998
|
-
: this.hitWindow.hitWindowFor50(isPrecise));
|
|
838
|
+
: this.hitWindow.hitWindowFor50(this.isPrecise));
|
|
999
839
|
const cursorAmounts = [];
|
|
1000
840
|
const cursorVectorTimes = [];
|
|
1001
841
|
for (let i = 0; i < this.downCursorInstances.length; ++i) {
|
|
@@ -1071,11 +911,6 @@ class ThreeFingerChecker {
|
|
|
1071
911
|
const objectCount = beatmapSection.lastObjectIndex -
|
|
1072
912
|
beatmapSection.firstObjectIndex +
|
|
1073
913
|
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
914
|
// We can ignore the first 3 (2 for drag) filled cursor instances
|
|
1080
915
|
// since they are guaranteed not 3 finger.
|
|
1081
916
|
const threeFingerCursorAmounts = cursorAmounts
|
|
@@ -1097,7 +932,7 @@ class ThreeFingerChecker {
|
|
|
1097
932
|
// Length factor applies more penalty if there are more 3-fingered object.
|
|
1098
933
|
const lengthFactor = 1 + Math.pow(objectCount / this.strainNoteCount, 1.2);
|
|
1099
934
|
this.nerfFactors.push({
|
|
1100
|
-
strainFactor: Math.max(1,
|
|
935
|
+
strainFactor: Math.max(1, beatmapSection.sumStrain),
|
|
1101
936
|
fingerFactor,
|
|
1102
937
|
lengthFactor,
|
|
1103
938
|
});
|
|
@@ -1797,48 +1632,46 @@ class TwoHandChecker {
|
|
|
1797
1632
|
*/
|
|
1798
1633
|
class SliderCheeseChecker {
|
|
1799
1634
|
/**
|
|
1800
|
-
* The
|
|
1635
|
+
* The beatmap that is being analyzed.
|
|
1801
1636
|
*/
|
|
1802
|
-
|
|
1637
|
+
beatmap;
|
|
1803
1638
|
/**
|
|
1804
1639
|
* The data of the replay.
|
|
1805
1640
|
*/
|
|
1806
1641
|
data;
|
|
1807
1642
|
/**
|
|
1808
|
-
* The
|
|
1643
|
+
* The difficulty attributes of the beatmap.
|
|
1809
1644
|
*/
|
|
1810
|
-
|
|
1645
|
+
difficultyAttributes;
|
|
1811
1646
|
/**
|
|
1812
1647
|
* The 50 osu!droid hit window of the analyzed beatmap.
|
|
1813
1648
|
*/
|
|
1814
1649
|
hitWindow50;
|
|
1815
1650
|
/**
|
|
1816
|
-
* The
|
|
1651
|
+
* The difficulty ratings of sliders that were cheesed.
|
|
1817
1652
|
*/
|
|
1818
|
-
|
|
1653
|
+
cheesedDifficultyRatings = [];
|
|
1819
1654
|
/**
|
|
1820
|
-
* @param
|
|
1655
|
+
* @param beatmap The beatmap to analyze.
|
|
1821
1656
|
* @param data The data of the replay.
|
|
1657
|
+
* @param difficultyAttributes The difficulty attributes of the beatmap.
|
|
1822
1658
|
*/
|
|
1823
|
-
constructor(
|
|
1824
|
-
this.
|
|
1659
|
+
constructor(beatmap, data, difficultyAttributes) {
|
|
1660
|
+
this.beatmap = beatmap;
|
|
1825
1661
|
this.data = data;
|
|
1662
|
+
this.difficultyAttributes = difficultyAttributes;
|
|
1826
1663
|
const stats = new osuBase.MapStats({
|
|
1827
|
-
od: this.
|
|
1828
|
-
mods: this.
|
|
1664
|
+
od: this.beatmap.difficulty.od,
|
|
1665
|
+
mods: this.difficultyAttributes.mods.filter((m) => m.isApplicableToDroid() &&
|
|
1829
1666
|
!osuBase.ModUtil.speedChangingMods.some((v) => v.acronym === m.acronym)),
|
|
1830
1667
|
}).calculate();
|
|
1831
|
-
this.
|
|
1832
|
-
this.hitWindow50 = this.hitWindow.hitWindowFor50(calculator.mods.some((m) => m instanceof osuBase.ModPrecise));
|
|
1668
|
+
this.hitWindow50 = new osuBase.DroidHitWindow(stats.od).hitWindowFor50(this.difficultyAttributes.mods.some((m) => m instanceof osuBase.ModPrecise));
|
|
1833
1669
|
}
|
|
1834
1670
|
/**
|
|
1835
1671
|
* 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
1672
|
*/
|
|
1840
1673
|
check() {
|
|
1841
|
-
if (this.
|
|
1674
|
+
if (this.difficultyAttributes.difficultSliders.length === 0) {
|
|
1842
1675
|
return {
|
|
1843
1676
|
aimPenalty: 1,
|
|
1844
1677
|
flashlightPenalty: 1,
|
|
@@ -1854,21 +1687,29 @@ class SliderCheeseChecker {
|
|
|
1854
1687
|
checkSliderCheesing() {
|
|
1855
1688
|
// Current loop indexes are stored for efficiency.
|
|
1856
1689
|
const cursorLoopIndexes = osuBase.Utils.initializeArray(10, 0);
|
|
1857
|
-
const
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1690
|
+
const circleSize = new osuBase.MapStats({
|
|
1691
|
+
cs: this.beatmap.difficulty.cs,
|
|
1692
|
+
mods: this.difficultyAttributes.mods,
|
|
1693
|
+
}).calculate({ mode: osuBase.Modes.droid }).cs;
|
|
1694
|
+
const scale = (1 - (0.7 * (circleSize - 5)) / 5) / 2;
|
|
1695
|
+
const acceptableRadius = 128 * scale;
|
|
1696
|
+
for (const difficultSlider of this.difficultyAttributes
|
|
1697
|
+
.difficultSliders) {
|
|
1698
|
+
if (difficultSlider.index >= this.data.hitObjectData.length) {
|
|
1864
1699
|
continue;
|
|
1865
1700
|
}
|
|
1866
|
-
const
|
|
1867
|
-
const objectData = this.data.hitObjectData[i];
|
|
1701
|
+
const objectData = this.data.hitObjectData[difficultSlider.index];
|
|
1868
1702
|
// If a slider break occurs, we disregard the check for that slider.
|
|
1869
1703
|
if (objectData.accuracy === Math.floor(this.hitWindow50) + 13) {
|
|
1870
1704
|
continue;
|
|
1871
1705
|
}
|
|
1706
|
+
let object = (this.beatmap.hitObjects.objects[difficultSlider.index]);
|
|
1707
|
+
if (object.droidScale !== scale) {
|
|
1708
|
+
// Deep clone the object so that we can assign scale properly.
|
|
1709
|
+
object = osuBase.Utils.deepCopy(object);
|
|
1710
|
+
object.droidScale = scale;
|
|
1711
|
+
}
|
|
1712
|
+
const objectStartPosition = object.getStackedPosition(osuBase.Modes.droid);
|
|
1872
1713
|
let isCheesed = false;
|
|
1873
1714
|
for (let j = 0; j < this.data.cursorMovement.length; ++j) {
|
|
1874
1715
|
if (isCheesed) {
|
|
@@ -1899,7 +1740,12 @@ class SliderCheeseChecker {
|
|
|
1899
1740
|
if (!tickWasHit) {
|
|
1900
1741
|
continue;
|
|
1901
1742
|
}
|
|
1902
|
-
|
|
1743
|
+
let nestedObject = object.nestedHitObjects[l];
|
|
1744
|
+
if (nestedObject.droidScale !== scale) {
|
|
1745
|
+
// Deep clone the object so that we can assign scale properly
|
|
1746
|
+
nestedObject = osuBase.Utils.deepCopy(nestedObject);
|
|
1747
|
+
nestedObject.droidScale = scale;
|
|
1748
|
+
}
|
|
1903
1749
|
const nestedPosition = nestedObject.getStackedPosition(osuBase.Modes.droid);
|
|
1904
1750
|
while (occurrenceLoopIndex < allOccurrences.length &&
|
|
1905
1751
|
allOccurrences[occurrenceLoopIndex].time <
|
|
@@ -1938,7 +1784,7 @@ class SliderCheeseChecker {
|
|
|
1938
1784
|
}
|
|
1939
1785
|
}
|
|
1940
1786
|
if (isCheesed) {
|
|
1941
|
-
this.
|
|
1787
|
+
this.cheesedDifficultyRatings.push(difficultSlider.difficultyRating);
|
|
1942
1788
|
}
|
|
1943
1789
|
}
|
|
1944
1790
|
}
|
|
@@ -1946,32 +1792,17 @@ class SliderCheeseChecker {
|
|
|
1946
1792
|
* Calculates the slider cheese penalty.
|
|
1947
1793
|
*/
|
|
1948
1794
|
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();
|
|
1795
|
+
const summedDifficultyRating = this.cheesedDifficultyRatings.reduce((a, v) => a + v, 0);
|
|
1971
1796
|
return {
|
|
1972
|
-
aimPenalty:
|
|
1973
|
-
|
|
1974
|
-
|
|
1797
|
+
aimPenalty: Math.pow(1 -
|
|
1798
|
+
summedDifficultyRating *
|
|
1799
|
+
this.difficultyAttributes.sliderFactor, 3),
|
|
1800
|
+
flashlightPenalty: Math.pow(1 -
|
|
1801
|
+
summedDifficultyRating *
|
|
1802
|
+
this.difficultyAttributes.flashlightSliderFactor, 3),
|
|
1803
|
+
visualPenalty: Math.pow(1 -
|
|
1804
|
+
summedDifficultyRating *
|
|
1805
|
+
this.difficultyAttributes.visualSliderFactor, 3),
|
|
1975
1806
|
};
|
|
1976
1807
|
}
|
|
1977
1808
|
}
|
|
@@ -2008,6 +1839,10 @@ class ReplayAnalyzer {
|
|
|
2008
1839
|
* The beatmap that is being analyzed. `DroidDifficultyCalculator` or `RebalanceDroidDifficultyCalculator` is required for three finger or two hand analyzing.
|
|
2009
1840
|
*/
|
|
2010
1841
|
beatmap;
|
|
1842
|
+
/**
|
|
1843
|
+
* The difficulty attributes of the beatmap.
|
|
1844
|
+
*/
|
|
1845
|
+
difficultyAttributes;
|
|
2011
1846
|
/**
|
|
2012
1847
|
* The results of the analyzer. `null` when initialized.
|
|
2013
1848
|
*/
|
|
@@ -2053,6 +1888,10 @@ class ReplayAnalyzer {
|
|
|
2053
1888
|
constructor(values) {
|
|
2054
1889
|
this.scoreID = values.scoreID;
|
|
2055
1890
|
this.beatmap = values.map;
|
|
1891
|
+
this.difficultyAttributes = values.difficultyAttributes;
|
|
1892
|
+
if (this.beatmap && !(this.beatmap instanceof osuBase.Beatmap)) {
|
|
1893
|
+
this.difficultyAttributes = this.beatmap.attributes;
|
|
1894
|
+
}
|
|
2056
1895
|
}
|
|
2057
1896
|
/**
|
|
2058
1897
|
* Analyzes a replay.
|
|
@@ -2094,10 +1933,10 @@ class ReplayAnalyzer {
|
|
|
2094
1933
|
*/
|
|
2095
1934
|
decompress() {
|
|
2096
1935
|
return new Promise((resolve, reject) => {
|
|
2097
|
-
const stream
|
|
2098
|
-
stream
|
|
2099
|
-
stream
|
|
2100
|
-
stream
|
|
1936
|
+
const stream = new node_stream.Readable();
|
|
1937
|
+
stream.push(this.originalODR);
|
|
1938
|
+
stream.push(null);
|
|
1939
|
+
stream
|
|
2101
1940
|
.pipe(unzipper.Parse())
|
|
2102
1941
|
.on("entry", async (entry) => {
|
|
2103
1942
|
const fileName = entry.path;
|
|
@@ -2489,15 +2328,15 @@ class ReplayAnalyzer {
|
|
|
2489
2328
|
/**
|
|
2490
2329
|
* Checks if a play is using 3 fingers.
|
|
2491
2330
|
*
|
|
2492
|
-
* Requires `analyze()` to be called first and `map` to be defined
|
|
2331
|
+
* Requires `analyze()` to be called first and `map` and `difficultyAttributes` to be defined.
|
|
2493
2332
|
*/
|
|
2494
2333
|
checkFor3Finger() {
|
|
2495
|
-
if (!
|
|
2496
|
-
this.beatmap instanceof osuRebalanceDifficultyCalculator.DroidDifficultyCalculator) ||
|
|
2497
|
-
!this.data) {
|
|
2334
|
+
if (!this.beatmap || !this.data || !this.difficultyAttributes) {
|
|
2498
2335
|
return;
|
|
2499
2336
|
}
|
|
2500
|
-
const threeFingerChecker = new ThreeFingerChecker(this.beatmap
|
|
2337
|
+
const threeFingerChecker = new ThreeFingerChecker(this.beatmap instanceof osuBase.Beatmap
|
|
2338
|
+
? this.beatmap
|
|
2339
|
+
: this.beatmap.beatmap, this.data, this.difficultyAttributes);
|
|
2501
2340
|
const result = threeFingerChecker.check();
|
|
2502
2341
|
this.is3Finger = result.is3Finger;
|
|
2503
2342
|
this.tapPenalty = result.penalty;
|
|
@@ -2523,15 +2362,15 @@ class ReplayAnalyzer {
|
|
|
2523
2362
|
/**
|
|
2524
2363
|
* Checks if a play has cheesed sliders.
|
|
2525
2364
|
*
|
|
2526
|
-
* Requires `analyze()` to be called first and `map` to be defined
|
|
2365
|
+
* Requires `analyze()` to be called first and `map` and `difficultyAttributes` to be defined.
|
|
2527
2366
|
*/
|
|
2528
2367
|
checkForSliderCheesing() {
|
|
2529
|
-
if (!
|
|
2530
|
-
this.beatmap instanceof osuRebalanceDifficultyCalculator.DroidDifficultyCalculator) ||
|
|
2531
|
-
!this.data) {
|
|
2368
|
+
if (!this.beatmap || !this.data || !this.difficultyAttributes) {
|
|
2532
2369
|
return;
|
|
2533
2370
|
}
|
|
2534
|
-
const sliderCheeseChecker = new SliderCheeseChecker(this.beatmap
|
|
2371
|
+
const sliderCheeseChecker = new SliderCheeseChecker(this.beatmap instanceof osuBase.Beatmap
|
|
2372
|
+
? this.beatmap
|
|
2373
|
+
: this.beatmap.beatmap, this.data, this.difficultyAttributes);
|
|
2535
2374
|
this.sliderCheesePenalty = sliderCheeseChecker.check();
|
|
2536
2375
|
this.hasBeenCheckedForSliderCheesing = true;
|
|
2537
2376
|
}
|