74 lines
2.3 KiB
TypeScript
74 lines
2.3 KiB
TypeScript
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<string, number> = {};
|
|
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);
|
|
}
|