Files
mc-bot-town/nodejs/model/minecraft.js
T
2026-07-12 17:26:59 -04:00

957 lines
27 KiB
JavaScript

'use strict';
const mineflayer = require('mineflayer');
const minecraftData = require('minecraft-data');
const { pathfinder, Movements, goals: { GoalNear } } = require('mineflayer-pathfinder');
const Vec3 = require('vec3');
const {sleep} = require('../utils');
// The server sends entity_velocity packets with undefined fields (e.g.
// packet.velocity.x is undefined). This causes fromNotchVelocity to return
// Vec3(NaN,NaN,NaN), which corrupts the bot's velocity, which the physics tick
// uses to update position → NaN position → physics.js deadlocks permanently.
const _conv = require('mineflayer/lib/conversions');
const _fromNotchVelocity = _conv.fromNotchVelocity;
_conv.fromNotchVelocity = function(vel) {
if (!Number.isFinite(vel.x) || !Number.isFinite(vel.y) || !Number.isFinite(vel.z))
return new Vec3(0, 0, 0);
return _fromNotchVelocity(vel);
};
class CJbot{
isReady = false;
listeners = {};
// Holds the minimum cool down time for chat messages
nextChatTime = 1500;
// Prevents executing commands while one is running
commandLock = false;
commandCollDownTime = 1500;
// Holds the list of commands this bot can execute
commands = {};
combatTag = false;
doLogOut = false;
// Map of all the bots currently in use
static bots = {};
// Forces the code to wait while until the bot is loaded before moving on to
// the next
static __addLock = false;
// Holds a list of bots that still needs to be connect
static __toConnect = [];
constructor(args){
// Friendly name to access the bot in `CJbot.bots`
this.name = args.name || args.username;
this.username = args.username;
this.password = args.password;
this.host = args.host;
this.auth = args.auth || 'microsoft';
this.version = args.version || '1.20.1';
this.hasAi = args.hasAi;
//
this.pluginsWanted = args.plugins || {};
// States if the bot should connect when its loaded
this.autoReConnect = 'autoConnect' in args ? args.autoReConnect : true;
this.autoConnect = 'autoConnect' in args ? args.autoConnect : true;
// On-demand mode: bot stays offline until ensureConnected() is called
this.onDemand = args.onDemand || false;
this._idleTimer = null;
this._taskQueue = [];
this._connecting = false;
this._idleTimeout = args.idleTimeout || 30000;
this._goToLock = false;
// Bumped by interruptTask; in-flight goTo calls compare against the
// value they captured at start and bail out when it changes.
this._interruptGen = 0;
// If we want the be always connected, kick off the function to auto
// reconnect
if(this.autoReConnect && !this.onDemand) this.__autoReConnect()
}
connect(){
console.log('CJbot.connect');
return new Promise((resolve, reject) =>{
try{
this.bot = mineflayer.createBot({
host: this.host,
username: this.username,
password: this.password,
version: this.version,
auth: this.auth,
});
// If an error happens before the login event, toss an error back to
// the caller of the function
let onError = this.bot.on('error', (m)=>{
console.log('ERROR CJbot.connect on error:', this.name, m.toString());
reject(m);
})
// If the connection ends before the login event, toss an error back
// to the caller of the function
this.bot.on('end', (reason, ...args)=>{
console.log(this.name, 'Connection ended:', reason, ...args);
this.pluginUnloadAll(this.onDemand); // keepDb=true for on-demand
this.isReady = false;
reject(reason);
});
// When the bot is ready, return to the caller success
this.bot.on('spawn', async()=>{
console.log('CJbot.connect on spawn')
await sleep(2000);
this.__onReady();
resolve();
this._pluginsReady = this.pluginLoadAll();
});
}catch(error){
console.log('CJbot.connect Error', error);
reject(error);
}
});
}
// Wrap the once method so it works correctly with await
once(event){
return new Promise((resolve, reject)=> this.bot.once(event, resolve));
}
// Internal method to kick off the functionality after its loaded in the
// server
async __onReady(){try{
this.bot.loadPlugin(pathfinder);
this.mcData = minecraftData(this.bot.version);
this.defaultMove = new Movements(this.bot, this.mcData);
this.defaultMove.canDig = false;
this.defaultMove.scafoldingBlocks = [];
/*// Make pathfinder avoid routing through chests/shulkers
if (!this.defaultMove.blocksCost) this.defaultMove.blocksCost = {};
const avoidBlocks = ['chest', 'trapped_chest', 'ender_chest',
'shulker_box', 'white_shulker_box', 'orange_shulker_box',
'magenta_shulker_box', 'light_blue_shulker_box', 'yellow_shulker_box',
'lime_shulker_box', 'pink_shulker_box', 'gray_shulker_box',
'light_gray_shulker_box', 'cyan_shulker_box', 'purple_shulker_box',
'blue_shulker_box', 'brown_shulker_box', 'green_shulker_box',
'red_shulker_box', 'black_shulker_box'];
for (const name of avoidBlocks) {
const block = this.mcData.blocksByName[name];
if (block) this.defaultMove.blocksCost[block.id] = 100;
}*/
// 1. Disable sprinting globally. This is the #1 cause of clipping
// on high-TPS servers because the bot moves too fast for its own
// rotation speed.
this.defaultMove.allowSprinting = false;
this.defaultMove.entityCost = 100;
this.defaultMove.allow1by1towers = false;
// 3. Entity Intersections
// Set this to true to make the bot more aware of collision boxes
this.defaultMove.allowEntityDetection = true;
this.bot.pathfinder.setMovements(this.defaultMove);
// Add the listeners to the bot. We do this so if the bot loses
// connection, the mineflayer instance will also be lost.
this.isReady = true;
this.__error();
this.__startListeners();
// Call the internal listeners when the bot is ready
for(let callback of this.listeners.onReady || []){
callback.call(this);
}
console.log('Bot is ready', this.bot.entity.username, this.username);
// Start chat listeners
this.__listen();
this.bot.on('title', (...args)=>console.log('on title', args))
}catch(error){
console.error('minecraft.js | __onReady | ', this.name, ' ', error);
}}
__startListeners(){
for(let event in this.listeners){
console.log('__adding listeners', event)
for(let callback of this.listeners[event]){
this.bot.on(event, callback);
}
}
}
// Wrap the .on method so we can hold the listeners internally
on(event, callback){
if(!this.listeners[event]) this.listeners[event] = [];
this.listeners[event].push(callback);
// If the bot is already loaded, add the passed listener to the
// mineflayer instance
if(this.isReady){
if(event === 'onReady') callback(this);
else this.bot.on(event, callback);
}
return ()=> this.off(event, callback);
}
// Remove listener for events
off(event, callback) {
console.log('off', event, callback)
if (!this.listeners[event]) return false;
const index = this.listeners[event].indexOf(callback);
if (index === -1) return false;
this.listeners[event].splice(index, 1);
// If bot is ready, also remove from the Mineflayer bot
if (this.isReady) {
this.bot.off(event, callback);
}
return true;
}
// Listen for ending events and call connect again
__autoReConnect(){
try{
console.log('auto re-connect function')
this.on('kicked', (...args)=>console.log('CJbot.__autoReConnect on kick', args))
this.on('end', async (...args)=>{
console.error('CJbot.__autoReConnect on end', args)
// connect() also rejects on 'end', which used to surface as an
// unhandled rejection (fatal on modern Node). Guard against
// overlapping loops and retry until we get back in.
if(this._reconnecting) return;
this._reconnecting = true;
try{
while(true){
await sleep(30000);
try{
await this.connect();
break;
}catch(error){
console.error('CJbot.__autoReConnect retry failed:', this.name, error?.message || error);
}
}
}finally{
this._reconnecting = false;
}
});
this.on('error', (error)=>{
console.error('MC on error', error);
// this.connect();
});
}catch(error){
console.error('error in __autoReConnect', error);
}
}
__error(message){
this.bot.on('error', (error)=>{
console.error(`ERROR!!! MC bot ${this.username} on ${this.host} had an error:\n`, error)
});
}
quit(force){
if(!this.combatTag || force){
this.bot.quit();
}else{
console.log('Logout prevented due to combatTag');
this.doLogOut = true;
}
}
/* Plugins */
static plungins = {};
static pluginAdd(cls){
this.plungins[cls.name] = cls;
// Queue for web registration if plugin has web support
if (typeof cls.createRouter === 'function' || cls.webUI) {
const webServer = require('../controller/web-server');
webServer.queuePlugin(cls);
}
}
plunginsLoaded = {};
async pluginLoadAll(){
for(let pluginName in this.pluginsWanted){
await this.pluginLoad(pluginName, this.pluginsWanted[pluginName]);
}
}
async pluginLoad(pluginName, opts){
console.log('CJbot.pluginLoad', pluginName)
let plugin = new this.constructor.plungins[pluginName]({...opts, bot:this})
await plugin.init();
this.plunginsLoaded[pluginName] = plugin;
}
async pluginUnload(name){
console.log('CJbot.pluginUnload', name)
if(this.plunginsLoaded[name]){
this.plunginsLoaded[name].unload();
delete this.plunginsLoaded[name];
console.log('CJbot.pluginUnload', name, 'done');
return true;
}
}
async pluginUnloadAll(keepDb = false){
console.log('CJbot.pluginUnloadAll', keepDb ? '(keepDb)' : '');
for(let pluginName in this.plunginsLoaded){
console.log('CJbot.pluginUnloadAll loop', pluginName)
try{
await this.plunginsLoaded[pluginName].unload(keepDb);
delete this.plunginsLoaded[pluginName];
}catch(error){
console.log('CJbot.pluginUnload loop error:', error)
}
}
}
/* On-demand lifecycle */
async ensureConnected(taskFn) {
this._resetIdleTimer();
if (this.isReady) return await taskFn();
return new Promise((resolve, reject) => {
this._taskQueue.push({ fn: taskFn, resolve, reject });
if (this._connecting) return;
this._connecting = true;
this.connect().then(async () => {
this._connecting = false;
// Wait for plugins to finish loading (pluginLoadAll runs after spawn)
await this._pluginsReady;
await this._drainTaskQueue();
}).catch((error) => {
this._connecting = false;
const queue = this._taskQueue.splice(0);
for (const task of queue) task.reject(error);
});
});
}
async _drainTaskQueue() {
while (this._taskQueue.length > 0) {
const task = this._taskQueue.shift();
try {
task.resolve(await task.fn());
} catch (error) {
task.reject(error);
}
this._resetIdleTimer();
}
}
_resetIdleTimer() {
if (!this.onDemand) return;
if (this._idleTimer) clearTimeout(this._idleTimer);
this._idleTimer = setTimeout(() => this._idleDisconnect(), this._idleTimeout);
}
_idleDisconnect() {
if (!this.isReady) return;
// A registered task (scan, organize, withdraw, trade) is still running —
// disconnecting now would abandon shulkers and leak locks
if (this._currentTask) {
console.log(`${this.name}: Idle timer fired mid-task (${this._currentTask.task}), rescheduling`);
this._resetIdleTimer();
return;
}
console.log(`${this.name}: Idle timeout, disconnecting on-demand bot`);
this.quit(true);
}
/* Chat and messaging*/
__listen(){
this.bot.on('chat', (from, message)=>{
// Ignore messages from this bot
if(from === this.bot.entity.username) return;
// Filter messages to this bot
if(message.startsWith(this.bot.entity.username)){
this.__doCommand(
from,
message.replace(`${this.bot.entity.username} ` , ' ').trim()
)
}
});
this.bot.on('whisper', (from, message)=>{
this.__doCommand(
from,
message.replace(`${this.bot.entity.username} ` , ' ').trim()
)
});
this.bot.on('message', (message, type)=>{
if(message.toString().includes(' invited you to teleport to him.')){
// teleport invite
console.log('found teleport', message.toString().split(' ')[0])
this.__doCommand(message.toString().split(' ')[0], '.invite');
}
if(message.toString().includes(' wants to trade with you!')){
// teleport invite
console.log('found Trade', message.toString().split(' ')[0])
this.__doCommand(message.toString().split(' ')[0], '.trade');
}
if(message.toString().includes('You are combat tagged by')){
try{
this.combatTag = true;
console.log('was attacked by')
let attacker = message.toString().split('. ')[0].replace('You are combat tagged by ', '')
console.log('was attacked by', attacker)
// teleport invite
this.whisper(attacker, 'Please do not attack me, I am a bot.')
}catch(error){
console.log('error!!!!!!', error)
}
}
if(message.toString().includes('You are no longer in combat. You may now logout.')){
this.combatTag = false
if(this.doLogOut){
this.quit()
}
}
});
}
__chatCoolDown(){
return Math.floor(Math.random() * (3000 - 2000) + 2000);
}
async say(...messages){
for(let message of messages){
(async (message)=>{
if(this.nextChatTime > Date.now()){
await sleep(this.nextChatTime-Date.now()+1)
}
this.bot.chat(message);
})(message);
this.nextChatTime = Date.now() + this.__chatCoolDown();
}
}
async sayAiSafe(...messages){
for(let message of messages){
if(message.startsWith('/') && !(message.startsWith('/msg') || message.startsWith('/help'))){
console.log('bot tried to execute bad command', message);
message = '.'+message;
}
await this.say(message);
}
}
async whisper(to, ...messages){
await this.say(...messages.map(message=>`/msg ${to} ${message}`));
}
/* Commands */
async __unLockCommand(time){
await sleep(this.commandCollDownTime);
this.commandLock = false;
}
__reduceCommands(from){
return Object.keys(this.commands).filter(command =>{
if (this.commands[command].allowed && !this.commands[command].allowed.includes(from)) return false;
return true;
});
}
async __doCommand(from, command){try{
let [cmd, ...parts] = command.split(/\s+/);
if(!this.__reduceCommands(from).includes(cmd)) return;
const cmdDef = this.commands[cmd];
if(this.commandLock && !cmdDef.ignoreLock){
this.whisper(from, `I'm busy with another command right now, please try again shortly...`);
return ;
}
if(!cmdDef.ignoreLock) this.commandLock = true;
try{
await cmdDef.function.call(this, from, ...parts);
}catch(error){
this.whisper(from, `The command encountered an error.`);
this.whisper(from, `ERROR: ${error}`);
console.error(`Chat command error on ${cmd} from ${from}\n`, error);
}
if(!cmdDef.ignoreLock) this.__unLockCommand();
}catch(error){
console.error('minecraft.js | __doCommand |', this.name, ' ', error)
}}
addCommand(name, obj){
if(this.commands[name]) return false;
this.commands[name] = obj;
}
getPlayers(){
for (let [username, value] of Object.entries(this.bot.players)){
value.lvl = Number(value.displayName.extra[0].text)
}
return this.bot.players;
}
/* Actions */
__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');
}
findBlockBySign(text){
return this.bot.findBlock({
useExtraInfo: true,
maxDistance: 64,
matching: (block)=> {
if(block.name.includes('sign') && block.signText.includes(text)){
return true;
}
}
});
}
findChestBySign(text){
return this.bot.findBlock({
point: this.findBlockBySign(text).position,
maxDistance: 4,
useExtraInfo: true,
matching: block => block.name === 'chest'
});
}
isWithinRange(target, range=2){
const botPos = this.bot.entity.position;
const distance = botPos.distanceTo(target);
return distance <= range+.9;
}
playerWithinBlock(player, block, range){
let playerData = this.bot.players[player];
if(!playerData || !playerData.entity) return; // Skip if no entity info
// Calculate the distance between the player and the block
let distance = playerData.entity.position.distanceTo(block.position);
console.log('CJbot.playerWithinBlock', distance, range, distance < range)
if(!range){
return distance;
}
return distance < range;
}
// Interrupt current movement so trade/storage commands can run
async interruptTask(from) {
this._interrupted = true;
this._interruptGen++;
this._currentTask = null;
try {
this.bot.pathfinder.stop();
} catch (e) { /* pathfinder may not be moving */ }
this.bot.clearControlStates();
}
registerTask(source, task, fn) {
this._interrupted = false;
this._currentTask = { source, task, fn };
}
clearTask() {
this._currentTask = null;
}
wasInterrupted() {
return this._interrupted;
}
async goTo(options) {
while (this._goToLock) await new Promise(r => setTimeout(r, 50));
this._goToLock = true;
// Captured after acquiring the lock: an interrupt kills goTo calls that
// were already running, not the interrupting command's own movement.
const gen = this._interruptGen;
try {
let range = options.range || 2;
let block = this.__blockOrVec(options.where);
let timeout = options.timeout || 60000;
const goal = block.position;
console.log('[goTo] target:', goal.toArray(), 'range:', range);
const startTime = Date.now();
let lastPos = this.bot.entity.position.clone();
let stuckTime = 0;
let recoveryCount = 0;
// Fire-and-forget: starts pathfinder, never awaited.
// PathStopped/GoalChanged/NoPath are expected during recovery.
const startPathfinder = () => {
this.bot.pathfinder.goto(
new GoalNear(...goal.toArray(), range)
).catch(e => {
if (e.name !== 'PathStopped' && e.name !== 'GoalChanged' && e.name !== 'NoPath')
console.log('[goTo] goto rejected:', e.name, e.message);
});
};
startPathfinder();
while (!this.isWithinRange(goal, range)) {
if (this._interruptGen !== gen) {
this.bot.pathfinder.stop();
console.log('[goTo] interrupted');
return false;
}
const elapsed = Date.now() - startTime;
if (elapsed > timeout) {
this.bot.pathfinder.stop();
console.log('[goTo] timed out after', timeout, 'ms');
return false;
}
const currentPos = this.bot.entity.position;
const moved = currentPos.distanceTo(lastPos);
if (moved > 0.2) {
// Making progress
stuckTime = 0;
recoveryCount = Math.max(0, recoveryCount - 1);
lastPos = currentPos.clone();
} else {
// Not moving
stuckTime += 400;
}
// Pathfinder legitimately pauses during jumps, replans, and corner
// turns — only treat 2s+ of zero movement as actually stuck.
if (stuckTime >= 2000) {
console.log('[goTo] stuck, recovery=', recoveryCount);
await this._recoverFromStuck(recoveryCount);
stuckTime = 0;
recoveryCount++;
lastPos = this.bot.entity.position.clone();
if (recoveryCount > 8) {
console.log('[goTo] giving up after', recoveryCount, 'recoveries');
return false;
}
startPathfinder();
}
if (!this.bot.pathfinder.isMoving()) {
startPathfinder();
}
await sleep(400);
}
this.bot.pathfinder.stop();
console.log('[goTo] arrived');
return true;
} finally {
this._goToLock = false;
}
}
// Like goTo, but failure to arrive aborts the caller instead of letting it
// operate on a container it never reached.
async goToMust(options) {
const arrived = await this.goTo(options);
if (!arrived) {
const pos = options.where?.position || options.where;
throw new Error(`Could not reach ${pos} (interrupted, stuck, or timed out)`);
}
return true;
}
// Simple unstuck: back up N blocks, strafe N blocks (alternating left/right),
// then let the main loop retry pathfinding to the goal.
async _recoverFromStuck(failureCount) {
this.bot.pathfinder.stop();
this.bot.clearControlStates();
await sleep(50);
// Cap at 3 blocks — backing way up just walks the bot away from the
// goal and into other obstacles. No jump: jumping while reversing
// climbs the bot onto chests and wedges it into corners.
const n = Math.min(failureCount + 1, 3);
const strafeDir = failureCount % 2 === 0 ? 'left' : 'right';
const blockTime = 250; // ms per block (walking speed ~4.3 blocks/sec)
console.log('[goTo] recover: back', n, 'strafe', n, strafeDir);
// Back up N blocks
this.bot.setControlState('back', true);
await sleep(n * blockTime);
this.bot.setControlState('back', false);
await sleep(50);
// Strafe N blocks (alternating direction)
this.bot.setControlState(strafeDir, true);
await sleep(n * blockTime);
this.bot.setControlState(strafeDir, false);
this.bot.clearControlStates();
await sleep(100);
}
async goToReturn(options){
let here = this.bot.entity.position;
let hereYaw = this.bot.entity.yaw
await this.goTo(options);
return async () =>{
await this.goTo({where: here, range: 0}, true);
await sleep(500);
await this.bot.look(Math.floor(hereYaw), 0);
}
}
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 openContainer(block){
block = this.__blockOrVec(block);
// A window left open by a failed earlier operation would otherwise be
// returned as-is below — for the wrong container, with wrong slot
// indexes. Close it and start clean.
if(this.bot.currentWindow){
console.log('CJbot.openContainer: closing stale window', this.bot.currentWindow.title);
try{
this.bot.closeWindow(this.bot.currentWindow);
}catch(error){
console.log('CJbot.openContainer: stale window close failed:', error.message);
}
await sleep(500);
}
// A chest with a solid block on top cannot open. If the obstruction is
// a shulker box it's one of ours, abandoned by a failed cycle — break
// it and try to collect it. Anything else is a hard, described failure
// instead of four pointless retries.
if(block.name.includes('chest')){
const above = this.bot.blockAt(block.position.offset(0, 1, 0));
if(above && above.boundingBox === 'block'){
if(above.name.includes('shulker_box')){
console.log(`CJbot.openContainer: chest at ${block.position} blocked by ${above.name} on top — breaking it`);
try{
await this.bot.lookAt(above.position.offset(0.5, 0.5, 0.5), true);
await this.bot.dig(above, 'raycast');
await sleep(300);
// Try to catch the drop so the box isn't lost to despawn
await this.goTo({ where: above.position, range: 0, timeout: 10000 });
await sleep(500);
}catch(error){
throw new Error(`Chest at ${block.position} blocked by ${above.name} on top (recovery failed: ${error.message})`);
}
}else{
throw new Error(`Chest at ${block.position} won't open: blocked by ${above.name} on top`);
}
}
}
let count = 0;
while(true){
// Face the container — some anti-cheat setups reject interactions
// the player isn't looking at
try{
await this.bot.lookAt(block.position.offset(0.5, 0.5, 0.5), true);
}catch(error){ /* best effort */ }
try{
const window = await this.bot.openContainer(block);
if(window) return window;
}catch(error){
if(!error.message.includes('Event windowOpen did not fire within timeout')) throw error;
}
// The open packet may have landed even though the event timed out
if(this.bot.currentWindow?.title) return this.bot.currentWindow;
if(++count > 3) throw new Error(`Block wont open (${block.name} at ${block.position})`);
await sleep(1500);
}
}
async openCraftingTable(block){
let count = 0;
block = this.__blockOrVec(block);
this.bot.activateBlock(block);
let window = await this.once('windowOpen');
return window;
}
async checkItemsFromContainer(containerBlock, itemName, count){
let currentSlot = 0;
let foundCount = 0;
let window = await this.openContainer(containerBlock);
for(let slot of window.slots){
if(currentSlot++ === window.inventoryStart) break;currentSlot
if(!slot) continue;
if(slot.name === itemName) foundCount += slot.count;
}
await this.bot.closeWindow(window);
if(foundCount >= count) return true;
}
async getItemsFromChest(containerBlock, itemName, count){
let window = await this.openContainer(containerBlock);
await sleep(500);
// console.log('item id', this.mcData.itemsByName[itemName], this.mcData)
await window.withdraw(this.mcData.itemsByName[itemName].id, null, count);
await this.bot.closeWindow(window);
}
async getFullShulkersFromChest(chestBlock, item) {
const fullShulkers = [];
let window = await this.openContainer(chestBlock);
let itemCount = 0
let currentSlot = 0;
for(let slot of window.slots){
if(currentSlot++ === window.inventoryStart) break;
if(!slot || slot.name !== 'shulker_box') continue;
// console.log('slot:', slot)
if(slot.nbt){
// console.log('nbt', slot.nbt, slot.nbt.value.BlockEntityTag)
// console.log('BlockEntityTag:', slot.nbt.value.BlockEntityTag.value.Items.value.value)
for(let shulkerSlot of slot.nbt.value.BlockEntityTag.value.Items.value.value){
console.log('shulkerSlot', shulkerSlot)
if(shulkerSlot.id?.value !== `minecraft:${item}`) continue;
itemCount += shulkerSlot.Count.value
}
if(this.bot.registry.itemsByName[item].stackSize * 27 === itemCount){
console.log('found full shulker');
this.bot.moveSlotItem(currentSlot, window.inventoryStart);
break;
}
}
}
await window.close();
}
async dumpToChest(block, blockName, amount) {
let window = await this.openContainer(block);
let items = window.slots.slice(window.inventoryStart).filter(function(item){
if(!item) return false;
if(blockName && blockName !== item.name) return false;
return true;
});
for(let item of items){
await sleep(500);
let currentSlot = Number(item.slot);
if(!window.slots[currentSlot]) continue;
try{
await this.bot.transfer({
window,
itemType: this.mcData.itemsByName[item.name].id,
sourceStart: currentSlot,
sourceEnd: currentSlot+1,
destStart: 0,
destEnd: window.inventoryStart-1,
count: amount || item.count,
})
if(amount) amount -= item.count
}catch(error){
console.log('error?', item.count, error.message, error);
}
}
await sleep(1000);
await this.bot.closeWindow(window);
return amount ? amount : true;
}
}
module.exports = {CJbot};