Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- miner_startup.lua — Flicker-free UI + debug + “mostly-stupid” miner
- -- Axis-only mine vectors. No area/volume planning. Controller drives vectors.
- -- Requires: CC:Tweaked turtle with wireless/Ender modem.
- -------------------- CONFIG --------------------
- local PROTOCOL = "minesys:v1"
- -- Home / logistics (edit to your base)
- local HOME = { x=-924, y=99, z=-1093 }
- local SHAFT_XZ = { x=-924, z=-1092 }
- local CHEST_FUEL = { x=-924, y=98, z=-1091 }
- local CHEST_DUMP = { x=-924, y=99, z=-1091 }
- -- Fuel policy
- local FUEL_MIN_BUFFER = 200
- local FUEL_PER_STEP = 1
- local REFUEL_TARGET = 3000
- -- Timing
- local HEARTBEAT_SEC = 2.0
- local UI_REFRESH = 0.5
- ------------------------------------------------
- -- Peripherals
- local modem = peripheral.find("modem", function(_,p) return p.isWireless() end)
- assert(modem, "No wireless modem")
- rednet.open(peripheral.getName(modem))
- -- UI window (double-buffered, no flicker)
- local root = term.current()
- local W,H = root.getSize()
- local ui = window.create(root, 1, 1, W, H, true)
- ui.setCursorBlink(false)
- local function uiBegin()
- ui.setVisible(false)
- ui.setBackgroundColor(colors.black)
- ui.setTextColor(colors.white)
- ui.clear()
- ui.setCursorPos(1,1)
- end
- local function uiEnd() ui.setVisible(true) end
- -- State
- local BOOT = os.epoch("utc")
- local function now() return os.epoch("utc") end
- local function uptime()
- local s = math.floor((now()-BOOT)/1000)
- local h = math.floor(s/3600); s=s-3600*h
- local m = math.floor(s/60); s=s-60*m
- return string.format("%02d:%02d:%02d", h,m,s)
- end
- local S = {
- status="idle", extra=nil, facing="N",
- pos=nil, lastFix=nil, fuel=turtle.getFuelLevel(),
- abort=false, stop=false, uiOn=true, lastAck=nil
- }
- -- GPS helpers
- local function gpsPos()
- local x,y,z = gps.locate(5)
- if x then
- S.pos = { x = math.floor(x+0.5), y = math.floor(y+0.5), z = math.floor(z+0.5) }
- S.lastFix = now()
- end
- return x,y,z
- end
- local function fixAge()
- if not S.lastFix then return nil end
- return math.floor((now()-S.lastFix)/1000)
- end
- -- Inventory stats
- local function invStats()
- local empty, fuelAvail = 0, 0
- for i=1,16 do
- local cnt = turtle.getItemCount(i)
- if cnt==0 then empty = empty + 1
- else
- turtle.select(i)
- if turtle.refuel(0) then fuelAvail = fuelAvail + cnt end
- end
- end
- turtle.select(1)
- return { freeSlots=empty, freePct=(empty/16)*100, fuelAvail=fuelAvail }
- end
- -- Heartbeat
- local function hb()
- S.fuel = turtle.getFuelLevel()
- if not S.pos then gpsPos() end
- rednet.broadcast({
- t=now(), op="status", id=os.getComputerID(), label=os.getComputerLabel(),
- pos=S.pos, fuel=S.fuel, facing=S.facing, status=S.status,
- extra={ kind=S.extra and S.extra.kind or nil, data=S.extra, gps={fixAge=fixAge()} },
- inv=invStats()
- }, PROTOCOL)
- end
- local function report(status, extra) S.status=status or S.status; S.extra=extra; hb() end
- -- UI
- local spinner = {"|","/","-","\\"}; local sp=1
- local function drawUI()
- if not S.uiOn then return end
- uiBegin()
- local lbl = os.getComputerLabel() or ("id"..os.getComputerID())
- ui.write(("Miner %s [%s] %s"):format(lbl, spinner[sp], uptime()))
- sp = (sp % #spinner) + 1
- local pos = S.pos and ("("..S.pos.x..","..S.pos.y..","..S.pos.z..")") or "(?, ?, ?)"
- local fage = fixAge() and (fixAge().."s") or "no-fix"
- ui.setCursorPos(1,3); ui.write(("Pos: %s GPS: %s"):format(pos, fage))
- ui.setCursorPos(1,4); ui.write(("Fuel: %s"):format(S.fuel or "?"))
- local inv = invStats()
- ui.setCursorPos(1,5); ui.write(("Inv: free %d/16 filled %d%% fuelItems %d")
- :format(inv.freeSlots, 100-math.floor(inv.freePct+0.5), inv.fuelAvail))
- ui.setCursorPos(1,6); ui.write(("Facing: %s Status: %s"):format(S.facing, S.status or "?"))
- if S.extra and S.extra.kind=="work" and S.extra.total and S.extra.step then
- local pct = math.floor(S.extra.step*100/S.extra.total+0.5)
- ui.setCursorPos(1,8); ui.write(("Work: %d/%d (%d%%)"):format(S.extra.step, S.extra.total, pct))
- elseif S.extra and S.extra.kind then
- ui.setCursorPos(1,8); ui.write(("Extra: %s"):format(S.extra.kind))
- end
- ui.setCursorPos(1,10); ui.write(("Abort:%s Stop:%s"):format(tostring(S.abort), tostring(S.stop)))
- local mname = peripheral.getName(modem)
- ui.setCursorPos(1,12); ui.write(("Modem: %s Rednet: open"):format(mname))
- if S.lastAck then ui.setCursorPos(1,13); ui.write(("Last ACK: %s"):format(S.lastAck)) end
- uiEnd()
- end
- -- Motion primitives
- local DIRS={N=0,E=1,S=2,W=3}
- local function setFacing(dir)
- if not DIRS[dir] then return false,"bad dir" end
- local cur=DIRS[S.facing] or 0; local tgt=DIRS[dir]
- local diff=(tgt-cur)%4
- if diff==1 then turtle.turnRight()
- elseif diff==2 then turtle.turnRight(); turtle.turnRight()
- elseif diff==3 then turtle.turnLeft() end
- S.facing=dir; report("idle",{kind="face",dir=dir}); return true
- end
- local function digForward() while turtle.detect() do turtle.dig(); sleep(0.05) end end
- local function stepForward()
- local tries=0
- digForward()
- while not turtle.forward() do
- digForward(); tries=tries+1
- if tries>60 then return false,"blocked" end
- sleep(0.05)
- end
- return true
- end
- local function stepUp()
- while turtle.detectUp() do turtle.digUp(); sleep(0.05) end
- local tries=0
- while not turtle.up() do
- while turtle.detectUp() do turtle.digUp(); sleep(0.05) end
- tries=tries+1; if tries>60 then return false,"blocked up" end
- sleep(0.05)
- end
- return true
- end
- local function stepDown()
- while turtle.detectDown() do turtle.digDown(); sleep(0.05) end
- local tries=0
- while not turtle.down() do
- while turtle.detectDown() do turtle.digDown(); sleep(0.05) end
- tries=tries+1; if tries>60 then return false,"blocked down" end
- sleep(0.05)
- end
- return true
- end
- local function moveAxis(axis, n)
- if n==0 then return true end
- local dir, steps = nil, math.abs(n)
- if axis=="x" then dir = n>0 and "E" or "W"
- elseif axis=="z" then dir = n>0 and "S" or "N"
- elseif axis=="y" then
- for i=1,steps do
- if S.abort then return false,"abort" end
- report("moving",{kind="move",axis="y",step=i,total=steps})
- local ok,err = (n>0) and stepUp() or stepDown()
- if not ok then return false,err end
- hb()
- end
- return true
- else return false,"bad axis" end
- assert(setFacing(dir))
- for i=1,steps do
- if S.abort then return false,"abort" end
- report("moving",{kind="move",axis=axis,step=i,total=steps})
- local ok,err = stepForward()
- if not ok then return false,err end
- hb()
- end
- return true
- end
- -- Distance/fuel estimates to get home
- local function distToHome()
- if not S.pos then return 999999 end
- local x,y,z = S.pos.x,S.pos.y,S.pos.z
- local d1 = math.abs(x-SHAFT_XZ.x) + math.abs(z-SHAFT_XZ.z)
- local d2 = math.abs(HOME.y - y)
- local d3 = math.abs(HOME.z - (-1092))
- return d1 + d2 + d3
- end
- local function fuelNeededToHome() return distToHome()*FUEL_PER_STEP + FUEL_MIN_BUFFER end
- -- Routing + base ops
- local function gotoPos(tx,ty,tz, order)
- order = order or {"y","x","z"}
- if not S.pos then gpsPos() end
- if not S.pos then return false,"no gps" end
- local dx,dy,dz = tx - S.pos.x, ty - S.pos.y, tz - S.pos.z
- local map={x=dx,y=dy,z=dz}
- for _,ax in ipairs(order) do
- local ok,err = moveAxis(ax, map[ax]); if not ok then return false,err end
- end
- return true
- end
- local function faceTo(target)
- if not S.pos then return false,"no gps" end
- local dx, dz = target.x - S.pos.x, target.z - S.pos.z
- if math.abs(dx)>math.abs(dz) then return setFacing(dx>0 and "E" or "W") else return setFacing(dz>0 and "S" or "N") end
- end
- local function returnToBase()
- report("returning",{kind="service",stage="to_shaft"}); assert(gotoPos(SHAFT_XZ.x, S.pos.y, SHAFT_XZ.z, {"x","z"}))
- report("returning",{kind="service",stage="shaft"}); assert(gotoPos(SHAFT_XZ.x, HOME.y, SHAFT_XZ.z, {"y"}))
- report("returning",{kind="service",stage="home"}); assert(gotoPos(HOME.x, HOME.y, HOME.z, {"z"}))
- return true
- end
- local function dumpAll()
- assert(gotoPos(CHEST_DUMP.x, CHEST_DUMP.y, CHEST_DUMP.z, {"x","y","z"}))
- faceTo({x=CHEST_DUMP.x+1, z=CHEST_DUMP.z})
- for i=1,16 do
- local d=turtle.getItemDetail(i)
- if d then
- turtle.select(i)
- if turtle.refuel(0) then else turtle.drop() end
- end
- end
- turtle.select(1); return true
- end
- local function refuelFromChest()
- assert(gotoPos(CHEST_FUEL.x, CHEST_FUEL.y, CHEST_FUEL.z, {"x","y","z"}))
- faceTo({x=CHEST_FUEL.x+1, z=CHEST_FUEL.z})
- local pulls=0
- while turtle.getFuelLevel() < REFUEL_TARGET and pulls<64 do
- if turtle.suck() then
- for i=1,16 do turtle.select(i); if turtle.refuel(0) then turtle.refuel() end end
- turtle.select(1)
- else break end
- pulls=pulls+1
- end
- return true
- end
- local function serviceIfNeeded()
- local need = fuelNeededToHome()
- if S.fuel < need then
- report("service",{kind="low_fuel", need=need, have=S.fuel})
- assert(returnToBase()); assert(dumpAll()); assert(refuelFromChest()); assert(gotoPos(HOME.x, HOME.y, HOME.z))
- report("idle",{kind="serviced"}); return true
- end
- local inv = invStats()
- if inv.freeSlots <= 1 then
- report("service",{kind="dump"})
- assert(returnToBase()); assert(dumpAll()); assert(refuelFromChest()); assert(gotoPos(HOME.x, HOME.y, HOME.z))
- report("idle",{kind="serviced"}); return true
- end
- return false
- end
- -- Mining (single axis only)
- local function mineVector(axis, len, stopOnAir, stopOnDirt)
- len = tonumber(len) or 0
- if len==0 then return true end
- serviceIfNeeded()
- if axis=="y" then
- local steps=math.abs(len)
- for i=1,steps do
- if S.abort then return false,"abort" end
- report("ACTIVE",{kind="work",step=i,total=steps})
- local ok,err = (len>0) and stepUp() or stepDown()
- if not ok then return false,err end
- hb(); serviceIfNeeded()
- end
- report("idle"); return true
- end
- local dir = (axis=="x") and ((len>0) and "E" or "W") or (axis=="z") and ((len>0) and "S" or "N") or nil
- if not dir then return false,"bad axis" end
- assert(setFacing(dir))
- local steps = math.abs(len)
- for i=1,steps do
- if S.abort then return false,"abort" end
- if stopOnAir or stopOnDirt then
- local ok, data = turtle.inspect()
- if ok and stopOnDirt and data and data.name and data.name:find("dirt") then report("STOPPED",{kind="halt_dirt"}); return true end
- if (not ok) and stopOnAir then report("STOPPED",{kind="halt_air"}); return true end
- end
- report("ACTIVE",{kind="work",step=i,total=steps})
- digForward()
- local ok,err = stepForward()
- if not ok then return false,err end
- hb(); serviceIfNeeded()
- end
- report("idle"); return true
- end
- -- Debug commands
- local function dbg_echo(msg) return true, ("echo: "..tostring(msg)) end
- local function dbg_uptime() return true, uptime() end
- local function dbg_toggle_ui(on) S.uiOn = on~=false; return true, "ui:"..tostring(S.uiOn) end
- local function dbg_where() return true, S.pos end
- local function dbg_gps()
- local x,y,z = gps.locate(3)
- local ids = {rednet.lookup("gps")}
- local n=0 for _ in pairs(ids) do n=n+1 end
- return x~=nil, {x=x,y=y,z=z, fixAge=fixAge(), beacons=n}
- end
- local function dbg_net()
- local list={}
- for _,n in ipairs(peripheral.getNames()) do table.insert(list, n..":"..peripheral.getType(n)) end
- return true, {periph=list}
- end
- -- Threads
- local function uiThread() while true do drawUI(); sleep(UI_REFRESH) end end
- local function hbThread() while true do sleep(HEARTBEAT_SEC); hb() end end
- local function rxThread()
- while true do
- local sid,msg,proto = rednet.receive(PROTOCOL)
- if proto==PROTOCOL and type(msg)=="table" and msg.op=="cmd" then
- local ok,err=false,nil
- if msg.act=="face" then ok,err=setFacing(msg.dir)
- elseif msg.act=="up" then ok,err=moveAxis("y", math.abs(msg.n or 1))
- elseif msg.act=="down" then ok,err=moveAxis("y", -math.abs(msg.n or 1))
- elseif msg.act=="mine_vector" then ok,err=mineVector(msg.axis, msg.len, msg.stopOnAir, msg.stopOnDirt)
- elseif msg.act=="return_service" then ok,err=returnToBase()
- elseif msg.act=="dump" then ok,err=dumpAll()
- elseif msg.act=="refuel" then ok,err=refuelFromChest()
- elseif msg.act=="set_facing_tag" then S.facing=msg.dir or S.facing; ok=true
- elseif msg.act=="set_pos" then if msg.x and msg.y and msg.z then S.pos={x=msg.x,y=msg.y,z=msg.z}; ok=true else err="bad pos" end
- elseif msg.act=="abort" then S.abort=true; ok=true
- elseif msg.act=="resume" then S.abort=false; ok=true
- elseif msg.act=="stop" then S.stop=true; ok=true
- -- DEBUG:
- elseif msg.act=="dbg_echo" then ok,err=dbg_echo(msg.msg)
- elseif msg.act=="dbg_net" then ok,err=dbg_net()
- elseif msg.act=="dbg_gps" then ok,err=dbg_gps()
- elseif msg.act=="dbg_uptime" then ok,err=dbg_uptime()
- elseif msg.act=="dbg_toggle_ui" then ok,err=dbg_toggle_ui(msg.on)
- elseif msg.act=="dbg_where" then ok,err=dbg_where()
- else err="unknown act" end
- S.lastAck = (ok and "ok") or ("err:"..tostring(err))
- rednet.send(sid,{op="ack", ok=ok, err=err, ref=msg.ref}, PROTOCOL)
- hb()
- end
- end
- end
- report("idle",{kind="boot"})
- parallel.waitForAny(uiThread, hbThread, rxThread)
Advertisement
Add Comment
Please, Sign In to add comment