import type { ClassId } from "../app/characterCatalog"; import { clampPlayerLevel } from "./progression"; import { runtimePartyTalentPlan } from "./partyTalentRuntime"; import { talentUiDescriptionForRank, talentUiNodeById, talentUiTreeById, type TalentUiCatalog, } from "./talentUiCatalog"; export interface PartyTalentSelection { readonly point: number; readonly level: number; readonly nodeId: string; readonly treeId: string; readonly treeName: string; readonly name: string; readonly rank: number; readonly maxRank: number; readonly icon: string; readonly description: string; } export function partyTalentSelections( catalog: TalentUiCatalog, classId: ClassId, specialization: string, ): readonly PartyTalentSelection[] { if (catalog.classId !== classId) return []; const ranks: Record = {}; return runtimePartyTalentPlan(classId, specialization).map((nodeId, index) => { const node = talentUiNodeById(catalog, nodeId); if (!node) throw new Error(`Party talent plan references missing UI node ${nodeId}.`); const tree = talentUiTreeById(catalog, node.treeId); if (!tree) throw new Error(`Party talent plan references missing UI tree ${node.treeId}.`); const rank = (ranks[nodeId] ?? 0) + 1; ranks[nodeId] = rank; return Object.freeze({ point: index + 1, level: index + 10, nodeId, treeId: tree.id, treeName: tree.name, name: node.name, rank, maxRank: node.maxRank, icon: node.icon, description: talentUiDescriptionForRank(node, rank), }); }); } export function learnedPartyTalentSelections( catalog: TalentUiCatalog, classId: ClassId, specialization: string, level: number, ): readonly PartyTalentSelection[] { const points = Math.max(0, clampPlayerLevel(level) - 9); return partyTalentSelections(catalog, classId, specialization).slice(0, points); } export function upcomingPartyTalentSelections( catalog: TalentUiCatalog, classId: ClassId, specialization: string, level: number, limit = 5, ): readonly PartyTalentSelection[] { const start = Math.max(0, clampPlayerLevel(level) - 9); const safeLimit = Number.isFinite(limit) ? Math.max(0, Math.trunc(limit)) : 0; return partyTalentSelections(catalog, classId, specialization).slice(start, start + safeLimit); }