This commit is contained in:
2026-07-12 17:26:59 -04:00
parent 60663b8d4a
commit ba1bef8ff9
25 changed files with 158880 additions and 1529 deletions
+86 -22
View File
@@ -57,14 +57,22 @@ class ShulkerHandler {
if (excludeSet.has(`${checkPos.x},${checkPos.y},${checkPos.z}`)) continue;
const blockAtPos = bot.bot.blockAt(checkPos);
const blockBelow = bot.bot.blockAt(checkPos.offset(0, -1, 0));
const blockAbove = bot.bot.blockAt(checkPos.offset(0, 1, 0));
if (blockAtPos && blockAtPos.name === 'air' && blockBelow && blockBelow.boundingBox === 'block') {
return {
position: checkPos,
placeOn: blockBelow,
faceVec: new Vec3(0, 1, 0),
};
}
if (!blockAtPos || blockAtPos.name !== 'air') continue;
if (!blockBelow || blockBelow.boundingBox !== 'block') continue;
// A shulker's lid opens upward — with a solid block above it the
// server refuses to open it ("Block wont open")
if (blockAbove && blockAbove.boundingBox === 'block') continue;
// Never place on top of storage blocks: a box sitting on a chest
// makes that chest unopenable (and abandons it if the cycle fails)
if (/chest|shulker|barrel|hopper|furnace/.test(blockBelow.name)) continue;
return {
position: checkPos,
placeOn: blockBelow,
faceVec: new Vec3(0, 1, 0),
};
}
throw new Error('No suitable placement spot found near bot');
@@ -237,7 +245,7 @@ class ShulkerHandler {
}
// Go to chest and open it
await bot.goTo({ where: chestPos, range: 3 });
await bot.goToMust({ where: chestPos, range: 3 });
const chestBlock = bot.bot.blockAt(chestPos);
if (!chestBlock || !chestBlock.name.includes('chest')) {
throw new Error(`Recovery failed — no chest at ${chestPos}`);
@@ -296,7 +304,7 @@ class ShulkerHandler {
}
// Verify the shulker is actually back in the chest
await bot.goTo({ where: chestPos, range: 3 });
await bot.goToMust({ where: chestPos, range: 3 });
const verifyBlock = bot.bot.blockAt(chestPos);
if (!verifyBlock || !verifyBlock.name.includes('chest')) {
throw new Error('Recovery verification failed — chest not found');
@@ -323,7 +331,7 @@ class ShulkerHandler {
async takeShulkerFromChest(bot, chestPos, chestSlot, shulkerId) {
console.log(`ShulkerHandler: Taking shulker from chest at ${chestPos}, slot ${chestSlot}`);
await bot.goTo({ where: chestPos, range: 3 });
await bot.goToMust({ where: chestPos, range: 3 });
const chestBlock = bot.bot.blockAt(chestPos);
if (!chestBlock || !chestBlock.name.includes('chest')) {
throw new Error(`No chest at ${chestPos} (found: ${chestBlock?.name || 'null'})`);
@@ -476,13 +484,30 @@ class ShulkerHandler {
throw new Error(`Failed to place shulker at ${spot.position} (found: ${placedBlock?.name || 'null'})`);
}
const window = await bot.openContainer(placedBlock);
let window;
try {
window = await bot.openContainer(placedBlock);
} catch (openError) {
// The box is placed but won't open — break it back into
// inventory before retrying, otherwise it's abandoned on the
// ground and the next attempt finds no box in inventory
console.log(`ShulkerHandler: Placed but cannot open (${openError.message}), collecting box back`);
const collected = await this.digAndCollectShulker(bot, spot.position);
if (!collected) {
openError.placedPos = spot.position;
}
throw openError;
}
await bot.bot.waitForTicks(5);
console.log(`ShulkerHandler: Shulker placed and opened at ${spot.position}`);
return { window, placedPos: spot.position };
} catch (error) {
console.log(`ShulkerHandler: Place attempt ${attempt + 1} failed (${error.message})`);
// A box we couldn't collect is sitting on the ground — retrying
// can't succeed (no box in inventory) and callers need placedPos
// to attempt recovery
if (error.placedPos) throw error;
if (attempt < 4) {
// Move the bot a few blocks so findPlacementSpot finds new spots
console.log('ShulkerHandler: Moving to find a better placement spot...');
@@ -524,7 +549,7 @@ class ShulkerHandler {
}
// Navigate back to chest and put shulker back
await bot.goTo({ where: chestPos, range: 3 });
await bot.goToMust({ where: chestPos, range: 3 });
const chestBlock = bot.bot.blockAt(chestPos);
const window = await bot.openContainer(chestBlock);
await sleep(300);
@@ -582,10 +607,12 @@ class ShulkerHandler {
console.log(`ShulkerHandler: Depositing ${count}x ${itemName} into shulker at chest ${chestPos} slot ${chestSlot}`);
let placedPos = null;
let taken = false;
try {
// Step 1: Take shulker from chest (DB immediately marks it in-transit)
const invSlot = await this.takeShulkerFromChest(bot, chestPos, chestSlot, shulkerId);
taken = true;
// Step 2: Place shulker on ground and open it
let shulkerWindow;
@@ -597,7 +624,7 @@ class ShulkerHandler {
console.error('ShulkerHandler: Failed to place shulker, recovering:', placeError.message);
let recovered = false;
try {
await this.returnShulkerToChest(bot, chestPos, chestSlot, null, chestId);
await this.returnShulkerToChest(bot, chestPos, chestSlot, placeError.placedPos || null, chestId);
recovered = true;
} catch (recoverError) {
console.error('ShulkerHandler: Recovery failed:', recoverError.message);
@@ -659,7 +686,7 @@ class ShulkerHandler {
try {
// Shift-click handles stacking optimally — no cursor issues
await bot.bot.clickWindow(i, 0, 1);
await sleep(200);
await bot.bot.waitForTicks(3); // let server confirm the move
// Measure what actually left this slot
const afterItem = shulkerWindow.slots[i];
@@ -672,6 +699,9 @@ class ShulkerHandler {
}
}
// Let server confirm all shift-click moves before closing window
await bot.bot.waitForTicks(4);
// Step 5: Close, break, return to chest (DB synced inside closeBreakReturn)
try {
const updatedSlotItem = await this.closeBreakReturn(bot, shulkerWindow, placedPos, chestPos, chestSlot, chestId);
@@ -691,6 +721,8 @@ class ShulkerHandler {
}
} catch (error) {
// Box never left the chest — nothing to recover, report the real error
if (!taken) throw error;
console.error('ShulkerHandler: Deposit failed, attempting recovery:', error.message);
let recovered = false;
try {
@@ -766,13 +798,17 @@ class ShulkerHandler {
throw new Error(`Failed to place shulker at ${spot.position}`);
}
// Steps 3-5 run with the box placed on the ground — any failure must
// still break it and collect it, or the box is abandoned
const extracted = [];
let inventoryFull = false;
try {
// Step 3: Open the shulker
const shulkerWindow = await bot.openContainer(placedBlock);
await bot.bot.waitForTicks(5);
// Step 4: Move ALL items from shulker into bot inventory
const extracted = [];
let inventoryFull = false;
const shulkerSlotCount = shulkerWindow.inventoryStart;
for (let s = 0; s < shulkerSlotCount; s++) {
@@ -820,10 +856,27 @@ class ShulkerHandler {
await bot.bot.closeWindow(shulkerWindow);
await bot.bot.waitForTicks(30);
} catch (error) {
// Recovery: get the placed box back into inventory before rethrowing
console.error(`ShulkerHandler: Unpack failed mid-cycle (${error.message}), recovering placed box`);
try { await bot.bot.closeWindow(bot.bot.currentWindow); } catch (e) { /* may not be open */ }
await sleep(300);
const recovered = await this.digAndCollectShulker(bot, spot.position);
if (!recovered) {
error.placedPos = spot.position;
console.error(`ShulkerHandler: SHULKER LEFT ON GROUND at ${spot.position}`);
}
throw error;
}
// Step 6: Break the shulker block and pick it up
const collected = await this.digAndCollectShulker(bot, spot.position);
if (!collected) {
console.error('ShulkerHandler: Shulker not found in inventory after breaking');
// Don't return success — the caller assumes the box is back in
// inventory and would store a box that doesn't exist
const error = new Error(`Shulker not collected after unpack at ${spot.position}`);
error.placedPos = spot.position;
throw error;
}
console.log(`ShulkerHandler: Unpacked ${extracted.length} item stacks from shulker`);
@@ -840,10 +893,12 @@ class ShulkerHandler {
console.log(`ShulkerHandler: Withdrawing ${count}x ${itemName} from shulker at chest ${chestPos} slot ${chestSlot}`);
let placedPos = null;
let taken = false;
try {
// Step 1: Take shulker from chest (DB immediately marks it in-transit)
const invSlot = await this.takeShulkerFromChest(bot, chestPos, chestSlot, shulkerId);
taken = true;
// Step 2: Place shulker on ground and open it
let shulkerWindow;
@@ -855,7 +910,7 @@ class ShulkerHandler {
console.error('ShulkerHandler: Failed to place shulker, recovering:', placeError.message);
let recovered = false;
try {
await this.returnShulkerToChest(bot, chestPos, chestSlot, null, chestId);
await this.returnShulkerToChest(bot, chestPos, chestSlot, placeError.placedPos || null, chestId);
recovered = true;
} catch (recoverError) {
console.error('ShulkerHandler: Recovery failed:', recoverError.message);
@@ -901,7 +956,7 @@ class ShulkerHandler {
if (remaining() >= beforeCount) {
// Need the whole stack or more — shift-click is optimal
await bot.bot.clickWindow(s, 0, 1);
await sleep(200);
await bot.bot.waitForTicks(3);
} else {
// Need fewer than the full stack — pick up, right-click exact amount, return rest
// Find an empty inventory slot to place items into
@@ -916,7 +971,7 @@ class ShulkerHandler {
// Left-click to pick up full stack onto cursor
await bot.bot.clickWindow(s, 0, 0);
await sleep(150);
await bot.bot.waitForTicks(2);
// Right-click on empty inventory slot N times to place exactly N items
for (let n = 0; n < remaining(); n++) {
@@ -926,7 +981,7 @@ class ShulkerHandler {
// Left-click back on shulker slot to return the remainder from cursor
await bot.bot.clickWindow(s, 0, 0);
await sleep(150);
await bot.bot.waitForTicks(2);
}
// Measure what actually left this slot
@@ -940,6 +995,9 @@ class ShulkerHandler {
}
}
// Let server confirm all shift-click moves before closing window
await bot.bot.waitForTicks(4);
// Step 4: Close, break, return to chest (DB synced inside closeBreakReturn)
try {
const updatedSlotItem = await this.closeBreakReturn(bot, shulkerWindow, placedPos, chestPos, chestSlot, chestId);
@@ -959,6 +1017,8 @@ class ShulkerHandler {
}
} catch (error) {
// Box never left the chest — nothing to recover, report the real error
if (!taken) throw error;
console.error('ShulkerHandler: Withdraw failed, attempting recovery:', error.message);
let recovered = false;
try {
@@ -980,10 +1040,12 @@ class ShulkerHandler {
console.log(`ShulkerHandler: Withdrawing from shulker slot ${shulkerSlot} at chest ${chestPos} slot ${chestSlot}`);
let placedPos = null;
let taken = false;
try {
// Step 1: Take shulker from chest
const invSlot = await this.takeShulkerFromChest(bot, chestPos, chestSlot, shulkerId);
taken = true;
// Step 2: Place shulker on ground and open it
let shulkerWindow;
@@ -995,7 +1057,7 @@ class ShulkerHandler {
console.error('ShulkerHandler: Failed to place shulker, recovering:', placeError.message);
let recovered = false;
try {
await this.returnShulkerToChest(bot, chestPos, chestSlot, null, chestId);
await this.returnShulkerToChest(bot, chestPos, chestSlot, placeError.placedPos || null, chestId);
recovered = true;
} catch (recoverError) {
console.error('ShulkerHandler: Recovery failed:', recoverError.message);
@@ -1078,6 +1140,8 @@ class ShulkerHandler {
}
} catch (error) {
// Box never left the chest — nothing to recover, report the real error
if (!taken) throw error;
console.error('ShulkerHandler: Slot withdraw failed, attempting recovery:', error.message);
await this.returnShulkerToChest(bot, chestPos, chestSlot, placedPos, chestId);
return { withdrawn: 0, updatedSlotItem: null };