From d72d788974f540fb09045b2cad5c12816b1aae0a Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Fri, 4 Nov 2022 20:21:42 +0000 Subject: [PATCH 1/2] feat: inherit lamp graphs and bp for bms pbs --- .../framework/pb/create-pb-doc.test.ts | 97 ++++++++++++++++ .../framework/pb/create-pb-doc.ts | 20 +++- .../framework/pb/game-specific-merge.ts | 109 +++++++++++------- 3 files changed, 183 insertions(+), 43 deletions(-) diff --git a/server/src/lib/score-import/framework/pb/create-pb-doc.test.ts b/server/src/lib/score-import/framework/pb/create-pb-doc.test.ts index fe24ddf09..9cadb2444 100644 --- a/server/src/lib/score-import/framework/pb/create-pb-doc.test.ts +++ b/server/src/lib/score-import/framework/pb/create-pb-doc.test.ts @@ -6,6 +6,7 @@ import { GetGamePTConfig } from "tachi-common"; import t from "tap"; import ResetDBState from "test-utils/resets"; import { Testing511SPA, TestingBMS7KScore, TestingIIDXSPScore } from "test-utils/test-data"; +import type { PBScoreDocumentNoRank } from "./create-pb-doc"; import type { KtLogger } from "lib/logger/logger"; const IIDXScore = TestingIIDXSPScore; @@ -193,5 +194,101 @@ t.test("#CreatePBDoc", (t) => { t.end(); }); + t.test("(BMS) Should inherit BP from the best BP score.", async (t) => { + await db.scores.remove({}); + await db.scores.insert([ + TestingBMS7KScore, + deepmerge(TestingBMS7KScore, { + scoreData: { + lamp: "FULL COMBO", + lampIndex: lamps.indexOf("FULL COMBO"), + score: 0, + percent: 0, + hitMeta: { + bp: 15, + }, + }, + calculatedData: { + sieglinde: 500, + }, + scoreID: "LAMP_PB_ID", + }), + deepmerge(TestingBMS7KScore, { + scoreData: { + hitMeta: { + bp: 1, + }, + }, + scoreID: "BP_PB_ID", + }), + ]); + + const res = (await CreatePBDoc(1, TestingBMS7KScore.chartID, logger)) as + | PBScoreDocumentNoRank<"bms:7K" | "bms:14K"> + | undefined; + + t.not(res, undefined, "Should actually return something."); + + t.equal( + res?.scoreData.hitMeta.bp, + 1, + "Should select the best BP's BP and not the score PBs." + ); + + t.strictSame(res?.composedFrom, { + lampPB: "LAMP_PB_ID", + scorePB: TestingBMS7KScore.scoreID, + other: [ + { + name: "Best BP", + scoreID: "BP_PB_ID", + }, + ], + }); + + t.end(); + }); + + t.test("(BMS) Should inherit graph data from the best lamp.", async (t) => { + await db.scores.remove({}); + await db.scores.insert([ + TestingBMS7KScore, + deepmerge(TestingBMS7KScore, { + scoreData: { + lamp: "FULL COMBO", + lampIndex: lamps.indexOf("FULL COMBO"), + score: 0, + percent: 0, + hitMeta: { + bp: 15, + gauge: 12, + gaugeHistory: [20, 20, 21, 12], + }, + }, + calculatedData: { + sieglinde: 500, + }, + scoreID: "LAMP_PB_ID", + }), + ]); + + const res = (await CreatePBDoc(1, TestingBMS7KScore.chartID, logger)) as + | PBScoreDocumentNoRank<"bms:7K" | "bms:14K"> + | undefined; + + t.not(res, undefined, "Should actually return something."); + + t.hasStrict( + res?.scoreData.hitMeta, + { + gauge: 12, + gaugeHistory: [20, 20, 21, 12], + }, + "Should select the lampPBs gauge data and not the score PBs." + ); + + t.end(); + }); + t.end(); }); diff --git a/server/src/lib/score-import/framework/pb/create-pb-doc.ts b/server/src/lib/score-import/framework/pb/create-pb-doc.ts index 5fd06d12c..070941091 100644 --- a/server/src/lib/score-import/framework/pb/create-pb-doc.ts +++ b/server/src/lib/score-import/framework/pb/create-pb-doc.ts @@ -1,17 +1,28 @@ import { BMSMergeFn, IIDXMergeFn, + PMSMergeFn, PopnMergeFn, SDVXMergeFn, USCMergeFn, } from "./game-specific-merge"; import db from "external/mongo/db"; -import { GetEveryonesRivalIDs, GetRivalIDs, GetRivalUsers } from "lib/rivals/rivals"; +import { GetEveryonesRivalIDs } from "lib/rivals/rivals"; import type { KtLogger } from "lib/logger/logger"; import type { BulkWriteUpdateOneOperation } from "mongodb"; -import type { Game, integer, PBScoreDocument, Playtype, ScoreDocument } from "tachi-common"; +import type { + Game, + IDStrings, + integer, + PBScoreDocument, + Playtype, + ScoreDocument, +} from "tachi-common"; -export type PBScoreDocumentNoRank = Omit; +export type PBScoreDocumentNoRank = Omit< + PBScoreDocument, + "rankingData" +>; export async function CreatePBDoc(userID: integer, chartID: string, logger: KtLogger) { const scorePB = await db.scores.findOne( @@ -200,8 +211,9 @@ function GetGameSpecificMergeFn(game: Game) { case "popn": return PopnMergeFn; case "bms": - case "pms": return BMSMergeFn; + case "pms": + return PMSMergeFn; default: return null; } diff --git a/server/src/lib/score-import/framework/pb/game-specific-merge.ts b/server/src/lib/score-import/framework/pb/game-specific-merge.ts index c1b43778e..2b51d254a 100644 --- a/server/src/lib/score-import/framework/pb/game-specific-merge.ts +++ b/server/src/lib/score-import/framework/pb/game-specific-merge.ts @@ -25,44 +25,7 @@ export async function IIDXMergeFn( DeleteUndefinedProps(pbDoc.scoreData.hitMeta); - // bad+poor PB document. This is a weird, third indepdenent metric that IIDX players sometimes care about. - const bpPB = (await db.scores.findOne( - { - userID: scorePB.userID, - chartID: scorePB.chartID, - "scoreData.hitMeta.bp": { $exists: true }, - }, - { - sort: { - // bp 0 is the best BP, bp 1 is worse, so on - "scoreData.hitMeta.bp": 1, - }, - } - )) as ScoreDocument<"iidx:DP" | "iidx:SP"> | null; - - if (!bpPB) { - logger.verbose( - `Could not find BP PB for ${scorePB.userID} ${scorePB.chartID} in PB joining. User likely has no scores with BP defined.`, - { pbDoc } - ); - - // this isn't actually an error! we just don't have to do anything. - return true; - } - - // by default scorePB is chosen for hitMeta fields, so, we can skip any assignments here by returning here. - if (bpPB.scoreID === scorePB.scoreID) { - logger.debug(`Skipped merging BP PB as scorePB was also BP PB.`); - return true; - } else if (bpPB.scoreID === lampPB.scoreID) { - pbDoc.scoreData.hitMeta.bp = lampPB.scoreData.hitMeta.bp; - logger.debug(`Skipped adding BP PB as composedFrom because lampPB was also BP PB.`); - return true; - } - - pbDoc.scoreData.hitMeta.bp = bpPB.scoreData.hitMeta.bp!; - - pbDoc.composedFrom.other = [{ name: "Best BP", scoreID: bpPB.scoreID }]; + await MergeBPPB(pbDoc, scorePB, lampPB, logger); return true; } @@ -78,7 +41,23 @@ export function PopnMergeFn( return true; } -export function BMSMergeFn( +export async function BMSMergeFn( + pbDoc: PBScoreDocument<"bms:7K" | "bms:14K">, + scorePB: ScoreDocument<"bms:7K" | "bms:14K">, + lampPB: ScoreDocument<"bms:7K" | "bms:14K">, + logger: KtLogger +) { + pbDoc.calculatedData.sieglinde = lampPB.calculatedData.sieglinde; + + pbDoc.scoreData.hitMeta.gaugeHistory = lampPB.scoreData.hitMeta.gaugeHistory; + pbDoc.scoreData.hitMeta.gauge = lampPB.scoreData.hitMeta.gauge; + + await MergeBPPB(pbDoc, scorePB, lampPB, logger); + + return true; +} + +export function PMSMergeFn( pbDoc: PBScoreDocument<"bms:7K" | "bms:14K">, scorePB: ScoreDocument<"bms:7K" | "bms:14K">, lampPB: ScoreDocument<"bms:7K" | "bms:14K">, @@ -168,3 +147,55 @@ export async function SDVXMergeFn( return true; } + +/** + * Given typical PB-Merge information, fetch the best `bp` for this user's scores + * on this chart and merge it with the `pbDoc` if it's large enough. + * + * @returns NOTHING, mutates original input. + */ +async function MergeBPPB( + pbDoc: PBScoreDocument<"bms:7K" | "bms:14K" | "iidx:DP" | "iidx:SP">, + scorePB: ScoreDocument<"bms:7K" | "bms:14K" | "iidx:DP" | "iidx:SP">, + lampPB: ScoreDocument<"bms:7K" | "bms:14K" | "iidx:DP" | "iidx:SP">, + logger: KtLogger +) { + // bad+poor PB document. This is a weird, third indepdenent metric that IIDX players sometimes care about. + const bpPB = (await db.scores.findOne( + { + userID: scorePB.userID, + chartID: scorePB.chartID, + "scoreData.hitMeta.bp": { $exists: true }, + }, + { + sort: { + // bp 0 is the best BP, bp 1 is worse, so on + "scoreData.hitMeta.bp": 1, + }, + } + )) as ScoreDocument<"iidx:DP" | "iidx:SP"> | null; + + if (!bpPB) { + logger.verbose( + `Could not find BP PB for ${scorePB.userID} ${scorePB.chartID} in PB joining. User likely has no scores with BP defined.`, + { pbDoc } + ); + + // this isn't actually an error! we just don't have to do anything. + return; + } + + // by default scorePB is chosen for hitMeta fields, so, we can skip any assignments here by returning here. + if (bpPB.scoreID === scorePB.scoreID) { + logger.debug(`Skipped merging BP PB as scorePB was also BP PB.`); + return true; + } else if (bpPB.scoreID === lampPB.scoreID) { + pbDoc.scoreData.hitMeta.bp = lampPB.scoreData.hitMeta.bp; + logger.debug(`Skipped adding BP PB as composedFrom because lampPB was also BP PB.`); + return; + } + + pbDoc.scoreData.hitMeta.bp = bpPB.scoreData.hitMeta.bp; + + pbDoc.composedFrom.other = [{ name: "Best BP", scoreID: bpPB.scoreID }]; +} From 7a6f088d002ecab7ad93089541b85c5b66cf0883 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Fri, 4 Nov 2022 20:25:53 +0000 Subject: [PATCH 2/2] feat: make PMS merge BP aswell --- .../framework/pb/create-pb-doc.test.ts | 61 +++++++++++++++++++ .../framework/pb/game-specific-merge.ts | 26 +++++--- 2 files changed, 79 insertions(+), 8 deletions(-) diff --git a/server/src/lib/score-import/framework/pb/create-pb-doc.test.ts b/server/src/lib/score-import/framework/pb/create-pb-doc.test.ts index 9cadb2444..e14940017 100644 --- a/server/src/lib/score-import/framework/pb/create-pb-doc.test.ts +++ b/server/src/lib/score-import/framework/pb/create-pb-doc.test.ts @@ -8,6 +8,7 @@ import ResetDBState from "test-utils/resets"; import { Testing511SPA, TestingBMS7KScore, TestingIIDXSPScore } from "test-utils/test-data"; import type { PBScoreDocumentNoRank } from "./create-pb-doc"; import type { KtLogger } from "lib/logger/logger"; +import type { ScoreDocument } from "tachi-common"; const IIDXScore = TestingIIDXSPScore; @@ -249,6 +250,66 @@ t.test("#CreatePBDoc", (t) => { t.end(); }); + t.test("(PMS) Should inherit BP from the best BP score.", async (t) => { + const pmsScore = deepmerge(TestingBMS7KScore, { + game: "pms", + playtype: "Controller", + }) as unknown as ScoreDocument<"pms:Controller">; + + await db.scores.remove({}); + await db.scores.insert([ + pmsScore, + deepmerge(pmsScore, { + scoreData: { + lamp: "FULL COMBO", + lampIndex: lamps.indexOf("FULL COMBO"), + score: 0, + percent: 0, + hitMeta: { + bp: 15, + }, + }, + calculatedData: { + sieglinde: 500, + }, + scoreID: "LAMP_PB_ID", + }), + deepmerge(pmsScore, { + scoreData: { + hitMeta: { + bp: 1, + }, + }, + scoreID: "BP_PB_ID", + }), + ]); + + const res = (await CreatePBDoc(1, TestingBMS7KScore.chartID, logger)) as + | PBScoreDocumentNoRank<"bms:7K" | "bms:14K"> + | undefined; + + t.not(res, undefined, "Should actually return something."); + + t.equal( + res?.scoreData.hitMeta.bp, + 1, + "Should select the best BP's BP and not the score PBs." + ); + + t.strictSame(res?.composedFrom, { + lampPB: "LAMP_PB_ID", + scorePB: TestingBMS7KScore.scoreID, + other: [ + { + name: "Best BP", + scoreID: "BP_PB_ID", + }, + ], + }); + + t.end(); + }); + t.test("(BMS) Should inherit graph data from the best lamp.", async (t) => { await db.scores.remove({}); await db.scores.insert([ diff --git a/server/src/lib/score-import/framework/pb/game-specific-merge.ts b/server/src/lib/score-import/framework/pb/game-specific-merge.ts index 2b51d254a..b3a090767 100644 --- a/server/src/lib/score-import/framework/pb/game-specific-merge.ts +++ b/server/src/lib/score-import/framework/pb/game-specific-merge.ts @@ -57,14 +57,16 @@ export async function BMSMergeFn( return true; } -export function PMSMergeFn( - pbDoc: PBScoreDocument<"bms:7K" | "bms:14K">, - scorePB: ScoreDocument<"bms:7K" | "bms:14K">, - lampPB: ScoreDocument<"bms:7K" | "bms:14K">, - _logger: KtLogger +export async function PMSMergeFn( + pbDoc: PBScoreDocument<"pms:Controller" | "pms:Keyboard">, + scorePB: ScoreDocument<"pms:Controller" | "pms:Keyboard">, + lampPB: ScoreDocument<"pms:Controller" | "pms:Keyboard">, + logger: KtLogger ) { pbDoc.calculatedData.sieglinde = lampPB.calculatedData.sieglinde; + await MergeBPPB(pbDoc, scorePB, lampPB, logger); + return true; } @@ -148,6 +150,14 @@ export async function SDVXMergeFn( return true; } +type IDStringsWithBP = + | "bms:7K" + | "bms:14K" + | "iidx:DP" + | "iidx:SP" + | "pms:Controller" + | "pms:Keyboard"; + /** * Given typical PB-Merge information, fetch the best `bp` for this user's scores * on this chart and merge it with the `pbDoc` if it's large enough. @@ -155,9 +165,9 @@ export async function SDVXMergeFn( * @returns NOTHING, mutates original input. */ async function MergeBPPB( - pbDoc: PBScoreDocument<"bms:7K" | "bms:14K" | "iidx:DP" | "iidx:SP">, - scorePB: ScoreDocument<"bms:7K" | "bms:14K" | "iidx:DP" | "iidx:SP">, - lampPB: ScoreDocument<"bms:7K" | "bms:14K" | "iidx:DP" | "iidx:SP">, + pbDoc: PBScoreDocument, + scorePB: ScoreDocument, + lampPB: ScoreDocument, logger: KtLogger ) { // bad+poor PB document. This is a weird, third indepdenent metric that IIDX players sometimes care about.