chore(minecraft): various legacy code cleanup

This commit is contained in:
Rin
2026-02-18 11:10:00 +08:00
committed by Neko Ayaka
parent 8704bce444
commit 4b2d06db3b
5 changed files with 104 additions and 384 deletions
@@ -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.`
// },
// }
// },
]
@@ -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()
@@ -56,26 +56,6 @@ export class Mineflayer extends EventEmitter<EventHandlers> {
public static async asyncBuild(options: MineflayerOptions) {
const mineflayer = new Mineflayer(options)
mineflayer.bot.on('messagestr', async (message, _, jsonMsg) => {
// jsonMsg.translate:
// - death.attack.player
// message:
// - <bot username> was slain by <player / entity>
// - <bot username> 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<EventHandlers> {
}
})
}
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<maxResponses; i++) {
// if (checkInterrupt()) break;
// let history = this.history.getHistory();
// let res = await this.prompter.promptConvo(history);
// console.log(`${this.name} full response to ${source}: ""${res}""`);
// if (res.trim().length === 0) {
// console.warn('no response')
// break; // empty response ends loop
// }
// let command_name = containsCommand(res);
// if (command_name) { // contains query or command
// res = truncCommandMessage(res); // everything after the command is ignored
// this.history.add(this.name, res);
// if (!commandExists(command_name)) {
// this.history.add('system', `Command ${command_name} does not exist.`);
// console.warn('Agent hallucinated command:', command_name)
// continue;
// }
// if (checkInterrupt()) break;
// this.self_prompter.handleUserPromptedCmd(self_prompt, isAction(command_name));
// if (settings.verbose_commands) {
// this.routeResponse(source, res);
// }
// else { // only output command name
// let pre_message = res.substring(0, res.indexOf(command_name)).trim();
// let chat_message = `*used ${command_name.substring(1)}*`;
// if (pre_message.length > 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;
}
}
+47 -34
View File
@@ -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<boolean> {
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<boolean> {
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<boolean> {
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<boolean> {
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 })
}
}
}
+37 -32
View File
@@ -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<boole
await furnace.takeInput()
if (furnace.fuelItem())
await furnace.takeFuel()
await mineflayer.bot.closeWindow(furnace)
return true
}