Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- ============================================================
- -- RADAR SURVEILLANCE SYSTEM v2.4
- -- Computronics Radar Edition
- -- Retro Government Terminal Interface (1960s-70s Style)
- -- ============================================================
- local component = require("component")
- local event = require("event")
- local term = require("term")
- local fs = require("filesystem")
- local serial = require("serialization")
- local computer = require("computer")
- local gpu = component.gpu
- if not component.isAvailable("radar") then
- io.stderr:write("SYSTEM ERROR: RADAR COMPONENT NOT FOUND\n")
- os.exit()
- end
- local radar = component.radar
- local hasInternet = component.isAvailable("internet")
- local internet = hasInternet and require("internet") or nil
- local hasDataCard = component.isAvailable("data")
- local dataCard = hasDataCard and component.data or nil
- -- ============================================================
- -- ЦВЕТА
- -- ============================================================
- local C = {
- bg = 0x000000,
- border = 0x00AA00,
- header = 0x00FF00,
- text = 0x00CC00,
- dim = 0x005500,
- alert = 0xFF3300,
- warning = 0xFFAA00,
- ok = 0x00FF00,
- highlight = 0x33FF33,
- classified = 0xFF0000,
- }
- local oldBg = gpu.getBackground()
- local oldFg = gpu.getForeground()
- local W, H = gpu.getResolution()
- -- ============================================================
- -- КОНФИГ
- -- ============================================================
- local CONFIG_FILE = "/home/radar_config.dat"
- local LOG_FILE = "/home/radar_log.txt"
- local config = {
- scanRange = 64,
- scanInterval = 3,
- timeZone = 3,
- mode = "local",
- botToken = "",
- chatID = "",
- whiteList = {"cloud", "LeShyj", "entity.Cat.name", "reload", "karnel"},
- }
- -- ============================================================
- -- ШИФРОВАНИЕ
- -- ============================================================
- local function xorCrypt(data, key)
- local result, keyLen = {}, #key
- for i = 1, #data do
- local ki = ((i-1) % keyLen) + 1
- local xb = (string.byte(data,i) + string.byte(key,ki)) % 256
- result[i] = string.char(xb)
- end
- return table.concat(result)
- end
- local function toHex(s)
- return (s:gsub(".", function(c) return string.format("%02x", c:byte()) end))
- end
- local function fromHex(h)
- return (h:gsub("..", function(cc) return string.char(tonumber(cc,16)) end))
- end
- local function encryptData(plain, pass)
- if hasDataCard then
- local key = dataCard.sha256(pass):sub(1,16)
- local iv = dataCard.random(16)
- return "AES:" .. toHex(iv) .. ":" .. toHex(dataCard.encrypt(plain, key, iv))
- end
- return "XOR:" .. toHex(xorCrypt(plain, pass))
- end
- local function decryptData(cipher, pass)
- if cipher:sub(1,4) == "AES:" then
- if not hasDataCard then return nil, "No Data Card" end
- local p = {}; for s in cipher:gmatch("[^:]+") do p[#p+1]=s end
- if #p < 3 then return nil, "Bad AES format" end
- local ok, dec = pcall(dataCard.decrypt, fromHex(p[3]), dataCard.sha256(pass):sub(1,16), fromHex(p[2]))
- return ok and dec or nil, "Decryption failed"
- elseif cipher:sub(1,4) == "XOR:" then
- return xorCrypt(fromHex(cipher:sub(5)), pass)
- end
- return nil, "Unknown format"
- end
- local function saveConfig(pass)
- local f, err = io.open(CONFIG_FILE, "w")
- if not f then return false, err end
- f:write(encryptData(serial.serialize(config), pass)); f:close()
- return true
- end
- local function loadConfig(pass)
- local f = io.open(CONFIG_FILE, "r")
- if not f then return false, "Not found" end
- local data, err = decryptData(f:read("*a"), pass); f:close()
- if not data then return false, err end
- local ok, t = pcall(serial.unserialize, data)
- if ok and type(t)=="table" then config=t; return true end
- return false, "Bad data"
- end
- -- ============================================================
- -- УТИЛИТЫ
- -- ============================================================
- local function sc(fg, bg)
- if fg then gpu.setForeground(fg) end
- if bg then gpu.setBackground(bg) end
- end
- local function centerText(y, txt, col)
- sc(col or C.text)
- local x = math.max(1, math.floor((W - #txt)/2) + 1)
- gpu.set(x, y, txt)
- end
- local function drawBox(x, y, w, h, title)
- sc(C.border)
- gpu.set(x, y, "+" .. string.rep("-", w-2) .. "+")
- gpu.set(x, y+h-1, "+" .. string.rep("-", w-2) .. "+")
- for i = 1, h-2 do
- gpu.set(x, y+i, "|"); gpu.set(x+w-1, y+i, "|")
- gpu.fill(x+1, y+i, w-2, 1, " ")
- end
- if title then sc(C.header); gpu.set(x+2, y, "[ "..title.." ]") end
- end
- local function getTime()
- -- fs.lastModified возвращает реальное время хоста в миллисекундах (Unix ms)
- -- os.time() возвращает игровое время — не подходит
- local name = os.tmpname()
- local f = io.open(name, "w")
- if f then
- f:close()
- local ms = fs.lastModified(name)
- fs.remove(name)
- if ms and ms > 0 then
- -- ms → секунды, прибавляем смещение GMT+3
- local t = math.floor(ms / 1000) + config.timeZone * 3600
- return os.date("%d.%m.%Y %H:%M:%S", t)
- end
- end
- -- Fallback: если tmpname не сработал
- return os.date("%d.%m.%Y %H:%M:%S", os.time())
- end
- local function urlencode(s)
- s = s:gsub("\n","\r\n")
- s = s:gsub("([^%w ])", function(c) return string.format("%%%02X", c:byte()) end)
- return s:gsub(" ", "+")
- end
- local function saveToFile(txt)
- local f = io.open(LOG_FILE, "a")
- if f then f:write(txt.."\n"); f:close() end
- end
- local function isWhitelisted(name)
- for _, v in ipairs(config.whiteList) do if v==name then return true end end
- return false
- end
- -- ============================================================
- -- TELEGRAM
- -- ============================================================
- local tgQueue = {}
- local function queueTelegram(txt) tgQueue[#tgQueue+1] = txt end
- local function processTelegramQueue()
- if not internet or #tgQueue == 0 then return end
- local txt = table.remove(tgQueue, 1)
- local url = "https://api.telegram.org/bot"..config.botToken..
- "/sendMessage?chat_id="..config.chatID..
- "&text="..urlencode(txt)
- local ok, h = pcall(internet.request, url)
- if ok and h then pcall(function() h() end) end
- end
- local function sendTelegramBlocking(txt)
- if not internet then return false end
- local url = "https://api.telegram.org/bot"..config.botToken..
- "/sendMessage?chat_id="..config.chatID..
- "&text="..urlencode(txt)
- local ok, h = pcall(internet.request, url)
- if not ok then return false end
- local res = ""; for chunk in h do res=res..chunk end
- return true
- end
- -- ============================================================
- -- РАЗБОР ОТВЕТА РАДАРА
- -- Формат: {{name="X", distance=Y}, {name="Z", distance=W}, ...}
- -- Возвращает: {["X"] = {name, distance, whitelisted}, ...}
- -- ============================================================
- local EXIT_DISTANCE = 30 -- дальше этой дистанции = считается вышедшим
- local function parseRadarResult(raw)
- local out = {}
- if type(raw) ~= "table" then return out end
- for i = 1, #raw do
- local entry = raw[i]
- if type(entry) == "table" then
- local name = entry.name
- local dist = entry.distance or 0
- -- Игрок виден радаром, но дальше порога выхода — не включаем в активные
- if type(name) == "string" and name ~= "" and dist < EXIT_DISTANCE then
- out[name] = {
- name = name,
- distance = dist,
- whitelisted = isWhitelisted(name),
- }
- end
- end
- end
- return out
- end
- -- ============================================================
- -- СОСТОЯНИЕ
- -- ============================================================
- -- currentTargets: {[name] = {name, distance, whitelisted}}
- -- Обновляется каждый скан целиком через = newScan
- local currentTargets = {}
- local eventLog = {} -- {time, kind="ENTER"/"EXIT", name, distance}
- local scanCount = 0
- local lastScanError = nil
- -- ============================================================
- -- ЦИКЛ СКАНИРОВАНИЯ
- -- ============================================================
- local function runScanCycle()
- scanCount = scanCount + 1
- lastScanError = nil
- -- Небольшая пауза — некоторые версии Computronics радара кешируют результат
- -- и возвращают старые данные если запрашивать слишком часто
- os.sleep(0.05)
- local ok, raw = pcall(radar.getPlayers, config.scanRange)
- if not ok then
- lastScanError = tostring(raw):sub(1, 60)
- return
- end
- if type(raw) ~= "table" then
- lastScanError = "bad type: " .. type(raw)
- return
- end
- local newScan = parseRadarResult(raw)
- local timeStr = getTime()
- -- ENTER: есть в newScan, не было в currentTargets
- for name, data in pairs(newScan) do
- if not currentTargets[name] then
- eventLog[#eventLog+1] = {time=timeStr, kind="ENTER", name=name, distance=data.distance}
- if not data.whitelisted then
- local msg = string.format("[ENTER] %s | %.1fm | %s", name, data.distance, timeStr)
- if config.mode == "telegram" then queueTelegram(msg) end
- saveToFile(msg)
- end
- end
- end
- -- EXIT: было в currentTargets, нет в newScan
- for name, data in pairs(currentTargets) do
- if not newScan[name] then
- eventLog[#eventLog+1] = {time=timeStr, kind="EXIT", name=name, distance=data.distance}
- if not data.whitelisted then
- local msg = string.format("[EXIT] %s | last %.1fm | %s", name, data.distance or 0, timeStr)
- if config.mode == "telegram" then queueTelegram(msg) end
- saveToFile(msg)
- end
- end
- end
- -- Заменяем состояние целиком → дистанции всегда актуальны
- currentTargets = newScan
- end
- -- ============================================================
- -- ОТРИСОВКА ГЛАВНОГО ЭКРАНА
- -- ============================================================
- local function drawMainScreen()
- gpu.setBackground(C.bg)
- gpu.fill(1, 1, W, H, " ")
- sc(C.classified)
- gpu.set(1, 1, string.rep("=", W))
- centerText(1, " TOP SECRET // SURVEILLANCE ACTIVE ")
- gpu.set(1, 2, string.rep("=", W))
- sc(C.header)
- centerText(3, "+--------------------------------------------+")
- centerText(4, "| R A D A R S U R V E I L L A N C E |")
- centerText(5, "| MONITORING ACTIVE |")
- centerText(6, "+--------------------------------------------+")
- -- Статусбар
- sc(C.text); gpu.set(2, 8, "STATUS: ")
- sc(C.ok); gpu.set(10, 8, "ONLINE")
- sc(C.text); gpu.set(20, 8, "| MODE: ")
- sc(C.highlight)
- gpu.set(28, 8, config.mode == "telegram" and "TG UPLINK" or "LOCAL ")
- sc(C.text); gpu.set(40, 8, "| RNG: ")
- sc(C.highlight); gpu.set(47, 8, config.scanRange.."m")
- sc(C.text); gpu.set(54, 8, "| SCAN#"..scanCount)
- sc(C.dim); gpu.set(1, 9, string.rep("-", W))
- -- Список живых целей (сортировка по дистанции)
- local liveList = {}
- for _, e in pairs(currentTargets) do liveList[#liveList+1] = e end
- table.sort(liveList, function(a,b) return (a.distance or 0) < (b.distance or 0) end)
- sc(C.warning); gpu.set(2, 10, ">> LIVE RADAR ["..#liveList.." contacts]")
- sc(C.dim); gpu.set(W-22, 10, "TIME: "..getTime())
- if lastScanError then
- sc(C.alert)
- gpu.set(2, 11, "ERR: "..lastScanError:sub(1, W-7))
- end
- sc(C.dim); gpu.set(1, 12, string.rep("-", W))
- local liveY = 13
- local maxLive = math.max(3, math.floor((H-18)/2))
- if #liveList == 0 then
- sc(C.dim); gpu.set(4, liveY, "-- NO CONTACTS IN RANGE --")
- else
- for i = 1, math.min(#liveList, maxLive) do
- local e = liveList[i]
- local y = liveY + i - 1
- -- Сначала очищаем всю строку чтобы старые символы не оставались
- gpu.setBackground(C.bg); gpu.fill(2, y, W-2, 1, " ")
- if e.whitelisted then
- sc(C.ok); gpu.set(2, y, "[OK] ")
- sc(C.text)
- else
- sc(C.alert); gpu.set(2, y, "[!!] ")
- sc(C.alert)
- end
- local info = string.format("%s | %.2fm", e.name, e.distance or 0)
- if #info > W-8 then info = info:sub(1, W-8) end
- gpu.set(7, y, info)
- end
- end
- -- Лог событий
- local usedLive = math.max(1, math.min(#liveList, maxLive))
- local logY = liveY + usedLive + 1
- sc(C.dim); gpu.set(1, logY, string.rep("-", W))
- sc(C.header); gpu.set(2, logY+1, "EVENT LOG ["..#eventLog.."] (ENTER / EXIT):")
- local maxLines = H - logY - 4
- local startIdx = math.max(1, #eventLog - maxLines + 1)
- for i = startIdx, #eventLog do
- local ent = eventLog[i]
- local y = logY + 2 + (i - startIdx)
- if y >= H-2 then break end
- if ent.kind == "ENTER" then
- sc(C.alert); gpu.set(2, y, "[IN ] ")
- else
- sc(C.warning); gpu.set(2, y, "[OUT] ")
- end
- sc(C.dim)
- local distStr = ent.distance and string.format("%.0fm", ent.distance) or "?"
- gpu.set(8, y, ent.time.." "..ent.name.." "..distStr)
- end
- -- Подвал
- sc(C.dim); gpu.set(1, H-2, string.rep("-", W))
- sc(C.text)
- gpu.set(2, H-1, "CYCLE:"..config.scanInterval.."s")
- gpu.set(14, H-1, "| EVENTS:"..#eventLog)
- gpu.set(30, H-1, "| WL:"..#config.whiteList)
- gpu.set(42, H-1, "| CRYPT:"..(hasDataCard and "AES" or "XOR"))
- if #tgQueue > 0 then sc(C.warning); gpu.set(56, H-1, "| TQ:"..#tgQueue) end
- sc(C.dim); gpu.set(1, H, string.rep("=", W))
- sc(C.warning); centerText(H, " [Q] TERMINATE SESSION ")
- end
- -- ============================================================
- -- ЭКРАНЫ ИНИЦИАЛИЗАЦИИ
- -- ============================================================
- local function clearScreen()
- gpu.setBackground(C.bg); gpu.fill(1,1,W,H," ")
- end
- local function inputAt(x, y, masked)
- term.setCursor(x, y); sc(C.highlight)
- if not masked then return io.read() end
- local buf = {}
- while true do
- local _, _, char, code = event.pull("key_down")
- if code == 28 then break
- elseif code == 14 and #buf > 0 then
- table.remove(buf)
- local cx = term.getCursor()
- gpu.set(cx-1, y, " "); term.setCursor(cx-1, y)
- elseif char and char >= 32 and char < 127 then
- buf[#buf+1] = string.char(char)
- gpu.set(x + #buf - 1, y, "*")
- term.setCursor(x + #buf, y)
- end
- end
- return table.concat(buf)
- end
- local function splash()
- clearScreen(); drawBox(1,1,W,H)
- centerText(2, "* * * T O P S E C R E T * * *", C.classified)
- centerText(3, "AUTHORIZED PERSONNEL ONLY", C.dim)
- local art = {
- " .---. ",
- " / \\ ",
- " | (( )) | ",
- " \\ / ",
- " .---`-----'---. ",
- " / R A D A R \\ ",
- " '----._________.----' ",
- " | | ",
- " | ||| | ",
- " _|___|___|_ ",
- }
- for i, ln in ipairs(art) do centerText(4+i, ln, C.header) end
- local iy = 16
- centerText(iy, "=======================================", C.text)
- centerText(iy+1, " RADAR SURVEILLANCE TERMINAL v2.4 ", C.text)
- centerText(iy+2, " COMPUTRONICS TRACKING DIVISION ", C.text)
- centerText(iy+3, "=======================================", C.text)
- centerText(iy+5, "Press any key to initialize system...", C.dim)
- end
- local function initialize()
- clearScreen(); splash(); event.pull("key_down")
- local password
- if fs.exists(CONFIG_FILE) then
- clearScreen(); drawBox(1,1,W,H)
- centerText(2, "* * * S E C U R E A C C E S S * * *", C.classified)
- sc(C.text); gpu.set(4,10,"Encrypted config: "..CONFIG_FILE)
- gpu.set(4,12,"ACCESS CODE > ")
- password = inputAt(18, 12, true)
- local ok, err = loadConfig(password)
- if ok then
- sc(C.ok); centerText(14,"CONFIG DECRYPTED [OK]")
- centerText(16,"Mode: "..config.mode:upper().." | Range: "..config.scanRange)
- os.sleep(2); return true, password
- end
- sc(C.alert); centerText(14,"FAILED: "..(err or "?"))
- centerText(16,"Starting fresh setup..."); os.sleep(2)
- end
- -- Новый пароль
- clearScreen(); drawBox(1,1,W,H)
- centerText(2, "* * * F I R S T R U N * * *", C.classified)
- sc(C.text)
- gpu.set(4,10,"Set a password to encrypt your config.")
- gpu.set(4,13,"NEW CODE > "); password = inputAt(18,13,true)
- gpu.set(4,15,"CONFIRM > ")
- if password ~= inputAt(18,15,true) then
- sc(C.alert); centerText(17,"MISMATCH - RETRY"); os.sleep(2)
- return initialize()
- end
- -- Выбор режима
- while true do
- clearScreen(); drawBox(1,1,W,H)
- centerText(4, "+==========================================+", C.header)
- centerText(5, "| SELECT OUTPUT MODE |", C.header)
- centerText(6, "+==========================================+", C.header)
- centerText(9, "[1] LOCAL STORAGE ("..LOG_FILE..")", C.highlight)
- centerText(11, "[2] TELEGRAM UPLINK", C.highlight)
- if not hasInternet then centerText(12," [!] NO INTERNET CARD", C.alert) end
- centerText(14,"CHOICE [1/2]: _", C.text)
- local _, _, char, code = event.pull("key_down")
- if char == 49 then config.mode="local"; break
- elseif char == 50 then
- if hasInternet then config.mode="telegram"
- else sc(C.alert); centerText(16,"No internet card! Using local."); os.sleep(2); config.mode="local"
- end
- break
- elseif code == 14 then return false, nil
- end
- end
- -- Telegram-токены
- if config.mode == "telegram" then
- clearScreen(); drawBox(1,1,W,H)
- centerText(4,"+==========================================+",C.header)
- centerText(5,"| TELEGRAM UPLINK CONFIG |",C.header)
- centerText(6,"+==========================================+",C.header)
- sc(C.text)
- gpu.set(4,9,"BOT TOKEN > "); term.setCursor(16,9); sc(C.highlight)
- config.botToken = io.read()
- sc(C.text)
- gpu.set(4,11,"CHAT ID > "); term.setCursor(16,11); sc(C.highlight)
- config.chatID = io.read()
- sc(C.warning); centerText(13,"TESTING CONNECTION..."); os.sleep(1)
- if sendTelegramBlocking("[SYSTEM] Radar v2.4 ONLINE.") then
- centerText(15,"CONNECTION OK", C.ok)
- else
- centerText(15,"FAILED - FALLING BACK TO LOCAL", C.alert)
- config.mode = "local"
- end
- os.sleep(2)
- end
- -- Сохранение
- clearScreen(); drawBox(1,1,W,H)
- centerText(H/2-1,"SAVING ENCRYPTED CONFIG...", C.warning)
- local ok, err = saveConfig(password)
- centerText(H/2+1, ok and "SAVED [OK]" or "SAVE FAILED: "..(err or "?"),
- ok and C.ok or C.alert)
- os.sleep(2)
- return true, password
- end
- -- ============================================================
- -- ГЛАВНЫЙ ЦИКЛ
- -- ============================================================
- local function main()
- gpu.setBackground(C.bg); gpu.setForeground(C.text)
- local ok = initialize()
- if not ok then
- gpu.setBackground(oldBg); gpu.setForeground(oldFg)
- term.clear(); print("SHUTDOWN - USER ABORT"); return
- end
- local running = true
- local lastScan = -9999
- drawMainScreen()
- while running do
- local id, _, char = event.pull(0.5)
- if id == "key_down" and (char == 113 or char == 81) then
- running = false
- end
- if running then
- local now = computer.uptime()
- if now - lastScan >= config.scanInterval then
- runScanCycle()
- drawMainScreen()
- lastScan = now
- end
- if config.mode == "telegram" then processTelegramQueue() end
- end
- end
- if config.mode == "telegram" then
- pcall(sendTelegramBlocking, "[SYSTEM] Radar v2.4 OFFLINE.")
- end
- gpu.setBackground(oldBg); gpu.setForeground(oldFg); term.clear()
- sc(C.ok)
- print("+------------------------------------------+")
- print("| SHUTDOWN COMPLETE |")
- print("| LOG: " .. LOG_FILE)
- print("| CONFIG: " .. CONFIG_FILE)
- print("| EVENTS: " .. #eventLog)
- print("+------------------------------------------+")
- gpu.setForeground(oldFg)
- end
- main()
Advertisement
Add Comment
Please, Sign In to add comment