From 513507e9413f442eda74a6f36ad97b791d922ea5 Mon Sep 17 00:00:00 2001 From: roberts Date: Mon, 30 Mar 2026 13:53:48 -0500 Subject: [PATCH] Smart crafting: walk to table, check materials, report results Bridge craft_item now: - Fuzzy-matches item names (wooden_pickaxe, wood_pickaxe, etc.) - Searches for crafting table within 32 blocks - Walks to crafting table if found but not close enough - Checks if materials are available - Reports specific failure reasons to chat: "I need a crafting table" / "I don't have the materials" - Reports success: "Done! Crafted 1 wooden pickaxe." Brain now: - Reports craft success/failure to in-game chat - Reports any HIGH priority task failure to chat - Handles craft_item as async (waits for pathfinding + crafting) Co-Authored-By: Claude Opus 4.6 (1M context) --- bridge/src/index.js | 83 ++++++++++++++++++++++++++++++++++++++----- dougbot/core/brain.py | 29 ++++++++++++++- 2 files changed, 102 insertions(+), 10 deletions(-) diff --git a/bridge/src/index.js b/bridge/src/index.js index 4ffc66e..ca36d23 100644 --- a/bridge/src/index.js +++ b/bridge/src/index.js @@ -647,18 +647,83 @@ async function handleAction(action, params = {}) { case 'craft_item': { const { itemName, count: craftCount } = params; const mcData = require('minecraft-data')(bot.version); - const item = mcData.itemsByName[itemName]; - if (!item) throw new Error(`Unknown item: ${itemName}`); - // Find crafting table nearby if needed - const craftingTable = bot.findBlock({ - matching: (block) => block.name.includes('crafting_table'), - maxDistance: 4, + // Try to find item — handle both "wooden_pickaxe" and "planks" style names + let item = mcData.itemsByName[itemName]; + if (!item) { + // Try common Bedrock name variants + const variants = [ + itemName, + itemName.replace('wooden_', 'wood_'), + itemName.replace('wood_', 'wooden_'), + `minecraft:${itemName}`, + itemName.replace('_', ''), + ]; + for (const v of variants) { + item = mcData.itemsByName[v]; + if (item) break; + } + // Try fuzzy match + if (!item) { + for (const [name, data] of Object.entries(mcData.itemsByName)) { + if (name.includes(itemName) || itemName.includes(name)) { + item = data; + break; + } + } + } + } + if (!item) { + return { crafted: false, error: `I don't know what "${itemName.replace(/_/g, ' ')}" is.` }; + } + + // Step 1: Find crafting table (search wider radius) + let craftingTable = bot.findBlock({ + matching: (block) => block.name.includes('crafting_table') || block.name.includes('workbench'), + maxDistance: 32, }); + + // Step 2: Walk to crafting table if found but not close enough + if (craftingTable) { + const dist = bot.entity.position.distanceTo(craftingTable.position); + if (dist > 4) { + log('client', 'INFO', `Walking to crafting table at ${craftingTable.position}`); + bot.pathfinder.setGoal(new GoalNear( + craftingTable.position.x, craftingTable.position.y, craftingTable.position.z, 2 + )); + // Wait for arrival (up to 15 seconds) + await new Promise((resolve) => { + const check = setInterval(() => { + const d = bot.entity.position.distanceTo(craftingTable.position); + if (d <= 3) { clearInterval(check); resolve(); } + }, 500); + setTimeout(() => { clearInterval(check); resolve(); }, 15000); + }); + // Re-find the table now that we're close + craftingTable = bot.findBlock({ + matching: (block) => block.name.includes('crafting_table') || block.name.includes('workbench'), + maxDistance: 4, + }); + } + } + + // Step 3: Check recipes const recipes = bot.recipesFor(item.id, null, null, craftingTable || undefined); - if (recipes.length === 0) throw new Error(`No recipe found for ${itemName}`); - await bot.craft(recipes[0], craftCount || 1, craftingTable || undefined); - return { crafted: itemName, count: craftCount || 1 }; + if (recipes.length === 0) { + const reason = craftingTable + ? `I don't have the materials to craft ${itemName.replace(/_/g, ' ')}.` + : `I need a crafting table to make ${itemName.replace(/_/g, ' ')}.`; + return { crafted: false, error: reason }; + } + + // Step 4: Craft! + try { + await bot.craft(recipes[0], craftCount || 1, craftingTable || undefined); + log('client', 'INFO', `Crafted ${craftCount || 1}x ${itemName}`); + return { crafted: true, item: itemName, count: craftCount || 1 }; + } catch (e) { + return { crafted: false, error: `Crafting failed: ${e.message}` }; + } } // --- Use/Activate Block --- diff --git a/dougbot/core/brain.py b/dougbot/core/brain.py index 8f9cc88..e6a8060 100644 --- a/dougbot/core/brain.py +++ b/dougbot/core/brain.py @@ -214,6 +214,29 @@ class DougBrain(QObject): def on_response(resp: ResponseMessage): if resp.status == "success": + data = resp.data or {} + + # Handle craft results specifically + if task.action == "craft_item": + self._waiting_for_action = False + if data.get("crafted"): + item = data.get("item", "item").replace("_", " ") + self._ws.send_request("send_chat", { + "message": f"Done! Crafted {data.get('count', 1)} {item}." + }) + self._tasks.complete() + else: + error = data.get("error", "Something went wrong.") + self._ws.send_request("send_chat", {"message": error}) + self._tasks.cancel() + return + + # Handle other results with error messages + if task.action in ("open_chest", "dig_block", "equip_item"): + self._waiting_for_action = False + self._tasks.complete() + return + # For non-movement actions, complete immediately if task.action not in ("move_to", "move_relative", "follow_player"): self._waiting_for_action = False @@ -221,7 +244,11 @@ class DougBrain(QObject): else: self._waiting_for_action = False self._tasks.cancel() - log.debug(f"Action failed: {resp.error}") + error = resp.error or "Something went wrong" + log.debug(f"Action failed: {error}") + # Report failure to chat for player-initiated tasks + if task.priority >= Priority.HIGH: + self._ws.send_request("send_chat", {"message": error}) self._ws.send_request(task.action, task.params, on_response)