@rian8337/osu-droid-replay-analyzer 1.4.12-alpha.4 → 1.4.12-alpha.7

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.
@@ -2,7 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ThreeFingerChecker = void 0;
4
4
  const osu_base_1 = require("@rian8337/osu-base");
5
- const __1 = require("..");
5
+ const hitResult_1 = require("../constants/hitResult");
6
+ const movementType_1 = require("../constants/movementType");
6
7
  const CursorData_1 = require("../data/CursorData");
7
8
  const BeatmapSectionGenerator_1 = require("./BeatmapSectionGenerator");
8
9
  const ThreeFingerBeatmapSection_1 = require("./data/ThreeFingerBeatmapSection");
@@ -15,25 +16,6 @@ class ThreeFingerChecker {
15
16
  * @param data The data of the replay.
16
17
  */
17
18
  constructor(map, data) {
18
- /**
19
- * The distance threshold between cursors to assume that two cursors are
20
- * actually pressed with 1 finger in osu!pixels.
21
- *
22
- * This is used to prevent cases where a player would lift their finger
23
- * too fast to the point where the 4th cursor instance or beyond is recorded
24
- * as 1st, 2nd, or 3rd cursor instance.
25
- */
26
- this.cursorDistancingDistanceThreshold = 60;
27
- /**
28
- * The threshold for the amount of cursors that are assumed to be pressed
29
- * by a single finger.
30
- */
31
- this.cursorDistancingCountThreshold = 10;
32
- /**
33
- * The threshold for the time difference of cursors that are assumed to be pressed
34
- * by a single finger, in milliseconds.
35
- */
36
- this.cursorDistancingTimeThreshold = 1000;
37
19
  /**
38
20
  * The ratio threshold between non-3 finger cursors and 3-finger cursors.
39
21
  *
@@ -49,7 +31,7 @@ class ThreeFingerChecker {
49
31
  * Note that this value does not account for the speed multiplier of
50
32
  * the play, similar to the way replay object data is stored.
51
33
  */
52
- this.maxSectionDeltaTime = 2000;
34
+ this.maxSectionDeltaTime = 1000;
53
35
  /**
54
36
  * The minimum object count required to make a beatmap section.
55
37
  *
@@ -60,14 +42,6 @@ class ThreeFingerChecker {
60
42
  * The sections of the beatmap that was cut based on `maxSectionDeltaTime` and `minSectionObjectCount`.
61
43
  */
62
44
  this.beatmapSections = [];
63
- /**
64
- * This threshold is used to filter out accidental taps.
65
- *
66
- * Increasing this number makes the filtration more sensitive, however it
67
- * will also increase the chance of 3-fingered plays getting out from
68
- * being flagged.
69
- */
70
- this.accidentalTapThreshold = 400;
71
45
  /**
72
46
  * A reprocessed break points to match right on object time.
73
47
  *
@@ -79,6 +53,11 @@ class ThreeFingerChecker {
79
53
  * A cursor data array that only contains `movementType.DOWN` movement ID occurrences.
80
54
  */
81
55
  this.downCursorInstances = [];
56
+ /**
57
+ * The indexes of cursors that presses objects that fall under {@link beatmapSections beatmap sections},
58
+ * mapped by each object's unadjusted start time.
59
+ */
60
+ this.objectCursorIndexes = new Map();
82
61
  /**
83
62
  * Nerf factors from all sections that were three-fingered.
84
63
  */
@@ -121,9 +100,9 @@ class ThreeFingerChecker {
121
100
  return { is3Finger: false, penalty: 1 };
122
101
  }
123
102
  this.getBeatmapSections();
124
- this.detectDragPlay();
103
+ this.assignFingerIndexes();
125
104
  this.getDetailedBeatmapSections();
126
- this.preventAccidentalTaps();
105
+ this.assignCursorIndexesToObjects();
127
106
  if (this.downCursorInstances.filter((v) => v.size > 0).length <= 3) {
128
107
  return { is3Finger: false, penalty: 1 };
129
108
  }
@@ -147,11 +126,11 @@ class ThreeFingerChecker {
147
126
  // For sliders and spinners, automatically set hit window length to be as lenient as possible.
148
127
  let beforeIndexHitWindowLength = this.hitWindow.hitWindowFor50(isPrecise);
149
128
  switch (objectData[beforeIndex].result) {
150
- case __1.hitResult.RESULT_300:
129
+ case hitResult_1.hitResult.RESULT_300:
151
130
  beforeIndexHitWindowLength =
152
131
  this.hitWindow.hitWindowFor300(isPrecise);
153
132
  break;
154
- case __1.hitResult.RESULT_100:
133
+ case hitResult_1.hitResult.RESULT_100:
155
134
  beforeIndexHitWindowLength =
156
135
  this.hitWindow.hitWindowFor100(isPrecise);
157
136
  break;
@@ -165,11 +144,11 @@ class ThreeFingerChecker {
165
144
  // For sliders and spinners, automatically set hit window length to be as lenient as possible.
166
145
  let afterIndexHitWindowLength = this.hitWindow.hitWindowFor50(isPrecise);
167
146
  switch (objectData[afterIndex].result) {
168
- case __1.hitResult.RESULT_300:
147
+ case hitResult_1.hitResult.RESULT_300:
169
148
  afterIndexHitWindowLength =
170
149
  this.hitWindow.hitWindowFor300(isPrecise);
171
150
  break;
172
- case __1.hitResult.RESULT_100:
151
+ case hitResult_1.hitResult.RESULT_100:
173
152
  afterIndexHitWindowLength =
174
153
  this.hitWindow.hitWindowFor100(isPrecise);
175
154
  break;
@@ -199,11 +178,11 @@ class ThreeFingerChecker {
199
178
  let firstObjectHitWindow = this.hitWindow.hitWindowFor50(isPrecise);
200
179
  if (objects[0].object instanceof osu_base_1.Circle) {
201
180
  switch (firstObjectResult) {
202
- case __1.hitResult.RESULT_300:
181
+ case hitResult_1.hitResult.RESULT_300:
203
182
  firstObjectHitWindow =
204
183
  this.hitWindow.hitWindowFor300(isPrecise);
205
184
  break;
206
- case __1.hitResult.RESULT_100:
185
+ case hitResult_1.hitResult.RESULT_100:
207
186
  firstObjectHitWindow =
208
187
  this.hitWindow.hitWindowFor100(isPrecise);
209
188
  break;
@@ -216,11 +195,11 @@ class ThreeFingerChecker {
216
195
  let lastObjectHitWindow = this.hitWindow.hitWindowFor50(isPrecise);
217
196
  if (objects.at(-1).object instanceof osu_base_1.Circle) {
218
197
  switch (lastObjectResult) {
219
- case __1.hitResult.RESULT_300:
198
+ case hitResult_1.hitResult.RESULT_300:
220
199
  lastObjectHitWindow =
221
200
  this.hitWindow.hitWindowFor300(isPrecise);
222
201
  break;
223
- case __1.hitResult.RESULT_100:
202
+ case hitResult_1.hitResult.RESULT_100:
224
203
  lastObjectHitWindow =
225
204
  this.hitWindow.hitWindowFor100(isPrecise);
226
205
  break;
@@ -244,7 +223,7 @@ class ThreeFingerChecker {
244
223
  id: [],
245
224
  });
246
225
  for (let j = 0; j < cursorInstance.size; ++j) {
247
- if (cursorInstance.id[j] !== __1.movementType.DOWN) {
226
+ if (cursorInstance.id[j] !== movementType_1.movementType.DOWN) {
248
227
  continue;
249
228
  }
250
229
  const time = cursorInstance.time[j];
@@ -273,162 +252,113 @@ class ThreeFingerChecker {
273
252
  this.beatmapSections.push(new ThreeFingerBeatmapSection_1.ThreeFingerBeatmapSection({
274
253
  firstObjectIndex: beatmapSection.firstObjectIndex,
275
254
  lastObjectIndex: beatmapSection.lastObjectIndex,
276
- isDragged: false,
277
- dragFingerIndex: -1,
255
+ mainFingerIndex: -1,
278
256
  }));
279
257
  }
280
258
  }
281
259
  /**
282
- * Checks whether or not each beatmap sections is dragged.
260
+ * Assigns main finger indexes for each beatmap section.
283
261
  */
284
- detectDragPlay() {
285
- for (let i = 0; i < this.beatmapSections.length; ++i) {
286
- const dragIndex = this.checkDrag(this.beatmapSections[i]);
287
- this.beatmapSections[i].dragFingerIndex = dragIndex;
288
- this.beatmapSections[i].isDragged = dragIndex !== -1;
262
+ assignFingerIndexes() {
263
+ for (const section of this.beatmapSections) {
264
+ let currentIndex = this.getMainFingerIndex(section);
265
+ if (currentIndex === null) {
266
+ const firstObject = this.map.objects[section.firstObjectIndex];
267
+ const firstObjectData = this.data.hitObjectData[section.firstObjectIndex];
268
+ const lastObject = this.map.objects[section.lastObjectIndex];
269
+ const lastObjectData = this.data.hitObjectData[section.lastObjectIndex];
270
+ const startTime = firstObject.object.startTime + firstObjectData.accuracy;
271
+ const endTime = lastObject.object.endTime + lastObjectData.accuracy;
272
+ // Get the cursor instance index in the section that has the most movementType.MOVE instance.
273
+ const moveInstanceAmounts = [];
274
+ for (let i = 0; i < this.data.cursorMovement.length; ++i) {
275
+ let amount = 0;
276
+ const c = this.data.cursorMovement[i];
277
+ for (let j = 0; j < c.size; ++j) {
278
+ if (c.time[j] < startTime) {
279
+ continue;
280
+ }
281
+ if (c.time[j] > endTime) {
282
+ break;
283
+ }
284
+ if (c.id[j] === movementType_1.movementType.MOVE) {
285
+ ++amount;
286
+ }
287
+ }
288
+ moveInstanceAmounts.push(amount);
289
+ }
290
+ currentIndex = moveInstanceAmounts.indexOf(Math.max(...moveInstanceAmounts));
291
+ }
292
+ section.mainFingerIndex = currentIndex;
289
293
  }
290
294
  }
291
295
  /**
292
- * Checks if a section is dragged and returns the index of the drag finger.
293
- *
294
- * If the section is not dragged, -1 will be returned.
296
+ * Gets the main finger index of a beatmap section.
295
297
  *
296
- * @param section The section to check.
298
+ * @param section The beatmap section.
297
299
  */
298
- checkDrag(section) {
299
- const objects = this.map.objects;
300
- const objectData = this.data.hitObjectData;
301
- const isPrecise = this.map.mods.some((m) => m instanceof osu_base_1.ModPrecise);
302
- const firstObject = objects[section.firstObjectIndex];
303
- const lastObject = objects[section.lastObjectIndex];
304
- let firstObjectMinHitTime = firstObject.object.startTime;
305
- if (firstObject.object instanceof osu_base_1.Circle) {
306
- switch (objectData[section.firstObjectIndex].result) {
307
- case __1.hitResult.RESULT_300:
308
- firstObjectMinHitTime -=
309
- this.hitWindow.hitWindowFor300(isPrecise);
310
- break;
311
- case __1.hitResult.RESULT_100:
312
- firstObjectMinHitTime -=
313
- this.hitWindow.hitWindowFor100(isPrecise);
314
- break;
315
- default:
316
- firstObjectMinHitTime -=
317
- this.hitWindow.hitWindowFor50(isPrecise);
318
- }
319
- }
320
- else {
321
- firstObjectMinHitTime -= this.hitWindow.hitWindowFor50(isPrecise);
322
- }
323
- let lastObjectMaxHitTime = lastObject.object.startTime;
324
- if (lastObject.object instanceof osu_base_1.Circle) {
325
- switch (objectData[section.lastObjectIndex].result) {
326
- case __1.hitResult.RESULT_300:
327
- lastObjectMaxHitTime +=
328
- this.hitWindow.hitWindowFor300(isPrecise);
329
- break;
330
- case __1.hitResult.RESULT_100:
331
- lastObjectMaxHitTime +=
332
- this.hitWindow.hitWindowFor100(isPrecise);
333
- break;
334
- default:
335
- lastObjectMaxHitTime +=
336
- this.hitWindow.hitWindowFor50(isPrecise);
337
- }
338
- }
339
- else {
340
- lastObjectMaxHitTime += this.hitWindow.hitWindowFor50(isPrecise);
341
- }
342
- // Since there may be more than 1 cursor instance index,
343
- // we check which cursor instance follows hitobjects all over.
344
- const cursorIndexes = [];
300
+ getMainFingerIndex(section) {
301
+ const validCursorIndexes = new Set();
302
+ const timeDiscrepancy = 10;
345
303
  for (let i = 0; i < this.data.cursorMovement.length; ++i) {
346
- const c = this.data.cursorMovement[i];
347
- if (c.size === 0) {
348
- continue;
304
+ if (this.data.cursorMovement[i].size > 0) {
305
+ validCursorIndexes.add(i);
349
306
  }
350
- // Do not include cursors that don't have an occurence in this section
351
- // this speeds up checking process.
352
- if (c.time.filter((v) => v >= firstObjectMinHitTime && v <= lastObjectMaxHitTime).length === 0) {
353
- continue;
354
- }
355
- // If this cursor instance doesn't move, it's not the cursor instance we want.
356
- if (c.id.filter((v) => v === __1.movementType.MOVE).length === 0) {
357
- continue;
358
- }
359
- cursorIndexes.push(i);
360
307
  }
361
- return this.findDragIndex(objects.slice(section.firstObjectIndex, section.lastObjectIndex + 1), objectData.slice(section.firstObjectIndex, section.lastObjectIndex + 1), cursorIndexes);
362
- }
363
- /**
364
- * Finds the drag index of the section.
365
- *
366
- * @param sectionObjects The objects in the section.
367
- * @param sectionReplayObjectData The hitobject data of all objects in the section.
368
- * @param cursorIndexes The indexes of the cursor instance that has at least an occurrence in the section.
369
- */
370
- findDragIndex(sectionObjects, sectionReplayObjectData, cursorIndexes) {
371
- let objectIndex = sectionObjects.findIndex((v, i) => !(v.object instanceof osu_base_1.Spinner) &&
372
- sectionReplayObjectData[i].result !== __1.hitResult.RESULT_0);
373
- if (objectIndex === -1) {
374
- return -1;
375
- }
376
- while (cursorIndexes.length > 0) {
377
- if (objectIndex === sectionObjects.length) {
308
+ for (let i = section.firstObjectIndex; i <= section.lastObjectIndex; ++i) {
309
+ if (validCursorIndexes.size === 0) {
378
310
  break;
379
311
  }
380
- const o = sectionObjects[objectIndex];
381
- const s = sectionReplayObjectData[objectIndex];
382
- ++objectIndex;
383
- if (s.result === __1.hitResult.RESULT_0) {
312
+ const object = this.map.objects[i];
313
+ if (object.object instanceof osu_base_1.Spinner) {
384
314
  continue;
385
315
  }
386
- // Get the cursor instance that is closest to the object's hit time.
387
- for (let j = 0; j < cursorIndexes.length; ++j) {
388
- const c = this.data.cursorMovement[cursorIndexes[j]];
389
- // Cursor instances aren't always recorded at all times,
390
- // therefore the game emulates the movement between
391
- // movementType.MOVE cursors.
392
- const hitTime = o.object.startTime + s.accuracy;
393
- const nextHitIndex = c.time.findIndex((v) => v >= hitTime);
394
- const hitIndex = nextHitIndex - 1;
395
- if (hitIndex <= -1) {
396
- cursorIndexes[j] = -1;
397
- continue;
398
- }
399
- if (c.id[hitIndex] === __1.movementType.UP) {
400
- cursorIndexes[j] = -1;
316
+ const objectData = this.data.hitObjectData[i];
317
+ const hitTime = object.startTime + objectData.accuracy;
318
+ for (let j = 0; j < this.data.cursorMovement.length; ++j) {
319
+ if (!validCursorIndexes.has(j)) {
401
320
  continue;
402
321
  }
403
- const cursorPosition = new osu_base_1.Vector2(c.x[hitIndex], c.y[hitIndex]);
404
- let isInObject = false;
405
- if (c.id[nextHitIndex] === __1.movementType.MOVE ||
406
- c.id[hitIndex] === __1.movementType.MOVE) {
407
- // Try to interpolate movement between two movementType.MOVE cursor every 1ms.
408
- // This minimizes rounding error.
409
- for (let mSecPassed = c.time[hitIndex]; mSecPassed <= c.time[nextHitIndex]; ++mSecPassed) {
410
- const t = (mSecPassed - c.time[nextHitIndex]) /
411
- (c.time[hitIndex] - c.time[nextHitIndex]);
412
- cursorPosition.x = osu_base_1.Interpolation.lerp(c.x[hitIndex], c.x[nextHitIndex], t);
413
- cursorPosition.y = osu_base_1.Interpolation.lerp(c.y[hitIndex], c.y[nextHitIndex], t);
414
- if (o.object.stackedPosition.getDistance(cursorPosition) <= o.object.radius) {
415
- isInObject = true;
416
- break;
322
+ const c = this.data.cursorMovement[j];
323
+ for (let k = 0; k < c.size; ++k) {
324
+ if (c.time[k] < hitTime - timeDiscrepancy ||
325
+ c.id[k] === movementType_1.movementType.UP) {
326
+ continue;
327
+ }
328
+ if (c.time[k] > hitTime + timeDiscrepancy) {
329
+ break;
330
+ }
331
+ // Calculate the initial cursor position assuming it's a DOWN movement type.
332
+ const cursorPosition = new osu_base_1.Vector2(c.x[k], c.y[k]);
333
+ let isInObject = false;
334
+ // But if the next cursor is a move instance, then we extend through
335
+ // time between both cursors and interpolate cursor position.
336
+ if (c.id[k + 1] === movementType_1.movementType.MOVE) {
337
+ // Loop every 1ms to increase accuracy and minimalize error.
338
+ for (let mSecPassed = c.time[k]; mSecPassed <= c.time[k + 1]; ++mSecPassed) {
339
+ const t = (mSecPassed - c.time[k + 1]) /
340
+ (c.time[k] - c.time[k + 1]);
341
+ cursorPosition.x = osu_base_1.Interpolation.lerp(c.x[k], c.x[k + 1], t);
342
+ cursorPosition.y = osu_base_1.Interpolation.lerp(c.y[k], c.y[k + 1], t);
343
+ if (object.object.stackedPosition.getDistance(cursorPosition) <= object.object.radius) {
344
+ isInObject = true;
345
+ break;
346
+ }
417
347
  }
418
348
  }
419
- }
420
- else {
421
- isInObject =
422
- o.object.stackedPosition.getDistance(cursorPosition) <=
423
- o.object.radius;
424
- }
425
- if (!isInObject) {
426
- cursorIndexes[j] = -1;
349
+ else {
350
+ isInObject =
351
+ object.object.stackedPosition.getDistance(cursorPosition) <= object.object.radius;
352
+ }
353
+ if (!isInObject) {
354
+ validCursorIndexes.delete(j);
355
+ break;
356
+ }
427
357
  }
428
358
  }
429
- cursorIndexes = cursorIndexes.filter((v) => v !== -1);
430
359
  }
431
- return cursorIndexes.shift() ?? -1;
360
+ const [first] = validCursorIndexes;
361
+ return first ?? null;
432
362
  }
433
363
  /**
434
364
  * Redivides the beatmap into sections.
@@ -457,18 +387,15 @@ class ThreeFingerChecker {
457
387
  newBeatmapSections.push({
458
388
  firstObjectIndex: newFirstObjectIndex,
459
389
  lastObjectIndex: i,
460
- isDragged: beatmapSection.isDragged,
461
- dragFingerIndex: beatmapSection.dragFingerIndex,
390
+ mainFingerIndex: beatmapSection.mainFingerIndex,
462
391
  });
463
392
  }
464
393
  }
465
394
  // Don't forget to manually add the last beatmap section, which would otherwise be ignored.
466
395
  if (inSpeedSection) {
467
396
  newBeatmapSections.push({
397
+ ...beatmapSection,
468
398
  firstObjectIndex: newFirstObjectIndex,
469
- lastObjectIndex: beatmapSection.lastObjectIndex,
470
- isDragged: beatmapSection.isDragged,
471
- dragFingerIndex: beatmapSection.dragFingerIndex,
472
399
  });
473
400
  }
474
401
  }
@@ -476,41 +403,57 @@ class ThreeFingerChecker {
476
403
  this.beatmapSections.push(...newBeatmapSections);
477
404
  }
478
405
  /**
479
- * Attempts to prevent accidental taps from being flagged.
480
- *
481
- * This detection will filter cursors that don't hit
482
- * any object in beatmap sections, thus eliminating any
483
- * unnecessary taps.
406
+ * Assigns cursor indexes for each object that is included in {@link beatmapSections beatmap sections}.
484
407
  */
485
- preventAccidentalTaps() {
486
- let filledCursorAmount = this.downCursorInstances.filter((v) => v.size > 0).length;
487
- if (filledCursorAmount <= 3) {
488
- return;
489
- }
490
- const objects = this.map.objects;
491
- const totalCursorAmount = this.downCursorInstances.reduce((acc, value) => acc + value.size, 0);
492
- for (let i = 0; i < this.downCursorInstances.length; ++i) {
493
- if (filledCursorAmount <= 3) {
494
- break;
495
- }
496
- const cursorInstance = this.downCursorInstances[i];
497
- // Use an estimation for accidental tap threshold.
498
- if (cursorInstance.size <=
499
- Math.ceil(objects.length / this.accidentalTapThreshold) &&
500
- cursorInstance.size / totalCursorAmount <
501
- this.threeFingerRatioThreshold * 2) {
502
- --filledCursorAmount;
503
- for (const property in cursorInstance) {
504
- const prop = property;
505
- if (Array.isArray(cursorInstance[prop])) {
506
- cursorInstance[prop].length = 0;
408
+ assignCursorIndexesToObjects() {
409
+ const isPrecise = this.map.mods.some((m) => m instanceof osu_base_1.ModPrecise);
410
+ for (const section of this.beatmapSections) {
411
+ for (let i = section.firstObjectIndex; i <= section.lastObjectIndex; ++i) {
412
+ const object = this.map.objects[i];
413
+ const objectData = this.data.hitObjectData[i];
414
+ if (object.object instanceof osu_base_1.Spinner ||
415
+ objectData.result === hitResult_1.hitResult.RESULT_0) {
416
+ this.objectCursorIndexes.set(object.object.startTime, section.mainFingerIndex);
417
+ continue;
418
+ }
419
+ let hitWindowLeniency = this.hitWindow.hitWindowFor50(isPrecise);
420
+ // For sliders, automatically set hit window length to be as lenient as possible.
421
+ if (object.object instanceof osu_base_1.Circle) {
422
+ switch (objectData.result) {
423
+ case hitResult_1.hitResult.RESULT_300:
424
+ hitWindowLeniency =
425
+ this.hitWindow.hitWindowFor300(isPrecise);
426
+ break;
427
+ case hitResult_1.hitResult.RESULT_100:
428
+ hitWindowLeniency =
429
+ this.hitWindow.hitWindowFor100(isPrecise);
430
+ break;
507
431
  }
508
- else {
509
- cursorInstance[prop] = 0;
432
+ }
433
+ const hitTime = object.object.startTime + objectData.accuracy;
434
+ // We only care about the object timing; position doesn't matter.
435
+ let smallestIndex = section.mainFingerIndex;
436
+ let smallestDeltaTime = Number.POSITIVE_INFINITY;
437
+ for (let j = 0; j < this.downCursorInstances.length; ++j) {
438
+ const c = this.downCursorInstances[j];
439
+ for (let k = 0; k < c.size; ++k) {
440
+ if (c.time[k] <
441
+ object.object.startTime - hitWindowLeniency) {
442
+ continue;
443
+ }
444
+ const deltaTime = Math.abs(hitTime - c.time[k]);
445
+ // Don't continue if it's past object hit time.
446
+ if (c.time[k] > hitTime) {
447
+ break;
448
+ }
449
+ if (smallestDeltaTime > deltaTime) {
450
+ smallestDeltaTime = deltaTime;
451
+ smallestIndex = j;
452
+ }
510
453
  }
511
454
  }
455
+ this.objectCursorIndexes.set(object.object.startTime, smallestIndex);
512
456
  }
513
- this.downCursorInstances[i] = cursorInstance;
514
457
  }
515
458
  }
516
459
  /**
@@ -520,132 +463,53 @@ class ThreeFingerChecker {
520
463
  */
521
464
  calculateNerfFactors() {
522
465
  const objects = this.map.objects;
523
- const objectData = this.data.hitObjectData;
524
- const isPrecise = this.data.convertedMods.some((m) => m instanceof osu_base_1.ModPrecise);
525
466
  // We only filter cursor instances that are above the strain threshold.
526
467
  // This minimalizes the amount of cursor instances to analyze.
527
468
  for (const beatmapSection of this.beatmapSections) {
528
- const dragIndex = beatmapSection.dragFingerIndex;
529
- const startTime = objects[beatmapSection.firstObjectIndex].object.startTime +
530
- (objectData[beatmapSection.firstObjectIndex].result !==
531
- __1.hitResult.RESULT_0
532
- ? objectData[beatmapSection.firstObjectIndex].accuracy
533
- : -this.hitWindow.hitWindowFor50(isPrecise));
534
- const endTime = objects[beatmapSection.lastObjectIndex].object.endTime +
535
- (objectData[beatmapSection.lastObjectIndex].result !==
536
- __1.hitResult.RESULT_0
537
- ? objectData[beatmapSection.lastObjectIndex].accuracy
538
- : this.hitWindow.hitWindowFor50(isPrecise));
539
- // Filter cursor instances during section.
540
- this.downCursorInstances.forEach((c) => {
541
- const i = c.time.findIndex((t) => t >= startTime);
542
- if (i !== -1) {
543
- c.size -= i;
544
- c.time.splice(0, i);
545
- c.x.splice(0, i);
546
- c.y.splice(0, i);
547
- c.id.splice(0, i);
548
- }
549
- });
550
- const cursorAmounts = [];
551
- const cursorVectorTimes = [];
552
- for (let i = 0; i < this.downCursorInstances.length; ++i) {
553
- // Do not include drag cursor instance.
554
- if (i === dragIndex) {
469
+ const cursorAmounts = osu_base_1.Utils.initializeArray(this.downCursorInstances.length, 0);
470
+ for (let i = beatmapSection.firstObjectIndex; i <= beatmapSection.lastObjectIndex; ++i) {
471
+ ++cursorAmounts[this.objectCursorIndexes.get(objects[i].object.startTime)];
472
+ }
473
+ let validCursorPressCount = 0;
474
+ let invalidCursorPressCount = 0;
475
+ const validIndexes = [beatmapSection.mainFingerIndex];
476
+ for (let i = 0; i < cursorAmounts.length; ++i) {
477
+ // Ignore the main cursor index.
478
+ if (i === beatmapSection.mainFingerIndex) {
555
479
  continue;
556
480
  }
557
- const cursorData = this.downCursorInstances[i];
558
- let amount = 0;
559
- for (let j = 0; j < cursorData.size; ++j) {
560
- if (cursorData.time[j] >= startTime &&
561
- cursorData.time[j] <= endTime) {
562
- ++amount;
563
- cursorVectorTimes.push({
564
- vector: new osu_base_1.Vector2(cursorData.x[j], cursorData.y[j]),
565
- time: cursorData.time[j],
566
- });
567
- }
568
- }
569
- cursorAmounts.push(amount);
570
- }
571
- // This index will be used to detect if a section is 3-fingered.
572
- // If the section is dragged, the dragged instance will be ignored,
573
- // hence why the index is 1 less than nondragged section.
574
- const fingerSplitIndex = dragIndex !== -1 ? 2 : 3;
575
- // Divide >=4th (3rd for drag) cursor instances with 1st + 2nd (+ 3rd for nondrag)
576
- // to check if the section is 3-fingered.
577
- const threeFingerRatio = cursorAmounts
578
- .slice(fingerSplitIndex)
579
- .reduce((acc, value) => acc + value, 0) /
580
- cursorAmounts
581
- .slice(0, fingerSplitIndex)
582
- .reduce((acc, value) => acc + value, 0);
583
- const similarPresses = [];
584
- for (const cursorVectorTime of cursorVectorTimes) {
585
- const pressIndex = similarPresses.findIndex((v) => v.vector.getDistance(cursorVectorTime.vector) <=
586
- this.cursorDistancingDistanceThreshold);
587
- if (pressIndex !== -1) {
588
- if (cursorVectorTime.time -
589
- similarPresses[pressIndex].lastTime >=
590
- this.cursorDistancingTimeThreshold) {
591
- similarPresses.splice(pressIndex, 1);
592
- similarPresses.push({
593
- vector: cursorVectorTime.vector,
594
- count: 1,
595
- lastTime: cursorVectorTime.time,
596
- });
597
- continue;
598
- }
599
- similarPresses[pressIndex].vector = cursorVectorTime.vector;
600
- similarPresses[pressIndex].lastTime = cursorVectorTime.time;
601
- ++similarPresses[pressIndex].count;
481
+ // Add 2f cursor presses to valid cursor count and the rest to invalid cursor count.
482
+ if (validIndexes.length <= 2) {
483
+ validCursorPressCount += cursorAmounts[i];
602
484
  }
603
485
  else {
604
- similarPresses.push({
605
- vector: cursorVectorTime.vector,
606
- count: 1,
607
- lastTime: cursorVectorTime.time,
608
- });
486
+ invalidCursorPressCount += cursorAmounts[i];
609
487
  }
488
+ validIndexes.push(i);
610
489
  }
611
- // Sort by highest count; assume the order is 3rd, 4th, 5th, ... finger
612
- const validPresses = similarPresses
613
- .filter((v) => v.count >= this.cursorDistancingCountThreshold)
614
- .sort((a, b) => {
615
- return b.count - a.count;
616
- })
617
- .slice(2);
618
490
  // Ignore cursor presses that are only 1 for now since they are very likely to be accidental
619
- if ((threeFingerRatio > this.threeFingerRatioThreshold &&
620
- cursorAmounts.filter((v) => v > 1).length > 3) ||
621
- validPresses.length > 0) {
491
+ if (invalidCursorPressCount / validCursorPressCount >
492
+ this.threeFingerRatioThreshold &&
493
+ cursorAmounts.filter((v) => v > 1).length > 3) {
622
494
  // Strain factor
623
495
  const objectCount = beatmapSection.lastObjectIndex -
624
496
  beatmapSection.firstObjectIndex +
625
497
  1;
626
498
  const strainFactor = Math.pow(objects
627
- .slice(beatmapSection.firstObjectIndex, beatmapSection.lastObjectIndex)
499
+ .slice(beatmapSection.firstObjectIndex, beatmapSection.lastObjectIndex + 1)
628
500
  .reduce((acc, value) => acc +
629
501
  value.originalTapStrain /
630
- ThreeFingerChecker.strainThreshold, 0), 0.85);
631
- // We can ignore the first 3 (2 for drag) filled cursor instances
632
- // since they are guaranteed not 3 finger.
633
- const threeFingerCursorAmounts = cursorAmounts
634
- .slice(fingerSplitIndex)
635
- .filter((amount) => amount > 0);
502
+ ThreeFingerChecker.strainThreshold, 0), 0.875);
636
503
  // Finger factor applies more penalty if more fingers were used.
637
- const fingerFactor = threeFingerRatio > this.threeFingerRatioThreshold
638
- ? threeFingerCursorAmounts.reduce((acc, value, index) => acc +
639
- Math.pow(((index + 1) * value * objectCount) /
640
- this.strainNoteCount, 0.8), 1)
641
- : Math.pow(validPresses.reduce((acc, value, index) => acc +
642
- Math.pow(((index + 1) *
643
- (value.count /
644
- (this
645
- .cursorDistancingCountThreshold *
646
- 2)) *
647
- objectCount) /
648
- this.strainNoteCount, 0.2), 1), 0.2);
504
+ let fingerFactor = 1;
505
+ for (let i = 0, penaltyIndex = 1; i < cursorAmounts.length; ++i) {
506
+ if (validIndexes.includes(i)) {
507
+ continue;
508
+ }
509
+ fingerFactor += Math.pow((penaltyIndex * cursorAmounts[i] * objectCount) /
510
+ this.strainNoteCount, 0.8);
511
+ ++penaltyIndex;
512
+ }
649
513
  // Length factor applies more penalty if there are more 3-fingered object.
650
514
  const lengthFactor = 1 + Math.pow(objectCount / this.strainNoteCount, 1.2);
651
515
  this.nerfFactors.push({
@@ -672,4 +536,4 @@ exports.ThreeFingerChecker = ThreeFingerChecker;
672
536
  *
673
537
  * Increasing this number will result in less sections being flagged.
674
538
  */
675
- ThreeFingerChecker.strainThreshold = 175;
539
+ ThreeFingerChecker.strainThreshold = 200;
@@ -3,13 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ThreeFingerBeatmapSection = void 0;
4
4
  const BeatmapSection_1 = require("./BeatmapSection");
5
5
  /**
6
- * A section of a beatmap. This is used to detect dragged sections.
6
+ * A section of a beatmap with extra information used for detecting three-finger usage.
7
7
  */
8
8
  class ThreeFingerBeatmapSection extends BeatmapSection_1.BeatmapSection {
9
9
  constructor(values) {
10
10
  super(values.firstObjectIndex, values.lastObjectIndex);
11
- this.isDragged = values.isDragged;
12
- this.dragFingerIndex = values.dragFingerIndex;
11
+ this.mainFingerIndex = values.mainFingerIndex;
13
12
  }
14
13
  }
15
14
  exports.ThreeFingerBeatmapSection = ThreeFingerBeatmapSection;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rian8337/osu-droid-replay-analyzer",
3
- "version": "1.4.12-alpha.4",
3
+ "version": "1.4.12-alpha.7",
4
4
  "description": "A replay analyzer for analyzing osu!droid replay files.",
5
5
  "keywords": [
6
6
  "osu",
@@ -45,5 +45,5 @@
45
45
  "publishConfig": {
46
46
  "access": "public"
47
47
  },
48
- "gitHead": "f8898821f7630859d2d826692fcda1959ec0bfa1"
48
+ "gitHead": "3db8ea957f7c4f6bf018224232f9db054b90f9b8"
49
49
  }