/** * PF2e Recall Knowledge DC calculation and type-to-skill mapping. * * DC is derived from creature level using the standard DC-by-level table * (Player Core / GM Core), adjusted for rarity. */ /** Standard DC-by-level table from PF2e GM Core. Index = level + 1 (level -1 → index 0). */ const DC_BY_LEVEL: readonly number[] = [ 13, // level -1 14, // level 0 15, // level 1 16, // level 2 18, // level 3 19, // level 4 20, // level 5 22, // level 6 23, // level 7 24, // level 8 26, // level 9 27, // level 10 28, // level 11 30, // level 12 31, // level 13 32, // level 14 34, // level 15 35, // level 16 36, // level 17 38, // level 18 39, // level 19 40, // level 20 42, // level 21 44, // level 22 46, // level 23 48, // level 24 50, // level 25 ]; const RARITY_ADJUSTMENT: Readonly> = { uncommon: 2, rare: 5, unique: 10, }; /** * Mapping from PF2e creature type traits to the skill(s) used for * Recall Knowledge. Types that map to multiple skills list all of them. */ const TYPE_TO_SKILLS: Readonly> = { aberration: ["Occultism"], animal: ["Nature"], astral: ["Occultism"], beast: ["Arcana", "Nature"], celestial: ["Religion"], construct: ["Arcana", "Crafting"], dragon: ["Arcana"], dream: ["Occultism"], elemental: ["Arcana", "Nature"], ethereal: ["Occultism"], fey: ["Nature"], fiend: ["Religion"], fungus: ["Nature"], giant: ["Society"], humanoid: ["Society"], monitor: ["Religion"], ooze: ["Occultism"], plant: ["Nature"], undead: ["Religion"], }; export interface RecallKnowledge { readonly dc: number; readonly type: string; readonly skills: readonly string[]; } /** * Calculate Recall Knowledge DC, type, and skill(s) for a PF2e creature. * * Returns `null` when no recognized type trait is found in the creature's * traits array, indicating the Recall Knowledge line should be omitted. */ export function recallKnowledge( level: number, traits: readonly string[], ): RecallKnowledge | null { // Find the first type trait that maps to a skill let matchedType: string | undefined; let skills: readonly string[] | undefined; for (const trait of traits) { const lower = trait.toLowerCase(); const mapped = TYPE_TO_SKILLS[lower]; if (mapped) { matchedType = trait; skills = mapped; break; } } if (!matchedType || !skills) return null; // Calculate DC from level const clampedIndex = Math.max(0, Math.min(level + 1, DC_BY_LEVEL.length - 1)); let dc = DC_BY_LEVEL[clampedIndex]; // Apply rarity adjustment (rarity traits are included in the traits array // for non-common creatures by the normalization pipeline) for (const trait of traits) { const adjustment = RARITY_ADJUSTMENT[trait.toLowerCase()]; if (adjustment) { dc += adjustment; break; } } return { dc, type: matchedType, skills }; }