This commit is contained in:
2024-10-11 09:46:18 -04:00
parent 84c45695b7
commit ddc6f2d167
15 changed files with 1504 additions and 559 deletions

View File

@ -54,6 +54,7 @@ class CJbot{
this.autoReConnect = 'autoConnect' in args ? args.autoReConnect : true;
this.autoConnect = 'autoConnect' in args ? args.autoConnect : true;
// If we want the be always connected, kick off the function to auto
// reconnect
if(this.autoReConnect) this.__autoReConnect()
@ -74,8 +75,8 @@ class CJbot{
// 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(this.name, m.toString())
reject(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
@ -87,9 +88,10 @@ class CJbot{
});
// When the bot is ready, return to the caller success
this.bot.on('login', ()=>{
this.__onReady()
resolve()
this.bot.on('spawn', async()=>{
console.log('CJbot.connect on spawn')
this.__onReady();
resolve();
});
// Set a timer to try to connect again in 30 seconds if the bot is
@ -123,20 +125,26 @@ class CJbot{
// 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 || []){
console.log('calling listener', callback);
await callback.call(this);
callback.call(this);
}
this.isReady = true;
this.__error();
console.log('Bot is ready', this.bot.entity.username, this.username);
// Start chat listeners
this.__listen();
// this.bot.on('path_update', (...args)=>{ console.log('EVENT path_update', args) })
// this.bot.on('goal_updated', (...args)=>{ console.log('EVENT goal_updated', args) })
// this.bot.on('path_reset', (...args)=>{ console.log('EVENT path_reset', args) })
// this.bot.on('path_stop', (...args)=>{ console.log('EVENT path_stop', args) })
}catch(error){
console.error('minecraft.js | __onReady | ', this.name, ' ', error);
@ -144,6 +152,7 @@ class CJbot{
__startListeners(){
for(let event in this.listeners){
console.log('__adding listeners', event)
for(let callback of this.listeners[event]){
this.bot.on(event, callback);
}
@ -163,10 +172,26 @@ class CJbot{
else this.bot.on(event, callback);
}
return event === 'onReady' ? true : ()=> this.bot.off(listener, callback);
return ()=> this.off(event, callback);
}
// todo; add .off wrapper
// 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(){
@ -207,6 +232,33 @@ class CJbot{
}
}
/* Plugins */
static plungins = {};
static pluginAdd(cls){
this.plungins[cls.name] = cls
}
plunginsLoaded = {};
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]){
await this.plunginsLoaded[name].unload();
delete this.plunginsLoaded[name];
console.log('CJbot.pluginUnload', name, 'done');
return true;
}
}
/* Chat and messaging*/
__listen(){
@ -277,10 +329,9 @@ class CJbot{
async say(...messages){
for(let message of messages){
console.log('next chat time:', this.nextChatTime > Date.now(), Date.now()+1, this.nextChatTime-Date.now()+1);
// console.log('next chat time:', this.nextChatTime > Date.now(), Date.now()+1, this.nextChatTime-Date.now()+1);
(async (message)=>{
if(this.nextChatTime > Date.now()){
console.log('am sleeping');
await sleep(this.nextChatTime-Date.now()+1)
}
this.bot.chat(message);
@ -357,6 +408,8 @@ class CJbot{
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;
@ -364,31 +417,83 @@ class CJbot{
throw new Error('Not supported block identifier');
}
async _goTo(block, range=2){
block = this.__blockOrVec(block);
return await this.bot.pathfinder.goto(new GoalNear(...block.position.toArray(), range));
}
goTo(options){
return new Promise(async(resolve, reject)=>{
let range = options.range || 2;
try{
await this._goTo(options.where, range)
return resolve();
}catch(error){
if(options.reTry) return reject('Action can not move to where')
await this._goTo(options, true);
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: 1,
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;
}
async _goTo(block, range=2){
try{
}catch(error){
if(error.message === 'Not supported block identifier'){
console.log('found block error')
await sleep(1000);
block = this.__blockOrVec(block);
}
console.log('other', error)
}
try{
}catch(error){
if(error.name === 'GoalChanged') return await this._goTo(block, range);
console.log('error in _goTo', error.name, '|', error.message);
}
}
async goTo(options) {
let range = options.range || 2;
let block = this.__blockOrVec(options.where);
while(!this.isWithinRange(this.__blockOrVec(options.where).position, range)){
try{
if(this.bot.pathfinder.isMoving()){
await sleep(500);
console.log('the bot is moving...')
continue;
}
await this.bot.pathfinder.goto(
new GoalNear(...block.position.toArray(), range)
);
}catch(error){
await sleep(500);
console.log('CJbot.goTo while loop error:', error)
// await this.bot.pathfinder.setGoal(null);
await this.bot.pathfinder.stop();
await sleep(500);
}
}
return true;
}
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);
@ -396,7 +501,6 @@ class CJbot{
}
}
async __nextContainerSlot(window, item) {
let firstEmptySlot = false;
@ -418,27 +522,124 @@ class CJbot{
async openContainer(block){
let count = 0;
block = this.__blockOrVec(block);
let window;
while(!this.bot.currentWindow){
let window = this.bot.openContainer(block);
await sleep(1500);
try{
window = await this.bot.openContainer(block);
}catch(error){
if(!error.message.includes('Event windowOpen did not fire within timeout')) throw error;
}
if(this.bot.currentWindow?.title){
break;
}
this.bot.removeAllListeners('windowOpen');
this.bot.removeAllListeners('windowOpen');
await sleep(1500);
if(count++ == 3) throw 'Block wont open';
}
return this.bot.currentWindow;
}
async openCraftingTable(block){
let count = 0;
block = this.__blockOrVec(block);
this.bot.activateBlock(block);
let window = await this.once('windowOpen');
// while(!this.bot.currentWindow){
// try{
// if(this.bot.currentWindow?.title){
// break;
// }
// this.bot.removeAllListeners('windowOpen');
// if(count++ == 3) throw 'Block wont open';
// }catch(error){
// console.error('ERROR in CJbot.openCraftingTable:', error)
// }
// }
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 currentSlot = 0;
for(let slot of window.slots){
if(currentSlot++ === window.inventoryStart) break;
if(!slot) continue;
// if(!slot || slot.name !== 'shulker_box') continue;
console.log('slot:', slot)
if(slot.nbt){
// console.log(slot.nbt)
console.log('BlockEntityTag:', slot.nbt.value.BlockEntityTag.value.Items.value.value)
}
}
/* // Get the inventory of the chest block
const chestInventory = chestBlock.getInventory();
// Iterate through the chest's inventory
chestInventory.forEach((slot, index) => {
// Check if the slot contains a shulker box
if (slot && slot.type === 'shulker_box') {
// Retrieve the shulker's inventory
const shulkerInventory = slot.getInventory();
// Check if the shulker is full of the specified item
const isFull = shulkerInventory.every(shulkerSlot => {
console.log('shulkerSlot', shulkerSlot)
return shulkerSlot && shulkerSlot.id === item.id && shulkerSlot.count === 64; // Assuming max stack size is 64
});
// If full, add the shulker box to the list
if (isFull) {
fullShulkers.push(slot);
}
}
});
return fullShulkers;*/
}
async dumpToChest(block, blockName, amount) {
let window = await this.openContainer(block);
let items = window.slots.slice(window.inventoryStart).filter(function(item){
let items = window.slots.slice(window.inventoryStart).filter(function(item){
if(!item) return false;
if(blockName && blockName !== item.name) return false;
return true;