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 f46436d4d..c773c0d8b 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"; @@ -122,6 +122,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 96b0e3634..5a89c557e 100644 --- a/server/src/game-implementations/games/ddr.ts +++ b/server/src/game-implementations/games/ddr.ts @@ -141,6 +141,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"> = { @@ -260,6 +279,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) => {