feat: Implement AutocraftState for automated crafting with workbench integration
This commit is contained in:
222
server/states/AutocraftState.js
Normal file
222
server/states/AutocraftState.js
Normal file
@@ -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)
|
||||
`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user