mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-09 07:09:57 +03:00
feat: add itg calculations
This commit is contained in:
@@ -13,8 +13,10 @@ import {
|
||||
PoyashiBPI,
|
||||
Volforce,
|
||||
WACCARate,
|
||||
ITGHighestUnbroken,
|
||||
} from "rg-stats";
|
||||
import { GetGamePTConfig } from "tachi-common";
|
||||
import { IsNullish } from "utils/misc";
|
||||
import type { DryScore } from "../common/types";
|
||||
import type { KtLogger } from "lib/logger/logger";
|
||||
import type { ChartDocument, Game, IDStrings, Lamps, Playtypes, ScoreDocument } from "tachi-common";
|
||||
@@ -68,6 +70,7 @@ const CalculatedDataFunctions: CalculatedDataFunctionsType = {
|
||||
"jubeat:Single": CalculateDataJubeat,
|
||||
"pms:Keyboard": CalculateDataPMSorBMS,
|
||||
"pms:Controller": CalculateDataPMSorBMS,
|
||||
"itg:Stamina": CalculateDataITGStamina,
|
||||
};
|
||||
|
||||
// Creates Game-Specific calculatedData for the provided game & playtype.
|
||||
@@ -239,3 +242,33 @@ function CalculateDataPopn(
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function CalculateDataITGStamina(
|
||||
dryScore: DryScore<"itg:Stamina">,
|
||||
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)) {
|
||||
return {
|
||||
blockRating: null,
|
||||
highest32: null,
|
||||
highest256: null,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
blockRating: chart.levelNum,
|
||||
highest32: ITGHighestUnbroken.calculateFromNPSPerMeasure(
|
||||
chart.data.breakdown.npsPerMeasure,
|
||||
chart.data.breakdown.notesPerMeasure,
|
||||
dryScore.scoreData.hitMeta.diedAt,
|
||||
32
|
||||
),
|
||||
highest256: ITGHighestUnbroken.calculateFromNPSPerMeasure(
|
||||
chart.data.breakdown.npsPerMeasure,
|
||||
chart.data.breakdown.notesPerMeasure,
|
||||
dryScore.scoreData.hitMeta.diedAt,
|
||||
256
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type {
|
||||
Game,
|
||||
IDStrings,
|
||||
integer,
|
||||
Playtype,
|
||||
ScoreDocument,
|
||||
SessionCalculatedDataLookup,
|
||||
@@ -9,20 +10,18 @@ import type {
|
||||
|
||||
type ScoreCalculatedDataOnly = Pick<ScoreDocument, "calculatedData">;
|
||||
|
||||
const RELEVANT_SCORES = 10;
|
||||
|
||||
function AverageBest10(vals: Array<number | null | undefined>) {
|
||||
function AverageBestN(vals: Array<number | null | undefined>, n = 10) {
|
||||
const numbers = vals.filter((e) => typeof e === "number") as Array<number>;
|
||||
|
||||
if (numbers.length < RELEVANT_SCORES) {
|
||||
if (numbers.length < n) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
numbers
|
||||
.sort((a, b) => b - a)
|
||||
.slice(0, RELEVANT_SCORES)
|
||||
.reduce((a, e) => a + e, 0) / RELEVANT_SCORES
|
||||
.slice(0, n)
|
||||
.reduce((a, e) => a + e, 0) / n
|
||||
);
|
||||
}
|
||||
|
||||
@@ -40,7 +39,21 @@ function AvgBest10Map(
|
||||
arr: Array<ScoreCalculatedDataOnly>,
|
||||
prop: keyof ScoreDocument["calculatedData"]
|
||||
) {
|
||||
return AverageBest10(arr.map((e) => e.calculatedData[prop]));
|
||||
return AverageBestN(
|
||||
arr.map((e) => e.calculatedData[prop]),
|
||||
10
|
||||
);
|
||||
}
|
||||
|
||||
function AvgBestNMap(
|
||||
arr: Array<ScoreCalculatedDataOnly>,
|
||||
prop: keyof ScoreDocument["calculatedData"],
|
||||
n: integer
|
||||
) {
|
||||
return AverageBestN(
|
||||
arr.map((e) => e.calculatedData[prop]),
|
||||
n
|
||||
);
|
||||
}
|
||||
|
||||
type SessionCalcDataFn = (
|
||||
@@ -107,6 +120,11 @@ function GetGPTSessionCalcDataFn(game: Game, playtype: Playtype): SessionCalcDat
|
||||
});
|
||||
case "maimai:Single":
|
||||
return () => ({});
|
||||
case "itg:Stamina":
|
||||
return (scd) => ({
|
||||
blockRating: AvgBestNMap(scd, "blockRating", 5),
|
||||
average32Speed: AvgBestNMap(scd, "highest32", 5),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ const STATIC_CLASS_HANDLERS: ClassHandlerMap = {
|
||||
Single: CalculateJubeatColour,
|
||||
},
|
||||
pms: null,
|
||||
itg: null,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -153,6 +153,13 @@ function GetGPTRatingFunction(game: Game, playtype: Playtype): RatingFunction {
|
||||
jubility: await CalculateJubility(g, p, u, l),
|
||||
naiveJubility: await LazySumN("jubility", 60)(g, p, u),
|
||||
});
|
||||
case "itg:Stamina":
|
||||
// HighestX is equivalent to summing 1.
|
||||
return async (g, p, u) => ({
|
||||
highestBlock: await LazySumN("blockRating", 1, true)(g, p, u),
|
||||
highest32: await LazySumN("highest32", 1, true)(g, p, u),
|
||||
highest256: await LazySumN("highest256", 1, true)(g, p, u),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
FindChartWithPTDF,
|
||||
FindChartWithPTDFVersion,
|
||||
FindDDRChartOnSongHash,
|
||||
FindITGChartOnHash,
|
||||
} from "utils/queries/charts";
|
||||
import { FindSongOnID, FindSongOnTitleInsensitive } from "utils/queries/songs";
|
||||
import type { DryScore } from "../../../framework/common/types";
|
||||
@@ -172,6 +173,34 @@ export async function ResolveMatchTypeToKTData(
|
||||
return { chart, song };
|
||||
}
|
||||
|
||||
case "itgChartHash": {
|
||||
if (game !== "itg") {
|
||||
throw new InvalidScoreFailure(`Cannot use itgChartHash lookup on ${game}.`);
|
||||
}
|
||||
|
||||
const chart = await FindITGChartOnHash(data.identifier);
|
||||
|
||||
if (!chart) {
|
||||
throw new KTDataNotFoundFailure(
|
||||
`Cannot find chart for itgChartHash ${data.identifier}.`,
|
||||
importType,
|
||||
data,
|
||||
context
|
||||
);
|
||||
}
|
||||
|
||||
const song = await FindSongOnID(game, chart.songID);
|
||||
|
||||
if (!song) {
|
||||
logger.severe(`ITG songID ${chart.songID} has charts but no parent song.`);
|
||||
throw new InternalFailure(
|
||||
`ITG songID ${chart.songID} has charts but no parent song.`
|
||||
);
|
||||
}
|
||||
|
||||
return { song, chart };
|
||||
}
|
||||
|
||||
case "ddrSongHash": {
|
||||
if (game !== "ddr") {
|
||||
throw new InvalidScoreFailure(`Cannot use ddrSongHash lookup on ${game}.`);
|
||||
|
||||
Reference in New Issue
Block a user