Add ir/kshook-sv3c parsers and converters.

This commit is contained in:
zkldi
2021-11-05 04:55:31 +00:00
parent cd9cb28784
commit 5b4d910d5a
3 changed files with 293 additions and 0 deletions
@@ -0,0 +1,99 @@
import { FindSDVXChartOnInGameIDVersion } from "utils/queries/charts";
import { EmptyObject } from "utils/types";
import { ConverterFunction } from "../../common/types";
import { KsHookSV3CScore } from "./types";
import { Lamps } from "tachi-common";
import {
InternalFailure,
KTDataNotFoundFailure,
} from "lib/score-import/framework/common/converter-failures";
import { FindSongOnID } from "utils/queries/songs";
import { GenericGetGradeAndPercent } from "lib/score-import/framework/common/score-utils";
import { DryScore } from "lib/score-import/framework/common/types";
export const ConverterIRKShookSV3C: ConverterFunction<KsHookSV3CScore, EmptyObject> = async (
data,
context,
importType,
logger
) => {
const diff = ConvertDifficulty(data.difficulty);
const chart = await FindSDVXChartOnInGameIDVersion(data.music_id, diff, "konaste");
if (!chart) {
throw new KTDataNotFoundFailure(
`Could not find chart with songID ${data.music_id} (${diff} for Konaste).`,
importType,
data,
context
);
}
const song = await FindSongOnID("sdvx", chart.songID);
if (!song) {
logger.severe(`Song ${chart.songID} (sdvx) has no parent song?`);
throw new InternalFailure(`Song ${chart.songID} (sdvx) has no parent song?`);
}
const { percent, grade } = GenericGetGradeAndPercent("sdvx", data.score, chart);
const dryScore: DryScore<"sdvx:Single"> = {
game: "sdvx",
service: "kshook SV3C",
comment: null,
importType: "ir/kshook-sv3c",
timeAchieved: Date.now(),
scoreData: {
score: data.score,
percent,
grade,
lamp: ConvertLamp(data.clear),
judgements: {
critical: data.critical,
near: data.near,
miss: data.error,
},
hitMeta: {
fast: data.early,
slow: data.late,
gauge: data.gauge,
maxCombo: data.max_chain,
},
},
scoreMeta: {},
};
return { song, chart, dryScore };
};
function ConvertLamp(clear: KsHookSV3CScore["clear"]): Lamps["sdvx:Single"] {
if (clear === "CLEAR_PLAYED") {
return "FAILED";
} else if (clear === "CLEAR_EFFECTIVE") {
return "CLEAR";
} else if (clear === "CLEAR_EXCESSIVE") {
return "EXCESSIVE CLEAR";
} else if (clear === "CLEAR_ULTIMATE_CHAIN") {
return "ULTIMATE CHAIN";
}
return "PERFECT ULTIMATE CHAIN";
}
function ConvertDifficulty(
diff: KsHookSV3CScore["difficulty"]
): "NOV" | "ADV" | "EXH" | "ANY_INF" | "MXM" {
if (diff === "DIFFICULTY_NOVICE") {
return "NOV";
} else if (diff === "DIFFICULTY_ADVANCED") {
return "ADV";
} else if (diff === "DIFFICULTY_EXHAUST") {
return "EXH";
} else if (diff === "DIFFICULTY_INFINITE") {
return "ANY_INF";
}
return "MXM";
}
@@ -0,0 +1,136 @@
import { SDVXDans } from "lib/constants/classes";
import { KtLogger } from "lib/logger/logger";
import { InvalidScoreFailure } from "lib/score-import/framework/common/converter-failures";
import p, { PrudenceSchema } from "prudence";
import { FormatPrError } from "utils/prudence";
import { EmptyObject } from "utils/types";
import { ParserFunctionReturns } from "../../common/types";
import { KsHookSV3CScore } from "./types";
const PR_KsHookSV3C: PrudenceSchema = {
appeal_id: p.isInteger,
clear: p.isIn(
"CLEAR_PLAYED",
"CLEAR_EFFECTIVE",
"CLEAR_EXCESSIVE",
"CLEAR_ULTIMATE_CHAIN",
"CLEAR_PERFECT"
),
difficulty: p.isIn(
"DIFFICULTY_NOVICE",
"DIFFICULTY_ADVANCED",
"DIFFICULTY_EXHAUST",
"DIFFICULTY_INFINITE",
"DIFFICULTY_MAXIMUM"
),
early: p.isPositiveInteger,
late: p.isPositiveInteger,
gauge: p.isBetween(0, 100),
grade: p.isIn(
"GRADE_D",
"GRADE_C",
"GRADE_B",
"GRADE_A",
"GRADE_A_PLUS",
"GRADE_AA",
"GRADE_AA_PLUS",
"GRADE_AAA",
"GRADE_AAA_PLUS",
"GRADE_S"
),
max_chain: p.isPositiveInteger,
music_id: p.isPositiveInteger,
btn_rate: p.isBetween(0, 200),
long_rate: p.isBetween(0, 200),
vol_rate: p.isBetween(0, 200),
player_name: "string",
rate: p.isIn("RATE_EFFECTIVE", "RATE_PERMISSIVE", "RATE_EXCESSIVE", "RATE_BLASTIVE"),
skill_frame: p.isIn(
"SKILL_FRAME_NONE",
"SKILL_FRAME_SILVER",
"SKILL_FRAME_GOLD",
"SKILL_FRAME_GOLD_HALO"
),
skill_level: p.isIn(
"SKILL_LEVEL_NONE",
"SKILL_LEVEL_01",
"SKILL_LEVEL_02",
"SKILL_LEVEL_03",
"SKILL_LEVEL_04",
"SKILL_LEVEL_05",
"SKILL_LEVEL_06",
"SKILL_LEVEL_07",
"SKILL_LEVEL_08",
"SKILL_LEVEL_09",
"SKILL_LEVEL_10",
"SKILL_LEVEL_11",
"SKILL_LEVEL_12"
),
track_no: p.isPositiveInteger,
};
export function ParseKsHookSV3C(
body: Record<string, unknown>,
logger: KtLogger
): ParserFunctionReturns<KsHookSV3CScore, EmptyObject> {
// Ignore excess keys, as SV3C might add more features in the future.
const err = p(body, PR_KsHookSV3C, undefined, { allowExcessKeys: true });
if (err) {
throw new InvalidScoreFailure(FormatPrError(err));
}
const score = body as unknown as KsHookSV3CScore;
return {
game: "sdvx",
iterable: [score],
context: {},
classHandler: () => {
const skillLevel = ConvertSkillLevel(score.skill_level);
// If null, the player doesn't have a dan.
if (!skillLevel) {
return;
}
return {
dan: skillLevel,
};
},
};
}
function ConvertSkillLevel(skill: KsHookSV3CScore["skill_level"]): SDVXDans | null {
switch (skill) {
case "SKILL_LEVEL_01":
return SDVXDans.DAN_1;
case "SKILL_LEVEL_02":
return SDVXDans.DAN_2;
case "SKILL_LEVEL_03":
return SDVXDans.DAN_3;
case "SKILL_LEVEL_04":
return SDVXDans.DAN_4;
case "SKILL_LEVEL_05":
return SDVXDans.DAN_5;
case "SKILL_LEVEL_06":
return SDVXDans.DAN_6;
case "SKILL_LEVEL_07":
return SDVXDans.DAN_7;
case "SKILL_LEVEL_08":
return SDVXDans.DAN_8;
case "SKILL_LEVEL_09":
return SDVXDans.DAN_9;
case "SKILL_LEVEL_10":
return SDVXDans.DAN_10;
case "SKILL_LEVEL_11":
return SDVXDans.DAN_11;
case "SKILL_LEVEL_12":
return SDVXDans.INF;
default:
return null;
}
}
@@ -0,0 +1,58 @@
import { integer } from "tachi-common";
export interface KsHookSV3CScore {
appeal_id: integer;
clear:
| "CLEAR_PLAYED"
| "CLEAR_EFFECTIVE"
| "CLEAR_EXCESSIVE"
| "CLEAR_ULTIMATE_CHAIN"
| "CLEAR_PERFECT";
difficulty: `DIFFICULTY_${"NOVICE" | "ADVANCED" | "EXHAUST" | "INFINITE" | "MAXIMUM"}`;
early: integer;
gauge: integer;
grade: `GRADE_${
| "D"
| "C"
| "B"
| "A"
| "A_PLUS"
| "AA"
| "AA_PLUS"
| "AAA"
| "AAA_PLUS"
| "S"}`;
late: integer;
max_chain: integer;
music_id: integer;
btn_rate: integer;
long_rate: integer;
vol_rate: integer;
critical: integer;
near: integer;
error: integer;
player_name: string;
rate: `RATE_${"EFFECTIVE" | "PERMISSIVE" | "EXCESSIVE" | "BLASTIVE"}`;
score: integer;
skill_frame: `SKILL_FRAME_${"NONE" | "SILVER" | "GOLD" | "GOLD_HALO"}`;
skill_level: `SKILL_LEVEL_${
| "NONE"
| "01"
| "02"
| "03"
| "04"
| "05"
| "06"
| "07"
| "08"
| "09"
| "10"
| "11"
| "12"}`;
skill_name: integer;
track_no: integer;
}