-- 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 | 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 ") print("mv