/** * AutocraftState - Automated crafting using a workbench peripheral * * The turtle must have a crafting table equipped as a peripheral. * Given a recipe (16-slot inventory layout), it will: * 1. Verify workbench peripheral is present * 2. Arrange items in the correct slots * 3. Pull materials from adjacent inventories * 4. Craft continuously * 5. Push crafted items into adjacent inventories */ import { BaseState } from './BaseState.js'; export class AutocraftState extends BaseState { constructor(turtle, data = {}) { super(turtle, data); // recipe: { 1: {name, count}, 2: null, 3: {name, count}, ... } (1-16 slots) this.recipe = data.recipe || {}; this.craftCount = 0; this.totalCrafted = 0; this.maxCrafts = data.maxCrafts || Infinity; this.warning = null; } get name() { return 'autocrafting'; } get description() { if (this.warning) return `Crafting - ${this.warning}`; return `Crafting - ${this.totalCrafted} items crafted`; } async *act() { console.log(`[${this.turtle.id}] Starting autocraft`); // Verify workbench peripheral const hasWorkbench = await this.exec(` local names = peripheral.getNames() for _, name in ipairs(names) do local types = {peripheral.getType(name)} for _, t in ipairs(types) do if t == "workbench" then return true end end end return false `); if (!hasWorkbench) { console.log(`[${this.turtle.id}] No workbench peripheral found`); this.warning = 'No crafting table equipped'; await this._sleep(3000); this.turtle.setState('idle'); return; } yield; // Initial craft to verify recipe works const initialCraft = await this.exec(` local workbench = peripheral.find("workbench") if workbench then local ok, msg = workbench.craft(0) return ok end return false `); if (!initialCraft) { console.log(`[${this.turtle.id}] Recipe validation failed`); this.warning = 'Invalid recipe'; await this._sleep(3000); this.turtle.setState('idle'); return; } yield; // Main crafting loop while (!this.cancelled && this.totalCrafted < this.maxCrafts) { this.warning = null; let recipeReady = true; // Arrange items for recipe for (let slot = 1; slot <= 16; slot++) { if (this.cancelled) return; const recipeItem = this.recipe[slot]; // Get current item in slot const currentItem = await this.exec(` local item = turtle.getItemDetail(${slot}) if item then return {name=item.name, count=item.count} end return nil `); yield; if (!recipeItem) { // Slot should be empty - move item out if present if (currentItem) { await this._transferSlotOut(slot); yield; } continue; } // Slot needs a specific item if (currentItem) { if (currentItem.name === recipeItem.name) { continue; // Already has correct item } // Wrong item - move it out await this._transferSlotOut(slot); yield; } // Pull the required item into this slot const pulled = await this._pullItemToSlot(recipeItem.name, slot); yield; if (!pulled) { this.warning = `Missing: ${recipeItem.name.replace('minecraft:', '')}`; recipeReady = false; break; } } if (!recipeReady) { yield; await this._sleep(5000); yield; continue; } // Craft! const craftResult = await this.exec(` local workbench = peripheral.find("workbench") if workbench then local ok, msg = workbench.craft() if ok then return true else return false end end return false `); if (craftResult) { this.totalCrafted++; this.craftCount++; console.log(`[${this.turtle.id}] Crafted item #${this.totalCrafted}`); // Push crafted items out to adjacent inventory await this._pushCraftedItems(); yield; } else { this.warning = 'Craft failed'; await this._sleep(2000); } yield; await this._sleep(500); } console.log(`[${this.turtle.id}] Autocraft finished: ${this.totalCrafted} total`); this.turtle.setState('idle'); } async _transferSlotOut(slot) { // Try to drop the item in the given slot into an adjacent inventory await this.exec(` turtle.select(${slot}) if not turtle.dropDown() then if not turtle.dropUp() then turtle.drop() end end turtle.select(1) `); } async _pullItemToSlot(itemName, targetSlot) { // Try to pull item from adjacent inventories // First try suckDown, then suckUp, then suck const result = await this.exec(` turtle.select(${targetSlot}) -- Try each direction for _, suckFn in ipairs({turtle.suckDown, turtle.suckUp, turtle.suck}) do suckFn(1) local item = turtle.getItemDetail(${targetSlot}) if item and item.name == "${itemName}" then return true elseif item then -- Wrong item, put it back for _, dropFn in ipairs({turtle.dropDown, turtle.dropUp, turtle.drop}) do if dropFn() then break end end end end turtle.select(1) return false `); return result === true; } async _pushCraftedItems() { // Push all items out of inventory await this.exec(` for slot = 1, 16 do local item = turtle.getItemDetail(slot) if item then turtle.select(slot) if not turtle.dropDown() then if not turtle.dropUp() then turtle.drop() end end end end turtle.select(1) `); } }