| const fs = require("fs"); |
| const express = require("express"); |
| const bodyParser = require("body-parser"); |
| const mineflayer = require("mineflayer"); |
|
|
| const skills = require("./lib/skillLoader"); |
| const {initCounter, getNextTime} = require("./lib/utils"); |
| const obs = require("./lib/observation/base"); |
| const OnChat = require("./lib/observation/onChat"); |
| const OnError = require("./lib/observation/onError"); |
| const {Voxels, BlockRecords} = require("./lib/observation/voxels"); |
| const Status = require("./lib/observation/status"); |
| const Inventory = require("./lib/observation/inventory"); |
| const OnSave = require("./lib/observation/onSave"); |
| const Chests = require("./lib/observation/chests"); |
| const {plugin: tool} = require("mineflayer-tool"); |
|
|
| const {pathfinder, Movements, goals} = require('mineflayer-pathfinder') |
| const {GoalXZ, GoalBlock} = goals |
|
|
| |
| const mineflayerViewer = require('prismarine-viewer').mineflayer |
|
|
| let bot = null; |
|
|
| const app = express(); |
|
|
| app.use(bodyParser.json({limit: "50mb"})); |
| app.use(bodyParser.urlencoded({limit: "50mb", extended: false})); |
|
|
| app.post("/start", (req, res) => { |
| if (bot) onDisconnect("Restarting bot"); |
| bot = null; |
| console.log(req.body); |
| bot = mineflayer.createBot({ |
| host: "localhost", |
| port: req.body.port, |
| username: `bot_${PORT}`, |
| disableChatSigning: true, |
| checkTimeoutInterval: 60 * 60 * 1000, |
| }); |
| bot.once("error", onConnectionFailed); |
|
|
| |
| bot.waitTicks = req.body.waitTicks; |
| bot.globalTickCounter = 0; |
| bot.stuckTickCounter = 0; |
| bot.stuckPosList = []; |
| bot.iron_pickaxe = false; |
|
|
| bot.on("kicked", onDisconnect); |
|
|
| |
| bot.on("mount", () => { |
| bot.dismount(); |
| }); |
|
|
| bot.once("spawn", async () => { |
| if (VISUAL_SERVER_PORT !== "-1") { |
| console.log("Initializing Mineflayer viewer..."); |
| mineflayerViewer(bot, {firstPerson: true, port: Number(VISUAL_SERVER_PORT)}); |
| console.log("Mineflayer viewer initialized."); |
| } |
| bot.removeListener("error", onConnectionFailed); |
| let itemTicks = 1; |
| if (req.body.reset === "hard") { |
| bot.chat("/clear @s"); |
| bot.chat("/kill @s"); |
| const inventory = req.body.inventory ? req.body.inventory : {}; |
| const equipment = req.body.equipment |
| ? req.body.equipment |
| : [null, null, null, null, null, null]; |
| for (let key in inventory) { |
| bot.chat(`/give @s minecraft:${key} ${inventory[key]}`); |
| itemTicks += 1; |
| } |
| const equipmentNames = [ |
| "armor.head", |
| "armor.chest", |
| "armor.legs", |
| "armor.feet", |
| "weapon.mainhand", |
| "weapon.offhand", |
| ]; |
| for (let i = 0; i < 6; i++) { |
| if (i === 4) continue; |
| if (equipment[i]) { |
| bot.chat( |
| `/item replace entity @s ${equipmentNames[i]} with minecraft:${equipment[i]}` |
| ); |
| itemTicks += 1; |
| } |
| } |
| } |
|
|
| if (req.body.position) { |
| bot.chat( |
| `/tp @s ${req.body.position.x} ${req.body.position.y} ${req.body.position.z}` |
| ); |
| } |
|
|
| |
| |
| |
| |
| |
| |
|
|
| const {pathfinder} = require("mineflayer-pathfinder"); |
| const tool = require("mineflayer-tool").plugin; |
| const collectBlock = require("mineflayer-collectblock").plugin; |
| const pvp = require("mineflayer-pvp").plugin; |
| const minecraftHawkEye = require("minecrafthawkeye"); |
| bot.loadPlugin(pathfinder); |
| bot.loadPlugin(tool); |
| bot.loadPlugin(collectBlock); |
| bot.loadPlugin(pvp); |
| bot.loadPlugin(minecraftHawkEye); |
|
|
| |
| |
|
|
| obs.inject(bot, [ |
| OnChat, |
| OnError, |
| Voxels, |
| Status, |
| Inventory, |
| OnSave, |
| Chests, |
| BlockRecords, |
| ]); |
| skills.inject(bot); |
|
|
| if (req.body.spread) { |
| bot.chat(`/spreadplayers ~ ~ 0 300 under 80 false @s`); |
| await bot.waitForTicks(bot.waitTicks); |
| } |
|
|
| await bot.waitForTicks(bot.waitTicks * itemTicks); |
| res.json(bot.observe()); |
|
|
| initCounter(bot); |
| bot.chat("/gamerule keepInventory true"); |
| bot.chat("/gamerule doDaylightCycle false"); |
| }); |
|
|
| function onConnectionFailed(e) { |
| console.log(e); |
| bot = null; |
| res.status(400).json({error: e}); |
| } |
|
|
| function onDisconnect(message) { |
| if (bot.viewer) { |
| bot.viewer.close(); |
| } |
| bot.end(); |
| console.log(message); |
| bot = null; |
| } |
| }); |
|
|
| app.post("/step", async (req, res) => { |
| |
| let response_sent = false; |
|
|
| function otherError(err) { |
| console.log("Uncaught Error"); |
| bot.emit("error", handleError(err)); |
| bot.waitForTicks(bot.waitTicks).then(() => { |
| if (!response_sent) { |
| response_sent = true; |
| res.json(bot.observe()); |
| } |
| }); |
| } |
|
|
| process.on("uncaughtException", otherError); |
|
|
| const mcData = require("minecraft-data")(bot.version); |
| mcData.itemsByName["leather_cap"] = mcData.itemsByName["leather_helmet"]; |
| mcData.itemsByName["leather_tunic"] = |
| mcData.itemsByName["leather_chestplate"]; |
| mcData.itemsByName["leather_pants"] = |
| mcData.itemsByName["leather_leggings"]; |
| mcData.itemsByName["leather_boots"] = mcData.itemsByName["leather_boots"]; |
| mcData.itemsByName["lapis_lazuli_ore"] = mcData.itemsByName["lapis_ore"]; |
| mcData.blocksByName["lapis_lazuli_ore"] = mcData.blocksByName["lapis_ore"]; |
| const { |
| Movements, |
| goals: { |
| Goal, |
| GoalBlock, |
| GoalNear, |
| GoalXZ, |
| GoalNearXZ, |
| GoalY, |
| GoalGetToBlock, |
| GoalLookAtBlock, |
| GoalBreakBlock, |
| GoalCompositeAny, |
| GoalCompositeAll, |
| GoalInvert, |
| GoalFollow, |
| GoalPlaceBlock, |
| }, |
| pathfinder, |
| Move, |
| ComputedPath, |
| PartiallyComputedPath, |
| XZCoordinates, |
| XYZCoordinates, |
| SafeBlock, |
| GoalPlaceBlockOptions, |
| } = require("mineflayer-pathfinder"); |
| const {Vec3} = require("vec3"); |
|
|
| |
| const movements = new Movements(bot, mcData); |
| bot.pathfinder.setMovements(movements); |
|
|
| bot.globalTickCounter = 0; |
| bot.stuckTickCounter = 0; |
| bot.stuckPosList = []; |
|
|
| function onTick() { |
| bot.globalTickCounter++; |
| if (bot.pathfinder.isMoving()) { |
| bot.stuckTickCounter++; |
| if (bot.stuckTickCounter >= 100) { |
| onStuck(1.5); |
| bot.stuckTickCounter = 0; |
| } |
| } |
| } |
|
|
| bot.on("physicTick", onTick); |
|
|
| |
| let _craftItemFailCount = 0; |
| let _killMobFailCount = 0; |
| let _mineBlockFailCount = 0; |
| let _placeItemFailCount = 0; |
| let _smeltItemFailCount = 0; |
|
|
| |
| const code = req.body.code; |
| const programs = req.body.programs; |
| bot.cumulativeObs = []; |
| await bot.waitForTicks(bot.waitTicks); |
| const r = await evaluateCode(code, programs); |
| process.off("uncaughtException", otherError); |
| if (r !== "success") { |
| bot.emit("error", handleError(r)); |
| } |
| await returnItems(); |
| |
| await bot.waitForTicks(bot.waitTicks); |
| if (!response_sent) { |
| response_sent = true; |
| res.json(bot.observe()); |
| } |
| bot.removeListener("physicTick", onTick); |
|
|
| async function evaluateCode(code, programs) { |
| |
| try { |
| await eval("(async () => {" + programs + "\n" + code + "})()"); |
| return "success"; |
| } catch (err) { |
| return err; |
| } |
| } |
|
|
| function onStuck(posThreshold) { |
| const currentPos = bot.entity.position; |
| bot.stuckPosList.push(currentPos); |
|
|
| |
| if (bot.stuckPosList.length === 5) { |
| const oldestPos = bot.stuckPosList[0]; |
| const posDifference = currentPos.distanceTo(oldestPos); |
|
|
| if (posDifference < posThreshold) { |
| teleportBot(); |
| } |
|
|
| |
| bot.stuckPosList.shift(); |
| } |
| } |
|
|
| function teleportBot() { |
| const blocks = bot.findBlocks({ |
| matching: (block) => { |
| return block.type === 0; |
| }, |
| maxDistance: 1, |
| count: 27, |
| }); |
|
|
| if (blocks) { |
| |
| const randomIndex = Math.floor(Math.random() * blocks.length); |
| const block = blocks[randomIndex]; |
| bot.chat(`/tp @s ${block.x} ${block.y} ${block.z}`); |
| } else { |
| bot.chat("/tp @s ~ ~1.25 ~"); |
| } |
| } |
|
|
| function returnItems() { |
| bot.chat("/gamerule doTileDrops false"); |
| const crafting_table = bot.findBlock({ |
| matching: mcData.blocksByName.crafting_table.id, |
| maxDistance: 128, |
| }); |
| if (crafting_table) { |
| bot.chat( |
| `/setblock ${crafting_table.position.x} ${crafting_table.position.y} ${crafting_table.position.z} air destroy` |
| ); |
| bot.chat("/give @s crafting_table"); |
| } |
| const furnace = bot.findBlock({ |
| matching: mcData.blocksByName.furnace.id, |
| maxDistance: 128, |
| }); |
| if (furnace) { |
| bot.chat( |
| `/setblock ${furnace.position.x} ${furnace.position.y} ${furnace.position.z} air destroy` |
| ); |
| bot.chat("/give @s furnace"); |
| } |
| if (bot.inventoryUsed() >= 32) { |
| |
| if (!bot.inventory.items().find((item) => item.name === "chest")) { |
| bot.chat("/give @s chest"); |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| bot.chat("/gamerule doTileDrops true"); |
| } |
|
|
| function handleError(err) { |
| let stack = err.stack; |
| if (!stack) { |
| return err; |
| } |
| console.log(stack); |
| const final_line = stack.split("\n")[1]; |
| const regex = /<anonymous>:(\d+):\d+\)/; |
|
|
| const programs_length = programs.split("\n").length; |
| let match_line = null; |
| for (const line of stack.split("\n")) { |
| const match = regex.exec(line); |
| if (match) { |
| const line_num = parseInt(match[1]); |
| if (line_num >= programs_length) { |
| match_line = line_num - programs_length; |
| break; |
| } |
| } |
| } |
| if (!match_line) { |
| return err.message; |
| } |
| let f_line = final_line.match( |
| /\((?<file>.*):(?<line>\d+):(?<pos>\d+)\)/ |
| ); |
| if (f_line && f_line.groups && fs.existsSync(f_line.groups.file)) { |
| const {file, line, pos} = f_line.groups; |
| const f = fs.readFileSync(file, "utf8").split("\n"); |
| |
| let source = file + `:${line}\n${f[line - 1].trim()}\n `; |
|
|
| const code_source = |
| "at " + |
| code.split("\n")[match_line - 1].trim() + |
| " in your code"; |
| return source + err.message + "\n" + code_source; |
| } else if ( |
| f_line && |
| f_line.groups && |
| f_line.groups.file.includes("<anonymous>") |
| ) { |
| const {file, line, pos} = f_line.groups; |
| let source = |
| "Your code" + |
| `:${match_line}\n${code.split("\n")[match_line - 1].trim()}\n `; |
| let code_source = ""; |
| if (line < programs_length) { |
| source = |
| "In your program code: " + |
| programs.split("\n")[line - 1].trim() + |
| "\n"; |
| code_source = `at line ${match_line}:${code |
| .split("\n") |
| [match_line - 1].trim()} in your code`; |
| } |
| return source + err.message + "\n" + code_source; |
| } |
| return err.message; |
| } |
| }); |
|
|
| app.post("/stop", (req, res) => { |
| bot.end(); |
| res.json({ |
| message: "Bot stopped", |
| }); |
| }); |
|
|
| app.post("/pause", (req, res) => { |
| if (!bot) { |
| res.status(400).json({error: "Bot not spawned"}); |
| return; |
| } |
| bot.chat("/pause"); |
| bot.waitForTicks(bot.waitTicks).then(() => { |
| res.json({message: "Success"}); |
| }); |
| }); |
|
|
| |
| const PORT = process.argv[2]; |
| const VISUAL_SERVER_PORT = process.argv[3]; |
| app.listen(PORT, () => { |
| console.log(`Server started on port ${PORT}`); |
| }); |
|
|