"use client"; import { useMemo } from "react"; import { Skill, SKILL_ABILITY } from "@/types/content"; import type { Effect } from "@/types/core"; import { useCreator } from "../CreatorContext"; import { getAbilityModifier } from "@/lib/dndHelpers"; const SKILLS = Object.keys(SKILL_ABILITY) as Skill[]; interface SkillChoice { featureId: string; featureName: string; count: number; from: Skill[]; } interface ExpertiseChoice { featureId: string; featureName: string; count: number; skillPool: Skill[] | "characterSkills"; toolPool: string[]; } export default function SkillsStep() { const { draft, update, classes, backgrounds, species, features } = useCreator(); const selectedClass = useMemo( () => classes.find((c) => c.id === draft.classes[0]?.classId), [classes, draft.classes] ); const selectedBackground = useMemo( () => backgrounds.find((b) => b.id === draft.backgroundId), [backgrounds, draft.backgroundId] ); const selectedSpecies = useMemo( () => species.find((s) => s.id === draft.speciesId), [species, draft.speciesId] ); const level = draft.classes[0]?.level ?? 1; const attachedFeatureIds = useMemo(() => { const ids: string[] = []; ids.push(...(selectedSpecies?.featureIds ?? [])); ids.push(...(selectedBackground?.featureIds ?? [])); for (const lf of selectedClass?.levelFeatures ?? []) { if (lf.level <= level) ids.push(...lf.featureIds); } return ids; }, [selectedSpecies, selectedBackground, selectedClass, level]); const skillChoices: SkillChoice[] = useMemo(() => { const out: SkillChoice[] = []; for (const fid of attachedFeatureIds) { const f = features[fid]; if (!f) continue; for (const eff of f.effects ?? []) { if (eff.target.kind === "skillProficiencyChoice") { out.push({ featureId: f.id, featureName: f.name, count: eff.target.count, from: eff.target.from, }); } } } return out; }, [attachedFeatureIds, features]); const expertiseChoices: ExpertiseChoice[] = useMemo(() => { const out: ExpertiseChoice[] = []; for (const fid of attachedFeatureIds) { const f = features[fid]; if (!f) continue; for (const eff of f.effects ?? []) { if (eff.target.kind === "expertiseChoice") { out.push({ featureId: f.id, featureName: f.name, count: eff.target.count, skillPool: eff.target.skillPool, toolPool: eff.target.toolPool, }); } } } return out; }, [attachedFeatureIds, features]); const classSkillOptions = selectedClass?.skillProficiencyOptions?.from ?? []; const classSkillCount = selectedClass?.skillProficiencyOptions?.count ?? 0; const backgroundSkills: Skill[] = selectedBackground?.skillProficiencies ?? []; const featureChosenSkills: Skill[] = useMemo( () => skillChoices.flatMap((c) => (draft.featureChoices[c.featureId] ?? []) as Skill[]), [skillChoices, draft.featureChoices] ); const chosen = draft.skillProficiencies; const chosenFromClass = chosen.filter( (s) => classSkillOptions.includes(s) && !backgroundSkills.includes(s) ); const toggleClassSkill = (s: Skill) => { if (backgroundSkills.includes(s) || featureChosenSkills.includes(s)) return; if (!classSkillOptions.includes(s)) return; if (chosen.includes(s)) { update({ skillProficiencies: chosen.filter((x) => x !== s) }); } else { if (chosenFromClass.length >= classSkillCount) return; update({ skillProficiencies: [...chosen, s] }); } }; const toggleFeatureChoice = (choice: SkillChoice, s: Skill) => { const current = (draft.featureChoices[choice.featureId] ?? []) as Skill[]; let next: Skill[]; if (current.includes(s)) { next = current.filter((x) => x !== s); } else { if (current.length >= choice.count) return; next = [...current, s]; } update({ featureChoices: { ...draft.featureChoices, [choice.featureId]: next }, }); }; const isEffectivelyChosen = (s: Skill) => chosen.includes(s) || backgroundSkills.includes(s) || featureChosenSkills.includes(s); const allCharacterSkills: Skill[] = useMemo( () => Array.from(new Set([...chosen, ...backgroundSkills, ...featureChosenSkills])), [chosen, backgroundSkills, featureChosenSkills] ); const toggleExpertise = (choice: ExpertiseChoice, key: string) => { const current = (draft.featureChoices[choice.featureId] ?? []) as string[]; let next: string[]; if (current.includes(key)) { next = current.filter((x) => x !== key); } else { if (current.length >= choice.count) return; next = [...current, key]; } update({ featureChoices: { ...draft.featureChoices, [choice.featureId]: next }, }); }; return (
Class allows {classSkillCount} choice(s). Background + features grant more.
Class chosen: {chosenFromClass.length}/{classSkillCount}
Pick skill proficiencies below first.
)}