208 lines
6.2 KiB
JavaScript
208 lines
6.2 KiB
JavaScript
import { SKILL_CAP } from "./constants.js";
|
|
import {
|
|
applyBonusSkillPicks,
|
|
collectBonusCapValidationErrors,
|
|
} from "./catalog.js";
|
|
import {
|
|
formatProfessionSkillKey,
|
|
formatProfessionSkillLabel,
|
|
getAgentSkillDefaults,
|
|
parseProfessionSkillKey,
|
|
} from "./keys.js";
|
|
import { validateProfessionFormState } from "./validation.js";
|
|
|
|
/**
|
|
* @param {Record<string, number>} automaticSkills
|
|
* @param {Record<string, number>} optionSkills
|
|
* @param {number} optionPicks
|
|
* @param {object} formState
|
|
* @param {Set<string>} formState.checkedOptionKeys
|
|
* @param {Record<string, string>} formState.chooseOneLabels - keyed by profession map key
|
|
* @param {string[]} formState.bonusCatalogIds - length 8
|
|
* @param {string[]} formState.bonusTypedLabels - length 8
|
|
* @param {string[]} [formState.bondNames]
|
|
* @param {string[]} [formState.bondRelationships]
|
|
* @param {object} [context]
|
|
* @param {Record<string, ProfessionSkillMeta>} [context.automaticMeta]
|
|
* @param {Record<string, ProfessionSkillMeta>} [context.optionMeta]
|
|
* @param {number} [context.bondCount]
|
|
* @returns {{
|
|
* fixedValues: Record<string, number>,
|
|
* typedValues: Record<string, { group: string, label: string, value: number }>,
|
|
* capWarnings: { label: string, attempted: number, waste: number }[],
|
|
* isValid: boolean,
|
|
* validationErrors: string[],
|
|
* modifiedFixedKeys: string[],
|
|
* modifiedTypedKeys: string[],
|
|
* }}
|
|
*/
|
|
export default function computeSkillValues(
|
|
automaticSkills,
|
|
optionSkills,
|
|
optionPicks,
|
|
formState,
|
|
{ automaticMeta = {}, optionMeta = {}, bondCount = 0 } = {},
|
|
) {
|
|
const defaults = getAgentSkillDefaults();
|
|
/** @type {Record<string, number>} */
|
|
const fixedValues = { ...defaults };
|
|
/** @type {Record<string, { group: string, label: string, value: number }>} */
|
|
const typedValues = {};
|
|
/** @type {Set<string>} */
|
|
const modifiedFixedKeys = new Set();
|
|
/** @type {Set<string>} */
|
|
const modifiedTypedKeys = new Set();
|
|
|
|
/**
|
|
* @param {TypedSkillRef} ref
|
|
* @param {string} label
|
|
*/
|
|
const markTypedModified = (ref, label) => {
|
|
modifiedTypedKeys.add(
|
|
formatProfessionSkillKey({ kind: "typed", group: ref.group, label }),
|
|
);
|
|
};
|
|
|
|
/**
|
|
* @param {string} mapKey
|
|
* @param {number} rating
|
|
* @param {{ labelOverride?: string }} [options]
|
|
* @returns {string | null} Merged typed storage key, if applied.
|
|
*/
|
|
const applyOverride = (mapKey, rating, options = {}) => {
|
|
const ref = parseProfessionSkillKey(mapKey);
|
|
if (!ref) return null;
|
|
const value = Number(rating);
|
|
if (ref.kind === "fixed") {
|
|
fixedValues[ref.key] = value;
|
|
return null;
|
|
}
|
|
|
|
const label = options.labelOverride?.trim() ?? ref.label?.trim() ?? "";
|
|
if (!label) return null;
|
|
|
|
const storageKey = formatProfessionSkillKey({
|
|
kind: "typed",
|
|
group: ref.group,
|
|
label,
|
|
});
|
|
typedValues[storageKey] = {
|
|
group: ref.group,
|
|
label,
|
|
value,
|
|
};
|
|
return storageKey;
|
|
};
|
|
|
|
for (const [key, rating] of Object.entries(automaticSkills ?? {})) {
|
|
const ref = parseProfessionSkillKey(key);
|
|
if (ref) {
|
|
let labelOverride;
|
|
let skip = false;
|
|
if (ref.kind === "typed" && automaticMeta[key]?.chooseOne) {
|
|
labelOverride = formState.chooseOneLabels?.[key]?.trim();
|
|
if (!labelOverride) skip = true;
|
|
}
|
|
if (!skip) {
|
|
const storageKey = applyOverride(key, rating, { labelOverride });
|
|
if (ref.kind === "fixed") modifiedFixedKeys.add(ref.key);
|
|
else if (storageKey) {
|
|
markTypedModified(ref, labelOverride ?? ref.label);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const key of formState.checkedOptionKeys ?? []) {
|
|
if (optionSkills[key] || optionSkills[key] === 0) {
|
|
const ref = parseProfessionSkillKey(key);
|
|
if (ref) {
|
|
let labelOverride;
|
|
let skip = false;
|
|
if (ref.kind === "typed" && optionMeta[key]?.chooseOne) {
|
|
labelOverride = formState.chooseOneLabels?.[key]?.trim();
|
|
if (!labelOverride) skip = true;
|
|
} else if (ref.kind === "typed") {
|
|
labelOverride = ref.label?.trim();
|
|
if (!labelOverride) skip = true;
|
|
}
|
|
if (!skip) {
|
|
const storageKey = applyOverride(key, optionSkills[key], {
|
|
labelOverride,
|
|
});
|
|
if (ref.kind === "fixed") modifiedFixedKeys.add(ref.key);
|
|
else if (storageKey) {
|
|
markTypedModified(ref, labelOverride ?? ref.label);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Pre-bonus base for cap warnings */
|
|
const baseFixed = { ...fixedValues };
|
|
const baseTyped = foundry.utils.deepClone(typedValues);
|
|
|
|
const bonusResult = applyBonusSkillPicks(baseFixed, baseTyped, formState);
|
|
|
|
/** @type {{ label: string, attempted: number, waste: number }[]} */
|
|
const capWarnings = [];
|
|
|
|
const checkCap = (label, base, final) => {
|
|
if (final > SKILL_CAP) {
|
|
capWarnings.push({
|
|
label,
|
|
attempted: final,
|
|
waste: final - SKILL_CAP,
|
|
});
|
|
}
|
|
};
|
|
|
|
for (const [key, final] of Object.entries(bonusResult.fixedValues)) {
|
|
const base = baseFixed[key] ?? defaults[key] ?? 0;
|
|
const ref = /** @type {FixedSkillRef} */ ({ kind: "fixed", key });
|
|
checkCap(formatProfessionSkillLabel(ref), base, final);
|
|
}
|
|
|
|
for (const [storageKey, data] of Object.entries(bonusResult.typedValues)) {
|
|
const base = baseTyped[storageKey]?.value ?? 0;
|
|
const ref = /** @type {TypedSkillRef} */ ({
|
|
kind: "typed",
|
|
group: data.group,
|
|
label: data.label,
|
|
});
|
|
checkCap(formatProfessionSkillLabel(ref), base, data.value);
|
|
}
|
|
|
|
const validationErrors = validateProfessionFormState(optionPicks, formState, {
|
|
automaticSkills,
|
|
optionSkills,
|
|
automaticMeta,
|
|
optionMeta,
|
|
bondCount,
|
|
});
|
|
|
|
validationErrors.push(
|
|
...collectBonusCapValidationErrors(
|
|
baseFixed,
|
|
baseTyped,
|
|
formState.bonusCatalogIds ?? [],
|
|
formState.bonusTypedLabels ?? [],
|
|
),
|
|
);
|
|
|
|
return {
|
|
fixedValues: bonusResult.fixedValues,
|
|
typedValues: bonusResult.typedValues,
|
|
capWarnings,
|
|
isValid: validationErrors.length === 0,
|
|
validationErrors,
|
|
modifiedFixedKeys: [
|
|
...new Set([...modifiedFixedKeys, ...bonusResult.modifiedFixedKeys]),
|
|
],
|
|
modifiedTypedKeys: [
|
|
...new Set([...modifiedTypedKeys, ...bonusResult.modifiedTypedKeys]),
|
|
],
|
|
};
|
|
}
|