mirror of
https://github.com/zkldi/Tachi.git
synced 2026-09-29 10:28:04 +03:00
ir/beatoraja converters and parsers
This commit is contained in:
Generated
+3
-3
@@ -61,7 +61,7 @@ dependencies:
|
||||
express-session: 1.17.1
|
||||
fast-json-stable-hash: 1.0.2
|
||||
fast-xml-parser: 3.19.0
|
||||
kamaitachi-common: github.com/zkldi/Kamaitachi-common/0398d77719606979c88833b37bce8020958cf058
|
||||
kamaitachi-common: github.com/zkldi/Kamaitachi-common/99d4590a08602bf9229583282cdbe77bdf8e8d97
|
||||
mongodb: 3.6.6
|
||||
monk: 7.3.4
|
||||
multer: 1.4.2
|
||||
@@ -4344,8 +4344,8 @@ packages:
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
github.com/zkldi/Kamaitachi-common/0398d77719606979c88833b37bce8020958cf058:
|
||||
resolution: {tarball: https://codeload.github.com/zkldi/Kamaitachi-common/tar.gz/0398d77719606979c88833b37bce8020958cf058}
|
||||
github.com/zkldi/Kamaitachi-common/99d4590a08602bf9229583282cdbe77bdf8e8d97:
|
||||
resolution: {tarball: https://codeload.github.com/zkldi/Kamaitachi-common/tar.gz/99d4590a08602bf9229583282cdbe77bdf8e8d97}
|
||||
name: kamaitachi-common
|
||||
version: 0.1.0
|
||||
dependencies:
|
||||
|
||||
@@ -12,6 +12,8 @@ import { ImportTypes, Game, AnyChartDocument, AnySongDocument } from "kamaitachi
|
||||
import { ConverterFailure } from "../../framework/common/converter-failures";
|
||||
import { ClassHandler } from "../../framework/user-game-stats/classes";
|
||||
import { DryScore } from "../../framework/common/types";
|
||||
import { BeatorajaIRScoreFormat } from "../../../../server/router/ir/beatoraja/charts/convert-scores";
|
||||
import { BeatorajaContext } from "../ir/beatoraja/types";
|
||||
|
||||
export interface ImportTypeDataMap {
|
||||
"file/eamusement-iidx-csv": IIDXEamusementCSVData;
|
||||
@@ -25,7 +27,7 @@ export interface ImportTypeDataMap {
|
||||
"ir/fervidex": FervidexScore;
|
||||
"ir/fervidex-static": FervidexStaticScore;
|
||||
"ir/chunitachi": BatchManualScore;
|
||||
"ir/beatoraja": EmptyObject;
|
||||
"ir/beatoraja": BeatorajaIRScoreFormat;
|
||||
"ir/usc": EmptyObject;
|
||||
|
||||
"api/arc-iidx": unknown;
|
||||
@@ -49,7 +51,7 @@ export interface ImportTypeContextMap {
|
||||
"ir/fervidex": FervidexContext;
|
||||
"ir/fervidex-static": FervidexStaticContext;
|
||||
"ir/chunitachi": BatchManualContext;
|
||||
"ir/beatoraja": EmptyObject;
|
||||
"ir/beatoraja": BeatorajaContext;
|
||||
"ir/usc": EmptyObject;
|
||||
|
||||
"api/arc-iidx": EmptyObject;
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
import { FindChartOnSHA256 } from "../../../../../utils/queries/charts";
|
||||
import { FindSongOnID } from "../../../../../utils/queries/songs";
|
||||
import { InternalFailure } from "../../../framework/common/converter-failures";
|
||||
import { GenericGetGradeAndPercent } from "../../../framework/common/score-utils";
|
||||
import { DryScore } from "../../../framework/common/types";
|
||||
import { ConverterFunction } from "../../common/types";
|
||||
import { BeatorajaContext, BeatorajaScore } from "./types";
|
||||
import { ChartDocument } from "kamaitachi-common";
|
||||
|
||||
const LAMP_LOOKUP = {
|
||||
NoPlay: "NO PLAY",
|
||||
Failed: "FAILED",
|
||||
LightAssistEasy: "ASSIST CLEAR",
|
||||
Easy: "EASY CLEAR",
|
||||
Normal: "CLEAR",
|
||||
Hard: "HARD CLEAR",
|
||||
ExHard: "EX HARD CLEAR",
|
||||
FullCombo: "FULL COMBO",
|
||||
Perfect: "FULL COMBO",
|
||||
} as const;
|
||||
|
||||
const RANDOM_LOOKUP = {
|
||||
0: "NONRAN",
|
||||
1: "MIRROR",
|
||||
2: "RANDOM",
|
||||
3: "R-RANDOM",
|
||||
4: "S-RANDOM",
|
||||
} as const;
|
||||
|
||||
export const ConverterIRBeatoraja: ConverterFunction<BeatorajaScore, BeatorajaContext> = async (
|
||||
data,
|
||||
context,
|
||||
importType,
|
||||
logger
|
||||
) => {
|
||||
const chart = (await FindChartOnSHA256("bms", data.sha256)) as ChartDocument<
|
||||
"bms:7K" | "bms:14K"
|
||||
>;
|
||||
|
||||
if (!chart) {
|
||||
// @todo #141 Import charts into the Kamaitachi Database if the chart
|
||||
// doesn't exist.
|
||||
throw new Error("unimplemented");
|
||||
}
|
||||
|
||||
const song = await FindSongOnID("bms", chart.songID);
|
||||
|
||||
if (!song) {
|
||||
logger.severe(`Song-Chart Desync with BMS ${chart.chartID}.`);
|
||||
throw new InternalFailure(`Song-Chart Desync with BMS ${chart.chartID}.`);
|
||||
}
|
||||
|
||||
// if CN mode
|
||||
if (data.lntype === 1) {
|
||||
// Divide the EX score by the CN note count, then multiply it by the LN
|
||||
// notecount. This converts CN EXScores to LN EXScores by routing
|
||||
// through percent.
|
||||
data.exscore = Math.floor((data.exscore / context.chart.notes) * chart.data.notecount);
|
||||
}
|
||||
|
||||
const { grade, percent } = GenericGetGradeAndPercent("bms", data.exscore, chart);
|
||||
|
||||
const hitMeta: DryScore<"bms:7K" | "bms:14K">["scoreData"]["hitMeta"] = {
|
||||
bp: data.minbp === -1 ? null : data.minbp,
|
||||
gauge: data.gauge === -1 ? null : data.gauge,
|
||||
};
|
||||
|
||||
let hitData: DryScore<"bms:7K" | "bms:14K">["scoreData"]["hitData"] = {};
|
||||
|
||||
// if NOT CN mode we can assign judgements properly
|
||||
if (data.lntype === 0) {
|
||||
for (const k of [
|
||||
"ebd",
|
||||
"lbd",
|
||||
"egd",
|
||||
"lgd",
|
||||
"egr",
|
||||
"lgr",
|
||||
"epg",
|
||||
"lpg",
|
||||
"epr",
|
||||
"lpr",
|
||||
] as const) {
|
||||
hitMeta[k] = data[k];
|
||||
}
|
||||
|
||||
hitMeta.epr! += data.ems;
|
||||
hitMeta.lpr! += data.lms;
|
||||
|
||||
hitData = {
|
||||
pgreat: data.epg + data.lpg,
|
||||
great: data.egr + data.lgr,
|
||||
good: data.egd + data.lgd,
|
||||
bad: data.ebd + data.lbd,
|
||||
poor: data.epr + data.lpr + data.ems + data.lms,
|
||||
};
|
||||
}
|
||||
|
||||
const lamp = LAMP_LOOKUP[data.clear];
|
||||
|
||||
const dryScore: DryScore<"bms:7K" | "bms:14K"> = {
|
||||
comment: null,
|
||||
game: "bms",
|
||||
importType,
|
||||
scoreData: {
|
||||
grade,
|
||||
percent,
|
||||
score: data.exscore,
|
||||
lamp,
|
||||
hitMeta,
|
||||
hitData,
|
||||
},
|
||||
scoreMeta: {
|
||||
client: context.client,
|
||||
inputDevice: data.deviceType,
|
||||
random: RANDOM_LOOKUP[data.option] ?? null,
|
||||
},
|
||||
timeAchieved: Date.now(),
|
||||
service: "Beatoraja IR",
|
||||
};
|
||||
|
||||
return { song, chart, dryScore };
|
||||
};
|
||||
@@ -0,0 +1,114 @@
|
||||
import { KtLogger } from "../../../../logger/logger";
|
||||
import p from "prudence";
|
||||
import ScoreImportFatalError from "../../../framework/score-importing/score-import-error";
|
||||
import { FormatPrError } from "../../../../../utils/prudence";
|
||||
import { BeatorajaChart, BeatorajaContext, BeatorajaScore } from "./types";
|
||||
import { ConverterIRBeatoraja } from "./converter";
|
||||
import { ParserFunctionReturnsSync } from "../../common/types";
|
||||
|
||||
const PR_BeatorajaScore = {
|
||||
sha256: "string",
|
||||
exscore: p.isPositiveInteger,
|
||||
passnotes: p.isPositiveInteger,
|
||||
gauge: p.or(p.isBetween(0, 100), p.is(-1)),
|
||||
deviceType: p.isIn("BM_CONTROLLER", "KEYBOARD"),
|
||||
minbp: p.or(p.isPositiveInteger, p.is(-1)),
|
||||
option: p.isBoundedInteger(0, 4),
|
||||
|
||||
lntype: p.isIn(0, 1),
|
||||
clear: p.isIn(
|
||||
"NoPlay",
|
||||
"Failed",
|
||||
"LightAssistEasy",
|
||||
"Easy",
|
||||
"Normal",
|
||||
"Hard",
|
||||
"ExHard",
|
||||
"FullCombo",
|
||||
"Perfect"
|
||||
),
|
||||
assist: p.is(0),
|
||||
maxcombo: p.isPositiveInteger,
|
||||
|
||||
epg: p.isPositiveInteger,
|
||||
egr: p.isPositiveInteger,
|
||||
egd: p.isPositiveInteger,
|
||||
ebd: p.isPositiveInteger,
|
||||
epr: p.isPositiveInteger,
|
||||
lpg: p.isPositiveInteger,
|
||||
lgr: p.isPositiveInteger,
|
||||
lgd: p.isPositiveInteger,
|
||||
lbd: p.isPositiveInteger,
|
||||
lpr: p.isPositiveInteger,
|
||||
ems: p.isPositiveInteger,
|
||||
lms: p.isPositiveInteger,
|
||||
};
|
||||
|
||||
const PR_BeatorajaChart = {
|
||||
md5: "string",
|
||||
sha256: "string",
|
||||
title: "string",
|
||||
subtitle: "string",
|
||||
genre: "string",
|
||||
artist: "string",
|
||||
subartist: "string",
|
||||
total: p.isPositive,
|
||||
|
||||
// currently only accepted playtypes.
|
||||
mode: p.isIn("BEAT_7K", "BEAT_14K"),
|
||||
judge: p.isPositive,
|
||||
notes: p.isPositiveInteger,
|
||||
hasUndefinedLN: p.is(false),
|
||||
hasRandom: p.is(false),
|
||||
};
|
||||
|
||||
export function ParseBeatorajaSingle(
|
||||
body: Record<string, unknown>,
|
||||
logger: KtLogger
|
||||
): ParserFunctionReturnsSync<BeatorajaScore, BeatorajaContext> {
|
||||
const err = p(
|
||||
body.score,
|
||||
PR_BeatorajaScore,
|
||||
{},
|
||||
{ allowExcessKeys: true, throwOnNonObject: false }
|
||||
);
|
||||
|
||||
if (err) {
|
||||
throw new ScoreImportFatalError(
|
||||
400,
|
||||
FormatPrError(err, "Invalid Beatoraja Import - Score")
|
||||
);
|
||||
}
|
||||
|
||||
const chartErr = p(
|
||||
body.chart,
|
||||
PR_BeatorajaChart,
|
||||
{},
|
||||
{ allowExcessKeys: true, throwOnNonObject: false }
|
||||
);
|
||||
|
||||
if (chartErr) {
|
||||
throw new ScoreImportFatalError(
|
||||
400,
|
||||
FormatPrError(chartErr, "Invalid Beatoraja Score - Chart")
|
||||
);
|
||||
}
|
||||
|
||||
const client = body.client;
|
||||
|
||||
if (client !== "beatoraja" && client !== "lr2oraja") {
|
||||
throw new ScoreImportFatalError(400, `Unknown client ${client}`);
|
||||
}
|
||||
|
||||
// asserted using prudence.
|
||||
return {
|
||||
context: {
|
||||
client,
|
||||
chart: body.chart as BeatorajaChart,
|
||||
},
|
||||
game: "bms",
|
||||
iterable: ([body] as unknown) as BeatorajaScore[],
|
||||
ConverterFunction: ConverterIRBeatoraja,
|
||||
classHandler: null,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { integer } from "kamaitachi-common";
|
||||
|
||||
export interface BeatorajaContext {
|
||||
client: "beatoraja" | "lr2oraja";
|
||||
chart: BeatorajaChart;
|
||||
}
|
||||
|
||||
export interface BeatorajaChart {
|
||||
md5: string;
|
||||
sha256: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
genre: string;
|
||||
artist: string;
|
||||
subartist: string;
|
||||
total: integer;
|
||||
|
||||
mode: "BEAT_7K" | "BEAT_14K";
|
||||
judge: number;
|
||||
notes: integer;
|
||||
}
|
||||
|
||||
export interface BeatorajaScore {
|
||||
sha256: string;
|
||||
exscore: integer;
|
||||
passnotes: integer;
|
||||
gauge: number;
|
||||
deviceType: "BM_CONTROLLER" | "KEYBOARD";
|
||||
minbp: integer;
|
||||
option: 0 | 1 | 2 | 3 | 4;
|
||||
|
||||
lntype: 0 | 1;
|
||||
clear:
|
||||
| "NoPlay"
|
||||
| "Failed"
|
||||
| "LightAssistEasy"
|
||||
| "Easy"
|
||||
| "Normal"
|
||||
| "Hard"
|
||||
| "ExHard"
|
||||
| "FullCombo"
|
||||
| "Perfect";
|
||||
assist: 0;
|
||||
maxcombo: integer;
|
||||
|
||||
epg: integer;
|
||||
egr: integer;
|
||||
egd: integer;
|
||||
ebd: integer;
|
||||
epr: integer;
|
||||
lpg: integer;
|
||||
lgr: integer;
|
||||
lgd: integer;
|
||||
lbd: integer;
|
||||
lpr: integer;
|
||||
ems: integer;
|
||||
lms: integer;
|
||||
}
|
||||
@@ -34,7 +34,7 @@ type BeatorajaPartialScoreFormat = {
|
||||
maxcombo: integer | null;
|
||||
};
|
||||
|
||||
export type BeatorajaScoreFormat = BeatorajaPartialScoreFormat & BeatorajaScoreJudgements;
|
||||
export type BeatorajaIRScoreFormat = BeatorajaPartialScoreFormat & BeatorajaScoreJudgements;
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import { Router } from "express";
|
||||
import db from "../../../../external/mongo/db";
|
||||
import { SYMBOL_KtchiData } from "../../../../lib/constants/ktchi";
|
||||
import { KtLogger } from "../../../../lib/logger/logger";
|
||||
import { ExpressWrappedScoreImportMain } from "../../../../lib/score-import/framework/express-wrapper";
|
||||
import { ParseBeatorajaSingle } from "../../../../lib/score-import/import-types/ir/beatoraja/parser";
|
||||
import { Random20Hex } from "../../../../utils/misc";
|
||||
import { PRIVATEINFO_GetUserCaseInsensitive } from "../../../../utils/user";
|
||||
import {
|
||||
GetUserWithIDGuaranteed,
|
||||
PRIVATEINFO_GetUserCaseInsensitive,
|
||||
} from "../../../../utils/user";
|
||||
import prValidate from "../../../middleware/prudence-validate";
|
||||
import { PasswordCompare } from "../../api/v1/auth/auth";
|
||||
import { ValidateAuthToken, ValidateIRClientVersion } from "./auth";
|
||||
@@ -66,7 +73,18 @@ router.use(ValidateAuthToken);
|
||||
* store it as a new chart alongside the new score.
|
||||
* @name POST /ir/beatoraja/submit-score
|
||||
*/
|
||||
router.post("/submit-score", async (req, res) => {});
|
||||
router.post("/submit-score", async (req, res) => {
|
||||
const userDoc = await GetUserWithIDGuaranteed(req[SYMBOL_KtchiData]!.beatorajaAuthDoc!.userID);
|
||||
|
||||
const ParserFunction = (logger: KtLogger) => ParseBeatorajaSingle(req.body, logger);
|
||||
|
||||
const importRes = await ExpressWrappedScoreImportMain(
|
||||
userDoc,
|
||||
false,
|
||||
"ir/beatoraja",
|
||||
ParserFunction
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Submits a course result to Kamaitachi. This only accepts a limited set of
|
||||
|
||||
@@ -167,7 +167,7 @@ export async function CreatePOSTScoresResponseBody(
|
||||
|
||||
const usersRanking = scorePB.rankingData.rank;
|
||||
|
||||
// This returns N scores immediately ranked higher
|
||||
// This returns immediately ranked higher
|
||||
// than the current user.
|
||||
|
||||
const adjAbove = (await db["score-pbs"].find(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"song": {
|
||||
"chart": {
|
||||
"md5": "f8dcdfe070630bbb365323c662561a1a",
|
||||
"sha256": "a6ed343ac9db07af0dd6eb8af3c80d79032aa777fd7ff2c28feb885717f47b89",
|
||||
"title": "星の器〜STAR OF ANDROMEDA(ANOTHER)",
|
||||
@@ -55,5 +55,6 @@
|
||||
"judgeAlgorithm": "Combo",
|
||||
"rule": "LR2",
|
||||
"exscore": 1004
|
||||
}
|
||||
}
|
||||
},
|
||||
"client": "beatoraja"
|
||||
}
|
||||
Reference in New Issue
Block a user