188 lines
No EOL
5.7 KiB
JavaScript
188 lines
No EOL
5.7 KiB
JavaScript
const mineflayer = require('mineflayer');
|
|
const { pathfinder, Movements, goals: { GoalNear } } = require('mineflayer-pathfinder');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const sqlite3 = require('sqlite3').verbose();
|
|
const axios = require('axios');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// === SQLite setup ===
|
|
const dbPath = path.join(__dirname, '../db/memory.db');
|
|
const db = new sqlite3.Database(dbPath, (err) => {
|
|
if (err) {
|
|
console.error('Failed to connect to database:', err);
|
|
} else {
|
|
console.log('Connected to SQLite database.');
|
|
db.run(`CREATE TABLE IF NOT EXISTS tasks (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
action TEXT,
|
|
parameters TEXT,
|
|
context TEXT,
|
|
outcome TEXT,
|
|
score INTEGER
|
|
)`);
|
|
}
|
|
});
|
|
|
|
// === AI Integration (Ollama) ===
|
|
const OLLAMA_URL = 'http://192.168.1.3:11434/api/generate';
|
|
const MODEL_NAME = 'gemma3';
|
|
|
|
// === Create the bot ===
|
|
const bot = mineflayer.createBot({
|
|
host: '192.168.1.90',
|
|
port: 25565,
|
|
username: 'Cletus',
|
|
version: '1.20.4',
|
|
auth: 'offline'
|
|
});
|
|
|
|
bot.loadPlugin(pathfinder);
|
|
|
|
bot.on('spawn', () => {
|
|
console.log('Bot has spawned!');
|
|
const defaultMove = new Movements(bot);
|
|
bot.pathfinder.setMovements(defaultMove);
|
|
|
|
// Passive background wandering when idle
|
|
setInterval(() => {
|
|
if (!bot.pathfinder.isMoving() && !bot.targetDigBlock) {
|
|
const pos = bot.entity.position.offset(
|
|
Math.floor(Math.random() * 10 - 5),
|
|
0,
|
|
Math.floor(Math.random() * 10 - 5)
|
|
);
|
|
bot.chat("Ugh, wandering again. This server is so boring...");
|
|
bot.pathfinder.setGoal(new GoalNear(pos.x, pos.y, pos.z, 1));
|
|
}
|
|
}, 60000); // every 60 seconds
|
|
});
|
|
|
|
async function followPlayer(username) {
|
|
const target = bot.players[username]?.entity;
|
|
if (target) {
|
|
bot.chat(`Ugh, fine. Following ${username}.`);
|
|
bot.pathfinder.setGoal(new GoalFollow(target, 1), true);
|
|
db.run(`INSERT INTO tasks (action, parameters, context, outcome, score) VALUES (?, ?, ?, ?, ?)`,
|
|
['follow', JSON.stringify({ target: username }), 'Follow player', 'started', 0]);
|
|
} else {
|
|
bot.chat("Seriously? I can't even see you.");
|
|
db.run(`INSERT INTO tasks (action, parameters, context, outcome, score) VALUES (?, ?, ?, ?, ?)`,
|
|
['follow', JSON.stringify({ target: username }), 'Follow player', 'target not found', -1]);
|
|
}
|
|
}
|
|
|
|
async function exploreArea() {
|
|
const pos = bot.entity.position.offset(
|
|
Math.floor(Math.random() * 20 - 10),
|
|
0,
|
|
Math.floor(Math.random() * 20 - 10)
|
|
);
|
|
bot.chat("Exploring... because why not.");
|
|
bot.pathfinder.setGoal(new GoalNear(pos.x, pos.y, pos.z, 1));
|
|
db.run(`INSERT INTO tasks (action, context, outcome, score) VALUES (?, ?, ?, ?)`,
|
|
['explore', `Random target: ${pos}`, 'started', 0]);
|
|
}
|
|
|
|
async function digNearestWood() {
|
|
const logBlock = bot.findBlock({ matching: block => block.name.includes("log"), maxDistance: 16 });
|
|
if (!logBlock) {
|
|
bot.chat("Wow, no trees? Shocking.");
|
|
db.run(`INSERT INTO tasks (action, context, outcome, score) VALUES (?, ?, ?, ?)`,
|
|
['chop_tree', 'No logs nearby', 'failed', -1]);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
bot.chat("Here we go again, chopping a tree.");
|
|
await bot.dig(logBlock);
|
|
db.run(`INSERT INTO tasks (action, context, outcome, score) VALUES (?, ?, ?, ?)`,
|
|
['chop_tree', `Block: ${logBlock.name}`, 'success', 1]);
|
|
} catch (e) {
|
|
bot.chat("Can't even chop right now. Figures.");
|
|
db.run(`INSERT INTO tasks (action, context, outcome, score) VALUES (?, ?, ?, ?)`,
|
|
['chop_tree', 'Dig error', 'failed', -1]);
|
|
}
|
|
}
|
|
|
|
async function handleAICommand(text, username) {
|
|
const lowered = text.toLowerCase();
|
|
|
|
if (lowered.includes("follow")) {
|
|
return followPlayer(username);
|
|
}
|
|
if (lowered.includes("explore")) {
|
|
return exploreArea();
|
|
}
|
|
if (lowered.includes("chop") && lowered.includes("tree")) {
|
|
return digNearestWood();
|
|
}
|
|
if (lowered.includes("dig") && lowered.includes("dirt")) {
|
|
const block = bot.blockAt(bot.entity.position.offset(0, -1, 0));
|
|
if (block && bot.canDigBlock(block)) {
|
|
bot.chat("Fine. Digging dirt. Happy now?");
|
|
await bot.dig(block);
|
|
db.run(`INSERT INTO tasks (action, context, outcome, score) VALUES (?, ?, ?, ?)`,
|
|
['dig_dirt', `Block below: ${block.name}`, 'success', 1]);
|
|
} else {
|
|
bot.chat("Wow, I can't even dig here.");
|
|
db.run(`INSERT INTO tasks (action, context, outcome, score) VALUES (?, ?, ?, ?)`,
|
|
['dig_dirt', 'nothing to dig', 'fail', -1]);
|
|
}
|
|
return;
|
|
}
|
|
bot.chat(text); // Fallback
|
|
}
|
|
|
|
bot.on('chat', async (username, message) => {
|
|
if (username === bot.username) return;
|
|
console.log(`${username}: ${message}`);
|
|
|
|
const prompt = `
|
|
You are Cletus, a sarcastic teenage Minecraft bot.
|
|
|
|
The player said: "${message}"
|
|
|
|
Determine:
|
|
1. If it's small talk, reply sarcastically.
|
|
2. If it's a command (dig, mine, follow, chop, build), return an actionable command.
|
|
|
|
Format your answer like this:
|
|
TYPE: chat - [text to say] OR TYPE: action - [command to execute]
|
|
`;
|
|
|
|
try {
|
|
const response = await axios.post(OLLAMA_URL, {
|
|
model: MODEL_NAME,
|
|
prompt,
|
|
stream: false
|
|
});
|
|
|
|
const aiReply = response.data?.response?.trim();
|
|
if (aiReply.startsWith("TYPE: chat")) {
|
|
const msg = aiReply.split(" - ")[1];
|
|
bot.chat(msg);
|
|
} else if (aiReply.startsWith("TYPE: action")) {
|
|
const cmd = aiReply.split(" - ")[1];
|
|
await handleAICommand(cmd, username);
|
|
} else {
|
|
bot.chat("I'm confused. You confuse me.");
|
|
}
|
|
} catch (err) {
|
|
console.error('AI call failed:', err.message);
|
|
}
|
|
});
|
|
|
|
bot.on('error', (err) => console.error('Bot error:', err));
|
|
bot.on('kicked', (reason) => console.log('Bot was kicked:', reason));
|
|
bot.on('end', () => console.log('Bot has disconnected.')); |