/** * march_army.js — Bots march in rank-and-file formation around a circular path. * * The gateway (RCON) acts as guardian angel — paving the road, * healing, buffing, clearing threats, and putting on a show. */ const mineflayer = require('mineflayer'); const pvp = require('mineflayer-pvp').plugin; const http = require('http'); const host = process.argv[2] || '192.168.0.244'; const port = parseInt(process.argv[3] || '25568', 10); const COUNT = parseInt(process.argv[4] || '8', 10); const NAMES = [ 'Guard_I', 'Guard_II', 'Guard_III', 'Guard_IV', 'Guard_V', 'Guard_VI', 'Guard_VII', 'Guard_VIII', ]; const HOSTILE = new Set(['zombie', 'husk', 'skeleton', 'creeper', 'spider', 'drowned', 'parched', 'phantom', 'witch']); const bots = []; let marchAngle = 0; const RADIUS = 25; const MARCH_SPEED = 0.02; // radians per tick — slow stately march const FORMATION_SPACING = Math.PI / (COUNT * 1.2); // spacing between soldiers // ── RCON helper (gateway control) ── function rcon(cmd) { return new Promise((resolve) => { const body = JSON.stringify({ command: cmd }); const req = http.request({ hostname: host, port: 25578, path: '/api/chat', method: 'POST', headers: { 'Content-Type': 'application/json' }, }, () => resolve()); req.on('error', () => resolve()); // Actually just use python for reliability resolve(); }); } function rconExec(cmd) { const { execSync } = require('child_process'); try { execSync(`python3 -c "from mcrcon import MCRcon; m=MCRcon('${host}','REDACTED_RCON',port=25578); m.connect(); m.command('${cmd.replace(/'/g, "\\'")}'); m.disconnect()"`, { timeout: 3000, stdio: 'pipe' }); } catch (e) {} } // ── Spawn soldiers ── function spawnSoldier(name, index) { return new Promise((resolve) => { console.log(`[${name}] Deploying...`); const bot = mineflayer.createBot({ host, port, username: name, auth: 'offline', version: '1.21.11', }); bot.loadPlugin(pvp); const soldier = { bot, name, index, alive: false, kills: 0 }; bot.once('spawn', () => { soldier.alive = true; console.log(`[${name}] Ready`); resolve(soldier); }); bot.on('death', () => { soldier.alive = false; setTimeout(() => { try { bot.respawn(); } catch (e) {} }, 2000); }); bot.on('spawn', () => { soldier.alive = true; }); bot.on('entityHurt', (e) => { if (e !== bot.entity) return; // Counter-attack for (const ent of Object.values(bot.entities)) { if (ent === bot.entity || ent.type === 'player') continue; if (HOSTILE.has((ent.name || '').toLowerCase()) && ent.position.distanceTo(bot.entity.position) < 6) { bot.pvp.attack(ent); break; } } }); bot.on('entityDead', (e) => { if (HOSTILE.has((e.name || '').toLowerCase())) soldier.kills++; }); bot.on('kicked', () => { console.log(`[${name}] Kicked, retrying in 10s...`); setTimeout(() => spawnSoldier(name, index).then(s => { bots[index] = s; }), 10000); }); bot.on('error', () => {}); bots.push(soldier); }); } // ── March loop — move soldiers in formation ── setInterval(() => { marchAngle += MARCH_SPEED; for (let i = 0; i < bots.length; i++) { const s = bots[i]; if (!s || !s.alive || !s.bot.entity) continue; // Calculate this soldier's position on the circle const angle = marchAngle + (i * FORMATION_SPACING); const targetX = Math.cos(angle) * RADIUS; const targetZ = Math.sin(angle) * RADIUS; const targetY = 80; // road level // Look in march direction (tangent to circle) const lookAngle = angle + Math.PI / 2; const lookX = s.bot.entity.position.x + Math.cos(lookAngle) * 5; const lookZ = s.bot.entity.position.z + Math.sin(lookAngle) * 5; // Move toward target position const pos = s.bot.entity.position; const dx = targetX - pos.x; const dz = targetZ - pos.z; const dist = Math.sqrt(dx * dx + dz * dz); if (dist > 2) { // Sprint to catch up s.bot.lookAt(s.bot.entity.position.offset(dx, 0, dz)); s.bot.setControlState('forward', true); s.bot.setControlState('sprint', dist > 5); } else { // In position — walk slowly const target = s.bot.entity.position.offset( Math.cos(angle + Math.PI / 2) * 3, 0, Math.sin(angle + Math.PI / 2) * 3 ); s.bot.lookAt(target); s.bot.setControlState('forward', true); s.bot.setControlState('sprint', false); } } }, 200); // ── Guardian Angel — gateway clears threats and buffs soldiers every 15s ── setInterval(() => { const { execSync } = require('child_process'); try { execSync(`python3 -c " from mcrcon import MCRcon import math with MCRcon('${host}', 'REDACTED_RCON', port=25578) as mcr: # Kill all hostiles near the march route mcr.command('kill @e[type=minecraft:zombie,distance=..60]') mcr.command('kill @e[type=minecraft:husk,distance=..60]') mcr.command('kill @e[type=minecraft:skeleton,distance=..60]') mcr.command('kill @e[type=minecraft:spider,distance=..60]') mcr.command('kill @e[type=minecraft:creeper,distance=..60]') mcr.command('kill @e[type=minecraft:phantom,distance=..60]') # Buff all soldiers names = ${JSON.stringify(NAMES.slice(0, COUNT))} for name in names: mcr.command(f'effect give {name} minecraft:regeneration 30 1') mcr.command(f'effect give {name} minecraft:resistance 30 1') mcr.command(f'effect give {name} minecraft:glowing 30 0') # Dramatic effects — fireworks at the lead soldier's position angle = ${marchAngle} if ${marchAngle} else 0 fx = int(25 * math.cos(angle)) fz = int(25 * math.sin(angle)) mcr.command(f'summon minecraft:firework_rocket {fx} 82 {fz}') "`, { timeout: 10000, stdio: 'pipe' }); } catch (e) {} }, 15000); // ── Status report ── setInterval(() => { const alive = bots.filter(s => s.alive).length; const totalKills = bots.reduce((sum, s) => sum + s.kills, 0); console.log(`MARCH: ${alive}/${bots.length} marching | Kills: ${totalKills} | Angle: ${(marchAngle * 180 / Math.PI).toFixed(0)}°`); }, 8000); // ── Deploy ── console.log(`\n⚔️ MORTDECAI ROYAL GUARD — ${COUNT} soldiers deploying ⚔️\n`); (async () => { for (let i = 0; i < COUNT; i++) { await spawnSoldier(NAMES[i], i); await new Promise(r => setTimeout(r, 6000)); } console.log(`\n🛡️ All ${COUNT} guards deployed! Marching begins.\n`); // Gear up const { execSync } = require('child_process'); try { execSync(`python3 -c " from mcrcon import MCRcon with MCRcon('${host}', 'REDACTED_RCON', port=25578) as mcr: names = ${JSON.stringify(NAMES.slice(0, COUNT))} for name in names: mcr.command(f'give {name} minecraft:iron_sword 1') mcr.command(f'item replace entity {name} armor.chest with minecraft:diamond_chestplate') mcr.command(f'item replace entity {name} armor.legs with minecraft:diamond_leggings') mcr.command(f'item replace entity {name} armor.feet with minecraft:diamond_boots') mcr.command(f'item replace entity {name} armor.head with minecraft:diamond_helmet') mcr.command(f'effect give {name} minecraft:regeneration 600 2') mcr.command(f'effect give {name} minecraft:glowing 600 0') # TP first to march starting positions import math for i, name in enumerate(names): angle = i * math.pi / (len(names) * 1.2) x = int(25 * math.cos(angle)) z = int(25 * math.sin(angle)) mcr.command(f'tp {name} {x} 80 {z}') print(f'Guards geared and positioned') mcr.command('tellraw @a [{\"text\":\"⚔️ \",\"color\":\"gold\"},{\"text\":\"THE ROYAL GUARD MARCHES! \",\"color\":\"gold\",\"bold\":true},{\"text\":\"Behold the divine army in formation.\",\"color\":\"yellow\"}]') # TP slingshooter to watch from center monument mcr.command('tp slingshooter08 0 90 0 0 30') "`, { timeout: 30000, stdio: 'pipe' }); } catch (e) { console.log('Gear-up error:', e.message?.slice(0, 80)); } })(); process.on('SIGINT', () => { console.log('\nDismissing the guard...'); bots.forEach(s => { try { s.bot.end(); } catch (e) {} }); setTimeout(() => process.exit(0), 2000); });