diff --git a/common/src/types.ts b/common/src/types.ts index a4d93eb7b..651e8ead2 100644 --- a/common/src/types.ts +++ b/common/src/types.ts @@ -1500,7 +1500,7 @@ export type BatchManualScore = { difficulty?: undefined; // hack to stop ts from screaming when this is accessed sometimes } | { - matchType: "ddrSongHash" | "inGameID" | "songTitle" | "tachiSongID"; + matchType: "ddrSongHash" | "inGameID" | "sdvxInGameID" | "songTitle" | "tachiSongID"; difficulty: Difficulties[I]; } ); diff --git a/docs/docs/tachi-server/batch-manual/index.md b/docs/docs/tachi-server/batch-manual/index.md index 026787f1d..d461a2681 100644 --- a/docs/docs/tachi-server/batch-manual/index.md +++ b/docs/docs/tachi-server/batch-manual/index.md @@ -90,7 +90,7 @@ The properties are described as this: #### Match Type -There are five match types, and they all use identifier +There are many match types, and they all use identifier in a different way. - songTitle @@ -124,23 +124,31 @@ This match type can only be used for BMS. This looks for the chart SHA1 that USC uses. As expected, this can only be used for USC. -- ddrSongHash - -This looks for the DDR 'song hash'. This is an identifier -used on the e-amusement website, and references a song, -not a chart. As such: -This match type *necessitates* that `difficulty` be defined -and set to a valid difficulty for DDR. - -This match type can only be used for DDR. - - inGameID This uses the in-game-ID for this **SONG**. You, therefore, **MUST** specify the difficulty for this chart aswell. -At the moment, this match type can only be used on `iidx`. -This is expected to change before release. +This is supported for the following games: + +- IIDX +- Pop'n Music +- Jubeat +- CHUNITHM +- GITADORA +- maimai +- MUSECA + +- sdvxInGameID + +This uses the in-game-ID for this SDVX song. You must specify +the difficulty for this chart aswell. + +The reason SDVX gets its own special `matchType` is because this +matchType supports `difficulty: "ANY_INF"`. This special difficulty +means that it will check for any of `INF/GRV/HVN/VVD/XCD` for this song. + +This is useful for services that store all of those as the same difficulty. ## Example diff --git a/server/src/lib/score-import/import-types/common/batch-manual/converter.test.ts b/server/src/lib/score-import/import-types/common/batch-manual/converter.test.ts index 6f2424bf8..c9ee1cf1b 100644 --- a/server/src/lib/score-import/import-types/common/batch-manual/converter.test.ts +++ b/server/src/lib/score-import/import-types/common/batch-manual/converter.test.ts @@ -1,13 +1,20 @@ import { ConverterBatchManual, ResolveChartFromSong, ResolveMatchTypeToKTData } from "./converter"; import { InvalidScoreFailure } from "../../../framework/common/converter-failures"; import deepmerge from "deepmerge"; +import db from "external/mongo/db"; import CreateLogCtx from "lib/logger/logger"; import t from "tap"; import ResetDBState from "test-utils/resets"; -import { BMSGazerChart, BMSGazerSong, Testing511Song, Testing511SPA } from "test-utils/test-data"; +import { + BMSGazerChart, + BMSGazerSong, + Testing511Song, + Testing511SPA, + TestingSDVXAlbidaChart, +} from "test-utils/test-data"; import { EscapeStringRegexp } from "utils/misc"; import type { BatchManualContext } from "./types"; -import type { BatchManualScore, Game } from "tachi-common"; +import type { BatchManualScore, ChartDocument, Game } from "tachi-common"; const baseBatchManualScore = { score: 500, @@ -103,6 +110,79 @@ t.test("#ResolveMatchTypeToKTData", (t) => { t.end(); }); + t.test("Should resolve for the sdvx inGameID if matchType is sdvxInGameID", async (t) => { + const res = await ResolveMatchTypeToKTData( + { + matchType: "sdvxInGameID", + identifier: "1", + difficulty: "ADV", + lamp: "CLEAR", + score: 9_000_001, + }, + context, + importType, + logger + ); + + t.hasStrict( + res, + { song: { id: 1 }, chart: { data: { inGameID: 1 } } }, + "Should return the right song and chart." + ); + + t.rejects( + () => + ResolveMatchTypeToKTData( + { + matchType: "sdvxInGameID", + identifier: "9999999", + difficulty: "ADV", + lamp: "CLEAR", + score: 9_000_001, + }, + context, + importType, + logger + ), + ktdWrap("Cannot find SDVX chart with inGameID 9999999") + ); + + t.end(); + }); + + t.test("Should support ANY_INF if matchType is sdvxInGameID", async (t) => { + await db.charts.sdvx.insert( + deepmerge(TestingSDVXAlbidaChart, { + chartID: "fake_xcd", + data: { + arcChartID: "fake222", + }, + difficulty: "XCD", + } as ChartDocument<"sdvx:Single">) + ); + + const res = await ResolveMatchTypeToKTData( + { + matchType: "sdvxInGameID", + identifier: "1", + difficulty: "ANY_INF", + lamp: "CLEAR", + score: 9_000_001, + }, + context, + importType, + logger + ); + + t.hasStrict( + res, + { song: { id: 1 }, chart: { data: { inGameID: 1 }, difficulty: "XCD" } }, + "Should return the right song and chart." + ); + + t.end(); + }); + t.test("Should resolve for the bms chartHash if the matchType is bmsChartHash", async (t) => { const GAZER17MD5 = "38616b85332037cc12924f2ae2840262"; const GAZER17SHA256 = "195fe1be5c3e74fccd04dc426e05f8a9cfa8a1059c339d0a23e99f63661f0b7d"; 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 42c19d2e5..f3723f6bf 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 @@ -9,12 +9,15 @@ import { AssertStrAsPositiveInt, } from "../../../framework/common/string-asserts"; import db from "external/mongo/db"; +import { GetGamePTConfig } from "tachi-common"; import { FindBMSChartOnHash, FindChartWithPTDF, FindChartWithPTDFVersion, FindDDRChartOnSongHash, FindITGChartOnHash, + FindSDVXChartOnInGameID, + FindSDVXChartOnInGameIDVersion, } from "utils/queries/charts"; import { FindSongOnID, FindSongOnTitleInsensitive } from "utils/queries/songs"; import type { DryScore } from "../../../framework/common/types"; @@ -28,6 +31,8 @@ import type { IDStrings, ImportTypes, SongDocument, + Difficulties, + GPTSupportedVersions, } from "tachi-common"; /** @@ -312,12 +317,71 @@ export async function ResolveMatchTypeToKTData( return { song, chart }; } + case "sdvxInGameID": { + if (game !== "sdvx") { + throw new InvalidScoreFailure( + `A matchType of sdvxInGameID is only supported by SDVX.` + ); + } + + let chart: ChartDocument | null; + + const identifier = Number(data.identifier); + + const config = GetGamePTConfig("sdvx", "Single"); + + if (!config.difficulties.includes(data.difficulty) && data.difficulty !== "ANY_INF") { + throw new InvalidScoreFailure( + `Invalid difficulty '${ + data.difficulty + }', Expected any of ${config.difficulties.join(", ")} or ANY_INF` + ); + } + + const diff = data.difficulty as Difficulties["sdvx:Single"] | "ANY_INF"; + + if (context.version) { + if (!config.supportedVersions.includes(context.version)) { + throw new InvalidScoreFailure( + `Unsupported version ${ + context.version + }. Expected any of ${config.supportedVersions.join(", ")}.` + ); + } + + chart = await FindSDVXChartOnInGameIDVersion( + identifier, + diff, + context.version as GPTSupportedVersions["sdvx:Single"] + ); + } else { + chart = await FindSDVXChartOnInGameID(identifier, diff); + } + + if (!chart) { + throw new KTDataNotFoundFailure( + `Cannot find SDVX chart with inGameID ${identifier}, difficulty ${diff} and version ${context.version}.`, + importType, + data, + context + ); + } + + const song = await db.songs[game].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 }; + } + case "inGameID": { const gamesWithInGameIDSupport = [ "iidx", "popn", "ddr", - "sdvx", "jubeat", "chunithm", "gitadora", @@ -325,6 +389,12 @@ export async function ResolveMatchTypeToKTData( "museca", ]; + if (game === "sdvx") { + throw new InvalidScoreFailure( + `Cannot use inGameID as a matchType for SDVX. Use matchType: 'sdvxInGameID' instead.` + ); + } + if (!gamesWithInGameIDSupport.includes(game)) { throw new InvalidScoreFailure( `Cannot use inGameID on game ${game}. The game may not have a concept of an in game ID, or support just might not exist yet.` @@ -334,7 +404,6 @@ export async function ResolveMatchTypeToKTData( let identifier: number | string = data.identifier; // ddr uses weird strings as IDs instead of numbers - if (game !== "ddr") { identifier = Number(data.identifier); } diff --git a/server/src/test-utils/test-data.ts b/server/src/test-utils/test-data.ts index 96e8b50b9..6a6629fa2 100644 --- a/server/src/test-utils/test-data.ts +++ b/server/src/test-utils/test-data.ts @@ -167,6 +167,23 @@ export const TestingSDVXScore: ScoreDocument<"sdvx:Single"> = { importType: "ir/direct-manual", }; +export const TestingSDVXAlbidaChart: ChartDocument<"sdvx:Single"> = { + rgcID: null, + chartID: "5088a4d0e1ee9d0cc2f625934306e45b1a60699b", + difficulty: "ADV", + songID: 1, + playtype: "Single", + levelNum: 10, + level: "10", + data: { + inGameID: 1, + arcChartID: "EOZ7FixCDpv", + }, + isPrimary: true, + versions: ["booth", "inf", "gw", "heaven", "vivid", "exceed", "konaste"], + tierlistInfo: {}, +}; + export const TestingKsHookSV6CScore: KsHookSV6CScore = { clear: "CLEAR_EXCESSIVE", critical: 1184, diff --git a/server/src/utils/queries/charts.ts b/server/src/utils/queries/charts.ts index 9c3ceabc7..a56863ffd 100644 --- a/server/src/utils/queries/charts.ts +++ b/server/src/utils/queries/charts.ts @@ -197,7 +197,7 @@ export function FindSDVXChartOnInGameID( ) { const diffQuery = difficulty === "ANY_INF" - ? { $in: ["INF", "GRV", "HVN", "VVD"] as Array } + ? { $in: ["INF", "GRV", "HVN", "VVD", "XCD"] as Array } : difficulty; return db.charts.sdvx.findOne({ @@ -209,12 +209,12 @@ export function FindSDVXChartOnInGameID( export function FindSDVXChartOnInGameIDVersion( inGameID: number, - difficulty: "ADV" | "ANY_INF" | "EXH" | "MXM" | "NOV", + difficulty: Difficulties["sdvx:Single"] | "ANY_INF", version: GPTSupportedVersions["sdvx:Single"] ) { const diffQuery = difficulty === "ANY_INF" - ? { $in: ["INF", "GRV", "HVN", "VVD"] as Array } + ? { $in: ["INF", "GRV", "HVN", "VVD", "XCD"] as Array } : difficulty; return db.charts.sdvx.findOne({ @@ -226,12 +226,12 @@ export function FindSDVXChartOnInGameIDVersion( export function FindSDVXChartOnDFVersion( songID: integer, - difficulty: "ADV" | "ANY_INF" | "EXH" | "MXM" | "NOV", + difficulty: Difficulties["sdvx:Single"] | "ANY_INF", version: GPTSupportedVersions["sdvx:Single"] ) { const diffQuery = difficulty === "ANY_INF" - ? { $in: ["INF", "GRV", "HVN", "VVD"] as Array } + ? { $in: ["INF", "GRV", "HVN", "VVD", "XCD"] as Array } : difficulty; return db.charts.sdvx.findOne({