From df2178fe131ed9e9d3427ee092a46b2448b09650 Mon Sep 17 00:00:00 2001 From: Lukas Obermann Date: Mon, 8 Jun 2026 00:20:52 +0200 Subject: [PATCH] fix: render ap value from explicit select options --- src/entities/partial/adventurePointsValue.ts | 113 +++++++++++++++---- 1 file changed, 88 insertions(+), 25 deletions(-) diff --git a/src/entities/partial/adventurePointsValue.ts b/src/entities/partial/adventurePointsValue.ts index 80d1636..0372dba 100644 --- a/src/entities/partial/adventurePointsValue.ts +++ b/src/entities/partial/adventurePointsValue.ts @@ -19,6 +19,62 @@ import type { BaseActivatable, BaseActivatableTranslation } from "../activatable import { evaluateMathOperation } from "./mathOperation.js" import { MISSING_VALUE } from "./unknown.js" +const typographicSign = (num: number): string => (num > 0 ? "" : "−") + Math.abs(num).toFixed() + +const renderFixedSelectOptionsAdventurePointsValue = ( + locale: LocaleEnvironment, + getAllSelectOptions: () => ResolvedSelectOption[], + getNameForSelectOptionId: (id: ResolvedSelectOptionIdentifier) => string | undefined, + getApValueForSelectOption: ( + item: ResolvedSelectOption, + id: ResolvedSelectOptionIdentifier, + ) => number | string, + applyNegative: (numValue: number) => number, +) => { + const entriesMap = Map.groupBy( + getAllSelectOptions().map((item): [apValue: number | string, item: ResolvedSelectOption] => [ + getApValueForSelectOption(item, item.id), + item, + ]), + item => item[0], + ) + .entries() + .toArray() + + if ( + entriesMap.every( + (entry): entry is [number, [[apValue: number, item: ResolvedSelectOption]]] => + typeof entry[0] === "number" && entry[1].length === 1, + ) + ) { + const entriesSortedByApValue = entriesMap.toSorted(on(item => item[0], numAsc)) + return entriesSortedByApValue + .map(entry => locale.translateMap(entry[1][0][1].content.translations)?.name ?? MISSING_VALUE) + .join("/") + .concat( + `: ${locale.translate("{$value} Adventure Points", { value: entriesSortedByApValue.map(entry => typographicSign(applyNegative(entry[0]))).join("/") })}`, + ) + } else { + return entriesMap + .map(([apValue, items]): [number | string, string] => [ + apValue, + locale.join( + items + .map(item => getNameForSelectOptionId(item[1].id)) + .filter(name => name !== undefined) + .toSorted(locale.compare), + "conjunction", + ), + ]) + .toSorted(on(item => item[1], locale.compare)) + .map( + ([apValue, itemNames]) => + `${itemNames}: ${locale.translate("{$value} Adventure Points", { value: typeof apValue === "number" ? applyNegative(apValue) : apValue })}`, + ) + .join("; ") + } +} + const renderSelectOptionsAdventurePointsValue = ( locale: LocaleEnvironment, derivedLabel: () => string, @@ -43,32 +99,15 @@ const renderSelectOptionsAdventurePointsValue = [ + return renderFixedSelectOptionsAdventurePointsValue( + locale, + getAllSelectOptions, + getNameForSelectOptionId, + item => config.Fixed.map.find(mapItem => deepEqual(mapItem.id, item.id))?.ap_value ?? - config.Fixed.default, - item, - ]), - item => item[0], + config.Fixed.default, + applyNegative, ) - .entries() - .map(([apValue, items]): [number, string] => [ - apValue, - locale.join( - items - .map(item => getNameForSelectOptionId(item[1].id)) - .filter(name => name !== undefined) - .toSorted(locale.compare), - "conjunction", - ), - ]) - .toArray() - .toSorted(on(item => item[1], locale.compare)) - .map( - ([apValue, itemNames]) => - `${itemNames}: ${locale.translate("{$value} Adventure Points", { value: applyNegative(apValue) })}`, - ) - .join("; ") default: return assertExhaustive(config) } @@ -137,7 +176,31 @@ export const renderAdventurePointsValue = ( case "DerivedFromSelection": { const derivedSelectOptions = entry.select_options?.derived if (derivedSelectOptions === undefined) { - return MISSING_VALUE + return renderFixedSelectOptionsAdventurePointsValue( + locale, + getAllSelectOptions, + getNameForSelectOptionId, + item => { + switch (item.content.ap_value?.kind) { + case "Fixed": + return item.content.ap_value.Fixed + case "DependingOnActiveInstances": + switch (item.content.ap_value.DependingOnActiveInstances.kind) { + case "Threshold": + return item.content.ap_value.DependingOnActiveInstances.Threshold.normal + case "Expression": + return MISSING_VALUE + default: + return assertExhaustive(item.content.ap_value.DependingOnActiveInstances) + } + case undefined: + return MISSING_VALUE + default: + return assertExhaustive(item.content.ap_value) + } + }, + applyNegative, + ) } switch (derivedSelectOptions.kind) { case "Blessings":