From 77fd7088a4c2257ca12baf5cdb9962b5ed77a351 Mon Sep 17 00:00:00 2001 From: tranq88 <50079142+tranq88@users.noreply.github.com> Date: Wed, 4 Dec 2024 13:24:41 +0000 Subject: [PATCH] New match type: `ddrSongHash` --- common/src/config/game-support/ddr.ts | 3 +- common/src/lib/schemas.ts | 3 +- common/src/types/batch-manual.ts | 3 +- seeds/scripts/test/match-type.test.ts | 28 +++++++++++++ .../common/batch-manual/converter.ts | 40 +++++++++++++++++++ 5 files changed, 74 insertions(+), 3 deletions(-) diff --git a/common/src/config/game-support/ddr.ts b/common/src/config/game-support/ddr.ts index 41f6745af..adcca1881 100644 --- a/common/src/config/game-support/ddr.ts +++ b/common/src/config/game-support/ddr.ts @@ -201,12 +201,13 @@ export const DDR_SP_CONF = { chartData: z.strictObject({ inGameID: zodNonNegativeInt, stepCount: zodNonNegativeInt.optional(), + ddrSongHash: z.string().optional(), // optional because konaste-only songs have no hashes }), preferences: z.strictObject({}), scoreMeta: z.strictObject({}), - supportedMatchTypes: ["inGameID", "songTitle", "tachiSongID"], + supportedMatchTypes: ["inGameID", "songTitle", "tachiSongID", "ddrSongHash"], } as const satisfies INTERNAL_GAME_PT_CONFIG; export const DDR_DP_CONF = { diff --git a/common/src/lib/schemas.ts b/common/src/lib/schemas.ts index c5c2ed372..4a9d4f772 100644 --- a/common/src/lib/schemas.ts +++ b/common/src/lib/schemas.ts @@ -875,7 +875,8 @@ const PR_BATCH_MANUAL_SCORE = (game: Game, playtype: Playtype): PrudenceSchema = "inGameID", "inGameStrID", "uscChartHash", - "popnChartHash" + "popnChartHash", + "ddrSongHash" ), identifier: "string", comment: optNull(p.isBoundedString(3, 240)), diff --git a/common/src/types/batch-manual.ts b/common/src/types/batch-manual.ts index 5c4ff40f1..fd5c9c098 100644 --- a/common/src/types/batch-manual.ts +++ b/common/src/types/batch-manual.ts @@ -23,7 +23,8 @@ type MatchTypesWithDifficulty = | "inGameStrID" | "sdvxInGameID" | "songTitle" - | "tachiSongID"; + | "tachiSongID" + | "ddrSongHash"; export type MatchTypes = MatchTypesNoDifficulty | MatchTypesWithDifficulty; diff --git a/seeds/scripts/test/match-type.test.ts b/seeds/scripts/test/match-type.test.ts index ea649a47b..1421b2188 100644 --- a/seeds/scripts/test/match-type.test.ts +++ b/seeds/scripts/test/match-type.test.ts @@ -61,6 +61,34 @@ const MATCH_TYPE_CHECKS: Record< }, }, uscChartHash: { type: "CHARTS", fn: (c) => c.data.hashSHA1 }, + ddrSongHash: { + type: "CHARTS", + fn: (c) => { + // if there's no ddrSongHash then it must be a konaste song + // so just use the inGameID + if (c.data.ddrSongHash === undefined) { + return `${c.data.inGameID}-${c.difficulty}`; + } + + // edge case for the song "B4U (The Acolyte mix)" + // + // for some reason konmai decided to change its inGameID in DDR A3 + // so now there's an a20plus version and an a3 version + // (see songIDs 37225 and 37310) + // + // unfortunately, they decided to keep the same ddrSongHash across both! + // so we manually distinguish it here. + const B4UAcolyteSongHash = "01lbO69qQiP691ll6DIiqPbIdd9O806o"; + if (c.data.ddrSongHash === B4UAcolyteSongHash) { + if (c.versions[0] === "a20plus") { + return `${c.data.ddrSongHash}-${c.difficulty}-a20plus`; + } + } + // perhaps we could just merge the two "songs" together in the seeds to avoid all this? + + return `${c.data.ddrSongHash}-${c.difficulty}`; + }, + }, }; let exitCode = 0; diff --git a/server/src/lib/score-import/import-types/common/batch-manual/converter.ts b/server/src/lib/score-import/import-types/common/batch-manual/converter.ts index c5d164295..0034c1ade 100644 --- a/server/src/lib/score-import/import-types/common/batch-manual/converter.ts +++ b/server/src/lib/score-import/import-types/common/batch-manual/converter.ts @@ -432,6 +432,46 @@ export async function ResolveMatchTypeToTachiData( return { song, chart }; } + case "ddrSongHash": { + const difficulty = AssertStrAsDifficulty(data.difficulty, game, playtype); + + let chart: ChartDocument | null; + + if (context.version) { + chart = await db.anyCharts.ddr.findOne({ + "data.ddrSongHash": data.identifier, + playtype, + difficulty, + versions: context.version, + }); + } else { + chart = await db.anyCharts.ddr.findOne({ + "data.ddrSongHash": data.identifier, + playtype, + difficulty, + isPrimary: true, + }); + } + + if (!chart) { + throw new SongOrChartNotFoundFailure( + `Cannot find chart for ddrSongHash ${data.identifier} (${playtype}).`, + importType, + data, + context + ); + } + + const song = await db.anySongs.ddr.findOne({ id: chart.songID }); + + if (!song) { + logger.severe(`Song-Chart desync on ${chart.songID}.`); + throw new InternalFailure(`Failed to get song for a chart that exists.`); + } + + return { song, chart }; + } + default: { const { matchType } = data;