233 lines
7.0 KiB
JavaScript
233 lines
7.0 KiB
JavaScript
/**
|
|
* 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) {
|
|
try {
|
|
// 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();
|
|
|
|
} catch (error) {
|
|
const isTimeout = error.message?.includes('timed out');
|
|
if (isTimeout) {
|
|
console.warn(`[${this.turtle.id}] Mining exec timeout, will retry next iteration`);
|
|
await this._sleep(3000);
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
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,
|
|
},
|
|
};
|
|
}
|
|
}
|