Skip to main content
Glama
system-detection.ts6.26 kB
/** * Game System Detection Utilities * * Detects the Foundry VTT game system (D&D 5e, Pathfinder 2e, etc.) and provides * system-specific data path mappings for cross-system compatibility. */ import { FoundryClient } from '../foundry-client.js'; import { Logger } from '../logger.js'; /** * Supported game systems */ export type GameSystem = 'dnd5e' | 'pf2e' | 'other'; /** * Cache for system detection (avoid repeated queries) */ let cachedSystem: GameSystem | null = null; let cachedSystemId: string | null = null; /** * Detect the active Foundry game system * Results are cached to avoid repeated queries */ export async function detectGameSystem(foundryClient: FoundryClient, logger?: Logger): Promise<GameSystem> { if (cachedSystem) { return cachedSystem; } try { const worldInfo = await foundryClient.query('foundry-mcp-bridge.getWorldInfo'); const systemId = worldInfo.system?.toLowerCase() || ''; cachedSystemId = systemId; if (systemId === 'dnd5e') { cachedSystem = 'dnd5e'; } else if (systemId === 'pf2e') { cachedSystem = 'pf2e'; } else { cachedSystem = 'other'; } if (logger) { logger.info('Game system detected', { systemId, detectedAs: cachedSystem }); } return cachedSystem; } catch (error) { if (logger) { logger.error('Failed to detect game system, defaulting to other', { error }); } cachedSystem = 'other'; return cachedSystem; } } /** * Get the raw system ID string (e.g., "dnd5e", "pf2e", "coc7") */ export function getCachedSystemId(): string | null { return cachedSystemId; } /** * Clear cached system detection (useful for testing or world switches) */ export function clearSystemCache(): void { cachedSystem = null; cachedSystemId = null; } /** * System-specific data paths for creature/actor stats */ export const SystemPaths = { dnd5e: { // D&D 5e specific paths challengeRating: 'system.details.cr', creatureType: 'system.details.type.value', size: 'system.traits.size', alignment: 'system.details.alignment', level: 'system.details.level.value', // For NPCs/characters hitPoints: 'system.attributes.hp', armorClass: 'system.attributes.ac.value', abilities: 'system.abilities', skills: 'system.skills', spells: 'system.spells', legendaryActions: 'system.resources.legact', legendaryResistances: 'system.resources.legres' }, pf2e: { // Pathfinder 2e specific paths level: 'system.details.level.value', creatureType: 'system.traits.value', // Array of traits size: 'system.traits.size.value', alignment: 'system.details.alignment.value', rarity: 'system.traits.rarity', traits: 'system.traits.value', // All traits as array hitPoints: 'system.attributes.hp', armorClass: 'system.attributes.ac.value', abilities: 'system.abilities', skills: 'system.skills', perception: 'system.perception', saves: 'system.saves', // PF2e doesn't have CR or legendary actions challengeRating: null, legendaryActions: null } } as const; /** * Get system-specific data paths based on detected system */ export function getSystemPaths(system: GameSystem) { if (system === 'dnd5e') { return SystemPaths.dnd5e; } else if (system === 'pf2e') { return SystemPaths.pf2e; } // Default to dnd5e paths for unknown systems (best effort) return SystemPaths.dnd5e; } /** * Extract a value from system data using a path string * Handles both simple and nested paths (e.g., "system.details.cr") */ export function extractSystemValue(data: any, path: string | null): any { if (!path || !data) { return undefined; } const parts = path.split('.'); let value = data; for (const part of parts) { if (value === undefined || value === null) { return undefined; } value = value[part]; } return value; } /** * Get creature level/CR based on system * Returns a normalized level value for both D&D 5e and PF2e */ export function getCreatureLevel(actorData: any, system: GameSystem): number | undefined { const paths = getSystemPaths(system); if (system === 'dnd5e') { // D&D 5e: Try CR first, then level const cr = extractSystemValue(actorData, paths.challengeRating); if (cr !== undefined) return Number(cr); const level = extractSystemValue(actorData, paths.level); if (level !== undefined) return Number(level); } else if (system === 'pf2e') { // PF2e: Level is the primary metric const level = extractSystemValue(actorData, paths.level); if (level !== undefined) return Number(level); } return undefined; } /** * Get creature type/traits based on system */ export function getCreatureType(actorData: any, system: GameSystem): string | string[] | undefined { if (system === 'dnd5e') { // D&D 5e: Single creature type string return extractSystemValue(actorData, SystemPaths.dnd5e.creatureType); } else if (system === 'pf2e') { // PF2e: Array of traits const traits = extractSystemValue(actorData, SystemPaths.pf2e.traits); return Array.isArray(traits) ? traits : undefined; } return undefined; } /** * Check if creature has spellcasting based on system */ export function hasSpellcasting(actorData: any, system: GameSystem): boolean { if (system === 'dnd5e') { // D&D 5e: Check for spells object or spellcasting level const spells = extractSystemValue(actorData, SystemPaths.dnd5e.spells); const spellLevel = extractSystemValue(actorData, 'system.details.spellLevel'); return !!(spells || spellLevel); } else if (system === 'pf2e') { // PF2e: Check for spellcasting entries const spellcasting = extractSystemValue(actorData, 'system.spellcasting'); return spellcasting && Object.keys(spellcasting).length > 0; } return false; } /** * Format system-specific error messages */ export function formatSystemError(system: GameSystem, systemId: string | null): string { if (system === 'other') { return `This tool currently supports D&D 5e and Pathfinder 2e. Your world uses system: "${systemId || 'unknown'}". Please use a supported system or request support for additional systems.`; } return 'Unknown system error'; }

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