diff --git a/server/states/ScanState.js b/server/states/ScanState.js new file mode 100644 index 0000000..7693ad3 --- /dev/null +++ b/server/states/ScanState.js @@ -0,0 +1,184 @@ +/** + * ScanState - Systematic scanning of surroundings using peripheral scanners + * + * Supports multiple scanner types: + * - geoScanner (Advanced Peripherals) + * - universal_scanner + * - plethora:scanner (Plethora mod) + * - Native turtle.inspect() fallback (6-directional scan) + */ +import { BaseState } from './BaseState.js'; + +export class ScanState extends BaseState { + constructor(turtle, data = {}) { + super(turtle, data); + this.scanRadius = data.scanRadius || 16; + this.blocksScanned = 0; + this.scannerType = null; + } + + get name() { + return 'scanning'; + } + + get description() { + return `Scanning (${this.scannerType || 'detecting'}) - ${this.blocksScanned} blocks`; + } + + async *act() { + console.log(`[${this.turtle.id}] Starting scan`); + + // Detect available scanner peripheral + this.scannerType = await this._detectScanner(); + yield; + + if (this.scannerType === 'geoScanner') { + yield* this._geoScan(); + } else if (this.scannerType === 'universal_scanner') { + yield* this._universalScan(); + } else if (this.scannerType === 'plethora:scanner') { + yield* this._plethuraScan(); + } else { + // Fallback to native inspect-based scanning + yield* this._nativeScan(); + } + + console.log(`[${this.turtle.id}] Scan complete: ${this.blocksScanned} blocks`); + this.turtle.setState('idle'); + } + + async _detectScanner() { + // Check for geoScanner + const geoResult = 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 == "geoScanner" then return "geoScanner" end + end + end + return nil + `); + if (geoResult) return geoResult; + + // Check for universal_scanner + const uniResult = 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 == "universal_scanner" then return "universal_scanner" end + end + end + return nil + `); + if (uniResult) return uniResult; + + // Check for plethora scanner + const plResult = 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 == "plethora:scanner" then return "plethora:scanner" end + end + end + return nil + `); + if (plResult) return plResult; + + return 'native'; + } + + async *_geoScan() { + // Wait for cooldown + yield; + await this._sleep(500); + + const result = await this.exec(` + local scanner = peripheral.find("geoScanner") + if not scanner then return nil end + local ok, blocks = pcall(scanner.scan, ${this.scanRadius}) + if ok then return blocks end + return nil + `); + + if (result && Array.isArray(result)) { + this._processScannedBlocks(result); + } + yield; + } + + async *_universalScan() { + yield; + await this._sleep(500); + + const result = await this.exec(` + local scanner = peripheral.find("universal_scanner") + if not scanner then return nil end + local ok, blocks = pcall(scanner.scan, "block", ${this.scanRadius}) + if ok then return blocks end + return nil + `); + + if (result && Array.isArray(result)) { + this._processScannedBlocks(result); + } + yield; + } + + async *_plethuraScan() { + yield; + + const result = await this.exec(` + local scanner = peripheral.find("plethora:scanner") + if not scanner then return nil end + local ok, blocks = pcall(scanner.scan) + if ok then return blocks end + return nil + `); + + if (result && Array.isArray(result)) { + this._processScannedBlocks(result); + } + yield; + } + + async *_nativeScan() { + // Fallback: scan all 6 directions using turtle.inspect + const scanResult = await this.scanSurroundings(); + if (scanResult) { + this.blocksScanned += Object.keys(scanResult).length; + } + yield; + } + + _processScannedBlocks(blocks) { + const pos = this.turtle.position; + if (!pos) return; + + const discoveredBlocks = []; + for (const block of blocks) { + if (!block || !block.name) continue; + if (block.name === 'minecraft:air') continue; + if (block.x === 0 && block.y === 0 && block.z === 0) continue; + + discoveredBlocks.push({ + x: pos.x + (block.x || 0), + y: pos.y + (block.y || 0), + z: pos.z + (block.z || 0), + name: block.name, + metadata: block.metadata || 0, + state: block.state || {}, + tags: block.tags || {}, + discoveredBy: this.turtle.id, + }); + } + + this.blocksScanned = discoveredBlocks.length; + + if (discoveredBlocks.length > 0) { + this.turtle.emit('blocksDiscovered', discoveredBlocks); + } + } +}