Skip to content

Commit b4f606e

Browse files
shinohara-rinnekomeowww
authored andcommitted
feat(minecraft): autocrafter and recepie planner
1 parent 377d937 commit b4f606e

File tree

2 files changed

+407
-0
lines changed

2 files changed

+407
-0
lines changed

services/minecraft/src/agents/action/tools.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { discard, equip, putInChest, takeFromChest } from '../../skills/actions/
99
import { activateNearestBlock, breakBlockAt, placeBlock } from '../../skills/actions/world-interactions'
1010
import { ActionError } from '../../utils/errors'
1111
import { useLogger } from '../../utils/logger'
12+
import { describeRecipePlan, planRecipe } from '../../utils/recipe-planner'
1213

1314
import * as skills from '../../skills'
1415
import * as world from '../../skills/world'
@@ -427,4 +428,51 @@ export const actionsList: Action[] = [
427428
return `Activated nearest [${type}]`
428429
},
429430
},
431+
{
432+
name: 'recipePlan',
433+
description: 'Plan how to craft an item. Shows the full recipe tree, what resources you have, what you\'re missing, and whether you can craft it now. Use this BEFORE attempting to craft complex items to understand what you need.',
434+
execution: 'parallel',
435+
schema: z.object({
436+
item_name: z.string().describe('The name of the item you want to craft (e.g., "diamond_pickaxe", "oak_planks").'),
437+
amount: z.number().int().min(1).default(1).describe('How many of the item you want to craft.'),
438+
}),
439+
perform: mineflayer => (item_name: string, amount: number = 1): string => {
440+
return pad(describeRecipePlan(mineflayer.bot, item_name, amount))
441+
},
442+
},
443+
{
444+
name: 'autoCraft',
445+
description: 'Automatically craft an item if you have all the required resources. This will check the recipe, verify you have materials, and craft it. Use recipePlan first to see if crafting is possible.',
446+
execution: 'sequential',
447+
schema: z.object({
448+
item_name: z.string().describe('The name of the item to craft.'),
449+
amount: z.number().int().min(1).default(1).describe('How many of the item to craft.'),
450+
}),
451+
perform: mineflayer => async (item_name: string, amount: number = 1) => {
452+
const plan = planRecipe(mineflayer.bot, item_name, amount)
453+
454+
if (plan.status === 'unknown_item') {
455+
throw new ActionError('UNKNOWN', `Unknown item: ${item_name}`)
456+
}
457+
458+
if (!plan.canCraftNow) {
459+
const missingList = Object.entries(plan.missing)
460+
.map(([item, count]) => `${count}x ${item}`)
461+
.join(', ')
462+
throw new ActionError('RESOURCE_MISSING', `Cannot craft ${item_name}: missing ${missingList}`, {
463+
missing: plan.missing,
464+
required: plan.totalRequired,
465+
})
466+
}
467+
468+
// Craft all intermediate steps first, then the final item
469+
for (const step of [...plan.steps].reverse()) {
470+
if (step.action === 'craft') {
471+
await skills.craftRecipe(mineflayer, step.item, Math.ceil(step.amount / (step.amount || 1)))
472+
}
473+
}
474+
475+
return `Successfully crafted ${amount}x ${item_name}`
476+
},
477+
},
430478
]

0 commit comments

Comments
 (0)