mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-03 20:38:17 +03:00
checkpoint: 12
This commit is contained in:
@@ -97,6 +97,10 @@ export function GetGamePTConfig(game: Game, playtype: Playtypes[Game]): GamePTCo
|
||||
return GAME_PT_CONFIGS[gptString];
|
||||
}
|
||||
|
||||
export function GetGPTConfig(gptString: GPTString): GamePTConfig {
|
||||
return GAME_PT_CONFIGS[gptString];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configuration for this specific Game + Playtype. This type is narrowed
|
||||
* down to its least generic form, and is instead for gpt-specific use cases.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { integer } from "../types";
|
||||
import type {
|
||||
AdditionalMetrics,
|
||||
OptionalMetrics,
|
||||
Versions,
|
||||
Difficulties,
|
||||
ExtractedClasses,
|
||||
@@ -28,7 +28,7 @@ export type BatchManualScore<GPT extends GPTString = GPTString> = ExtractMetrics
|
||||
comment?: string | null;
|
||||
judgements?: Record<Judgements[GPT], integer>;
|
||||
timeAchieved?: number | null;
|
||||
additionalMetrics?: Partial<ExtractMetrics<AdditionalMetrics[GPT]>>;
|
||||
additionalMetrics?: Partial<ExtractMetrics<OptionalMetrics[GPT]>>;
|
||||
scoreMeta?: Partial<ScoreMeta[GPT]>;
|
||||
} & (
|
||||
| {
|
||||
|
||||
@@ -3,7 +3,7 @@ import type {
|
||||
Versions,
|
||||
Classes,
|
||||
Difficulties,
|
||||
ExtractedAdditionalMetrics,
|
||||
ExtractedOptionalMetrics as ExtractedOptionalMetrics,
|
||||
ExtractedScoreMetrics,
|
||||
GPTString,
|
||||
GPTStringToGame,
|
||||
@@ -415,7 +415,7 @@ export type QuestSubscriptionDocument = {
|
||||
|
||||
export type ScoreData<GPT extends GPTString = GPTString> = ExtractedScoreMetrics[GPT] & {
|
||||
judgements: Partial<Record<Judgements[GPT], integer | null>>;
|
||||
additionalMetrics: Partial<ExtractedAdditionalMetrics[GPT]>;
|
||||
optional: Partial<ExtractedOptionalMetrics[GPT]>;
|
||||
};
|
||||
|
||||
export interface ScoreDocument<GPT extends GPTString = GPTString> {
|
||||
|
||||
@@ -134,7 +134,7 @@ export type DerivedMetrics = {
|
||||
[G in GPTString]: typeof GAME_PT_CONFIGS[G]["derivedMetrics"];
|
||||
};
|
||||
|
||||
export type AdditionalMetrics = {
|
||||
export type OptionalMetrics = {
|
||||
[G in GPTString]: typeof GAME_PT_CONFIGS[G]["optionalMetrics"];
|
||||
};
|
||||
|
||||
@@ -167,7 +167,7 @@ export type ExtractedScoreMetrics = {
|
||||
ExtractMetrics<typeof GAME_PT_CONFIGS[G]["providedMetrics"]>;
|
||||
};
|
||||
|
||||
export type ExtractedAdditionalMetrics = {
|
||||
export type ExtractedOptionalMetrics = {
|
||||
[G in GPTString]: ExtractMetrics<typeof GAME_PT_CONFIGS[G]["optionalMetrics"]>;
|
||||
};
|
||||
|
||||
@@ -239,7 +239,7 @@ export interface SpecificGamePTConfig<GPT extends GPTString> {
|
||||
* The idea of additionalMetrics allow us to store useful metrics about scores
|
||||
* without necessitating that they exist on arrival. Incredibly convenient.
|
||||
*/
|
||||
additionalMetrics: AdditionalMetrics[GPT];
|
||||
additionalMetrics: OptionalMetrics[GPT];
|
||||
|
||||
/**
|
||||
* What rating algorithms may a score have attached onto it for this GPT?
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { integer } from "../types";
|
||||
import type { ChartDocument } from "./documents";
|
||||
import type { AdditionalMetrics, DerivedMetrics, GPTString, ProvidedMetrics } from "./game-config";
|
||||
import type { OptionalMetrics, DerivedMetrics, GPTString, ProvidedMetrics } from "./game-config";
|
||||
|
||||
export type DecimalMetricValidator<GPT extends GPTString> = (
|
||||
metric: number,
|
||||
@@ -101,10 +101,10 @@ export type ExtractEnumMetricNames<R extends Record<string, ScoreMetric>> = {
|
||||
export type ExtractEnumValues<
|
||||
GPT extends GPTString,
|
||||
MetricName extends ExtractEnumMetricNames<
|
||||
AdditionalMetrics[GPT] & DerivedMetrics[GPT] & ProvidedMetrics[GPT]
|
||||
DerivedMetrics[GPT] & OptionalMetrics[GPT] & ProvidedMetrics[GPT]
|
||||
>
|
||||
> = (AdditionalMetrics[GPT] &
|
||||
DerivedMetrics[GPT] &
|
||||
> = (DerivedMetrics[GPT] &
|
||||
OptionalMetrics[GPT] &
|
||||
ProvidedMetrics[GPT])[MetricName] extends EnumScoreMetric<infer EnumValues>
|
||||
? EnumValues
|
||||
: never;
|
||||
@@ -122,12 +122,14 @@ export type ExtractMetrics<R extends Record<string, ScoreMetric>> = {
|
||||
[K in keyof R]: ExtractMetricType<R[K]>;
|
||||
};
|
||||
|
||||
export type DerivedMetricValue = Array<number> | integer | number | string;
|
||||
|
||||
export type MetricDeriver<
|
||||
M extends Record<string, PossibleMetrics>,
|
||||
GPT extends GPTString,
|
||||
// possible return values
|
||||
// from a derived fn
|
||||
V extends Array<number> | integer | number | string
|
||||
V extends DerivedMetricValue = DerivedMetricValue
|
||||
> = (mandatoryMetrics: M, chart: ChartDocument<GPT>) => V;
|
||||
|
||||
/**
|
||||
|
||||
@@ -195,8 +195,8 @@ export function FormatChart(
|
||||
|
||||
// For games with 'BP', show that next to the clear.
|
||||
export function IIDXBMSLampGoalFormatter(pb: PBScoreDocument<GPTStrings["bms" | "iidx" | "pms"]>) {
|
||||
if (typeof pb.scoreData.additionalMetrics.bp === "number") {
|
||||
return `${pb.scoreData.lamp.string} (BP: ${pb.scoreData.additionalMetrics.bp})`;
|
||||
if (typeof pb.scoreData.optional.bp === "number") {
|
||||
return `${pb.scoreData.lamp.string} (BP: ${pb.scoreData.optional.bp})`;
|
||||
}
|
||||
|
||||
return pb.scoreData.lamp;
|
||||
|
||||
@@ -24,7 +24,6 @@ import type { ChartDocument, Game, GPTString, Lamps, Playtypes, ScoreDocument }
|
||||
export async function CreateCalculatedData(
|
||||
dryScore: DryScore,
|
||||
chart: ChartDocument,
|
||||
esd: number | null,
|
||||
logger: KtLogger
|
||||
): Promise<ScoreDocument["calculatedData"]> {
|
||||
const game = dryScore.game;
|
||||
|
||||
@@ -1,14 +1,25 @@
|
||||
import type { GPTString, Playtype, ScoreDocument } from "tachi-common";
|
||||
import type {
|
||||
GPTString,
|
||||
Playtype,
|
||||
ProvidedMetrics,
|
||||
ScoreDocument,
|
||||
ExtractedOptionalMetrics,
|
||||
Judgements,
|
||||
integer,
|
||||
} from "tachi-common";
|
||||
|
||||
/**
|
||||
* An intermediate score format that will be filled out by
|
||||
* An intermediate score format that will be fully filled out by
|
||||
* HydrateScore.
|
||||
*/
|
||||
export type DryScore<I extends GPTString = GPTString> = Pick<
|
||||
ScoreDocument<I>,
|
||||
export type DryScore<GPT extends GPTString = GPTString> = Pick<
|
||||
ScoreDocument<GPT>,
|
||||
"comment" | "game" | "importType" | "scoreMeta" | "service" | "timeAchieved"
|
||||
> & {
|
||||
scoreData: Omit<ScoreDocument<I>["scoreData"], "esd" | "gradeIndex" | "lampIndex">;
|
||||
scoreData: ProvidedMetrics[GPT] & {
|
||||
optional: ExtractedOptionalMetrics[GPT];
|
||||
judgements: Partial<Record<Judgements[GPT], integer | null>>;
|
||||
};
|
||||
};
|
||||
|
||||
export type ScorePlaytypeMap = Partial<Record<Playtype, Array<ScoreDocument>>>;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
// ^ ts eslint currently gets very confused about the complexity on show here
|
||||
@@ -6,9 +9,11 @@
|
||||
import { GetGrade } from "./common-utils";
|
||||
import { IIDXLIKE_DERIVERS } from "./games/iidx-like";
|
||||
import { SDVXLIKE_DERIVERS } from "./games/sdvx-like";
|
||||
import { InternalFailure } from "../common/converter-failures";
|
||||
import {
|
||||
CHUNITHM_GBOUNDARIES,
|
||||
GITADORA_GBOUNDARIES,
|
||||
GetGPTConfig,
|
||||
ITG_GBOUNDARIES,
|
||||
JUBEAT_GBOUNDARIES,
|
||||
MAIMAIDX_GBOUNDARIES,
|
||||
@@ -16,8 +21,16 @@ import {
|
||||
POPN_GBOUNDARIES,
|
||||
WACCA_GBOUNDARIES,
|
||||
} from "tachi-common";
|
||||
import type { DryScore } from "../common/types";
|
||||
import type { GPTDerivers } from "./types";
|
||||
import type { GPTString } from "tachi-common";
|
||||
import type {
|
||||
GPTString,
|
||||
ChartDocument,
|
||||
ProvidedMetrics,
|
||||
DerivedMetrics,
|
||||
ScoreData,
|
||||
} from "tachi-common";
|
||||
import type { MetricDeriver } from "tachi-common/types/metrics";
|
||||
|
||||
type AllGPTDerivers = {
|
||||
[GPT in GPTString]: GPTDerivers<GPT>;
|
||||
@@ -26,7 +39,7 @@ type AllGPTDerivers = {
|
||||
/**
|
||||
* How do we derive the "derivedMetrics" for each game?
|
||||
*/
|
||||
export const GPT_DERIVERS: AllGPTDerivers = {
|
||||
const GPT_DERIVERS: AllGPTDerivers = {
|
||||
// these games quite literally *all* work the same way.
|
||||
"bms:14K": IIDXLIKE_DERIVERS,
|
||||
"bms:7K": IIDXLIKE_DERIVERS,
|
||||
@@ -119,3 +132,74 @@ export const GPT_DERIVERS: AllGPTDerivers = {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Given the providedMetrics and chart this score is on, derive the rest of the metrics
|
||||
* we want to store.
|
||||
*/
|
||||
function DeriveMetrics<GPT extends GPTString>(
|
||||
gpt: GPTString,
|
||||
metrics: ProvidedMetrics[GPT],
|
||||
chart: ChartDocument<GPT>
|
||||
) {
|
||||
const deriverImplementation: Record<
|
||||
string,
|
||||
MetricDeriver<ProvidedMetrics[GPT], GPT>
|
||||
> = GPT_DERIVERS[gpt];
|
||||
|
||||
const derivedMetrics: Record<string, DerivedMetricValue> = {};
|
||||
|
||||
const gptConfig = GetGPTConfig(gpt);
|
||||
|
||||
for (const [key, fn] of Object.entries(deriverImplementation)) {
|
||||
const metricConfig = gptConfig.derivedMetrics[key];
|
||||
|
||||
if (!metricConfig) {
|
||||
throw new InternalFailure(
|
||||
`${gpt} has a deriver defined for '${key}', but no such field exists in the config?`
|
||||
);
|
||||
}
|
||||
|
||||
const value = fn(metrics, chart);
|
||||
|
||||
// enum values on scores are stored as { string, index }. One is convenient
|
||||
// for sorting and stuff in the DB, the other is convenient to know what
|
||||
// actually is what. Integers only sucks.
|
||||
if (metricConfig.type === "ENUM") {
|
||||
const index = metricConfig.values.indexOf(value);
|
||||
|
||||
if (index === -1) {
|
||||
throw new InternalFailure(
|
||||
`Failed to get the index for ENUM ${gpt} '${key}' for value ${value}. This should never happen!`
|
||||
);
|
||||
}
|
||||
|
||||
derivedMetrics[key] = {
|
||||
string: value,
|
||||
index,
|
||||
};
|
||||
} else {
|
||||
derivedMetrics[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return derivedMetrics as DerivedMetrics[GPT];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a full piece of scoreData.
|
||||
*/
|
||||
export function CreateFullScoreData<GPT extends GPTString>(
|
||||
gpt: GPTString,
|
||||
dryScoreData: DryScore["scoreData"],
|
||||
chart: ChartDocument<GPT>
|
||||
) {
|
||||
const derivedMetrics = DeriveMetrics(gpt, dryScoreData, chart);
|
||||
|
||||
const scoreData: ScoreData = {
|
||||
...dryScoreData,
|
||||
derivedMetrics,
|
||||
};
|
||||
|
||||
return scoreData;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CreateCalculatedData } from "../calculated-data/calculated-data";
|
||||
import { CalculateESDForGame } from "../common/score-utils";
|
||||
import { GetGamePTConfig } from "tachi-common";
|
||||
import { CreateFullScoreData } from "../derivers/derivers";
|
||||
import { GetGPTString } from "tachi-common";
|
||||
import type { DryScore } from "../common/types";
|
||||
import type { KtLogger } from "lib/logger/logger";
|
||||
import type { ChartDocument, integer, ScoreDocument, SongDocument } from "tachi-common";
|
||||
@@ -18,32 +18,17 @@ export async function HydrateScore(
|
||||
scoreID: string,
|
||||
logger: KtLogger
|
||||
): Promise<ScoreDocument> {
|
||||
const esd = CalculateESDForGame(
|
||||
dryScore.game,
|
||||
chart.playtype,
|
||||
dryScore.scoreData.percent / 100
|
||||
);
|
||||
const gpt = GetGPTString(dryScore.game, chart.playtype);
|
||||
|
||||
const calculatedData = await CreateCalculatedData(dryScore, chart, esd, logger);
|
||||
const scoreData = CreateFullScoreData(gpt, dryScore.scoreData, chart);
|
||||
|
||||
const gptConfig = GetGamePTConfig(dryScore.game, chart.playtype);
|
||||
|
||||
// Fill out the rest of the fields we want for scoreData
|
||||
const scoreData = {
|
||||
lampIndex: gptConfig.lamps.indexOf(dryScore.scoreData.lamp),
|
||||
gradeIndex: gptConfig.grades.indexOf(dryScore.scoreData.grade),
|
||||
esd,
|
||||
...dryScoreData,
|
||||
};
|
||||
const calculatedData = await CreateCalculatedData(dryScore, chart, logger);
|
||||
|
||||
const score: ScoreDocument = {
|
||||
...dryScore,
|
||||
|
||||
// then push our score data.
|
||||
scoreData: {
|
||||
...dryScore.scoreData,
|
||||
...derivedMetrics,
|
||||
},
|
||||
// then push our new score data.
|
||||
scoreData,
|
||||
|
||||
// everything below this point is sane
|
||||
highlight: false,
|
||||
|
||||
Reference in New Issue
Block a user