checkpoint: 12

This commit is contained in:
zkldi
2023-01-19 16:39:30 +00:00
parent 75a99610a5
commit db133795e6
10 changed files with 129 additions and 44 deletions
+4
View File
@@ -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.
+2 -2
View File
@@ -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]>;
} & (
| {
+2 -2
View File
@@ -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> {
+3 -3
View File
@@ -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?
+7 -5
View File
@@ -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;
/**
+2 -2
View File
@@ -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,