'use strict'; const {sleep} = require('../utils'); const mineflayer = require('mineflayer'); const minecraftData = require('minecraft-data'); const { pathfinder, Movements, goals: { GoalNear } } = require('mineflayer-pathfinder'); const Vec3 = require('vec3'); class MCAction{ static Vec3 = Vec3 isLocked = false actions = {}; currentAction = false; constructor(cjbot){ this.cjbot = cjbot; this.bot = this.cjbot.bot; this.__onReady(); this.cjbot.on('onReady', this.__onReady.bind(this)); } async __onReady(){ this.bot.loadPlugin(pathfinder); this.mcData = minecraftData(this.bot.version); this.defaultMove = new Movements(this.bot, this.mcData); this.defaultMove.canDig = false this.bot.pathfinder.setMovements(this.defaultMove); } __blockOrVec(thing){ if(thing instanceof Vec3.Vec3) return this.bot.blockAt(thing); if(thing.constructor && thing.constructor.name === 'Block') return thing; throw new Error('Not supported block identifier'); } actionAdd(name, obj){ if(this.actions[name]) throw new Error('Action already exists'); if(!obj.function && typeof obj.function !== "function") throw new Error('Action must have a function') this.actions[name] = {name, ...obj}; return true; } actionGoTo(action, reTry){ return new Promise(async(resolve, reject)=>{ let range = reTryCount ? 10 + (action.range || 0) : action.range; try{ await this.goto(action.where, range) return reTry ? await this.actionGoTo(action) : resolve(); }catch(error){ if(reTry) return reject('Action can not move to where') await this.actionGoTo(action, true); } }); } async action(name, ...args){ if(!this.actions[name]) throw new Error('Action not found.'); let action = this.actions[name]; console.log('action', name) if(action.skip){ if(action.skip === true && await action.until.call({...action, ...this}))return true; if(typeof action.skip === 'function' && await action.skip.call({...action, ...this})) return true; } let handler = async (resolve, reject)=>{ let clear = false; let error = false; if(action.where) await this.goto(action.where, action.range) if(action.timeout !== false){ clear = setTimeout( async reject =>{ if(this.bot.currentWindow){ try{ console.log('found open widow on timeout') await this.bot.closeWindow(this.bot.currentWindow); }catch(error){ console.log('error on close window timeout', error) } } reject('Action timed out.'); }, action.timeout || 10000, reject ); } console.log('doing') try { var res = await action.function.call({...action, ...this}, ...args);} catch(error){ error = error; } if(clear) clearInterval(clear); if(action.until && !await action.until.call({...action, ...this})){ if(action.untilCoolDown){ console.log('sleeping for until') await sleep(action.untilCoolDown) } console.log('until not met, running agian') return handler(resolve, reject) } return error ? reject(error) : resolve(res); } await (new Promise(handler)); } routines = {} currentRoutine = false; async routine(name, ...args){ if(!this.routines[name]) throw new Error('Routine not found.'); let routine = this.routines[name]; let state = routine.state = { run: true, step: 0, completeCount: 0, } while(true){ let action = this.actions[routine.actions[state.step]]; // console.log('action', action, routine.actions[state.step]) try{ await this.action(action.name); }catch(error){ console.log(action.name, 'error', error) if(routine.onStepError){ routine.onStepError(step) } } if(state.step++ == routine.actions.length-1){ state.step = 0; state.completeCount++; if(routine.coolDown || routine.stepCoolDown) await sleep(routine.coolDown || routine.stepCoolDown); }else{ if(routine.stepCoolDown) await sleep(routine.stepCoolDown); } } } addRoutine(name, obj){ if(this.routines[name]) throw new Error('Action already exists'); // if(!obj.function && typeof obj.function !== "function") throw new Error('Action must have a function') this.routines[name] = obj; return true; } inventoryCount(block){ if(Number.isInteger(Number(block))) block = Number(block) else block = this.mcData.itemsByName[block].id return this.bot.inventory.count(block); } async goto(block, range=2){ block = this.__blockOrVec(block); return await this.bot.pathfinder.goto(new GoalNear(...block.position.toArray(), range)); } async __nextContainerSlot(window, item) { let firstEmptySlot = false; await window.containerItems(); for(let slot in window.slots.slice(0, window.inventoryStart)){ if(window.slots[slot] === null ){ if(!Number.isInteger(firstEmptySlot)) firstEmptySlot = Number(slot); continue; } if(item.type === window.slots[slot].type && window.slots[slot].count < window.slots[slot].stackSize){ return slot; } } return firstEmptySlot; } async put(block, blockName, amount) { block = this.__blockOrVec(block); this.bot.openContainer(block); let window = await this.cjbot.once('windowOpen'); for(let item of window.slots.slice(window.inventoryStart).filter(function(item){ if(!item) return false; if(blockName && blockName !== item.name) return false; return true; })){ let currentSlot = Number(item.slot); if(!window.slots[currentSlot]) continue; if(amount && !amount--) return; let chestSlot = await this.__nextContainerSlot(window, item); await this.bot.moveSlotItem(currentSlot, chestSlot) let res = await this.put(...arguments); if(res === false) return amount ? amount : false; } await this.bot.closeWindow(window); return amount ? amount : true; } async __nextInventorySlot(window, item) { let firstEmptySlot = false; for(let idx in window.slots.slice(window.inventoryStart)){ let currentItem = window.slots[Number(idx)+window.inventoryStart] if(currentItem === null){ if(!Number.isInteger(firstEmptySlot)) firstEmptySlot = Number(idx)+window.inventoryStart; continue; } if(currentItem.type === item.type && item.count < item.stackSize){ return currentItem.slot; } } return firstEmptySlot; } async get(block, blockName, amount) { block = this.__blockOrVec(block); // Open the chest this.bot.openContainer(block); let window = await this.cjbot.once('windowOpen'); for(let item of await window.containerItems()){ console.log('in get') if(item.slot > window.inventoryStart) break; let currentSlot = Number(item.slot); if(!window.slots[currentSlot]) continue; if(amount && !amount--) break; let inventorySlot = await this.__nextInventorySlot(window, item); await this.bot.moveSlotItem(currentSlot, inventorySlot) // let res = await this.get(...arguments); // if(res === false) return amount ? amount : false; } await this.bot.closeWindow(window); return amount ? amount : true; } async trade(villagerID, tradeID, amount){ return await trade(this, villagerID, tradeID, amount) } getNearVillagers(distance=4){ const villagers = Object.keys(this.bot.entities) .map(id => this.bot.entities[id]) .filter(e => e.entityType === this.mcData.entitiesByName.villager.id); const closeVillagersId = villagers .filter(e => this.bot.entity.position.distanceTo(e.position) < distance) return closeVillagersId; } } // Trade helper functions // I did NOT write this non-sens. I did have to hack it to *sometimes* work // https://github.com/PrismarineJS/mineflayer/blob/a0befeb042fe3851ac35887da116c2910f505791/examples/trader.js function trade (actionBot, id, index, count) { function hasResources (window, trade, count) { const first = enough(trade.inputItem1, count) const second = !trade.hasItem2 || enough(trade.secondaryInput, count) return first && second function enough (item, count) { return true; return window.count(item.type, item.metadata) >= item.count * count } } return new Promise(async(resolve, reject)=>{ const bot = actionBot.bot const e = bot.entities[id] switch (true) { case !e: console.log(`cant find entity with id ${id}`) break case e.entityType !== actionBot.mcData.entitiesByName.villager.id: console.log('entity is not a villager') break case bot.entity.position.distanceTo(e.position) > 3: console.log('villager out of reach') break default: { let villager; let timeout = setTimeout(async(resolve, villager)=>{ console.log('villager', villager ? villager : 'no villager loaded') console.log('trade Promise timeout reject'); if(villager) try{ await villager.close() }catch(error){ console.error('villager close error', error) try{ if(bot.currentWindow) await bot.currentWindow.close(); }catch(error){ } } resolve(); }, 5000, resolve, villager); try{ console.log('getting villager') villager = await bot.openVillager(e) console.log('have villager') }catch(error){ clearTimeout(timeout) return reject(error) } const trade = villager.trades[index] count = count || trade.maxTradeuses - trade.tooluses switch (true) { case !trade: console.log('trade not found') villager.close() break case trade.inputItem1.name !== 'paper': console.log('villager does not have paper') villager.close() case trade.tradeDisabled: console.log('trade is disabled') villager.close() break // case trade.maxTradeuses - trade.tooluses < count: // villager.close() // console.log('cant trade that often') // break; case !hasResources(villager.window, trade, count): villager.close() console.log('dont have the resources to do that trade') break default: console.log('starting to trade') try { await bot.trade(villager, index, count) console.log(`traded ${count} times`) } catch (err) { clearTimeout(timeout); return reject(err) } await villager.close(); } clearTimeout(timeout); } } return resolve(); }) } module.exports = {MCAction, Vec3};