@zerospacegg/anrubic
Version:
Anrubic - ZeroSpace.gg MCP Server for AI agents to access game data
234 lines (232 loc) • 9.16 kB
JavaScript
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