diff --git a/server/states/MiningState.js b/server/states/MiningState.js new file mode 100644 index 0000000..123489f --- /dev/null +++ b/server/states/MiningState.js @@ -0,0 +1,221 @@ +/** + * MiningState - Autonomous mining with intelligent exploration + * Mines ores, explores caves, manages inventory + */ +import { BaseState } from './BaseState.js'; + +const VALUABLE_BLOCKS = new Set([ + 'minecraft:coal_ore', 'minecraft:iron_ore', 'minecraft:gold_ore', + 'minecraft:diamond_ore', 'minecraft:emerald_ore', 'minecraft:redstone_ore', + 'minecraft:lapis_ore', 'minecraft:copper_ore', + 'minecraft:deepslate_coal_ore', 'minecraft:deepslate_iron_ore', + 'minecraft:deepslate_gold_ore', 'minecraft:deepslate_diamond_ore', + 'minecraft:deepslate_emerald_ore', 'minecraft:deepslate_redstone_ore', + 'minecraft:deepslate_lapis_ore', 'minecraft:deepslate_copper_ore', + 'minecraft:nether_gold_ore', 'minecraft:nether_quartz_ore', + 'minecraft:ancient_debris', +]); + +export class MiningState extends BaseState { + constructor(turtle, data = {}) { + super(turtle, data); + this.maxDistance = data.maxDistance || 200; + this.minFuel = data.minFuel || 500; + this.blocksMined = 0; + this.oresFound = 0; + this.stuckCounter = 0; + this.visitedPositions = new Set(); + this.miningArea = data.miningArea || null; // Optional bounded area + } + + get name() { + return 'mining'; + } + + get description() { + return `Mining - ${this.blocksMined} mined, ${this.oresFound} ores found`; + } + + async *act() { + console.log(`[${this.turtle.id}] Starting mining operation`); + + while (!this.cancelled) { + // Safety checks + const fuel = await this.checkFuel(); + if (fuel !== 'unlimited' && fuel < this.minFuel) { + console.log(`[${this.turtle.id}] Low fuel (${fuel}), attempting refuel`); + const refueled = await this.tryRefuel(); + if (!refueled) { + console.log(`[${this.turtle.id}] Cannot refuel, going home`); + this.turtle.setState('goHome', { reason: 'low_fuel' }); + return; + } + } + + // Check inventory + const isFull = await this.isInventoryFull(); + if (isFull) { + console.log(`[${this.turtle.id}] Inventory full, going home to dump`); + this.turtle.setState('goHome', { reason: 'inventory_full', returnState: 'mining', returnData: this.data }); + return; + } + + // Check distance from home + if (this._isTooFar()) { + console.log(`[${this.turtle.id}] Too far from home, returning`); + this.turtle.setState('goHome', { reason: 'too_far' }); + return; + } + + // Mark current position as visited + const pos = this.turtle.position; + if (pos) { + this.visitedPositions.add(`${pos.x},${pos.y},${pos.z}`); + } + + // Scan surroundings for ores + const scanResult = await this.scanSurroundings(); + + // Mine any ores found in surroundings + yield* this._mineAdjacentOres(); + + // Explore step - try to find new areas + yield* this._exploreStep(); + + yield; + await this._sleep(200); + } + } + + /** + * Mine ores in adjacent positions + */ + async *_mineAdjacentOres() { + // Check all 6 directions for ores + const directions = [ + { check: 'turtle.inspect()', dig: 'turtle.dig()', dir: 'forward' }, + { check: 'turtle.inspectUp()', dig: 'turtle.digUp()', dir: 'up' }, + { check: 'turtle.inspectDown()', dig: 'turtle.digDown()', dir: 'down' }, + ]; + + for (const { check, dig, dir } of directions) { + if (this.cancelled) return; + + const result = await this.exec(` + local hasBlock, data = ${check} + if hasBlock then + return {name = data.name, metadata = data.metadata or 0} + end + return nil + `); + + if (result && VALUABLE_BLOCKS.has(result.name)) { + console.log(`[${this.turtle.id}] Found ore: ${result.name} (${dir})`); + await this.exec(dig); + this.blocksMined++; + this.oresFound++; + + // Report mined block to server + this.turtle.emit('blockMined', { blockType: result.name, direction: dir }); + yield; + } + } + } + + /** + * Explore step - move to unvisited positions, favor horizontal movement + */ + async *_exploreStep() { + const pos = this.turtle.position; + if (!pos) return; + + const r = Math.random() * 100; + + // 75%: horizontal exploration + if (r < 75) { + // Try to find an unvisited forward direction + let moved = false; + + // Try current facing first + const forwardPos = this.turtle.getBlockPositionInDirection('forward'); + if (forwardPos && !this.visitedPositions.has(`${forwardPos.x},${forwardPos.y},${forwardPos.z}`)) { + const success = await this.moveForward(true); + if (success) { + this.stuckCounter = 0; + moved = true; + } + } + + // If forward is visited or blocked, try other directions + if (!moved) { + for (let i = 0; i < 4; i++) { + await this.exec('turtle.turnRight()'); + this.turtle.facing = (this.turtle.facing + 1) % 4; + + const newForwardPos = this.turtle.getBlockPositionInDirection('forward'); + if (newForwardPos && !this.visitedPositions.has(`${newForwardPos.x},${newForwardPos.y},${newForwardPos.z}`)) { + const success = await this.moveForward(true); + if (success) { + this.stuckCounter = 0; + moved = true; + break; + } + } + } + } + + if (!moved) { + // All directions visited, just move forward + const success = await this.moveForward(true); + if (!success) { + this.stuckCounter++; + await this.exec('turtle.turnRight()'); + this.turtle.facing = (this.turtle.facing + 1) % 4; + } + } + + // 15%: go down (if not too deep) + } else if (r < 90 && pos.y > -50) { + const downPos = { x: pos.x, y: pos.y - 1, z: pos.z }; + if (!this.visitedPositions.has(`${downPos.x},${downPos.y},${downPos.z}`)) { + await this.moveDown(true); + } + + // 10%: go up + } else { + const upPos = { x: pos.x, y: pos.y + 1, z: pos.z }; + if (!this.visitedPositions.has(`${upPos.x},${upPos.y},${upPos.z}`)) { + await this.moveUp(true); + } + } + + // Handle being stuck + if (this.stuckCounter > 5) { + console.log(`[${this.turtle.id}] Stuck! Trying to escape`); + await this.moveUp(true); + this.stuckCounter = 0; + } + + yield; + } + + _isTooFar() { + const pos = this.turtle.position; + const home = this.turtle.homePosition; + if (!pos || !home) return false; + + const dist = Math.abs(pos.x - home.x) + Math.abs(pos.y - home.y) + Math.abs(pos.z - home.z); + return dist > this.maxDistance; + } + + getRecoveryData() { + return { + ...super.getRecoveryData(), + data: { + maxDistance: this.maxDistance, + minFuel: this.minFuel, + miningArea: this.miningArea, + }, + }; + } +}