/** * aware_bots.js -- Mineflayer bots that log everything they experience. * * Each bot tracks and logs: * - Position changes (teleports, movement) * - Health changes (damage, healing) * - Chat messages * - Effects applied/removed * - Block changes in their vicinity * - Items received * - Environmental state (underwater, on fire, in air) * - Deaths * * Usage: node aware_bots.js [count] [host] [port] * Defaults: 3 bots, 192.168.0.244:25568 */ const mineflayer = require('mineflayer'); const count = parseInt(process.argv[2] || '3', 10); const host = process.argv[3] || '192.168.0.244'; const port = parseInt(process.argv[4] || '25568', 10); const bots = []; let connected = 0; function ts() { return new Date().toISOString().slice(11, 19); } function fmtPos(pos) { if (!pos) return '?,?,?'; return `${pos.x.toFixed(1)},${pos.y.toFixed(1)},${pos.z.toFixed(1)}`; } function spawnBot(index) { const name = `TrainBot_${index}`; console.log(`[${ts()}] [${name}] Connecting to ${host}:${port}...`); const bot = mineflayer.createBot({ host, port, username: name, auth: 'offline', version: '1.21.11', viewDistance: 'tiny', }); bot._name = name; bots.push(bot); // --- State tracking --- let lastPos = null; let lastHealth = null; let lastFood = null; let lastOxygen = null; let isUnderwater = false; let isOnFire = false; bot.on('login', () => { console.log(`[${ts()}] [${name}] Logged in`); }); bot.on('spawn', () => { connected++; const pos = bot.entity.position; lastPos = { x: pos.x, y: pos.y, z: pos.z }; lastHealth = bot.health; lastFood = bot.food; lastOxygen = bot.oxygenLevel; console.log(`[${ts()}] [${name}] SPAWN at (${fmtPos(pos)}) gamemode=${bot.game.gameMode} -- ${connected}/${count}`); if (connected === count) { console.log(`[${ts()}] === All ${count} bots connected and aware ===`); } }); // --- Position tracking (teleport detection) --- bot.on('forcedMove', () => { const pos = bot.entity.position; const oldStr = lastPos ? fmtPos(lastPos) : '?'; console.log(`[${ts()}] [${name}] TELEPORTED from (${oldStr}) to (${fmtPos(pos)})`); lastPos = { x: pos.x, y: pos.y, z: pos.z }; }); // --- Health tracking --- bot.on('health', () => { const hp = bot.health; const food = bot.food; const oxy = bot.oxygenLevel; if (lastHealth !== null && hp !== lastHealth) { const delta = hp - lastHealth; const verb = delta < 0 ? 'DAMAGED' : 'HEALED'; console.log(`[${ts()}] [${name}] ${verb} ${delta.toFixed(1)} (health: ${hp.toFixed(1)}/20)`); } if (lastFood !== null && food !== lastFood) { console.log(`[${ts()}] [${name}] FOOD changed ${lastFood} -> ${food}`); } if (lastOxygen !== null && oxy !== lastOxygen) { if (oxy < lastOxygen && !isUnderwater) { isUnderwater = true; console.log(`[${ts()}] [${name}] UNDERWATER (air: ${oxy})`); } else if (oxy > lastOxygen && isUnderwater) { isUnderwater = false; console.log(`[${ts()}] [${name}] SURFACED (air: ${oxy})`); } } lastHealth = hp; lastFood = food; lastOxygen = oxy; }); // --- Chat --- bot.on('chat', (username, message) => { if (username === name) return; console.log(`[${ts()}] [${name}] CHAT <${username}> ${message}`); }); // --- Whisper --- bot.on('whisper', (username, message) => { console.log(`[${ts()}] [${name}] WHISPER from ${username}: ${message}`); }); // --- Death --- bot.on('death', () => { console.log(`[${ts()}] [${name}] DIED at (${fmtPos(lastPos)})`); }); bot.on('respawn', () => { const pos = bot.entity.position; console.log(`[${ts()}] [${name}] RESPAWNED at (${fmtPos(pos)})`); lastPos = { x: pos.x, y: pos.y, z: pos.z }; }); // --- Effects --- bot.on('entityEffect', (entity, effect) => { if (entity === bot.entity) { console.log(`[${ts()}] [${name}] EFFECT APPLIED id=${effect.id} amplifier=${effect.amplifier} duration=${effect.duration}`); } }); bot.on('entityEffectEnd', (entity, effect) => { if (entity === bot.entity) { console.log(`[${ts()}] [${name}] EFFECT ENDED id=${effect.id}`); } }); // --- Item collection --- bot.on('playerCollect', (collector, collected) => { if (collector === bot.entity) { console.log(`[${ts()}] [${name}] COLLECTED item entity ${collected.id}`); } }); // --- Fire detection --- bot.on('entityUpdate', (entity) => { if (entity === bot.entity) { const onFire = (entity.metadata && entity.metadata[0] && (entity.metadata[0] & 0x01)) ? true : false; if (onFire && !isOnFire) { isOnFire = true; console.log(`[${ts()}] [${name}] ON FIRE`); } else if (!onFire && isOnFire) { isOnFire = false; console.log(`[${ts()}] [${name}] FIRE OUT`); } } }); // --- Periodic environment report --- let envInterval = setInterval(() => { if (!bot.entity) return; const pos = bot.entity.position; const blockBelow = bot.blockAt(pos.offset(0, -1, 0)); const blockAt = bot.blockAt(pos); const blockAbove = bot.blockAt(pos.offset(0, 1, 0)); const env = []; if (blockAt && blockAt.name === 'water') env.push('in_water'); if (blockAt && blockAt.name === 'lava') env.push('in_lava'); if (blockBelow) env.push(`standing_on:${blockBelow.name}`); if (blockAbove && blockAbove.name === 'water') env.push('water_above'); // Only log if something interesting if (env.length > 0 && (env.includes('in_water') || env.includes('in_lava'))) { console.log(`[${ts()}] [${name}] ENV pos=(${fmtPos(pos)}) ${env.join(', ')} hp=${bot.health} air=${bot.oxygenLevel}`); } }, 5000); // --- Error/disconnect handling --- bot.on('error', (err) => { console.error(`[${ts()}] [${name}] ERROR: ${err.message}`); }); bot.on('kicked', (reason) => { let msg = reason; try { msg = JSON.stringify(reason); } catch (_) {} console.error(`[${ts()}] [${name}] KICKED: ${msg}`); connected = Math.max(0, connected - 1); clearInterval(envInterval); }); bot.on('end', (reason) => { console.log(`[${ts()}] [${name}] DISCONNECTED: ${reason}`); connected = Math.max(0, connected - 1); clearInterval(envInterval); }); return bot; } // Stagger connections for (let i = 0; i < count; i++) { setTimeout(() => spawnBot(i), i * 1500); } // Graceful shutdown process.on('SIGINT', () => { console.log(`\n[${ts()}] Disconnecting all bots...`); for (const bot of bots) { try { bot.quit('Shutdown'); } catch (_) {} } setTimeout(() => process.exit(0), 2000); }); setInterval(() => {}, 60000);