mirror of
https://github.com/zkldi/Tachi.git
synced 2026-09-29 02:18:09 +03:00
Merge pull request #651 from TNG-dev:zkldi/issue-650
feat: barbatos sdvx6 support
This commit is contained in:
@@ -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.");
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user