545 lines
15 KiB
JavaScript
545 lines
15 KiB
JavaScript
'use strict';
|
|
|
|
const conf = require('../conf');
|
|
const { sleep } = require('../utils');
|
|
|
|
const botSlots = [0, 1, 2, 3, 9, 10, 11, 12, 18, 19, 20, 21];
|
|
|
|
class FarmSupply {
|
|
constructor(args) {
|
|
this.bot = args.bot;
|
|
this.config = conf.farmSupply || {};
|
|
this._storageBotKey = this.config.storageBotName;
|
|
this.isAction = true;
|
|
this._onTimeListen = null;
|
|
this._onReadyListen = null;
|
|
this._resupplying = false;
|
|
}
|
|
|
|
get storageBotName() {
|
|
const storageBot = this.bot.constructor.bots[this._storageBotKey];
|
|
if (storageBot && storageBot.bot && storageBot.bot.entity) {
|
|
return storageBot.bot.entity.username;
|
|
}
|
|
return this._storageBotKey;
|
|
}
|
|
|
|
async init() {
|
|
if (!this._storageBotKey) {
|
|
console.log('FarmSupply: No storageBotName configured, plugin disabled');
|
|
return false;
|
|
}
|
|
|
|
console.log(`FarmSupply: Initialized for bot ${this.bot.name}, storage bot: ${this._storageBotKey}`);
|
|
|
|
this._onReadyListen = this.bot.on('onReady', async () => {
|
|
await sleep(3000);
|
|
let lastTimeOfDay = this.bot.bot.time.timeOfDay;
|
|
|
|
this._onTimeListen = this.bot.bot.on('time', async () => {
|
|
const currentTime = this.bot.bot.time.timeOfDay;
|
|
if (lastTimeOfDay < 12000 && currentTime >= 12000 && !this._resupplying) {
|
|
lastTimeOfDay = currentTime;
|
|
console.log('FarmSupply: Sunset detected, starting resupply');
|
|
try {
|
|
await this.resupply();
|
|
} catch (error) {
|
|
console.error('FarmSupply: Sunset resupply error:', error);
|
|
}
|
|
} else {
|
|
lastTimeOfDay = currentTime;
|
|
}
|
|
});
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
unload() {
|
|
if (this._onReadyListen) this._onReadyListen();
|
|
if (this._onTimeListen) this._onTimeListen = null;
|
|
return true;
|
|
}
|
|
|
|
// ========================================
|
|
// Pause / Resume farm plugins
|
|
// ========================================
|
|
|
|
pauseFarmPlugins() {
|
|
const paused = [];
|
|
for (const [name, plugin] of Object.entries(this.bot.plunginsLoaded)) {
|
|
if (name === 'FarmSupply' || name === 'AutoEat') continue;
|
|
if (plugin.isAction) {
|
|
console.log(`FarmSupply: Pausing plugin ${name}`);
|
|
try {
|
|
plugin.unload();
|
|
} catch (error) {
|
|
console.error(`FarmSupply: Error pausing ${name}:`, error);
|
|
}
|
|
paused.push(name);
|
|
delete this.bot.plunginsLoaded[name];
|
|
}
|
|
}
|
|
return paused;
|
|
}
|
|
|
|
async resumeFarmPlugins(paused) {
|
|
for (const name of paused) {
|
|
console.log(`FarmSupply: Resuming plugin ${name}`);
|
|
try {
|
|
await this.bot.pluginLoad(name);
|
|
} catch (error) {
|
|
console.error(`FarmSupply: Error resuming ${name}:`, error);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ========================================
|
|
// Wait after trade teleport — let server tp us back and chunks load
|
|
// ========================================
|
|
|
|
async settleAfterTrade() {
|
|
console.log('FarmSupply: Settling after trade teleport...');
|
|
await sleep(3000);
|
|
// Wait for pathfinder to be idle
|
|
while (this.bot.bot.pathfinder.isMoving()) {
|
|
this.bot.bot.pathfinder.stop();
|
|
await sleep(500);
|
|
}
|
|
this.bot.bot.clearControlStates();
|
|
await sleep(1000);
|
|
}
|
|
|
|
// ========================================
|
|
// Main resupply flow
|
|
// ========================================
|
|
|
|
async resupply() {
|
|
if (this._resupplying) {
|
|
console.log('FarmSupply: Already resupplying, skipping');
|
|
return;
|
|
}
|
|
|
|
this._resupplying = true;
|
|
console.log('FarmSupply: Starting resupply...');
|
|
const paused = this.pauseFarmPlugins();
|
|
|
|
try {
|
|
await this.emptyFilledBoxes();
|
|
await this.fillEmptyShulkers();
|
|
} catch (error) {
|
|
console.error('FarmSupply: Resupply error:', error);
|
|
}
|
|
|
|
await this.resumeFarmPlugins(paused);
|
|
this._resupplying = false;
|
|
console.log('FarmSupply: Resupply complete, farm resumed.');
|
|
}
|
|
|
|
// ========================================
|
|
// Empty "filled boxes" chest
|
|
// ========================================
|
|
|
|
async emptyFilledBoxes() {
|
|
let filledChest;
|
|
try {
|
|
filledChest = this.bot.findChestBySign('filled boxes');
|
|
} catch (error) {
|
|
console.log('FarmSupply: No "filled boxes" chest found, skipping');
|
|
return;
|
|
}
|
|
if (!filledChest) {
|
|
console.log('FarmSupply: No "filled boxes" chest found, skipping');
|
|
return;
|
|
}
|
|
|
|
console.log('FarmSupply: Processing filled boxes chest');
|
|
|
|
let hasMore = true;
|
|
while (hasMore) {
|
|
// Re-find chest each loop — block ref may be stale after trade teleport
|
|
filledChest = this.bot.findChestBySign('filled boxes');
|
|
await this.bot.goTo({ where: filledChest.position, range: 2 });
|
|
let window = await this.bot.openContainer(filledChest);
|
|
await sleep(300);
|
|
|
|
let taken = 0;
|
|
for (let i = 0; i < window.inventoryStart; i++) {
|
|
if (taken >= 12) break;
|
|
const item = window.slots[i];
|
|
if (item && item.name.includes('shulker_box')) {
|
|
try {
|
|
await this.bot.bot.moveSlotItem(i, window.inventoryStart + taken);
|
|
await sleep(200);
|
|
taken++;
|
|
} catch (error) {
|
|
console.error('FarmSupply: Error taking shulker from chest:', error);
|
|
}
|
|
}
|
|
}
|
|
|
|
await this.bot.bot.closeWindow(window);
|
|
await sleep(300);
|
|
|
|
if (taken === 0) {
|
|
console.log('FarmSupply: No more filled shulker boxes to deposit');
|
|
hasMore = false;
|
|
break;
|
|
}
|
|
|
|
console.log(`FarmSupply: Took ${taken} shulker boxes, initiating trade deposit`);
|
|
await this.tradeDeposit();
|
|
await this.waitForStorageBotIdle();
|
|
await this.settleAfterTrade();
|
|
}
|
|
}
|
|
|
|
// ========================================
|
|
// Fill "empty shulkers" chest
|
|
// ========================================
|
|
|
|
async fillEmptyShulkers() {
|
|
let emptyChest;
|
|
try {
|
|
emptyChest = this.bot.findChestBySign('empty shulkers');
|
|
} catch (error) {
|
|
console.log('FarmSupply: No "empty shulkers" chest found, skipping');
|
|
return;
|
|
}
|
|
if (!emptyChest) {
|
|
console.log('FarmSupply: No "empty shulkers" chest found, skipping');
|
|
return;
|
|
}
|
|
|
|
console.log('FarmSupply: Processing empty shulkers chest');
|
|
|
|
// Re-find and navigate to chest
|
|
emptyChest = this.bot.findChestBySign('empty shulkers');
|
|
await this.bot.goTo({ where: emptyChest.position, range: 2 });
|
|
let window = await this.bot.openContainer(emptyChest);
|
|
await sleep(300);
|
|
|
|
let emptySlots = 0;
|
|
for (let i = 0; i < window.inventoryStart; i++) {
|
|
if (!window.slots[i]) emptySlots++;
|
|
}
|
|
|
|
await this.bot.bot.closeWindow(window);
|
|
await sleep(300);
|
|
|
|
if (emptySlots === 0) {
|
|
console.log('FarmSupply: Empty shulkers chest is full, skipping');
|
|
return;
|
|
}
|
|
|
|
console.log(`FarmSupply: Need ${emptySlots} shulker boxes`);
|
|
|
|
// Check current inventory for materials
|
|
let shellsInInv = 0;
|
|
let chestsInInv = 0;
|
|
let shulkerBoxesInInv = 0;
|
|
for (const item of this.bot.bot.inventory.items()) {
|
|
if (item.name === 'shulker_shell') shellsInInv += item.count;
|
|
if (item.name === 'chest') chestsInInv += item.count;
|
|
if (item.name.includes('shulker_box')) shulkerBoxesInInv += item.count;
|
|
}
|
|
|
|
const canCraft = Math.min(Math.floor(shellsInInv / 2), chestsInInv);
|
|
const totalAvailable = shulkerBoxesInInv + canCraft;
|
|
const needed = Math.min(emptySlots, 27);
|
|
|
|
if (totalAvailable < needed) {
|
|
const toCraft = needed - shulkerBoxesInInv;
|
|
const shellsNeeded = Math.max(0, (toCraft * 2) - shellsInInv);
|
|
const chestsNeeded = Math.max(0, toCraft - chestsInInv);
|
|
|
|
if (shellsNeeded > 0) {
|
|
console.log(`FarmSupply: Withdrawing ${shellsNeeded} shulker_shell from storage`);
|
|
await this.tradeWithdraw('shulker_shell', shellsNeeded);
|
|
await this.waitForStorageBotIdle();
|
|
await this.settleAfterTrade();
|
|
}
|
|
|
|
if (chestsNeeded > 0) {
|
|
console.log(`FarmSupply: Withdrawing ${chestsNeeded} chest from storage`);
|
|
await this.tradeWithdraw('chest', chestsNeeded);
|
|
await this.waitForStorageBotIdle();
|
|
await this.settleAfterTrade();
|
|
}
|
|
}
|
|
|
|
// Recount after possible withdrawal
|
|
shellsInInv = 0;
|
|
chestsInInv = 0;
|
|
shulkerBoxesInInv = 0;
|
|
for (const item of this.bot.bot.inventory.items()) {
|
|
if (item.name === 'shulker_shell') shellsInInv += item.count;
|
|
if (item.name === 'chest') chestsInInv += item.count;
|
|
if (item.name.includes('shulker_box')) shulkerBoxesInInv += item.count;
|
|
}
|
|
|
|
// Craft shulker boxes
|
|
const craftCount = Math.min(Math.floor(shellsInInv / 2), chestsInInv, needed - shulkerBoxesInInv);
|
|
if (craftCount > 0) {
|
|
console.log(`FarmSupply: Crafting ${craftCount} shulker boxes`);
|
|
for (let i = 0; i < craftCount; i++) {
|
|
try {
|
|
await this.craftShulkerBox();
|
|
await sleep(300);
|
|
} catch (error) {
|
|
console.error('FarmSupply: Crafting error:', error);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Re-find chest and dump shulker boxes
|
|
emptyChest = this.bot.findChestBySign('empty shulkers');
|
|
await this.bot.goTo({ where: emptyChest.position, range: 2 });
|
|
window = await this.bot.openContainer(emptyChest);
|
|
await sleep(300);
|
|
|
|
let deposited = 0;
|
|
for (let i = window.inventoryStart; i < window.inventoryEnd; i++) {
|
|
const item = window.slots[i];
|
|
if (item && item.name.includes('shulker_box')) {
|
|
let targetSlot = null;
|
|
for (let j = 0; j < window.inventoryStart; j++) {
|
|
if (!window.slots[j]) {
|
|
targetSlot = j;
|
|
break;
|
|
}
|
|
}
|
|
if (targetSlot === null) break;
|
|
|
|
try {
|
|
await this.bot.bot.moveSlotItem(i, targetSlot);
|
|
await sleep(200);
|
|
deposited++;
|
|
} catch (error) {
|
|
console.error('FarmSupply: Error depositing shulker box:', error);
|
|
}
|
|
}
|
|
}
|
|
|
|
await this.bot.bot.closeWindow(window);
|
|
console.log(`FarmSupply: Deposited ${deposited} shulker boxes into empty shulkers chest`);
|
|
}
|
|
|
|
// ========================================
|
|
// Trade: Withdraw items from storage bot
|
|
// ========================================
|
|
|
|
async tradeWithdraw(itemName, count) {
|
|
console.log(`FarmSupply: Requesting ${count}x ${itemName} from ${this.storageBotName}`);
|
|
|
|
await this.bot.whisper(this.storageBotName, `.withdraw ${itemName} ${count}`);
|
|
await sleep(3000);
|
|
|
|
await this.bot.say(`/trade ${this.storageBotName}`);
|
|
let window = await this.bot.once('windowOpen');
|
|
|
|
// Wait for storage bot to place items and confirm
|
|
await sleep(2000);
|
|
|
|
// Confirm our side
|
|
try {
|
|
this.bot.bot.moveSlotItem(37, 37);
|
|
} catch (error) {
|
|
console.error('FarmSupply: Error confirming trade:', error);
|
|
}
|
|
|
|
try {
|
|
await Promise.race([
|
|
this.bot.once('windowClose'),
|
|
sleep(15000),
|
|
]);
|
|
} catch (error) {
|
|
console.error('FarmSupply: Trade withdraw timeout or error:', error);
|
|
}
|
|
|
|
await sleep(500);
|
|
console.log(`FarmSupply: Withdraw trade complete for ${itemName}`);
|
|
}
|
|
|
|
// ========================================
|
|
// Trade: Deposit items to storage bot
|
|
// ========================================
|
|
|
|
async tradeDeposit() {
|
|
console.log(`FarmSupply: Depositing shulker boxes to ${this.storageBotName}`);
|
|
|
|
await this.bot.say(`/trade ${this.storageBotName}`);
|
|
let window = await this.bot.once('windowOpen');
|
|
|
|
let placed = 0;
|
|
for (const slotNum of botSlots) {
|
|
if (placed >= 12) break;
|
|
for (let i = window.inventoryStart; i < window.inventoryEnd; i++) {
|
|
const item = window.slots[i];
|
|
if (!item || !item.name.includes('shulker_box')) continue;
|
|
try {
|
|
await this.bot.bot.moveSlotItem(i, slotNum);
|
|
await sleep(200);
|
|
placed++;
|
|
} catch (error) {
|
|
console.error('FarmSupply: Error placing item in trade:', error);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
console.log(`FarmSupply: Placed ${placed} shulker boxes in trade window`);
|
|
|
|
await sleep(500);
|
|
try {
|
|
this.bot.bot.moveSlotItem(37, 37);
|
|
} catch (error) {
|
|
console.error('FarmSupply: Error confirming deposit trade:', error);
|
|
}
|
|
|
|
try {
|
|
await Promise.race([
|
|
this.bot.once('windowClose'),
|
|
sleep(15000),
|
|
]);
|
|
} catch (error) {
|
|
console.error('FarmSupply: Trade deposit timeout or error:', error);
|
|
}
|
|
|
|
await sleep(500);
|
|
console.log(`FarmSupply: Deposit trade complete`);
|
|
}
|
|
|
|
// ========================================
|
|
// Wait for storage bot to finish processing
|
|
// ========================================
|
|
|
|
async waitForStorageBotIdle() {
|
|
const storageBot = this.bot.constructor.bots[this._storageBotKey];
|
|
if (!storageBot) return;
|
|
|
|
const storage = storageBot.plunginsLoaded['Storage'];
|
|
if (!storage) return;
|
|
|
|
console.log('FarmSupply: Waiting for storage bot to finish processing...');
|
|
const maxWait = 120000;
|
|
const start = Date.now();
|
|
while (storage._busy && (Date.now() - start) < maxWait) {
|
|
await sleep(2000);
|
|
}
|
|
|
|
if (storage._busy) {
|
|
console.log('FarmSupply: Storage bot still busy after timeout, continuing anyway');
|
|
} else {
|
|
console.log('FarmSupply: Storage bot idle');
|
|
}
|
|
await sleep(1000);
|
|
}
|
|
|
|
// ========================================
|
|
// Craft a shulker box
|
|
// ========================================
|
|
|
|
async craftShulkerBox() {
|
|
let craftingTable;
|
|
try {
|
|
const signBlock = this.bot.findBlockBySign('crafting table');
|
|
if (signBlock) {
|
|
craftingTable = this.bot.bot.findBlock({
|
|
point: signBlock.position,
|
|
matching: this.bot.mcData.blocksByName.crafting_table.id,
|
|
maxDistance: 4,
|
|
});
|
|
}
|
|
} catch (error) {
|
|
// No sign, search nearby
|
|
}
|
|
|
|
if (!craftingTable) {
|
|
craftingTable = this.bot.bot.findBlock({
|
|
matching: this.bot.mcData.blocksByName.crafting_table.id,
|
|
maxDistance: 64,
|
|
});
|
|
}
|
|
|
|
if (!craftingTable) {
|
|
throw new Error('FarmSupply: No crafting table found nearby');
|
|
}
|
|
|
|
await this.bot.goTo({ where: craftingTable.position, range: 2 });
|
|
|
|
const recipe = this.bot.bot.recipesAll(
|
|
this.bot.mcData.itemsByName.shulker_box.id,
|
|
null,
|
|
craftingTable
|
|
)[0];
|
|
|
|
if (!recipe) {
|
|
throw new Error('FarmSupply: No recipe found for shulker box');
|
|
}
|
|
|
|
const window = await this.bot.openCraftingTable(craftingTable);
|
|
const inventory = window.slots.slice(window.inventoryStart, window.inventoryEnd);
|
|
|
|
const ingredientsByType = {};
|
|
for (let row = 0; row < recipe.inShape.length; row++) {
|
|
for (let col = 0; col < recipe.inShape[row].length; col++) {
|
|
const shape = recipe.inShape[row][col];
|
|
if (shape.id === -1) continue;
|
|
const gridSlot = row * 3 + col + 1;
|
|
if (!ingredientsByType[shape.id]) ingredientsByType[shape.id] = [];
|
|
ingredientsByType[shape.id].push(gridSlot);
|
|
}
|
|
}
|
|
|
|
for (const [typeId, gridSlots] of Object.entries(ingredientsByType)) {
|
|
const invIdx = inventory.findIndex(el => el && el.type === parseInt(typeId));
|
|
if (invIdx === -1) {
|
|
await window.close();
|
|
throw new Error(`FarmSupply: Missing ingredient type ${typeId} in inventory`);
|
|
}
|
|
const actualSlot = window.inventoryStart + invIdx;
|
|
|
|
await this.bot.bot.clickWindow(actualSlot, 0, 0);
|
|
await sleep(100);
|
|
|
|
for (const gridSlot of gridSlots) {
|
|
await this.bot.bot.clickWindow(gridSlot, 1, 0);
|
|
await sleep(100);
|
|
}
|
|
|
|
await this.bot.bot.clickWindow(actualSlot, 0, 0);
|
|
await sleep(100);
|
|
}
|
|
|
|
await sleep(500);
|
|
|
|
if (window.slots[0]) {
|
|
let outputSlot = null;
|
|
for (let j = window.inventoryStart; j < window.inventoryEnd; j++) {
|
|
if (!window.slots[j]) { outputSlot = j; break; }
|
|
}
|
|
if (outputSlot === null) outputSlot = window.inventoryStart;
|
|
await this.bot.bot.clickWindow(0, 0, 0);
|
|
await sleep(100);
|
|
await this.bot.bot.clickWindow(outputSlot, 0, 0);
|
|
await sleep(100);
|
|
}
|
|
|
|
await window.close();
|
|
await sleep(300);
|
|
console.log('FarmSupply: Crafted a shulker box');
|
|
}
|
|
}
|
|
|
|
FarmSupply.getStatus = function(instance) {
|
|
return {
|
|
active: true,
|
|
storageBotName: instance._storageBotKey,
|
|
resupplying: instance._resupplying,
|
|
};
|
|
};
|
|
|
|
module.exports = FarmSupply;
|