diff --git a/server/states/FarmingState.js b/server/states/FarmingState.js new file mode 100644 index 0000000..4f791b8 --- /dev/null +++ b/server/states/FarmingState.js @@ -0,0 +1,124 @@ +/** + * FarmingState - Automated farming (plant, harvest, replant) + * Works with wheat, carrots, potatoes, beetroot, etc. + */ +import { BaseState } from './BaseState.js'; + +const CROP_SEEDS = { + 'minecraft:wheat': 'minecraft:wheat_seeds', + 'minecraft:carrots': 'minecraft:carrot', + 'minecraft:potatoes': 'minecraft:potato', + 'minecraft:beetroots': 'minecraft:beetroot_seeds', +}; + +export class FarmingState extends BaseState { + constructor(turtle, data = {}) { + super(turtle, data); + this.area = data.area || null; // {minX, minZ, maxX, maxZ, y} + this.harvestCount = 0; + } + + get name() { + return 'farming'; + } + + get description() { + return `Farming - ${this.harvestCount} harvested`; + } + + async *act() { + console.log(`[${this.turtle.id}] Starting farming operation`); + + if (!this.area) { + console.log(`[${this.turtle.id}] No farming area defined`); + this.turtle.setState('idle'); + return; + } + + // Farm in a zigzag pattern over the area + const { minX, minZ, maxX, maxZ, y } = this.area; + let direction = 1; // 1 = positive Z, -1 = negative Z + + for (let x = minX; x <= maxX; x++) { + const zStart = direction === 1 ? minZ : maxZ; + const zEnd = direction === 1 ? maxZ : minZ; + const zStep = direction; + + for (let z = zStart; direction === 1 ? z <= zEnd : z >= zEnd; z += zStep) { + if (this.cancelled) return; + + // Navigate to position above the crop + const target = { x, y: y + 1, z }; + const navGen = this.navigateTo(target); + for await (const _ of navGen) { + if (this.cancelled) return; + yield; + } + + // Check the block below + const blockBelow = await this.exec(` + local hasBlock, data = turtle.inspectDown() + if hasBlock then + return {name = data.name, state = data.state} + end + return nil + `); + + if (blockBelow) { + // Check if crop is mature (age = 7 for most crops) + const isMature = blockBelow.state && blockBelow.state.age === 7; + + if (isMature) { + // Harvest + await this.exec('turtle.digDown()'); + this.harvestCount++; + + // Replant + const seedName = CROP_SEEDS[blockBelow.name]; + if (seedName) { + await this.exec(` + for slot = 1, 16 do + local item = turtle.getItemDetail(slot) + if item and item.name == "${seedName}" then + turtle.select(slot) + turtle.placeDown() + turtle.select(1) + return true + end + end + return false + `); + } + } + } + + yield; + } + + direction *= -1; // Zigzag + } + + // Done farming one pass - check inventory + const isFull = await this.isInventoryFull(); + if (isFull) { + this.turtle.setState('goHome', { + reason: 'inventory_full', + returnState: 'farming', + returnData: this.data + }); + } else { + // Start another pass + console.log(`[${this.turtle.id}] Farm pass complete, ${this.harvestCount} harvested`); + yield* this.act(); // Loop + } + } + + getRecoveryData() { + return { + ...super.getRecoveryData(), + data: { + area: this.area, + }, + }; + } +}