Files
remoteturtle/server/states/AutocraftState.js

223 lines
5.9 KiB
JavaScript

/**
* 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)
`);
}
}