checkpoint: 31 (SERVER BUILDS)

DON'T EVER DOUBT ME AGAIN
This commit is contained in:
zkldi
2023-01-19 16:39:40 +00:00
parent 067825cefc
commit acdd0e2264
4 changed files with 68 additions and 158 deletions
@@ -47,7 +47,13 @@ const BMS_IMPL: GPTServerImplementation<GPTStrings["bms" | "pms"]> = {
pb.scoreData.percent,
IIDXLIKE_GBOUNDARIES[goalValue]!.name,
// use notecount to turn the percent deltas into whole ex-scores.
(v) => Math.floor((v / 100) * pb.scoreData.score).toFixed(0)
(deltaPercent) => {
const chartNotecount = Math.floor(
pb.scoreData.score / (pb.scoreData.percent / 100)
);
return (deltaPercent * (chartNotecount * 2)).toFixed(0);
}
),
},
};
@@ -60,7 +60,7 @@ const IIDX_GOAL_PG_FMT: GPTGoalProgressFormatters<"iidx:DP" | "iidx:SP"> = {
return pb.scoreData.lamp;
},
grade: (pb, gradeIndex, chart) =>
grade: (pb, gradeIndex) =>
GradeGoalFormatter(
IIDXLIKE_GBOUNDARIES,
pb.scoreData.grade,
@@ -68,7 +68,13 @@ const IIDX_GOAL_PG_FMT: GPTGoalProgressFormatters<"iidx:DP" | "iidx:SP"> = {
IIDXLIKE_GBOUNDARIES[gradeIndex]!.name,
// use notecount to turn the percent deltas into whole ex-scores.
(v) => Math.floor(v * chart.data.notecount * 2).toFixed(0)
(deltaPercent) => {
const chartNotecount = Math.floor(
pb.scoreData.score / (pb.scoreData.percent / 100)
);
return (deltaPercent * (chartNotecount * 2)).toFixed(0);
}
),
};
-4
View File
@@ -110,10 +110,6 @@ export type GPTDerivers<GPT extends GPTString> = {
* Format a goal into a string. If a function is provided, it's called with this goals
* criteria value, so a goal of "get 3600 on $CHART" would recieve 3600 as its
* argument.
*
* Optionally, undefined can be returned to signify this failed somehow.
* This is convenient for things like enum lookups which never fail in practice
* but the typesystem thinks they might be undefined.
*/
export type GoalCriteriaFormatter = (num: number) => string;
+53 -151
View File
@@ -4,9 +4,9 @@ import fjsh from "fast-json-stable-hash";
import { GPT_SERVER_IMPLEMENTATIONS } from "game-implementations/game-implementations";
import { SubscribeFailReasons } from "lib/constants/err-codes";
import CreateLogCtx from "lib/logger/logger";
import { FormatGame, GetGPTString, GetGamePTConfig } from "tachi-common";
import { FormatGame, GetGPTConfig, GetGPTString, GetScoreMetricConf } from "tachi-common";
import { GetFolderChartIDs } from "utils/folder";
import { IsNullish } from "utils/misc";
import type { GoalCriteriaFormatter } from "game-implementations/types";
import type { KtLogger } from "lib/logger/logger";
import type { FilterQuery } from "mongodb";
import type {
@@ -18,6 +18,7 @@ import type {
Playtype,
QuestDocument,
QuestSubscriptionDocument,
GPTString,
} from "tachi-common";
const logger = CreateLogCtx(__filename);
@@ -71,29 +72,25 @@ export async function EvaluateGoalForUser(
chartID: { $in: chartIDs },
};
const gptString = GetGPTString(goal.game, goal.playtype);
switch (goal.criteria.mode) {
case "single": {
const res = await db["personal-bests"].findOne(scoreQuery);
// hack, but guaranteed to work.
const scoreDataKey = goal.criteria.key;
const outOfHuman = HumaniseGoalOutOf(
goal.game,
goal.playtype,
goal.criteria.key,
goal.criteria.value
);
const outOfHuman = HumaniseGoalOutOf(gptString, goal.criteria.key, goal.criteria.value);
if (res) {
return {
achieved: true,
outOf: goal.criteria.value,
progress: res.scoreData[scoreDataKey],
// @ts-expect-error completely ok, as it'll definitely be on these props.
progress: res.scoreData[goal.criteria.key],
outOfHuman,
progressHuman: HumaniseGoalProgress(
goal.game,
goal.playtype,
gptString,
goal.criteria.key,
goal.criteria.value,
res
@@ -129,10 +126,10 @@ export async function EvaluateGoalForUser(
achieved: false,
outOf: goal.criteria.value,
outOfHuman,
// @ts-expect-error completely ok, as it'll definitely be on these props.
progress: nextBestScore.scoreData[scoreDataKey],
progressHuman: HumaniseGoalProgress(
goal.game,
goal.playtype,
gptString,
goal.criteria.key,
goal.criteria.value,
nextBestScore
@@ -212,159 +209,64 @@ type GoalKeys = GoalDocument["criteria"]["key"];
* IIDX lamp goals.
*/
export function HumaniseGoalProgress(
game: Game,
playtype: Playtype,
gptString: GPTString,
key: GoalKeys,
goalValue: integer,
userPB: PBScoreDocument
): string {
switch (key) {
case "scoreData.gradeIndex": {
// for iidx, pms and bms we expect grade deltas (rarely) formatted like
// AAA-1520
// however, for other games (namely those that run with larger numbers)
// we want to format this stuff like AA-172k.
const fmtFn =
game === "iidx" || game === "pms" || game === "bms"
? undefined
: (num: number) => Intl.NumberFormat("en", { notation: "compact" }).format(num);
const gptImpl = GPT_SERVER_IMPLEMENTATIONS[gptString];
const goalGrade = GetGamePTConfig(game, playtype).grades[goalValue];
// @ts-expect-error yeah this might fail, i know.
const formatter = gptImpl.goalProgressFormatters[key];
if (!goalGrade) {
throw new Error(
`Invalid Goal -- Tried to get a grade with index '${goalValue}', but no such grade exists for ${FormatGame(
game,
playtype
)}`
);
}
const { lower, upper, closer } = GenericFormatGradeDelta(
game,
playtype,
userPB.scoreData.score,
userPB.scoreData.percent,
userPB.scoreData.grade,
fmtFn
);
// If this goal is, say, AAA $chart, and the user's deltas are AA+40, AAA-100
// instead of picking the one with less delta from the grade (AA+40)
// pick the one closest to the target grade.
// Because sometimes this function wraps the grade operand in brackets
// (see (MAX-)-50), we need a regexp for this.
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
if (upper && new RegExp(`\\(?${goalGrade}`, "u").exec(upper)) {
return upper;
}
// for some reason, our TS compiler disagrees that this is non-nullable.
// but my IDE thinks it is. Who knows.
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
return closer === "lower" ? lower : upper!;
}
case "scoreData.lampIndex": {
switch (game) {
case "iidx":
case "bms":
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.optional.bp;
// render BP if it exists
if (!IsNullish(maybeBP)) {
return `${userPB.scoreData.lamp} (BP: ${maybeBP})`;
}
break;
}
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.optional.diedAt;
// render diedAt if it exists as "FAILED (Died 25% in)"
if (!IsNullish(maybeDiedAt)) {
return `${userPB.scoreData.lamp} (Died ${maybeDiedAt.toFixed(0)}% in)`;
}
break;
}
// no special logic needed for these games, just render the lamp.
case "chunithm":
case "gitadora":
case "jubeat":
case "museca":
case "popn":
case "sdvx":
case "usc":
case "wacca":
case "maimaidx":
}
// otherwise, just roll on.
return userPB.scoreData.lamp;
}
case [`scoreData.${gptConfig.defaultMetric}`]:
return `${userPB.scoreData.percent.toFixed(2)}%`;
case "scoreData.score":
return userPB.scoreData.score.toLocaleString();
default:
throw new Error(`Broken goal - invalid key ${key}.`);
if (!formatter) {
throw new Error(
`Attempted to format progress for metric '${key}' when no such score metric exists for ${gptString}.`
);
}
return formatter(userPB, goalValue);
}
/**
* Turn a goal's "outOf" (i.e. HARD CLEAR; AAA or score=2450) into a human-understandable
* string.
*/
export function HumaniseGoalOutOf(game: Game, playtype: Playtype, key: GoalKeys, value: number) {
const gptImpl = GPT_SERVER_IMPLEMENTATIONS[GetGPTString(game, playtype)];
export function HumaniseGoalOutOf(gptString: GPTString, key: GoalKeys, value: number) {
const gptConf = GetGPTConfig(gptString);
switch (key) {
case "scoreData.gradeIndex": {
const grade = gptConfig.grades[value];
const metricConf = GetScoreMetricConf(gptConf, key);
if (!grade) {
throw new Error(
`Invalid goal: Requested a grade with index '${value}' for ${FormatGame(
game,
playtype
)}, yet none existed?`
);
}
return grade;
}
case "scoreData.lampIndex": {
const lamp = gptConfig.lamps[value];
if (!lamp) {
throw new Error(
`Invalid goal: Requested a lamp with index '${value}' for ${FormatGame(
game,
playtype
)}, yet none existed?`
);
}
return lamp;
}
case [`scoreData.${gptConfig.defaultMetric}`]:
return `${value.toFixed(2)}%`;
case "scoreData.score":
return value.toLocaleString();
default:
throw new Error(`Broken goal - invalid key ${key}.`);
if (!metricConf) {
throw new Error(
`Attempted to format outOf for metric '${key}' when no such score metric exists for ${gptString}.`
);
}
if (metricConf.type === "ENUM") {
const val = metricConf.values[value];
if (val === undefined) {
throw new Error(
`Attempted to format outOf for metric '${key}' but no such enum exists at index ${value}. (${gptString})`
);
}
return val;
}
const gptImpl = GPT_SERVER_IMPLEMENTATIONS[gptString];
// @ts-expect-error yeah this is technically unsafe, whatever
const fmt: GoalCriteriaFormatter | undefined = gptImpl.goalCriteriaFormatters[key];
if (!fmt) {
throw new Error(
`Invalid metric '${key}' passed to format outOf, as no goalCriteriaFormatter exists for it.`
);
}
return fmt(value);
}
/**