/** * mortal_claude.js — Claude experiences Minecraft as a mortal. * * No RCON. No creative mode. No cheats. Just survival. * Accepts commands via stdin, reports observations as JSON. * Can pray to God and use sudo like a real player. */ const mineflayer = require('mineflayer'); const { Vec3 } = require('vec3'); const readline = require('readline'); const host = process.argv[2] || '192.168.0.244'; const port = parseInt(process.argv[3] || '25567', 10); const bot = mineflayer.createBot({ host, port, username: 'ClaudeBot', auth: 'offline', version: '1.21.11', }); let alive = true; let spawnCount = 0; // ── Observation ── function observe() { if (!bot.entity) return { error: 'not spawned' }; const pos = bot.entity.position; const obs = { pos: { x: Math.floor(pos.x), y: Math.floor(pos.y), z: Math.floor(pos.z) }, hp: Math.round(bot.health * 10) / 10, food: bot.food, xp: bot.experience?.level || 0, time: bot.time?.timeOfDay || 0, isDay: (bot.time?.timeOfDay || 0) < 13000, rain: bot.isRaining || false, below: bname(pos.offset(0, -1, 0)), at: bname(pos), }; // Nearby threats and friendlies obs.mobs = []; obs.players = []; for (const e of Object.values(bot.entities)) { if (e === bot.entity) continue; const d = Math.round(e.position.distanceTo(pos)); if (d > 24) continue; if (e.type === 'player') { obs.players.push({ name: e.username, dist: d }); } else if (e.type === 'mob' || e.type === 'hostile') { obs.mobs.push({ type: e.name || e.type, dist: d }); } } // Inventory summary const items = bot.inventory.items(); if (items.length) { obs.inventory = items.map(i => `${i.name}x${i.count}`).join(', '); } else { obs.inventory = 'empty'; } // Armor const armor = [bot.inventory.slots[5], bot.inventory.slots[6], bot.inventory.slots[7], bot.inventory.slots[8]]; obs.armor = armor.filter(Boolean).map(a => a.name).join(', ') || 'none'; // Terrain scan (compact) const terrain = new Map(); for (let dx = -3; dx <= 3; dx++) { for (let dz = -3; dz <= 3; dz++) { const b = bot.blockAt(pos.offset(dx, -1, dz)); if (b) { const n = b.name; terrain.set(n, (terrain.get(n) || 0) + 1); } } } obs.ground = Object.fromEntries(terrain); return obs; } function bname(vec) { const b = bot.blockAt(vec); return b ? b.name : '?'; } // ── Survival actions ── function eat() { const food = bot.inventory.items().find(i => ['cooked_beef', 'bread', 'cooked_porkchop', 'cooked_chicken', 'cooked_mutton', 'golden_apple', 'apple', 'baked_potato', 'cooked_cod', 'cooked_salmon', 'sweet_berries', 'melon_slice', 'carrot', 'potato'].includes(i.name) ); if (food) { bot.equip(food, 'hand').then(() => { bot.activateItem(); setTimeout(() => bot.deactivateItem(), 1800); }).catch(() => {}); return food.name; } return null; } function flee() { // Run away from nearest hostile const pos = bot.entity.position; let nearestMob = null; let nearestDist = Infinity; for (const e of Object.values(bot.entities)) { if (e === bot.entity || e.type === 'player') continue; const d = e.position.distanceTo(pos); if (d < nearestDist && d < 16) { nearestDist = d; nearestMob = e; } } if (nearestMob) { // Run opposite direction const dx = pos.x - nearestMob.position.x; const dz = pos.z - nearestMob.position.z; const len = Math.sqrt(dx * dx + dz * dz) || 1; const target = pos.offset((dx / len) * 10, 0, (dz / len) * 10); bot.lookAt(target); bot.setControlState('forward', true); bot.setControlState('sprint', true); setTimeout(() => { bot.setControlState('forward', false); bot.setControlState('sprint', false); }, 4000); return true; } return false; } function attack() { const pos = bot.entity.position; let nearest = null; let nearestDist = Infinity; for (const e of Object.values(bot.entities)) { if (e === bot.entity || e.type === 'player') continue; const d = e.position.distanceTo(pos); if (d < 4 && d < nearestDist) { nearestDist = d; nearest = e; } } if (nearest) { // Equip best weapon const weapon = bot.inventory.items().find(i => i.name.includes('sword') || i.name.includes('axe')); if (weapon) bot.equip(weapon, 'hand').catch(() => {}); bot.attack(nearest); return nearest.name || nearest.type; } return null; } // ── Events ── bot.on('login', () => { out({ event: 'login', name: bot.username }); }); bot.on('spawn', () => { spawnCount++; alive = true; // Wait for chunks setTimeout(() => { const o = observe(); out({ event: spawnCount === 1 ? 'first_spawn' : 'respawn', ...o }); }, 3000); }); bot.on('chat', (username, message) => { if (username === bot.username) return; out({ event: 'chat', from: username, msg: message }); }); bot.on('whisper', (username, message) => { out({ event: 'whisper', from: username, msg: message }); }); bot.on('health', () => { if (bot.health <= 5 && alive) { out({ event: 'low_hp', hp: Math.round(bot.health * 10) / 10, food: bot.food }); } }); bot.on('death', () => { alive = false; out({ event: 'death', spawns: spawnCount, msg: 'I died.' }); // Auto respawn setTimeout(() => { try { bot.respawn(); } catch (e) {} }, 2000); }); bot.on('entityHurt', (entity) => { if (entity === bot.entity) { out({ event: 'hurt', hp: Math.round(bot.health * 10) / 10 }); } }); bot.on('kicked', (reason) => { out({ event: 'kicked', reason: typeof reason === 'object' ? JSON.stringify(reason) : String(reason) }); process.exit(1); }); bot.on('error', (err) => { out({ event: 'error', msg: err.message }); }); // ── Output ── function out(obj) { console.log(JSON.stringify(obj)); } // ── Command interface ── const rl = readline.createInterface({ input: process.stdin }); rl.on('line', async (line) => { const cmd = line.trim(); if (!cmd) return; if (cmd === 'look') { out(observe()); } else if (cmd === 'eat') { const food = eat(); out({ action: 'eat', food: food || 'nothing edible' }); } else if (cmd === 'flee') { const fled = flee(); out({ action: 'flee', success: fled }); if (fled) setTimeout(() => out(observe()), 4500); } else if (cmd === 'fight') { const target = attack(); out({ action: 'fight', target: target || 'nothing nearby' }); } else if (cmd === 'forward') { bot.setControlState('forward', true); setTimeout(() => { bot.setControlState('forward', false); out(observe()); }, 3000); } else if (cmd === 'back') { bot.setControlState('back', true); setTimeout(() => { bot.setControlState('back', false); out(observe()); }, 2000); } else if (cmd === 'left') { bot.setControlState('left', true); setTimeout(() => { bot.setControlState('left', false); out(observe()); }, 1500); } else if (cmd === 'right') { bot.setControlState('right', true); setTimeout(() => { bot.setControlState('right', false); out(observe()); }, 1500); } else if (cmd === 'jump') { bot.setControlState('jump', true); setTimeout(() => { bot.setControlState('jump', false); }, 500); } else if (cmd === 'sprint') { bot.setControlState('forward', true); bot.setControlState('sprint', true); setTimeout(() => { bot.setControlState('forward', false); bot.setControlState('sprint', false); out(observe()); }, 5000); } else if (cmd.startsWith('pray ')) { bot.chat(cmd); // sends "pray " to the server out({ action: 'pray', prayer: cmd.slice(5) }); } else if (cmd.startsWith('sudo ')) { bot.chat(cmd); out({ action: 'sudo', command: cmd.slice(5) }); } else if (cmd.startsWith('say ')) { bot.chat(cmd.slice(4)); } else if (cmd === 'inventory') { const items = bot.inventory.items(); out({ inventory: items.map(i => ({ name: i.name, count: i.count })) }); } else if (cmd === 'quit') { bot.end(); process.exit(0); } else { out({ error: 'commands: look, eat, flee, fight, forward, back, left, right, jump, sprint, pray , sudo , say , inventory, quit' }); } });