Skip to main content
Glama
adapter.ts11.2 kB
/** * DSA5 System Adapter * * Implements SystemAdapter interface for DSA5 (Das Schwarze Auge 5) support. * Handles creature indexing, filtering, formatting, and data extraction. */ import type { SystemAdapter, SystemMetadata, SystemCreatureIndex, DSA5CreatureIndex } from '../types.js'; import { DSA5FiltersSchema, matchesDSA5Filters, describeDSA5Filters, type DSA5Filters } from './filters.js'; import { FIELD_PATHS, getExperienceLevel, EIGENSCHAFT_NAMES } from './constants.js'; /** * DSA5 system adapter */ export class DSA5Adapter implements SystemAdapter { getMetadata(): SystemMetadata { return { id: 'dsa5', name: 'dsa5', displayName: 'Das Schwarze Auge 5', version: '1.0.0', description: 'Support for DSA5 (Das Schwarze Auge 5. Edition) with Eigenschaften, Talente, Erfahrungsgrade, and LeP/AsP/KaP resources', supportedFeatures: { creatureIndex: true, characterStats: true, spellcasting: true, powerLevel: true // Uses Experience Level (Erfahrungsgrad 1-7) } }; } canHandle(systemId: string): boolean { return systemId.toLowerCase() === 'dsa5'; } /** * Extract creature data from Foundry document for indexing * This is called by the index builder in Foundry's browser context */ extractCreatureData(doc: any, pack: any): { creature: SystemCreatureIndex; errors: number } | null { // Implementation is in index-builder.ts since it runs in browser // This method is here for type compliance but delegates to IndexBuilder throw new Error('extractCreatureData should be called from DSA5IndexBuilder, not the adapter'); } getFilterSchema() { return DSA5FiltersSchema; } matchesFilters(creature: SystemCreatureIndex, filters: Record<string, any>): boolean { // Validate filters match DSA5 schema const validated = DSA5FiltersSchema.safeParse(filters); if (!validated.success) { return false; } return matchesDSA5Filters(creature, validated.data as DSA5Filters); } getDataPaths(): Record<string, string | null> { return { // DSA5 specific paths level: FIELD_PATHS.DETAILS_EXPERIENCE_TOTAL, // Level is calculated from AP species: FIELD_PATHS.DETAILS_SPECIES, culture: FIELD_PATHS.DETAILS_CULTURE, profession: FIELD_PATHS.DETAILS_CAREER, // IMPORTANT: 'career' not 'profession' size: FIELD_PATHS.STATUS_SIZE, // Characteristics (Eigenschaften) characteristics: FIELD_PATHS.CHARACTERISTICS, mu: FIELD_PATHS.CHAR_MU, kl: FIELD_PATHS.CHAR_KL, in: FIELD_PATHS.CHAR_IN, ch: FIELD_PATHS.CHAR_CH, ff: FIELD_PATHS.CHAR_FF, ge: FIELD_PATHS.CHAR_GE, ko: FIELD_PATHS.CHAR_KO, kk: FIELD_PATHS.CHAR_KK, // Status values wounds: FIELD_PATHS.STATUS_WOUNDS, lifePoints: FIELD_PATHS.STATUS_WOUNDS_CURRENT, // wounds.current has actual LeP astralenergy: FIELD_PATHS.STATUS_ASTRAL, karmaenergy: FIELD_PATHS.STATUS_KARMA, speed: FIELD_PATHS.STATUS_SPEED, initiative: FIELD_PATHS.STATUS_INITIATIVE, dodge: FIELD_PATHS.STATUS_DODGE, armor: FIELD_PATHS.STATUS_ARMOR, // Tradition tradition: FIELD_PATHS.TRADITION, // D&D5e-specific paths don't exist in DSA5 challengeRating: null, creatureType: null, alignment: null, hitPoints: null, armorClass: null, legendaryActions: null, legendaryResistances: null, // PF2e-specific paths don't exist in DSA5 perception: null, saves: null, rarity: null, }; } formatCreatureForList(creature: SystemCreatureIndex): any { const dsa5Creature = creature as DSA5CreatureIndex; const formatted: any = { id: creature.id, name: creature.name, type: creature.type, pack: { id: creature.packName, label: creature.packLabel } }; // Add DSA5 specific stats if (dsa5Creature.systemData) { const stats: any = {}; if (dsa5Creature.systemData.level !== undefined) { stats.level = dsa5Creature.systemData.level; // Add experience level name (e.g., "Erfahren") const expLevel = getExperienceLevel(dsa5Creature.systemData.experiencePoints ?? 0); stats.experienceLevel = expLevel.name; } if (dsa5Creature.systemData.species) { stats.species = dsa5Creature.systemData.species; } if (dsa5Creature.systemData.culture) { stats.culture = dsa5Creature.systemData.culture; } if (dsa5Creature.systemData.size) { stats.size = dsa5Creature.systemData.size; } if (dsa5Creature.systemData.lifePoints) { stats.lifePoints = dsa5Creature.systemData.lifePoints; } if (dsa5Creature.systemData.meleeDefense) { stats.meleeDefense = dsa5Creature.systemData.meleeDefense; } if (dsa5Creature.systemData.hasSpells) { stats.spellcaster = true; } if (Object.keys(stats).length > 0) { formatted.stats = stats; } } if (creature.img) { formatted.hasImage = true; } return formatted; } formatCreatureForDetails(creature: SystemCreatureIndex): any { const dsa5Creature = creature as DSA5CreatureIndex; const formatted = this.formatCreatureForList(creature); // Add additional details if (dsa5Creature.systemData) { const expLevel = getExperienceLevel(dsa5Creature.systemData.experiencePoints ?? 0); formatted.detailedStats = { level: dsa5Creature.systemData.level, experienceLevel: { name: expLevel.name, nameEn: expLevel.nameEn, level: expLevel.level, apRange: `${expLevel.min}-${expLevel.max === Infinity ? '∞' : expLevel.max}` }, experiencePoints: dsa5Creature.systemData.experiencePoints, species: dsa5Creature.systemData.species, culture: dsa5Creature.systemData.culture, profession: dsa5Creature.systemData.profession, size: dsa5Creature.systemData.size, lifePoints: dsa5Creature.systemData.lifePoints, meleeDefense: dsa5Creature.systemData.meleeDefense, rangedDefense: dsa5Creature.systemData.rangedDefense, armor: dsa5Creature.systemData.armor, hasSpells: dsa5Creature.systemData.hasSpells, hasAstralEnergy: dsa5Creature.systemData.hasAstralEnergy, hasKarmaEnergy: dsa5Creature.systemData.hasKarmaEnergy, traits: dsa5Creature.systemData.traits || [], rarity: dsa5Creature.systemData.rarity, }; } if (creature.img) { formatted.img = creature.img; } return formatted; } describeFilters(filters: Record<string, any>): string { const validated = DSA5FiltersSchema.safeParse(filters); if (!validated.success) { return 'ungültige Filter'; } return describeDSA5Filters(validated.data as DSA5Filters); } getPowerLevel(creature: SystemCreatureIndex): number | undefined { const dsa5Creature = creature as DSA5CreatureIndex; // DSA5: Use Experience Level (Erfahrungsgrad 1-7) if (dsa5Creature.systemData?.level !== undefined) { return dsa5Creature.systemData.level; } return undefined; } /** * Extract character statistics from actor data */ extractCharacterStats(actorData: any): any { const system = actorData.system || {}; const stats: any = {}; // Basic info stats.name = actorData.name; stats.type = actorData.type; // Experience and Level const totalAP = system.details?.experience?.total ?? 0; const spentAP = system.details?.experience?.spent ?? 0; if (totalAP > 0) { const expLevel = getExperienceLevel(totalAP); stats.experience = { total: totalAP, spent: spentAP, available: totalAP - spentAP, level: expLevel.level, levelName: expLevel.name, levelNameEn: expLevel.nameEn }; } // LeP (Lebensenergie) - wounds.current contains actual current LeP const wounds = system.status?.wounds; if (wounds) { stats.lifePoints = { current: wounds.current ?? 0, max: wounds.max ?? 0, }; } // AsP (Astralenergie) const astral = system.status?.astralenergy; if (astral && astral.max > 0) { stats.astralEnergy = { current: astral.value ?? 0, max: astral.max ?? 0, }; } // KaP (Karmaenergie) const karma = system.status?.karmaenergy; if (karma && karma.max > 0) { stats.karmaEnergy = { current: karma.value ?? 0, max: karma.max ?? 0, }; } // Eigenschaften (Characteristics: MU, KL, IN, CH, FF, GE, KO, KK) if (system.characteristics) { stats.characteristics = {}; for (const [key, eigenschaft] of Object.entries(system.characteristics)) { const eigenschaftData = eigenschaft as any; const upperKey = key.toUpperCase(); stats.characteristics[upperKey] = { value: eigenschaftData.value ?? 8, initial: eigenschaftData.initial ?? 8, name: EIGENSCHAFT_NAMES[upperKey]?.german, nameEn: EIGENSCHAFT_NAMES[upperKey]?.english }; } } // Combat values const initiative = system.status?.initiative?.value ?? system.status?.initiative; if (initiative !== undefined) { stats.initiative = initiative; } const speed = system.status?.speed?.value ?? system.status?.speed; if (speed !== undefined) { stats.speed = speed; } const dodge = system.status?.dodge?.value ?? system.status?.dodge; if (dodge !== undefined) { stats.dodge = dodge; } const armor = system.status?.armour?.value ?? system.status?.armor?.value ?? 0; if (armor) { stats.armor = armor; } // Identity info if (system.details) { const identity: any = {}; const species = system.details.species?.value; if (species) { identity.species = species; } const culture = system.details.culture?.value; if (culture) { identity.culture = culture; } const career = system.details.career?.value; if (career) { identity.profession = career; } if (Object.keys(identity).length > 0) { stats.identity = identity; } } // Size const size = system.status?.size?.value; if (size) { stats.size = size; } // Tradition (magical/clerical) if (system.tradition) { const tradition: any = {}; if (system.tradition.magical) { tradition.magical = system.tradition.magical; } if (system.tradition.clerical) { tradition.clerical = system.tradition.clerical; } if (Object.keys(tradition).length > 0) { stats.tradition = tradition; } } // Spellcasting detection const hasSpells = !!(astral?.max || karma?.max || system.tradition); if (hasSpells) { stats.spellcasting = { hasSpells: true, hasAstralEnergy: !!(astral?.max), hasKarmaEnergy: !!(karma?.max) }; } return stats; } }

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