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) <noreply@anthropic.com>
This commit is contained in:
parent
813f8704bb
commit
513507e941
2 changed files with 102 additions and 10 deletions
|
|
@ -647,18 +647,83 @@ async function handleAction(action, params = {}) {
|
||||||
case 'craft_item': {
|
case 'craft_item': {
|
||||||
const { itemName, count: craftCount } = params;
|
const { itemName, count: craftCount } = params;
|
||||||
const mcData = require('minecraft-data')(bot.version);
|
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
|
// Try to find item — handle both "wooden_pickaxe" and "planks" style names
|
||||||
const craftingTable = bot.findBlock({
|
let item = mcData.itemsByName[itemName];
|
||||||
matching: (block) => block.name.includes('crafting_table'),
|
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,
|
maxDistance: 4,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Check recipes
|
||||||
const recipes = bot.recipesFor(item.id, null, null, craftingTable || undefined);
|
const recipes = bot.recipesFor(item.id, null, null, craftingTable || undefined);
|
||||||
if (recipes.length === 0) throw new Error(`No recipe found for ${itemName}`);
|
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);
|
await bot.craft(recipes[0], craftCount || 1, craftingTable || undefined);
|
||||||
return { crafted: itemName, count: craftCount || 1 };
|
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 ---
|
// --- Use/Activate Block ---
|
||||||
|
|
|
||||||
|
|
@ -214,6 +214,29 @@ class DougBrain(QObject):
|
||||||
|
|
||||||
def on_response(resp: ResponseMessage):
|
def on_response(resp: ResponseMessage):
|
||||||
if resp.status == "success":
|
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
|
# For non-movement actions, complete immediately
|
||||||
if task.action not in ("move_to", "move_relative", "follow_player"):
|
if task.action not in ("move_to", "move_relative", "follow_player"):
|
||||||
self._waiting_for_action = False
|
self._waiting_for_action = False
|
||||||
|
|
@ -221,7 +244,11 @@ class DougBrain(QObject):
|
||||||
else:
|
else:
|
||||||
self._waiting_for_action = False
|
self._waiting_for_action = False
|
||||||
self._tasks.cancel()
|
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)
|
self._ws.send_request(task.action, task.params, on_response)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue