feat: Enhance DumpInventoryState with fuzzy container detection and detailed dump reporting
This commit is contained in:
@@ -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,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user