Skip to main content
Glama
index-builder.ts9.83 kB
/** * DSA5 Index Builder * * Builds enhanced creature index from Foundry compendiums. * This code runs in Foundry's browser context, not Node.js. * * Ported from foundry-module/src/tools/dsa5/creature-index.ts * Following v0.6.0 Registry Pattern. */ import type { IndexBuilder, DSA5CreatureIndex } from '../types.js'; import { SIZE_MAP_DE_TO_EN } from './constants.js'; import { getExperienceLevel } from './constants.js'; // Foundry browser globals (unavailable in Node.js TypeScript compilation) declare const ui: any; /** * Result of extractCreatureData operation */ interface DSA5ExtractionResult { creature: DSA5CreatureIndex; errors: number; } /** * DSA5 implementation of IndexBuilder */ export class DSA5IndexBuilder implements IndexBuilder { private moduleId: string; constructor(moduleId: string = 'foundry-mcp-bridge') { this.moduleId = moduleId; } getSystemId() { return 'dsa5' as const; } /** * Build enhanced creature index from compendium packs */ async buildIndex(packs: any[], force = false): Promise<DSA5CreatureIndex[]> { const startTime = Date.now(); let progressNotification: any = null; let totalErrors = 0; try { const actorPacks = packs.filter(pack => pack.metadata.type === 'Actor'); const enhancedCreatures: DSA5CreatureIndex[] = []; // Show initial progress notification console.log(`[${this.moduleId}] Starte DSA5 Kreaturen-Index aus ${actorPacks.length} Paketen...`); if (typeof ui !== 'undefined' && ui.notifications) { ui.notifications.info(`Starte DSA5 Kreaturen-Index aus ${actorPacks.length} Paketen...`); } for (let i = 0; i < actorPacks.length; i++) { const pack = actorPacks[i]; const currentPack = i + 1; // Update progress notification if (progressNotification && typeof ui !== 'undefined') { progressNotification.remove(); } if (typeof ui !== 'undefined' && ui.notifications) { progressNotification = ui.notifications.info( `Erstelle DSA5 Index: Paket ${currentPack}/${actorPacks.length} (${pack.metadata.label})...` ); } try { // Ensure pack index is loaded if (!pack.indexed) { await pack.getIndex({}); } // Process creatures in this pack const packResult = await this.extractDataFromPack(pack); enhancedCreatures.push(...packResult.creatures); totalErrors += packResult.errors; } catch (error) { console.warn(`[${this.moduleId}] Failed to process pack ${pack.metadata.label}:`, error); if (typeof ui !== 'undefined' && ui.notifications) { ui.notifications.warn(`Warnung: Fehler beim Indizieren von "${pack.metadata.label}" - fahre fort`); } } } // Clear progress notification if (progressNotification && typeof ui !== 'undefined') { progressNotification.remove(); } const buildTimeSeconds = Math.round((Date.now() - startTime) / 1000); const errorText = totalErrors > 0 ? ` (${totalErrors} Extraktionsfehler)` : ''; const successMessage = `DSA5 Kreaturen-Index fertig! ${enhancedCreatures.length} Kreaturen indiziert aus ${actorPacks.length} Paketen in ${buildTimeSeconds}s${errorText}`; console.log(`[${this.moduleId}] ${successMessage}`); if (typeof ui !== 'undefined' && ui.notifications) { ui.notifications.info(successMessage); } return enhancedCreatures; } catch (error) { if (progressNotification && typeof ui !== 'undefined') { progressNotification.remove(); } const errorMessage = `Fehler beim Erstellen des DSA5 Kreaturen-Index: ${error instanceof Error ? error.message : 'Unbekannter Fehler'}`; console.error(`[${this.moduleId}] ${errorMessage}`); if (typeof ui !== 'undefined' && ui.notifications) { ui.notifications.error(errorMessage); } throw error; } } /** * Extract creature data from a single compendium pack */ async extractDataFromPack(pack: any): Promise<{ creatures: DSA5CreatureIndex[]; errors: number }> { const creatures: DSA5CreatureIndex[] = []; let errors = 0; try { // Load all documents from pack const documents = await pack.getDocuments(); for (const doc of documents) { try { // Only process NPCs, characters, and creatures if (doc.type !== 'npc' && doc.type !== 'character' && doc.type !== 'creature') { continue; } const result = this.extractCreatureData(doc, pack); if (result) { creatures.push(result.creature); errors += result.errors; } } catch (error) { console.warn( `[${this.moduleId}] Failed to extract DSA5 data from ${doc.name} in ${pack.metadata.label}:`, error ); errors++; } } } catch (error) { console.warn(`[${this.moduleId}] Failed to load documents from ${pack.metadata.label}:`, error); errors++; } return { creatures, errors }; } /** * Extract DSA5 creature data from a single Foundry document * * @param doc - Foundry actor document * @param pack - Source compendium pack * @returns Extracted creature data or null if failed */ extractCreatureData(doc: any, pack: any): DSA5ExtractionResult | null { try { const system = doc.system || {}; // Extract experience points (AP) const experiencePoints = system.details?.experience?.total ?? system.experience?.total ?? system.status?.experience ?? 0; // Calculate level from AP using EXPERIENCE_LEVELS const experienceLevel = getExperienceLevel(experiencePoints); const level = experienceLevel.level; // Extract species let species = system.details?.species?.value ?? system.species?.value ?? system.details?.type ?? 'Unbekannt'; if (typeof species !== 'string') { species = String(species || 'Unbekannt'); } // Extract culture (with default) let culture = system.details?.culture?.value ?? system.culture?.value ?? 'Keine'; if (typeof culture !== 'string') { culture = String(culture || 'Keine'); } // Extract profession/career let profession = system.details?.career?.value ?? system.details?.profession?.value ?? system.career?.value ?? undefined; if (profession && typeof profession !== 'string') { profession = String(profession); } // Extract and normalize size let size = system.status?.size?.value ?? system.size?.value ?? 'mittel'; if (typeof size !== 'string') { size = String(size || 'mittel'); } size = SIZE_MAP_DE_TO_EN[size.toLowerCase()] || 'medium'; // Extract combat values // Note: wounds.current contains actual LeP (based on template.json reverse engineering) const lifePoints = system.status?.wounds?.max ?? system.status?.wounds?.current ?? system.wounds?.max ?? 1; const meleeDefense = system.status?.defense?.value ?? system.defense?.value ?? system.status?.defense ?? 10; const rangedDefense = system.status?.rangeDefense?.value ?? system.rangeDefense?.value ?? meleeDefense; const armor = system.status?.armour?.value ?? system.status?.armor?.value ?? 0; // Detect spellcasting capability const hasAstralEnergy = !!(system.status?.astralenergy?.max); const hasKarmaEnergy = !!(system.status?.karmaenergy?.max); const hasSpells = !!( hasAstralEnergy || hasKarmaEnergy || system.spells || system.liturgies || system.details?.tradition ); // Extract traits const traitsValue = system.details?.traits?.value || system.traits?.value || []; const traits = Array.isArray(traitsValue) ? traitsValue : []; // Optional fields const rarity = system.details?.rarity ?? system.rarity ?? undefined; return { creature: { // Base SystemCreatureIndex fields id: doc._id, name: doc.name, type: doc.type, packName: pack.metadata.id, packLabel: pack.metadata.label, img: doc.img, system: 'dsa5', // DSA5-specific systemData systemData: { level, experiencePoints, species, culture, profession, size, lifePoints, meleeDefense, rangedDefense, armor, hasSpells, hasAstralEnergy, hasKarmaEnergy, traits, ...(rarity && { rarity }), }, }, errors: 0, }; } catch (error) { console.warn(`[${this.moduleId}] Failed to extract DSA5 data from ${doc.name}:`, error); // Return fallback data with error flag return { creature: { id: doc._id, name: doc.name, type: doc.type, packName: pack.metadata.id, packLabel: pack.metadata.label, img: doc.img, system: 'dsa5', systemData: { level: 1, experiencePoints: 0, species: 'Unbekannt', culture: 'Keine', size: 'medium', lifePoints: 1, meleeDefense: 10, rangedDefense: 10, armor: 0, hasSpells: false, traits: [], }, }, errors: 1, }; } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/adambdooley/foundry-vtt-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server