mirror of
https://github.com/zkldi/Tachi.git
synced 2026-09-30 10:58:07 +03:00
checkpoint: 14
This commit is contained in:
@@ -167,7 +167,7 @@ export function FormatScoreData<I extends GPTString = GPTString>(score: ScOrPBDo
|
||||
score as ScOrPBDoc<
|
||||
"bms:7K" | "bms:14K" | "iidx:DP" | "iidx:SP" | "pms:Controller" | "pms:Keyboard"
|
||||
>
|
||||
).scoreData.hitMeta.bp;
|
||||
).scoreData.optional.bp;
|
||||
|
||||
lampStr = `${score.scoreData.lamp} (BP: ${bp ?? "No Data"})`;
|
||||
|
||||
|
||||
@@ -247,6 +247,7 @@ export const BMS_7K_CONF = {
|
||||
tableFolders: z.array(z.strictObject({ table: z.string(), level: z.string() })),
|
||||
sglEC: z.number().nullable(),
|
||||
sglHC: z.number().nullable(),
|
||||
aiLevel: z.string().nullable(),
|
||||
}),
|
||||
|
||||
preferences: z.strictObject({
|
||||
|
||||
@@ -88,14 +88,16 @@ export const IIDX_SP_CONF = {
|
||||
comboBreak: { type: "INTEGER" },
|
||||
|
||||
// The players history for the gauge type they were playing on.
|
||||
gaugeHistory: { type: "GRAPH" },
|
||||
// this may fall into "NULL" if the user fails.
|
||||
gaugeHistory: { type: "NULLABLE_GRAPH" },
|
||||
scoreHistory: { type: "NULLABLE_GRAPH" },
|
||||
|
||||
// if "GSM" is enabled (via fervidex.dll) then all graphs
|
||||
// are sent. we should store all of them.
|
||||
gsmEasy: { type: "GRAPH" },
|
||||
gsmNormal: { type: "GRAPH" },
|
||||
gsmHard: { type: "GRAPH" },
|
||||
gsmEXHard: { type: "GRAPH" },
|
||||
gsmEasy: { type: "NULLABLE_GRAPH" },
|
||||
gsmNormal: { type: "NULLABLE_GRAPH" },
|
||||
gsmHard: { type: "NULLABLE_GRAPH" },
|
||||
gsmEXHard: { type: "NULLABLE_GRAPH" },
|
||||
},
|
||||
|
||||
defaultScoreRatingAlg: "ktLampRating",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { FAST_SLOW_MAXCOMBO } from "./_common";
|
||||
import { ClassValue, ToDecimalPlaces, zodNonNegativeInt, zodTierlistData } from "../config-utils";
|
||||
import { z } from "zod";
|
||||
import type { INTERNAL_GAME_CONFIG, GamePTConfig } from "../../types/internals";
|
||||
@@ -97,6 +98,7 @@ export const SDVX_SINGLE_CONF = {
|
||||
preferredDefaultEnum: "grade",
|
||||
|
||||
optionalMetrics: {
|
||||
...FAST_SLOW_MAXCOMBO,
|
||||
exScore: { type: "INTEGER" },
|
||||
gauge: { type: "DECIMAL" },
|
||||
},
|
||||
|
||||
+27
-21
@@ -20,16 +20,10 @@ export type GraphMetricValidator<GPT extends GPTString> = (
|
||||
chart: ChartDocument<GPT>
|
||||
) => string | true;
|
||||
|
||||
/**
|
||||
* A metric for a score that's a floating point number.
|
||||
*/
|
||||
export interface ConfDecimalScoreMetric {
|
||||
type: "DECIMAL";
|
||||
}
|
||||
|
||||
/**
|
||||
* A metric for a score that's an integer.
|
||||
*/
|
||||
export interface ConfIntegerScoreMetric {
|
||||
type: "INTEGER";
|
||||
}
|
||||
@@ -49,19 +43,25 @@ export interface ConfEnumScoreMetric<V extends string> {
|
||||
}
|
||||
|
||||
/**
|
||||
* A metric for a score that represents an array of numbers.
|
||||
*
|
||||
* This is intended for use by graphs and other equivalent things.
|
||||
* Corresponds to Array<number>
|
||||
*/
|
||||
export interface ConfGraphScoreMetric {
|
||||
type: "GRAPH";
|
||||
}
|
||||
|
||||
/**
|
||||
* Corresponds to Array<number | null>.
|
||||
*/
|
||||
export interface ConfNullableGraphScoreMetric {
|
||||
type: "NULLABLE_GRAPH";
|
||||
}
|
||||
|
||||
export type ConfScoreMetric =
|
||||
| ConfDecimalScoreMetric
|
||||
| ConfEnumScoreMetric<string>
|
||||
| ConfGraphScoreMetric
|
||||
| ConfIntegerScoreMetric;
|
||||
| ConfIntegerScoreMetric
|
||||
| ConfNullableGraphScoreMetric;
|
||||
|
||||
/**
|
||||
* When we store and interact with enum values, we want them to be both strings
|
||||
@@ -84,6 +84,8 @@ export type ExtractMetricType<M extends ConfScoreMetric> = M extends ConfDecimal
|
||||
? EnumValue<V>
|
||||
: M extends ConfGraphScoreMetric
|
||||
? Array<number>
|
||||
: M extends ConfNullableGraphScoreMetric
|
||||
? Array<number | null>
|
||||
: never;
|
||||
|
||||
/**
|
||||
@@ -136,7 +138,7 @@ export type ExtractMetrics<R extends Record<string, ConfScoreMetric>> = {
|
||||
[K in keyof R]: ExtractMetricType<R[K]>;
|
||||
};
|
||||
|
||||
export type DerivedMetricValue = Array<number> | integer | number | string;
|
||||
export type DerivedMetricValue = Array<number | null> | Array<number> | integer | number | string;
|
||||
|
||||
export type MetricDeriver<
|
||||
M extends Record<string, PossibleMetrics>,
|
||||
@@ -150,13 +152,17 @@ export type MetricDeriver<
|
||||
* A function that will derive this metric, given a function of other metrics and
|
||||
* a chart for this GPT.
|
||||
*/
|
||||
export type ScoreMetricDeriver<
|
||||
M extends ConfScoreMetric,
|
||||
GPT extends GPTString
|
||||
> = M extends ConfGraphScoreMetric
|
||||
? MetricDeriver<ExtractMetrics<ConfProvidedMetrics[GPT]>, GPT, Array<number>>
|
||||
: M extends ConfEnumScoreMetric<infer V>
|
||||
? MetricDeriver<ExtractMetrics<ConfProvidedMetrics[GPT]>, GPT, V>
|
||||
: M extends ConfIntegerScoreMetric
|
||||
? MetricDeriver<ExtractMetrics<ConfProvidedMetrics[GPT]>, GPT, integer>
|
||||
: MetricDeriver<ExtractMetrics<ConfProvidedMetrics[GPT]>, GPT, number>;
|
||||
export type ScoreMetricDeriver<M extends ConfScoreMetric, GPT extends GPTString> =
|
||||
// graph score metrics correspond to Array<number>
|
||||
M extends ConfGraphScoreMetric
|
||||
? MetricDeriver<ExtractMetrics<ConfProvidedMetrics[GPT]>, GPT, Array<number>>
|
||||
: // nullable graphs correspond to Array<number | null>
|
||||
M extends ConfNullableGraphScoreMetric
|
||||
? MetricDeriver<ExtractMetrics<ConfProvidedMetrics[GPT]>, GPT, Array<number | null>>
|
||||
: // enums correspond to their string unions ("FAILED"|"CLEAR")
|
||||
M extends ConfEnumScoreMetric<infer V>
|
||||
? MetricDeriver<ExtractMetrics<ConfProvidedMetrics[GPT]>, GPT, V>
|
||||
: // the other two are obvious
|
||||
M extends ConfIntegerScoreMetric
|
||||
? MetricDeriver<ExtractMetrics<ConfProvidedMetrics[GPT]>, GPT, integer>
|
||||
: MetricDeriver<ExtractMetrics<ConfProvidedMetrics[GPT]>, GPT, number>;
|
||||
|
||||
@@ -13,7 +13,7 @@ const migration: Migration = {
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
"scoreData.hitMeta.bp": null,
|
||||
"scoreData.optional.bp": null,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -234,7 +234,7 @@ function CalculateDataITGStamina(
|
||||
chart: ChartDocument<"itg:Stamina">
|
||||
): CalculatedData<"itg:Stamina"> {
|
||||
// If the user failed -- and we don't know when, return null for everything.
|
||||
if (dryScore.scoreData.lamp === "FAILED" && IsNullish(dryScore.scoreData.hitMeta.diedAt)) {
|
||||
if (dryScore.scoreData.lamp === "FAILED" && IsNullish(dryScore.scoreData.optional.diedAt)) {
|
||||
return {
|
||||
blockRating: null,
|
||||
highest32: null,
|
||||
@@ -245,14 +245,14 @@ function CalculateDataITGStamina(
|
||||
let h32 = ITGHighestUnbroken.calculateFromNPSPerMeasure(
|
||||
chart.data.breakdown.npsPerMeasure,
|
||||
chart.data.breakdown.notesPerMeasure,
|
||||
dryScore.scoreData.hitMeta.diedAt,
|
||||
dryScore.scoreData.optional.diedAt,
|
||||
32
|
||||
);
|
||||
|
||||
let h256 = ITGHighestUnbroken.calculateFromNPSPerMeasure(
|
||||
chart.data.breakdown.npsPerMeasure,
|
||||
chart.data.breakdown.notesPerMeasure,
|
||||
dryScore.scoreData.hitMeta.diedAt,
|
||||
dryScore.scoreData.optional.diedAt,
|
||||
256
|
||||
);
|
||||
|
||||
|
||||
@@ -1,190 +1,6 @@
|
||||
import { InternalFailure, InvalidScoreFailure } from "./converter-failures";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import { ESDCore, GetGamePTConfig } from "tachi-common";
|
||||
import { IsNullish, NotNullish } from "utils/misc";
|
||||
import type {
|
||||
ChartDocument,
|
||||
Game,
|
||||
GameToGPTString,
|
||||
Grades,
|
||||
GPTString,
|
||||
Playtype,
|
||||
Lamps,
|
||||
integer,
|
||||
} from "tachi-common";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
/**
|
||||
* Util for getting a games' grade for a given percent.
|
||||
*/
|
||||
export function GetGradeFromPercent<I extends GPTString = GPTString>(
|
||||
game: Game,
|
||||
playtype: Playtype,
|
||||
percent: number
|
||||
): Grades[I] {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
const boundaries = gptConfig.gradeBoundaries;
|
||||
const grades = gptConfig.grades;
|
||||
|
||||
// (hey, this for loop is backwards!)
|
||||
for (let i = boundaries.length - 1; i >= 0; i--) {
|
||||
if (percent + Number.EPSILON >= NotNullish(boundaries[i])) {
|
||||
if (IsNullish(grades[i])) {
|
||||
logger.error(
|
||||
`Attempted to get the ${i}th grade for ${game} (${playtype}) for a percent of ${percent}%. The grade fetched was null or undefined. Refusing to process this score.`
|
||||
);
|
||||
throw new InternalFailure(
|
||||
`Failed to process a score with a percent of ${percent}. This has been reported.`
|
||||
);
|
||||
}
|
||||
|
||||
return grades[i] as Grades[I];
|
||||
}
|
||||
}
|
||||
|
||||
logger.error(`Could not resolve grade for percent ${percent} on game ${game}`);
|
||||
throw new InternalFailure(`Could not resolve grade for percent ${percent} on game ${game}.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* A generic function for calculating a percent from a given score on
|
||||
* a given game.
|
||||
*/
|
||||
export function GenericCalculatePercent(game: Game, score: number, chart?: ChartDocument): number {
|
||||
switch (game) {
|
||||
case "museca":
|
||||
case "chunithm":
|
||||
case "wacca":
|
||||
return (score / 1_000_000) * 100;
|
||||
case "sdvx":
|
||||
case "usc":
|
||||
return (score / 10_000_000) * 100;
|
||||
case "popn":
|
||||
return (score / 100_000) * 100;
|
||||
|
||||
case "bms":
|
||||
case "pms":
|
||||
case "iidx": {
|
||||
if (!chart) {
|
||||
logger.severe("No Chart passed to GenericCalcPercent but game was iidx/bms/pms.");
|
||||
throw new InternalFailure(
|
||||
"No Chart passed to GenericCalcPercent but game was iidx/bms/pms."
|
||||
);
|
||||
}
|
||||
|
||||
// Yeah, we declare it like this so the below return is actually clear.
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const MAX =
|
||||
(
|
||||
chart as ChartDocument<
|
||||
| "bms:7K"
|
||||
| "bms:14K"
|
||||
| "iidx:DP"
|
||||
| "iidx:SP"
|
||||
| "pms:Controller"
|
||||
| "pms:Keyboard"
|
||||
>
|
||||
).data.notecount * 2;
|
||||
|
||||
return (100 * score) / MAX;
|
||||
}
|
||||
|
||||
case "jubeat":
|
||||
throw new Error(`Cannot calculate percent for jubeat. Music Rate must be provided.`);
|
||||
|
||||
// gitadora's score is just a percent
|
||||
// this is a hack: we need a refactor for this.
|
||||
case "gitadora":
|
||||
case "itg":
|
||||
case "maimaidx":
|
||||
return score;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper utility for validating percents on a game. This throws an InvalidScoreFailure if the percent is
|
||||
* invalid, and returns void on success.
|
||||
*/
|
||||
export function ValidatePercent(
|
||||
game: Game,
|
||||
playtype: Playtype,
|
||||
percent: number,
|
||||
chart: ChartDocument
|
||||
) {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
if (percent > gptConfig.percentMax) {
|
||||
throw new InvalidScoreFailure(
|
||||
`Invalid percent of ${percent} - expected a value less than ${gptConfig.percentMax}% (${chart.songID} ${chart.playtype} ${chart.difficulty}).`
|
||||
);
|
||||
}
|
||||
|
||||
if (percent < 0) {
|
||||
throw new InvalidScoreFailure(
|
||||
`Invalid percent of ${percent} - Expected a positive number? (${chart.songID} ${chart.playtype} ${chart.difficulty})`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generically gets the grade and percent for a given score on a given game. This only works for games where
|
||||
* grades are just percent boundaries. This will throw an InvalidScoreFailure if the percent is invalid,
|
||||
* or if the grade is invalid.
|
||||
*/
|
||||
export function GenericGetGradeAndPercent<G extends Game>(
|
||||
game: G,
|
||||
score: number,
|
||||
chart: ChartDocument
|
||||
): { percent: number; grade: Grades[GameToGPTString[G]] } {
|
||||
const percent = GenericCalculatePercent(game, score, chart);
|
||||
|
||||
ValidatePercent(game, chart.playtype, percent, chart);
|
||||
|
||||
const grade: Grades[GameToGPTString[G]] = GetGradeFromPercent(game, chart.playtype, percent);
|
||||
|
||||
return { percent, grade };
|
||||
}
|
||||
|
||||
export function JubeatGetGrade(score: number): GetEnumValue<"jubeat:Single", "grade"> {
|
||||
if (score === 1_000_000) {
|
||||
return "EXC";
|
||||
} else if (score >= 980_000) {
|
||||
return "SSS";
|
||||
} else if (score >= 950_000) {
|
||||
return "SS";
|
||||
} else if (score >= 900_000) {
|
||||
return "S";
|
||||
} else if (score >= 850_000) {
|
||||
return "A";
|
||||
} else if (score >= 800_000) {
|
||||
return "B";
|
||||
} else if (score >= 700_000) {
|
||||
return "C";
|
||||
} else if (score >= 500_000) {
|
||||
return "D";
|
||||
}
|
||||
|
||||
return "E";
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the ESD for a given game + percent combo. This function returns
|
||||
* null if the game does not support support ESD.
|
||||
*/
|
||||
export function CalculateESDForGame(
|
||||
game: Game,
|
||||
playtype: Playtype,
|
||||
percent: number
|
||||
): number | null {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
if (!gptConfig.supportsESD) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ESDCore.CalculateESD(gptConfig.judgementWindows, percent);
|
||||
}
|
||||
import { InvalidScoreFailure } from "./converter-failures";
|
||||
import type { integer } from "tachi-common";
|
||||
import type { GetEnumValue } from "tachi-common/types/metrics";
|
||||
|
||||
/**
|
||||
* Parses and validates a date from a string.
|
||||
@@ -211,7 +27,10 @@ export function ParseDateFromString(str: string | null | undefined): number | nu
|
||||
* Museca actually handles clears differently with a bunch of grafica nonsense,
|
||||
* but nobody actually cares about it, so...
|
||||
*/
|
||||
export function MusecaGetLamp(score: integer, missCount: integer): GetEnumValue<"museca:Single", "lamp"> {
|
||||
export function MusecaGetLamp(
|
||||
score: integer,
|
||||
missCount: integer
|
||||
): GetEnumValue<"museca:Single", "lamp"> {
|
||||
if (score === 1_000_000) {
|
||||
return "PERFECT CONNECT ALL";
|
||||
} else if (missCount === 0) {
|
||||
@@ -223,7 +42,10 @@ export function MusecaGetLamp(score: integer, missCount: integer): GetEnumValue<
|
||||
return "FAILED";
|
||||
}
|
||||
|
||||
export function JubeatGetLamp(score: integer, missCount: integer): GetEnumValue<"jubeat:Single", "lamp"> {
|
||||
export function JubeatGetLamp(
|
||||
score: integer,
|
||||
missCount: integer
|
||||
): GetEnumValue<"jubeat:Single", "lamp"> {
|
||||
if (score === 1_000_000) {
|
||||
return "EXCELLENT";
|
||||
} else if (missCount === 0) {
|
||||
|
||||
@@ -232,7 +232,7 @@ t.test("#CreatePBDoc", (t) => {
|
||||
t.not(res, undefined, "Should actually return something.");
|
||||
|
||||
t.equal(
|
||||
res?.scoreData.hitMeta.bp,
|
||||
res?.scoreData.optional.bp,
|
||||
1,
|
||||
"Should select the best BP's BP and not the score PBs."
|
||||
);
|
||||
@@ -292,7 +292,7 @@ t.test("#CreatePBDoc", (t) => {
|
||||
t.not(res, undefined, "Should actually return something.");
|
||||
|
||||
t.equal(
|
||||
res?.scoreData.hitMeta.bp,
|
||||
res?.scoreData.optional.bp,
|
||||
1,
|
||||
"Should select the best BP's BP and not the score PBs."
|
||||
);
|
||||
@@ -341,7 +341,7 @@ t.test("#CreatePBDoc", (t) => {
|
||||
t.not(res, undefined, "Should actually return something.");
|
||||
|
||||
t.hasStrict(
|
||||
res?.scoreData.hitMeta,
|
||||
res?.scoreData.optional,
|
||||
{
|
||||
gauge: 12,
|
||||
gaugeHistory: [20, 20, 21, 12],
|
||||
|
||||
@@ -190,7 +190,7 @@ async function MergeScoreLampIntoPB(
|
||||
judgements: scorePB.scoreData.judgements,
|
||||
|
||||
// this will probably be overrode by game-specific fns
|
||||
optional: scorePB.scoreData.hitMeta,
|
||||
optional: scorePB.scoreData.optional,
|
||||
},
|
||||
calculatedData: scorePB.calculatedData,
|
||||
};
|
||||
|
||||
@@ -19,13 +19,13 @@ export async function IIDXMergeFn(
|
||||
pbDoc.calculatedData.ktLampRating = lampPB.calculatedData.ktLampRating;
|
||||
|
||||
// Update lamp related iidx-specific info from the lampPB.
|
||||
pbDoc.scoreData.hitMeta.gsm = lampPB.scoreData.hitMeta.gsm;
|
||||
pbDoc.scoreData.hitMeta.gauge = lampPB.scoreData.hitMeta.gauge;
|
||||
pbDoc.scoreData.hitMeta.gaugeHistory = lampPB.scoreData.hitMeta.gaugeHistory;
|
||||
pbDoc.scoreData.optional.gsm = lampPB.scoreData.optional.gsm;
|
||||
pbDoc.scoreData.optional.gauge = lampPB.scoreData.optional.gauge;
|
||||
pbDoc.scoreData.optional.gaugeHistory = lampPB.scoreData.optional.gaugeHistory;
|
||||
|
||||
pbDoc.scoreData.hitMeta.comboBreak = lampPB.scoreData.hitMeta.comboBreak;
|
||||
pbDoc.scoreData.optional.comboBreak = lampPB.scoreData.optional.comboBreak;
|
||||
|
||||
DeleteUndefinedProps(pbDoc.scoreData.hitMeta);
|
||||
DeleteUndefinedProps(pbDoc.scoreData.optional);
|
||||
|
||||
await MergeBPPB(pbDoc, scorePB, lampPB, logger, asOfTimestamp);
|
||||
|
||||
@@ -38,7 +38,7 @@ export function PopnMergeFn(
|
||||
lampPB: ScoreDocument<"popn:9B">,
|
||||
_logger: KtLogger
|
||||
) {
|
||||
pbDoc.scoreData.hitMeta.specificClearType = lampPB.scoreData.hitMeta.specificClearType;
|
||||
pbDoc.scoreData.optional.specificClearType = lampPB.scoreData.optional.specificClearType;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -52,8 +52,8 @@ export async function BMSMergeFn(
|
||||
) {
|
||||
pbDoc.calculatedData.sieglinde = lampPB.calculatedData.sieglinde;
|
||||
|
||||
pbDoc.scoreData.hitMeta.gaugeHistory = lampPB.scoreData.hitMeta.gaugeHistory;
|
||||
pbDoc.scoreData.hitMeta.gauge = lampPB.scoreData.hitMeta.gauge;
|
||||
pbDoc.scoreData.optional.gaugeHistory = lampPB.scoreData.optional.gaugeHistory;
|
||||
pbDoc.scoreData.optional.gauge = lampPB.scoreData.optional.gauge;
|
||||
|
||||
await MergeBPPB(pbDoc, scorePB, lampPB, logger, asOfTimestamp);
|
||||
|
||||
@@ -133,7 +133,7 @@ export async function SDVXMergeFn(
|
||||
|
||||
const query: FilterQuery<ScoreDocument> = {
|
||||
chartID: pbDoc.chartID,
|
||||
"scoreData.hitMeta.exScore": { $type: "number" },
|
||||
"scoreData.optional.exScore": { $type: "number" },
|
||||
};
|
||||
|
||||
if (asOfTimestamp !== undefined) {
|
||||
@@ -143,14 +143,14 @@ export async function SDVXMergeFn(
|
||||
// find the users score with the highest exScore
|
||||
const bestExScore = (await db.scores.findOne(query, {
|
||||
sort: {
|
||||
"scoreData.hitMeta.exScore": -1,
|
||||
"scoreData.optional.exScore": -1,
|
||||
},
|
||||
})) as ScoreDocument<"sdvx:Single"> | null;
|
||||
|
||||
if (!bestExScore) {
|
||||
pbDoc.scoreData.hitMeta.exScore = undefined;
|
||||
pbDoc.scoreData.optional.exScore = undefined;
|
||||
} else {
|
||||
pbDoc.scoreData.hitMeta.exScore = bestExScore.scoreData.hitMeta.exScore;
|
||||
pbDoc.scoreData.optional.exScore = bestExScore.scoreData.optional.exScore;
|
||||
|
||||
pbDoc.composedFrom.other = [{ name: "exScorePB", scoreID: bestExScore.scoreID }];
|
||||
}
|
||||
@@ -183,7 +183,7 @@ async function MergeBPPB(
|
||||
const query: FilterQuery<ScoreDocument> = {
|
||||
userID: scorePB.userID,
|
||||
chartID: scorePB.chartID,
|
||||
"scoreData.hitMeta.bp": { $exists: true },
|
||||
"scoreData.optional.bp": { $exists: true },
|
||||
};
|
||||
|
||||
if (asOfTimestamp !== undefined) {
|
||||
@@ -193,7 +193,7 @@ async function MergeBPPB(
|
||||
const bpPB = (await db.scores.findOne(query, {
|
||||
sort: {
|
||||
// bp 0 is the best BP, bp 1 is worse, so on
|
||||
"scoreData.hitMeta.bp": 1,
|
||||
"scoreData.optional.bp": 1,
|
||||
},
|
||||
})) as ScoreDocument<"iidx:DP" | "iidx:SP"> | null;
|
||||
|
||||
@@ -207,17 +207,17 @@ async function MergeBPPB(
|
||||
return;
|
||||
}
|
||||
|
||||
// by default scorePB is chosen for hitMeta fields, so, we can skip any assignments here by returning here.
|
||||
// by default scorePB is chosen for optional fields, so, we can skip any assignments here by returning here.
|
||||
if (bpPB.scoreID === scorePB.scoreID) {
|
||||
logger.debug(`Skipped merging BP PB as scorePB was also BP PB.`);
|
||||
return true;
|
||||
} else if (bpPB.scoreID === lampPB.scoreID) {
|
||||
pbDoc.scoreData.hitMeta.bp = lampPB.scoreData.hitMeta.bp;
|
||||
pbDoc.scoreData.optional.bp = lampPB.scoreData.optional.bp;
|
||||
logger.debug(`Skipped adding BP PB as composedFrom because lampPB was also BP PB.`);
|
||||
return;
|
||||
}
|
||||
|
||||
pbDoc.scoreData.hitMeta.bp = bpPB.scoreData.hitMeta.bp;
|
||||
pbDoc.scoreData.optional.bp = bpPB.scoreData.optional.bp;
|
||||
|
||||
pbDoc.composedFrom.other = [{ name: "Best BP", scoreID: bpPB.scoreID }];
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ export const ConverterBatchManual: ConverterFunction<BatchManualScore, BatchManu
|
||||
grade,
|
||||
percent,
|
||||
judgements: data.judgements ?? {},
|
||||
optional: data.hitMeta ?? {},
|
||||
optional: data.optional ?? {},
|
||||
},
|
||||
scoreMeta: data.scoreMeta ?? {},
|
||||
};
|
||||
|
||||
@@ -515,11 +515,11 @@ t.test("#ParserFn", (t) => {
|
||||
const fn = () =>
|
||||
ParserFn(dm({ optional: { not_key: 123 } }), "file/batch-manual", logger);
|
||||
|
||||
t.throws(fn, mockErr("scores[0].hitMeta | Unexpected"));
|
||||
t.throws(fn, mockErr("scores[0].optional | Unexpected"));
|
||||
|
||||
const fn2 = () => ParserFn(dm({ optional: { bp: -1 } }), "file/batch-manual", logger);
|
||||
|
||||
t.throws(fn2, mockErr("scores[0].hitMeta.bp | Expected a positive integer"));
|
||||
t.throws(fn2, mockErr("scores[0].optional.bp | Expected a positive integer"));
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
@@ -166,7 +166,7 @@ const ConvertEamIIDXCSV: ConverterFunction<
|
||||
);
|
||||
}
|
||||
|
||||
dryScore.scoreData.hitMeta.bp = numBP;
|
||||
dryScore.scoreData.optional.bp = numBP;
|
||||
} else if (eamScore.bp === "---") {
|
||||
logger.debug(
|
||||
`Skipped assigning BP for score as it had expected null value of ${eamScore.bp}.`
|
||||
|
||||
@@ -2,14 +2,14 @@ import {
|
||||
InvalidScoreFailure,
|
||||
SongOrChartNotFoundFailure,
|
||||
} from "lib/score-import/framework/common/converter-failures";
|
||||
import { GenericGetGradeAndPercent } from "lib/score-import/framework/common/score-utils";
|
||||
import { AssertStrAsPositiveInt } from "lib/score-import/framework/common/string-asserts";
|
||||
import { FindChartWithPTDF } from "utils/queries/charts";
|
||||
import { FindSongOnTitle } from "utils/queries/songs";
|
||||
import type { ConverterFunction } from "../../common/types";
|
||||
import type { SDVXEamusementCSVData } from "./types";
|
||||
import type { DryScore } from "lib/score-import/framework/common/types";
|
||||
import type { Difficulties, Lamps } from "tachi-common";
|
||||
import type { Difficulties } from "tachi-common";
|
||||
import type { GetEnumValue } from "tachi-common/types/metrics";
|
||||
import type { EmptyObject } from "utils/types";
|
||||
|
||||
const DIFFICULTY_MAP: Map<string, Difficulties["sdvx:Single"]> = new Map([
|
||||
@@ -105,8 +105,6 @@ const ConvertEamSDVXCSV: ConverterFunction<SDVXEamusementCSVData, EmptyObject> =
|
||||
throw new InvalidScoreFailure(`${humanisedChartTitle} - Invalid lamp of ${data.lamp}.`);
|
||||
}
|
||||
|
||||
const { percent, grade } = GenericGetGradeAndPercent("sdvx", score, chart);
|
||||
|
||||
const dryScore: DryScore<"sdvx:Single"> = {
|
||||
service: "e-amusement",
|
||||
game: "sdvx",
|
||||
@@ -119,8 +117,6 @@ const ConvertEamSDVXCSV: ConverterFunction<SDVXEamusementCSVData, EmptyObject> =
|
||||
scoreData: {
|
||||
score,
|
||||
lamp,
|
||||
percent,
|
||||
grade,
|
||||
judgements: {},
|
||||
optional: {
|
||||
exScore,
|
||||
|
||||
@@ -2,16 +2,13 @@ import {
|
||||
InternalFailure,
|
||||
SongOrChartNotFoundFailure,
|
||||
} from "../../../framework/common/converter-failures";
|
||||
import {
|
||||
GenericGetGradeAndPercent,
|
||||
ParseDateFromString,
|
||||
} from "../../../framework/common/score-utils";
|
||||
import { ParseDateFromString } from "../../../framework/common/score-utils";
|
||||
import { FindIIDXChartOnInGameID } 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 { MerScore } from "./types";
|
||||
import type { Lamps } from "tachi-common";
|
||||
import type { GetEnumValue } from "tachi-common/types/metrics";
|
||||
import type { EmptyObject } from "utils/types";
|
||||
|
||||
function ConvertMERLamp(lamp: MerScore["clear_type"]): GetEnumValue<"iidx:DP" | "iidx:SP", "lamp"> {
|
||||
@@ -48,8 +45,6 @@ export const ConvertFileMerIIDX: ConverterFunction<MerScore, EmptyObject> = asyn
|
||||
throw new InternalFailure(`Song-Chart Desync on songID ${chart.songID}`);
|
||||
}
|
||||
|
||||
const { percent, grade } = GenericGetGradeAndPercent("iidx", data.score, chart);
|
||||
|
||||
const lamp = ConvertMERLamp(data.clear_type);
|
||||
|
||||
const timeAchieved = ParseDateFromString(ConvertDateToJST(data.update_time));
|
||||
@@ -61,8 +56,6 @@ export const ConvertFileMerIIDX: ConverterFunction<MerScore, EmptyObject> = asyn
|
||||
service: "MER",
|
||||
scoreData: {
|
||||
score: data.score,
|
||||
percent,
|
||||
grade,
|
||||
lamp,
|
||||
judgements: {},
|
||||
optional: {
|
||||
|
||||
@@ -2,13 +2,11 @@ import {
|
||||
InternalFailure,
|
||||
SongOrChartNotFoundFailure,
|
||||
} from "../../../framework/common/converter-failures";
|
||||
import { GenericGetGradeAndPercent } from "../../../framework/common/score-utils";
|
||||
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, BarbatosSDVX6Score } from "./types";
|
||||
import type { Lamps } from "tachi-common";
|
||||
|
||||
const LAMP_LOOKUP = {
|
||||
1: "FAILED",
|
||||
@@ -16,7 +14,7 @@ const LAMP_LOOKUP = {
|
||||
3: "EXCESSIVE CLEAR",
|
||||
4: "ULTIMATE CHAIN",
|
||||
5: "PERFECT ULTIMATE CHAIN",
|
||||
};
|
||||
} as const;
|
||||
|
||||
const DIFFICULTY_LOOKUP = {
|
||||
0: "NOV",
|
||||
@@ -26,18 +24,13 @@ const DIFFICULTY_LOOKUP = {
|
||||
// special case for inf/grv/hvn/vvd - which are all the same diff internally. (kinda).
|
||||
3: "ANY_INF",
|
||||
4: "MXM",
|
||||
};
|
||||
} as const;
|
||||
|
||||
export const ConverterIRBarbatos: ConverterFunction<
|
||||
BarbatosScore | BarbatosSDVX6Score,
|
||||
BarbatosContext
|
||||
> = async (data, context, importType, logger) => {
|
||||
const difficulty = DIFFICULTY_LOOKUP[data.difficulty] as
|
||||
| "ADV"
|
||||
| "ANY_INF"
|
||||
| "EXH"
|
||||
| "MXM"
|
||||
| "NOV";
|
||||
const difficulty = DIFFICULTY_LOOKUP[data.difficulty];
|
||||
|
||||
const chart = await FindSDVXChartOnInGameIDVersion(data.song_id, difficulty, context.version);
|
||||
|
||||
@@ -57,8 +50,6 @@ export const ConverterIRBarbatos: ConverterFunction<
|
||||
throw new InternalFailure(`Song ${chart.songID} (sdvx) has no parent song?`);
|
||||
}
|
||||
|
||||
const { percent, grade } = GenericGetGradeAndPercent("sdvx", data.score, chart);
|
||||
|
||||
const { critical, near, miss } = GetJudgements(data);
|
||||
const { fast, slow } = GetFastSlow(data);
|
||||
|
||||
@@ -70,9 +61,7 @@ export const ConverterIRBarbatos: ConverterFunction<
|
||||
timeAchieved: context.timeReceived,
|
||||
scoreData: {
|
||||
score: data.score,
|
||||
percent,
|
||||
grade,
|
||||
lamp: LAMP_LOOKUP[data.clear_type] as GetEnumValue<"sdvx:Single", "lamp">,
|
||||
lamp: LAMP_LOOKUP[data.clear_type],
|
||||
judgements: {
|
||||
critical,
|
||||
near,
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
InvalidScoreFailure,
|
||||
SongOrChartNotFoundFailure,
|
||||
} from "../../../framework/common/converter-failures";
|
||||
import { GenericGetGradeAndPercent } from "../../../framework/common/score-utils";
|
||||
import db from "external/mongo/db";
|
||||
import { HandleOrphanQueue } from "lib/orphan-queue/orphan-queue";
|
||||
import { ReprocessOrphan } from "lib/score-import/framework/orphans/orphans";
|
||||
@@ -17,6 +16,7 @@ import type { ConverterFunction } from "../../common/types";
|
||||
import type { BeatorajaChart, BeatorajaContext, BeatorajaScore } from "./types";
|
||||
import type { KtLogger } from "lib/logger/logger";
|
||||
import type { ChartDocument, SongDocument, Playtypes } from "tachi-common";
|
||||
import type { Mutable } from "utils/types";
|
||||
|
||||
const LAMP_LOOKUP = {
|
||||
NoPlay: "NO PLAY",
|
||||
@@ -177,11 +177,9 @@ export const ConverterIRBeatoraja: ConverterFunction<BeatorajaScore, BeatorajaCo
|
||||
throw new InternalFailure(`Song-Chart Desync with ${game} ${chart.chartID}.`);
|
||||
}
|
||||
|
||||
const { grade, percent } = GenericGetGradeAndPercent(game, data.exscore, chart);
|
||||
|
||||
const optional: DryScore<
|
||||
"bms:7K" | "bms:14K" | "pms:Controller" | "pms:Keyboard"
|
||||
>["scoreData"]["hitMeta"] = {
|
||||
const optional: Mutable<
|
||||
DryScore<"bms:7K" | "bms:14K" | "pms:Controller" | "pms:Keyboard">["scoreData"]["optional"]
|
||||
> = {
|
||||
bp: data.minbp === -1 ? null : data.minbp,
|
||||
gauge: data.gauge === -1 ? null : data.gauge,
|
||||
};
|
||||
@@ -198,11 +196,11 @@ export const ConverterIRBeatoraja: ConverterFunction<BeatorajaScore, BeatorajaCo
|
||||
"epr",
|
||||
"lpr",
|
||||
] as const) {
|
||||
hitMeta[k] = data[k];
|
||||
optional[k] = data[k];
|
||||
}
|
||||
|
||||
hitMeta.epr = hitMeta.epr! + data.ems;
|
||||
hitMeta.lpr = hitMeta.lpr! + data.lms;
|
||||
optional.epr = (optional.epr ?? 0) + (data.ems ?? 0);
|
||||
optional.lpr = (optional.lpr ?? 0) + (data.lms ?? 0);
|
||||
|
||||
const judgements = {
|
||||
[game === "pms" ? "cool" : "pgreat"]: data.epg + data.lpg,
|
||||
@@ -212,8 +210,8 @@ export const ConverterIRBeatoraja: ConverterFunction<BeatorajaScore, BeatorajaCo
|
||||
poor: data.epr + data.lpr + data.ems + data.lms,
|
||||
};
|
||||
|
||||
hitMeta.fast = (["ebd", "egr", "epg", "epr", "ems"] as const).reduce((a, e) => a + data[e], 0);
|
||||
hitMeta.slow = (["lbd", "lgr", "lpg", "lpr", "lms"] as const).reduce((a, e) => a + data[e], 0);
|
||||
optional.fast = (["ebd", "egr", "epg", "epr", "ems"] as const).reduce((a, e) => a + data[e], 0);
|
||||
optional.slow = (["lbd", "lgr", "lpg", "lpr", "lms"] as const).reduce((a, e) => a + data[e], 0);
|
||||
|
||||
let random = null;
|
||||
|
||||
@@ -230,17 +228,21 @@ export const ConverterIRBeatoraja: ConverterFunction<BeatorajaScore, BeatorajaCo
|
||||
game,
|
||||
importType,
|
||||
scoreData: {
|
||||
grade,
|
||||
percent,
|
||||
score: data.exscore,
|
||||
lamp,
|
||||
hitMeta,
|
||||
optional,
|
||||
judgements,
|
||||
},
|
||||
scoreMeta: {
|
||||
client: context.client,
|
||||
inputDevice: data.deviceType,
|
||||
random,
|
||||
|
||||
// silly hack
|
||||
// it's complaining that this might be assigned for 14k
|
||||
// but it's not because of the conditions under which this is assigned.
|
||||
// sorry!
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
random: random as any,
|
||||
},
|
||||
timeAchieved: context.timeReceived,
|
||||
service: "Beatoraja IR",
|
||||
@@ -257,15 +259,16 @@ function ConvertBeatorajaChartToTachi(chart: BeatorajaChart, playtype: Playtypes
|
||||
level: "?",
|
||||
levelNum: 0,
|
||||
playtype,
|
||||
rgcID: null,
|
||||
songID: 0,
|
||||
versions: [],
|
||||
tierlistInfo: {},
|
||||
data: {
|
||||
hashMD5: chart.md5,
|
||||
hashSHA256: chart.sha256,
|
||||
notecount: chart.notes,
|
||||
tableFolders: [],
|
||||
aiLevel: null,
|
||||
sglEC: null,
|
||||
sglHC: null,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -2,14 +2,12 @@ import {
|
||||
InternalFailure,
|
||||
SongOrChartNotFoundFailure,
|
||||
} from "../../../framework/common/converter-failures";
|
||||
import { GenericGetGradeAndPercent } from "../../../framework/common/score-utils";
|
||||
import { FERVIDEX_LAMP_LOOKUP, SplitFervidexChartRef } from "../fervidex/converter";
|
||||
import { FindChartOnInGameIDVersion } 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 { FervidexStaticContext, FervidexStaticScore } from "./types";
|
||||
import type { Lamps } from "tachi-common";
|
||||
|
||||
export const ConverterIRFervidexStatic: ConverterFunction<
|
||||
FervidexStaticScore,
|
||||
@@ -41,12 +39,10 @@ export const ConverterIRFervidexStatic: ConverterFunction<
|
||||
throw new InternalFailure(`Song ${chart.songID} (iidx) has no parent song?`);
|
||||
}
|
||||
|
||||
const { percent, grade } = GenericGetGradeAndPercent("iidx", data.ex_score, chart);
|
||||
|
||||
const optional: { bp?: number | null } = {};
|
||||
|
||||
if (data.miss_count !== undefined) {
|
||||
hitMeta.bp = data.miss_count === -1 ? null : data.miss_count;
|
||||
optional.bp = data.miss_count === -1 ? null : data.miss_count;
|
||||
}
|
||||
|
||||
const dryScore: DryScore<"iidx:DP" | "iidx:SP"> = {
|
||||
@@ -57,11 +53,9 @@ export const ConverterIRFervidexStatic: ConverterFunction<
|
||||
timeAchieved: null,
|
||||
scoreData: {
|
||||
score: data.ex_score,
|
||||
percent,
|
||||
grade,
|
||||
lamp: FERVIDEX_LAMP_LOOKUP[data.clear_type] as GetEnumValue<"iidx:DP" | "iidx:SP", "lamp">,
|
||||
lamp: FERVIDEX_LAMP_LOOKUP[data.clear_type],
|
||||
judgements: {},
|
||||
hitMeta,
|
||||
optional,
|
||||
},
|
||||
scoreMeta: {},
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {
|
||||
InternalFailure,
|
||||
InvalidScoreFailure,
|
||||
SkipScoreFailure,
|
||||
SongOrChartNotFoundFailure,
|
||||
} from "../../../framework/common/converter-failures";
|
||||
import { GenericGetGradeAndPercent } from "../../../framework/common/score-utils";
|
||||
import db from "external/mongo/db";
|
||||
import { CreateScoreID } from "lib/score-import/framework/score-importing/score-id";
|
||||
import { IsNullishOrEmptyStr } from "utils/misc";
|
||||
@@ -12,7 +12,7 @@ import { FindSongOnID } from "utils/queries/songs";
|
||||
import type { DryScore } from "../../../framework/common/types";
|
||||
import type { ConverterFunction } from "../../common/types";
|
||||
import type { FervidexContext, FervidexScore } from "./types";
|
||||
import type { Difficulties, Lamps, Playtypes } from "tachi-common";
|
||||
import type { Difficulties, Playtypes } from "tachi-common";
|
||||
|
||||
export const FERVIDEX_LAMP_LOOKUP = {
|
||||
0: "NO PLAY",
|
||||
@@ -23,7 +23,7 @@ export const FERVIDEX_LAMP_LOOKUP = {
|
||||
5: "HARD CLEAR",
|
||||
6: "EX HARD CLEAR",
|
||||
7: "FULL COMBO",
|
||||
};
|
||||
} as const;
|
||||
|
||||
export function TachifyAssist(
|
||||
assist: Required<FervidexScore>["option"]["assist"]
|
||||
@@ -108,10 +108,8 @@ export function SplitFervidexChartRef(ferDif: FervidexScore["chart"]) {
|
||||
let difficulty: Difficulties["iidx:DP" | "iidx:SP"];
|
||||
|
||||
switch (ferDif[ferDif.length - 1]) {
|
||||
case "b": {
|
||||
difficulty = "BEGINNER";
|
||||
break;
|
||||
}
|
||||
case "b":
|
||||
throw new SkipScoreFailure(`BEGINNER charts are not supported.`);
|
||||
|
||||
case "n": {
|
||||
difficulty = "NORMAL";
|
||||
@@ -191,8 +189,6 @@ export const ConverterIRFervidex: ConverterFunction<FervidexScore, FervidexConte
|
||||
throw new InvalidScoreFailure(`Invalid value of gauge ${gauge}.`);
|
||||
}
|
||||
|
||||
const { percent, grade } = GenericGetGradeAndPercent("iidx", data.ex_score, chart);
|
||||
|
||||
let bp: number | null = data.bad + data.poor;
|
||||
|
||||
if (data.dead) {
|
||||
@@ -207,9 +203,7 @@ export const ConverterIRFervidex: ConverterFunction<FervidexScore, FervidexConte
|
||||
timeAchieved: context.timeReceived,
|
||||
scoreData: {
|
||||
score: data.ex_score,
|
||||
percent,
|
||||
grade,
|
||||
lamp: FERVIDEX_LAMP_LOOKUP[data.clear_type] as GetEnumValue<"iidx:DP" | "iidx:SP", "lamp">,
|
||||
lamp: FERVIDEX_LAMP_LOOKUP[data.clear_type],
|
||||
judgements: {
|
||||
pgreat: data.pgreat,
|
||||
great: data.great,
|
||||
@@ -226,15 +220,16 @@ export const ConverterIRFervidex: ConverterFunction<FervidexScore, FervidexConte
|
||||
gauge,
|
||||
bp,
|
||||
comboBreak: data.combo_break,
|
||||
gsm: data["2dx-gsm"],
|
||||
gsmEasy: data["2dx-gsm"]?.EASY,
|
||||
gsmNormal: data["2dx-gsm"]?.NORMAL,
|
||||
gsmHard: data["2dx-gsm"]?.HARD,
|
||||
gsmEXHard: data["2dx-gsm"]?.EX_HARD,
|
||||
},
|
||||
},
|
||||
scoreMeta: {
|
||||
assist: TachifyAssist(data.option?.assist),
|
||||
gauge: TachifyGauge(data.option?.gauge),
|
||||
|
||||
// @ts-expect-error Awkward expansion of iidx:SP|DP strings here causes this
|
||||
// to complain that [x,x] is not assignable to SP randoms. This is a lazy ignore!
|
||||
random:
|
||||
chart.playtype === "SP"
|
||||
? TachifyRandom(data.option?.style)
|
||||
|
||||
@@ -2,13 +2,12 @@ import {
|
||||
InternalFailure,
|
||||
SongOrChartNotFoundFailure,
|
||||
} from "lib/score-import/framework/common/converter-failures";
|
||||
import { GenericGetGradeAndPercent } from "lib/score-import/framework/common/score-utils";
|
||||
import { FindSDVXChartOnInGameIDVersion } from "utils/queries/charts";
|
||||
import { FindSongOnID } from "utils/queries/songs";
|
||||
import type { ConverterFunction } from "../../common/types";
|
||||
import type { KsHookSV6CContext, KsHookSV6CScore } from "./types";
|
||||
import type { DryScore } from "lib/score-import/framework/common/types";
|
||||
import type { Lamps } from "tachi-common";
|
||||
import type { GetEnumValue } from "tachi-common/types/metrics";
|
||||
|
||||
export const ConverterIRKsHookSV6C: ConverterFunction<KsHookSV6CScore, KsHookSV6CContext> = async (
|
||||
data,
|
||||
@@ -36,8 +35,6 @@ export const ConverterIRKsHookSV6C: ConverterFunction<KsHookSV6CScore, KsHookSV6
|
||||
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 SV6C",
|
||||
@@ -46,8 +43,6 @@ export const ConverterIRKsHookSV6C: ConverterFunction<KsHookSV6CScore, KsHookSV6
|
||||
timeAchieved: context.timeReceived,
|
||||
scoreData: {
|
||||
score: data.score,
|
||||
percent,
|
||||
grade,
|
||||
lamp: SV6CConvertLamp(data.clear),
|
||||
judgements: {
|
||||
critical: data.critical,
|
||||
@@ -66,7 +61,9 @@ export const ConverterIRKsHookSV6C: ConverterFunction<KsHookSV6CScore, KsHookSV6
|
||||
return { song, chart, dryScore };
|
||||
};
|
||||
|
||||
export function SV6CConvertLamp(clear: KsHookSV6CScore["clear"]): GetEnumValue<"sdvx:Single", "lamp"> {
|
||||
export function SV6CConvertLamp(
|
||||
clear: KsHookSV6CScore["clear"]
|
||||
): GetEnumValue<"sdvx:Single", "lamp"> {
|
||||
if (clear === "CLEAR_PLAYED") {
|
||||
return "FAILED";
|
||||
} else if (clear === "CLEAR_EFFECTIVE") {
|
||||
|
||||
@@ -275,7 +275,7 @@ export function HumaniseGoalProgress(
|
||||
case "pms": {
|
||||
// @ts-expect-error This is guaranteed to exist, we're going to ignore it.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const maybeBP: number | null | undefined = userPB.scoreData.hitMeta.bp;
|
||||
const maybeBP: number | null | undefined = userPB.scoreData.optional.bp;
|
||||
|
||||
// render BP if it exists
|
||||
if (!IsNullish(maybeBP)) {
|
||||
@@ -288,7 +288,7 @@ export function HumaniseGoalProgress(
|
||||
case "itg": {
|
||||
// @ts-expect-error This is guaranteed to exist, we're going to ignore it.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const maybeDiedAt: number | null | undefined = userPB.scoreData.hitMeta.diedAt;
|
||||
const maybeDiedAt: number | null | undefined = userPB.scoreData.optional.diedAt;
|
||||
|
||||
// render diedAt if it exists as "FAILED (Died 25% in)"
|
||||
if (!IsNullish(maybeDiedAt)) {
|
||||
|
||||
+1
-1
@@ -143,7 +143,7 @@ router.get(
|
||||
pb.scoreData.score.toString(), // ex
|
||||
pb.scoreData.judgements.pgreat?.toString() ?? "0", // pgreat
|
||||
pb.scoreData.judgements.great?.toString() ?? "0", // great
|
||||
pb.scoreData.hitMeta.bp?.toString() ?? "0", // BP
|
||||
pb.scoreData.optional.bp?.toString() ?? "0", // BP
|
||||
ConvertEamLamp(pb.scoreData.lamp), // lamp
|
||||
ConvertEamGrade(pb.scoreData.grade) // grade
|
||||
);
|
||||
|
||||
@@ -59,14 +59,14 @@ export function TachiScoreDataToBeatorajaFormat(
|
||||
playcount,
|
||||
clear: LAMP_TO_BEATORAJA[scoreData.lampIndex] ?? 0,
|
||||
date: pbScore.timeAchieved ?? 0,
|
||||
maxcombo: scoreData.hitMeta.maxCombo ?? 0,
|
||||
gauge: scoreData.hitMeta.gauge ?? 0,
|
||||
maxcombo: scoreData.optional.maxCombo ?? 0,
|
||||
gauge: scoreData.optional.gauge ?? 0,
|
||||
|
||||
// These two are now unsupported due to performance concerns.
|
||||
deviceType: null,
|
||||
random: null,
|
||||
|
||||
minbp: scoreData.hitMeta.bp ?? 0,
|
||||
minbp: scoreData.optional.bp ?? 0,
|
||||
passnotes: 0,
|
||||
notes: notecount,
|
||||
};
|
||||
@@ -85,7 +85,7 @@ export function TachiScoreDataToBeatorajaFormat(
|
||||
"ems",
|
||||
"lms",
|
||||
] as Array<BeatorajaJudgements>) {
|
||||
judgements[key] = scoreData.hitMeta[key] ?? 0;
|
||||
judgements[key] = scoreData.optional[key] ?? 0;
|
||||
}
|
||||
|
||||
// // If we have no epg/egr data, we can't calculate EX score on the beatoraja client.
|
||||
|
||||
@@ -120,3 +120,6 @@ export type MigrationDocument = {
|
||||
status: "pending";
|
||||
}
|
||||
);
|
||||
|
||||
// https://www.designcise.com/web/tutorial/how-to-change-readonly-properties-to-be-writable-in-typescript
|
||||
export type Mutable<T> = { -readonly [K in keyof T]: T[K] };
|
||||
|
||||
Reference in New Issue
Block a user