Merge pull request #651 from TNG-dev:zkldi/issue-650

feat: barbatos sdvx6 support
This commit is contained in:
zkldi
2022-08-25 23:57:25 +01:00
committed by GitHub
7 changed files with 258 additions and 34 deletions
@@ -4,7 +4,7 @@ import type { ClassHandler } from "../../framework/user-game-stats/types";
import type { SDVXEamusementCSVData } from "../file/eamusement-sdvx-csv/types";
import type { MerScore } from "../file/mer-iidx/types";
import type { S3Score } from "../file/solid-state-squad/types";
import type { BarbatosContext, BarbatosScore } from "../ir/barbatos/types";
import type { BarbatosContext, BarbatosScore, BarbatosSDVX6Score } from "../ir/barbatos/types";
import type { BeatorajaContext, BeatorajaScore } from "../ir/beatoraja/types";
import type { FervidexStaticContext, FervidexStaticScore } from "../ir/fervidex-static/types";
import type { FervidexContext, FervidexScore } from "../ir/fervidex/types";
@@ -36,7 +36,7 @@ export interface ImportTypeDataMap {
"file/pli-iidx-csv": IIDXEamusementCSVData;
"ir/direct-manual": BatchManualScore;
"ir/barbatos": BarbatosScore;
"ir/barbatos": BarbatosScore | BarbatosSDVX6Score;
"ir/fervidex": FervidexScore;
"ir/fervidex-static": FervidexStaticScore;
"ir/beatoraja": BeatorajaScore;
@@ -4,7 +4,7 @@ import db from "external/mongo/db";
import CreateLogCtx from "lib/logger/logger";
import t from "tap";
import ResetDBState from "test-utils/resets";
import { MockBarbatosScore } from "test-utils/test-data";
import { MockBarbatosScore, MockBarbatosSDVX6Score } from "test-utils/test-data";
import type { BarbatosScore } from "./types";
const logger = CreateLogCtx(__filename);
@@ -36,13 +36,13 @@ t.test("#ConverterIRBarbatos", (t) => {
inGameID: 1,
},
isPrimary: true,
versions: ["booth", "inf", "gw", "heaven", "vivid"],
versions: ["booth", "inf", "gw", "heaven", "vivid", "exceed"],
};
t.test("Should convert a BarbatosScore into a Dry Score", async (t) => {
const res = await ConverterIRBarbatos(
MockBarbatosScore,
{ timeReceived: 10 },
{ timeReceived: 10, version: "vivid" },
"ir/barbatos",
logger
);
@@ -52,7 +52,7 @@ t.test("#ConverterIRBarbatos", (t) => {
chart: albidaChart,
dryScore: {
game: "sdvx",
service: "Barbatos",
service: "Barbatos (vivid)",
comment: null,
importType: "ir/barbatos",
@@ -72,6 +72,7 @@ t.test("#ConverterIRBarbatos", (t) => {
slow: 10,
gauge: 90,
maxCombo: 100,
exScore: null,
},
},
scoreMeta: {
@@ -83,12 +84,57 @@ t.test("#ConverterIRBarbatos", (t) => {
t.end();
});
t.test("Should convert a BarbatosSDVX6Score into a Dry Score", async (t) => {
const res = await ConverterIRBarbatos(
MockBarbatosSDVX6Score,
{ timeReceived: 10, version: "exceed" },
"ir/barbatos",
logger
);
t.hasStrict(res, {
song: albidaSong,
chart: albidaChart,
dryScore: {
game: "sdvx",
service: "Barbatos (exceed)",
comment: null,
importType: "ir/barbatos",
// timeAchieved: , its Date.now() give or take lol
scoreData: {
score: 9000000,
percent: 90,
grade: "A+",
lamp: "CLEAR",
judgements: {
critical: 26,
near: 2,
miss: 17,
},
hitMeta: {
fast: 6,
slow: 9,
gauge: 90,
maxCombo: 100,
exScore: 1234,
},
},
scoreMeta: {
inSkillAnalyser: null,
},
},
});
t.end();
});
t.test("Should throw KTDataNotFound if chart not found.", (t) => {
t.rejects(
() =>
ConverterIRBarbatos(
deepmerge(MockBarbatosScore, { song_id: 1000 }) as BarbatosScore,
{ timeReceived: 10 },
{ timeReceived: 10, version: "vivid" },
"ir/barbatos",
logger
),
@@ -100,13 +146,47 @@ t.test("#ConverterIRBarbatos", (t) => {
t.end();
});
t.test("Should honor provided context.version, and match accordingly.", async (t) => {
// remove vividwave from the set of charts that this chart appears in.
await db.charts.sdvx.update(
{
"data.inGameID": 1,
},
{
$pull: {
versions: "vivid",
},
}
);
t.rejects(
() =>
ConverterIRBarbatos(
MockBarbatosScore,
{ timeReceived: 10, version: "vivid" },
"ir/barbatos",
logger
),
{
message: /Could not find chart with songID 1/u,
}
);
t.end();
});
t.test("Should throw InternalFailure if song-chart desync.", async (t) => {
// force a song-chart desync
await db.songs.sdvx.remove({ id: 1 });
t.rejects(
() =>
ConverterIRBarbatos(MockBarbatosScore, { timeReceived: 10 }, "ir/barbatos", logger),
ConverterIRBarbatos(
MockBarbatosScore,
{ timeReceived: 10, version: "vivid" },
"ir/barbatos",
logger
),
{
message: /Song 1 \(sdvx\) has no parent song/u,
}
@@ -3,11 +3,11 @@ import {
KTDataNotFoundFailure,
} from "../../../framework/common/converter-failures";
import { GenericGetGradeAndPercent } from "../../../framework/common/score-utils";
import { FindSDVXChartOnInGameID } from "utils/queries/charts";
import { FindSDVXChartOnInGameIDVersion } from "utils/queries/charts";
import { FindSongOnID } from "utils/queries/songs";
import type { DryScore } from "../../../framework/common/types";
import type { ConverterFunction } from "../../common/types";
import type { BarbatosContext, BarbatosScore } from "./types";
import type { BarbatosContext, BarbatosScore, BarbatosSDVX6Score } from "./types";
import type { Lamps } from "tachi-common";
const LAMP_LOOKUP = {
@@ -28,12 +28,10 @@ const DIFFICULTY_LOOKUP = {
4: "MXM",
};
export const ConverterIRBarbatos: ConverterFunction<BarbatosScore, BarbatosContext> = async (
data,
context,
importType,
logger
) => {
export const ConverterIRBarbatos: ConverterFunction<
BarbatosScore | BarbatosSDVX6Score,
BarbatosContext
> = async (data, context, importType, logger) => {
const difficulty = DIFFICULTY_LOOKUP[data.difficulty] as
| "ADV"
| "ANY_INF"
@@ -41,7 +39,7 @@ export const ConverterIRBarbatos: ConverterFunction<BarbatosScore, BarbatosConte
| "MXM"
| "NOV";
const chart = await FindSDVXChartOnInGameID(data.song_id, difficulty);
const chart = await FindSDVXChartOnInGameIDVersion(data.song_id, difficulty, context.version);
if (!chart) {
throw new KTDataNotFoundFailure(
@@ -61,9 +59,12 @@ export const ConverterIRBarbatos: ConverterFunction<BarbatosScore, BarbatosConte
const { percent, grade } = GenericGetGradeAndPercent("sdvx", data.score, chart);
const { critical, near, miss } = GetJudgements(data);
const { fast, slow } = GetFastSlow(data);
const dryScore: DryScore<"sdvx:Single"> = {
game: "sdvx",
service: "Barbatos",
service: `Barbatos (${context.version})`,
comment: null,
importType: "ir/barbatos",
timeAchieved: context.timeReceived,
@@ -73,21 +74,58 @@ export const ConverterIRBarbatos: ConverterFunction<BarbatosScore, BarbatosConte
grade,
lamp: LAMP_LOOKUP[data.clear_type] as Lamps["sdvx:Single"],
judgements: {
critical: data.critical,
near: data.near_total,
miss: data.error,
critical,
near,
miss,
},
hitMeta: {
fast: data.near_fast,
slow: data.near_slow,
fast,
slow,
gauge: data.percent,
maxCombo: data.max_chain,
// only sdvx6 scores store this info
exScore: "ex_score" in data ? data.ex_score : null,
},
},
scoreMeta: {
inSkillAnalyser: data.is_skill_analyzer,
// for some reason, only sdvx5 scores store this info.
inSkillAnalyser: "is_skill_analyzer" in data ? data.is_skill_analyzer : null,
},
};
return { song, chart, dryScore };
};
function GetJudgements(data: BarbatosScore | BarbatosSDVX6Score) {
if ("ex_score" in data) {
// this is a sdvx6 score.
return {
critical: data.early_crit + data.s_crit + data.late_crit,
near: data.chip_near,
miss: data.early_error + data.late_error,
};
}
// otherwise, normal score
return {
critical: data.critical,
near: data.near_total,
miss: data.error,
};
}
function GetFastSlow(data: BarbatosScore | BarbatosSDVX6Score) {
if ("ex_score" in data) {
return {
fast: data.early_near,
slow: data.late_near,
};
}
return {
fast: data.near_fast,
slow: data.near_slow,
};
}
@@ -2,7 +2,7 @@ import ScoreImportFatalError from "../../../framework/score-importing/score-impo
import p from "prudence";
import { FormatPrError } from "utils/prudence";
import type { ParserFunctionReturns } from "../../common/types";
import type { BarbatosContext, BarbatosScore } from "./types";
import type { BarbatosContext, BarbatosScore, BarbatosSDVX6Score } from "./types";
import type { KtLogger } from "lib/logger/logger";
const PR_BARBATOS = {
@@ -23,21 +23,55 @@ const PR_BARBATOS = {
is_skill_analyzer: "boolean",
};
const PR_BARBATOS_SDVX6 = {
difficulty: p.isIn(1, 2, 3, 4),
level: p.isBoundedInteger(1, 20),
score: p.isBoundedInteger(0, 10_000_000),
ex_score: p.isInteger,
clear_type: p.isIn(1, 2, 3, 4, 5),
song_id: p.isPositiveInteger,
grade: p.any,
percent: p.isBetween(0, 100),
max_chain: p.isPositiveInteger,
// this is a bit much lmao.
early_error: p.isInteger,
early_near: p.isInteger,
early_crit: p.isInteger,
s_crit: p.isInteger,
late_crit: p.isInteger,
late_near: p.isInteger,
late_error: p.isInteger,
chip_s_crit: p.isInteger,
chip_crit: p.isInteger,
chip_near: p.isInteger,
chip_error: p.isInteger,
long_crit: p.isInteger,
long_error: p.isInteger,
vol_crit: p.isInteger,
vol_error: p.isInteger,
gauge_type: p.isIn(0, 1, 2, 3),
};
export function ParseBarbatosSingle(
body: Record<string, unknown>,
_logger: KtLogger
): ParserFunctionReturns<BarbatosScore, BarbatosContext> {
const err = p(body, PR_BARBATOS);
): ParserFunctionReturns<BarbatosScore | BarbatosSDVX6Score, BarbatosContext> {
// this is an extremely sketchy way of sniffing out whether it's sdvx6 or not.
// We could ask barbatos to send a header, but what difference does it make?
const schema = "ex_score" in body ? PR_BARBATOS_SDVX6 : PR_BARBATOS;
const err = p(body, schema);
if (err) {
throw new ScoreImportFatalError(400, FormatPrError(err, "Invalid Barbatos Request"));
}
// asserted using prudence.
return {
context: { timeReceived: Date.now() },
context: { timeReceived: Date.now(), version: "ex_score" in body ? "exceed" : "vivid" },
game: "sdvx",
iterable: [body] as unknown as Array<BarbatosScore>,
iterable: [body] as unknown as Array<BarbatosScore | BarbatosSDVX6Score>,
classHandler: null,
};
}
@@ -1,4 +1,4 @@
import type { integer } from "tachi-common";
import type { GPTSupportedVersions, integer } from "tachi-common";
export interface BarbatosScore {
difficulty: 0 | 1 | 2 | 3 | 4;
@@ -14,10 +14,43 @@ export interface BarbatosScore {
percent: number;
did_fail: boolean;
clear_type: 1 | 2 | 3 | 4 | 5;
gauge_type: 1 | 2;
gauge_type: 0 | 1 | 2 | 3;
is_skill_analyzer: boolean;
}
export interface BarbatosSDVX6Score {
difficulty: 0 | 1 | 2 | 3 | 4;
level: integer;
score: integer;
ex_score: integer;
clear_type: 1 | 2 | 3 | 4 | 5;
song_id: integer;
grade: unknown; // don't care, we calculate this
percent: number; // "health"
max_chain: integer;
early_error: integer;
early_near: integer;
early_crit: integer;
s_crit: integer;
late_crit: integer;
late_near: integer;
late_error: integer;
chip_s_crit: integer;
chip_crit: integer;
chip_near: integer;
chip_error: integer;
long_crit: integer;
long_error: integer;
vol_crit: integer;
vol_error: integer;
gauge_type: 0 | 1 | 2 | 3;
}
export interface BarbatosContext {
timeReceived: number;
version: GPTSupportedVersions["sdvx:Single"];
}
@@ -20,7 +20,7 @@ t.test("POST /ir/barbatos/score/submit", (t) => {
t.equal(res.body.body.errors.length, 0, "Should have 0 failed scores.");
const scores = await db.scores.count({
service: "Barbatos",
service: "Barbatos (vivid)",
});
t.equal(scores, 1, "Should import 1 score.");
+40 -1
View File
@@ -12,7 +12,10 @@ import fs from "fs";
import path from "path";
import type { DryScore } from "lib/score-import/framework/common/types";
import type { S3Score } from "lib/score-import/import-types/file/solid-state-squad/types";
import type { BarbatosScore } from "lib/score-import/import-types/ir/barbatos/types";
import type {
BarbatosScore,
BarbatosSDVX6Score,
} from "lib/score-import/import-types/ir/barbatos/types";
import type { FervidexScore } from "lib/score-import/import-types/ir/fervidex/types";
import type { KsHookSV6CScore } from "lib/score-import/import-types/ir/kshook-sv6c/types";
import type { LR2HookScore } from "lib/score-import/import-types/ir/lr2hook/types";
@@ -615,6 +618,42 @@ export const MockBarbatosScore: BarbatosScore = {
song_id: 1,
};
export const MockBarbatosSDVX6Score: BarbatosSDVX6Score = {
clear_type: 2,
difficulty: 1,
level: 10,
max_chain: 100,
percent: 90,
score: 9_000_000,
song_id: 1,
chip_s_crit: 4,
chip_crit: 3,
chip_near: 2,
chip_error: 1,
early_crit: 5,
early_near: 6,
early_error: 7,
ex_score: 1234,
gauge_type: 1,
grade: "whocares",
late_crit: 8,
late_near: 9,
late_error: 10,
long_crit: 11,
long_error: 12,
s_crit: 13,
vol_crit: 14,
vol_error: 15,
};
export const uscChart: ChartDocument<"usc:Controller" | "usc:Keyboard"> = {
rgcID: null,
chartID: "USC_CHART_ID",