Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --[[
- ROBCO SGC DIALING COMPUTER
- Single-file CC:Tweaked script for a 2x4 advanced monitor
- Features:
- - Boot animation
- - Gate diagram + status
- - Address book (dial/add/delete)
- - Virtual DHD (manual address entry)
- - Iris control
- - Persistent audit log
- - Touchscreen UI
- - Designed to integrate with Stargate Journey mod
- NOTE:
- - Adjust peripheral names in CONFIG section as needed.
- - Adapt STARGATE JOURNEY ADAPTER methods to match your mod version.
- ]]--
- -------------------------
- -- CONFIG
- -------------------------
- local CONFIG = {
- monitorSide = "top", -- 2x4 advanced monitor
- stargateSide = "back", -- Stargate Journey peripheral
- logFile = "sgc_audit.log",
- addressBookFile = "sgc_address_book.json",
- theme = {
- bg = colors.black,
- fg = colors.green,
- accent = colors.lime,
- warning = colors.red,
- inactive = colors.gray,
- title = colors.cyan,
- }
- }
- -------------------------
- -- PERIPHERALS
- -------------------------
- local mon = peripheral.wrap(CONFIG.monitorSide)
- if not mon then error("Monitor not found on "..CONFIG.monitorSide) end
- mon.setTextScale(0.5)
- local w, h = mon.getSize()
- local sg = peripheral.wrap(CONFIG.stargateSide)
- if not sg then error("Stargate peripheral not found on "..CONFIG.stargateSide) end
- -------------------------
- -- STATE
- -------------------------
- local state = {
- currentScreen = "main", -- main | address_book | add_address | delete_address | dhd
- selectedEntry = nil,
- manualAddress = "",
- manualName = "",
- manualType = "MW", -- MW | PG | SD (Milky Way, Pegasus, Same Dimension)
- addressBook = {},
- gateStatus = "UNKNOWN",
- irisState = "UNKNOWN",
- lastEvent = "",
- }
- -------------------------
- -- UTIL
- -------------------------
- local function centerText(y, text, color)
- mon.setCursorPos(1, y)
- mon.setTextColor(color or CONFIG.theme.fg)
- local x = math.floor((w - #text) / 2) + 1
- mon.setCursorPos(x, y)
- mon.write(text)
- end
- local function clear(bg)
- mon.setBackgroundColor(bg or CONFIG.theme.bg)
- mon.clear()
- mon.setCursorPos(1,1)
- end
- local function drawBox(x1, y1, x2, y2, bg, fg, label)
- mon.setBackgroundColor(bg or CONFIG.theme.bg)
- mon.setTextColor(fg or CONFIG.theme.fg)
- for y = y1, y2 do
- mon.setCursorPos(x1, y)
- mon.write(string.rep(" ", x2 - x1 + 1))
- end
- if label then
- local lx = x1 + math.floor(((x2 - x1 + 1) - #label) / 2)
- local ly = math.floor((y1 + y2) / 2)
- mon.setCursorPos(lx, ly)
- mon.write(label)
- end
- end
- local function logEvent(msg)
- local f = fs.open(CONFIG.logFile, "a")
- if f then
- f.writeLine(os.date("%Y-%m-%d %H:%M:%S").." | "..msg)
- f.close()
- end
- state.lastEvent = msg
- end
- -------------------------
- -- PERSISTENCE
- -------------------------
- local function loadAddressBook()
- if not fs.exists(CONFIG.addressBookFile) then
- state.addressBook = {}
- return
- end
- local f = fs.open(CONFIG.addressBookFile, "r")
- if not f then return end
- local content = f.readAll()
- f.close()
- local ok, data = pcall(textutils.unserializeJSON, content)
- if ok and type(data) == "table" then
- state.addressBook = data
- else
- state.addressBook = {}
- end
- end
- local function saveAddressBook()
- local f = fs.open(CONFIG.addressBookFile, "w")
- if not f then return end
- f.write(textutils.serializeJSON(state.addressBook))
- f.close()
- end
- -------------------------
- -- STARGATE JOURNEY ADAPTER
- -- Adjust these to match your mod’s API
- -------------------------
- local function sg_getStatus()
- -- Example: adapt to your API
- -- return sg.getGateStatus() or "UNKNOWN"
- local ok, status = pcall(sg.getGateStatus)
- if ok and status then return status end
- return "UNKNOWN"
- end
- local function sg_getIrisState()
- local ok, iris = pcall(sg.getIrisState)
- if ok and iris then return iris end
- return "UNKNOWN"
- end
- local function sg_openIris()
- pcall(sg.openIris)
- logEvent("Iris opened")
- end
- local function sg_closeIris()
- pcall(sg.closeIris)
- logEvent("Iris closed")
- end
- local function sg_disconnect()
- pcall(sg.disconnect)
- logEvent("Gate disconnected")
- end
- local function sg_dial(address)
- -- address: string, e.g. "ABC-DEF-GHI"
- logEvent("Dialing "..address)
- pcall(sg.dial, address)
- end
- -------------------------
- -- BOOT ANIMATION
- -------------------------
- local function bootAnimation()
- clear()
- mon.setTextColor(CONFIG.theme.title)
- centerText(3, "ROBCO INDUSTRIES UNIFIED OPERATING SYSTEM")
- centerText(4, "STARGATE COMMAND DIALING COMPUTER")
- sleep(1.0)
- local lines = {
- "[BOOT] Initializing ROBCO SGC Subsystems...",
- "[BOOT] Linking to Stargate Journey Interface...",
- "[BOOT] Loading Address Book...",
- "[BOOT] Mounting Persistent Audit Log...",
- "[BOOT] Calibrating Gate Status Sensors...",
- "[BOOT] Verifying Iris Control Circuits...",
- "[OK] System Ready."
- }
- mon.setTextColor(CONFIG.theme.fg)
- local y = 6
- for _, line in ipairs(lines) do
- mon.setCursorPos(2, y)
- mon.write(line)
- y = y + 1
- sleep(0.4)
- end
- sleep(0.8)
- end
- -------------------------
- -- GATE DIAGRAM
- -------------------------
- local function drawGateDiagram(x, y, radius, status)
- -- Simple ASCII-ish ring
- local ringChar = "O"
- local activeColor = CONFIG.theme.accent
- local idleColor = CONFIG.theme.inactive
- local warnColor = CONFIG.theme.warning
- local color = idleColor
- if status == "CONNECTED" or status == "DIALING" or status == "OPEN" then
- color = activeColor
- elseif status == "ERROR" then
- color = warnColor
- end
- mon.setTextColor(color)
- -- crude circle
- for dx = -radius, radius do
- for dy = -radius, radius do
- if dx*dx + dy*dy >= (radius-1)^2 and dx*dx + dy*dy <= (radius+1)^2 then
- local px = x + dx
- local py = y + dy
- if px >= 1 and px <= w and py >= 1 and py <= h then
- mon.setCursorPos(px, py)
- mon.write(ringChar)
- end
- end
- end
- end
- -- center
- mon.setTextColor(CONFIG.theme.fg)
- mon.setCursorPos(x-1, y)
- mon.write("SG")
- end
- -------------------------
- -- MAIN SCREEN UI
- -------------------------
- -- Button layout (for 2x4 monitor, typically 32x16 chars):
- -- Top: title
- -- Left: gate diagram
- -- Right: status + iris
- -- Bottom: buttons row
- local buttons = {}
- local function registerButton(id, x1, y1, x2, y2, label, screen)
- buttons[id] = {
- x1=x1, y1=y1, x2=x2, y2=y2,
- label=label,
- screen=screen or "any"
- }
- end
- local function drawButton(id, bg, fg)
- local b = buttons[id]
- if not b then return end
- drawBox(b.x1, b.y1, b.x2, b.y2, bg, fg, b.label)
- end
- local function findButtonAt(x, y)
- for id, b in pairs(buttons) do
- if (b.screen == "any" or b.screen == state.currentScreen) and
- x >= b.x1 and x <= b.x2 and y >= b.y1 and y <= b.y2 then
- return id
- end
- end
- return nil
- end
- local function drawMainScreen()
- clear()
- mon.setTextColor(CONFIG.theme.title)
- centerText(1, "ROBCO SGC DIALING COMPUTER")
- state.gateStatus = sg_getStatus()
- state.irisState = sg_getIrisState()
- -- Gate diagram on left
- drawGateDiagram(math.floor(w*0.25), math.floor(h*0.45), 5, state.gateStatus)
- -- Status on right
- mon.setTextColor(CONFIG.theme.fg)
- mon.setCursorPos(math.floor(w*0.5), 3)
- mon.write("Gate: "..state.gateStatus)
- mon.setCursorPos(math.floor(w*0.5), 4)
- mon.write("Iris: "..state.irisState)
- mon.setCursorPos(math.floor(w*0.5), 5)
- mon.write("Last: "..(state.lastEvent or ""))
- -- Buttons bottom row
- local rowY = h-3
- registerButton("dial", 2, rowY, 8, rowY+2, "DIAL", "main")
- registerButton("hangup", 10, rowY, 16, rowY+2, "HANG UP", "main")
- registerButton("iris", 18, rowY, 24, rowY+2, "IRIS", "main")
- registerButton("addr_book", 26, rowY, 32, rowY+2, "ADDRESSES", "main")
- registerButton("dhd", 2, rowY-4, 16, rowY-2, "VIRTUAL DHD", "main")
- registerButton("logs_hint", 18, rowY-4, 32, rowY-2, "LOG: "..CONFIG.logFile, "main")
- drawButton("dial", CONFIG.theme.accent, CONFIG.theme.bg)
- drawButton("hangup", CONFIG.theme.warning, CONFIG.theme.bg)
- drawButton("iris", CONFIG.theme.title, CONFIG.theme.bg)
- drawButton("addr_book", CONFIG.theme.title, CONFIG.theme.bg)
- drawButton("dhd", CONFIG.theme.accent, CONFIG.theme.bg)
- drawButton("logs_hint", CONFIG.theme.inactive,CONFIG.theme.bg)
- end
- -------------------------
- -- ADDRESS BOOK SCREENS
- -------------------------
- local function drawAddressBookScreen()
- clear()
- state.currentScreen = "address_book"
- mon.setTextColor(CONFIG.theme.title)
- centerText(1, "ADDRESS BOOK")
- -- List entries
- mon.setTextColor(CONFIG.theme.fg)
- local y = 3
- for i, entry in ipairs(state.addressBook) do
- if y > h-4 then break end
- mon.setCursorPos(2, y)
- mon.write(string.format("%2d) %-10s %-3s %s", i, entry.name or "NONAME", entry.type or "?", entry.address or ""))
- y = y + 1
- end
- -- Buttons
- local rowY = h-3
- registerButton("addr_back", 2, rowY, 8, rowY+2, "BACK", "address_book")
- registerButton("addr_add", 10, rowY, 16, rowY+2, "ADD", "address_book")
- registerButton("addr_del", 18, rowY, 24, rowY+2, "DELETE", "address_book")
- registerButton("addr_dial", 26, rowY, 32, rowY+2, "DIAL", "address_book")
- drawButton("addr_back", CONFIG.theme.warning, CONFIG.theme.bg)
- drawButton("addr_add", CONFIG.theme.accent, CONFIG.theme.bg)
- drawButton("addr_del", CONFIG.theme.title, CONFIG.theme.bg)
- drawButton("addr_dial", CONFIG.theme.accent, CONFIG.theme.bg)
- mon.setCursorPos(2, h-5)
- mon.write("Tap entry number area to select.")
- end
- local function drawAddAddressScreen()
- clear()
- state.currentScreen = "add_address"
- mon.setTextColor(CONFIG.theme.title)
- centerText(1, "ADD ADDRESS")
- mon.setTextColor(CONFIG.theme.fg)
- mon.setCursorPos(2,3); mon.write("Name: "..state.manualName)
- mon.setCursorPos(2,4); mon.write("Addr: "..state.manualAddress)
- mon.setCursorPos(2,5); mon.write("Type: "..state.manualType.." (MW/PG/SD)")
- registerButton("add_save", 2, h-3, 8, h-1, "SAVE", "add_address")
- registerButton("add_cancel",10, h-3, 16, h-1, "CANCEL","add_address")
- registerButton("add_type", 18, h-3, 24, h-1, "TYPE", "add_address")
- registerButton("add_name", 2, 3, 20, 3, "EDIT NAME", "add_address")
- registerButton("add_addr", 2, 4, 20, 4, "EDIT ADDR", "add_address")
- drawButton("add_save", CONFIG.theme.accent, CONFIG.theme.bg)
- drawButton("add_cancel", CONFIG.theme.warning, CONFIG.theme.bg)
- drawButton("add_type", CONFIG.theme.title, CONFIG.theme.bg)
- end
- local function drawDeleteAddressScreen()
- clear()
- state.currentScreen = "delete_address"
- mon.setTextColor(CONFIG.theme.title)
- centerText(1, "DELETE ADDRESS")
- mon.setTextColor(CONFIG.theme.fg)
- mon.setCursorPos(2,3); mon.write("Tap entry number to delete.")
- mon.setCursorPos(2,4); mon.write("BACK to cancel.")
- local y = 6
- for i, entry in ipairs(state.addressBook) do
- if y > h-4 then break end
- mon.setCursorPos(2, y)
- mon.write(string.format("%2d) %-10s %-3s %s", i, entry.name or "NONAME", entry.type or "?", entry.address or ""))
- y = y + 1
- end
- registerButton("del_back", 2, h-3, 8, h-1, "BACK", "delete_address")
- drawButton("del_back", CONFIG.theme.warning, CONFIG.theme.bg)
- end
- -------------------------
- -- VIRTUAL DHD SCREEN
- -------------------------
- local function drawDhdScreen()
- clear()
- state.currentScreen = "dhd"
- mon.setTextColor(CONFIG.theme.title)
- centerText(1, "VIRTUAL DHD")
- mon.setTextColor(CONFIG.theme.fg)
- mon.setCursorPos(2,3); mon.write("Addr: "..state.manualAddress)
- mon.setCursorPos(2,4); mon.write("Type: "..state.manualType.." (MW/PG/SD)")
- -- Simple keypad: we treat address as free-form string, but
- -- you can enforce 7/8/9 length based on type if you want.
- local keys = {
- {"A","B","C","D","E","F","G","H"},
- {"I","J","K","L","M","N","O","P"},
- {"Q","R","S","T","U","V","W","X"},
- {"Y","Z","-","DEL","CLR","OK","BK",""}
- }
- local startY = 6
- local cellW = math.floor(w / 8)
- for row=1,#keys do
- for col=1,#keys[row] do
- local label = keys[row][col]
- if label ~= "" then
- local x1 = (col-1)*cellW + 1
- local x2 = col*cellW
- local y1 = startY + (row-1)*2
- local y2 = y1 + 1
- local id = "key_"..label
- registerButton(id, x1, y1, x2, y2, label, "dhd")
- drawButton(id, CONFIG.theme.accent, CONFIG.theme.bg)
- end
- end
- end
- end
- -------------------------
- -- INPUT HELPERS
- -------------------------
- local function readLinePrompt(prompt)
- -- crude text input using term, not monitor
- term.clear()
- term.setCursorPos(1,1)
- term.write(prompt..": ")
- return read()
- end
- local function cycleType(current)
- if current == "MW" then return "PG"
- elseif current == "PG" then return "SD"
- else return "MW" end
- end
- local function enforceAddressLength(addr, t)
- local len = #addr:gsub("-", "")
- if t == "MW" and len ~= 7 then
- return false, "Milky Way addresses must have 7 symbols."
- elseif t == "PG" and len ~= 8 then
- return false, "Pegasus addresses must have 8 symbols."
- elseif t == "SD" and len ~= 9 then
- return false, "Same-dimension addresses must have 9 symbols."
- end
- return true
- end
- -------------------------
- -- BUTTON HANDLERS
- -------------------------
- local function handleMainButton(id)
- if id == "dial" then
- -- If we have a selected entry, dial it; otherwise go to DHD
- if state.selectedEntry and state.addressBook[state.selectedEntry] then
- local e = state.addressBook[state.selectedEntry]
- local ok, err = enforceAddressLength(e.address, e.type or "MW")
- if not ok then
- logEvent("Dial failed: "..err)
- else
- sg_dial(e.address)
- end
- else
- state.manualAddress = ""
- state.manualType = "MW"
- drawDhdScreen()
- end
- elseif id == "hangup" then
- sg_disconnect()
- elseif id == "iris" then
- if state.irisState == "OPEN" then
- sg_closeIris()
- else
- sg_openIris()
- end
- elseif id == "addr_book" then
- drawAddressBookScreen()
- elseif id == "dhd" then
- state.manualAddress = ""
- state.manualType = "MW"
- drawDhdScreen()
- end
- end
- local function handleAddressBookTap(x, y)
- -- detect which entry row was tapped
- local rowStart = 3
- local index = y - rowStart + 1
- if index >= 1 and index <= #state.addressBook then
- state.selectedEntry = index
- logEvent("Selected entry "..index)
- drawAddressBookScreen()
- end
- end
- local function handleAddressBookButton(id, x, y)
- if id == "addr_back" then
- state.currentScreen = "main"
- drawMainScreen()
- elseif id == "addr_add" then
- state.manualName = ""
- state.manualAddress = ""
- state.manualType = "MW"
- drawAddAddressScreen()
- elseif id == "addr_del" then
- drawDeleteAddressScreen()
- elseif id == "addr_dial" then
- if state.selectedEntry and state.addressBook[state.selectedEntry] then
- local e = state.addressBook[state.selectedEntry]
- local ok, err = enforceAddressLength(e.address, e.type or "MW")
- if not ok then
- logEvent("Dial failed: "..err)
- else
- sg_dial(e.address)
- state.currentScreen = "main"
- drawMainScreen()
- end
- else
- logEvent("No entry selected")
- end
- else
- -- maybe tapped list area
- handleAddressBookTap(x, y)
- end
- end
- local function handleDeleteAddressTap(x, y)
- local rowStart = 6
- local index = y - rowStart + 1
- if index >= 1 and index <= #state.addressBook then
- local e = state.addressBook[index]
- logEvent("Deleted entry "..(e.name or "NONAME"))
- table.remove(state.addressBook, index)
- saveAddressBook()
- drawDeleteAddressScreen()
- end
- end
- local function handleDeleteAddressButton(id, x, y)
- if id == "del_back" then
- drawAddressBookScreen()
- else
- handleDeleteAddressTap(x, y)
- end
- end
- local function handleAddAddressButton(id)
- if id == "add_cancel" then
- drawAddressBookScreen()
- elseif id == "add_type" then
- state.manualType = cycleType(state.manualType)
- drawAddAddressScreen()
- elseif id == "add_name" then
- state.manualName = readLinePrompt("Enter name")
- drawAddAddressScreen()
- elseif id == "add_addr" then
- state.manualAddress = readLinePrompt("Enter address (e.g. ABC-DEF-GHI)")
- drawAddAddressScreen()
- elseif id == "add_save" then
- if state.manualName == "" or state.manualAddress == "" then
- logEvent("Cannot save: missing name or address")
- return
- end
- local ok, err = enforceAddressLength(state.manualAddress, state.manualType)
- if not ok then
- logEvent("Cannot save: "..err)
- return
- end
- table.insert(state.addressBook, {
- name = state.manualName,
- address = state.manualAddress,
- type = state.manualType
- })
- saveAddressBook()
- logEvent("Saved address "..state.manualName)
- drawAddressBookScreen()
- end
- end
- local function handleDhdKey(label)
- if label == "DEL" then
- state.manualAddress = state.manualAddress:sub(1, #state.manualAddress-1)
- elseif label == "CLR" then
- state.manualAddress = ""
- elseif label == "OK" then
- local ok, err = enforceAddressLength(state.manualAddress, state.manualType)
- if not ok then
- logEvent("Dial failed: "..err)
- else
- sg_dial(state.manualAddress)
- state.currentScreen = "main"
- drawMainScreen()
- end
- elseif label == "BK" then
- state.currentScreen = "main"
- drawMainScreen()
- else
- state.manualAddress = state.manualAddress..label
- end
- if state.currentScreen == "dhd" then
- drawDhdScreen()
- end
- end
- local function handleDhdButton(id)
- local label = id:match("^key_(.+)$")
- if label then
- handleDhdKey(label)
- end
- end
- -------------------------
- -- MAIN EVENT LOOP
- -------------------------
- local function main()
- loadAddressBook()
- bootAnimation()
- state.currentScreen = "main"
- drawMainScreen()
- while true do
- local e, p1, p2, p3 = os.pullEvent()
- if e == "monitor_touch" then
- local side, x, y = p1, p2, p3
- if side ~= CONFIG.monitorSide then
- -- ignore other monitors
- else
- local id = findButtonAt(x, y)
- if state.currentScreen == "main" then
- if id then handleMainButton(id) end
- elseif state.currentScreen == "address_book" then
- handleAddressBookButton(id, x, y)
- elseif state.currentScreen == "add_address" then
- if id then handleAddAddressButton(id) end
- elseif state.currentScreen == "delete_address" then
- handleDeleteAddressButton(id, x, y)
- elseif state.currentScreen == "dhd" then
- if id then handleDhdButton(id) end
- end
- end
- elseif e == "timer" or e == "rednet_message" or e == "stargate_status" then
- -- You can expand here to react to gate events if the mod fires them.
- -- For now, periodically refresh main screen when on it.
- if state.currentScreen == "main" then
- drawMainScreen()
- end
- end
- end
- end
- main()
Advertisement
Add Comment
Please, Sign In to add comment