-- Mining Turtle Script -- Sits on top of an infinite cobblestone generator. -- Continuously mines the block below and pushes items into -- networked storage via wired modem. -- Requires a wired modem attached to the turtle. local shell = _ENV.shell local function fatal(msg) printError(msg) print("\nPress any key to exit...") os.pullEvent("key") return end ------------------------------------------------- -- Default configuration (overridden by .miner_config) ------------------------------------------------- local MINE_INTERVAL = 0.5 -- seconds between dig attempts local DUMP_THRESHOLD = 14 -- dump when this many slots are occupied local DUMP_INTERVAL = 30 -- force dump every N seconds even if not full local FUEL_SLOT = 16 -- reserve this slot for fuel (0 = disabled) local SYSTEM_CHANNEL = 4205 -- remote reboot channel ------------------------------------------------- -- Load config from file if present ------------------------------------------------- local _baseDir = fs.getDir(shell.getRunningProgram()) local function _path(rel) return fs.combine(_baseDir, rel) end -- Persistent config path (survives Opus package updates) local _PERSIST_DIR = "usr/config/inventory-manager" local function _configPath(rel) if fs.isDir(_PERSIST_DIR) or fs.isDir("packages/inventory-manager") then if not fs.isDir(_PERSIST_DIR) then fs.makeDir(_PERSIST_DIR) end return fs.combine(_PERSIST_DIR, rel) end return _path(rel) end local CONFIG_FILE = _configPath(".miner_config") local function loadConfig() if not fs.exists(CONFIG_FILE) then return end local f = fs.open(CONFIG_FILE, "r") local data = f.readAll() f.close() local ok, cfg = pcall(textutils.unserialiseJSON, data) if not ok or not cfg then print("[WARN] Failed to parse " .. CONFIG_FILE) return end if cfg.mineInterval then MINE_INTERVAL = cfg.mineInterval end if cfg.dumpThreshold then DUMP_THRESHOLD = cfg.dumpThreshold end if cfg.dumpInterval then DUMP_INTERVAL = cfg.dumpInterval end if cfg.fuelSlot then FUEL_SLOT = cfg.fuelSlot end print("[CONFIG] Loaded from " .. CONFIG_FILE) end loadConfig() ------------------------------------------------- -- Setup ------------------------------------------------- print("=================================") print(" Mining Turtle (Cobble Miner)") print("=================================") print("") if not turtle then return fatal("[ERR] Not a turtle!") end -- Find wired modem and get our network name local modem = nil local modemSide = nil local selfName = nil for _, side in ipairs({"top", "bottom", "left", "right", "front", "back"}) do if peripheral.getType(side) == "modem" then local m = peripheral.wrap(side) if m.getNameLocal then local name = m.getNameLocal() if name then modem = m modemSide = side selfName = name break end end end end if not modem or not selfName then return fatal("[ERR] No wired modem found!\n Attach a wired modem to the turtle\n and connect it to the network.") end print("[OK] Modem: " .. modemSide) print("[OK] Network name: " .. selfName) print("[OK] Mine interval: " .. MINE_INTERVAL .. "s") print("[OK] Dump threshold: " .. DUMP_THRESHOLD .. " slots") print("") ------------------------------------------------- -- Find chests on the network to dump into ------------------------------------------------- local function getChests() local chests = {} for _, name in ipairs(peripheral.getNames()) do local ptype = peripheral.getType(name) if ptype == "minecraft:chest" or ptype == "minecraft:barrel" or ptype == "minecraft:shulker_box" then table.insert(chests, name) end end return chests end ------------------------------------------------- -- Dump inventory into networked storage -- Uses pull approach: wrap the remote chest and call -- chest.pullItems(selfName, slot) — avoids needing -- to wrap the turtle's own peripheral. ------------------------------------------------- local function dumpInventory() local chests = getChests() if #chests == 0 then print("[DUMP] No chests on network!") return 0 end local totalPushed = 0 for slot = 1, 16 do if slot ~= FUEL_SLOT and turtle.getItemCount(slot) > 0 then for _, chestName in ipairs(chests) do local chest = peripheral.wrap(chestName) if chest and chest.pullItems then local ok, n = pcall(chest.pullItems, selfName, slot) if ok and n and n > 0 then totalPushed = totalPushed + n if turtle.getItemCount(slot) == 0 then break -- slot empty, move to next end end end end end end return totalPushed end ------------------------------------------------- -- Count occupied inventory slots ------------------------------------------------- local function occupiedSlots() local count = 0 for slot = 1, 16 do if slot ~= FUEL_SLOT and turtle.getItemCount(slot) > 0 then count = count + 1 end end return count end ------------------------------------------------- -- Auto-refuel from fuel slot ------------------------------------------------- local function autoRefuel() if FUEL_SLOT <= 0 then return end if turtle.getFuelLevel() == "unlimited" then return end if turtle.getFuelLevel() > 100 then return end local prev = turtle.getSelectedSlot() turtle.select(FUEL_SLOT) if turtle.getItemCount() > 0 then turtle.refuel(1) print("[FUEL] Refueled. Level: " .. turtle.getFuelLevel()) end turtle.select(prev) end ------------------------------------------------- -- Stats ------------------------------------------------- local totalMined = 0 local totalDumped = 0 local startTime = os.clock() local function printStats() local elapsed = os.clock() - startTime local mins = math.floor(elapsed / 60) local secs = math.floor(elapsed % 60) term.clear() term.setCursorPos(1, 1) print("=================================") print(" Mining Turtle - Running") print("=================================") print(string.format(" Mined: %d blocks", totalMined)) print(string.format(" Dumped: %d items", totalDumped)) print(string.format(" Uptime: %dm %ds", mins, secs)) if turtle.getFuelLevel() ~= "unlimited" then print(string.format(" Fuel: %d", turtle.getFuelLevel())) end print(string.format(" Inv used: %d/16 slots", occupiedSlots())) print("=================================") end ------------------------------------------------- -- Main mining loop ------------------------------------------------- local function mineLoop() local lastDump = os.clock() while true do -- Auto-refuel if low autoRefuel() -- Check fuel if turtle.getFuelLevel() ~= "unlimited" and turtle.getFuelLevel() < 1 then print("[WARN] Out of fuel! Waiting...") while turtle.getFuelLevel() < 1 do autoRefuel() sleep(5) end end -- Mine the block below if turtle.detectDown() then local ok, err = turtle.digDown() if ok then totalMined = totalMined + 1 end end -- Dump inventory if threshold reached or timer expired local now = os.clock() if occupiedSlots() >= DUMP_THRESHOLD or (now - lastDump) >= DUMP_INTERVAL then if occupiedSlots() > 0 then local pushed = dumpInventory() totalDumped = totalDumped + pushed if pushed > 0 then print(string.format("[DUMP] Pushed %d items to storage", pushed)) end end lastDump = os.clock() end -- Refresh display periodically printStats() sleep(MINE_INTERVAL) end end ------------------------------------------------- -- Reboot listener (remote reboot support) ------------------------------------------------- local function rebootListener() modem.open(SYSTEM_CHANNEL) while true do local _, _, channel, _, message = os.pullEvent("modem_message") if channel == SYSTEM_CHANNEL and type(message) == "table" and message.type == "reboot" then local target = message.target or "all" if target == "all" or target == "miner" or target == tostring(os.getComputerID()) then print("[SYSTEM] Reboot command received.") sleep(0.5) os.reboot() end end end end ------------------------------------------------- -- Entry point ------------------------------------------------- print("Starting mining loop...") print("Press Ctrl+T to stop.") print("") sleep(1) local ok, err = pcall(function() parallel.waitForAny(mineLoop, rebootListener) end) if not ok then fatal("[ERR] Mining turtle crashed:\n" .. tostring(err)) end