/** * bot_army.js — Spawn an army of combat bots. * * Each bot runs autonomous combat with mineflayer-pvp. * They attack all hostiles but never each other. * Reports collective stats. * * Usage: node bot_army.js [count] [host] [port] */ const mineflayer = require('mineflayer'); const pvp = require('mineflayer-pvp').plugin; const armorManager = require('mineflayer-armor-manager'); const pathfinder = require('mineflayer-pathfinder'); const { GoalNear } = require('mineflayer-pathfinder').goals; const count = parseInt(process.argv[2] || '8', 10); const host = process.argv[3] || '192.168.0.244'; const port = parseInt(process.argv[4] || '25567', 10); const HOSTILE = new Set([ 'zombie', 'husk', 'skeleton', 'creeper', 'spider', 'cave_spider', 'witch', 'enderman', 'pillager', 'vindicator', 'phantom', 'drowned', 'stray', 'blaze', 'ghast', 'slime', 'magma_cube', 'parched', 'camel_husk', 'zombie_villager', ]); const NAMES = [ 'Soldier_Alpha', 'Soldier_Bravo', 'Soldier_Charlie', 'Soldier_Delta', 'Soldier_Echo', 'Soldier_Foxtrot', 'Soldier_Golf', 'Soldier_Hotel', 'Soldier_India', 'Soldier_Juliet', 'Soldier_Kilo', 'Soldier_Lima', 'Soldier_Mike', 'Soldier_November', 'Soldier_Oscar', 'Soldier_Papa', ]; // Track all bot names so they don't attack each other const armyNames = new Set(NAMES.slice(0, count)); armyNames.add('ClaudeBot'); armyNames.add('slingshooter08'); armyNames.add('OracleBot'); const army = []; let totalKills = 0; let totalDeaths = 0; function spawnSoldier(name, index) { return new Promise((resolve) => { setTimeout(() => { console.log(`[${name}] Spawning...`); const bot = mineflayer.createBot({ host, port, username: name, auth: 'offline', version: '1.21.11', }); bot.loadPlugin(pvp); bot.loadPlugin(armorManager); bot.loadPlugin(pathfinder.pathfinder); const soldier = { bot, name, kills: 0, deaths: 0, alive: false }; bot.once('spawn', () => { soldier.alive = true; // Configure pathfinder try { const mcData = require('minecraft-data')(bot.version); const movements = new pathfinder.Movements(bot, mcData); movements.allowSprinting = true; movements.canDig = false; bot.pathfinder.setMovements(movements); } catch (e) {} console.log(`[${name}] Ready at (${Math.floor(bot.entity.position.x)}, ${Math.floor(bot.entity.position.y)}, ${Math.floor(bot.entity.position.z)})`); resolve(soldier); }); // Respawn on death bot.on('death', () => { soldier.alive = false; soldier.deaths++; totalDeaths++; console.log(`[${name}] DIED (kills: ${soldier.kills}, deaths: ${soldier.deaths})`); setTimeout(() => { try { bot.respawn(); } catch (e) {} }, 2000); }); bot.on('spawn', () => { if (soldier.deaths > 0) { soldier.alive = true; } }); // Track kills bot.on('entityDead', (entity) => { const ename = (entity.name || entity.type || '').toLowerCase(); if (HOSTILE.has(ename)) { soldier.kills++; totalKills++; } }); // Counter-attack when hit bot.on('entityHurt', (entity) => { if (entity !== bot.entity) return; const attacker = findNearestHostile(bot, 8); if (attacker && !bot.pvp.target) { bot.pvp.attack(attacker); } }); bot.on('kicked', (reason) => { console.log(`[${name}] Kicked: ${JSON.stringify(reason).slice(0, 80)}`); }); bot.on('error', (err) => { console.error(`[${name}] Error: ${err.message}`); }); army.push(soldier); }, index * 3000); // Stagger spawns by 3 seconds }); } function findNearestHostile(bot, maxDist) { if (!bot.entity) return null; let nearest = null; let nearestDist = maxDist; for (const e of Object.values(bot.entities)) { if (e === bot.entity) continue; // Never attack army members or players if (e.type === 'player') continue; const ename = (e.name || e.type || '').toLowerCase(); if (!HOSTILE.has(ename)) continue; const d = e.position.distanceTo(bot.entity.position); if (d < nearestDist) { nearestDist = d; nearest = e; } } return nearest; } // ── Autonomous combat loop for all bots ── setInterval(() => { for (const soldier of army) { if (!soldier.alive || !soldier.bot.entity) continue; const bot = soldier.bot; const hp = bot.health; // Find nearest hostile const target = findNearestHostile(bot, 16); // Flee if critical if (hp < 5 && target) { bot.pvp.stop(); const pos = bot.entity.position; const dx = pos.x - target.position.x; const dz = pos.z - target.position.z; const len = Math.sqrt(dx * dx + dz * dz) || 1; bot.lookAt(pos.offset((dx / len) * 10, 0, (dz / len) * 10)); bot.setControlState('forward', true); bot.setControlState('sprint', true); setTimeout(() => { bot.setControlState('forward', false); bot.setControlState('sprint', false); }, 3000); continue; } // Attack nearest hostile if (target && !bot.pvp.target) { bot.pvp.attack(target); } // If no target but pvp active, stop if (!target && bot.pvp.target) { bot.pvp.stop(); } } }, 800); // ── Status report every 10 seconds ── setInterval(() => { const alive = army.filter(s => s.alive).length; const fighting = army.filter(s => s.bot.pvp?.target).length; console.log(`\n=== ARMY STATUS: ${alive}/${army.length} alive | ${fighting} fighting | Kills: ${totalKills} | Deaths: ${totalDeaths} ===`); for (const s of army) { if (!s.bot.entity) continue; const pos = s.bot.entity.position; const target = s.bot.pvp?.target; const status = !s.alive ? 'DEAD' : target ? `FIGHTING ${target.name || target.type}` : 'PATROLLING'; console.log(` [${s.name}] HP:${Math.round(s.bot.health)} K:${s.kills} D:${s.deaths} — ${status}`); } }, 10000); // ── Startup ── console.log(`\n╔════════════════════════════════════════╗`); console.log(`║ MORTDECAI BOT ARMY — ${count} SOLDIERS ║`); console.log(`║ Target: ${host}:${port} ║`); console.log(`║ Rules: Kill all hostiles, protect allies ║`); console.log(`╚════════════════════════════════════════╝\n`); // Whitelist soldiers (skip if dev server / no whitelist) const { execSync } = require('child_process'); const rconPort = port === 25567 ? 25577 : 25578; const rconPass = port === 25567 ? 'REDACTED_RCON' : 'REDACTED_RCON'; try { for (let i = 0; i < count; i++) { execSync(`python3 -c "from mcrcon import MCRcon; m=MCRcon('${host}','${rconPass}',port=${rconPort}); m.connect(); m.command('whitelist add ${NAMES[i]}'); m.disconnect()"`, { timeout: 5000, stdio: 'pipe' }); } console.log(`Whitelisted ${count} soldiers`); } catch (e) { console.log('Whitelist skipped (dev server or already done)'); } // Spawn soldiers (async () => { for (let i = 0; i < count; i++) { await spawnSoldier(NAMES[i], 0); await new Promise(r => setTimeout(r, 3000)); } console.log(`\nAll ${count} soldiers deployed!`); // Gear them all up via RCON try { execSync(`python3 -c " from mcrcon import MCRcon with MCRcon('${host}', '${rconPass}', port=${rconPort}) as mcr: soldiers = ${JSON.stringify(NAMES.slice(0, count))} for name in soldiers: mcr.command(f'give {name} minecraft:iron_sword 1') mcr.command(f'give {name} minecraft:cooked_beef 16') mcr.command(f'item replace entity {name} armor.chest with minecraft:iron_chestplate') mcr.command(f'item replace entity {name} armor.legs with minecraft:iron_leggings') mcr.command(f'item replace entity {name} armor.feet with minecraft:iron_boots') mcr.command(f'item replace entity {name} armor.head with minecraft:iron_helmet') mcr.command(f'effect give {name} minecraft:regeneration 600 1') print(f'Geared up {len(soldiers)} soldiers') "`, { timeout: 30000 }); } catch (e) { console.log('Gear-up error:', e.message?.slice(0, 80)); } })(); // Graceful shutdown process.on('SIGINT', () => { console.log(`\n\nFinal score: ${totalKills} kills, ${totalDeaths} deaths`); console.log('Disconnecting army...'); army.forEach(s => { try { s.bot.end(); } catch (e) {} }); setTimeout(() => process.exit(0), 2000); });