-- central_startup.lua (updated)
-- Advanced computer with Ender modem + two 3x3 Advanced Monitors.
-- LEFT monitor: widened columns table. RIGHT monitor: larger font with focused turtle details.
-- Adds miner-specific commands: mine_vector, return_service, dump, refuel, abort/resume/stop.
-- ====== CONFIG ======
local PROTOCOL = "minesys:v1"
local MON_LEFT = "left" -- side name of LEFT monitor (3x3 merged)
local MON_RIGHT = "right" -- side name of RIGHT monitor (3x3 merged)
local HOME_POS = {x=-924,y=99,z=-1093}
-- =====================
-- Peripherals
local modem = peripheral.find("modem", function(_,p) return p.isWireless() end)
assert(modem, "No wireless/Ender modem")
rednet.open(peripheral.getName(modem))
local monL = peripheral.wrap(MON_LEFT); assert(monL and peripheral.getType(MON_LEFT)=="monitor","LEFT monitor not found")
local monR = peripheral.wrap(MON_RIGHT); assert(monR and peripheral.getType(MON_RIGHT)=="monitor","RIGHT monitor not found")
monL.setTextScale(0.5) -- keep dense table on the left
monR.setTextScale(1.0) -- larger text on the right (more readable)
-- State
local function now() return os.epoch("utc") end
-- turtles[key] = {id,label,pos={x,y,z},fuel,facing,status,inv={freeSlots,freePct,fuelAvail},tSeen,job}
local turtles = {}
local focusKey = nil
local function count(tbl) local n=0 for _ in pairs(tbl) do n=n+1 end return n end
local function dist2D(a,b) local dx=a.x-b.x local dz=a.z-b.z return math.sqrt(dx*dx+dz*dz) end
-- Ingest miner heartbeat/status
local function upsertStatus(m)
local key = m.label or m.id; if not key then return end
local t = turtles[key] or {}
t.id = m.id or t.id
t.label = m.label or t.label or ("t"..tostring(m.id or ""))
t.pos = m.pos or t.pos
t.fuel = m.fuel or t.fuel
t.facing = m.facing or t.facing
t.status = m.status or t.status or "?"
if m.extra and m.extra.inv then
t.inv = t.inv or {}
t.inv.freeSlots = m.extra.inv.freeSlots
t.inv.freePct = m.extra.inv.freePct
end
if m.extra and m.extra.inv and m.extra.inv.fuelAvail then
t.inv = t.inv or {}; t.inv.fuelAvail = m.extra.inv.fuelAvail
end
t.tSeen = now()
turtles[key] = t
if not focusKey then focusKey = key end
end
-- Task classifier
local function classifyTask(t)
local s = (t.status or ""):lower()
if s:find("err") or s:find("stop") then return "STOPPED", colors.red, nil end
if t.job and t.job.kind=="move" and t.job.total and t.job.done then
local pct = math.max(0, math.min(100, math.floor((1 - (t.job.done/t.job.total))*100+0.5)))
return "MOVING", colors.orange, pct
end
if t.job and t.job.kind=="work" and t.job.total and t.job.done then
local pct = math.max(0, math.min(100, math.floor((1 - (t.job.done/t.job.total))*100+0.5)))
return "ACTIVE", colors.yellow, pct
end
if t.pos and dist2D(t.pos, HOME_POS)<=2 and (s=="waiting" or s=="idle" or s=="") then
return "HOME", colors.lime, nil
end
return "HOME", colors.lime, nil
end
-- LEFT monitor: widened columns
-- Columns: >Lbl | X | Y | Z | Task(%) | Fuel(cur/inv) | Inv%Filled
local function drawLeft()
local m=monL
m.setBackgroundColor(colors.black); m.clear()
m.setTextColor(colors.white)
m.setCursorPos(1,1); m.write(("Turtles: %d"):format(count(turtles)))
m.setCursorPos(1,2); m.write("Label X Y Z Task(%) Fuel Inv%")
local y=3
local h
do local _,hh=m.getSize(); h=hh end
for _,t in pairs(turtles) do
if y>h then break end
local lbl=(t.label or "?"):sub(1,8)
local xs=(t.pos and t.pos.x) and string.format("%5d", t.pos.x) or " ?"
local ys=(t.pos and t.pos.y) and string.format("%5d", t.pos.y) or " ?"
local zs=(t.pos and t.pos.z) and string.format("%5d", t.pos.z) or " ?"
local task,col,pct = classifyTask(t)
local fuelCur = t.fuel and tostring(t.fuel) or "?"
local fuelInv = t.inv and t.inv.fuelAvail and tostring(t.inv.fuelAvail) or "?"
local invFilled = t.inv and t.inv.freePct and (100 - math.floor(t.inv.freePct+0.5)) or nil
local invTxt = invFilled and string.format("%3d", invFilled) or " --"
m.setTextColor(colors.white)
m.setCursorPos(1,y); m.write(string.format(">%-8s", lbl))
m.setCursorPos(11,y); m.write(xs)
m.setCursorPos(17,y); m.write(ys)
m.setCursorPos(23,y); m.write(zs)
m.setCursorPos(29,y); m.setTextColor(col)
m.write(string.format("%-8s", task))
if pct then m.write(string.format("(%3d)", pct)) else m.write(" ") end
m.setTextColor(colors.white)
m.setCursorPos(44,y); m.write(string.format("%-6s/%-6s", fuelCur, fuelInv))
m.setCursorPos(58,y); m.write(invTxt)
-- focus marker
if focusKey == (t.label or t.id) then
m.setCursorPos(1,y); m.setTextColor(colors.cyan); m.write(">")
m.setTextColor(colors.white)
end
y=y+1
end
end
-- RIGHT monitor: larger font details
local function drawRight()
local m=monR
m.setBackgroundColor(colors.black); m.clear()
m.setTextColor(colors.white)
local t = focusKey and turtles[focusKey]
if not t then m.setCursorPos(1,1); m.write("No focus"); return end
local task,col,pct = classifyTask(t)
local invFilled = t.inv and t.inv.freePct and (100 - math.floor(t.inv.freePct+0.5)) or nil
m.setCursorPos(1,1); m.write("Focus:"); m.setTextColor(colors.cyan)
m.write(" "..(t.label or ("#"..tostring(t.id)))); m.setTextColor(colors.white)
m.setCursorPos(1,3); m.write("Pos: ")
m.write(t.pos and string.format("(%d,%d,%d)", t.pos.x,t.pos.y,t.pos.z) or "(?, ?, ?)")
m.setCursorPos(1,4); m.write("Facing: "..(t.facing or "?"))
m.setCursorPos(1,5); m.write("Fuel: "..(t.fuel and tostring(t.fuel) or "?"))
m.setCursorPos(1,6); m.write("FuelInv: "..(t.inv and t.inv.fuelAvail and tostring(t.inv.fuelAvail) or "?"))
m.setCursorPos(1,7); m.write("Inv fill: "..(invFilled and (invFilled.."%") or "?"))
m.setCursorPos(1,9); m.setTextColor(col)
m.write("Task: "..task..(pct and (" ("..pct.."%)") or "")); m.setTextColor(colors.white)
if t.status then m.setCursorPos(1,11); m.write("Status: "..t.status) end
if t.job then
m.setCursorPos(1,12); m.write("Job: "..(t.job.kind or "?"))
if t.job.total then m.setCursorPos(1,13); m.write(("Steps: %d/%d"):format(t.job.done or 0, t.job.total)) end
if t.job.note then m.setCursorPos(1,14); m.write("Note: "..t.job.note) end
end
m.setCursorPos(1,16); m.setTextColor(colors.lightGray)
m.write("help | focus <lab> | mv | svc | dump | refuel | abort/resume/stop")
m.setTextColor(colors.white)
end
-- ---- Send commands / job tracking ----
local function findKey(lblOrId)
for k,t in pairs(turtles) do
if t.label==lblOrId or tostring(t.id)==tostring(lblOrId) then return k end
end
end
local function sendCmd(target, tbl, onAck)
tbl.op="cmd"; tbl.ref=math.random(1,1e9)
local tgtID=nil
for _,t in pairs(turtles) do
if t.label==target or tostring(t.id)==tostring(target) then tgtID=t.id break end
end
if not tgtID then return false,"unknown target" end
rednet.send(tgtID, tbl, PROTOCOL)
local tm=os.startTimer(3)
while true do
local ev,a,b,c = os.pullEvent()
if ev=="rednet_message" then
local sid,msg,proto=a,b,c
if proto==PROTOCOL and type(msg)=="table" and msg.op=="ack" and msg.ref==tbl.ref then
if onAck then onAck(msg) end
return msg.ok, msg.err
end
elseif ev=="timer" and a==tm then
return false,"ack timeout"
end
end
end
-- Miner-oriented helpers
local function mv(lbl, axis, len, haltAir, haltDirt)
len = tonumber(len) or 0
local ha = (haltAir == "1") or (haltAir == true)
local hd = (haltDirt== "1") or (haltDirt== true)
return sendCmd(lbl, {act="mine_vector", axis=axis, len=len, stopOnAir=ha, stopOnDirt=hd}, function()
local k=findKey(lbl); if not k then return end
local t=turtles[k]; t.job = {kind="work", total=math.abs(len), done=0, note=("axis "..axis)}
end)
end
local function service(lbl) return sendCmd(lbl, {act="return_service"}) end
local function doDump(lbl) return sendCmd(lbl, {act="dump"}) end
local function doRefuel(lbl) return sendCmd(lbl, {act="refuel"}) end
local function doAbort(lbl) return sendCmd(lbl, {act="abort"}) end
local function doResume(lbl) return sendCmd(lbl, {act="resume"}) end
local function doStop(lbl) return sendCmd(lbl, {act="stop"}) end
local function setFacingTag(lbl,dir) return sendCmd(lbl, {act="set_facing_tag", dir=dir}) end
-- Progress bump on movement (coarse). Any position delta advances current job by 1.
local function onMinerStatusForProgress(k, prevPos)
local t=turtles[k]; if not t then return end
if not prevPos or not t.pos then return end
if prevPos.x~=t.pos.x or prevPos.y~=t.pos.y or prevPos.z~=t.pos.z then
if t.job then
t.job.done = math.min((t.job.done or 0)+1, t.job.total or (t.job.done or 0))
if t.job.total and t.job.done >= t.job.total then t.job=nil end
end
end
end
-- Net + UI
local function netUi()
while true do
local id,msg,proto = rednet.receive(PROTOCOL, 0.2)
if id and proto==PROTOCOL and type(msg)=="table" and msg.op=="status" then
local key = msg.label or msg.id
local prevPos = key and turtles[key] and turtles[key].pos
upsertStatus(msg)
onMinerStatusForProgress(key, prevPos)
end
drawLeft(); drawRight()
end
end
-- REPL
local function help()
print("help")
print("list")
print("focus <label|id>")
print("mv <label> <axis:x|y|z> <len> [haltAir 0/1] [haltDirt 0/1]")
print("svc <label> -- return_service (dump+refuel handled on miner)")
print("dump <label> -- force dump")
print("refuel <label> -- force refuel")
print("abort <label> | resume <label> | stop <label>")
print("tagface <label> <N|E|S|W>")
end
local function repl()
term.clear(); term.setCursorPos(1,1); help()
while true do
io.write("> ")
local line = read()
local a,b,c,d,e,f = line:match("^(%S+)%s*(%S*)%s*(%S*)%s*(%S*)%s*(%S*)%s*(%S*)")
if a=="help" then help()
elseif a=="list" then
for _,t in pairs(turtles) do
print((t.label or "?"), t.id or "?", t.pos and (t.pos.x..","..t.pos.y..","..t.pos.z) or "?, ?, ?")
end
elseif a=="focus" then
local k=findKey(b); if k then focusKey=k else print("unknown") end
elseif a=="mv" then
if b=="" or c=="" or d=="" then print("usage: mv <label> <axis:x|y|z> <len> [haltAir 0/1] [haltDirt 0/1]") else
print(mv(b,c,d,e,f))
end
elseif a=="svc" then print(service(b))
elseif a=="dump" then print(doDump(b))
elseif a=="refuel" then print(doRefuel(b))
elseif a=="abort" then print(doAbort(b))
elseif a=="resume" then print(doResume(b))
elseif a=="stop" then print(doStop(b))
elseif a=="tagface" then print(setFacingTag(b,c))
else print("unknown") end
end
end
parallel.waitForAny(netUi, repl)
Comments