UNPKG

@zerospacegg/anrubic

Version:

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

520 lines (455 loc) • 17.8 kB
#!/usr/bin/env node import { readFileSync } from "fs"; import { dirname, join } from "path"; import { fileURLToPath } from "url"; // Get the directory containing this script const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Types for our game data (same as main server) interface Entity { id: string; name: string; slug: string; type: string; faction: string; tags: string[]; description?: string; lore?: string; tier?: string; health?: number; shields?: number; attack?: number; armor?: number; speed?: number; supply?: number; flux?: number; buildTime?: number; } interface TagInfo { tag: string; label: string; description: string; slug: string; } interface GameData { entities: Entity[]; tags: Record<string, TagInfo>; entitiesByFaction: Record<string, Entity[]>; entitiesByType: Record<string, Entity[]>; entitiesById: Record<string, Entity>; mechanics?: any; } // Load game data from iolin JSON files function loadGameData(): GameData { const iolinPath = join(__dirname, "../../iolin/dist/json"); try { // Load all entities const allData = JSON.parse(readFileSync(join(iolinPath, "all.json"), "utf8")); const tagsData = JSON.parse(readFileSync(join(iolinPath, "tags.json"), "utf8")); // Load mechanics data let mechanicsData = null; try { mechanicsData = JSON.parse(readFileSync(join(iolinPath, "meta/mechanics.json"), "utf8")); } catch (mechanicsError) { console.error("āš ļø Could not load mechanics data:", mechanicsError); } // Build our structured data const entities: Entity[] = []; const entitiesByFaction: Record<string, Entity[]> = {}; const entitiesByType: Record<string, Entity[]> = {}; const entitiesById: Record<string, Entity> = {}; // Process entities from all.json for (const [category, categoryData] of Object.entries(allData)) { if (typeof categoryData === "object" && categoryData !== null) { for (const [key, entityData] of Object.entries(categoryData as Record<string, any>)) { if (typeof entityData === "object" && entityData !== null && "id" in entityData) { const entity: Entity = { id: entityData.id || key, name: entityData.name || key, slug: entityData.slug || key, type: entityData.type || category, faction: entityData.faction || "unknown", tags: Array.isArray(entityData.tags) ? entityData.tags : [], description: entityData.description, lore: entityData.lore, tier: entityData.tier, health: typeof entityData.health === "number" ? entityData.health : undefined, shields: typeof entityData.shields === "number" ? entityData.shields : undefined, attack: typeof entityData.attack === "number" ? entityData.attack : undefined, armor: typeof entityData.armor === "number" ? entityData.armor : undefined, speed: typeof entityData.speed === "number" ? entityData.speed : undefined, supply: typeof entityData.supply === "number" ? entityData.supply : undefined, flux: typeof entityData.flux === "number" ? entityData.flux : undefined, buildTime: typeof entityData.buildTime === "number" ? entityData.buildTime : undefined, }; entities.push(entity); entitiesById[entity.id] = entity; // Group by faction if (!entitiesByFaction[entity.faction]) { entitiesByFaction[entity.faction] = []; } entitiesByFaction[entity.faction]!.push(entity); // Group by type if (!entitiesByType[entity.type]) { entitiesByType[entity.type] = []; } entitiesByType[entity.type]!.push(entity); } } } } return { entities, tags: tagsData.tags || {}, entitiesByFaction, entitiesByType, entitiesById, mechanics: mechanicsData, }; } catch (error) { console.error("Failed to load game data:", error); process.exit(1); } } // CLI Commands function showHelp() { console.log(` Anrubic CLI - ZeroSpace Game Data Tool Usage: anrubic <command> [options] Commands: status Show server status and data statistics tags List all available tags factions List all factions with entity counts faction <name> Show all entities for a faction search <query> [limit] Search entities by name/description/tags get <id> Get entity details by ID types List all entity types type <name> Show all entities of a specific type ids [type] [faction] List all entity IDs (optionally filtered) mechanics [term] Show game mechanics (all or specific term) Examples: anrubic status anrubic tags anrubic faction "Terran Alliance" anrubic search marine 5 anrubic get marine anrubic type unit anrubic ids unit "Xol Collective" anrubic mechanics anrubic mechanics ABES anrubic mechanics Infuse šŸš€ ZeroSpace.gg - Cosmic Data Empire `); } function formatEntity(entity: Entity, detailed = false): string { let output = `${entity.name} (${entity.id})`; if (detailed) { output += `\n Type: ${entity.type}`; output += `\n Faction: ${entity.faction}`; if (entity.description) output += `\n Description: ${entity.description}`; if (entity.lore) output += `\n Lore: ${entity.lore}`; if (entity.tier) output += `\n Tier: ${entity.tier}`; if (entity.health) output += `\n Health: ${entity.health}`; if (entity.shields) output += `\n Shields: ${entity.shields}`; if (entity.attack) output += `\n Attack: ${entity.attack}`; if (entity.armor) output += `\n Armor: ${entity.armor}`; if (entity.speed) output += `\n Speed: ${entity.speed}`; if (entity.supply) output += `\n Supply: ${entity.supply}`; if (entity.flux) output += `\n Flux: ${entity.flux}`; if (entity.buildTime) output += `\n Build Time: ${entity.buildTime}s`; if (entity.tags.length > 0) output += `\n Tags: ${entity.tags.join(", ")}`; } else { output += ` [${entity.type}/${entity.faction}]`; } return output; } // Parse command line arguments const args = process.argv.slice(2); const command = args[0]; const isJsonOutput = args.includes('--json'); // Initialize game data if (!isJsonOutput) { console.error("Loading ZeroSpace game data..."); } const gameData = loadGameData(); if (!isJsonOutput) { console.error( `Loaded ${gameData.entities.length} entities from ${Object.keys(gameData.entitiesByFaction).length} factions`, ); } switch (command) { case "status": console.log("šŸš€ Anrubic Status"); console.log("================"); console.log(`Entities: ${gameData.entities.length}`); console.log(`Factions: ${Object.keys(gameData.entitiesByFaction).length}`); console.log(`Types: ${Object.keys(gameData.entitiesByType).length}`); console.log(`Tags: ${Object.keys(gameData.tags).length}`); const basicCount = gameData.mechanics?.mechanics?.basicMechanics ? Object.keys(gameData.mechanics.mechanics.basicMechanics).length : 0; const transformCount = gameData.mechanics?.mechanics?.transformations ? Object.keys(gameData.mechanics.mechanics.transformations).length : 0; console.log(`Mechanics: ${basicCount} basic, ${transformCount} transformations`); console.log(`Timestamp: ${new Date().toISOString()}`); break; case "tags": console.log("šŸ·ļø Available Tags"); console.log("================="); for (const [tag, info] of Object.entries(gameData.tags)) { console.log(`${tag}: ${info.label}`); if (info.description && info.description !== info.label) { console.log(` ${info.description}`); } } break; case "factions": console.log("šŸ›ļø Available Factions"); console.log("===================="); for (const [faction, entities] of Object.entries(gameData.entitiesByFaction)) { console.log(`${faction}: ${entities.length} entities`); } break; case "faction": const factionName = args[1]; if (!factionName) { console.error("Please specify a faction name"); console.error("Available factions:", Object.keys(gameData.entitiesByFaction).join(", ")); process.exit(1); } const factionEntities = gameData.entitiesByFaction[factionName]; if (!factionEntities) { console.error(`Faction "${factionName}" not found`); console.error("Available factions:", Object.keys(gameData.entitiesByFaction).join(", ")); process.exit(1); } console.log(`šŸ›ļø ${factionName} (${factionEntities.length} entities)`); console.log("=".repeat(factionName.length + 20)); // Group by type const factionByType: Record<string, Entity[]> = {}; for (const entity of factionEntities) { if (!factionByType[entity.type]) { factionByType[entity.type] = []; } factionByType[entity.type]!.push(entity); } for (const [type, entities] of Object.entries(factionByType)) { console.log(`\n${type.toUpperCase()}S:`); for (const entity of entities) { console.log(` ${formatEntity(entity)}`); } } break; case "search": const query = args[1]; const limit = parseInt(args[2] || "10") || 10; if (!query) { console.error("Please specify a search query"); process.exit(1); } const lowerQuery = query.toLowerCase(); const matches = gameData.entities .filter( (entity) => entity.name.toLowerCase().includes(lowerQuery) || entity.description?.toLowerCase().includes(lowerQuery) || entity.lore?.toLowerCase().includes(lowerQuery) || entity.tags.some((tag) => tag.toLowerCase().includes(lowerQuery)), ) .slice(0, limit); console.log(`šŸ” Search Results for "${query}" (${matches.length} matches)`); console.log("=".repeat(30 + query.length)); if (matches.length === 0) { console.log("No matches found"); } else { for (const entity of matches) { console.log(` ${formatEntity(entity)}`); } } break; case "get": const entityId = args[1]; if (!entityId) { if (isJsonOutput) { console.log(JSON.stringify({ error: "Please specify an entity ID" }, null, 2)); } else { console.error("Please specify an entity ID"); } process.exit(1); } const entity = gameData.entitiesById[entityId]; if (!entity) { if (isJsonOutput) { console.log(JSON.stringify({ error: `Entity "${entityId}" not found` }, null, 2)); } else { console.error(`Entity "${entityId}" not found`); } process.exit(1); } if (isJsonOutput) { console.log(JSON.stringify(entity, null, 2)); } else { console.log(`Entity Details: ${entity.name}`); console.log("=".repeat(20 + entity.name.length)); console.log(formatEntity(entity, true)); } break; case "types": console.log("šŸ“¦ Available Types"); console.log("================="); for (const [type, entities] of Object.entries(gameData.entitiesByType)) { console.log(`${type}: ${entities.length} entities`); } break; case "type": const typeName = args[1]; if (!typeName) { console.error("Please specify a type name"); console.error("Available types:", Object.keys(gameData.entitiesByType).join(", ")); process.exit(1); } const typeEntities = gameData.entitiesByType[typeName]; if (!typeEntities) { console.error(`Type "${typeName}" not found`); console.error("Available types:", Object.keys(gameData.entitiesByType).join(", ")); process.exit(1); } console.log(`šŸ“¦ ${typeName.toUpperCase()} (${typeEntities.length} entities)`); console.log("=".repeat(typeName.length + 20)); for (const entity of typeEntities) { console.log(` ${formatEntity(entity)}`); } break; case "ids": const filterType = args[1]; const filterFaction = args[2]; let filteredEntities = gameData.entities; if (filterType) { filteredEntities = filteredEntities.filter((e) => e.type === filterType); } if (filterFaction) { filteredEntities = filteredEntities.filter((e) => e.faction === filterFaction); } console.log(`šŸ†” Entity IDs (${filteredEntities.length} total)`); if (filterType || filterFaction) { console.log( `Filters: ${filterType ? `type=${filterType}` : ""}${filterType && filterFaction ? ", " : ""}${filterFaction ? `faction=${filterFaction}` : ""}`, ); } console.log("=".repeat(30)); for (const entity of filteredEntities) { console.log(`${entity.id} - ${entity.name} [${entity.type}/${entity.faction}]`); } break; case "mechanics": const mechanicTerm = args[1]; if (!gameData.mechanics) { console.error("Mechanics data not available"); process.exit(1); } // Flatten hierarchical mechanics for search const allMechanics: Record<string, any> = {}; if (gameData.mechanics.mechanics?.basicMechanics) { Object.assign(allMechanics, gameData.mechanics.mechanics.basicMechanics); } if (gameData.mechanics.mechanics?.transformations) { Object.assign(allMechanics, gameData.mechanics.mechanics.transformations); } if (mechanicTerm) { // Show specific mechanic const mechanic = allMechanics[mechanicTerm]; if (!mechanic) { console.error(`Mechanic "${mechanicTerm}" not found`); console.error("Available mechanics:", Object.keys(allMechanics).join(", ")); process.exit(1); } console.log(`Mechanic: ${mechanic.term || mechanic.name}`); console.log("=".repeat(15 + (mechanic.term || mechanic.name).length)); if (mechanic.term) { // Basic mechanic console.log(`Keywords: ${mechanic.keywords?.join(", ") || "None"}`); console.log(`Description: ${mechanic.description || "No description"}`); } else if (mechanic.name) { // Transformation console.log(`Type: ${mechanic.type}`); console.log(`Keywords: ${mechanic.keywords?.join(", ") || "None"}`); console.log(`Description: ${mechanic.description || "No description"}`); console.log( `Stackable: ${mechanic.stackable ? "Yes" : "No"}${mechanic.maxStacks ? ` (max ${mechanic.maxStacks})` : ""}`, ); if (mechanic.statModifications) { console.log("\nStat Modifications:"); if (mechanic.statModifications.hpMultiplier) { console.log(` HP: ${mechanic.statModifications.hpMultiplier}x`); } if (mechanic.statModifications.damageMultiplier) { console.log(` Damage: ${mechanic.statModifications.damageMultiplier}x`); } } if (mechanic.hasPhases && (mechanic.preDeathPhase || mechanic.postDeathPhase)) { console.log("\nPhases:"); if (mechanic.preDeathPhase) { console.log( ` Pre-death: Speed ${mechanic.preDeathPhase.speedMultiplier}x, Cooldowns ${mechanic.preDeathPhase.cooldownMultiplier}x`, ); } if (mechanic.postDeathPhase) { console.log( ` Post-death: Speed ${mechanic.postDeathPhase.speedMultiplier}x, Cooldowns ${mechanic.postDeathPhase.cooldownMultiplier}x`, ); } } if (mechanic.notes) { console.log(`\nNotes: ${mechanic.notes}`); } } } else { // Show all mechanics const basicCount = gameData.mechanics.mechanics?.basicMechanics ? Object.keys(gameData.mechanics.mechanics.basicMechanics).length : 0; const transformCount = gameData.mechanics.mechanics?.transformations ? Object.keys(gameData.mechanics.mechanics.transformations).length : 0; console.log(`ZeroSpace Game Mechanics (${basicCount + transformCount} total)`); console.log("==============================="); console.log(`${basicCount} basic mechanics, ${transformCount} transformations\n`); if (gameData.mechanics.mechanics?.basicMechanics) { console.log("BASIC MECHANICS:"); for (const [key, mechanic] of Object.entries(gameData.mechanics.mechanics.basicMechanics)) { const typedMechanic = mechanic as { term?: string; description?: string }; console.log( ` ${typedMechanic.term || key} - ${(typedMechanic.description || "").substring(0, 80)}${(typedMechanic.description || "").length > 80 ? "..." : ""}`, ); } console.log(); } if (gameData.mechanics.mechanics?.transformations) { console.log("TRANSFORMATIONS:"); for (const [key, transformation] of Object.entries( gameData.mechanics.mechanics.transformations, )) { const typedTransformation = transformation as { name?: string; description?: string }; console.log( ` ${typedTransformation.name || key} - ${(typedTransformation.description || "").substring(0, 80)}${(typedTransformation.description || "").length > 80 ? "..." : ""}`, ); } } } break; case "help": case "--help": case "-h": showHelp(); break; default: if (!command) { showHelp(); } else { console.error(`Unknown command: ${command}`); showHelp(); process.exit(1); } }