mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-08 06:48:04 +03:00
checkpoint: 20
This commit is contained in:
@@ -266,6 +266,7 @@ export const BMS_7K_CONF = {
|
||||
|
||||
export const BMS_14K_CONF = {
|
||||
...BMS_7K_CONF,
|
||||
|
||||
classes: {
|
||||
genocideDan: { type: "PROVIDED", values: BMSGenocideDans },
|
||||
stslDan: { type: "PROVIDED", values: BMSDPSlDans },
|
||||
|
||||
+72
-15
@@ -7,6 +7,19 @@ import type {
|
||||
ConfProvidedMetrics,
|
||||
} from "./game-config";
|
||||
|
||||
// WARNING! THIS FILE IS VERY COMPLEX. THERE IS SOME HIGH-TIER TYPESCRIPT MAGIC GOING
|
||||
// ON HERE.
|
||||
|
||||
// The rough outline is that Tachi allows games to have up to 5 kinds of metrics
|
||||
// "DECIMAL" | "INTEGER" | "ENUM" | "GRAPH" and "NULLABLE_GRAPH".
|
||||
|
||||
// Metrics themselves then come in three groups:
|
||||
// - Provided: They **must** be provided for a score import to be usable
|
||||
// - Derived: They **will** be put on a score document, but are a function
|
||||
// of the provided metrics and the chart this score is on (i.e. Grades for IIDX)
|
||||
// - Additional: They **may** exist. We want to store them if they exist, but don't
|
||||
// mandate their existence (i.e. fast/slow/maxCombo)
|
||||
|
||||
export type DecimalMetricValidator<GPT extends GPTString> = (
|
||||
metric: number,
|
||||
chart: ChartDocument<GPT>
|
||||
@@ -66,6 +79,8 @@ export type ConfScoreMetric =
|
||||
/**
|
||||
* When we store and interact with enum values, we want them to be both strings
|
||||
* and integers.
|
||||
*
|
||||
* This is used by score documents that are stored in the DB.
|
||||
*/
|
||||
export interface EnumValue<S extends string> {
|
||||
string: S;
|
||||
@@ -74,9 +89,9 @@ export interface EnumValue<S extends string> {
|
||||
|
||||
/**
|
||||
* Given a Metric Type, turn it into its evaluated form. An IntegerScoreMetric
|
||||
* becomes an integer, etc.
|
||||
* becomes an integer and an enum becomes an EnumValue.
|
||||
*/
|
||||
export type ExtractMetricType<M extends ConfScoreMetric> = M extends ConfDecimalScoreMetric
|
||||
export type ExtractMetricValue<M extends ConfScoreMetric> = M extends ConfDecimalScoreMetric
|
||||
? number
|
||||
: M extends ConfIntegerScoreMetric
|
||||
? integer
|
||||
@@ -88,6 +103,26 @@ export type ExtractMetricType<M extends ConfScoreMetric> = M extends ConfDecimal
|
||||
? Array<number | null>
|
||||
: never;
|
||||
|
||||
/**
|
||||
* Given a Metric Type, turn it into its evaluated form with the caveat that
|
||||
* enums become strings instead of EnumValue<string>.
|
||||
*
|
||||
* This is used on the intermediate score importing step, where ENUM metrics aren't
|
||||
* fully realised. We'd be working with { lamp: "FAILED" } instead of
|
||||
* { lamp: { string: "FAILED", index: 1 }}.
|
||||
*/
|
||||
export type DryExtractMetricType<M extends ConfScoreMetric> = M extends ConfDecimalScoreMetric
|
||||
? number
|
||||
: M extends ConfIntegerScoreMetric
|
||||
? integer
|
||||
: M extends ConfEnumScoreMetric<infer V>
|
||||
? V
|
||||
: M extends ConfGraphScoreMetric
|
||||
? Array<number>
|
||||
: M extends ConfNullableGraphScoreMetric
|
||||
? Array<number | null>
|
||||
: never;
|
||||
|
||||
/**
|
||||
* Extract all the names of enum types from this record of score metrics.
|
||||
*
|
||||
@@ -109,6 +144,9 @@ export type ExtractEnumMetricNames<R extends Record<string, ConfScoreMetric>> =
|
||||
[K in keyof R]: R[K] extends ConfEnumScoreMetric<infer _> ? K : never;
|
||||
}[keyof R];
|
||||
|
||||
/**
|
||||
* What are all the metrics available for this GPT?
|
||||
*/
|
||||
export type AllMetrics = {
|
||||
[GPT in GPTString]: ConfDerivedMetrics[GPT] &
|
||||
ConfOptionalMetrics[GPT] &
|
||||
@@ -125,28 +163,47 @@ export type GetEnumValue<
|
||||
MetricName extends ExtractEnumMetricNames<AllMetrics[GPT]>
|
||||
> = AllMetrics[GPT][MetricName] extends ConfEnumScoreMetric<infer EnumValues> ? EnumValues : never;
|
||||
|
||||
type PossibleMetrics = ExtractMetricType<ConfScoreMetric>;
|
||||
|
||||
/**
|
||||
* Turn a record of ScoreMetrics into their actual literal values.
|
||||
* Turn a record of ConfigScoreMetrics into their actual literal values.
|
||||
*
|
||||
* @example ExtractMetrics<{ score: IntegerScoreMetric; lamp: EnumScoreMetric<"FAILED"|"CLEAR"> }>
|
||||
* @example ExtractMetrics<{
|
||||
* score: ConfIntegerScoreMetric; lamp: ConfEnumScoreMetric<"FAILED"|"CLEAR">
|
||||
* }>
|
||||
* will equal
|
||||
* { score: integer; lamp: "FAILED" | "CLEAR" }
|
||||
* { score: integer; lamp: { string: "FAILED" | "CLEAR "..., index: number } }
|
||||
*/
|
||||
export type ExtractMetrics<R extends Record<string, ConfScoreMetric>> = {
|
||||
[K in keyof R]: ExtractMetricType<R[K]>;
|
||||
[K in keyof R]: ExtractMetricValue<R[K]>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Turn a record of score metrics in to their "dry" values. These are used by dry
|
||||
* scores and consist of one change:
|
||||
*
|
||||
* Enums would normally be { string: "FAILED" | ..., index: number }. In dry extraction
|
||||
* they are extracted to just "FAILED" | ...
|
||||
*
|
||||
* This is because dry scores don't fill out these values.
|
||||
*/
|
||||
export type DryExtractMetrics<R extends Record<string, ConfScoreMetric>> = {
|
||||
[K in keyof R]: DryExtractMetricType<R[K]>;
|
||||
};
|
||||
|
||||
// We want some signatures for implementing metric "derivers".
|
||||
// This complex type nonsense effectively gives us a typesafe form for:
|
||||
// MetricDeriver<"iidx:SP", number>
|
||||
// (metrics: IIDXSPMetrics, chart: Chart<"iidx:SP"> ) => number
|
||||
|
||||
export type DerivedMetricValue = Array<number | null> | Array<number> | integer | number | string;
|
||||
|
||||
export type MetricValue = ExtractMetricValue<ConfScoreMetric>;
|
||||
|
||||
export type MetricDeriver<
|
||||
M extends Record<string, PossibleMetrics>,
|
||||
GPT extends GPTString,
|
||||
// possible return values
|
||||
// from a derived fn
|
||||
V extends DerivedMetricValue = DerivedMetricValue
|
||||
> = (mandatoryMetrics: M, chart: ChartDocument<GPT>) => V;
|
||||
> = (mandatoryMetrics: DryExtractMetrics<ConfProvidedMetrics[GPT]>, chart: ChartDocument<GPT>) => V;
|
||||
|
||||
/**
|
||||
* A function that will derive this metric, given a function of other metrics and
|
||||
@@ -155,14 +212,14 @@ export type MetricDeriver<
|
||||
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>>
|
||||
? MetricDeriver<GPT, Array<number>>
|
||||
: // nullable graphs correspond to Array<number | null>
|
||||
M extends ConfNullableGraphScoreMetric
|
||||
? MetricDeriver<ExtractMetrics<ConfProvidedMetrics[GPT]>, GPT, Array<number | null>>
|
||||
? MetricDeriver<GPT, Array<number | null>>
|
||||
: // enums correspond to their string unions ("FAILED"|"CLEAR")
|
||||
M extends ConfEnumScoreMetric<infer V>
|
||||
? MetricDeriver<ExtractMetrics<ConfProvidedMetrics[GPT]>, GPT, V>
|
||||
? MetricDeriver<GPT, V>
|
||||
: // the other two are obvious
|
||||
M extends ConfIntegerScoreMetric
|
||||
? MetricDeriver<ExtractMetrics<ConfProvidedMetrics[GPT]>, GPT, integer>
|
||||
: MetricDeriver<ExtractMetrics<ConfProvidedMetrics[GPT]>, GPT, number>;
|
||||
? MetricDeriver<GPT, integer>
|
||||
: MetricDeriver<GPT, number>;
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { CalculateDataFunction, GPTScoreCalculators } from "../types";
|
||||
|
||||
const SGLCalc: CalculateDataFunction<"bms:7K" | "bms:14K"> = (dryScore, chart) => {
|
||||
const ecValue = chart.data.sglEC ?? 0;
|
||||
const hcValue = chart.data.sglHC ?? 0;
|
||||
|
||||
switch (dryScore.scoreData.lamp) {
|
||||
case "FULL COMBO":
|
||||
case "EX HARD CLEAR":
|
||||
case "HARD CLEAR":
|
||||
return Math.max(hcValue, ecValue);
|
||||
case "CLEAR":
|
||||
case "EASY CLEAR":
|
||||
return ecValue;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
export const BMS_7K_SCORE_CALC: GPTScoreCalculators["bms:7K"] = {
|
||||
sieglinde: SGLCalc,
|
||||
};
|
||||
|
||||
export const BMS_14K_SCORE_CALC: GPTScoreCalculators["bms:14K"] = {
|
||||
sieglinde: SGLCalc,
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
import { CHUNITHMRating } from "rg-stats";
|
||||
import type { GPTScoreCalculators } from "../types";
|
||||
|
||||
export const CHUNITHM_SCORE_CALC: GPTScoreCalculators["chunithm:Single"] = {
|
||||
rating: (dryScore, chart) => CHUNITHMRating.calculate(dryScore.scoreData.score, chart.levelNum),
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
import { PoyashiBPI } from "rg-stats";
|
||||
import type { GPTScoreCalculators, CalculateDataFunction } from "../types";
|
||||
|
||||
const BPICalc: CalculateDataFunction<"iidx:DP" | "iidx:SP"> = (dryScore, chart) => {
|
||||
if (chart.data.kaidenAverage === null || chart.data.worldRecord === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return PoyashiBPI.calculate(
|
||||
dryScore.scoreData.score,
|
||||
chart.data.kaidenAverage,
|
||||
chart.data.worldRecord,
|
||||
chart.data.notecount * 2,
|
||||
chart.data.bpiCoefficient
|
||||
);
|
||||
};
|
||||
|
||||
export const IIDX_SP_SCORE_CALC: GPTScoreCalculators["iidx:SP"] = {
|
||||
BPI: BPICalc,
|
||||
ktLampRating: (dryScore, chart) => {
|
||||
// if chart has no ncValue, use the rating of the chart instead.
|
||||
const ncValue = chart.data.ncTier?.value ?? chart.levelNum;
|
||||
|
||||
// if hc < nc, hcValue = ncValue
|
||||
// this means you can't lose score from getting a better clear.
|
||||
// same for exhc < hc.
|
||||
const hcValue = Math.max(chart.data.hcTier?.value ?? 0, ncValue);
|
||||
const exhcValue = Math.max(chart.data.exhcTier?.value ?? 0, hcValue);
|
||||
|
||||
switch (dryScore.scoreData.lamp) {
|
||||
case "FULL COMBO":
|
||||
case "EX HARD CLEAR":
|
||||
return exhcValue;
|
||||
case "HARD CLEAR":
|
||||
return hcValue;
|
||||
case "CLEAR":
|
||||
return ncValue;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export const IIDX_DP_SCORE_CALC: GPTScoreCalculators["iidx:DP"] = {
|
||||
BPI: BPICalc,
|
||||
ktLampRating: (dryScore, chart) => {
|
||||
// if chart has no tier, use the rating of the chart instead.
|
||||
const ecValue = chart.data.dpTier?.value ?? chart.levelNum;
|
||||
|
||||
switch (dryScore.scoreData.lamp) {
|
||||
case "FULL COMBO":
|
||||
case "EX HARD CLEAR":
|
||||
case "HARD CLEAR":
|
||||
case "CLEAR":
|
||||
case "EASY CLEAR":
|
||||
return ecValue;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Jubility } from "rg-stats";
|
||||
import type { GPTScoreCalculators } from "../types";
|
||||
|
||||
export const JUBEAT_SCORE_CALC: GPTScoreCalculators["jubeat:Single"] = {
|
||||
jubility: (dryScore, chart) =>
|
||||
Jubility.calculate(dryScore.scoreData.score, dryScore.scoreData.musicRate, chart.levelNum),
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
import { PopnClearMedalToLamp } from "../../derivers/games/popn";
|
||||
import { PopnClassPoints } from "rg-stats";
|
||||
import type { GPTScoreCalculators } from "../types";
|
||||
|
||||
export const POPN_9B_SCORE_CALC: GPTScoreCalculators["popn:9B"] = {
|
||||
classPoints: (dryScore, chart) =>
|
||||
PopnClassPoints.calculate(
|
||||
dryScore.scoreData.score,
|
||||
PopnClearMedalToLamp(dryScore.scoreData.clearMedal),
|
||||
chart.levelNum
|
||||
),
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
import { WACCARate } from "rg-stats";
|
||||
import type { GPTScoreCalculators } from "../types";
|
||||
|
||||
export const WACCA_SCORE_CALC: GPTScoreCalculators["wacca:Single"] = {
|
||||
rate: (dryScore, chart) => WACCARate.calculate(dryScore.scoreData.score, chart.levelNum),
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import { BMS_14K_SCORE_CALC, BMS_7K_SCORE_CALC } from "./games/bms";
|
||||
import { CHUNITHM_SCORE_CALC } from "./games/chunithm";
|
||||
import { IIDX_DP_SCORE_CALC, IIDX_SP_SCORE_CALC } from "./games/iidx";
|
||||
import type { GPTScoreCalculators } from "./types";
|
||||
|
||||
export const SCORE_CALCULATORS: GPTScoreCalculators = {
|
||||
"iidx:SP": IIDX_SP_SCORE_CALC,
|
||||
"iidx:DP": IIDX_DP_SCORE_CALC,
|
||||
"bms:7K": BMS_7K_SCORE_CALC,
|
||||
"bms:14K": BMS_14K_SCORE_CALC,
|
||||
"chunithm:Single": CHUNITHM_SCORE_CALC,
|
||||
"jubeat:Single": JUBEAT_SCORE_CALC,
|
||||
"gitadora:Dora": GITADORA_DORA_SCORE_CALC,
|
||||
"gitadora:Gita": GITADORA_GITA_SCORE_CALC,
|
||||
"itg:Stamina": ITG_STAMINA_SCORE_CALC,
|
||||
"maimaidx:Single": MAIMAI_DX_SCORE_CALC,
|
||||
"museca:Single": MUSECA_SCORE_CALC,
|
||||
"pms:Controller": PMS_CONTROLLER_SCORE_CALC,
|
||||
"pms:Keyboard": PMS_KEYBOARD_SCORE_CALC,
|
||||
"popn:9B": POPN_9B_SCORE_CALC,
|
||||
"sdvx:Single": SDVX_SCORE_CALC,
|
||||
"usc:Controller": USC_CONTROLLER_SCORE_CALC,
|
||||
"usc:Keyboard": USC_KEYBOARD_SCORE_CALC,
|
||||
"wacca:Single": WACCA_SCORE_CALC,
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { DryScore } from "../common/types";
|
||||
import type { KtLogger } from "lib/logger/logger";
|
||||
import type { GPTString, ChartDocument, ScoreRatingAlgorithms } from "tachi-common";
|
||||
|
||||
export type CalculateDataFunction<GPT extends GPTString> = (
|
||||
dryScore: DryScore<GPT>,
|
||||
chart: ChartDocument<GPT>,
|
||||
logger: KtLogger
|
||||
) => number | null;
|
||||
|
||||
export type GPTScoreCalculators = {
|
||||
[G in GPTString]: Record<ScoreRatingAlgorithms[G], CalculateDataFunction<G>>;
|
||||
};
|
||||
@@ -6,8 +6,9 @@ import type {
|
||||
ScoreDocument,
|
||||
integer,
|
||||
OptionalMetrics,
|
||||
ConfProvidedMetrics,
|
||||
} from "tachi-common";
|
||||
import type { EnumValue } from "tachi-common/types/metrics";
|
||||
import type { DryExtractMetrics, EnumValue } from "tachi-common/types/metrics";
|
||||
import type { Mutable } from "utils/types";
|
||||
|
||||
/**
|
||||
@@ -21,7 +22,7 @@ export type ExtractEnumValues<TMetrics extends Record<string, unknown>> = {
|
||||
[K in keyof TMetrics]: TMetrics[K] extends EnumValue<infer V> ? V : TMetrics[K];
|
||||
};
|
||||
|
||||
type DryScoreData<GPT extends GPTString> = ExtractEnumValues<ProvidedMetrics[GPT]> & {
|
||||
export type DryScoreData<GPT extends GPTString> = DryExtractMetrics<ConfProvidedMetrics[GPT]> & {
|
||||
optional: Mutable<ExtractEnumValues<OptionalMetrics[GPT]>>;
|
||||
judgements: Partial<Record<Judgements[GPT], integer | null>>;
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import { GetGrade } from "./common-utils";
|
||||
import { IIDXLIKE_DERIVERS } from "./games/iidx-like";
|
||||
import { PopnClearMedalToLamp } from "./games/popn";
|
||||
import { SDVXLIKE_DERIVERS } from "./games/sdvx-like";
|
||||
import { InternalFailure } from "../common/converter-failures";
|
||||
import {
|
||||
@@ -21,16 +22,10 @@ import {
|
||||
POPN_GBOUNDARIES,
|
||||
WACCA_GBOUNDARIES,
|
||||
} from "tachi-common";
|
||||
import type { DryScore } from "../common/types";
|
||||
import type { DryScore, DryScoreData } from "../common/types";
|
||||
import type { GPTDerivers } from "./types";
|
||||
import type {
|
||||
GPTString,
|
||||
ChartDocument,
|
||||
ConfProvidedMetrics,
|
||||
ConfDerivedMetrics,
|
||||
ScoreData,
|
||||
} from "tachi-common";
|
||||
import type { MetricDeriver } from "tachi-common/types/metrics";
|
||||
import type { ChartDocument, DerivedMetrics, GPTString, ScoreData } from "tachi-common";
|
||||
import type { MetricValue } from "tachi-common/types/metrics";
|
||||
|
||||
type AllGPTDerivers = {
|
||||
[GPT in GPTString]: GPTDerivers<GPT>;
|
||||
@@ -74,14 +69,14 @@ const GPT_DERIVERS: AllGPTDerivers = {
|
||||
// don't check if metrics.survivedPercent === 100, as due to floating
|
||||
// point inaccuracies, it's possible to have a 100% fail
|
||||
// (on extremely long charts, for example)
|
||||
if (metrics.lamp.string === "FAILED") {
|
||||
if (metrics.lamp === "FAILED") {
|
||||
return metrics.survivedPercent;
|
||||
}
|
||||
|
||||
return 100 + metrics.scorePercent;
|
||||
},
|
||||
grade: ({ scorePercent, lamp }) => {
|
||||
if (lamp.string === "FAILED") {
|
||||
if (lamp === "FAILED") {
|
||||
return "F";
|
||||
}
|
||||
|
||||
@@ -95,35 +90,16 @@ const GPT_DERIVERS: AllGPTDerivers = {
|
||||
grade: ({ score }) => GetGrade(MUSECA_GBOUNDARIES, score),
|
||||
},
|
||||
"popn:9B": {
|
||||
lamp: ({ clearMedal }) => {
|
||||
switch (clearMedal.string) {
|
||||
case "perfect":
|
||||
return "PERFECT";
|
||||
case "fullComboCircle":
|
||||
case "fullComboDiamond":
|
||||
case "fullComboStar":
|
||||
return "FULL COMBO";
|
||||
case "clearCircle":
|
||||
case "clearDiamond":
|
||||
case "clearStar":
|
||||
return "CLEAR";
|
||||
case "easyClear":
|
||||
return "EASY CLEAR";
|
||||
case "failedCircle":
|
||||
case "failedDiamond":
|
||||
case "failedStar":
|
||||
return "FAILED";
|
||||
}
|
||||
},
|
||||
lamp: ({ clearMedal }) => PopnClearMedalToLamp(clearMedal),
|
||||
grade: ({ score, clearMedal }) => {
|
||||
const gradeString = GetGrade(POPN_GBOUNDARIES, score);
|
||||
|
||||
const c = clearMedal.string;
|
||||
|
||||
// grades are kneecapped at "A" if you failed.
|
||||
if (
|
||||
score >= 90_000 &&
|
||||
(c === "failedCircle" || c === "failedDiamond" || c === "failedStar")
|
||||
(clearMedal === "failedCircle" ||
|
||||
clearMedal === "failedDiamond" ||
|
||||
clearMedal === "failedStar")
|
||||
) {
|
||||
return "A";
|
||||
}
|
||||
@@ -138,16 +114,13 @@ const GPT_DERIVERS: AllGPTDerivers = {
|
||||
* we want to store.
|
||||
*/
|
||||
function DeriveMetrics<GPT extends GPTString>(
|
||||
gpt: GPTString,
|
||||
metrics: ConfProvidedMetrics[GPT],
|
||||
gpt: GPT,
|
||||
metrics: DryScoreData<GPT>,
|
||||
chart: ChartDocument<GPT>
|
||||
) {
|
||||
const deriverImplementation: Record<
|
||||
string,
|
||||
MetricDeriver<ConfProvidedMetrics[GPT], GPT>
|
||||
> = GPT_DERIVERS[gpt];
|
||||
const deriverImplementation: GPTDerivers<GPT> = GPT_DERIVERS[gpt];
|
||||
|
||||
const derivedMetrics: Record<string, DerivedMetricValue> = {};
|
||||
const derivedMetrics: Record<string, MetricValue> = {};
|
||||
|
||||
const gptConfig = GetGPTConfig(gpt);
|
||||
|
||||
@@ -162,15 +135,12 @@ function DeriveMetrics<GPT extends GPTString>(
|
||||
|
||||
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!`
|
||||
`Tried to turn a ${key} enum value of ${value} into an index, got -1?`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -183,7 +153,7 @@ function DeriveMetrics<GPT extends GPTString>(
|
||||
}
|
||||
}
|
||||
|
||||
return derivedMetrics as ConfDerivedMetrics[GPT];
|
||||
return derivedMetrics as DerivedMetrics[GPT];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { GetEnumValue } from "tachi-common/types/metrics";
|
||||
|
||||
export function PopnClearMedalToLamp(
|
||||
clearMedal: GetEnumValue<"popn:9B", "clearMedal">
|
||||
): GetEnumValue<"popn:9B", "lamp"> {
|
||||
switch (clearMedal) {
|
||||
case "perfect":
|
||||
return "PERFECT";
|
||||
case "fullComboCircle":
|
||||
case "fullComboDiamond":
|
||||
case "fullComboStar":
|
||||
return "FULL COMBO";
|
||||
case "clearCircle":
|
||||
case "clearDiamond":
|
||||
case "clearStar":
|
||||
return "CLEAR";
|
||||
case "easyClear":
|
||||
return "EASY CLEAR";
|
||||
case "failedCircle":
|
||||
case "failedDiamond":
|
||||
case "failedStar":
|
||||
return "FAILED";
|
||||
}
|
||||
}
|
||||
@@ -4,32 +4,6 @@ import type { KtLogger } from "lib/logger/logger";
|
||||
import type { ChartDocument, Game, integer, Playtype, GPTStrings } from "tachi-common";
|
||||
import type { GetEnumValue } from "tachi-common/types/metrics";
|
||||
|
||||
export function CalculateKTLampRatingIIDXSP(dryScore: DryScore, chart: ChartDocument<"iidx:SP">) {
|
||||
const ncValue = chart.tierlistInfo["kt-NC"]?.value ?? 0;
|
||||
const hcValue = Math.max(chart.tierlistInfo["kt-HC"]?.value ?? 0, ncValue);
|
||||
const exhcValue = Math.max(chart.tierlistInfo["kt-EXHC"]?.value ?? 0, hcValue);
|
||||
|
||||
if (!exhcValue && !hcValue && !ncValue) {
|
||||
return LampRatingNoTierlistInfo(dryScore, "iidx", chart.playtype, chart);
|
||||
}
|
||||
|
||||
const lamp = dryScore.scoreData.lamp;
|
||||
|
||||
const gptConfig = GetGamePTConfig("iidx", chart.playtype);
|
||||
|
||||
const lampIndex = gptConfig.lamps.indexOf(lamp);
|
||||
|
||||
if (exhcValue && lampIndex >= IIDX_LAMPS.EX_HARD_CLEAR) {
|
||||
return exhcValue;
|
||||
} else if (hcValue && lampIndex >= IIDX_LAMPS.HARD_CLEAR) {
|
||||
return hcValue;
|
||||
} else if (ncValue && lampIndex >= IIDX_LAMPS.CLEAR) {
|
||||
return ncValue;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function CalculateKTLampRatingIIDXDP(dryScore: DryScore, chart: ChartDocument<"iidx:DP">) {
|
||||
const tierlistValue = chart.tierlistInfo["dp-tier"]?.value ?? 0;
|
||||
|
||||
@@ -49,45 +23,3 @@ export function CalculateKTLampRatingIIDXDP(dryScore: DryScore, chart: ChartDocu
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
function LampRatingNoTierlistInfo(
|
||||
dryScore: DryScore,
|
||||
game: Game,
|
||||
playtype: Playtype,
|
||||
chart: ChartDocument
|
||||
) {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
const lamps = gptConfig.lamps;
|
||||
|
||||
const CLEAR_LAMP_INDEX = lamps.indexOf(gptConfig.clearLamp);
|
||||
|
||||
// if this is a clear
|
||||
if (lamps.indexOf(dryScore.scoreData.lamp) >= CLEAR_LAMP_INDEX) {
|
||||
// return this chart's numeric level as the lamp rating
|
||||
return chart.levelNum;
|
||||
}
|
||||
|
||||
// else, this score is worth 0.
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function CalculateSieglinde(
|
||||
chart: ChartDocument<GPTStrings["bms" | "pms"]>,
|
||||
lamp: GetEnumValue<GPTStrings["bms" | "pms"], "lamp">
|
||||
) {
|
||||
const ecValue = chart.data.sglEC ?? 0;
|
||||
const hcValue = chart.data.sglHC ?? 0;
|
||||
|
||||
switch (lamp) {
|
||||
case "FULL COMBO":
|
||||
case "EX HARD CLEAR":
|
||||
case "HARD CLEAR":
|
||||
return Math.max(hcValue, ecValue);
|
||||
case "CLEAR":
|
||||
case "EASY CLEAR":
|
||||
return ecValue;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user