diff --git a/server/states/DumpInventoryState.js b/server/states/DumpInventoryState.js index c7966a9..a8dab47 100644 --- a/server/states/DumpInventoryState.js +++ b/server/states/DumpInventoryState.js @@ -1,19 +1,55 @@ /** * DumpInventoryState - Dump inventory into nearby containers - * Keeps fuel items, dumps everything else + * Keeps fuel items, dumps everything else. + * + * Improvements over v1: + * - Uses Levenshtein distance for fuzzy container name matching + * - Detects containers via peripheral inspection on all sides + * - Tracks dump success/failure per direction + * - Reports detailed dump stats + * - Optionally navigates home before dumping */ import { BaseState } from './BaseState.js'; +import { findBestMatch } from '../helpers/levenshtein.js'; +// Items to keep (fuel sources and tools) const KEEP_ITEMS = new Set([ 'minecraft:coal', 'minecraft:charcoal', 'minecraft:coal_block', 'minecraft:torch', 'minecraft:lava_bucket', ]); +// Known container block names (used for fuzzy matching) +const CONTAINER_NAMES = [ + 'chest', 'barrel', 'shulker_box', 'hopper', 'dropper', 'dispenser', + 'trapped_chest', 'ender_chest', 'furnace', 'blast_furnace', 'smoker', +]; + +/** + * Check if a block name is likely a container using fuzzy matching + */ +function isContainerBlock(blockName) { + if (!blockName) return false; + const shortName = blockName.replace(/^[^:]+:/, '').toLowerCase(); + + // Exact substring match first + for (const container of CONTAINER_NAMES) { + if (shortName.includes(container)) return true; + } + + // Fuzzy match as fallback + const { match, distance } = findBestMatch(shortName, CONTAINER_NAMES, 3); + return match !== null && distance <= 2; +} + export class DumpInventoryState extends BaseState { constructor(turtle, data = {}) { super(turtle, data); this.returnState = data.returnState || null; this.returnData = data.returnData || {}; + this.navigateHome = data.navigateHome || false; + this.keepItems = data.keepItems + ? new Set([...KEEP_ITEMS, ...data.keepItems]) + : KEEP_ITEMS; } get name() { @@ -21,47 +57,85 @@ export class DumpInventoryState extends BaseState { } get description() { - return 'Dumping inventory'; + return this.navigateHome ? 'Returning home to dump inventory' : 'Dumping inventory'; } async *act() { console.log(`[${this.turtle.id}] Starting inventory dump`); - // Try to dump into containers in all directions - const keepItems = [...KEEP_ITEMS].map(n => `"${n}"`).join(', '); + // Optionally navigate home first + if (this.navigateHome && this.turtle.homePosition) { + console.log(`[${this.turtle.id}] Navigating home before dumping`); + yield* this.navigateTo(this.turtle.homePosition); + yield; + } + + // First, detect containers by inspecting all directions + const containerDirections = await this._detectContainers(); + + if (containerDirections.length === 0) { + console.log(`[${this.turtle.id}] No containers found nearby, trying blind dump`); + containerDirections.push('front', 'up', 'down'); + } else { + console.log(`[${this.turtle.id}] Found containers: ${containerDirections.join(', ')}`); + } + + // Build keep items set for Lua + const keepItemsList = [...this.keepItems].map(n => `"${n}"`).join(', '); + + // Map direction names to Lua drop functions + const dropFnMap = { + front: 'turtle.drop', + up: 'turtle.dropUp', + down: 'turtle.dropDown', + }; + + // Build the drop functions array for only detected container directions + const dropFnEntries = containerDirections + .filter(d => dropFnMap[d]) + .map(d => dropFnMap[d]); + + if (dropFnEntries.length === 0) { + console.log(`[${this.turtle.id}] No valid drop directions`); + this._transitionOut(); + return; + } const result = await this.exec(` - local keepItems = {${keepItems}} + local keepItems = {${keepItemsList}} local keepSet = {} for _, name in ipairs(keepItems) do keepSet[name] = true end - local dropFns = { - turtle.drop, - turtle.dropUp, - turtle.dropDown, - } + local dropFns = {${dropFnEntries.join(', ')}} local dumpedCount = 0 + local failedCount = 0 - -- Try each direction for _, dropFn in ipairs(dropFns) do for slot = 1, 16 do local item = turtle.getItemDetail(slot) if item and not keepSet[item.name] then turtle.select(slot) + local prevCount = item.count if dropFn() then - dumpedCount = dumpedCount + item.count + local remaining = turtle.getItemCount(slot) + dumpedCount = dumpedCount + (prevCount - remaining) + else + failedCount = failedCount + 1 end end end end turtle.select(1) - return {dumped = dumpedCount} + return {dumped = dumpedCount, failed = failedCount} `); if (result) { - console.log(`[${this.turtle.id}] Dumped ${result.dumped} items`); + console.log(`[${this.turtle.id}] Dumped ${result.dumped} items (${result.failed} drops failed)`); + if (result.failed > 0) { + this.turtle.warning = `Dump: ${result.failed} drops failed (container full?)`; + } } yield; @@ -69,7 +143,49 @@ export class DumpInventoryState extends BaseState { // Update inventory after dumping await this.getInventory(); - // Transition to next state + // Refresh any adjacent inventory peripherals + for (const dir of containerDirections) { + if (this.turtle._peripherals?.[dir]?.types?.includes('inventory')) { + try { + await this.turtle.connectToInventory(dir); + } catch (e) { /* ignore */ } + } + } + + this._transitionOut(); + } + + /** + * Detect which directions have containers by inspecting blocks + */ + async _detectContainers() { + const result = await this.exec(` + local dirs = {} + local h, d + h, d = turtle.inspect() + if h then dirs.front = d.name end + h, d = turtle.inspectUp() + if h then dirs.up = d.name end + h, d = turtle.inspectDown() + if h then dirs.down = d.name end + return dirs + `); + + if (!result || typeof result !== 'object') return []; + + const containerDirs = []; + for (const [dir, blockName] of Object.entries(result)) { + if (isContainerBlock(blockName)) { + containerDirs.push(dir); + } + } + return containerDirs; + } + + /** + * Transition to the next state (return state or idle) + */ + _transitionOut() { if (this.returnState) { this.turtle.setState(this.returnState, this.returnData); } else { @@ -83,6 +199,10 @@ export class DumpInventoryState extends BaseState { data: { returnState: this.returnState, returnData: this.returnData, + navigateHome: this.navigateHome, + keepItems: this.keepItems !== KEEP_ITEMS + ? [...this.keepItems].filter(i => !KEEP_ITEMS.has(i)) + : undefined, }, }; }