diff --git a/client/src/components/tables/cells/DDRScoreCell.tsx b/client/src/components/tables/cells/DDRScoreCell.tsx index 39d4a931f..59954a748 100644 --- a/client/src/components/tables/cells/DDRScoreCell.tsx +++ b/client/src/components/tables/cells/DDRScoreCell.tsx @@ -6,12 +6,14 @@ export default function DDRScoreCell({ score, colour, grade, + exScore, scoreRenderFn, }: { score?: integer; grade: string; colour: string; showScore?: boolean; + exScore?: integer; scoreRenderFn?: (s: number) => string; }) { return ( @@ -23,6 +25,12 @@ export default function DDRScoreCell({ {grade}
{score !== undefined && <>{scoreRenderFn ? scoreRenderFn(score) : score}} + {typeof exScore === "number" && ( + <> +
+ [EX: {exScore}] + + )} ); } diff --git a/common/src/config/game-support/ddr.ts b/common/src/config/game-support/ddr.ts index 497ef1595..99b28b1f1 100644 --- a/common/src/config/game-support/ddr.ts +++ b/common/src/config/game-support/ddr.ts @@ -1,5 +1,5 @@ -import { IIDXDans } from "./iidx"; -import { FmtNum, FmtPercent, FmtScoreNoCommas } from "../../utils/util"; +import { FAST_SLOW_MAXCOMBO } from "./_common"; +import { FmtNum, FmtScoreNoCommas } from "../../utils/util"; import { ClassValue, zodNonNegativeInt } from "../config-utils"; import { p } from "prudence"; import { z } from "zod"; @@ -123,6 +123,19 @@ export const DDR_SP_CONF = { minimumRelevantValue: "0", description: "The Flare rank. If no Flare is provided, Flare 0 is chosen by default.", }, + exScore: { + type: "INTEGER", + formatter: FmtNum, + validate: p.isPositiveInteger, + + // We want to track the best EXScore a user gets, but it is an optional + // metric. + partOfScoreID: true, + + description: + "The EXScore value. Marvelous and O.K. judgements are worth 3 points, Perfect judgements are worth 2 points, Great judgements are worth 1 point, and Good and lower judgements are not worth any points.", + }, + ...FAST_SLOW_MAXCOMBO, }, defaultMetric: "score", diff --git a/server/src/game-implementations/games/ddr.ts b/server/src/game-implementations/games/ddr.ts index 5a14c1cad..174e9c90b 100644 --- a/server/src/game-implementations/games/ddr.ts +++ b/server/src/game-implementations/games/ddr.ts @@ -136,6 +136,25 @@ export const DDR_SCORE_VALIDATORS: Array> = default: } }, + (s) => { + const { MARVELOUS, PERFECT, GREAT, OK } = s.scoreData.judgements; + + if ( + IsNullish(MARVELOUS) || + IsNullish(PERFECT) || + IsNullish(GREAT) || + IsNullish(OK) || + IsNullish(s.scoreData.optional.exScore) + ) { + return; + } + + const calculatedExScore = MARVELOUS * 3 + OK * 3 + PERFECT * 2 + GREAT; + + if (calculatedExScore !== s.scoreData.optional.exScore) { + return `EXScore expected to be ${calculatedExScore} instead of ${s.scoreData.optional.exScore}`; + } + }, ]; export const DDR_IMPL: GPTServerImplementation<"ddr:DP" | "ddr:SP"> = { @@ -255,6 +274,9 @@ export const DDR_IMPL: GPTServerImplementation<"ddr:DP" | "ddr:SP"> = { base.scoreData.score = score.scoreData.score; base.scoreData.grade = score.scoreData.grade; }), + CreatePBMergeFor("largest", "optional.exScore", "Best EX Score", (base, score) => { + base.scoreData.optional.exScore = score.scoreData.optional.exScore; + }), ], profileCalcs: { flareSkill: async (game: Game, playtype: Playtype, userID: integer) => {