From e401c39bb30dc2dba3670eae5893500dc67b97e9 Mon Sep 17 00:00:00 2001 From: MayaTheShy Date: Fri, 20 Feb 2026 02:12:29 -0500 Subject: [PATCH] feat: Implement AutocraftState for automated crafting with workbench integration --- server/states/AutocraftState.js | 222 ++++++++++++++++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 server/states/AutocraftState.js diff --git a/server/states/AutocraftState.js b/server/states/AutocraftState.js new file mode 100644 index 0000000..dc5c114 --- /dev/null +++ b/server/states/AutocraftState.js @@ -0,0 +1,222 @@ +/** + * 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) + `); + } +}