diff --git a/.package b/.package index 07b48b9..2b66776 100644 --- a/.package +++ b/.package @@ -28,9 +28,10 @@ print(" 1) Inventory Manager (main controller)") print(" 2) Inventory Client (display-only)") print(" 3) Web Bridge (HTTP forwarder)") - print(" 4) Skip setup") + print(" 4) Mining Turtle (cobble miner)") + print(" 5) Skip setup") print("") - write("Choice (1/2/3/4): ") + write("Choice (1/2/3/4/5): ") local choice = read() if choice == "1" then @@ -92,6 +93,21 @@ f.close() print("Saved web bridge config.") + elseif choice == "4" then + print("") + print("-- Mining Turtle Configuration --") + local mineInterval = ask("Mine interval in seconds", "0.5") + local dumpThreshold = ask("Dump when N slots full", "14") + + local cfg = { + mineInterval = tonumber(mineInterval) or 0.5, + dumpThreshold = tonumber(dumpThreshold) or 14, + } + local f = fs.open(fs.combine(cfgDir, ".miner_config"), "w") + f.write(textutils.serialiseJSON(cfg)) + f.close() + print("Saved miner config.") + else print("Skipped — edit config files manually later.") end diff --git a/autorun/startup.lua b/autorun/startup.lua index fcb3a8f..3f7ef28 100644 --- a/autorun/startup.lua +++ b/autorun/startup.lua @@ -26,6 +26,8 @@ elseif cfgExists('.client_config') then role = 'client' elseif cfgExists('.webbridge_config') then role = 'bridge' +elseif cfgExists('.miner_config') then + role = 'miner' elseif _G.turtle then role = 'turtle' end @@ -69,6 +71,7 @@ local programs = { client = 'inventoryClient.lua', bridge = 'inventoryWebBridge.lua', turtle = 'craftingTurtle.lua', + miner = 'miningTurtle.lua', } local program = fs.combine(BASE, programs[role]) diff --git a/miningTurtle.lua b/miningTurtle.lua new file mode 100644 index 0000000..bb7ff6d --- /dev/null +++ b/miningTurtle.lua @@ -0,0 +1,297 @@ +-- 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 + +-- Wrap our own inventory peripheral for pushItems +local selfInv = peripheral.wrap(selfName) +if not selfInv then + return fatal("[ERR] Cannot wrap own peripheral: " .. selfName .. "\n Make sure the wired modem is connected.") +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 +------------------------------------------------- + +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 ok, n = pcall(selfInv.pushItems, chestName, 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 + 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 diff --git a/startup/miner.lua b/startup/miner.lua new file mode 100644 index 0000000..aa7d429 --- /dev/null +++ b/startup/miner.lua @@ -0,0 +1,58 @@ +-- startup.lua for Mining Turtle +-- Auto-updates from git then launches miningTurtle.lua + +local REPO_RAW = "https://git.spatulaa.com/MayaTheShy/Inventory-Manager-CC/raw/branch/main" + +local FILES = { + ["miningTurtle.lua"] = "miningTurtle.lua", +} + +------------------------------------------------- + +local function download(remotePath, localPath) + local url = REPO_RAW .. "/" .. remotePath + local response = http.get(url) + if response then + local f = fs.open(localPath, "w") + f.write(response.readAll()) + f.close() + response.close() + return true + end + return false +end + +------------------------------------------------- + +term.clear() +term.setCursorPos(1, 1) +print("==================================") +print(" Mining Turtle - Startup") +print(" Computer ID: " .. os.getComputerID()) +print("==================================") +print("") + +local updated, failed = 0, 0 +for localPath, remotePath in pairs(FILES) do + write(" " .. localPath .. " ... ") + if download(remotePath, localPath) then + print("OK") + updated = updated + 1 + else + print("FAIL") + failed = failed + 1 + end +end + +print("") +if failed > 0 then + print(string.format("Updated %d files, %d failed.", updated, failed)) +else + print(string.format("All %d files up to date.", updated)) +end + +print("") +print("Starting miningTurtle...") +sleep(1) + +shell.run("miningTurtle.lua")