diff --git a/server/Turtle.js b/server/Turtle.js index 75962dc..6b7a798 100644 --- a/server/Turtle.js +++ b/server/Turtle.js @@ -735,10 +735,15 @@ export class Turtle extends EventEmitter { */ async refuel(count) { const cmd = count != null ? `return turtle.refuel(${count})` : 'return turtle.refuel()'; + const fuelBefore = this._fuel; const result = await this.exec(cmd); // Update fuel level const fuelLevel = await this.exec('return turtle.getFuelLevel()'); - if (fuelLevel != null) this._fuel = fuelLevel; + if (fuelLevel != null) { + this._totalFuelUsed += (fuelLevel - fuelBefore); + this._fuel = fuelLevel; + this._stepsSinceLastRefuel = 0; + } return result; } @@ -806,6 +811,83 @@ export class Turtle extends EventEmitter { return this._position; } + /** + * Write content to a file on the turtle's filesystem + * @param {string} path - The file path on the turtle (e.g., 'log.txt', 'scripts/miner.lua') + * @param {string} content - The content to write + * @param {boolean} append - Whether to append instead of overwrite + */ + async writeToFile(path, content, append = false) { + const safePath = path.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); + // Encode content as base64-like hex to avoid Lua string escaping issues + const mode = append ? 'a' : 'w'; + // Split content into chunks to avoid oversized eval commands + const chunkSize = 4000; + const chunks = []; + for (let i = 0; i < content.length; i += chunkSize) { + chunks.push(content.substring(i, i + chunkSize)); + } + + if (chunks.length <= 1) { + // Single write for small files + const safeContent = (chunks[0] || '').replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, ''); + return this.exec(` + local f = fs.open("${safePath}", "${mode}") + if f then + f.write("${safeContent}") + f.close() + return true + end + return false, "Cannot open file" + `); + } + + // Multi-chunk write for large files + for (let i = 0; i < chunks.length; i++) { + const safeChunk = chunks[i].replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, ''); + const writeMode = i === 0 ? mode : 'a'; + const result = await this.exec(` + local f = fs.open("${safePath}", "${writeMode}") + if f then + f.write("${safeChunk}") + f.close() + return true + end + return false, "Cannot open file" + `); + if (result !== true) return result; + } + return true; + } + + /** + * Refresh detailed inventory state for all 16 slots. + * Gets name, count, damage, and maxCount for each slot. + */ + async refreshInventoryState() { + const result = await this.exec(` + local inv = {} + for slot = 1, 16 do + local item = turtle.getItemDetail(slot) + if item then + inv[tostring(slot)] = { + name = item.name, + count = item.count, + damage = item.damage or 0, + maxCount = turtle.getItemSpace(slot) + item.count + } + end + end + return inv + `); + + if (result && typeof result === 'object') { + this._inventory = result; + this._emitUpdate(); + } + return result; + } + /** * Connect to an adjacent inventory peripheral and read its contents */ @@ -1111,6 +1193,9 @@ export class Turtle extends EventEmitter { peripherals: this._peripherals, error: this._error, warning: this._warning, + stepsSinceLastRefuel: this._stepsSinceLastRefuel, + totalSteps: this._totalSteps, + totalFuelUsed: this._totalFuelUsed, }; }