UNPKG

mineflayer-schem

Version:

A builder plugin for Mineflayer that allows bots to build structures using schematics

204 lines (167 loc) 5.9 kB
const { goals, Movements } = require('../mineflayer-pathfinder'); const interactable = require('./lib/interactable.json'); const Vec3 = require('vec3'); const toolPlugin = require('mineflayer-tool').plugin; function wait(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } function inject(bot, options = {}) { if (!bot.pathfinder) { throw new Error('pathfinder must be loaded before builder'); } const mcData = require('minecraft-data')(bot.version); const Item = require('prismarine-item')(bot.version); const defaultOptions = { buildSpeed: 1.0, onError: 'pause', bots: [bot], }; const settings = { ...defaultOptions, ...options }; const movements = new Movements(bot, { maxDropDown: 256, maxClimbUp: 256, }); bot.pathfinder.setMovements(movements); bot.pathfinder.searchRadius = 10; bot.loadPlugin(toolPlugin); bot.builder = {}; let currentBuild = null; bot.builder.build = async (build) => { currentBuild = build; try { while (build.actions.length > 0) { if (build.isCancelled) { bot.emit('builder_cancelled'); break; } if (build.isPaused) { await wait(1000); continue; } const availableActions = build.getAvailableActions(); if (availableActions.length === 0) { break; } const bots = settings.bots.filter(b => b.pathfinder && b.pathfinder.movements); for (const bot of bots) { if (build.actions.length === 0) break; const action = build.actions[0]; try { if (action.type === 'place') { const item = build.getItemForState(action.state); const properties = build.properties[action.state]; const half = properties.half || properties.type; const faces = build.getPossibleDirections(action.state, action.pos); for (const face of faces) { const block = bot.blockAt(action.pos.plus(face)); } const { facing, is3D } = build.getFacing(action.state, properties.facing); const goal = new goals.GoalPlaceBlock(action.pos, bot.world, { faces, facing, facing3D: is3D, half, }); if (!goal.isEnd(bot.entity.position.floored())) { bot.pathfinder.setMovements(movements); await bot.pathfinder.goto(goal); } await equipToolForAction(action, item); const faceAndRef = goal.getFaceAndRef(bot.entity.position.floored().offset(0.5, 1.6, 0.5)); if (!faceAndRef) { throw new Error('no face and ref'); } bot.lookAt(faceAndRef.to, true); const refBlock = bot.blockAt(faceAndRef.ref); const sneak = interactable.indexOf(refBlock.name) > 0; if (sneak) bot.setControlState('sneak', true); await bot._placeBlockWithOptions(refBlock, faceAndRef.face.scaled(-1), { half, delta: faceAndRef.to.minus(faceAndRef.ref) }); if (sneak) bot.setControlState('sneak', false); const block = bot.world.getBlock(action.pos); if (block.stateId !== action.state) { throw new Error('Block placement failed'); } } else if (action.type === 'dig') { const block = bot.blockAt(action.pos); await bot.dig(block); } build.markActionComplete(action); build.removeAction(action); bot.emit('builder_progress', build.getProgress()); await wait(1000 / settings.buildSpeed); } catch (e) { bot.emit('builder_error', e); if (settings.onError === 'pause') { build.pause(); bot.emit('builder_paused'); break; } else if (settings.onError === 'cancel') { build.cancel(); bot.emit('builder_cancelled'); break; } } } } if (!build.isCancelled) { bot.emit('builder_finished'); } } catch (e) { bot.emit('builder_error', e); } finally { currentBuild = null; } }; async function equipToolForAction(action, item) { const block = bot.blockAt(action.pos); if (item && block) { await bot.tool.equipForBlock(block, {}); } } bot.builder.pause = () => { if (currentBuild) { currentBuild.pause(); bot.emit('builder_paused'); } }; bot.builder.resume = () => { if (currentBuild) { currentBuild.resume(); bot.emit('builder_resumed'); } }; bot.builder.cancel = () => { if (currentBuild) { currentBuild.cancel(); } }; bot.builder.getProgress = () => { if (currentBuild) { return currentBuild.getProgress(); } return null; }; bot.builder.chest = async (coords) => { const [x, y, z] = coords.split(',').map(coord => parseInt(coord.trim())); const chestPos = new Vec3(x, y, z); const chest = bot.blockAt(chestPos); if (chest.name !== 'chest') { return; } try { await bot.openChest(chest); const neededItems = currentBuild.schematic.palette.map(stateId => currentBuild.items[stateId]); for (const item of neededItems) { if (!bot.inventory.items().find(i => i.type === item.id)) { const chestItems = bot.inventory.slots.filter(slot => slot && slot.type === item.id); if (chestItems.length > 0) { await bot.tossStack(chestItems[0]); } } } await bot.closeInventory(); } catch (err) { bot.emit('builder_error', err); } }; } module.exports = { Build: require('./lib/build.js'), builder: inject, };