diff --git a/src/agent/library/skills.js b/src/agent/library/skills.js index 715455073..669ab4e2d 100644 --- a/src/agent/library/skills.js +++ b/src/agent/library/skills.js @@ -846,12 +846,27 @@ export async function discard(bot, itemName, num=-1) { * await skills.discard(bot, "oak_log"); **/ let discarded = 0; + // Helper: find an item by name anywhere in the inventory slots (including off-hand) + function findItemByName(name) { + // prefer the inventory.items() convenience list + let it = bot.inventory.items().find(i => i.name === name); + if (it) return it; + // fallback: scan all slots (this will include off-hand and armor slots) + const slots = bot.inventory.slots || {}; + for (const key of Object.keys(slots)) { + const slot = slots[key]; + if (slot && slot.name === name) return slot; + } + return null; + } + while (true) { - let item = bot.inventory.items().find(item => item.name === itemName); + let item = findItemByName(itemName); if (!item) { break; } let to_discard = num === -1 ? item.count : Math.min(num - discarded, item.count); + // Use toss with the item type (toss will remove from any slot containing that type) await bot.toss(item.type, null, to_discard); discarded += to_discard; if (num !== -1 && discarded >= num) { diff --git a/src/mindcraft/mindcraft.js b/src/mindcraft/mindcraft.js index a168d03a5..f5c0b4376 100644 --- a/src/mindcraft/mindcraft.js +++ b/src/mindcraft/mindcraft.js @@ -21,7 +21,18 @@ export async function init(host_public=false, port=8080, auto_open_ui=true) { setTimeout(() => { // check if browser listener is already open if (numStateListeners() === 0) { - open('http://localhost:'+port); + // dynamically import `open` only when we need to open the UI so + // running the code (or tests) without the `open` package + // installed won't fail at module import time. + (async () => { + try { + const openModule = await import('open'); + const open = openModule?.default || openModule; + open('http://localhost:'+port); + } catch (e) { + console.warn('`open` package not available, skipping auto-open of UI'); + } + })(); } }, 3000); } diff --git a/tests/test_discard_offhand.js b/tests/test_discard_offhand.js new file mode 100644 index 000000000..5bd3ea058 --- /dev/null +++ b/tests/test_discard_offhand.js @@ -0,0 +1,53 @@ +(async () => { + // lightweight test for skills.discard to ensure items in off-hand slots are found + const path = '../src/agent/library/skills.js'; + const skills = await import(path); + + const tossCalls = []; + + const bot = { + inventory: { + // no items() entries (simulates bug where off-hand isn't included) + items: () => [], + // slots: include an off-hand slot with index commonly 45 in mineflayer (varies) — we don't rely on index meaning + slots: { + 0: null, + 1: null, + 40: { type: 5000, name: 'torch', count: 2 }, // off-hand-like slot + }, + }, + // toss should be called with (type, null, count) + toss: async (type, _null, count) => { + tossCalls.push({ type, count }); + // simulate removing the item from slots + for (const k of Object.keys(bot.inventory.slots)) { + const s = bot.inventory.slots[k]; + if (s && s.type === type) { + s.count -= count; + if (s.count <= 0) bot.inventory.slots[k] = null; + break; + } + } + }, + }; + + console.log('Running discard test (off-hand)'); + const ok = await skills.discard(bot, 'torch', 1); + console.log('discard returned:', ok); + console.log('tossCalls:', JSON.stringify(tossCalls)); + + if (!ok) { + console.error('TEST FAILED: discard returned false'); + process.exit(1); + } + if (tossCalls.length !== 1) { + console.error('TEST FAILED: toss was not called exactly once'); + process.exit(1); + } + if (tossCalls[0].count !== 1) { + console.error('TEST FAILED: toss called with wrong count', tossCalls[0]); + process.exit(1); + } + + console.log('TEST PASSED'); +})();