Pastebin
Retrouvez, créez et partagez vos snippets en temps réel.
Rechercher un Pastebin
Aucun paste trouvé.
Créer un paste
Pastebin
Blog
prog
-- power_client_touch.lua -- Touch UI client that requests access from server and enforces energy gate via redstone output. -- Adds GPS locate: sends x,y,z to server on register/request/heartbeat if available. local PROTO = "power_access_ui_v2" -- MUST MATCH SERVER local CFG = { gateSide = "back", -- redstone output side to control Mekanism gate textScale = 0.5, autoDiscover = true, serverId = nil, -- set to a number to lock; else discover heartbeatSec = 8, gpsTimeout = 1.5, -- seconds for gps.locate gpsEverySec = 15, -- refresh gps at most every N seconds dataFile = "power_client_db.txt", ui = { bg = colors.black, panel = colors.gray, panel2 = colors.lightGray, accent = colors.cyan, ok = colors.lime, danger = colors.red, warn = colors.orange, muted = colors.gray, } } -- ========================= -- Utils / Storage / Rednet -- ========================= local function now() return math.floor(os.epoch("utc")/1000) end local function clamp(v,a,b) if v<a then return a elseif v>b then return b else return v end end local function trim(s) return (tostring(s or ""):gsub("^%s+",""):gsub("%s+$","")) end local function load() if fs.exists(CFG.dataFile) then local f = fs.open(CFG.dataFile, "r") local s = f.readAll(); f.close() local ok, t = pcall(textutils.unserialize, s) if ok and type(t)=="table" then return t end end return { user=nil, serverId=nil, lastX=nil, lastY=nil, lastZ=nil } end local function save(t) local f = fs.open(CFG.dataFile, "w") f.write(textutils.serialize(t)) f.close() end local function ensureRednet() local modemSide for _, n in ipairs(peripheral.getNames()) do if peripheral.getType(n) == "modem" then modemSide = n break end end if not modemSide then error("Aucun modem detecte (wireless) pour rednet.") end if not rednet.isOpen(modemSide) then rednet.open(modemSide) end end local function discoverServer(timeout) timeout = timeout or 3 rednet.broadcast({kind="discover"}, PROTO) local t0 = os.clock() while os.clock() - t0 < timeout do local id, msg, proto = rednet.receive(PROTO, 0.5) if id and proto == PROTO and type(msg)=="table" and msg.kind=="discover_reply" then return msg.serverId or id end end return nil end -- ========================= -- Monitor / Drawing Toolkit -- ========================= local function pickMonitor() local best, bestArea for _, name in ipairs(peripheral.getNames()) do if peripheral.getType(name) == "monitor" then local m = peripheral.wrap(name) local w,h = m.getSize() local area = w*h if not best or area > bestArea then best, bestArea = m, area end end end return best end local mon = pickMonitor() local screen = mon or term.current() if mon then mon.setTextScale(CFG.textScale) end local function scrSize() return screen.getSize() end local function set(bg, fg) screen.setBackgroundColor(bg); screen.setTextColor(fg) end local function clear(bg) set(bg or CFG.ui.bg, colors.white); screen.clear(); screen.setCursorPos(1,1) end local function fill(x1,y1,x2,y2,bg) set(bg, colors.white) local w = x2-x1+1 local line = (" "):rep(w) for y=y1,y2 do screen.setCursorPos(x1,y) screen.write(line) end end local function writeAt(x,y,text,fg,bg) if bg then screen.setBackgroundColor(bg) end if fg then screen.setTextColor(fg) end screen.setCursorPos(x,y) screen.write(text) end local function centerText(y, text, fg, bg) local w,_ = scrSize() local x = math.max(1, math.floor((w-#text)/2)+1) writeAt(x,y,text,fg,bg) end local function fit(text, maxLen) text = tostring(text or "") if #text <= maxLen then return text end if maxLen <= 3 then return text:sub(1,maxLen) end return text:sub(1, maxLen-3) .. "..." end local BTN = {} local function btnClear() BTN = {} end local function btnAdd(id,x1,y1,x2,y2,label,bg,fg) BTN[#BTN+1] = {id=id,x1=x1,y1=y1,x2=x2,y2=y2} fill(x1,y1,x2,y2,bg) local w = x2-x1+1 local tx = x1 + math.max(0, math.floor((w-#label)/2)) local ty = y1 + math.floor((y2-y1)/2) writeAt(tx,ty,label,fg,bg) end local function btnHit(x,y) for i=#BTN,1,-1 do local b = BTN[i] if x>=b.x1 and x<=b.x2 and y>=b.y1 and y<=b.y2 then return b.id end end return nil end -- ========================= -- Gate Control -- ========================= local function setGate(on) redstone.setOutput(CFG.gateSide, on == true) end -- ========================= -- State -- ========================= ensureRednet() local st = load() local S = { serverId = CFG.serverId or st.serverId, user = st.user, allowed = false, expires = 0, lastUpdate = 0, -- gps cached x = st.lastX, y = st.lastY, z = st.lastZ, gpsLastTry = 0, toast = nil, toastUntil = 0, editingUser = false, userBuf = "", } local function toast(msg, sec) S.toast = msg S.toastUntil = now() + (sec or 2) end local function fmtExp() if not S.allowed then return "-" end if not S.expires or S.expires == 0 then return "∞" end local left = S.expires - now() if left <= 0 then return "0s" end if left < 60 then return left.."s" end if left < 3600 then return math.floor(left/60).."m" end return math.floor(left/3600).."h" end local function fmtAgo(ts) if not ts or ts==0 then return "-" end local d = math.max(0, now()-ts) if d < 60 then return d.."s" end if d < 3600 then return math.floor(d/60).."m" end return math.floor(d/3600).."h" end local function fmtPos() if type(S.x)=="number" and type(S.y)=="number" and type(S.z)=="number" then return string.format("%d %d %d", math.floor(S.x), math.floor(S.y), math.floor(S.z)) end return "-" end -- ========================= -- GPS locate (cached) -- ========================= local function tryGPS(force) local t = now() if not force and (t - (S.gpsLastTry or 0)) < CFG.gpsEverySec then return (S.x~=nil) end S.gpsLastTry = t local ok, x, y, z = pcall(function() return gps.locate(CFG.gpsTimeout) end) if ok and type(x)=="number" and type(y)=="number" and type(z)=="number" then S.x, S.y, S.z = x, y, z st.lastX, st.lastY, st.lastZ = x, y, z save(st) return true end return false end -- ========================= -- User / Server -- ========================= local function pickUserUI() S.editingUser = true S.userBuf = S.user or "" end local function commitUser() local u = trim(S.userBuf) if u == "" or u:find("%s") or #u < 2 or #u > 16 then toast("Pseudo invalide (2-16, sans espace)", 2) return end S.user = u st.user = u save(st) S.editingUser = false toast("Pseudo: "..u, 2) end local function ensureServer() if not S.serverId and CFG.autoDiscover then S.serverId = discoverServer(4) end if not S.serverId then toast("Serveur introuvable", 2) return false end st.serverId = S.serverId save(st) return true end local function send(kind) if not ensureServer() then return end if not S.user then pickUserUI(); return end -- update gps if possible (non-blocking-ish: cached) tryGPS(false) local payload = { kind=kind, user=S.user } -- include coords if we have them if type(S.x)=="number" and type(S.y)=="number" and type(S.z)=="number" then payload.x, payload.y, payload.z = S.x, S.y, S.z end rednet.send(S.serverId, payload, PROTO) end -- ========================= -- UI Draw -- ========================= local function draw() btnClear() clear(CFG.ui.bg) local w,h = scrSize() -- top bar fill(1,1,w,2,CFG.ui.panel) writeAt(2,1,"MEGA POWER CLIENT", colors.white, CFG.ui.panel) local sid = tostring(S.serverId or "?") writeAt(2,2,"Serveur: "..sid.." Gate: "..CFG.gateSide, colors.lightGray, CFG.ui.panel) -- status card local cardY1 = 4 local cardY2 = math.min(h-6, 13) fill(2,cardY1,w-1,cardY2,CFG.ui.panel2) local uLine = "Utilisateur : "..tostring(S.user or "(non defini)") writeAt(4,cardY1+1,fit(uLine, w-6), colors.black, CFG.ui.panel2) local stTxt = S.allowed and "ACCES AUTORISE" or "ACCES REFUSE" local stCol = S.allowed and CFG.ui.ok or CFG.ui.danger fill(4,cardY1+3,w-3,cardY1+4, stCol) centerText(cardY1+3, stTxt, colors.black, stCol) writeAt(4,cardY1+6,"Expire : "..fmtExp(), colors.black, CFG.ui.panel2) writeAt(4,cardY1+7,"Derniere maj : "..fmtAgo(S.lastUpdate), colors.black, CFG.ui.panel2) -- position local pos = fmtPos() writeAt(4,cardY1+9,"Position : "..pos, colors.black, CFG.ui.panel2) -- gps indicator local gpsOk = (pos ~= "-") writeAt(4,cardY1+10, gpsOk and "GPS: OK" or "GPS: OFF (ajoute des antennes GPS)", gpsOk and colors.green or colors.red, CFG.ui.panel2) -- buttons local by = h-4 fill(1,by,w,h,CFG.ui.panel) local x = 2 local function add(id,label,bg,fg,width) width = width or 12 btnAdd(id, x, by+1, x+width-1, by+2, label, bg, fg) x = x + width + 1 end add("REQ","Demander", CFG.ui.accent, colors.black, 12) add("REFRESH","Refresh", CFG.ui.panel2, colors.black, 12) add("GPS","GPS", CFG.ui.panel2, colors.black, 8) add("USER","Pseudo", CFG.ui.panel2, colors.black, 10) add("QUIT","Quit", CFG.ui.warn, colors.black, 8) -- typing overlay for username if S.editingUser then local ow,oh = math.min(w-4, 32), 7 local ox1 = math.floor((w-ow)/2)+1 local oy1 = math.floor((h-oh)/2)+1 local ox2 = ox1+ow-1 local oy2 = oy1+oh-1 fill(ox1,oy1,ox2,oy2, CFG.ui.panel2) writeAt(ox1+2,oy1+1,"Nouveau pseudo:", colors.black, CFG.ui.panel2) writeAt(ox1+2,oy1+3,"["..(S.userBuf or "").."_]", colors.black, CFG.ui.panel2) btnAdd("U_OK", ox2-12, oy2-1, ox2-2, oy2-1, "OK", CFG.ui.ok, colors.black) btnAdd("U_X", ox1+2, oy2-1, ox1+10, oy2-1, "CANCEL", CFG.ui.danger, colors.white) end if S.toast and now() < S.toastUntil then fill(1,h,w,h,CFG.ui.panel2) centerText(h, S.toast, colors.black, CFG.ui.panel2) end end -- ========================= -- Event Handling -- ========================= local function onButton(id) if not id then return end if S.editingUser then if id == "U_X" then S.editingUser=false; return end if id == "U_OK" then commitUser(); return end return end if id == "REQ" then send("request") toast("Demande envoyee", 2) return end if id == "REFRESH" then send("heartbeat") toast("Sync...", 1) return end if id == "GPS" then local ok = tryGPS(true) toast(ok and ("GPS OK: "..fmtPos()) or "GPS OFF / introuvable", 2) -- push coords quickly if we have server+user if ok and S.serverId and S.user then send("heartbeat") end return end if id == "USER" then pickUserUI() return end if id == "QUIT" then setGate(false) error("Quit") end end -- ========================= -- Background Networking -- ========================= local function netLoop() -- init if not S.serverId then ensureServer() end if not S.user then pickUserUI() end -- initial gps (non-blocking) tryGPS(false) if S.serverId and S.user then send("register") end local hb = os.startTimer(CFG.heartbeatSec) while true do local ev = { os.pullEventRaw() } if ev[1] == "terminate" then setGate(false); error("Terminated") end if ev[1] == "timer" and ev[2] == hb then hb = os.startTimer(CFG.heartbeatSec) if S.serverId and S.user then send("heartbeat") end end if ev[1] == "rednet_message" then local senderId, msg, proto = ev[2], ev[3], ev[4] if proto == PROTO and (not S.serverId or senderId == S.serverId) and type(msg)=="table" then if msg.kind == "status" and msg.user == S.user then S.allowed = msg.allowed == true S.expires = msg.expires or 0 S.lastUpdate = now() setGate(S.allowed) toast(S.allowed and ("Acces OK ("..fmtExp()..")") or "Acces refuse", 2) end end end end end local function uiLoop() while true do draw() local timer = os.startTimer(1) while true do local ev = { os.pullEventRaw() } if ev[1] == "terminate" then setGate(false); error("Terminated") end if ev[1] == "timer" and ev[2] == timer then break end if ev[1] == "monitor_touch" then local _,_,x,y = table.unpack(ev) onButton(btnHit(x,y)) break end if ev[1] == "mouse_click" and not mon then local _,_,x,y = table.unpack(ev) onButton(btnHit(x,y)) break end if ev[1] == "char" and S.editingUser then local ch = ev[2] if ch and #S.userBuf < 16 then S.userBuf = S.userBuf .. ch end break end if ev[1] == "key" and S.editingUser then local k = ev[2] if k == keys.backspace then S.userBuf = S.userBuf:sub(1, math.max(0, #S.userBuf-1)) elseif k == keys.enter or k == keys.numPadEnter then commitUser() elseif k == keys.escape then S.editingUser = false end break end end end end -- ========================= -- Start -- ========================= setGate(false) parallel.waitForAny(netLoop, uiLoop)
Créé il y a 3 semaines.