5b28002001
Major changes from this session: Training: - 0.6.0 training running: 9B on steel141 3090 Ti, 27B on rented H100 NVL - 7,256 merged training examples (up from 3,183) - New training data: failure modes (85), midloop messaging (27), prompt injection defense (29), personality (32), gold from quarantine bank (232), new tool examples (30), claude's own experience (10) - All training data RCON-validated at 100% pass rate - Bake-off: gemma3:27b 66%, qwen3.5:27b 61%, translategemma:27b 56% Oracle Bot (Mind's Eye): - Invisible spectator bot (mineflayer) streams world state via WebSocket - HTML5 Canvas frontend at mind.mortdec.ai - Real-time tool trace visualization with expandable entries - Streaming model tokens during inference - Gateway integration: fire-and-forget POST /trace on every tool call Reinforcement Learning: - Gymnasium environment wrapping mineflayer bot (minecraft_env.py) - PPO training via Stable Baselines3 (10K param policy network) - Behavioral cloning pretraining (97.5% accuracy on expert policy) - Infinite training loop with auto-restart and checkpoint resume - Bot learns combat, survival, navigation from raw experience Bot Army: - 8-soldier marching formation with autonomous combat - Combat bots using mineflayer-pvp, pathfinder, armor-manager - Multilingual prayer bots via translategemma:27b (18 languages) - Frame-based AI architecture: LLM planner + reactive micro-scripts Infrastructure: - Fixed mattpc.sethpc.xyz billing gateway (API key + player list parser) - Billing gateway now tracks all LAN traffic (LAN auto-auth) - Gateway fallback for empty god-mode responses - Updated mortdec.ai landing page Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
267 lines
7.9 KiB
JavaScript
267 lines
7.9 KiB
JavaScript
/**
|
|
* combat_bot.js — Full combat-capable bot using mineflayer plugins.
|
|
*
|
|
* Uses:
|
|
* mineflayer-pvp — combat AI (attack, strafe, shield)
|
|
* mineflayer-pathfinder — A* navigation
|
|
* mineflayer-armor-manager — auto-equip best armor
|
|
* mineflayer-auto-eat — auto-eat when hungry/hurt
|
|
*
|
|
* The bot autonomously fights, eats, equips armor, and pathfinds.
|
|
* AI frames via stdin set high-level goals.
|
|
*/
|
|
|
|
const mineflayer = require('mineflayer');
|
|
const pathfinder = require('mineflayer-pathfinder');
|
|
const pvp = require('mineflayer-pvp').plugin;
|
|
const armorManager = require('mineflayer-armor-manager');
|
|
const autoEat = require('mineflayer-auto-eat').loader;
|
|
const { GoalNear, GoalFollow, GoalBlock } = require('mineflayer-pathfinder').goals;
|
|
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',
|
|
});
|
|
|
|
// Load plugins
|
|
bot.loadPlugin(pathfinder.pathfinder);
|
|
bot.loadPlugin(pvp);
|
|
bot.loadPlugin(armorManager);
|
|
bot.loadPlugin(autoEat);
|
|
|
|
let deaths = 0;
|
|
let kills = 0;
|
|
let alive = false;
|
|
|
|
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',
|
|
]);
|
|
|
|
// ── Spawn setup ──
|
|
bot.once('spawn', () => {
|
|
alive = true;
|
|
deaths++;
|
|
|
|
// Configure pathfinder
|
|
const mcData = require('minecraft-data')(bot.version);
|
|
const movements = new pathfinder.Movements(bot, mcData);
|
|
movements.allowSprinting = true;
|
|
movements.canDig = false; // Don't destroy blocks
|
|
bot.pathfinder.setMovements(movements);
|
|
|
|
// Configure auto-eat
|
|
bot.autoEat.options = {
|
|
priority: 'foodPoints',
|
|
startAt: 16,
|
|
bannedFood: [],
|
|
};
|
|
|
|
out({ event: 'ready', msg: 'Plugins loaded. Combat-ready.' });
|
|
setTimeout(() => out(observe()), 3000);
|
|
});
|
|
|
|
bot.on('spawn', () => {
|
|
if (deaths > 1) {
|
|
alive = true;
|
|
out({ event: 'respawn', deaths });
|
|
setTimeout(() => out(observe()), 3000);
|
|
}
|
|
});
|
|
|
|
// ── Combat events ──
|
|
bot.on('entityHurt', (entity) => {
|
|
if (entity === bot.entity) {
|
|
// I'm being hit — find attacker and fight back
|
|
const attacker = nearestHostile(8);
|
|
if (attacker && !bot.pvp.target) {
|
|
out({ event: 'counter_attack', target: attacker.name || attacker.type, dist: Math.round(attacker.position.distanceTo(bot.entity.position)) });
|
|
bot.pvp.attack(attacker);
|
|
}
|
|
}
|
|
});
|
|
|
|
bot.on('playerCollect', (collector, collected) => {
|
|
if (collector === bot.entity) {
|
|
out({ event: 'pickup', item: collected.name || 'item' });
|
|
}
|
|
});
|
|
|
|
bot.on('death', () => {
|
|
alive = false;
|
|
deaths++;
|
|
out({ event: 'death', deaths, kills });
|
|
setTimeout(() => { try { bot.respawn(); } catch (e) {} }, 2000);
|
|
});
|
|
|
|
bot.on('chat', (username, message) => {
|
|
if (username === bot.username) return;
|
|
out({ event: 'chat', from: username, msg: message });
|
|
});
|
|
|
|
bot.on('kicked', (reason) => {
|
|
out({ event: 'kicked', reason: typeof reason === 'object' ? JSON.stringify(reason) : String(reason) });
|
|
process.exit(1);
|
|
});
|
|
|
|
// Track kills
|
|
bot.on('entityDead', (entity) => {
|
|
const name = (entity.name || entity.type || '').toLowerCase();
|
|
if (HOSTILE.has(name)) {
|
|
kills++;
|
|
out({ event: 'kill', target: name, totalKills: kills });
|
|
}
|
|
});
|
|
|
|
// ── Autonomous combat loop ──
|
|
setInterval(() => {
|
|
if (!alive || !bot.entity) return;
|
|
|
|
const hp = bot.health;
|
|
const nearest = nearestHostile(16);
|
|
|
|
// If low HP and being attacked, flee
|
|
if (hp < 6 && nearest && nearest.position.distanceTo(bot.entity.position) < 6) {
|
|
bot.pvp.stop();
|
|
fleeSmart(nearest);
|
|
return;
|
|
}
|
|
|
|
// If hostile nearby and not already fighting, engage
|
|
if (nearest && !bot.pvp.target) {
|
|
const dist = nearest.position.distanceTo(bot.entity.position);
|
|
if (dist < 10) {
|
|
bot.pvp.attack(nearest);
|
|
}
|
|
}
|
|
}, 1000);
|
|
|
|
// ── Periodic status report ──
|
|
setInterval(() => {
|
|
if (!alive) return;
|
|
out(observe());
|
|
}, 6000);
|
|
|
|
// ── Helper functions ──
|
|
|
|
function nearestHostile(maxDist) {
|
|
if (!bot.entity) return null;
|
|
let nearest = null;
|
|
let nearestDist = maxDist || 16;
|
|
for (const e of Object.values(bot.entities)) {
|
|
if (e === bot.entity) continue;
|
|
const name = (e.name || e.type || '').toLowerCase();
|
|
if (!HOSTILE.has(name)) continue;
|
|
const d = e.position.distanceTo(bot.entity.position);
|
|
if (d < nearestDist) {
|
|
nearestDist = d;
|
|
nearest = e;
|
|
}
|
|
}
|
|
return nearest;
|
|
}
|
|
|
|
function fleeSmart(from) {
|
|
if (!from || !bot.entity) return;
|
|
const pos = bot.entity.position;
|
|
const dx = pos.x - from.position.x;
|
|
const dz = pos.z - from.position.z;
|
|
const len = Math.sqrt(dx * dx + dz * dz) || 1;
|
|
const fleeX = pos.x + (dx / len) * 30;
|
|
const fleeZ = pos.z + (dz / len) * 30;
|
|
|
|
try {
|
|
bot.pathfinder.setGoal(new GoalNear(fleeX, pos.y, fleeZ, 2));
|
|
} catch (e) {
|
|
// Fallback: just sprint
|
|
bot.setControlState('forward', true);
|
|
bot.setControlState('sprint', true);
|
|
setTimeout(() => {
|
|
bot.setControlState('forward', false);
|
|
bot.setControlState('sprint', false);
|
|
}, 3000);
|
|
}
|
|
}
|
|
|
|
function observe() {
|
|
if (!bot.entity) return { alive: false };
|
|
const pos = bot.entity.position;
|
|
const mobs = [];
|
|
|
|
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;
|
|
const name = e.name || e.type;
|
|
const hostile = HOSTILE.has((name || '').toLowerCase());
|
|
if (e.type === 'player') {
|
|
mobs.push({ type: 'player', name: e.username, dist: d });
|
|
} else if (hostile || d < 10) {
|
|
mobs.push({ type: name, dist: d, hostile });
|
|
}
|
|
}
|
|
|
|
return {
|
|
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,
|
|
day: (bot.time?.timeOfDay || 0) < 13000,
|
|
inv: bot.inventory.items().map(i => `${i.name}x${i.count}`).join(', ') || 'empty',
|
|
armor: [5, 6, 7, 8].map(s => bot.inventory.slots[s]?.name).filter(Boolean).join(', ') || 'none',
|
|
weapon: bot.heldItem?.name || 'fist',
|
|
fighting: bot.pvp.target ? (bot.pvp.target.name || bot.pvp.target.type) : null,
|
|
mobs: mobs.sort((a, b) => a.dist - b.dist).slice(0, 8),
|
|
kills,
|
|
deaths,
|
|
};
|
|
}
|
|
|
|
function out(obj) {
|
|
console.log(JSON.stringify(obj));
|
|
}
|
|
|
|
// ── AI Frame commands ──
|
|
const rl = readline.createInterface({ input: process.stdin });
|
|
rl.on('line', (line) => {
|
|
const cmd = line.trim();
|
|
|
|
if (cmd === 'look') out(observe());
|
|
else if (cmd === 'hunt') {
|
|
// Actively seek and kill nearest hostile
|
|
const target = nearestHostile(30);
|
|
if (target) {
|
|
bot.pvp.attack(target);
|
|
out({ action: 'hunting', target: target.name || target.type, dist: Math.round(target.position.distanceTo(bot.entity.position)) });
|
|
} else {
|
|
out({ action: 'hunt', msg: 'no hostiles nearby' });
|
|
}
|
|
}
|
|
else if (cmd === 'stop') { bot.pvp.stop(); bot.pathfinder.stop(); out({ action: 'stopped' }); }
|
|
else if (cmd.startsWith('goto ')) {
|
|
const [, x, y, z] = cmd.split(' ').map(Number);
|
|
bot.pathfinder.setGoal(new GoalNear(x, y, z, 2));
|
|
out({ action: 'navigating', target: { x, y, z } });
|
|
}
|
|
else if (cmd.startsWith('follow ')) {
|
|
const name = cmd.slice(7);
|
|
const player = bot.players[name];
|
|
if (player?.entity) {
|
|
bot.pathfinder.setGoal(new GoalFollow(player.entity, 3), true);
|
|
out({ action: 'following', target: name });
|
|
}
|
|
}
|
|
else if (cmd.startsWith('pray ')) { bot.chat(cmd); out({ action: 'pray' }); }
|
|
else if (cmd.startsWith('sudo ')) { bot.chat(cmd); out({ action: 'sudo' }); }
|
|
else if (cmd.startsWith('say ')) { bot.chat(cmd.slice(4)); }
|
|
else if (cmd === 'quit') { bot.end(); process.exit(0); }
|
|
else { out({ commands: ['look', 'hunt', 'stop', 'goto X Y Z', 'follow PLAYER', 'pray MSG', 'sudo CMD', 'say MSG', 'quit'] }); }
|
|
});
|