const { Movements } = require('mineflayer-pathfinder') const v = require('vec3') let cfg = {} let bot = {} // let moving let pathfinder let mcData let movements = [] function initMoves(bot = bot, mcData = bot.mcData) { if (movements.length > 0) { bot.pathfinder.setMovements(movements.defaultMove) return console.warn("go init: movements already initialized!", movements) } const normalMove = new Movements(bot, mcData) normalMove.canDig = false normalMove.scafoldingBlocks.push(mcData.blocksByName.slime_block.id) normalMove.blocksCantBreak.add(mcData.blocksByName.glass.id) normalMove.blocksToAvoid.add(mcData.blocksByName.magma_block.id) movements.push(normalMove) movements.defaultMove = movements[0] bot.pathfinder.setMovements(normalMove) } function moveNear(pos, distance = 3) { const { GoalNear } = require('mineflayer-pathfinder').goals pos = v(pos) cfg.quiet || bot.chat(`moving to ${pos.floored()}`) bot.pathfinder.setMovements(movements.defaultMove) bot.pathfinder.setGoal(new GoalNear(pos.x, pos.y, pos.z, distance)) } function moveXZ(pos) { const { GoalXZ } = require('mineflayer-pathfinder').goals if (Array.isArray(pos) && pos.length == 2) { pos = v(pos[0], 0, pos[1]) } pos = v(pos) console.log(pos) cfg.quiet || bot.chat(`moving to ${pos.floored()}`) bot.pathfinder.setMovements(movements.defaultMove) bot.pathfinder.setGoal(new GoalXZ(pos.x, pos.z)) } function moveY(pos) { const { GoalY } = require('mineflayer-pathfinder').goals if (Array.isArray(pos) && pos.length == 1) { pos = v(null, pos[0], null) } pos = v(pos) console.log(pos) cfg.quiet || bot.chat(`moving to ${pos.floored()}`) bot.pathfinder.setMovements(movements.defaultMove) bot.pathfinder.setGoal(new GoalY(pos.y)) } function follow(entity, dynamic = true, distance = 3) { console.assert(entity) const { GoalFollow } = require('mineflayer-pathfinder').goals // console.log(entity) cfg.quiet || bot.chat( `following ${entity.type }: ${entity.username || entity.displayName }${dynamic ? "" : " once"}` ) entity = entity.entity ? entity.entity : entity // console.log(entity) bot.pathfinder.setMovements(movements.defaultMove) bot.pathfinder.setGoal(new GoalFollow(entity, distance), dynamic) } function away(entity = bot.nearestEntity(), invertInvert = true, dynamic = true, distance = 10) { const currentGoal = bot.pathfinder.goal console.assert(currentGoal || entity) const { GoalInvert } = require('mineflayer-pathfinder').goals bot.pathfinder.setMovements(movements.defaultMove) if (!currentGoal) { const { GoalFollow } = require('mineflayer-pathfinder').goals if (entity.entity) { console.log("go away entity:", entity, entity.entity) entity = entity.entity } cfg.quiet || bot.chat( `going away from ${entity?.type }: ${entity?.username || entity?.displayName }${dynamic ? "" : " once"}` ) // alternative implementation // follow(entity, dynamic, distance) // bot.pathfinder.setGoal(new GoalInvert(bot.pathfinder.goal), dynamic) return bot.pathfinder.setGoal(new GoalInvert( new GoalFollow(entity, distance) ), dynamic) } if (currentGoal instanceof GoalInvert) { const currEntity = currentGoal.goal.entity console.log("go away inverse goal:", currentGoal.goal) if (invertInvert) { cfg.quiet || bot.chat( `switching towards ${currentGoal.goal?.constructor.name }: ${currEntity?.type }: ${currEntity?.username || currEntity?.displayName }${dynamic ? "" : " once"}` ) bot.pathfinder.setGoal(currentGoal.goal, dynamic) } else { cfg.quiet || bot.chat( `already going away from ${currentGoal.goal?.constructor.name }; not switching` ) } } else { const currEntity = currentGoal.entity console.log("go away goal:", currentGoal) cfg.quiet || bot.chat( `going away from ${currentGoal?.constructor.name }: ${currEntity?.type }: ${currEntity?.username || currEntity?.displayName }${dynamic ? "" : " once"}` ) bot.pathfinder.setGoal(new GoalInvert(currentGoal), dynamic) } } function ride(entity) { entity = entity?.entity || entity const ridableMobs = ["Horse", "Donkey", "Pig", "Strider", "Mule"] const vehicle = entity && typeof entity !== "string" ? entity : bot.nearestEntity(e => { if (typeof entity === "string") return e.name === entity const maybeRidableMob = e.mobType?.split(" ") return e.kind == "Vehicles" || ridableMobs.includes(e.mobType) || maybeRidableMob && ridableMobs.includes(maybeRidableMob[maybeRidableMob.length - 1]) }) if (!vehicle) { return cfg.quiet || bot.chat(`nothing to ride!`) } else if ((dist = bot.entity.position.distanceSquared(vehicle.position)) > 36) { bot.lookAt(vehicle.position) follow(vehicle, false) bot.once('goal_reached', ride) return cfg.quiet || bot.chat(`${vehicle.name} bit far`) } console.log("vehicle:", vehicle) bot.mount(vehicle) } function moveOrRide(turn = false, reverse = -1, directionLabel, message_parts2) { // bot.once("attach", state = "vehiccel") if (bot.vehicle) { // FIXME moveVehicle should be +-1 or 0? const amount = parseInt(message_parts2[0]) * -reverse bot.moveVehicle(turn && Math.sign(amount) || 0, !turn && amount || 0) } else { command([directionLabel].concat(message_parts2)) } } function hit(blockOrEntity) { bot.chat(`hitting ${entity.name || entity.type}`) } function goalReached(goal) { console.log(goal) const entity = goal?.entity let entityInfo = "" if (entity) { entityInfo += entity.type + ": " switch (entity.type) { case "player": entityInfo += entity.username break; default: break; } } cfg.quiet || bot.chat(`goal reached: ${entityInfo}; pos: [x:${goal?.x}, y:${goal?.y}, z:${goal?.z}]`) } function stop() { bot.pathfinder.setGoal(null) bot.stopDigging() } function command(message_parts, player) { const message_parts2 = message_parts.slice(1) switch (message_parts[0]) { case "init": initMoves() break case "near": switch (message_parts2.length) { case 0: moveNear(bot.nearestEntity().position) break case 1: switch (message_parts2[0]) { case "me": if (player) { moveNear(player.position) } else { cfg.quiet || bot.chat("can't see you") } break; default: const aPlayer = bot.players[message_parts2[0]] ? bot.players[message_parts2[0]].entity : null if (aPlayer) { moveNear(aPlayer.position) } else { cfg.quiet || bot.chat(`can't see ${message_parts2[0]}`) } break; } break case 2: //TODO this isn't near moveXZ(message_parts2) break case 3: //TODO more checks moveNear(message_parts2) break default: break } break case "follow": // message_parts2 = message_parts.slice(2) switch (message_parts2.length) { case 0: follow(bot.nearestEntity()) break case 1: let dist = 3 switch (message_parts2[0]) { case "close": dist = 1 case "me": case "once": if (player) { follow(player, message_parts2[0] === "me", dist) } else { cfg.quiet || bot.chat("can't see you") } break; default: const aPlayer = bot.players[message_parts2[0]] ? bot.players[message_parts2[0]].entity : null if (aPlayer) { follow(aPlayer) } else { cfg.quiet || bot.chat(`can't see ${message_parts2[0]}`) } break; } break // case 2: // bot.lookAt({}) goalxz? // break // case 3: //TODO more checks // moveNear(message_parts2) // break default: cfg.quiet || bot.chat("unknown or bad command") break } break case "ride": case "mount": ride(message_parts2[0]) break case "away": case "run": case "runaway": away() break case "w": case "f": moveOrRide(0, -1, "forward", message_parts2) break case "s": case "b": moveOrRide(0, 1, "back", message_parts2) break case "a": case "l": moveOrRide(1, -1, "right", message_parts2) break case "d": case "r": moveOrRide(1, 1, "left", message_parts2) break case "up": case "u": case "j": moveOrRide(1, 1, "left", message_parts2) break case "back": case "forward": case "jump": case "left": case "right": case "sneak": case "sprint": console.info(bot.controlState[message_parts[0]], bot.entity.position.floored()) bot.setControlState(message_parts[0], true) console.info(bot.controlState[message_parts[0]]) setTimeout(bot.setControlState, 100 * (message_parts[1] || 2), message_parts[0], false) setTimeout(console.info, 5000, bot.controlState[message_parts[0]], bot.entity.position.floored()) break case "stop": stop() break default: return cfg.quiet || bot.chat(`unknown command ${message_parts[0]}`) } } const load = (config) => { cfg = config bot = cfg.bot cfg.move = { // auto: true, canDig: false, // list: ["hello", "wassup"], quiet: !!cfg.quiet, movements: [] } mcData = bot.mcData || (bot.mcData = require('minecraft-data')(bot.version)) pathfinder = bot.pathfinder || bot.loadPlugin(require('mineflayer-pathfinder').pathfinder) // initMoves(bot, mcData) setTimeout(initMoves, 500, bot, mcData) bot.on('goal_reached', goalReached) } const unload = () => { stop() bot.off('goal_reached', goalReached) } module.exports = { load, unload, command, stop, initMoves, moveNear, moveXZ, moveY, follow, ride }