223 lines
5.9 KiB
JavaScript
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)
|
|
`);
|
|
}
|
|
}
|