UNPKG

@zerospacegg/anrubic

Version:

Anrubic - ZeroSpace.gg MCP Server for AI agents to access game data

234 lines (232 loc) 9.16 kB
import { readFileSync } from "fs"; import { dirname, join } from "path"; import { fileURLToPath } from "url"; export class MarkdownFormatter { /** * Format a mechanic as markdown for MCP responses */ static formatMechanic(mechanic, detailed = false) { let output = `# ${mechanic.name}`; if (mechanic.category === "transformation") { output += " Transformation"; } output += `\n\n`; if (mechanic.keywords.length > 0) { output += `**Keywords**: ${mechanic.keywords.join(", ")}\n\n`; } output += `**Description**: ${mechanic.description}\n\n`; if (detailed && mechanic.category === "transformation") { const transform = mechanic.data; output += this.formatTransformationDetails(transform); } return output.trim(); } /** * Format transformation-specific details */ static formatTransformationDetails(transform) { let output = ""; output += `**Type**: ${transform.type}\n`; output += `**Stackable**: ${transform.stackable ? "Yes" : "No"}`; if (transform.maxStacks) { output += ` (max ${transform.maxStacks})`; } output += `\n\n`; if (transform.statModifications) { output += `**Stat Modifications**:\n`; if (transform.statModifications.hpMultiplier) { output += `- HP: ${transform.statModifications.hpMultiplier}x\n`; } if (transform.statModifications.damageMultiplier) { output += `- Damage: ${transform.statModifications.damageMultiplier}x\n`; } output += `\n`; } if (transform.hasPhases && (transform.preDeathPhase || transform.postDeathPhase)) { output += `**Phases**:\n`; if (transform.preDeathPhase) { output += `- Pre-death: Speed ${transform.preDeathPhase.speedMultiplier}x, Cooldowns ${transform.preDeathPhase.cooldownMultiplier}x\n`; } if (transform.postDeathPhase) { output += `- Post-death: Speed ${transform.postDeathPhase.speedMultiplier}x, Cooldowns ${transform.postDeathPhase.cooldownMultiplier}x\n`; } output += `\n`; } if (transform.notes) { output += `**Notes**: ${transform.notes}`; } return output; } /** * Format mechanics overview with statistics */ static formatMechanicsOverview(mechanics, stats) { const mechanicsList = mechanics.map((mechanic) => ({ category: mechanic.category === "basic" ? "Basic Mechanic" : "Transformation", term: mechanic.name, keywords: mechanic.keywords, description: (mechanic.description || "").length > 150 ? (mechanic.description || "").substring(0, 150) + "..." : mechanic.description || "", })); return `# ZeroSpace Game Mechanics Found ${stats.totalMechanics} documented mechanics: - ${stats.basicMechanics} basic mechanics - ${stats.transformations} transformations ${JSON.stringify(mechanicsList, null, 2)}`; } /** * Format error message for mechanic not found */ static formatMechanicNotFound(term, availableKeys) { return `ERROR: Mechanic '${term}' not found. Available mechanics: ${availableKeys.join(", ")}`; } /** * Format general error message */ static formatError(message) { return `ERROR: ${message}`; } } export class MechanicsService { constructor() { this.mechanicsData = null; const __filename = fileURLToPath(import.meta.url); this.__dirname = dirname(__filename); } /** * Load mechanics data from the JSON file */ async loadMechanics() { if (this.mechanicsData) { return this.mechanicsData; } try { const mechanicsPath = join(this.__dirname, "..", "..", "iolin", "dist", "json", "meta", "mechanics.json"); const mechanicsFileContent = readFileSync(mechanicsPath, "utf-8"); const parsed = JSON.parse(mechanicsFileContent); // Validate structure if (!parsed.mechanics) { throw new Error("Invalid mechanics file: missing 'mechanics' root object"); } if (!parsed.mechanics.basicMechanics) { console.warn("Warning: No basicMechanics found in mechanics file"); parsed.mechanics.basicMechanics = {}; } if (!parsed.mechanics.transformations) { console.warn("Warning: No transformations found in mechanics file"); parsed.mechanics.transformations = {}; } this.mechanicsData = parsed; return this.mechanicsData; } catch (error) { console.error("Failed to load mechanics data:", error); // Return empty structure on error const emptyData = { mechanics: { basicMechanics: {}, transformations: {}, }, }; this.mechanicsData = emptyData; return emptyData; } } /** * Get flattened mechanics for easy searching */ async getFlattenedMechanics() { const data = await this.loadMechanics(); const flattened = {}; // Add basic mechanics for (const [key, mechanic] of Object.entries(data.mechanics.basicMechanics)) { flattened[key] = { key, category: "basic", name: mechanic.term || key, keywords: mechanic.keywords || [], description: mechanic.description || "", data: mechanic, }; } // Add transformations for (const [key, transformation] of Object.entries(data.mechanics.transformations)) { flattened[key] = { key, category: "transformation", name: transformation.name || key, keywords: transformation.keywords || [], description: transformation.description || "", data: transformation, }; } return flattened; } /** * Search for a specific mechanic by key */ async getMechanic(key) { const flattened = await this.getFlattenedMechanics(); return flattened[key] || null; } /** * Get all mechanics with optional filtering */ async getAllMechanics(filter) { const flattened = await this.getFlattenedMechanics(); let mechanics = Object.values(flattened); if (filter?.category) { mechanics = mechanics.filter((m) => m.category === filter.category); } if (filter?.searchTerm) { const searchLower = filter.searchTerm.toLowerCase(); mechanics = mechanics.filter((m) => m.name.toLowerCase().includes(searchLower) || m.description.toLowerCase().includes(searchLower) || m.keywords.some((k) => k.toLowerCase().includes(searchLower))); } return mechanics; } /** * Get statistics about the mechanics system */ async getStats() { const data = await this.loadMechanics(); const flattened = await this.getFlattenedMechanics(); const basicCount = Object.keys(data.mechanics.basicMechanics).length; const transformCount = Object.keys(data.mechanics.transformations).length; const totalCount = basicCount + transformCount; const mechanicsWithKeywords = Object.values(flattened).filter((m) => m.keywords.length > 0).length; const totalKeywords = Object.values(flattened).reduce((sum, m) => sum + m.keywords.length, 0); const averageKeywords = totalCount > 0 ? totalKeywords / totalCount : 0; return { totalMechanics: totalCount, basicMechanics: basicCount, transformations: transformCount, mechanicsWithKeywords, averageKeywords: Math.round(averageKeywords * 100) / 100, }; } /** * Format a mechanic for display (delegated to MarkdownFormatter) */ formatMechanic(mechanic, detailed = false) { return MarkdownFormatter.formatMechanic(mechanic, detailed); } /** * Format mechanics list for overview (delegated to MarkdownFormatter) */ formatMechanicsList(mechanics) { return mechanics.map((mechanic) => ({ category: mechanic.category === "basic" ? "Basic Mechanic" : "Transformation", term: mechanic.name, keywords: mechanic.keywords, description: (mechanic.description || "").length > 150 ? (mechanic.description || "").substring(0, 150) + "..." : mechanic.description || "", })); } } // Export singleton instance for convenience export const mechanicsService = new MechanicsService(); //# sourceMappingURL=mechanics-service.js.map