diff --git a/chat.js b/chat.js deleted file mode 100644 index 6e249db..0000000 --- a/chat.js +++ /dev/null @@ -1,29 +0,0 @@ - -const chatStack = []; -let messageWaitTime = 2000; - -const start = (waitTime) => { - if(waitTime) messageWaitTime = waitTime; - if(chatStack.length > 0) { - let info = chatStack.shift(); - console.log(`${info.bot.player.username} sending>>>`, info.message); - info.bot.chat(info.message); - } - setTimeout(start.bind(this), messageWaitTime); -} - -const addChat = (bot, message, directTo) => { - message.split('\n').forEach(message => { - if(!directTo) { - chatStack.push({bot, message}); - } else { - chatStack.push({bot, message: `/msg ${directTo} ` + message}); - } - }); - -} - -module.exports = { - start, - addChat -} \ No newline at end of file diff --git a/chat.ts b/chat.ts new file mode 100644 index 0000000..986d892 --- /dev/null +++ b/chat.ts @@ -0,0 +1,35 @@ +import { Bot } from "mineflayer"; + +interface Message { + bot: Bot; + message: string; +} + +export class ChatBuffer { + private chatStack: Message[] = []; + private messageWaitTime: number; + + constructor(messageWaitTime: number = 2000) { + this.messageWaitTime = messageWaitTime; + } + + public start() { + if (this.chatStack.length > 0) { + let info = this.chatStack.shift() as Message; + console.log(`${info.bot.player.username} sending>>>`, info.message); + info.bot.chat(info.message); + } + setTimeout(this.start.bind(this), this.messageWaitTime); + } + + public addChat(bot: Bot, message: string, directTo: string | null = null) { + message.split('\n').forEach(message => { + if (!directTo) { + this.chatStack.push({ bot, message }); + } else { + this.chatStack.push({ bot, message: `/msg ${directTo} ` + message }); + } + }); + + } +} \ No newline at end of file diff --git a/followBot.js b/followBot.js deleted file mode 100644 index e0c3e9b..0000000 --- a/followBot.js +++ /dev/null @@ -1,40 +0,0 @@ -const mineflayer = require('mineflayer') -const pathfinder = require('mineflayer-pathfinder').pathfinder -const Movements = require('mineflayer-pathfinder').Movements -const { GoalNear } = require('mineflayer-pathfinder').goals -const bot = mineflayer.createBot({ username: 'pathfinder', host: 'localhost', port: 53925, version: '1.16.4' }) - - -bot.on('kicked', console.log); -bot.on('error', console.log); - -bot.loadPlugin(pathfinder) -let done = false; - -bot.once('spawn', () => { - - const mcData = require('minecraft-data')(bot.version) - bot.chat("Hello"); - - const defaultMove = new Movements(bot, mcData) - - - - bot.on('chat', function(username, message) { - - if (username === bot.username) return - - const target = bot.players[username] ? bot.players[username].entity : null - if (message === 'come') { - if (!target) { - bot.chat('I don\'t see you !') - return - } - const p = target.position - - bot.pathfinder.setMovements(defaultMove) - bot.pathfinder.setGoal(new GoalNear(p.x, p.y, p.z, 1)) - - }; -}); -}); diff --git a/index.js b/index.ts similarity index 62% rename from index.js rename to index.ts index 5fd51de..71b8010 100644 --- a/index.js +++ b/index.ts @@ -1,103 +1,104 @@ +import { IndexedData } from "minecraft-data"; +import { Bot } from "mineflayer"; +import { Behaviours } from "./utils"; +import { ChatBuffer } from "./chat"; +import { Individual } from "./individual"; +import { Swarm, SwarmConfig } from "./swarm"; + const mineflayer = require('mineflayer'); const { Movements } = require('mineflayer-pathfinder'); -const { GoalNear, GoalBlock, GoalXZ, GoalY, GoalInvert, GoalFollow } = require('mineflayer-pathfinder').goals; -const { createSwarm } = require('./swarm'); -const chat = require('./chat'); -const jobSelector = require('./individual').handleChat; -const {Behaviours} = require('./dist/utils'); const utils = new Behaviours(); -let botNames = [ - // 'Annie', - // 'Baldwin', - // 'Claire', +const chat = new ChatBuffer(); +const botNames: string[] = [ 'QuailBotherer' ]; -const host = process.argv[2]; -const port = parseInt(process.argv[3], 10); -let password = process.argv[4]; -const masters = [process.argv[5]]; +const host: string = process.argv[2]; +const port: number = parseInt(process.argv[3], 10); +const password: string = process.argv[4]; +const masters: string[] = [process.argv[5]]; console.log(`Starting: Bots: ${botNames}, ${host}:${port}. Controllers: ${masters}. Pass set = ${!!password}`); -function sleep(ms) { +function sleep(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)); } -const autoLogin = async (bot) => { +const autoLogin = async (bot: Bot) => { await sleep(3000); console.log("Auto logging in..."); chat.addChat(bot, `/register ${password} ${password}`); chat.addChat(bot, `/login ${password}`); }; -const newBots = (names) => { - const newBots = createSwarm(names, config, mineflayer); - console.log(`Created ${names} (${newBots.map(nb => nb.username)}). Waiting for spawns..`); +const newBots = (names: string[]) => { + const newBots = Swarm.Create(names, botConfig); + console.log(`Created ${names} (${newBots.map((nb: Bot) => nb.username)}). Waiting for spawns..`); let counter = 0; - newBots.forEach(bot => { + newBots.forEach((bot: Bot) => { bot.once('spawn', () => { counter++; console.log("Spawned", bot.username, `(${counter}/${newBots.length})`); if(counter === newBots.length) { - newBots.forEach(b => swarm.push(b)); - console.log("All bots now:", swarm.map(b => b.username)); + newBots.forEach((b: Bot) => swarm.push(b)); + console.log("All bots now:", swarm.map((b: Bot) => b.username)); } }); }); } -const botInit = (bot) => { +const botInit = (bot: Bot) => { console.log(`[${bot.username}] Loading plugins...`); bot.loadPlugins([require('mineflayer-pathfinder').pathfinder, require('mineflayer-armor-manager'), require('mineflayer-blockfinder')(mineflayer)]); console.log(bot.username, 'initalised'); // Once we've spawn, it is safe to access mcData because we know the version - const mcData = require('minecraft-data')(bot.version); + const mcData: IndexedData = require('minecraft-data')(bot.version); prepFriendlyProtection(mcData, swarm); const defaultMove = new Movements(bot, mcData); defaultMove.allowFreeMotion = true - bot.on('chat', (username, message, x, y, z) => { + const individual: Individual = new Individual(bot, chat); + bot.on('chat', (username: string, message: string) => { console.log(`[Msg>${bot.username}]`, username, message); try { - jobSelector(username, message, bot, masters, chat, false, newBots) + individual.handleChat(username, message, bot, masters, false, newBots) } catch (err) { console.log(err); chat.addChat(bot, `Can't, sorry`, username); } }); - bot.on('whisper', (username, message) => { + bot.on('whisper', (username: string, message: string) => { console.log(`[Whisper>${bot.username}]`, username, message); try { - jobSelector(username, message, bot, masters, chat, true, newBots) + individual.handleChat(username, message, bot, masters, true, newBots) } catch (err) { console.log(err); chat.addChat(bot, `Can't, sorry`, username); } }); - bot.on("end", (reason) => { + bot.on("end", (reason: string) => { console.warn(`${bot.player.username} disconnected! (${reason})`); }); - const startTime = Date.now(); + const startTime: number = Date.now(); bot.on('health', () => { if (Date.now() - startTime < 500) return; - utils.attackNearestMob(bot, defaultMove) + utils.attackNearestMob(bot, defaultMove, () => {}); }); - bot.on('kicked', (reason) => console.log("kicked", reason)); - bot.on('error', console.log); + bot.on('kicked', (reason: string) => console.log("kicked", reason)); + bot.on('error', console.error); autoLogin(bot); masters.forEach(master => { chat.addChat(bot, `I'm online`, master); }); -};; +}; let haveSetupProtection = false; -const prepFriendlyProtection = (mcData, swarm) => { +const prepFriendlyProtection = (mcData: IndexedData, swarm: Bot[]) => { if (haveSetupProtection) return; swarm[swarm.length - 1].once('spawn', () => { swarm.forEach(bot => { @@ -123,7 +124,7 @@ const prepFriendlyProtection = (mcData, swarm) => { haveSetupProtection = true; } -const config = { +const botConfig: SwarmConfig = { host, port, version: '1.20.1', @@ -131,4 +132,4 @@ const config = { }; chat.start(); -const swarm = createSwarm(botNames, config, mineflayer); \ No newline at end of file +const swarm = Swarm.Create(botNames, botConfig); \ No newline at end of file diff --git a/individual.js b/individual.js deleted file mode 100644 index 7509976..0000000 --- a/individual.js +++ /dev/null @@ -1,292 +0,0 @@ -const { Vec3 } = require('vec3'); -const Movements = require('mineflayer-pathfinder').Movements -const { Behaviours } = require('./dist/utils'); - -const utils = new Behaviours(); - -const movementCallback = (returnAddress, bot, chat, target, successful) => { - const announcement = successful ? `:)` : `I can't get there`; - chat.addChat(bot, announcement, returnAddress); -} - -let stopLearn = null; -const handleChat = (username, message, bot, masters, chat, isWhisper = false, createBotCallback = null) => { - if (username === bot.username || !masters.includes(username)) return; - - // insert bot name for whispers, if not present, for easier parsing - if (!message.startsWith(bot.username)) message = bot.username + ' ' + message; - const returnAddress = isWhisper ? username : null; // used for direct response or global chat depending on how we were spoken to - - const messageParts = message.split(' '); - let messageFor = messageParts.shift(); - if (messageFor != bot.username && messageFor != 'swarm') return; - console.log("Command:", username, messageFor, messageParts); - - let target = bot.players[username].entity; - const mcData = require('minecraft-data')(bot.version) - const defaultMove = new Movements(bot, mcData); - switch (messageParts[0]) { - case 'come': - chat.addChat(bot, 'coming', returnAddress); - utils.goToTarget(bot, target, defaultMove, 0, (success) => { - movementCallback(returnAddress, bot, chat, target, success); - }); - break; - case 'follow': - if (messageParts.length > 1) { - let player = bot.players[messageParts[1]] - if (player) { - target = player.entity; - } else { - chat.addChat(bot, "No-one is called " + messageParts[1], returnAddress); - return; - } - } - utils.follow(bot, target, defaultMove); - chat.addChat(bot, 'ok', returnAddress); - break; - case 'avoid': - if (messageParts.length > 1) { - let player = bot.players[messageParts[1]] - if (player) { - target = player.entity; - } else { - chat.addChat(bot, "No-one is called " + messageParts[1], returnAddress); - return; - } - } - utils.avoid(bot, target, defaultMove); - chat.addChat(bot, 'ok', returnAddress); - break; - case 'shift': - utils.shift(bot, defaultMove); - break; - case 'stay': - case 'stop': - utils.stop(bot); - chat.addChat(bot, 'ok', returnAddress); - break; - case 'info': - chat.addChat(bot, utils.info(bot, messageParts), returnAddress); - break; - case 'hole': - utils.hole(bot, messageParts, defaultMove, (msg) => { - chat.addChat(bot, msg, returnAddress); - }); - break; - case 'nearby': - chat.addChat(bot, utils.nearbyBlocks(bot).join(', '), returnAddress); - break; - case 'inventory': - chat.addChat(bot, utils.inventoryAsString(bot, bot.inventory.items()), returnAddress); - break; - case 'equip': - if (messageParts.length == 1) { - chat.addChat(bot, "equip what?", returnAddress); - return; - } - utils.equipByNameDescriptive(bot, messageParts[1], mcData, (msg) => { - chat.addChat(bot, msg, returnAddress); - }); - break; - case 'drop': - case 'gimme': - if (messageParts.length < 3) { - chat.addChat(bot, "drop how much of what?", returnAddress); - return; - } - utils.tossItem(bot, messageParts[2], messageParts[1], username, (msg) => { - chat.addChat(bot, msg, returnAddress); - });; - break; - case 'harvest': - if (messageParts.length == 1) { - chat.addChat(bot, "Harvest how much of what!?", returnAddress); - return; - } - utils.harvest(bot, messageParts[2], defaultMove, parseInt(messageParts[1], 10), mcData, (msg) => { - chat.addChat(bot, msg, returnAddress); - }); - break; - case 'collect': - utils.collectDrops(bot, defaultMove, 30, () => chat.addChat(bot, "Everything's collected", returnAddress)); - break; - case 'hunt': - chat.addChat(bot, "I'm off hunting", returnAddress); - utils.hunt(bot, defaultMove, parseInt(messageParts[1], 30), 30, () => { - chat.addChat(bot, 'finished hunting', returnAddress); - }); - break; - case 'protect': - chat.addChat(bot, "I'm on it", returnAddress); - utils.attackNearestMob(bot, defaultMove, (msg) => { - chat.addChat(bot, msg, returnAddress); - }); - break; - case 'goto': - case 'go': - let goto = (messageParts) => { - if (messageParts.length > 3) { - let x = parseInt(messageParts[1], 10); - let y = parseInt(messageParts[2], 10); - let z = parseInt(messageParts[3], 10); - chat.addChat(bot, "Ok, on my way", target); - utils.goToTarget(bot, { position: { x, y, z } }, defaultMove, 0, (success) => { - movementCallback(returnAddress, bot, chat, target, success); - }); - } else { - let player = bot.players[messageParts[1]] - if (player) { - chat.addChat(bot, "Ok, I'll find 'em", target); - target = player.entity; - } else { - if (messageParts[1] == 'home') { - let homePos = utils.getHome(bot); - if (homePos) { - target = { position: homePos }; - } else { - chat.addChat(bot, "I'm homeless, I've got no home to go to", returnAddress); - return; - } - } else { - chat.addChat(bot, "No-one is called " + messageParts[1], returnAddress); - return; - } - } - utils.goToTarget(bot, target, defaultMove, 1, (success) => { - movementCallback(returnAddress, bot, chat, target, success); - }); - } - }; - goto(messageParts); - break; - case 'move': - let move = (messageParts) => { - let x = parseInt(messageParts[1], 10); - let y = parseInt(messageParts[2], 10); - let z = parseInt(messageParts[3], 10); - let targetPos = { x, y, z }; - utils.goToTarget(bot, { position: bot.entity.position.add(targetPos) }, defaultMove, 0, (success) => { - movementCallback(returnAddress, bot, chat, target, success); - }); - }; - move(messageParts); - break; - case 'learn': - utils.learn(bot, target, console.log); - break; - case 'recite': - utils.finishLearn(bot); - break; - case 'craft': - let itemName = messageParts[1]; - let friendlyItemName = itemName.split("_").join(" "); - let amount = messageParts.length > 2 ? parseInt(messageParts[2]) : 1; - let craftingTableBlockInfo = utils.nameToBlock('crafting_table', mcData); - - let craftingTable = bot.findBlockSync({ - matching: craftingTableBlockInfo.id, - point: bot.entity.position - })[0]; - - utils.goToTarget(bot, craftingTable, defaultMove, 2, (arrivedSuccessfully) => { - if (!arrivedSuccessfully && craftingTable != null) return chat.addChat(bot, `Couldn't get to the crafting table`, returnAddress); - utils.craft(bot, itemName, mcData, amount, craftingTable, (err) => { - if (err) { - chat.addChat(bot, `Couldn't make a ${friendlyItemName}`, returnAddress); - console.log(err); - } else { - chat.addChat(bot, `Made the ${friendlyItemName}${amount > 1 && !friendlyItemName.endsWith("s") ? "s" : ""}`, returnAddress); - } - }); - }); - break; - case 'sethome': - utils.setHome(bot, bot.entity.position); - chat.addChat(bot, "Homely!", returnAddress); - break; - case 'where': - chat.addChat(bot, bot.entity.position.toString(), returnAddress); - break; - case 'say': - messageParts.shift(); - const msgToSend = messageParts.join(' '); - chat.addChat(bot, `Ok I'll say "${msgToSend}"`, username); - console.log('repeat', msgToSend); - chat.addChat(bot, msgToSend, null); - break; - case 'use': - bot.activateItem(); - break; - case 'disuse': - bot.deactivateItem(); - break; - case 'empty': - utils.emptyNearestChest(bot, 7, () => { chat.addChat(bot, 'Emptied the chest', returnAddress) }); - break; - case 'fill': - utils.emptyNearestChest(bot, 7, () => { chat.addChat(bot, 'Filled the chest', returnAddress), true }); - break; - case 'place': - // place, block, x, y, z - // 0 1 2 3 4 - if (messageParts.length !== 5) return chat.addChat(bot, 'place what block where?!', returnAddress); - const position = new Vec3(Number(messageParts[2]), Number(messageParts[3]), Number(messageParts[4])); - const blockToPlace = messageParts[1]; - utils.getAdjacentTo(bot, { position }, defaultMove, () => { - utils.placeBlockAt(bot, position, blockToPlace, mcData, () => { - chat.addChat(bot, `Placed the ${blockToPlace}`, returnAddress); - }); - }); - break; - case 'portal': - // portal, (bl)x, y, z, (tr)x, y, z, (opt)blockname - // 0 1 2 3 4 5 6 7 - if (messageParts.length < 7) return chat.addChat(bot, 'portal from where to where?!', returnAddress); - const bottomLeft = new Vec3(Number(messageParts[1]), Number(messageParts[2]), Number(messageParts[3])); - const topRight = new Vec3(Number(messageParts[4]), Number(messageParts[5]), Number(messageParts[6])); - const portalBlockName = messageParts.length === 8 ? messageParts[7] : 'obsidian'; - //return console.log(portalBlockName, messageParts, messageParts.length); - const portalBlock = utils.nameToBlock(portalBlockName, mcData); - defaultMove.blocksCantBreak.add(portalBlock.id); - utils.goToTarget(bot, { position: bottomLeft }, defaultMove, 5, () => { - utils.buildPortal(bot, bottomLeft, topRight, mcData, defaultMove, portalBlockName, () => { - chat.addChat(bot, `Built the ${portalBlockName} portal`, returnAddress); - defaultMove.blocksCantBreak.delete(portalBlock.id); - }); - }); - break; - case 'new': - if (!createBotCallback) { - console.warn("Tried to create new bot(s) but no callback provided"); - chat.addChat(bot, `Can't right now`, returnAddress); - break; - } - messageParts.shift(); - // Allow for " ", "," and ", " separated names - const names = messageParts.reduce((prev, curr) => { - return [...prev, ...curr.split(",").map(n => n.trim())]; - }, []); - createBotCallback(names); - break; - case 'sleep': - utils.sleep(bot, (msg) => { - chat.addChat(bot, msg, returnAddress); - }); - break; - case 'worldspawn': - chat.addChat(bot, `Heading to spawn`, returnAddress); - utils.goToTarget(bot, { position: bot.spawnPoint }, defaultMove, 2, returnAddress); - break; - case 'torch': - chat.addChat(bot, `Torch? ${utils.shouldPlaceTorch(bot)}`, returnAddress); - break; - default: - chat.addChat(bot, 'What do you mean?', returnAddress); - return; - } -}; - -module.exports = { - handleChat -}; \ No newline at end of file diff --git a/individual.ts b/individual.ts new file mode 100644 index 0000000..332412f --- /dev/null +++ b/individual.ts @@ -0,0 +1,298 @@ +import { Bot } from "mineflayer"; +import { ChatBuffer } from "./chat"; +import { Behaviours } from "./utils"; +import { Movements } from "mineflayer-pathfinder"; +import { IndexedData } from "minecraft-data"; +import { Vec3 } from "vec3"; + +export class Individual { + private utils: Behaviours; + private chat: ChatBuffer; + private bot: Bot; + private defaultMove: Movements; + private mcData: IndexedData; + + constructor(bot: Bot, chat: ChatBuffer) { + this.utils = new Behaviours(); + this.chat = chat; + this.bot = bot; + this.mcData = require('minecraft-data')(this.bot.version); + this.defaultMove = new Movements(this.bot); + } + + private movementCallback(returnAddress: string | null, successful: boolean) { + const announcement = successful ? `:)` : `I can't get there`; + this.chat.addChat(this.bot, announcement, returnAddress); + } + + private goto(messageParts: string[], returnAddress: string | null): void { + if (messageParts.length > 3) { + let x = parseInt(messageParts[1], 10); + let y = parseInt(messageParts[2], 10); + let z = parseInt(messageParts[3], 10); + this.chat.addChat(this.bot, "Ok, on my way", returnAddress); + this.utils.goToTarget(this.bot, { position: { x, y, z } }, this.defaultMove, 0, (success) => { + this.movementCallback(returnAddress, success); + }); + } else { + let player = this.bot.players[messageParts[1]] + let target: { position: Vec3 } = player.entity; + if (player) { + this.chat.addChat(this.bot, "Ok, I'll find 'em", returnAddress); + } else { + if (messageParts[1] == 'home') { + let homePos = this.utils.getHome(this.bot); + if (homePos) { + target = { position: homePos }; + } else { + this.chat.addChat(this.bot, "I'm homeless, I've got no home to go to", returnAddress); + return; + } + } else { + this.chat.addChat(this.bot, "No-one is called " + messageParts[1], returnAddress); + return; + } + } + this.utils.goToTarget(this.bot, target, this.defaultMove, 1, (success) => { + this.movementCallback(returnAddress, success); + }); + } + }; + + public handleChat(username: string, message: string, bot: Bot, masters: string[], + isWhisper: boolean = false, createBotCallback: ((names: string[]) => void) | null = null) { + if (username === this.bot.username || !masters.includes(username)) return; + + // insert bot name for whispers, if not present, for easier parsing + if (!message.startsWith(this.bot.username)) message = this.bot.username + ' ' + message; + const returnAddress = isWhisper ? username : null; // used for direct response or global chat depending on how we were spoken to + + const messageParts = message.split(' '); + let messageFor = messageParts.shift(); + if (messageFor != this.bot.username && messageFor != 'swarm') return; + console.log("Command:", username, messageFor, messageParts); + + let target = this.bot.players[username].entity; + switch (messageParts[0]) { + case 'come': + this.chat.addChat(this.bot, 'coming', returnAddress); + this.utils.goToTarget(this.bot, target, this.defaultMove, 0, (success: boolean) => { + this.movementCallback(returnAddress, success); + }); + break; + case 'follow': + if (messageParts.length > 1) { + let player = this.bot.players[messageParts[1]] + if (player) { + target = player.entity; + } else { + this.chat.addChat(this.bot, "No-one is called " + messageParts[1], returnAddress); + return; + } + } + this.utils.follow(this.bot, target, this.defaultMove); + this.chat.addChat(this.bot, 'ok', returnAddress); + break; + case 'avoid': + if (messageParts.length > 1) { + let player = this.bot.players[messageParts[1]] + if (player) { + target = player.entity; + } else { + this.chat.addChat(this.bot, "No-one is called " + messageParts[1], returnAddress); + return; + } + } + this.utils.avoid(this.bot, target, this.defaultMove); + this.chat.addChat(this.bot, 'ok', returnAddress); + break; + case 'shift': + this.utils.shift(this.bot, this.defaultMove); + break; + case 'stay': + case 'stop': + this.utils.stop(bot); + this.chat.addChat(this.bot, 'ok', returnAddress); + break; + case 'info': + this.chat.addChat(this.bot, this.utils.info(this.bot, messageParts), returnAddress); + break; + case 'hole': + this.utils.hole(this.bot, messageParts, this.defaultMove, (msg: string) => { + this.chat.addChat(this.bot, msg, returnAddress); + }); + break; + case 'nearby': + this.chat.addChat(this.bot, this.utils.nearbyBlocks(bot).join(', '), returnAddress); + break; + case 'inventory': + this.chat.addChat(this.bot, this.utils.inventoryAsString(this.bot), returnAddress); + break; + case 'equip': + if (messageParts.length == 1) { + this.chat.addChat(this.bot, "equip what?", returnAddress); + return; + } + this.utils.equipByNameDescriptive(this.bot, messageParts[1], this.mcData, (msg: string) => { + this.chat.addChat(this.bot, msg, returnAddress); + }); + break; + case 'drop': + case 'gimme': + if (messageParts.length < 3) { + this.chat.addChat(this.bot, "drop how much of what?", returnAddress); + return; + } + this.utils.tossItem(this.bot, messageParts[2], messageParts[1], username, (msg: string) => { + this.chat.addChat(this.bot, msg, returnAddress); + });; + break; + case 'harvest': + if (messageParts.length == 1) { + this.chat.addChat(this.bot, "Harvest how much of what!?", returnAddress); + return; + } + this.utils.harvest(this.bot, messageParts[2], this.defaultMove, parseInt(messageParts[1], 10), this.mcData, (msg: string) => { + this.chat.addChat(this.bot, msg, returnAddress); + }); + break; + case 'collect': + this.utils.collectDrops(this.bot, this.defaultMove, 30, () => this.chat.addChat(this.bot, "Everything's collected", returnAddress)); + break; + case 'hunt': + this.chat.addChat(this.bot, "I'm off hunting", returnAddress); + this.utils.hunt(this.bot, this.defaultMove, parseInt(messageParts[1], 30), 30, () => { + this.chat.addChat(this.bot, 'finished hunting', returnAddress); + }); + break; + case 'protect': + this.chat.addChat(this.bot, "I'm on it", returnAddress); + this.utils.attackNearestMob(this.bot, this.defaultMove, (msg: string) => { + this.chat.addChat(this.bot, msg, returnAddress); + }); + break; + case 'goto': + case 'go': + this.goto(messageParts, returnAddress); + break; + case 'move': + let move = (messageParts: string[]) => { + const x = parseInt(messageParts[1], 10); + const y = parseInt(messageParts[2], 10); + const z = parseInt(messageParts[3], 10); + this.utils.goToTarget(this.bot, { position: this.bot.entity.position.add(new Vec3(x, y, z)) }, this.defaultMove, 0, (success) => { + this.movementCallback(returnAddress, success); + }); + }; + move(messageParts); + break; + case 'craft': + const itemName = messageParts[1]; + const friendlyItemName = itemName.split("_").join(" "); + const amount = messageParts.length > 2 ? parseInt(messageParts[2]) : 1; + const craftingTableBlockInfo = this.utils.nameToBlock('crafting_table', this.mcData); + + const craftingTablePos = this.bot.findBlocks({ + matching: craftingTableBlockInfo.id, + point: this.bot.entity.position + })[0]; + + this.utils.goToTarget(this.bot, {position: craftingTablePos}, this.defaultMove, 2, (arrivedSuccessfully) => { + if (!arrivedSuccessfully && craftingTablePos != null) return this.chat.addChat(this.bot, `Couldn't get to the crafting table`, returnAddress); + this.utils.craft(this.bot, itemName, this.mcData, amount, bot.blockAt(craftingTablePos), (err) => { + if (err) { + this.chat.addChat(this.bot, `Couldn't make a ${friendlyItemName}`, returnAddress); + console.log(err); + } else { + this.chat.addChat(this.bot, `Made the ${friendlyItemName}${amount > 1 && !friendlyItemName.endsWith("s") ? "s" : ""}`, returnAddress); + } + }); + }); + break; + case 'sethome': + this.utils.setHome(this.bot, this.bot.entity.position); + this.chat.addChat(this.bot, "Homely!", returnAddress); + break; + case 'where': + this.chat.addChat(this.bot, this.bot.entity.position.toString(), returnAddress); + break; + case 'say': + messageParts.shift(); + const msgToSend = messageParts.join(' '); + this.chat.addChat(this.bot, `Ok I'll say "${msgToSend}"`, username); + console.log('repeat', msgToSend); + this.chat.addChat(this.bot, msgToSend, null); + break; + case 'use': + this.bot.activateItem(); + break; + case 'disuse': + this.bot.deactivateItem(); + break; + case 'empty': + this.utils.emptyNearestChest(this.bot, 7, () => { this.chat.addChat(this.bot, 'Emptied the chest', returnAddress) }); + break; + case 'fill': + this.utils.emptyNearestChest(this.bot, 7, () => { this.chat.addChat(this.bot, 'Filled the chest', returnAddress), true }); + break; + case 'place': + // place, block, x, y, z + // 0 1 2 3 4 + if (messageParts.length !== 5) return this.chat.addChat(this.bot, 'place what block where?!', returnAddress); + const position = new Vec3(Number(messageParts[2]), Number(messageParts[3]), Number(messageParts[4])); + const blockToPlace = messageParts[1]; + this.utils.getAdjacentTo(this.bot, { position }, this.defaultMove, () => { + this.utils.placeBlockAt(this.bot, position, blockToPlace, this.mcData, () => { + this.chat.addChat(this.bot, `Placed the ${blockToPlace}`, returnAddress); + }); + }); + break; + case 'portal': + // portal, (bl)x, y, z, (tr)x, y, z, (opt)blockname + // 0 1 2 3 4 5 6 7 + if (messageParts.length < 7) return this.chat.addChat(this.bot, 'portal from where to where?!', returnAddress); + const bottomLeft = new Vec3(Number(messageParts[1]), Number(messageParts[2]), Number(messageParts[3])); + const topRight = new Vec3(Number(messageParts[4]), Number(messageParts[5]), Number(messageParts[6])); + const portalBlockName = messageParts.length === 8 ? messageParts[7] : 'obsidian'; + //return console.log(portalBlockName, messageParts, messageParts.length); + const portalBlock = this.utils.nameToBlock(portalBlockName, this.mcData); + this.defaultMove.blocksCantBreak.add(portalBlock.id); + this.utils.goToTarget(this.bot, { position: bottomLeft }, this.defaultMove, 5, () => { + this.utils.buildPortal(this.bot, bottomLeft, topRight, this.mcData, this.defaultMove, portalBlockName, () => { + this.chat.addChat(this.bot, `Built the ${portalBlockName} portal`, returnAddress); + this.defaultMove.blocksCantBreak.delete(portalBlock.id); + }); + }); + break; + case 'new': + if (!createBotCallback) { + console.warn("Tried to create new bot(s) but no callback provided"); + this.chat.addChat(this.bot, `Can't right now`, returnAddress); + break; + } + messageParts.shift(); + // Allow for " ", "," and ", " separated names + const names: string[] = []; + messageParts.forEach((curr: string) => { + names.push(...curr.split(",").map(n => n.trim())); + }); + createBotCallback(names); + break; + case 'sleep': + this.utils.sleep(this.bot, (msg: string) => { + this.chat.addChat(this.bot, msg, returnAddress); + }); + break; + case 'worldspawn': + this.chat.addChat(this.bot, `Heading to spawn`, returnAddress); + this.utils.goToTarget(this.bot, { position: this.bot.spawnPoint }, this.defaultMove, 2, (success) => this.movementCallback(returnAddress, success)); + break; + case 'torch': + this.chat.addChat(this.bot, `Torch? ${this.utils.shouldPlaceTorch(bot)}`, returnAddress); + break; + default: + this.chat.addChat(this.bot, 'What do you mean?', returnAddress); + return; + } + }; +} \ No newline at end of file diff --git a/single-example.js b/single-example.js deleted file mode 100644 index 804e64d..0000000 --- a/single-example.js +++ /dev/null @@ -1,409 +0,0 @@ -const mineflayer = require('mineflayer') -const pathfinder = require('mineflayer-pathfinder').pathfinder -const Movements = require('mineflayer-pathfinder').Movements -const { GoalNear, GoalBlock, GoalXZ, GoalY, GoalInvert, GoalFollow } = require('mineflayer-pathfinder').goals -let mcData; - -const bot = mineflayer.createBot({ - host: process.argv[2], - port: parseInt(process.argv[3]), - username: process.argv[4] ? process.argv[4] : 'botfren', - password: process.argv[5] -}); - -bot.loadPlugin(pathfinder) - -bot.once('spawn', () => { - - mcData = require('minecraft-data')(bot.version) - - const defaultMove = new Movements(bot, mcData); - - bot.on("health", () => { - const hostiles = Object.values(bot.entities) - .filter(entity => entity.kind === 'Hostile mobs') - .sort((mobA, mobB) => { - return (mobA.position.distanceTo(bot.entity.position) - mobB.position.distanceTo(bot.entity.position)); - }); - - if (hostiles.length > 0) { - const hostile = hostiles[0]; - if (hostile.position.distanceTo(bot.entity.position) < 10) { - kill(defaultMove, [hostile], () => bot.chat("Sorted myself out")); - } - } - }); - - bot.on('chat', function (username, message) { - - if (username === bot.username) return - const pass = "pal "; - if (!message.startsWith(pass)) { - return; - } - message = message.split(pass)[1]; - const messageParts = message.split(' '); - - const target = bot.players[username] ? bot.players[username].entity : null - - switch (messageParts[0]) { - case 'come': - goToTarget(target, defaultMove); - break; - case 'follow': - follow(target, defaultMove); - break; - case 'stop': - stop(); - break; - case 'info': - info(messageParts); - break; - case 'hole': - hole(messageParts, defaultMove); - break; - case 'nearby': - bot.chat(nearbyBlocks().join(', ')); - break; - case 'inventory': - sayItems(bot.inventory.items()); - break; - case 'equip': - if (messageParts.length == 1) { - bot.chat("equip what?"); - return; - } - equipByName(messageParts[1]); - break; - case 'drop': - if (messageParts.length < 3) { - bot.chat("drop how much of what?!"); - return; - } - tossItem(messageParts[2], messageParts[1], username); - break; - case 'harvest': - if (messageParts.length == 1) { - bot.chat("Harvest how much of what!?"); - return; - } - harvest(messageParts[2], defaultMove, parseInt(messageParts[1], 10)); - break; - case 'collect': - collectDrops(defaultMove, null, 30, () => bot.chat("Everything's collected")); - break; - case 'hunt': - bot.chat("It's open season, yeehaw!"); - hunt(defaultMove, parseInt(messageParts[1], 30)); - break; - default: - bot.chat('I don\'t understand'); - return; - } - bot.chat("okeydoke fren"); - - bot.on('error', console.log); - }); -}); - -function hunt(movement, amount, maxDist = 30) { - const mobs = Object.values(bot.entities) - .filter(entity => entity.kind === 'Passive mobs') - .filter(mob => !['squid', 'horse', 'salmon', 'wolf'].includes(mob.name)) - .filter(mob => mob.position.distanceTo(bot.entity.position) < maxDist) - .sort((mobA, mobB) => { - return (mobA.position.distanceTo(bot.entity.position) - mobB.position.distanceTo(bot.entity.position)); - }).slice(0, amount); - kill(movement, mobs, () => { - bot.chat(`Finished hunting`); - }); -} - -function kill(movement, mobs, cb) { - if (mobs.length == 0) return cb(); - const tool = bestTool({ material: 'flesh' }); - if (tool) { - bot.equip(tool, 'hand'); - } - const mob = mobs.shift(); - - bot.lookAt(mob.position); - - follow(mob, movement); - const attackLoop = () => { - if (mob.isValid) { - bot.attack(mob); - setTimeout(attackLoop, 100); - } else { - collectDrops(movement, null, 10, () => { - setImmediate(kill.bind(this, movement, mobs, cb)); - }); - } - } - bot.chat("Stalking a " + mob.name); - attackLoop(); -} - -function collectDrops(movement, drops, maxDist, cb) { - if (!drops) drops = Object.values(bot.entities) - .filter(entity => entity.kind === 'Drops') - .filter(drop => drop.position.distanceTo(bot.entity.position) < maxDist) - .sort((dropA, dropB) => { - return (dropA.position.distanceTo(bot.entity.position) - dropB.position.distanceTo(bot.entity.position)); - }); - if (drops.length == 0) { - cb(); - return; - } - goToTarget(drops.shift(), movement, 0, () => { - setImmediate(collectDrops.bind(this, movement, drops, maxDist, cb)); - }); -} - -function harvest(blockName, movement, amount) { - if (amount <= 0) return bot.chat(`I collected all the ${blockName} you asked for`); - - const lookupBlock = mcData.blocksByName[blockName]; - if (!lookupBlock) return bot.chat(`What's a ${blockName}?`); - const id = lookupBlock.id; - - let block = bot.findBlock({ - matching: id, - point: bot.entity.position, - maxDistance: 30 - }); - - if (!block) { - bot.chat(`Can't see any more ${blockName} nearby`); - return; - } else { - bot.lookAt(block.position); - goToTarget(block, movement, 2, () => { - digBlockAt(block.position, () => { - collectDrops(movement, null, 5, () => { - harvest(blockName, movement, --amount); - }); - }); - }); - } -} - -function itemByName(name) { - return bot.inventory.items().filter(item => item.name === name)[0]; -} - -function tossItem(name, amount, toPerson) { - bot.lookAt(bot.players[toPerson].entity.position, false, () => { - amount = parseInt(amount, 10); - const item = itemByName(name); - if (!item) { - bot.chat(`I have no ${name}`); - } else if (amount) { - bot.toss(item.type, null, amount, checkIfTossed); - } else { - bot.tossStack(item, checkIfTossed); - } - - function checkIfTossed(err) { - if (err) { - bot.chat(`might be a few short of what you wanted`); - } else { - bot.chat(`dropped the ${name}`); - } - } - }); -} - -function nearbyBlocks(maxDist = 30) { - bot.chat('One sec, just counting blocks...'); - let nearbyBlocks = {}; - for (let y = maxDist * -1; y <= maxDist; y++) { - for (let x = maxDist * -1; x <= maxDist; x++) { - for (let z = maxDist * -1; z <= maxDist; z++) { - let block = bot.blockAt(bot.entity.position.offset(x, y, z)); - - if (bot.blockAt(block.position.offset(0, 1, 0)).name != 'air') continue; - if (block.name == 'air' || block.name == 'cave_air') continue; - nearbyBlocks[block.name] = nearbyBlocks[block.name] ? nearbyBlocks[block.name] + 1 : 1; - } - } - } - let names = Object.keys(nearbyBlocks); - let amounts = Object.values(nearbyBlocks); - return names.map((name, index) => { - return { name, amount: amounts[index] } - }).sort((x, y) => y.amount - x.amount).map(x => `${x.name}x${x.amount}`); -} - -function itemByNameIndex() { - let itemsByName - if (bot.supportFeature('itemsAreNotBlocks')) { - itemsByName = 'itemsByName' - } else if (bot.supportFeature('itemsAreAlsoBlocks')) { - itemsByName = 'blocksByName' - } - return itemsByName; -} - -function equipByName(name, output = true) { - const item = mcData[itemByNameIndex()][name]; - if (!item) return bot.chat(`Equip a ${name}? What do you mean?`); - - bot.equip(item.id, 'hand', (err) => { - if (err) { - if (output) bot.chat(`unable to equip ${name}, ${err.message}`); - return false; - } else { - if (output) bot.chat(`ok, got ${name}`); - return true; - } - }); -} - -function sayItems(items) { - const output = items.map(itemToString).join(', ') - if (output) { - bot.chat(output) - } else { - bot.chat('nothing') - } -} - -function itemToString(item) { - if (item) { - return `${item.name} x ${item.count}` - } else { - return '(nothing)' - } -} - -function hole(messageParts, defaultMove) { - if (messageParts.length < 2) { - bot.chat("How big though?"); - return; - } - // width, length, depth - const size = messageParts[1].split('x'); - bot.chat(size[0] + " along x, " + size[1] + " along z and " + size[2] + " deep - got it!"); - let offsets = { - x: Math.floor(Number(size[0]) / 2), - z: Math.floor(Number(size[1]) / 2), - y: Math.floor(Number(size[2])) - } - const positions = []; - for (let yO = 0; yO >= offsets.y * -1; yO--) { - for (let xO = offsets.x * -1; xO <= offsets.x; xO++) { - for (let zO = offsets.z * -1; zO <= offsets.z; zO++) { - positions.push(bot.entity.position.offset(xO, yO, zO)); - } - } - } - - digBlocksInOrder(positions, () => bot.chat("Finished my hole :)"), defaultMove); -} - -function digBlocksInOrder(positions, onComplete, defaultMove) { - if (positions == null || positions == undefined || positions.length == 0) return onComplete ? onComplete() : null; - - const nextPosition = positions.shift(); - goToTarget({ position: nextPosition }, defaultMove, 5, () => { - digBlockAt(nextPosition, digBlocksInOrder.bind(this, positions, onComplete, defaultMove)); - }); -} - -function bestTool(block) { - let tool = bot.pathfinder.bestHarvestTool(block); - if (tool) return tool; - - if (block.name == 'air') return; - const materials = ['wooden', 'stone', 'gold', 'iron', 'diamond']; - switch (block.material) { - case 'dirt': - return bestToolOfTypeInInv("shovel", materials); - case 'wood': - return bestToolOfTypeInInv("axe", materials); - case 'flesh': - case 'plant': - return bestToolOfTypeInInv("sword", materials); - case 'rock': - return bestToolOfTypeInInv("pickaxe", materials); - case undefined: - break; - default: - bot.chat("What's this material > " + block.material); - break; - } - return null; -} - -function bestToolOfTypeInInv(toolname, materials) { - const tools = materials.map(x => x + '_' + toolname); - for (let i = tools.length - 1; i >= 0; i--) { - const tool = tools[i]; - let matches = bot.inventory.items().filter(item => item.name === tool); - if (matches.length > 0) return matches[0]; - } - return null; -} - -function digBlockAt(position, onComplete) { - var target = bot.blockAt(position); - bot.lookAt(target.position); - const tool = bestTool(target); - - bot.equip(tool, 'hand', () => { - if (target && bot.canDigBlock(target) && target.name != 'air') { - bot.dig(target, onComplete) - } else { - if (onComplete) onComplete(); - } - }); -} - -function info(messageParts) { - const playerName = messageParts[1]; - bot.chat("Info about " + playerName); - - const player = bot.players[playerName]; - let info = null; - if (player) { - info = "Pos: " + player.entity.position + "\r\n"; - info += "Vel: " + player.entity.velocity; - } else { - info = 'No-one is called ' + playerName; - } - - bot.chat(info); -} - -function stop() { - bot.pathfinder.setGoal(null); -} - -function follow(target, movement) { - bot.pathfinder.setMovements(movement); - bot.pathfinder.setGoal(new GoalFollow(target, 3), true); -} - -function goToTarget(target, movement, dist = 0, cb) { - if (!target) { - bot.chat('I can\'t see there!'); - cb(); - return; - } - const p = target.position; - - bot.pathfinder.setMovements(movement); - const goal = new GoalNear(p.x, p.y, p.z, dist); - bot.pathfinder.setGoal(goal); - - const callbackCheck = () => { - if (goal.isEnd(bot.entity.position.floored())) { - cb(); - } else { - setTimeout(callbackCheck.bind(this), 1000); - } - }; - - if (cb) callbackCheck(); -} \ No newline at end of file diff --git a/swarm.js b/swarm.js deleted file mode 100644 index beb22e0..0000000 --- a/swarm.js +++ /dev/null @@ -1,25 +0,0 @@ - -/** - * Create a swarm with each bot having a name from the given array - * @param {[]} botNames A config item for each bot - * @param {mineflayer} mineflayer Mineflayer instance - * @returns {Swarm} The swarm - */ -const createSwarm = (botNames, botConf, mineflayer) => { - const initBot = (name) => { - const bot = mineflayer.createBot({ ...botConf, username: name}); - console.log(`Bot ${name} created`); - - bot.once('spawn', botConf.initCallback.bind(this, bot)); - return bot; - }; - - const bots = []; - botNames.forEach(name => bots.push(initBot(name))); - - return bots; -} - -module.exports = { - createSwarm: createSwarm -} \ No newline at end of file diff --git a/swarm.ts b/swarm.ts new file mode 100644 index 0000000..a920e3b --- /dev/null +++ b/swarm.ts @@ -0,0 +1,25 @@ +import { Bot, createBot } from "mineflayer"; + +export interface SwarmConfig { + host: string, + port: number, + version: string, + initCallback: (bot: Bot) => void +} + +export class Swarm { + public static Create(names: string[], config: SwarmConfig) { + const bots: Bot[] = []; + names.forEach(name => bots.push(Swarm.InitBot(name, config))); + + return bots; + } + + private static InitBot(name: string, config: SwarmConfig): Bot { + const bot = createBot({ ...config, username: name }); + console.log(`Bot ${name} created`); + + bot.once('spawn', config.initCallback.bind(this, bot)); + return bot; + } +} \ No newline at end of file diff --git a/utils.ts b/utils.ts index a3630e5..70a88d0 100644 --- a/utils.ts +++ b/utils.ts @@ -3,6 +3,7 @@ import { Bot, Chest } from "mineflayer"; import { Movements } from "mineflayer-pathfinder"; import { Block } from 'prismarine-block' import PrismarineEntity from 'prismarine-entity'; +import {Item as PrismarineItem} from 'prismarine-item'; import { Vec3 } from "vec3"; const Vec3Obj = require('vec3').Vec3; @@ -191,12 +192,12 @@ export class Behaviours { bot.equip(item.id, 'hand'); } - public inventoryAsString(bot: Bot, items: Item[]) { - const output = items.map(this.itemToString).join(', ') + public inventoryAsString(bot: Bot) { + const output = bot.inventory.items().map(this.itemToString).join(', ') return output ? output : 'nothing'; } - public itemToString(item: Item): string { + public itemToString(item: PrismarineItem): string { if (item) { return `${item.name} x ${item.count}` } else { @@ -328,7 +329,7 @@ export class Behaviours { bot.pathfinder.setGoal(new GoalXZ(pos.x + offsetX, pos.z + offsetZ), true); } - public goToTarget(bot: Bot, target: { position: Vec3 }, movement: Movements, dist: number, cb: (success: boolean) => void) { + public goToTarget(bot: Bot, target: { position: {x: number, y: number, z: number} }, movement: Movements, dist: number, cb: (success: boolean) => void) { if (!target) { if (cb) cb(false); return; @@ -412,14 +413,14 @@ export class Behaviours { } } - public craft(bot: Bot, itemName: string, mcData: IndexedData, amount = 1, craftingTable = undefined, craftComplete: null | ((msg: string | void) => void) = null) { + public craft(bot: Bot, itemName: string, mcData: IndexedData, amount = 1, craftingTable: Block | null = null, craftComplete: null | ((msg: string | void) => void) = null) { let recipes = this.getRecipe(bot, itemName, amount, mcData, craftingTable); if (!recipes || recipes.length === 0) { if (craftComplete) craftComplete(`I don't know the recipe for ${itemName}`); return; } if (recipes[0].inShape) recipes[0].inShape = recipes[0].inShape.reverse(); - bot.craft(recipes[0], amount, craftingTable).then(craftComplete ? craftComplete : () => { }); + bot.craft(recipes[0], amount, craftingTable ? craftingTable : undefined).then(craftComplete ? craftComplete : () => { }); } public getRecipe(bot: Bot, itemName: string, amount: number, mcData: IndexedData, craftingTable: Block | null = null) {