diff --git a/packages/tosu/src/api/types/v2.ts b/packages/tosu/src/api/types/v2.ts index dc8fcfda..9855c76f 100644 --- a/packages/tosu/src/api/types/v2.ts +++ b/packages/tosu/src/api/types/v2.ts @@ -368,6 +368,10 @@ export interface Pp { fc: number; maxAchievedThisPlay: number; } +export interface Pp2 { + current: number; + fc: number; +} export interface Leaderboard { isFailed: boolean; @@ -438,6 +442,7 @@ export interface Series { } export interface ResultsScreen { + playerName: string; mode: NumberName; score: number; accuracy: number; @@ -446,6 +451,7 @@ export interface ResultsScreen { mods: NumberName; maxCombo: number; rank: string; + pp: Pp2; createdAt: string; } diff --git a/packages/tosu/src/api/utils/buildResultV2.ts b/packages/tosu/src/api/utils/buildResultV2.ts index 3f4275e9..62638b54 100644 --- a/packages/tosu/src/api/utils/buildResultV2.ts +++ b/packages/tosu/src/api/utils/buildResultV2.ts @@ -389,18 +389,17 @@ export const buildResult = (instanceManager: InstanceManager): ApiAnswer => { graph: beatmapPpData.strainsAll }, resultsScreen: { + playerName: resultsScreenData.PlayerName, + mode: { - number: gamePlayData.Mode, - name: Modes[gamePlayData.Mode] || '' + number: resultsScreenData.Mode, + name: Modes[resultsScreenData.Mode] || '' }, score: resultsScreenData.Score, - accuracy: calculateAccuracy({ - hits: resultScreenHits, - mode: gamePlayData.Mode - }), + accuracy: resultsScreenData.Accuracy, - name: resultsScreenData.PlayerName, + name: resultsScreenData.PlayerName, // legacy, remove it later hits: resultScreenHits, mods: { number: resultsScreenData.Mods, @@ -408,6 +407,10 @@ export const buildResult = (instanceManager: InstanceManager): ApiAnswer => { }, maxCombo: resultsScreenData.MaxCombo, rank: resultsScreenData.Grade, + pp: { + current: resultsScreenData.pp, + fc: resultsScreenData.fcPP + }, createdAt: resultsScreenData.Date }, folders: { diff --git a/packages/tosu/src/entities/ResultsScreenData/index.ts b/packages/tosu/src/entities/ResultsScreenData/index.ts index 1b79e419..ebf02f48 100644 --- a/packages/tosu/src/entities/ResultsScreenData/index.ts +++ b/packages/tosu/src/entities/ResultsScreenData/index.ts @@ -1,8 +1,13 @@ +import { Calculator } from '@kotrikd/rosu-pp'; import { wLogger } from '@tosu/common'; import { AbstractEntity } from '@/entities/AbstractEntity'; import { OsuInstance } from '@/objects/instanceManager/osuInstance'; -import { calculateGrade } from '@/utils/calculators'; +import { + calculateAccuracy, + calculateGrade, + calculatePassedObjects +} from '@/utils/calculators'; import { netDateBinaryToDate } from '@/utils/converters'; import { OsuMods } from '@/utils/osuMods.types'; @@ -20,6 +25,11 @@ export class ResultsScreenData extends AbstractEntity { HitMiss: number; Grade: string; Date: string; + Accuracy: number; + pp: number; + fcPP: number; + + previousBeatmap: string; constructor(osuInstance: OsuInstance) { super(osuInstance); @@ -42,15 +52,20 @@ export class ResultsScreenData extends AbstractEntity { this.HitKatu = 0; this.HitMiss = 0; this.Date = ''; + this.Accuracy = 0; + this.pp = 0; + this.fcPP = 0; } updateState() { try { - const { process, patterns, allTimesData } = + const { process, patterns, allTimesData, beatmapPpData, menuData } = this.osuInstance.getServices([ 'process', 'patterns', - 'allTimesData' + 'allTimesData', + 'beatmapPpData', + 'menuData' ]); if (process === null) { throw new Error('Process not found'); @@ -68,13 +83,13 @@ export class ResultsScreenData extends AbstractEntity { process.readInt(rulesetsAddr - 0xb) + 0x4 ); if (rulesetAddr === 0) { - wLogger.debug('RSD(init) rulesetAddr is zero'); + wLogger.debug('RSD(updateState) rulesetAddr is zero'); return; } const resultScreenBase = process.readInt(rulesetAddr + 0x38); if (resultScreenBase === 0) { - wLogger.debug('RSD(init) resultScreenBase is zero'); + wLogger.debug('RSD(updateState) resultScreenBase is zero'); return; } @@ -106,17 +121,25 @@ export class ResultsScreenData extends AbstractEntity { this.HitKatu = process.readShort(resultScreenBase + 0x90); // HitMiss int16 `mem:"[Ruleset + 0x38] + 0x92"` this.HitMiss = process.readShort(resultScreenBase + 0x92); + + const hits = { + 300: this.Hit300, + geki: 0, + 100: this.Hit100, + katu: 0, + 50: this.Hit50, + 0: this.HitMiss + }; + this.Grade = calculateGrade({ mods: this.Mods, mode: this.Mode, - hits: { - 300: this.Hit300, - geki: 0, - 100: this.Hit100, - katu: 0, - 50: this.Hit50, - 0: this.HitMiss - } + hits + }); + + this.Accuracy = calculateAccuracy({ + mode: this.Mode, + hits }); this.Date = netDateBinaryToDate( @@ -124,6 +147,49 @@ export class ResultsScreenData extends AbstractEntity { process.readInt(resultScreenBase + 0xa0) ).toISOString(); + const key = `${menuData.MD5}${this.Mods}${this.Mode}${this.PlayerName}`; + if (this.previousBeatmap === key) { + return; + } + + const currentBeatmap = beatmapPpData.getCurrentBeatmap(); + if (!currentBeatmap) { + wLogger.debug("RSD(updateState) can't get current map"); + return; + } + + const scoreParams = { + passedObjects: calculatePassedObjects( + this.Mode, + this.Hit300, + this.Hit100, + this.Hit50, + this.HitMiss, + this.HitKatu, + this.HitGeki + ), + combo: this.MaxCombo, + mods: this.Mods, + nMisses: this.HitMiss, + n50: this.Hit50, + n100: this.Hit100, + n300: this.Hit300 + }; + + const curPerformance = new Calculator(scoreParams).performance( + currentBeatmap + ); + const fcPerformance = new Calculator({ + combo: curPerformance.difficulty.maxCombo, + mods: this.Mods, + nMisses: 0, + acc: this.Accuracy + }).performance(currentBeatmap); + + this.pp = curPerformance.pp; + this.fcPP = fcPerformance.pp; + + this.previousBeatmap = key; this.resetReportCount('RSD(updateState)'); } catch (exc) { this.reportError( diff --git a/packages/tosu/src/objects/instanceManager/osuInstance.ts b/packages/tosu/src/objects/instanceManager/osuInstance.ts index e1f0706a..93d3724c 100644 --- a/packages/tosu/src/objects/instanceManager/osuInstance.ts +++ b/packages/tosu/src/objects/instanceManager/osuInstance.ts @@ -299,6 +299,11 @@ export class OsuInstance { resultsScreenData.init(); beatmapPpData.resetCurrentAttributes(); } + + // Reset ResultScreen if we in song select + if (resultsScreenData.PlayerName) { + resultsScreenData.init(); + } break; case 2: