forked from phenom/healer-man
107 lines
3.5 KiB
TypeScript
107 lines
3.5 KiB
TypeScript
import type { ClassId } from "../app/characterCatalog";
|
|
|
|
export interface TalentUiTree {
|
|
readonly id: string;
|
|
readonly classId: ClassId;
|
|
readonly name: string;
|
|
readonly icon: string;
|
|
readonly background: string;
|
|
readonly description: string;
|
|
readonly system?: "classic" | "coa";
|
|
}
|
|
|
|
export interface TalentUiNode {
|
|
readonly id: string;
|
|
readonly treeId: string;
|
|
readonly index: number;
|
|
readonly prerequisiteId: string | null;
|
|
readonly prerequisiteRank: number | null;
|
|
readonly prerequisites: readonly {
|
|
readonly talentId: string;
|
|
readonly requiredRank: number;
|
|
}[];
|
|
readonly column: number;
|
|
readonly row: number;
|
|
readonly icon: string;
|
|
readonly maxRank: number;
|
|
readonly name: string;
|
|
readonly description: string;
|
|
readonly rankDescriptions: readonly string[];
|
|
readonly system?: "classic" | "coa";
|
|
readonly entryType?: "Ability" | "Talent";
|
|
readonly abilityEssenceCost?: number;
|
|
readonly talentEssenceCost?: number;
|
|
readonly requiredClassPoints?: number;
|
|
readonly requiredSpecPoints?: number;
|
|
readonly requiredLevel?: number;
|
|
readonly isPassive?: boolean;
|
|
}
|
|
|
|
export interface TalentUiCatalog {
|
|
readonly schemaVersion: 1;
|
|
readonly classId: ClassId;
|
|
readonly trees: readonly TalentUiTree[];
|
|
readonly nodes: readonly TalentUiNode[];
|
|
}
|
|
|
|
interface TalentUiCatalogModule {
|
|
readonly default: TalentUiCatalog;
|
|
}
|
|
|
|
const shardLoaders = import.meta.glob<TalentUiCatalogModule>("./generated/talentUi/*.json");
|
|
const loadCache = new Map<ClassId, Promise<TalentUiCatalog>>();
|
|
const loadedCatalogs = new Map<ClassId, TalentUiCatalog>();
|
|
|
|
export function loadTalentUiCatalog(classId: ClassId): Promise<TalentUiCatalog> {
|
|
const cached = loadCache.get(classId);
|
|
if (cached) return cached;
|
|
const key = `./generated/talentUi/${classId}.json`;
|
|
const loader = shardLoaders[key];
|
|
if (!loader) return Promise.reject(new Error(`No talent UI catalog exists for ${classId}.`));
|
|
const pending = loader().then((module) => {
|
|
if (module.default.schemaVersion !== 1 || module.default.classId !== classId) {
|
|
throw new Error(`Talent UI catalog ${classId} has incompatible metadata.`);
|
|
}
|
|
loadedCatalogs.set(classId, module.default);
|
|
return module.default;
|
|
}).catch((error: unknown) => {
|
|
loadCache.delete(classId);
|
|
throw error;
|
|
});
|
|
loadCache.set(classId, pending);
|
|
return pending;
|
|
}
|
|
|
|
export function loadedTalentUiCatalog(classId: ClassId): TalentUiCatalog | null {
|
|
return loadedCatalogs.get(classId) ?? null;
|
|
}
|
|
|
|
export function talentUiDescriptionForRank(node: TalentUiNode, rank: number): string {
|
|
const normalizedRank = Math.min(
|
|
node.maxRank,
|
|
Math.max(1, Math.trunc(Number.isFinite(rank) ? rank : 1)),
|
|
);
|
|
return node.rankDescriptions[normalizedRank - 1] ?? node.description;
|
|
}
|
|
|
|
export function talentUiTreeById(catalog: TalentUiCatalog, treeId: string): TalentUiTree | null {
|
|
return catalog.trees.find((tree) => tree.id === treeId) ?? null;
|
|
}
|
|
|
|
export function talentUiNodeById(catalog: TalentUiCatalog, nodeId: string): TalentUiNode | null {
|
|
return catalog.nodes.find((node) => node.id === nodeId) ?? null;
|
|
}
|
|
|
|
export function talentUiNodesForTree(catalog: TalentUiCatalog, treeId: string): readonly TalentUiNode[] {
|
|
return catalog.nodes
|
|
.filter((node) => node.treeId === treeId)
|
|
.sort((left, right) => left.row - right.row || left.index - right.index);
|
|
}
|
|
|
|
export function talentUiArtUrls(catalog: TalentUiCatalog): readonly string[] {
|
|
return [...new Set([
|
|
...catalog.trees.flatMap((tree) => [tree.icon, tree.background]),
|
|
...catalog.nodes.map((node) => node.icon),
|
|
])];
|
|
}
|