mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-06 05:48:00 +03:00
ir/usc converters and parsers
This commit is contained in:
@@ -26,7 +26,7 @@ export interface USCClientScore {
|
||||
near: integer;
|
||||
error: integer;
|
||||
options: {
|
||||
gaugeType: integer;
|
||||
gaugeType: 0 | 1;
|
||||
gaugeOpt: integer;
|
||||
mirror: boolean;
|
||||
random: boolean;
|
||||
|
||||
@@ -6,6 +6,10 @@ import { ChartDocument, PBScoreDocument } from "kamaitachi-common";
|
||||
import { AssertStrAsPositiveNonZeroInt } from "../../../score-import/framework/common/string-asserts";
|
||||
import CreateLogCtx from "../../../common/logger";
|
||||
import { KtchiScoreToServerScore } from "./common";
|
||||
import { ExpressWrappedScoreImportMain } from "../../../score-import/framework/express-wrapper";
|
||||
import { GetUserWithID } from "../../../common/user";
|
||||
import { KtLogger } from "../../../types";
|
||||
import { ParseIRUSC } from "../../../score-import/import-types/ir/usc/parser";
|
||||
|
||||
const logger = CreateLogCtx("usc.ts");
|
||||
|
||||
@@ -57,6 +61,8 @@ const ValidateUSCRequest: RequestHandler = async (req, res, next) => {
|
||||
}
|
||||
|
||||
req[SYMBOL_KtchiData] = { uscAuthDoc };
|
||||
|
||||
return next();
|
||||
};
|
||||
|
||||
router.use(ValidateUSCRequest);
|
||||
@@ -225,6 +231,46 @@ router.get("/charts/:chartHash/leaderboard", RetrieveChart, async (req, res) =>
|
||||
* https://uscir.readthedocs.io/en/latest/endpoints/score-submit.html
|
||||
* @name POST /api/ir/usc/scores
|
||||
*/
|
||||
router.post("/scores", async (req, res) => {});
|
||||
router.post("/scores", async (req, res) => {
|
||||
if (typeof req.body.chart !== "object" || req.body.chart === null) {
|
||||
return res.status(200).json({
|
||||
statusCode: STATUS_CODES.BAD_REQ,
|
||||
description: "Invalid chart provided.",
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof req.body.chart.chartHash !== "string") {
|
||||
return res.status(200).json({
|
||||
statusCode: STATUS_CODES.BAD_REQ,
|
||||
description: "Invalid chart provided.",
|
||||
});
|
||||
}
|
||||
|
||||
const chartDoc = (await FindChartOnSHA256(
|
||||
"usc",
|
||||
req.body.chart.chartHash
|
||||
)) as ChartDocument<"usc:Single"> | null;
|
||||
|
||||
if (!chartDoc) {
|
||||
return res.status(200).json({
|
||||
statusCode: STATUS_CODES.CHART_REFUSE,
|
||||
description: "This chart is not supported.",
|
||||
});
|
||||
}
|
||||
|
||||
const userDoc = await GetUserWithID(req[SYMBOL_KtchiData]!.uscAuthDoc!.userID);
|
||||
|
||||
if (!userDoc) {
|
||||
logger.severe(`User ${req[SYMBOL_KtchiData]!.uscAuthDoc!.userID} as no parent userDoc?`);
|
||||
return res.status(200).json({
|
||||
statusCode: STATUS_CODES.SERVER_ERROR,
|
||||
description: "An internal server error has occured.",
|
||||
});
|
||||
}
|
||||
|
||||
const importParser = (logger: KtLogger) => ParseIRUSC(req.body, chartDoc, logger);
|
||||
|
||||
const importRes = await ExpressWrappedScoreImportMain(userDoc, false, "ir/usc", importParser);
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { USCClientScore } from "../../../../api/ir/usc/common";
|
||||
import { FindSongOnID } from "../../../../common/database-lookup/song";
|
||||
import { ConverterFunction, DryScore, KtLogger } from "../../../../types";
|
||||
import { InternalFailure } from "../../../framework/common/converter-failures";
|
||||
import { GenericGetGradeAndPercent } from "../../../framework/common/score-utils";
|
||||
import { IRUSCContext } from "./types";
|
||||
import { Lamps } from "kamaitachi-common";
|
||||
|
||||
function DeriveNoteMod(data: USCClientScore): "NORMAL" | "MIRROR" | "RANDOM" | "MIR-RAN" {
|
||||
if (data.options.mirror && data.options.random) {
|
||||
return "MIR-RAN";
|
||||
} else if (data.options.mirror) {
|
||||
return "MIRROR";
|
||||
} else if (data.options.random) {
|
||||
return "RANDOM";
|
||||
}
|
||||
|
||||
return "NORMAL";
|
||||
}
|
||||
|
||||
function DeriveLamp(scoreDoc: USCClientScore, logger: KtLogger): Lamps["usc:Single"] {
|
||||
if (scoreDoc.score === 10_000_000) {
|
||||
return "PERFECT ULTIMATE CHAIN";
|
||||
} else if (scoreDoc.error === 0) {
|
||||
return "ULTIMATE CHAIN";
|
||||
} else if (scoreDoc.options.gaugeType === 0) {
|
||||
return scoreDoc.gauge >= 70 ? "CLEAR" : "FAILED";
|
||||
} else if (scoreDoc.options.gaugeType === 1) {
|
||||
return scoreDoc.gauge > 0 ? "EXCESSIVE CLEAR" : "FAILED";
|
||||
}
|
||||
|
||||
logger.error(`Could not derive Lamp from Score Document`, { scoreDoc });
|
||||
throw new InternalFailure(`Could not derive Lamp from Score Document`);
|
||||
}
|
||||
|
||||
export const ConverterIRUSC: ConverterFunction<USCClientScore, IRUSCContext> = async (
|
||||
data,
|
||||
context,
|
||||
importType,
|
||||
logger
|
||||
) => {
|
||||
const song = await FindSongOnID("usc", context.chart.songID);
|
||||
|
||||
if (!song) {
|
||||
logger.severe(`Song-Chart desync on USCIR ${context.chart.songID}.`);
|
||||
throw new InternalFailure(`Song-Chart desync on USCIR ${context.chart.songID}.`);
|
||||
}
|
||||
|
||||
const { grade, percent } = GenericGetGradeAndPercent("usc", data.score, context.chart);
|
||||
|
||||
const dryScore: DryScore<"usc:Single"> = {
|
||||
comment: null,
|
||||
game: "usc",
|
||||
importType,
|
||||
timeAchieved: Date.now(),
|
||||
service: "USC-IR",
|
||||
scoreData: {
|
||||
grade,
|
||||
percent,
|
||||
score: data.score,
|
||||
lamp: DeriveLamp(data, logger),
|
||||
hitData: {
|
||||
critical: data.crit,
|
||||
near: data.near,
|
||||
miss: data.error,
|
||||
},
|
||||
hitMeta: {
|
||||
gauge: data.gauge,
|
||||
},
|
||||
},
|
||||
scoreMeta: {
|
||||
gaugeMod: data.options.gaugeOpt === 0 ? "NORMAL" : "HARD",
|
||||
noteMod: DeriveNoteMod(data),
|
||||
},
|
||||
};
|
||||
|
||||
return { chart: context.chart, song, dryScore };
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
import { KtLogger, ParserFunctionReturnsSync } from "../../../../types";
|
||||
import { ChartDocument } from "kamaitachi-common";
|
||||
import p, { PrudenceSchema } from "prudence";
|
||||
import { InvalidScoreFailure } from "../../../framework/common/converter-failures";
|
||||
import { FormatPrError } from "../../../../common/prudence";
|
||||
import { USCClientScore } from "../../../../api/ir/usc/common";
|
||||
import { IRUSCContext } from "./types";
|
||||
import { ConverterIRUSC } from "./converter";
|
||||
|
||||
const PR_USCIRScore: PrudenceSchema = {
|
||||
score: p.isBoundedInteger(0, 10_000_000),
|
||||
gauge: p.isBetween(0, 100),
|
||||
timestamp: p.isPositiveInteger,
|
||||
crit: p.isPositiveInteger,
|
||||
near: p.isPositiveInteger,
|
||||
error: p.isPositiveInteger,
|
||||
options: {
|
||||
gaugeType: p.isIn(0, 1),
|
||||
mirror: "boolean",
|
||||
random: "boolean",
|
||||
autoFlags: p.isInteger,
|
||||
},
|
||||
};
|
||||
|
||||
export function ParseIRUSC(
|
||||
body: Record<string, unknown>,
|
||||
chart: ChartDocument<"usc:Single">,
|
||||
logger: KtLogger
|
||||
): ParserFunctionReturnsSync<USCClientScore, IRUSCContext> {
|
||||
const err = p(
|
||||
body.score,
|
||||
PR_USCIRScore,
|
||||
{},
|
||||
{ throwOnNonObject: false, allowExcessKeys: true }
|
||||
);
|
||||
|
||||
if (err) {
|
||||
throw new InvalidScoreFailure(FormatPrError(err, "Invalid USC Score."));
|
||||
}
|
||||
|
||||
return {
|
||||
context: {
|
||||
chart,
|
||||
},
|
||||
game: "usc",
|
||||
iterable: [body.score] as USCClientScore[],
|
||||
classHandler: null,
|
||||
ConverterFunction: ConverterIRUSC,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { ChartDocument } from "kamaitachi-common";
|
||||
|
||||
export interface IRUSCContext {
|
||||
chart: ChartDocument<"usc:Single">;
|
||||
}
|
||||
Reference in New Issue
Block a user