import type { BestiaryIndex, BestiaryIndexEntry } from "@initiative/domain"; import rawIndex from "../../../../data/bestiary/index.json"; interface CompactCreature { readonly n: string; readonly s: string; readonly ac: number; readonly hp: number; readonly dx: number; readonly cr: string; readonly ip: number; readonly sz: string; readonly tp: string; } interface CompactIndex { readonly sources: Record; readonly creatures: readonly CompactCreature[]; } function mapCreature(c: CompactCreature): BestiaryIndexEntry { return { name: c.n, source: c.s, ac: c.ac, hp: c.hp, dex: c.dx, cr: c.cr, initiativeProficiency: c.ip, size: c.sz, type: c.tp, }; } // Source codes whose filename on the remote differs from a simple lowercase. // Plane Shift sources use a hyphen: PSA -> ps-a, etc. const FILENAME_OVERRIDES: Record = { PSA: "ps-a", PSD: "ps-d", PSI: "ps-i", PSK: "ps-k", PSX: "ps-x", PSZ: "ps-z", }; // Source codes with no corresponding remote bestiary file. // Excluded from the index entirely so creatures aren't searchable // without a fetchable source. const EXCLUDED_SOURCES = new Set([]); let cachedIndex: BestiaryIndex | undefined; export function loadBestiaryIndex(): BestiaryIndex { if (cachedIndex) return cachedIndex; const compact = rawIndex as unknown as CompactIndex; const sources = Object.fromEntries( Object.entries(compact.sources).filter( ([code]) => !EXCLUDED_SOURCES.has(code), ), ); cachedIndex = { sources, creatures: compact.creatures .filter((c) => !EXCLUDED_SOURCES.has(c.s)) .map(mapCreature), }; return cachedIndex; } export function getAllSourceCodes(): string[] { const index = loadBestiaryIndex(); return Object.keys(index.sources).filter((c) => !EXCLUDED_SOURCES.has(c)); } function sourceCodeToFilename(sourceCode: string): string { return FILENAME_OVERRIDES[sourceCode] ?? sourceCode.toLowerCase(); } export function getDefaultFetchUrl( sourceCode: string, baseUrl?: string, ): string { const filename = `bestiary-${sourceCodeToFilename(sourceCode)}.json`; if (baseUrl !== undefined) { const normalized = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`; return `${normalized}${filename}`; } return `https://raw.githubusercontent.com/5etools-mirror-3/5etools-src/main/data/bestiary/${filename}`; } export function getSourceDisplayName(sourceCode: string): string { const index = loadBestiaryIndex(); return index.sources[sourceCode] ?? sourceCode; }