@rian8337/osu-droid-replay-analyzer 4.0.0-beta.96 → 4.0.0-beta.98
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 +82 -34
- package/package.json +5 -5
- package/typings/index.d.ts +19 -7
package/dist/index.js
CHANGED
|
@@ -1327,7 +1327,7 @@ class TwoHandChecker {
|
|
|
1327
1327
|
// this.csvString = `Mods,${
|
|
1328
1328
|
// data.convertedMods.reduce((a, m) => a + m.acronym, "") || "NM"
|
|
1329
1329
|
// }\nCombo,${data.maxCombo}\nAccuracy,"${(
|
|
1330
|
-
// data.accuracy.value
|
|
1330
|
+
// data.accuracy.value * 100
|
|
1331
1331
|
// ).toFixed(2)}% [${data.accuracy.n300}/${data.accuracy.n100}/${
|
|
1332
1332
|
// data.accuracy.n50
|
|
1333
1333
|
// }/${
|
|
@@ -2195,15 +2195,22 @@ class ReplayData {
|
|
|
2195
2195
|
*/
|
|
2196
2196
|
class ReplayV3Data extends ReplayData {
|
|
2197
2197
|
/**
|
|
2198
|
-
*
|
|
2198
|
+
* Obtains the total score achieved in the play, after applying score multiplier from mods.
|
|
2199
|
+
*
|
|
2200
|
+
* @param difficulty The `BeatmapDifficulty` of the beatmap that was played. Required to correctly compute
|
|
2201
|
+
* the score multiplier of difficulty-dependent mods. If not provided, those mods fall back to their
|
|
2202
|
+
* difficulty-independent multiplier.
|
|
2199
2203
|
*/
|
|
2200
|
-
|
|
2201
|
-
|
|
2204
|
+
getTotalScore(difficulty = null) {
|
|
2205
|
+
let baseScore = this.score;
|
|
2202
2206
|
if (this.replayVersion < 8) {
|
|
2203
|
-
|
|
2207
|
+
// Replay versions 3 to 7 store the score after applying score multiplier
|
|
2208
|
+
// from mods, so we need to reverse it to get the base score.
|
|
2209
|
+
const legacyScoreMultiplier = new osuBase.DroidLegacyScoreMultiplierCalculator(difficulty).calculateFor(this.convertedMods.values());
|
|
2210
|
+
baseScore = Math.round(this.score / legacyScoreMultiplier);
|
|
2204
2211
|
}
|
|
2205
|
-
|
|
2206
|
-
return Math.round(
|
|
2212
|
+
const scoreMultiplier = new osuBase.DroidScoreMultiplierCalculator(difficulty).calculateFor(this.convertedMods.values());
|
|
2213
|
+
return Math.round(baseScore * scoreMultiplier);
|
|
2207
2214
|
}
|
|
2208
2215
|
constructor(values) {
|
|
2209
2216
|
super(values);
|
|
@@ -2331,7 +2338,6 @@ class ReplayAnalyzer {
|
|
|
2331
2338
|
* `analyze()` must be called before calling this, and `beatmap` must be defined.
|
|
2332
2339
|
*/
|
|
2333
2340
|
calculateHitError() {
|
|
2334
|
-
var _a, _b;
|
|
2335
2341
|
if (!this.data || !this.beatmap) {
|
|
2336
2342
|
return null;
|
|
2337
2343
|
}
|
|
@@ -2341,14 +2347,7 @@ class ReplayAnalyzer {
|
|
|
2341
2347
|
let positiveTotal = 0;
|
|
2342
2348
|
let negativeTotal = 0;
|
|
2343
2349
|
const { objects } = this.beatmap.hitObjects;
|
|
2344
|
-
const
|
|
2345
|
-
? this.data.convertedMods
|
|
2346
|
-
: ((_b = (_a = this.difficultyAttributes) === null || _a === void 0 ? void 0 : _a.mods) !== null && _b !== void 0 ? _b : new osuBase.ModMap());
|
|
2347
|
-
const adjustedDifficulty = new osuBase.BeatmapDifficulty(this.beatmap.difficulty);
|
|
2348
|
-
osuBase.ModUtil.applyModsToBeatmapDifficulty(adjustedDifficulty, osuBase.Modes.Droid, mods);
|
|
2349
|
-
const mehWindow = mods.has(osuBase.ModPrecise)
|
|
2350
|
-
? new osuBase.PreciseDroidHitWindow(adjustedDifficulty.od).mehWindow
|
|
2351
|
-
: new osuBase.DroidHitWindow(adjustedDifficulty.od).mehWindow;
|
|
2350
|
+
const { mehWindow } = this.getBeatmapHitWindow();
|
|
2352
2351
|
const accuracies = [];
|
|
2353
2352
|
for (let i = 0; i < hitObjectData.length; ++i) {
|
|
2354
2353
|
const v = hitObjectData[i];
|
|
@@ -2385,7 +2384,7 @@ class ReplayAnalyzer {
|
|
|
2385
2384
|
};
|
|
2386
2385
|
}
|
|
2387
2386
|
/**
|
|
2388
|
-
* Obtains the amount of slider
|
|
2387
|
+
* Obtains the amount of slider nested objects hit in the replay.
|
|
2389
2388
|
*
|
|
2390
2389
|
* This requires `analyze()` to be called first and `beatmap` to be defined.
|
|
2391
2390
|
*
|
|
@@ -2397,27 +2396,46 @@ class ReplayAnalyzer {
|
|
|
2397
2396
|
return null;
|
|
2398
2397
|
}
|
|
2399
2398
|
const sliderInformation = {
|
|
2400
|
-
|
|
2401
|
-
|
|
2399
|
+
head: { obtained: 0, total: beatmap.hitObjects.sliders },
|
|
2400
|
+
tick: { obtained: 0, total: 0 },
|
|
2401
|
+
repeat: { obtained: 0, total: 0 },
|
|
2402
|
+
end: { obtained: 0, total: 0 },
|
|
2402
2403
|
};
|
|
2404
|
+
const hitWindow = this.getBeatmapHitWindow();
|
|
2403
2405
|
for (let i = 0; i < data.hitObjectData.length; ++i) {
|
|
2404
2406
|
const object = beatmap.hitObjects.objects[i];
|
|
2405
2407
|
const objectData = data.hitObjectData[i];
|
|
2406
|
-
if (
|
|
2407
|
-
!(object instanceof osuBase.Slider)) {
|
|
2408
|
+
if (!(object instanceof osuBase.Slider)) {
|
|
2408
2409
|
continue;
|
|
2409
2410
|
}
|
|
2410
|
-
|
|
2411
|
+
let lateHitThreshold = hitWindow.mehWindow;
|
|
2412
|
+
// Before replay version 8, the slider head's hit window is capped to the duration of the slider.
|
|
2413
|
+
if (data.replayVersion < 8) {
|
|
2414
|
+
lateHitThreshold = Math.min(lateHitThreshold, object.duration);
|
|
2415
|
+
}
|
|
2416
|
+
if (-hitWindow.mehWindow <= objectData.accuracy &&
|
|
2417
|
+
objectData.accuracy <= lateHitThreshold) {
|
|
2418
|
+
++sliderInformation.head.obtained;
|
|
2419
|
+
}
|
|
2411
2420
|
for (let j = 1; j < object.nestedHitObjects.length; ++j) {
|
|
2412
2421
|
const nested = object.nestedHitObjects[j];
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2422
|
+
let tickInformation;
|
|
2423
|
+
switch (true) {
|
|
2424
|
+
case nested instanceof osuBase.SliderTick:
|
|
2425
|
+
tickInformation = sliderInformation.tick;
|
|
2426
|
+
break;
|
|
2427
|
+
case nested instanceof osuBase.SliderRepeat:
|
|
2428
|
+
tickInformation = sliderInformation.repeat;
|
|
2429
|
+
break;
|
|
2430
|
+
case nested instanceof osuBase.SliderTail:
|
|
2431
|
+
tickInformation = sliderInformation.end;
|
|
2432
|
+
break;
|
|
2433
|
+
default:
|
|
2434
|
+
continue;
|
|
2418
2435
|
}
|
|
2419
|
-
|
|
2420
|
-
|
|
2436
|
+
++tickInformation.total;
|
|
2437
|
+
if (objectData.tickset[j - 1]) {
|
|
2438
|
+
++tickInformation.obtained;
|
|
2421
2439
|
}
|
|
2422
2440
|
}
|
|
2423
2441
|
}
|
|
@@ -2567,8 +2585,16 @@ class ReplayAnalyzer {
|
|
|
2567
2585
|
.setRequireAPIkey(false)
|
|
2568
2586
|
.setEndpoint("upload")
|
|
2569
2587
|
.addParameter("", `${this.scoreID.toString()}.odr`);
|
|
2570
|
-
|
|
2588
|
+
let result;
|
|
2589
|
+
try {
|
|
2590
|
+
result = yield apiRequestBuilder.sendRequest();
|
|
2591
|
+
}
|
|
2592
|
+
catch (e) {
|
|
2593
|
+
console.error(`Error retrieving replay for score ID ${this.scoreID.toString()}: ${e.message}`);
|
|
2594
|
+
return null;
|
|
2595
|
+
}
|
|
2571
2596
|
if (result.statusCode !== 200) {
|
|
2597
|
+
console.error(`Error retrieving replay for score ID ${this.scoreID.toString()}: ${result.describeFailure()}`);
|
|
2572
2598
|
return null;
|
|
2573
2599
|
}
|
|
2574
2600
|
return result.data;
|
|
@@ -2646,9 +2672,16 @@ class ReplayAnalyzer {
|
|
|
2646
2672
|
resultObject.accuracy.n100 = buf.readInt32BE(20);
|
|
2647
2673
|
resultObject.accuracy.n50 = buf.readInt32BE(24);
|
|
2648
2674
|
resultObject.accuracy.nmiss = buf.readInt32BE(28);
|
|
2649
|
-
resultObject.
|
|
2650
|
-
|
|
2651
|
-
|
|
2675
|
+
if (resultObject.replayVersion >= 9) {
|
|
2676
|
+
// From replay version 9 onwards, the score is stored as a long (8 bytes) instead of an int (4 bytes).
|
|
2677
|
+
resultObject.score = Number(buf.readBigInt64BE(32));
|
|
2678
|
+
resultObject.maxCombo = buf.readInt32BE(40);
|
|
2679
|
+
}
|
|
2680
|
+
else {
|
|
2681
|
+
resultObject.score = buf.readInt32BE(32);
|
|
2682
|
+
resultObject.maxCombo = buf.readInt32BE(36);
|
|
2683
|
+
}
|
|
2684
|
+
resultObject.isFullCombo = resultObject.accuracy.value === 1;
|
|
2652
2685
|
resultObject.playerName = rawObject[5];
|
|
2653
2686
|
if (resultObject.replayVersion >= 7) {
|
|
2654
2687
|
resultObject.convertedMods = osuBase.ModUtil.deserializeMods(JSON.parse(rawObject[6]));
|
|
@@ -2674,6 +2707,7 @@ class ReplayAnalyzer {
|
|
|
2674
2707
|
case 3:
|
|
2675
2708
|
case 7:
|
|
2676
2709
|
case 8:
|
|
2710
|
+
case 9:
|
|
2677
2711
|
bufferIndex = 7;
|
|
2678
2712
|
break;
|
|
2679
2713
|
case 4:
|
|
@@ -2867,7 +2901,7 @@ class ReplayAnalyzer {
|
|
|
2867
2901
|
resultObject.convertedMods.has(osuBase.ModFlashlight);
|
|
2868
2902
|
const hit300Ratio = resultObject.accuracy.n300 / totalHits;
|
|
2869
2903
|
switch (true) {
|
|
2870
|
-
case resultObject.accuracy.value
|
|
2904
|
+
case resultObject.accuracy.value === 1:
|
|
2871
2905
|
return isHidden ? "XH" : "X";
|
|
2872
2906
|
case hit300Ratio > 0.9 &&
|
|
2873
2907
|
resultObject.accuracy.n50 / totalHits < 0.01 &&
|
|
@@ -2898,6 +2932,20 @@ class ReplayAnalyzer {
|
|
|
2898
2932
|
: (_a = this.difficultyAttributes) === null || _a === void 0 ? void 0 : _a.mods;
|
|
2899
2933
|
return this.beatmap.createDroidPlayableBeatmap(mods);
|
|
2900
2934
|
}
|
|
2935
|
+
getBeatmapHitWindow() {
|
|
2936
|
+
var _a, _b;
|
|
2937
|
+
if (!this.data || !this.beatmap) {
|
|
2938
|
+
return null;
|
|
2939
|
+
}
|
|
2940
|
+
const mods = this.data.isReplayV3()
|
|
2941
|
+
? this.data.convertedMods
|
|
2942
|
+
: ((_b = (_a = this.difficultyAttributes) === null || _a === void 0 ? void 0 : _a.mods) !== null && _b !== void 0 ? _b : new osuBase.ModMap());
|
|
2943
|
+
const adjustedDifficulty = new osuBase.BeatmapDifficulty(this.beatmap.difficulty);
|
|
2944
|
+
osuBase.ModUtil.applyModsToBeatmapDifficulty(adjustedDifficulty, osuBase.Modes.Droid, mods);
|
|
2945
|
+
return mods.has(osuBase.ModPrecise)
|
|
2946
|
+
? new osuBase.PreciseDroidHitWindow(adjustedDifficulty.od)
|
|
2947
|
+
: new osuBase.DroidHitWindow(adjustedDifficulty.od);
|
|
2948
|
+
}
|
|
2901
2949
|
readByte(buffer) {
|
|
2902
2950
|
const num = buffer.readInt8(this.bufferOffset);
|
|
2903
2951
|
this.bufferOffset += 1;
|
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.98",
|
|
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.98",
|
|
38
|
+
"@rian8337/osu-difficulty-calculator": "4.0.0-beta.98",
|
|
39
|
+
"@rian8337/osu-rebalance-difficulty-calculator": "4.0.0-beta.98",
|
|
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": "0d1f9256f0caee5123379cf5a8b3c92d1dcfa7bf"
|
|
50
50
|
}
|
package/typings/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Vector2, HitResult, Accuracy, ScoreRank, ModMap, DroidPlayableBeatmap, Beatmap, HitWindow } from '@rian8337/osu-base';
|
|
1
|
+
import { Vector2, HitResult, Accuracy, ScoreRank, ModMap, BeatmapDifficulty, DroidPlayableBeatmap, Beatmap, HitWindow } from '@rian8337/osu-base';
|
|
2
2
|
import { IExtendedDroidDifficultyAttributes } from '@rian8337/osu-rebalance-difficulty-calculator';
|
|
3
3
|
import { IExtendedDroidDifficultyAttributes as IExtendedDroidDifficultyAttributes$1 } from '@rian8337/osu-difficulty-calculator';
|
|
4
4
|
|
|
@@ -340,7 +340,7 @@ declare class ReplayV3Data extends ReplayData {
|
|
|
340
340
|
* Between replay version 3 and 7, this is the final score after applying score multiplier from mods.
|
|
341
341
|
* From replay version 8 onwards, this is the score before applying score multiplier from mods.
|
|
342
342
|
*
|
|
343
|
-
* The {@link
|
|
343
|
+
* The {@link getTotalScore} method can be used to get the total score across all replay versions.
|
|
344
344
|
*/
|
|
345
345
|
readonly score: number;
|
|
346
346
|
/**
|
|
@@ -360,10 +360,13 @@ declare class ReplayV3Data extends ReplayData {
|
|
|
360
360
|
*/
|
|
361
361
|
readonly convertedMods: ModMap;
|
|
362
362
|
/**
|
|
363
|
-
*
|
|
363
|
+
* Obtains the total score achieved in the play, after applying score multiplier from mods.
|
|
364
|
+
*
|
|
365
|
+
* @param difficulty The `BeatmapDifficulty` of the beatmap that was played. Required to correctly compute
|
|
366
|
+
* the score multiplier of difficulty-dependent mods. If not provided, those mods fall back to their
|
|
367
|
+
* difficulty-independent multiplier.
|
|
364
368
|
*/
|
|
365
|
-
|
|
366
|
-
private scoreMultiplier?;
|
|
369
|
+
getTotalScore(difficulty?: BeatmapDifficulty | null): number;
|
|
367
370
|
constructor(values: ReplayInformation);
|
|
368
371
|
}
|
|
369
372
|
|
|
@@ -1000,10 +1003,18 @@ type ExportedReplayJSONV4 = ExportedReplayJSON<4, ExportedReplayJSONDataV4>;
|
|
|
1000
1003
|
* Represents hit information about sliders in a beatmap.
|
|
1001
1004
|
*/
|
|
1002
1005
|
interface SliderHitInformation {
|
|
1006
|
+
/**
|
|
1007
|
+
* Hit information of slider heads.
|
|
1008
|
+
*/
|
|
1009
|
+
readonly head: SliderNestedHitObjectInformation;
|
|
1003
1010
|
/**
|
|
1004
1011
|
* Hit information of slider ticks.
|
|
1005
1012
|
*/
|
|
1006
1013
|
readonly tick: SliderNestedHitObjectInformation;
|
|
1014
|
+
/**
|
|
1015
|
+
* Hit information of slider repeats.
|
|
1016
|
+
*/
|
|
1017
|
+
readonly repeat: SliderNestedHitObjectInformation;
|
|
1007
1018
|
/**
|
|
1008
1019
|
* hit information of slider ends.
|
|
1009
1020
|
*/
|
|
@@ -1020,7 +1031,7 @@ interface SliderNestedHitObjectInformation {
|
|
|
1020
1031
|
/**
|
|
1021
1032
|
* The amount of the nested hit objects in the beatmap.
|
|
1022
1033
|
*/
|
|
1023
|
-
|
|
1034
|
+
total: number;
|
|
1024
1035
|
}
|
|
1025
1036
|
|
|
1026
1037
|
interface HitErrorInformation {
|
|
@@ -1123,7 +1134,7 @@ declare class ReplayAnalyzer {
|
|
|
1123
1134
|
*/
|
|
1124
1135
|
calculateHitError(): HitErrorInformation | null;
|
|
1125
1136
|
/**
|
|
1126
|
-
* Obtains the amount of slider
|
|
1137
|
+
* Obtains the amount of slider nested objects hit in the replay.
|
|
1127
1138
|
*
|
|
1128
1139
|
* This requires `analyze()` to be called first and `beatmap` to be defined.
|
|
1129
1140
|
*
|
|
@@ -1182,6 +1193,7 @@ declare class ReplayAnalyzer {
|
|
|
1182
1193
|
private parseOldReplayInformation;
|
|
1183
1194
|
private calculateRank;
|
|
1184
1195
|
private constructPlayableBeatmap;
|
|
1196
|
+
private getBeatmapHitWindow;
|
|
1185
1197
|
private readByte;
|
|
1186
1198
|
private readShort;
|
|
1187
1199
|
private readInt;
|