From 4b2d06db3b6f3253d7fabd8b0c64adec9f5883b1 Mon Sep 17 00:00:00 2001 From: Rin Date: Fri, 9 Jan 2026 00:16:31 +0800 Subject: [PATCH] chore(minecraft): various legacy code cleanup --- services/minecraft/src/agents/action/tools.ts | 165 ------------------ .../src/cognitive/conscious/task-manager.ts | 31 ++-- .../minecraft/src/libs/mineflayer/core.ts | 142 --------------- .../minecraft/src/skills/actions/ensure.ts | 81 +++++---- services/minecraft/src/skills/crafting.ts | 69 ++++---- 5 files changed, 104 insertions(+), 384 deletions(-) diff --git a/services/minecraft/src/agents/action/tools.ts b/services/minecraft/src/agents/action/tools.ts index 555e6f6d6..f0a546c7e 100644 --- a/services/minecraft/src/agents/action/tools.ts +++ b/services/minecraft/src/agents/action/tools.ts @@ -89,21 +89,6 @@ export const actionsList: Action[] = [ return pad(`NEARBY_ENTITIES${result.length ? `\n${result.join('\n')}` : ': none'}`) }, }, - // getNewAction(): Action { - // return { - // name: 'newAction', - // description: 'Perform new and unknown custom behaviors that are not available as a command.', - // schema: z.object({ - // prompt: z.string().describe('A natural language prompt to guide code generation. Make a detailed step-by-step plan.'), - // }), - // perform: (mineflayer: BotContext) => async (prompt: string) => { - // if (!settings.allow_insecure_coding) - // return 'newAction not allowed! Code writing is disabled in settings. Notify the user.' - // return await ctx.coder.generateCode(mineflayer.history) - // }, - // } - // }, - // todo: must 'stop now' can be used to stop the agent { name: 'stop', @@ -113,53 +98,13 @@ export const actionsList: Action[] = [ // await ctx.actions.stop() // ctx.clearBotLogs() // ctx.actions.cancelResume() - // ctx.bot.emit('idle') - mineflayer.emit('interrupt') - const msg = 'Agent stopped.' // if (mineflayer.self_prompter.on) // msg += ' Self-prompting still active.' return msg }, }, - - // getStfuAction(): Action { - // return { - // name: 'stfu', - // description: 'Stop all chatting and self prompting, but continue current action.', - // schema: z.object({}), - // perform: (mineflayer: BotContext) => async () => { - // ctx.openChat('Shutting up.') - // ctx.shutUp() - // return 'Shutting up.' - // }, - // } - // }, - - // getRestartAction(): Action { - // return { - // name: 'restart', - // description: 'Restart the agent process.', - // schema: z.object({}), - // perform: (mineflayer: BotContext) => async () => { - // ctx.cleanKill() - // return 'Restarting agent...' - // }, - // } - // }, - - // getClearChatAction(): Action { - // return { - // name: 'clearChat', - // description: 'Clear the chat history.', - // schema: z.object({}), - // perform: (mineflayer: BotContext) => async () => { - // ctx.history.clear() - // return `${ctx.name}'s chat history was cleared, starting new conversation from scratch.` - // }, - // } - // }, { name: 'goToPlayer', description: 'Go to the given player.', @@ -172,7 +117,6 @@ export const actionsList: Action[] = [ return 'Moving to player...' }, }, - { name: 'followPlayer', description: 'Endlessly follow the given player.', @@ -185,7 +129,6 @@ export const actionsList: Action[] = [ return 'Following player...' }, }, - { name: 'goToCoordinates', description: 'Go to the given x, y, z location.', @@ -200,7 +143,6 @@ export const actionsList: Action[] = [ return 'Moving to coordinates...' }, }, - { name: 'searchForBlock', description: 'Find and go to the nearest block of a given type in a given range.', @@ -213,7 +155,6 @@ export const actionsList: Action[] = [ return 'Searching for block...' }, }, - { name: 'searchForEntity', description: 'Find and go to the nearest entity of a given type in a given range.', @@ -226,7 +167,6 @@ export const actionsList: Action[] = [ return 'Searching for entity...' }, }, - { name: 'moveAway', description: 'Move away from the current location in any direction by a given distance.', @@ -238,7 +178,6 @@ export const actionsList: Action[] = [ return 'Moving away...' }, }, - { name: 'givePlayer', description: 'Give the specified item to the given player.', @@ -252,7 +191,6 @@ export const actionsList: Action[] = [ return 'Giving items to player...' }, }, - { name: 'consume', description: 'Eat/drink the given item.', @@ -264,7 +202,6 @@ export const actionsList: Action[] = [ return 'Consuming item...' }, }, - { name: 'equip', description: 'Equip the given item.', @@ -276,7 +213,6 @@ export const actionsList: Action[] = [ return 'Equipping item...' }, }, - { name: 'putInChest', description: 'Put the given item in the nearest chest.', @@ -289,7 +225,6 @@ export const actionsList: Action[] = [ return 'Putting items in chest...' }, }, - { name: 'takeFromChest', description: 'Take the given items from the nearest chest.', @@ -302,7 +237,6 @@ export const actionsList: Action[] = [ return 'Taking items from chest...' }, }, - { name: 'viewChest', description: 'View the items/counts of the nearest chest.', @@ -312,7 +246,6 @@ export const actionsList: Action[] = [ return 'Viewing chest contents...' }, }, - { name: 'discard', description: 'Discard the given item from the inventory.', @@ -325,7 +258,6 @@ export const actionsList: Action[] = [ return 'Discarding items...' }, }, - { name: 'collectBlocks', description: 'Collect the nearest blocks of a given type.', @@ -338,7 +270,6 @@ export const actionsList: Action[] = [ return 'Collecting blocks...' }, }, - { name: 'craftRecipe', description: 'Craft the given recipe a given number of times.', @@ -351,7 +282,6 @@ export const actionsList: Action[] = [ return 'Crafting items...' }, }, - { name: 'smeltItem', description: 'Smelt the given item the given number of times.', @@ -364,7 +294,6 @@ export const actionsList: Action[] = [ return 'Smelting items...' }, }, - { name: 'clearFurnace', description: 'Take all items out of the nearest furnace.', @@ -374,7 +303,6 @@ export const actionsList: Action[] = [ return 'Clearing furnace...' }, }, - { name: 'placeHere', description: 'Place a given block in the current location. Do NOT use to build structures, only use for single blocks/torches.', @@ -387,7 +315,6 @@ export const actionsList: Action[] = [ return 'Placing block...' }, }, - { name: 'attack', description: 'Attack and kill the nearest entity of a given type.', @@ -399,7 +326,6 @@ export const actionsList: Action[] = [ return 'Attacking entity...' }, }, - { name: 'attackPlayer', description: 'Attack a specific player until they die or run away. Remember this is just a game and does not cause real life harm.', @@ -416,7 +342,6 @@ export const actionsList: Action[] = [ return 'Attacking player...' }, }, - { name: 'goToBed', description: 'Go to the nearest bed and sleep.', @@ -426,7 +351,6 @@ export const actionsList: Action[] = [ return 'Going to bed...' }, }, - { name: 'activate', description: 'Activate the nearest object of a given type.', @@ -438,7 +362,6 @@ export const actionsList: Action[] = [ return 'Activating block...' }, }, - { name: 'stay', description: 'Stay in the current location no matter what. Pauses all modes.', @@ -450,92 +373,4 @@ export const actionsList: Action[] = [ return 'Staying in place...' }, }, - // getSetModeAction(): Action { - // return { - // name: 'setMode', - // description: 'Set a mode to on or off. A mode is an automatic behavior that constantly checks and responds to the environment.', - // schema: z.object({ - // mode_name: z.string().describe('The name of the mode to enable.'), - // on: z.boolean().describe('Whether to enable or disable the mode.'), - // }), - // perform: (mineflayer: BotContext) => async (mode_name: string, on: boolean) => { - // const modes = ctx.bot.modes - // if (!modes.exists(mode_name)) - // return `Mode ${mode_name} does not exist.${modes.getDocs()}` - // if (modes.isOn(mode_name) === on) - // return `Mode ${mode_name} is already ${on ? 'on' : 'off'}.` - // modes.setOn(mode_name, on) - // return `Mode ${mode_name} is now ${on ? 'on' : 'off'}.` - // }, - // } - // }, - - // getGoalAction(): Action { - // return { - // name: 'goal', - // description: 'Set a goal prompt to endlessly work towards with continuous self-prompting.', - // schema: z.object({ - // selfPrompt: z.string().describe('The goal prompt.'), - // }), - // perform: (mineflayer: BotContext) => async (prompt: string) => { - // if (convoManager.inConversation()) { - // ctx.self_prompter.setPrompt(prompt) - // convoManager.scheduleSelfPrompter() - // } - // else { - // ctx.self_prompter.start(prompt) - // } - // return 'Goal set...' - // }, - // } - // }, - - // getEndGoalAction(): Action { - // return { - // name: 'endGoal', - // description: 'Call when you have accomplished your goal. It will stop self-prompting and the current action.', - // schema: z.object({}), - // perform: (mineflayer: BotContext) => async () => { - // ctx.self_prompter.stop() - // convoManager.cancelSelfPrompter() - // return 'Self-prompting stopped.' - // }, - // } - // }, - - // getStartConversationAction(): Action { - // return { - // name: 'startConversation', - // description: 'Start a conversation with a player. Use for bots only.', - // schema: z.object({ - // player_name: z.string().describe('The name of the player to send the message to.'), - // message: z.string().describe('The message to send.'), - // }), - // perform: (mineflayer: BotContext) => async (player_name: string, message: string) => { - // if (!convoManager.isOtherAgent(player_name)) - // return `${player_name} is not a bot, cannot start conversation.` - // if (convoManager.inConversation() && !convoManager.inConversation(player_name)) - // convoManager.forceEndCurrentConversation() - // else if (convoManager.inConversation(player_name)) - // ctx.history.add('system', `You are already in conversation with ${player_name}. Don't use this command to talk to them.`) - // convoManager.startConversation(player_name, message) - // }, - // } - // }, - - // getEndConversationAction(): Action { - // return { - // name: 'endConversation', - // description: 'End the conversation with the given player.', - // schema: z.object({ - // player_name: z.string().describe('The name of the player to end the conversation with.'), - // }), - // perform: (mineflayer: BotContext) => async (player_name: string) => { - // if (!convoManager.inConversation(player_name)) - // return `Not in conversation with ${player_name}.` - // convoManager.endConversation(player_name) - // return `Converstaion with ${player_name} ended.` - // }, - // } - // }, ] diff --git a/services/minecraft/src/cognitive/conscious/task-manager.ts b/services/minecraft/src/cognitive/conscious/task-manager.ts index f1c27837e..564acc680 100644 --- a/services/minecraft/src/cognitive/conscious/task-manager.ts +++ b/services/minecraft/src/cognitive/conscious/task-manager.ts @@ -30,7 +30,8 @@ export class TaskManager { if (!this.primaryTask) { this.primaryTask = task this.logger.withFields({ taskId: task.id, goal, type: 'primary' }).log('TaskManager: Created new primary task') - } else { + } + else { this.secondaryTasks.set(task.id, task) this.logger.withFields({ taskId: task.id, goal, type: 'secondary' }).log('TaskManager: Created new secondary task') } @@ -94,7 +95,7 @@ export class TaskManager { // We don't remove it yet, we wait for completeTask to be called this.addToHistory(task) - + // Cleanup reference immediately to allow new primary tasks if this was primary? // No, we should wait for the orchestrator to call completeTask/cleanup. } @@ -113,14 +114,16 @@ export class TaskManager { */ public completeTask(taskId: string): void { const task = this.getTaskById(taskId) - if (!task) return + if (!task) + return this.logger.withFields({ taskId: task.id }).log('TaskManager: Task completed') this.addToHistory(task) if (this.primaryTask?.id === taskId) { this.primaryTask = null - } else { + } + else { this.secondaryTasks.delete(taskId) } } @@ -143,7 +146,8 @@ export class TaskManager { * Get a task by ID */ public getTaskById(taskId: string): TaskContext | null { - if (this.primaryTask?.id === taskId) return this.primaryTask + if (this.primaryTask?.id === taskId) + return this.primaryTask return this.secondaryTasks.get(taskId) || null } @@ -160,9 +164,12 @@ export class TaskManager { const elapsedSeconds = Math.floor((Date.now() - startTime) / 1000) lines.push(`[PRIMARY TASK] (${status.toUpperCase()}): "${goal}"`) lines.push(`- Duration: ${elapsedSeconds}s`) - if (currentStep) lines.push(`- Step: ${currentStep}`) - if (plan) lines.push(`- Plan: ${plan.steps.length} steps (${plan.status})`) - } else { + if (currentStep) + lines.push(`- Step: ${currentStep}`) + if (plan) + lines.push(`- Plan: ${plan.steps.length} steps (${plan.status})`) + } + else { lines.push('No primary task active.') } @@ -182,7 +189,8 @@ export class TaskManager { */ public getAllActiveTasks(): TaskContext[] { const tasks: TaskContext[] = [] - if (this.primaryTask) tasks.push(this.primaryTask) + if (this.primaryTask) + tasks.push(this.primaryTask) tasks.push(...this.secondaryTasks.values()) return tasks } @@ -200,8 +208,9 @@ export class TaskManager { private addToHistory(task: TaskContext): void { // Only add if not already in history (simple check) - if (this.taskHistory.some(t => t.id === task.id)) return - + if (this.taskHistory.some(t => t.id === task.id)) + return + this.taskHistory.push(task) if (this.taskHistory.length > this.maxHistorySize) { this.taskHistory.shift() diff --git a/services/minecraft/src/libs/mineflayer/core.ts b/services/minecraft/src/libs/mineflayer/core.ts index 41b8dbd9d..6d9d5332c 100644 --- a/services/minecraft/src/libs/mineflayer/core.ts +++ b/services/minecraft/src/libs/mineflayer/core.ts @@ -56,26 +56,6 @@ export class Mineflayer extends EventEmitter { public static async asyncBuild(options: MineflayerOptions) { const mineflayer = new Mineflayer(options) - mineflayer.bot.on('messagestr', async (message, _, jsonMsg) => { - // jsonMsg.translate: - // - death.attack.player - // message: - // - was slain by - // - drowned - if (jsonMsg.translate && jsonMsg.translate.startsWith('death') && message.startsWith(mineflayer.username)) { - const deathPos = mineflayer.bot.entity.position - - // mineflayer.memory_bank.rememberPlace('last_death_position', deathPos.x, deathPos.y, deathPos.z) - let deathPosStr: string | undefined - if (deathPos) { - deathPosStr = `x: ${deathPos.x.toFixed(2)}, y: ${deathPos.y.toFixed(2)}, z: ${deathPos.x.toFixed(2)}` - } - - const dimension = mineflayer.bot.game.dimension - await mineflayer.handleMessage('system', `You died at position ${deathPosStr || 'unknown'} in the ${dimension} dimension with the final message: '${message}'. Your place of death has been saved as 'last_death_position' if you want to return. Previous actions were stopped and you have re-spawned.`) - } - }) - mineflayer.bot.once('resourcePack', () => { mineflayer.bot.acceptResourcePack() }) @@ -223,126 +203,4 @@ export class Mineflayer extends EventEmitter { } }) } - - private async handleMessage(_source: string, _message: string, _maxResponses: number = Infinity) { - // if (!source || !message) { - // console.warn('Received empty message from', source); - // return false; - // } - - // let used_command = false; - // if (maxResponses === null) { - // maxResponses = settings.max_commands === -1 ? Infinity : settings.max_commands; - // } - // if (maxResponses === -1) { - // maxResponses = Infinity; - // } - - // const self_prompt = source === 'system' || source === ctx.botName; - // const from_other_bot = convoManager.isOtherAgent(source); - - // if (!self_prompt && !from_other_bot) { // from user, check for forced commands - // const user_command_name = containsCommand(message); - // if (user_command_name) { - // if (!commandExists(user_command_name)) { - // this.routeResponse(source, `Command '${user_command_name}' does not exist.`); - // return false; - // } - // this.routeResponse(source, `*${source} used ${user_command_name.substring(1)}*`); - // if (user_command_name === '!newAction') { - // // all user-initiated commands are ignored by the bot except for this one - // // add the preceding message to the history to give context for newAction - // this.history.add(source, message); - // } - // let execute_res = await executeCommand(this, message); - // if (execute_res) - // this.routeResponse(source, execute_res); - // return true; - // } - // } - - // if (from_other_bot) - // this.last_sender = source; - - // // Now translate the message - // message = await handleEnglishTranslation(message); - // console.log('received message from', source, ':', message); - - // const checkInterrupt = () => this.self_prompter.shouldInterrupt(self_prompt) || this.shut_up || convoManager.responseScheduledFor(source); - - // let behavior_log = this.bot.modes.flushBehaviorLog(); - // if (behavior_log.trim().length > 0) { - // const MAX_LOG = 500; - // if (behavior_log.length > MAX_LOG) { - // behavior_log = '...' + behavior_log.substring(behavior_log.length - MAX_LOG); - // } - // behavior_log = 'Recent behaviors log: \n' + behavior_log.substring(behavior_log.indexOf('\n')); - // await this.history.add('system', behavior_log); - // } - - // // Handle other user messages - // await this.history.add(source, message); - // this.history.save(); - - // if (!self_prompt && this.self_prompter.on) // message is from user during self-prompting - // maxResponses = 1; // force only respond to this message, then let self-prompting take over - // for (let i=0; i 0) - // chat_message = `${pre_message} ${chat_message}`; - // this.routeResponse(source, chat_message); - // } - - // let execute_res = await executeCommand(this, res); - - // console.log('Agent executed:', command_name, 'and got:', execute_res); - // used_command = true; - - // if (execute_res) - // this.history.add('system', execute_res); - // else - // break; - // } - // else { // conversation response - // this.history.add(this.name, res); - // this.routeResponse(source, res); - // break; - // } - - // this.history.save(); - // } - - // return used_command; - } } diff --git a/services/minecraft/src/skills/actions/ensure.ts b/services/minecraft/src/skills/actions/ensure.ts index 504cb8e13..1c5e2b375 100644 --- a/services/minecraft/src/skills/actions/ensure.ts +++ b/services/minecraft/src/skills/actions/ensure.ts @@ -179,7 +179,8 @@ export async function ensureSticks(mineflayer: Mineflayer, neededAmount: number) sticksCount = getItemCount(mineflayer, 'stick') } - if (sticksCount >= neededAmount) return true + if (sticksCount >= neededAmount) + return true throw new ActionError('RESOURCE_MISSING', 'Failed to ensure sticks', { needed: neededAmount, current: sticksCount }) } @@ -188,7 +189,7 @@ export async function ensureSticks(mineflayer: Mineflayer, neededAmount: number) export async function ensureChests(mineflayer: Mineflayer, quantity: number = 1): Promise { logger.log(`Bot: Checking for ${quantity} chest(s)...`) - let chestCount = getItemCount(mineflayer, 'chest') + const chestCount = getItemCount(mineflayer, 'chest') if (chestCount >= quantity) { return true @@ -196,11 +197,11 @@ export async function ensureChests(mineflayer: Mineflayer, quantity: number = 1) await ensurePlanks(mineflayer, 8 * (quantity - chestCount)) const crafted = await craftRecipe(mineflayer, 'chest', quantity - chestCount) - + if (!crafted) { throw new ActionError('CRAFTING_FAILED', 'Failed to craft chests') } - + return true } @@ -208,7 +209,7 @@ export async function ensureChests(mineflayer: Mineflayer, quantity: number = 1) export async function ensureFurnaces(mineflayer: Mineflayer, quantity: number = 1): Promise { logger.log(`Bot: Checking for ${quantity} furnace(s)...`) - let furnaceCount = getItemCount(mineflayer, 'furnace') + const furnaceCount = getItemCount(mineflayer, 'furnace') if (furnaceCount >= quantity) { return true @@ -217,15 +218,16 @@ export async function ensureFurnaces(mineflayer: Mineflayer, quantity: number = const stoneNeeded = 8 * (quantity - furnaceCount) try { await ensureCobblestone(mineflayer, stoneNeeded) - } catch (e) { - throw new ActionError('RESOURCE_MISSING', 'Failed to gather cobblestone for furnace', { error: e }) + } + catch (e) { + throw new ActionError('RESOURCE_MISSING', 'Failed to gather cobblestone for furnace', { error: e }) } const crafted = await craftRecipe(mineflayer, 'furnace', quantity - furnaceCount) if (!crafted) { throw new ActionError('CRAFTING_FAILED', 'Failed to craft furnace') } - + return true } @@ -233,7 +235,7 @@ export async function ensureFurnaces(mineflayer: Mineflayer, quantity: number = export async function ensureTorches(mineflayer: Mineflayer, quantity: number = 1): Promise { logger.log(`Bot: Checking for ${quantity} torch(es)...`) - let torchCount = getItemCount(mineflayer, 'torch') + const torchCount = getItemCount(mineflayer, 'torch') if (torchCount >= quantity) { return true @@ -241,11 +243,12 @@ export async function ensureTorches(mineflayer: Mineflayer, quantity: number = 1 const needed = quantity - torchCount await ensureSticks(mineflayer, Math.ceil(needed / 4)) - + try { await ensureCoal(mineflayer, Math.ceil(needed / 4)) - } catch (e) { - throw new ActionError('RESOURCE_MISSING', 'Failed to gather coal for torches', { error: e }) + } + catch (e) { + throw new ActionError('RESOURCE_MISSING', 'Failed to gather coal for torches', { error: e }) } const crafted = await craftRecipe(mineflayer, 'torch', Math.ceil(needed / 4)) @@ -261,19 +264,21 @@ export async function ensureCampfire(mineflayer: Mineflayer): Promise { logger.log('Bot: Checking for a campfire...') const hasCampfire = getItemCount(mineflayer, 'campfire') > 0 - if (hasCampfire) return true + if (hasCampfire) + return true await ensurePlanks(mineflayer, 3) await ensureSticks(mineflayer, 3) try { await ensureCoal(mineflayer, 1) - } catch (e) { - throw new ActionError('RESOURCE_MISSING', 'Failed to gather coal/charcoal for campfire', { error: e }) + } + catch (e) { + throw new ActionError('RESOURCE_MISSING', 'Failed to gather coal/charcoal for campfire', { error: e }) } const crafted = await craftRecipe(mineflayer, 'campfire', 1) if (!crafted) { - throw new ActionError('CRAFTING_FAILED', 'Failed to craft campfire') + throw new ActionError('CRAFTING_FAILED', 'Failed to craft campfire') } return true @@ -315,7 +320,8 @@ export async function ensureCobblestone(mineflayer: Mineflayer, requiredCobblest cobblestoneCount = getItemCount(mineflayer, 'cobblestone') } - if (cobblestoneCount >= requiredCobblestone) return true + if (cobblestoneCount >= requiredCobblestone) + return true throw new ActionError('RESOURCE_MISSING', 'Could not gather enough cobblestone', { required: requiredCobblestone, current: cobblestoneCount }) } @@ -347,7 +353,8 @@ export async function ensureCoal(mineflayer: Mineflayer, neededAmount: number, m coalCount = getItemCount(mineflayer, 'coal') } - if (coalCount >= neededAmount) return true + if (coalCount >= neededAmount) + return true throw new ActionError('RESOURCE_MISSING', 'Could not gather enough coal', { required: neededAmount, current: coalCount }) } @@ -404,31 +411,37 @@ async function ensureTool(mineflayer: Mineflayer, toolType: ToolType, quantity: try { await ensureCraftingTable(mineflayer) await ensureSticks(mineflayer, 2) - + const crafted = await craftRecipe(mineflayer, toolRecipe, 1) if (crafted) { toolCount++ mineflayer.bot.chat(`I have crafted a ${material} ${toolType}.`) - if (toolCount >= quantity) return true + if (toolCount >= quantity) + return true } - } catch (err) { - if (err instanceof ActionError && err.code === 'RESOURCE_MISSING') { - // Just fall through to next material if resources missing - } else { - logger.error(`Failed to craft ${material} ${toolType}, trying next material.`) - } } - } else if (material === 'wooden') { + catch (err) { + if (err instanceof ActionError && err.code === 'RESOURCE_MISSING') { + // Just fall through to next material if resources missing + } + else { + logger.error(`Failed to craft ${material} ${toolType}, trying next material.`) + } + } + } + else if (material === 'wooden') { // Last resort: make wooden tools // This will try to gather wood if needed, or throw if it fails try { - await ensurePlanks(mineflayer, 4) - await ensureCraftingTable(mineflayer) - await ensureSticks(mineflayer, 2) - const crafted = await craftRecipe(mineflayer, `wooden_${toolType}`, 1) - if (crafted) return true - } catch (err) { - throw new ActionError('CRAFTING_FAILED', `Could not craft any ${toolType}`, { error: err }) + await ensurePlanks(mineflayer, 4) + await ensureCraftingTable(mineflayer) + await ensureSticks(mineflayer, 2) + const crafted = await craftRecipe(mineflayer, `wooden_${toolType}`, 1) + if (crafted) + return true + } + catch (err) { + throw new ActionError('CRAFTING_FAILED', `Could not craft any ${toolType}`, { error: err }) } } } diff --git a/services/minecraft/src/skills/crafting.ts b/services/minecraft/src/skills/crafting.ts index ba4877495..3c152299f 100644 --- a/services/minecraft/src/skills/crafting.ts +++ b/services/minecraft/src/skills/crafting.ts @@ -70,8 +70,8 @@ export async function craftRecipe( ) const recipes = mineflayer.bot.recipesFor(itemId, null, 1, craftingTable) if (!recipes || recipes.length === 0) { - // If we have a crafting table but still no recipes, we are missing materials - return false // Let the caller decide or fall through + // If we have a crafting table but still no recipes, we are missing materials + return false // Let the caller decide or fall through } success = await attemptCraft(recipes, craftingTable) } @@ -81,13 +81,14 @@ export async function craftRecipe( (err as Error).message }`, ) - if (err instanceof ActionError) throw err + if (err instanceof ActionError) + throw err } attempts++ } if (!success) { - throw new ActionError('NAVIGATION_FAILED', 'Could not reach crafting table') + throw new ActionError('NAVIGATION_FAILED', 'Could not reach crafting table') } return success @@ -129,19 +130,19 @@ export async function craftRecipe( logger.log(`Step 1: Try to craft without a crafting table`) const recipes = mineflayer.bot.recipesFor(itemId, null, 1, null) if (recipes && recipes.length > 0) { - // We have recipes without table - if (await attemptCraft(recipes)) { - return true - } + // We have recipes without table + if (await attemptCraft(recipes)) { + return true + } } // RECURSION GUARD: - // If we failed to craft basic items (planks, sticks) without a table, + // If we failed to craft basic items (planks, sticks) without a table, // do NOT try to find a table. These items do not need a table. // Seeking a table often triggers "ensureCraftingTable" -> "ensurePlanks" -> infinite loop. if (itemName.includes('planks') || itemName === 'stick' || itemName === 'crafting_table') { - logger.log(`Recursion Guard: Skipping crafting table search for basic item: ${itemName}`) - throw new ActionError('RESOURCE_MISSING', `Cannot craft ${itemName} - missing ingredients`, { item: itemName }) + logger.log(`Recursion Guard: Skipping crafting table search for basic item: ${itemName}`) + throw new ActionError('RESOURCE_MISSING', `Cannot craft ${itemName} - missing ingredients`, { item: itemName }) } // Step 2: Find and use a crafting table @@ -151,7 +152,7 @@ export async function craftRecipe( if (await findAndUseCraftingTable(craftingTableRange)) { return true } - + // If we got here, maybe we didn't have recipes even with a table? // Let's verify if resources are missing // We can check recipes again assuming table is available (which we tried to ensure) @@ -172,7 +173,7 @@ export async function smeltItem(mineflayer: Mineflayer, itemName: string, num = ] if (!itemName.includes('raw') && !foods.includes(itemName)) { throw new ActionError('CRAFTING_FAILED', `Cannot smelt ${itemName}, must be a "raw" item`) - } + } let placedFurnace = false let furnaceBlock = getNearestBlock(mineflayer, 'furnace', 32) @@ -192,9 +193,9 @@ export async function smeltItem(mineflayer: Mineflayer, itemName: string, num = } } if (!furnaceBlock) { - throw new ActionError('RESOURCE_MISSING', 'There is no furnace nearby and I have no furnace to place') + throw new ActionError('RESOURCE_MISSING', 'There is no furnace nearby and I have no furnace to place') } - + if (mineflayer.bot.entity.position.distanceTo(furnaceBlock.position) > 4) { await goToNearestBlock(mineflayer, 'furnace', 4, 32) } @@ -209,14 +210,16 @@ export async function smeltItem(mineflayer: Mineflayer, itemName: string, num = && inputItem.type !== getItemId(itemName) && inputItem.count > 0 ) { - if (placedFurnace) await collectBlock(mineflayer, 'furnace', 1) + if (placedFurnace) + await collectBlock(mineflayer, 'furnace', 1) throw new ActionError('CRAFTING_FAILED', `The furnace is currently smelting ${getItemName(inputItem.type)}`) } - + // Check if the bot has enough items to smelt const invCounts = getInventoryCounts(mineflayer) if (!invCounts[itemName] || invCounts[itemName] < num) { - if (placedFurnace) await collectBlock(mineflayer, 'furnace', 1) + if (placedFurnace) + await collectBlock(mineflayer, 'furnace', 1) throw new ActionError('RESOURCE_MISSING', `I do not have enough ${itemName} to smelt`, { required: num }) } @@ -227,20 +230,22 @@ export async function smeltItem(mineflayer: Mineflayer, itemName: string, num = .find(item => item.name === 'coal' || item.name === 'charcoal') const putFuel = Math.ceil(num / 8) if (!fuel || fuel.count < putFuel) { - if (placedFurnace) await collectBlock(mineflayer, 'furnace', 1) + if (placedFurnace) + await collectBlock(mineflayer, 'furnace', 1) throw new ActionError('RESOURCE_MISSING', `I do not have enough coal or charcoal to smelt`, { required: putFuel }) } await furnace.putFuel(fuel.type, null, putFuel) } - + // Put the items in the furnace const itemId = getItemId(itemName) if (itemId === null) { - if (placedFurnace) await collectBlock(mineflayer, 'furnace', 1) - throw new ActionError('UNKNOWN', `Invalid item name: ${itemName}`) + if (placedFurnace) + await collectBlock(mineflayer, 'furnace', 1) + throw new ActionError('UNKNOWN', `Invalid item name: ${itemName}`) } await furnace.putInput(itemId, null, num) - + // Wait for the items to smelt let total = 0 let collectedLast = true @@ -249,16 +254,16 @@ export async function smeltItem(mineflayer: Mineflayer, itemName: string, num = // Wait limit 30s per item? const maxWait = num * 12000 // approx 10s per item + buffer let waited = 0 - + while (total < num) { await new Promise(resolve => setTimeout(resolve, 5000)) waited += 5000 - + // Safety break if (waited > maxWait) { - break + break } - + logger.log('checking...') let collected = false if (furnace.outputItem()) { @@ -272,7 +277,7 @@ export async function smeltItem(mineflayer: Mineflayer, itemName: string, num = // If we didn't collect anything twice in a row, maybe it stopped? // Check input if (!furnace.inputItem() && !furnace.outputItem()) { - break // empty? + break // empty? } } collectedLast = collected @@ -282,11 +287,11 @@ export async function smeltItem(mineflayer: Mineflayer, itemName: string, num = if (placedFurnace) { await collectBlock(mineflayer, 'furnace', 1) } - + if (total < num) { - throw new ActionError('CRAFTING_FAILED', `Failed to smelt all items, only got ${total}/${num}`) + throw new ActionError('CRAFTING_FAILED', `Failed to smelt all items, only got ${total}/${num}`) } - + logger.log( `Successfully smelted ${itemName}, got ${total} ${getItemName( smeltedItem?.type || 0, @@ -311,7 +316,7 @@ export async function clearNearestFurnace(mineflayer: Mineflayer): Promise