diff --git a/services/minecraft/src/agents/action/tools.ts b/services/minecraft/src/agents/action/tools.ts index fa2025dc7..9781641a6 100644 --- a/services/minecraft/src/agents/action/tools.ts +++ b/services/minecraft/src/agents/action/tools.ts @@ -138,8 +138,8 @@ export const actionsList: Action[] = [ search_range: z.number().describe('The range to search for the block.').min(32).max(512), }), perform: mineflayer => async (block_type: string, range: number) => { - await skills.goToNearestBlock(mineflayer, block_type, 4, range) - return `Arrived at nearest [${block_type}]` // TODO more spacial context? + const block = await skills.goToNearestBlock(mineflayer, block_type, 4, range) + return `Arrived at nearest [${block.name}] at (${block.position.x}, ${block.position.y}, ${block.position.z})` // TODO more spacial context? }, }, { diff --git a/services/minecraft/src/skills/movement.ts b/services/minecraft/src/skills/movement.ts index 4586eed92..d91ade9ec 100644 --- a/services/minecraft/src/skills/movement.ts +++ b/services/minecraft/src/skills/movement.ts @@ -1,3 +1,4 @@ +import type { Block } from 'prismarine-block' import type { Entity } from 'prismarine-entity' import type { Mineflayer } from '../libs/mineflayer' @@ -43,7 +44,7 @@ export async function goToNearestBlock( blockType: string, minDistance = 2, range = 64, -): Promise { +): Promise { const MAX_RANGE = 512 if (range > MAX_RANGE) { log(mineflayer, `Maximum search range capped at ${MAX_RANGE}.`) @@ -52,13 +53,12 @@ export async function goToNearestBlock( const block = getNearestBlock(mineflayer, blockType, range) if (!block) { - log(mineflayer, `Could not find any ${blockType} in ${range} blocks.`) - return false + throw new Error(`Could not find any ${blockType} in ${range} blocks.`) } log(mineflayer, `Found ${blockType} at ${block.position}.`) await goToPosition(mineflayer, block.position.x, block.position.y, block.position.z, minDistance) - return true + return block } export async function goToNearestEntity( diff --git a/services/minecraft/src/skills/world.ts b/services/minecraft/src/skills/world.ts index 60f99fb09..05f4a18fd 100644 --- a/services/minecraft/src/skills/world.ts +++ b/services/minecraft/src/skills/world.ts @@ -58,11 +58,25 @@ export function getNearestFreeSpace( } export function getNearestBlocks(mineflayer: Mineflayer, blockTypes: string[] | string | null = null, distance: number = 16, count: number = 10000): Block[] { - const blockIds = blockTypes === null - ? mc.getAllBlockIds(['air']) - : (Array.isArray(blockTypes) ? blockTypes : [blockTypes]).map(mc.getBlockId).filter((id): id is number => id !== null) + const blockNames = blockTypes === null + ? mc.getAllBlocks(['air']).map(block => block.name) + : (Array.isArray(blockTypes) ? blockTypes : [blockTypes]) + .map((name) => { + const id = mc.getBlockId(name) + if (id) + return name - const positions = mineflayer.bot.findBlocks({ matching: blockIds, maxDistance: distance, count }) + const closest = mc.getClosestBlockName(name) + const suggestion = closest ? `; did you mean ${closest}?` : '' + throw new Error(`Unknown block type: ${name}${suggestion}`) + }) + + const blockNameSet = new Set(blockNames) + const positions = mineflayer.bot.findBlocks({ + matching: block => block && blockNameSet.has(block.name), + maxDistance: distance, + count, + }) return positions .map((pos) => { diff --git a/services/minecraft/src/utils/mcdata.ts b/services/minecraft/src/utils/mcdata.ts index 8a18ba401..ee50c9e87 100644 --- a/services/minecraft/src/utils/mcdata.ts +++ b/services/minecraft/src/utils/mcdata.ts @@ -99,6 +99,45 @@ export function getBlockName(blockId: number): string { return block.name || '' } +export function getClosestBlockName(input: string): string | null { + const names = Object.keys(gameData.blocksByName ?? {}) + let best: { name: string | null, distance: number } = { name: null, distance: Number.POSITIVE_INFINITY } + + for (const name of names) { + const distance = levenshteinDistance(input, name) + if (distance < best.distance) { + best = { name, distance } + if (distance === 0) + break + } + } + + return best.name +} + +function levenshteinDistance(a: string, b: string): number { + const matrix: number[][] = Array.from({ length: a.length + 1 }, () => + Array.from({ length: b.length + 1 }, () => 0)) + + for (let i = 0; i <= a.length; i++) + matrix[i][0] = i + for (let j = 0; j <= b.length; j++) + matrix[0][j] = j + + for (let i = 1; i <= a.length; i++) { + for (let j = 1; j <= b.length; j++) { + const cost = a[i - 1] === b[j - 1] ? 0 : 1 + matrix[i][j] = Math.min( + matrix[i - 1][j] + 1, + matrix[i][j - 1] + 1, + matrix[i - 1][j - 1] + cost, + ) + } + } + + return matrix[a.length][b.length] +} + export function getAllItems(ignore: string[] = []): any[] { const items: any[] = [] for (const itemId in gameData.items) {