diff --git a/addresses.lua b/addresses.lua new file mode 100644 index 0000000..443b875 --- /dev/null +++ b/addresses.lua @@ -0,0 +1,42 @@ +--[[ + Address Book + Contains all known stargate addresses organized by category +]] + +local addresses = {} + +--------------------------------------------- +-- MAIN GATES +--------------------------------------------- +-- Common 7-chevron addresses for main destinations + +addresses.MainGates = { + { "OVERWORLD", { 27, 25, 4, 35, 10, 28, 0 } }, + { "Nether", { 27, 23, 4, 34, 12, 28, 0 } }, + { "End", { 13, 24, 2, 19, 3, 30, 0 } }, + { "Abydos", { 26, 6, 14, 31, 11, 29, 0 } }, + { "Chulak", { 8, 1, 22, 14, 36, 19, 0 } } +} + +--------------------------------------------- +-- PLAYER GATES +--------------------------------------------- +-- Typically 9-chevron addresses for player bases + +addresses.playerGates = { + { "Glaive", { 33, 6, 10, 24, 1, 30, 3, 17, 0 } }, + { "Moon", { 32, 33, 8, 7, 25, 21, 14, 35, 0 } }, + { "Caldoric", { 18, 2, 24, 16, 8, 19, 4, 29, 0 } }, + { "Trading Hall", { 16, 19, 6, 18, 35, 27, 9, 8, 0 } } +} + +--------------------------------------------- +-- HAZARD GATES +--------------------------------------------- +-- Dangerous destinations + +addresses.hazardGates = { + -- Add hazardous gate addresses here +} + +return addresses diff --git a/config.lua b/config.lua new file mode 100644 index 0000000..289dbe3 --- /dev/null +++ b/config.lua @@ -0,0 +1,55 @@ +--[[ + Configuration File + All user-configurable settings for the Stargate Control System +]] + +local config = {} + +--------------------------------------------- +-- GATE BEHAVIOR +--------------------------------------------- + +-- Gate dialing speed (seconds between chevrons for non-Milky Way gates) +config.gatespeed = 0.5 + +-- Manual dial for Milky Way gates (rotating ring animation) +config.manualDial = true + +-- Access control +config.canAccessHazardGates = true + +--------------------------------------------- +-- IRIS CONFIGURATION +--------------------------------------------- + +config.irisEnabled = true +config.autoCloseIrisOnIncoming = true +config.irisCloseDelay = 0.1 -- seconds before closing iris on incoming +config.autoOpenIrisAfterDisconnect = true + +--------------------------------------------- +-- SECURITY +--------------------------------------------- + +-- Whitelist (if not empty, only these addresses can enter) +-- Format: {"Name", {address_table}} +config.whitelist = { + { "Glaive", { 33, 6, 10, 24, 1, 30, 3, 17, 0 } }, + { "Moon", { 32, 33, 8, 7, 25, 21, 14, 35, 0 } }, + { "Caldoric", { 18, 2, 24, 16, 8, 19, 4, 29, 0 } }, + { "Trading Hall", { 16, 19, 6, 18, 35, 27, 9, 8, 0 } } +} + +-- Blacklist (these addresses are always blocked) +config.blacklist = { + -- {"DangerGate", {1,2,3,4,5,6,0}}, +} + +--------------------------------------------- +-- LOGGING +--------------------------------------------- + +config.enableLogging = true +config.logFile = "stargate.log" + +return config diff --git a/currentCode.lua b/currentCode.lua deleted file mode 100644 index e69de29..0000000 diff --git a/display.lua b/display.lua new file mode 100644 index 0000000..638e688 --- /dev/null +++ b/display.lua @@ -0,0 +1,222 @@ +--[[ + Display Functions + Handles all monitor/screen drawing operations +]] + +local display = {} + +-- Will be set by startup.lua +local mon +local config +local addresses +local utils + +-- State variables +local buttonXY = {} +local computerAddresses = {} +local computerNames = {} +local x, y = 0, 0 + +function display.init(monitor, cfg, addr, utilsModule) + mon = monitor + config = cfg + addresses = addr + utils = utilsModule +end + +function display.getButtonData() + return buttonXY, computerAddresses, computerNames +end + +function display.clearButtonData() + buttonXY = {} + computerAddresses = {} + computerNames = {} +end + +--------------------------------------------- +-- DRAWING FUNCTIONS +--------------------------------------------- + +function display.drawIrisStatus() + mon.setCursorPos(1, 19) + mon.setBackgroundColor(colors.black) + local irisState = utils.getIrisState() + if irisState == "OPEN" then + mon.setTextColor(colors.green) + elseif irisState == "CLOSED" then + mon.setTextColor(colors.red) + else + mon.setTextColor(colors.gray) + end + mon.write("IRIS: " .. irisState .. " ") + mon.setTextColor(colors.white) + if irisState == "MOVING" then + sleep(0.1) + display.drawIrisStatus() + end +end + +function display.screenWrite(list, fcount, fy) + for i = 1, #list do + local x1, x2 = 0, 0 + + if fcount == 0 then + x = 2 + fcount = 1 + elseif fcount == 1 then + x = 11 + fcount = 2 + else + x = 20 + fcount = 0 + fy = fy + 2 + end + + mon.setCursorPos(x, fy) + mon.write(list[i][1]) + + x1 = x + x2 = x + 7 + + table.insert(buttonXY, { x1, x2, fy }) + table.insert(computerNames, list[i][1]) + table.insert(computerAddresses, list[i][2]) + end + + local oldterm = term.redirect(mon) + paintutils.drawFilledBox(23, 17, 28, 19, colors.red) + mon.setCursorPos(24, 18) + mon.write("Back") + term.redirect(oldterm) + + return fcount, fy +end + +function display.selectionTabs() + mon.setBackgroundColor(colors.black) + mon.clear() + local oldterm = term.redirect(mon) + + if #addresses.MainGates ~= 0 then + paintutils.drawFilledBox(2, 2, 13, 6, colors.purple) + mon.setCursorPos(4, 4) + mon.setBackgroundColor(colors.purple) + mon.write("Main Gates") + end + + if #addresses.playerGates ~= 0 then + paintutils.drawFilledBox(16, 2, 27, 6, colors.green) + mon.setCursorPos(18, 4) + mon.setBackgroundColor(colors.green) + mon.write("Player") + mon.setCursorPos(18, 5) + mon.write("Base gates") + end + + if (#addresses.hazardGates ~= 0) and (config.canAccessHazardGates == true) then + paintutils.drawFilledBox(2, 8, 13, 12, colors.red) + mon.setCursorPos(4, 9) + mon.setBackgroundColor(colors.red) + mon.write("Hazard") + mon.setCursorPos(4, 11) + mon.write("gates") + end + + paintutils.drawFilledBox(23, 17, 28, 19, colors.red) + mon.setCursorPos(24, 18) + mon.write("Back") + + display.drawIrisStatus() + term.redirect(oldterm) +end + +function display.showIncoming(addressString, allowed, reason) + mon.setBackgroundColor(colors.black) + mon.clear() + + if allowed then + mon.setBackgroundColor(colors.green) + mon.setTextScale(1) + mon.setCursorPos(1, 2) + mon.write("INCOMING - AUTHORIZED") + else + mon.setBackgroundColor(colors.red) + mon.setTextScale(1) + mon.setCursorPos(1, 2) + mon.write("INCOMING - UNAUTHORIZED") + mon.setCursorPos(1, 4) + mon.write(reason) + end + + mon.setCursorPos(1, 6) + mon.setBackgroundColor(colors.black) + mon.write("Address:\n" .. addressString) +end + +function display.showEntity(entityType, entityName, allowed) + mon.setTextScale(1) + mon.setCursorPos(1, 10) + mon.write("Type: " .. entityType) + mon.setCursorPos(1, 12) + mon.write("Name: " .. entityName) + + if not allowed then + mon.setCursorPos(1, 14) + mon.setTextColor(colors.red) + mon.write("IRIS IMPACT!") + mon.setTextColor(colors.white) + end +end + +function display.showDialing(chevron, symbol, gateType) + mon.setBackgroundColor(colors.black) + mon.clear() + mon.setBackgroundColor(colors.red) + mon.setTextScale(2) + mon.setCursorPos(2, 3) + mon.write("DIALING GATE") + mon.setCursorPos(4, 5) + mon.write("CHEVERON") + mon.setCursorPos(7, 7) + mon.setBackgroundColor(colors.black) + mon.write(chevron) + mon.setBackgroundColor(colors.red) + + if symbol ~= 0 then + mon.setCursorPos(4, 9) + mon.write("ENGAGED") + else + mon.setCursorPos(5, 9) + mon.setBackgroundColor(colors.green) + mon.write("LOCKED") + end +end + +function display.showConnected(destName, destAddr) + mon.setBackgroundColor(colors.green) + mon.clear() + mon.setTextScale(1) + mon.setCursorPos(6, 5) + mon.write(destName) + + mon.setCursorPos(3, 10) + for i = 1, #destAddr do + mon.write(destAddr[i]) + mon.write(" ") + end + + display.drawIrisStatus() +end + +function display.showMainMenu() + mon.setTextScale(1) + mon.setBackgroundColor(colors.black) + mon.clear() + mon.setCursorPos(9, 1) + mon.setBackgroundColor(colors.red) + mon.write("press to start") + display.drawIrisStatus() +end + +return display diff --git a/startup.lua b/startup.lua index e69de29..d97aad0 100644 --- a/startup.lua +++ b/startup.lua @@ -0,0 +1,427 @@ +--[[ + Stargate Control System - Main Entry Point + For Stargate Journey Mod - AllTheMods 9 + + Comprehensive control program featuring: + - Full dialing system with touch screen interface + - Automatic iris control with security features + - Incoming/outgoing wormhole management + - Address book with categories + - Entity tracking and logging + - Whitelist/blacklist security +]] + +--------------------------------------------- +-- LOAD MODULES +--------------------------------------------- + +local config = require("config") +local addresses = require("addresses") +local utils = require("utils") +local display = require("display") + +--------------------------------------------- +-- INITIALIZATION +--------------------------------------------- + +-- Find and initialize peripherals +local mon = peripheral.find("monitor") +local gate = peripheral.find("advanced_crystal_interface") + or peripheral.find("crystal_interface") + or peripheral.find("basic_interface") + +if gate == nil then + error("Stargate interface not found! Please connect an interface.") +end + +if mon == nil then + error("Monitor not found! Please connect a monitor.") +end + +-- Check iris availability +if gate.getIris() == nil then + print("Config has Iris enabled, but there is no iris! Disabling Iris") + config.irisEnabled = false +end + +-- Initialize modules +utils.init(config, gate) +display.init(mon, config, addresses, utils) + +-- Ensure gate starts disconnected +gate.disconnectStargate() + +-- Ensure iris starts open +if config.irisEnabled then + gate.openIris() +end + +--------------------------------------------- +-- GLOBAL STATE +--------------------------------------------- + +local dialing = false +local totalstate = nil +local disconnect = false +local incomingAddress = {} +local incomingEntityType = "" +local incomingEntityName = "" +local destAddress = {} +local destAddressname = "" +local selx, sely = 0, 0 +local y = 0 + +--------------------------------------------- +-- EVENT HANDLERS +--------------------------------------------- + +local function GetClick() + mon.setTextScale(1) + local event, _, xPos, yPos = os.pullEvent("monitor_touch") + return xPos, yPos +end + +local function GetActivation() + _, _, incomingAddress = os.pullEvent("stargate_incoming_wormhole") + utils.log("Incoming wormhole: " .. gate.addressToString(incomingAddress)) + return 1 +end + +local function ParaDisconnect() + local dx, dy = 0, 0 + _, _, dx, dy = os.pullEvent("monitor_touch") + + if (dx ~= 0) and (dy ~= 0) then + gate.disconnectStargate() + redstone.setOutput("top", false) + utils.log("Manual disconnect triggered") + end + return 1 +end + +local function EntityRead() + sleep(0.1) + _, _, incomingEntityType, incomingEntityName, _ = os.pullEvent("stargate_reconstructing_entity") + utils.log("Entity reconstructed: " .. incomingEntityName .. " (" .. incomingEntityType .. ")") + return 1 +end + +local function DisconnectCheck() + local _, _, disCode = os.pullEvent("stargate_disconnected") + redstone.setOutput("top", false) + utils.log("Stargate disconnected (code: " .. tostring(disCode) .. ")") + + if config.autoOpenIrisAfterDisconnect then + utils.openIris() + end + + return 2 +end + +local function Paratimeout() + sleep(300) + return 2 +end + +--------------------------------------------- +-- INCOMING WORMHOLE HANDLER +--------------------------------------------- + +local function handleIncomingWormhole() + mon.setBackgroundColor(colors.black) + mon.clear() + mon.setBackgroundColor(colors.red) + mon.setTextScale(1) + mon.setCursorPos(9, 4) + mon.write("INCOMING") + + incomingAddress = utils.addressToTable(incomingAddress) + + -- Check security + local allowed, reason = utils.isAddressAllowed(incomingAddress) + local addressString = gate.addressToString(incomingAddress) or "Unknown" + + utils.log("Incoming wormhole from: " .. addressString .. " " .. reason) + + -- Show incoming connection status + display.showIncoming(addressString, allowed, reason) + + -- Handle iris + if config.autoCloseIrisOnIncoming then + sleep(config.irisCloseDelay) + if allowed then + utils.openIris() + else + utils.closeIris() + end + end + + -- Monitor for entities + disconnect = false + while (disconnect == false) do + -- parallel.waitForAny runs multiple functions at the same time and returns which one finished first + -- Here we're watching for 3 things simultaneously: + -- 1 = EntityRead (someone/something came through the gate) + -- 2 = DisconnectCheck (gate disconnected on its own) + -- ParaDisconnect (user clicked screen to manually disconnect) + -- Whichever happens first, that function returns and we handle it + local incomingcheck = parallel.waitForAny(EntityRead, DisconnectCheck, ParaDisconnect) + if (incomingcheck == 1) then + display.showEntity(incomingEntityType, incomingEntityName, allowed) + incomingEntityType = "" + incomingEntityName = "" + else + disconnect = true + end + end + disconnect = false +end + +--------------------------------------------- +-- DIALING FUNCTIONS +--------------------------------------------- + +local function dialGate(address) + utils.log("Dialing: " .. gate.addressToString(address)) + + local gateType = gate.getStargateType() + + -- Manual Milky Way dialing with ring rotation + if gateType == "sgjourney:milky_way_stargate" and config.manualDial == true then + local addressLength = #address + + if addressLength == 8 then + gate.setChevronConfiguration({ 1, 2, 3, 4, 6, 7, 8, 5 }) + elseif addressLength == 9 then + gate.setChevronConfiguration({ 1, 2, 3, 4, 5, 6, 7, 8 }) + end + + local start = gate.getChevronsEngaged() + 1 + + for chevron = start, addressLength, 1 do + local symbol = address[chevron] + + if chevron % 2 == 0 then + gate.rotateClockwise(symbol) + else + gate.rotateAntiClockwise(symbol) + end + + while (not gate.isCurrentSymbol(symbol)) do + sleep(0) + end + + gate.openChevron() + sleep(1) + gate.closeChevron() + sleep(0.5) + + display.showDialing(chevron, symbol, gateType) + end + else + -- Automatic dialing for other gate types + if gateType ~= "sgjourney:universe_stargate" then + local addressLength = #address + if addressLength == 8 then + gate.setChevronConfiguration({ 1, 2, 3, 4, 6, 7, 8, 5 }) + elseif addressLength == 9 then + gate.setChevronConfiguration({ 1, 2, 3, 4, 5, 6, 7, 8 }) + end + end + + local start = gate.getChevronsEngaged() + 1 + + for chevron = start, #address, 1 do + local symbol = address[chevron] + + gate.engageSymbol(symbol) + sleep(config.gatespeed) + + display.showDialing(chevron, symbol, gateType) + + if (symbol) ~= 0 then + if (gateType == "sgjourney:universe_stargate") or (gateType == "sgjourney:pegasus_stargate") then + os.pullEvent("stargate_chevron_engaged") + end + else + if gateType == "sgjourney:universe_stargate" then + os.pullEvent("stargate_chevron_engaged") + redstone.setOutput("top", true) + elseif (gateType == "sgjourney:pegasus_stargate") then + os.pullEvent("stargate_chevron_engaged") + end + end + end + end +end + +local function selectGateFromList() + local selecting = true + while dialing == false and selecting == true do + selx, sely = GetClick() + local buttonXY, computerAddresses, computerNames = display.getButtonData() + + for i = 1, #buttonXY do + if (sely == buttonXY[i][3]) and ((selx >= buttonXY[i][1]) and (selx <= buttonXY[i][2])) then + dialGate(computerAddresses[i]) + destAddressname = computerNames[i] + destAddress = computerAddresses[i] + dialing = true + sely = 0 + selx = 0 + elseif sely >= 17 and selx >= 23 then + selecting = false + sely = 0 + selx = 0 + end + end + end + + display.clearButtonData() + return dialing +end + +local function selectCategory() + local state = true + + while state == true do + display.selectionTabs() + + local tabx, taby = GetClick() + y = 2 + local count = 0 + + if (taby >= 2) and (taby <= 6) and ((tabx >= 2) and (tabx <= 13)) then + if #addresses.MainGates ~= 0 then + mon.setBackgroundColor(colors.black) + mon.clear() + mon.setBackgroundColor(colors.purple) + count, y = display.screenWrite(addresses.MainGates, count, y) + + local returnstate = selectGateFromList() + if returnstate == true then + state = false + end + + display.clearButtonData() + else + mon.setCursorPos(9, 7) + mon.write("no gates available") + sleep(5) + end + + elseif (taby >= 2) and (taby <= 6) and ((tabx >= 16) and (tabx <= 27)) then + if #addresses.playerGates ~= 0 then + mon.setBackgroundColor(colors.black) + mon.clear() + mon.setBackgroundColor(colors.green) + count, y = display.screenWrite(addresses.playerGates, count, y) + + local returnstate = selectGateFromList() + if returnstate == true then + state = false + end + + display.clearButtonData() + else + mon.setCursorPos(9, 7) + mon.write("no gates available") + sleep(5) + end + + elseif (((taby >= 8) and (taby <= 12)) and ((tabx >= 2) and (tabx <= 13))) and (config.canAccessHazardGates == true) then + if (#addresses.hazardGates ~= 0) and (config.canAccessHazardGates == true) then + mon.setBackgroundColor(colors.black) + mon.clear() + mon.setBackgroundColor(colors.red) + count, y = display.screenWrite(addresses.hazardGates, count, y) + + local returnstate = selectGateFromList() + if returnstate == true then + state = false + end + + display.clearButtonData() + else + mon.setCursorPos(9, 7) + mon.write("no gates available") + sleep(5) + end + + elseif (taby >= 17) and (tabx >= 23) then + state = false + totalstate = false + end + end + return 1 +end + +local function handleOutgoingDial() + totalstate = true + local PDO = 0 + + -- parallel.waitForAny runs functions simultaneously and returns which finished first + -- Here we're waiting for either: + -- 1 = selectCategory (user selected a gate to dial) + -- 2 = Paratimeout (5 minutes passed with no selection) + -- This prevents the gate from staying on the selection screen forever + PDO = parallel.waitForAny(selectCategory, Paratimeout) + + if (PDO == 1) and totalstate == true then + sleep(1) + + os.pullEvent("stargate_outgoing_wormhole") + + display.showConnected(destAddressname, destAddress) + + if (gate.isStargateConnected() == true) then + -- parallel.waitForAny runs functions at the same time, returns which finished first + -- While the wormhole is open, we wait for: + -- DisconnectCheck = gate disconnects naturally (timeout or remote disconnect) + -- ParaDisconnect = user manually clicks screen to disconnect + -- Paratimeout = safety timeout (5 minutes) + -- Whichever happens first ends the connection + PDO = parallel.waitForAny(DisconnectCheck, ParaDisconnect, Paratimeout) + dialing = false + end + end + + display.clearButtonData() + destAddress = {} + destAddressname = "" +end + +--------------------------------------------- +-- MAIN MENU +--------------------------------------------- + +local function mainMenu() + while true do + display.showMainMenu() + + -- parallel.waitForAny runs multiple functions simultaneously, returns which completes first + -- The main menu waits for any of these events: + -- 1 = GetClick (user touched the monitor anywhere - assumes they want to dial out) + -- 2 = GetActivation (incoming wormhole detected with address) + -- Note: We don't wait for CheveronActivation here because it fires at the same time + -- as the incoming wormhole event, causing a race condition where we might miss the address + local answer = parallel.waitForAny(GetClick, GetActivation) + + if (answer == 1) then + handleOutgoingDial() + else + handleIncomingWormhole() + end + end +end + +--------------------------------------------- +-- STARTUP +--------------------------------------------- + +utils.log("=== Stargate Control System Starting ===") +utils.log("Gate Type: " .. gate.getStargateType()) +utils.log("Iris Available: " .. tostring(config.irisEnabled)) + +-- Start the main menu +mainMenu() diff --git a/startup_old.lua b/startup_old.lua new file mode 100644 index 0000000..d9758f5 --- /dev/null +++ b/startup_old.lua @@ -0,0 +1,826 @@ +-- Gate dialing speed (seconds between chevrons for non-Milky Way gates) +local gatespeed = 0.5 +local manualDial = true +local canAccessHazardGates = true + +local irisEnabled = true +local autoCloseIrisOnIncoming = true +local irisCloseDelay = 0.1 -- seconds before closing iris on incoming +local autoOpenIrisAfterDisconnect = true + +local whitelist = { + { "Glaive", { 33, 6, 10, 24, 1, 30, 3, 17, 0 } }, + { "Moon", { 32, 33, 8, 7, 25, 21, 14, 35, 0 } }, + { "Caldoric", { 18, 2, 24, 16, 8, 19, 4, 29, 0 } }, + { "Trading Hall", { 16, 19, 6, 18, 35, 27, 9, 8, 0 } } +} + +local blacklist = { + -- {"DangerGate", {1,2,3,4,5,6,0}}, +} + +local enableLogging = true +local logFile = "stargate.log" + +--------------------------------------------- +-- ADDRESS BOOK +--------------------------------------------- + +-- Main Gates (7-chevron addresses for common destinations) +local MainGates = { + { "OVERWORLD", { 27, 25, 4, 35, 10, 28, 0 } }, + { "Nether", { 27, 23, 4, 34, 12, 28, 0 } }, + { "End", { 13, 24, 2, 19, 3, 30, 0 } }, + { "Abydos", { 26, 6, 14, 31, 11, 29, 0 } }, + { "Chulak", { 8, 1, 22, 14, 36, 19, 0 } } +} + +-- Player Gates (typically 9-chevron addresses) +local playerGates = { + { "Glaive", { 33, 6, 10, 24, 1, 30, 3, 17, 0 } }, + { "Moon", { 32, 33, 8, 7, 25, 21, 14, 35, 0 } }, + { "Caldoric", { 18, 2, 24, 16, 8, 19, 4, 29, 0 } }, + { "Trading Hall", { 16, 19, 6, 18, 35, 27, 9, 8, 0 } } +} + +-- Hazard Gates (dangerous destinations) +local hazardGates = { + -- Add hazardous gate addresses here +} + +--------------------------------------------- +-- INITIALIZATION +--------------------------------------------- + +-- Find and initialize peripherals +local mon = peripheral.find("monitor") +local gate = peripheral.find("advanced_crystal_interface") + +if gate == nil then + error("Stargate interface not found! Please connect an interface.") +end + +if mon == nil then + error("Monitor not found! Please connect a monitor.") +end + +-- Ensure gate starts disconnected +gate.disconnectStargate() + +-- Ensure iris starts open (unless there's a threat) +if irisEnabled then + gate.openIris() +end + +--------------------------------------------- +-- GLOBAL VARIABLES +--------------------------------------------- + +-- Outgoing connection state +local destAddress = {} +local buttonXY = {} +local computerAddresses = {} +local computerNames = {} +local destAddressname = "" +local x, y = 0, 0 +local selx, sely = 0, 0 +local dialing = false +local totalstate = nil + +-- Incoming connection state +local incomingAddress = {} +local incomingEntityType = "" +local incomingEntityName = "" + +-- Menu state +local disconnect = false + +--------------------------------------------- +-- UTILITY FUNCTIONS +--------------------------------------------- + +local function deepcopy(orig) + local orig_type = type(orig) + local copy + if orig_type == 'table' then + copy = {} + for orig_key, orig_value in next, orig, nil do + copy[deepcopy(orig_key)] = deepcopy(orig_value) + end + setmetatable(copy, deepcopy(getmetatable(orig))) + else -- number, string, boolean, etc + copy = orig + end + return copy +end + + +local function log(message) + if enableLogging then + local timestamp = os.date("%Y-%m-%d %H:%M:%S") + local logEntry = "[" .. timestamp .. "]\n" .. message + -- local logEntry = message + + -- Write to console + print(logEntry) + + -- Write to file + local file = fs.open(logFile, "a") + if file then + file.writeLine(logEntry) + file.close() + end + end +end + +local function addressToTable(address) + if type(address) == "table" then + return address + end + return {} +end + +local function compareAddresses(addr1, addr2) + addr1 = gate.addressToString(addr1) + addr2 = gate.addressToString(addr2) + return addr1 == addr2 +end + +local function isAddressInList(address, list) + for _, entry in ipairs(list) do + local listAddr = entry[2] + tmp = deepcopy(listAddr) + table.remove(tmp) + if compareAddresses(address, tmp) then + return true, entry[1] + end + end + return false, nil +end + +local function isAddressAllowed(address) + -- Check blacklist first + local isBlacklisted, blackName = isAddressInList(address, blacklist) + if isBlacklisted then + return false, "Blacklisted: " .. blackName + end + + -- If whitelist is not empty, check whitelist + if #whitelist > 0 then + local isWhitelisted, whiteName = isAddressInList(address, whitelist) + if isWhitelisted then + return true, "Whitelisted: " .. whiteName + else + return false, "Not on whitelist" + end + end + + -- If no whitelist, allow all non-blacklisted + return true, "Allowed" +end + +--------------------------------------------- +-- IRIS CONTROL FUNCTIONS +--------------------------------------------- + +local function closeIris() + if irisEnabled then + gate.closeIris() + log("Iris closed") + return true + end + return false +end + +local function openIris() + if irisEnabled then + gate.openIris() + log("Iris opened") + return true + end + return false +end + +local function getIrisState() + if irisEnabled then + if gate.getIrisProgressPercentage() == 0 then + return "OPEN" + else + if gate.getIrisProgressPercentage() == 100 then + return "CLOSED" + else + return "MOVING" + end + end + end + return "NO IRIS" +end + +--------------------------------------------- +-- DISPLAY FUNCTIONS +--------------------------------------------- + +local function screenWrite(list, fcount, fy) + local internaladdress = {} + + for i = 1, #list do + local x1, x2 = 0, 0 + + if fcount == 0 then + x = 2 + fcount = 1 + elseif fcount == 1 then + x = 11 + fcount = 2 + else + x = 20 + fcount = 0 + fy = fy + 2 + end + + mon.setCursorPos(x, fy) + mon.write(list[i][1]) + + x1 = x + x2 = x + 7 + + table.insert(buttonXY, { x1, x2, fy }) + table.insert(computerNames, list[i][1]) + + -- list[i][2] is now the address table directly + table.insert(computerAddresses, list[i][2]) + internaladdress = {} + end + + local oldterm = term.redirect(mon) + paintutils.drawFilledBox(23, 17, 28, 19, colors.red) + mon.setCursorPos(24, 18) + mon.write("Back") + term.redirect(oldterm) + + return fcount, fy +end + +local function drawIrisStatus() + mon.setCursorPos(1, 19) + mon.setBackgroundColor(colors.black) + local irisState = getIrisState() + if irisState == "OPEN" then + mon.setTextColor(colors.green) + elseif irisState == "CLOSED" then + mon.setTextColor(colors.red) + else + mon.setTextColor(colors.gray) + end + mon.write("IRIS: " .. irisState .. " ") + mon.setTextColor(colors.white) + if irisState == "MOVING" then + sleep(0.1) + drawIrisStatus() + end +end + +local function SelectDial() + local x1, x2 = 0, 0 + local count = 0 + y = 2 + + mon.setBackgroundColor(colors.black) + mon.clear() + + if #MainGates ~= 0 then + mon.setBackgroundColor(colors.purple) + count, y = screenWrite(MainGates, count, y) + end + + if #playerGates ~= 0 then + mon.setBackgroundColor(colors.green) + count, y = screenWrite(playerGates, count, y) + end + + if (#hazardGates ~= 0) and (canAccessHazardGates == true) then + mon.setBackgroundColor(colors.red) + count, y = screenWrite(hazardGates, count, y) + end + + drawIrisStatus() + return 1 +end + +local function selectionTabs() + mon.setBackgroundColor(colors.black) + mon.clear() + local oldterm = term.redirect(mon) + + if #MainGates ~= 0 then + paintutils.drawFilledBox(2, 2, 13, 6, colors.purple) + mon.setCursorPos(4, 4) + mon.setBackgroundColor(colors.purple) + mon.write("Main Gates") + end + + if #playerGates ~= 0 then + paintutils.drawFilledBox(16, 2, 27, 6, colors.green) + mon.setCursorPos(18, 4) + mon.setBackgroundColor(colors.green) + mon.write("Player") + mon.setCursorPos(18, 5) + mon.write("Base gates") + end + + if (#hazardGates ~= 0) and (canAccessHazardGates == true) then + paintutils.drawFilledBox(2, 8, 13, 12, colors.red) + mon.setCursorPos(4, 9) + mon.setBackgroundColor(colors.red) + mon.write("Hazard") + mon.setCursorPos(4, 11) + mon.write("gates") + end + + paintutils.drawFilledBox(23, 17, 28, 19, colors.red) + mon.setCursorPos(24, 18) + mon.write("Back") + + drawIrisStatus() + term.redirect(oldterm) +end + +--------------------------------------------- +-- EVENT HANDLING FUNCTIONS +--------------------------------------------- + +local function GetClick() + mon.setTextScale(1) + local event, _, xPos, yPos = os.pullEvent("monitor_touch") + return xPos, yPos +end + +local function GetActivation() + _, _, incomingAddress = os.pullEvent("stargate_incoming_wormhole") + return 1 +end + +local function CheveronActivation() + local event, _, _, _, incomingBool = os.pullEvent("stargate_chevron_engaged") + if incomingBool == true then + log("External dial detected") + else + log("Internal dial detected") + end + return 1 +end + +local function ParaDisconnect() + local dx, dy = 0, 0 + _, _, dx, dy = os.pullEvent("monitor_touch") + + if (dx ~= 0) and (dy ~= 0) then + gate.disconnectStargate() + redstone.setOutput("top", false) + log("Manual disconnect triggered") + end + return 1 +end + +local function EntityRead() + sleep(0.1) + _, _, incomingEntityType, incomingEntityName, _ = os.pullEvent("stargate_reconstructing_entity") + log("Entity reconstructed: " .. incomingEntityName .. " (" .. incomingEntityType .. ")") + return 1 +end + +local function DisconnectCheck() + local _, _, disCode = os.pullEvent("stargate_disconnected") + redstone.setOutput("top", false) + log("Stargate disconnected (code: " .. tostring(disCode) .. ")") + + -- Auto-open iris after disconnect + if autoOpenIrisAfterDisconnect then + openIris() + end + + return 2 +end + +--------------------------------------------- +-- INCOMING WORMHOLE HANDLER +--------------------------------------------- + +local function PRIMARYwormholeIncoming() + mon.setBackgroundColor(colors.black) + mon.clear() + mon.setBackgroundColor(colors.red) + mon.setTextScale(1) + mon.setCursorPos(9, 4) + mon.write("INCOMING") + + -- Convert address to table if needed + incomingAddress = addressToTable(incomingAddress) + + -- Check security + local allowed, reason = isAddressAllowed(incomingAddress) + + -- Try to get address string + local addressString = gate.addressToString(incomingAddress) or "Unknown" + + log("Incoming wormhole from: " .. addressString .. " " .. reason) + + mon.setBackgroundColor(colors.black) + mon.clear() + + if allowed then + mon.setBackgroundColor(colors.green) + mon.setTextScale(1) + mon.setCursorPos(1, 2) + mon.write("INCOMING - AUTHORIZED") + + -- Open iris for authorized connections + if autoCloseIrisOnIncoming then + sleep(irisCloseDelay) + openIris() + end + else + mon.setBackgroundColor(colors.red) + mon.setTextScale(1) + mon.setCursorPos(1, 2) + mon.write("INCOMING - UNAUTHORIZED") + mon.setCursorPos(1, 4) + mon.write(reason) + + -- Close iris for unauthorized connections + if autoCloseIrisOnIncoming then + sleep(irisCloseDelay) + closeIris() + end + end + + mon.setCursorPos(1, 6) + mon.setBackgroundColor(colors.black) + mon.write("Address:") + mon.setCursorPos(1, 7) + mon.write(addressString) + + -- Monitor for incoming entities + disconnect = false + while (disconnect == false) do + -- parallel.waitForAny runs multiple functions at the same time and returns which one finished first + -- Here we're watching for 3 things simultaneously: + -- 1 = EntityRead (someone/something came through the gate) + -- 2 = DisconnectCheck (gate disconnected on its own) + -- ParaDisconnect (user clicked screen to manually disconnect) + -- Whichever happens first, that function returns and we handle it + local incomingcheck = parallel.waitForAny(EntityRead, DisconnectCheck, ParaDisconnect) + if (incomingcheck == 1) then + mon.setTextScale(1) + mon.setCursorPos(1, 10) + mon.write("Type: " .. incomingEntityType) + mon.setCursorPos(1, 12) + mon.write("Name: " .. incomingEntityName) + + -- Check if entity should be blocked + if not allowed then + mon.setCursorPos(1, 14) + mon.setTextColor(colors.red) + mon.write("IRIS IMPACT!") + mon.setTextColor(colors.white) + end + + incomingEntityType = "" + incomingEntityName = "" + else + disconnect = true + end + end + disconnect = false +end + +--------------------------------------------- +-- DIALING FUNCTIONS +--------------------------------------------- + +local function Dial(address) + log("Dialing: " .. gate.addressToString(address)) + + local gateType = gate.getStargateType() + + -- Manual Milky Way dialing with ring rotation + if gateType == "sgjourney:milky_way_stargate" and manualDial == true then + mon.setBackgroundColor(colors.black) + mon.clear() + mon.setBackgroundColor(colors.red) + mon.setTextScale(2) + mon.setCursorPos(2, 3) + mon.write("DIALING GATE") + + local addressLength = #address + + if addressLength == 8 then + gate.setChevronConfiguration({ 1, 2, 3, 4, 6, 7, 8, 5 }) + elseif addressLength == 9 then + gate.setChevronConfiguration({ 1, 2, 3, 4, 5, 6, 7, 8 }) + end + + local start = gate.getChevronsEngaged() + 1 + + for chevron = start, addressLength, 1 do + local symbol = address[chevron] + + if chevron % 2 == 0 then + gate.rotateClockwise(symbol) + else + gate.rotateAntiClockwise(symbol) + end + + while (not gate.isCurrentSymbol(symbol)) do + sleep(0) + end + + gate.openChevron() + sleep(1) + gate.closeChevron() + sleep(0.5) + + mon.setBackgroundColor(colors.black) + mon.clear() + mon.setBackgroundColor(colors.red) + mon.setTextScale(2) + mon.setCursorPos(2, 3) + mon.write("DIALING GATE") + mon.setCursorPos(4, 5) + mon.write("CHEVERON") + mon.setCursorPos(7, 7) + mon.setBackgroundColor(colors.black) + mon.write(chevron) + mon.setBackgroundColor(colors.red) + + if symbol ~= 0 then + mon.setCursorPos(4, 9) + mon.write("ENGAGED") + else + mon.setCursorPos(5, 9) + mon.setBackgroundColor(colors.green) + mon.write("LOCKED") + end + end + else + -- Automatic dialing for other gate types + mon.setBackgroundColor(colors.black) + mon.clear() + mon.setBackgroundColor(colors.red) + mon.setTextScale(2) + mon.setCursorPos(2, 3) + mon.write("DIALING GATE") + + if gateType ~= "sgjourney:universe_stargate" then + local addressLength = #address + if addressLength == 8 then + gate.setChevronConfiguration({ 1, 2, 3, 4, 6, 7, 8, 5 }) + elseif addressLength == 9 then + gate.setChevronConfiguration({ 1, 2, 3, 4, 5, 6, 7, 8 }) + end + end + + local start = gate.getChevronsEngaged() + 1 + + for chevron = start, #address, 1 do + local symbol = address[chevron] + + gate.engageSymbol(symbol) + sleep(gatespeed) + + mon.setBackgroundColor(colors.black) + mon.clear() + mon.setBackgroundColor(colors.red) + mon.setTextScale(2) + mon.setCursorPos(2, 3) + mon.write("DIALING GATE") + mon.setCursorPos(4, 5) + mon.write("CHEVERON") + mon.setCursorPos(7, 7) + mon.setBackgroundColor(colors.black) + mon.write(chevron) + mon.setBackgroundColor(colors.red) + + if (symbol) ~= 0 then + mon.setCursorPos(4, 9) + mon.write("ENGAGED") + + if (gateType == "sgjourney:universe_stargate") or (gateType == "sgjourney:pegasus_stargate") then + os.pullEvent("stargate_chevron_engaged") + end + else + if gateType == "sgjourney:universe_stargate" then + os.pullEvent("stargate_chevron_engaged") + redstone.setOutput("top", true) + elseif (gateType == "sgjourney:pegasus_stargate") then + os.pullEvent("stargate_chevron_engaged") + end + + mon.setCursorPos(5, 9) + mon.setBackgroundColor(colors.green) + mon.write("LOCKED") + end + end + + address = nil + end +end + +local function ParaDial() + local selecting = true + while dialing == false and selecting == true do + selx, sely = GetClick() + for i = 1, #buttonXY do + if (sely == buttonXY[i][3]) and ((selx >= buttonXY[i][1]) and (selx <= buttonXY[i][2])) then + Dial(computerAddresses[i]) + destAddressname = computerNames[i] + destAddress = computerAddresses[i] + dialing = true + sely = 0 + selx = 0 + elseif sely >= 17 and selx >= 23 then + selecting = false + sely = 0 + selx = 0 + end + end + end + + buttonXY = {} + computerAddresses = {} + computerNames = {} + + return dialing +end + +local function tabSelector() + local state = true + + while state == true do + mon.setBackgroundColor(colors.black) + mon.clear() + + selectionTabs() + + local tabx, taby = GetClick() + + y = 2 + local count = 0 + + if (taby >= 2) and (taby <= 6) and ((tabx >= 2) and (tabx <= 13)) then + if #MainGates ~= 0 then + mon.setBackgroundColor(colors.black) + mon.clear() + + mon.setBackgroundColor(colors.purple) + count, y = screenWrite(MainGates, count, y) + + local returnstate = ParaDial() + + if returnstate == true then + state = false + end + + computerAddresses = {} + computerNames = {} + else + mon.setCursorPos(9, 7) + mon.write("no gates available") + sleep(5) + end + elseif (taby >= 2) and (taby <= 6) and ((tabx >= 16) and (tabx <= 27)) then + if #playerGates ~= 0 then + mon.setBackgroundColor(colors.black) + mon.clear() + + mon.setBackgroundColor(colors.green) + count, y = screenWrite(playerGates, count, y) + + local returnstate = ParaDial() + + if returnstate == true then + state = false + end + + computerAddresses = {} + computerNames = {} + else + mon.setCursorPos(9, 7) + mon.write("no gates available") + sleep(5) + end + elseif (((taby >= 8) and (taby <= 12)) and ((tabx >= 2) and (tabx <= 13))) and (canAccessHazardGates == true) then + if (#hazardGates ~= 0) and (canAccessHazardGates == true) then + mon.setBackgroundColor(colors.black) + mon.clear() + + mon.setBackgroundColor(colors.red) + count, y = screenWrite(hazardGates, count, y) + + local returnstate = ParaDial() + + if returnstate == true then + state = false + end + + computerAddresses = {} + computerNames = {} + else + mon.setCursorPos(9, 7) + mon.write("no gates available") + sleep(5) + end + elseif (taby >= 17) and (tabx >= 23) then + state = false + totalstate = false + end + end + return 1 +end + +local function Paratimeout() + sleep(300) + return 2 +end + +local function DialText() + mon.setBackgroundColor(colors.green) + mon.clear() + mon.setTextScale(1) + mon.setCursorPos(6, 5) + mon.write(destAddressname) + + mon.setCursorPos(3, 10) + for i = 1, #destAddress do + mon.write(destAddress[i]) + mon.write(" ") + end + + drawIrisStatus() + + destAddress = {} + destAddressname = "" +end + +local function PRIMARYDialingOut() + totalstate = true + local PDO = 0 + PDO = parallel.waitForAny(tabSelector, Paratimeout) + + if (PDO == 1) and totalstate == true then + sleep(1) + + os.pullEvent("stargate_outgoing_wormhole") + + DialText() + + if (gate.isStargateConnected() == true) then + PDO = parallel.waitForAny(DisconnectCheck, ParaDisconnect, Paratimeout) + dialing = false + end + end + + computerAddresses = {} + computerNames = {} +end + +--------------------------------------------- +-- MAIN MENU +--------------------------------------------- + +function Menu() + while true do + mon.setTextScale(1) + mon.setBackgroundColor(colors.black) + mon.clear() + mon.setCursorPos(9, 1) + mon.setBackgroundColor(colors.red) + mon.write("press to start") + + drawIrisStatus() + local answer = parallel.waitForAny(GetClick, GetActivation) + + if (answer == 1) then + PRIMARYDialingOut() + else + PRIMARYwormholeIncoming() + end + end +end + +--------------------------------------------- +-- STARTUP +--------------------------------------------- + +if gate.getIris() == nil then + log("Config has Iris enabled, but there is no iris! Disabling Iris") + irisEnabled = false +end + +-- log("=== Stargate Control System Starting ===") +-- log("Gate Type: " .. gate.getStargateType()) +-- log("Iris Available: " .. tostring(irisEnabled)) +-- log("Configuration: Manual Dial=" .. tostring(manualDial) .. ", Auto Iris=" .. tostring(autoCloseIrisOnIncoming)) + +-- Start the main menu +Menu() diff --git a/test.lua b/test.lua deleted file mode 100644 index edf1019..0000000 --- a/test.lua +++ /dev/null @@ -1,13 +0,0 @@ -shell.run("clear") -local interface = peripheral.find("basic_interface") or peripheral.find("crystal_interface") or peripheral.find("advanced_crystal_interface") -interface.openIris() -while true do -local _, _, address = os.pullEvent("stargate_incoming_wormhole") -interface.closeIris() -print("Incoming connection from " .. interface.addressToString(address)) -local _, _, _, name, _ = os.pullEvent(stargate_reconstructing_entity) -print(name .. " got squished") -interface.disconnectStargate() -interface.openIris() -end - \ No newline at end of file diff --git a/utils.lua b/utils.lua new file mode 100644 index 0000000..dbf8ecc --- /dev/null +++ b/utils.lua @@ -0,0 +1,139 @@ +--[[ + Utility Functions + Helper functions for logging, address handling, and iris control +]] + +local utils = {} + +-- Import config (set by startup.lua) +local config +local gate + +function utils.init(cfg, gateInterface) + config = cfg + gate = gateInterface +end + +--------------------------------------------- +-- LOGGING +--------------------------------------------- + +function utils.log(message) + if config.enableLogging then + local timestamp = os.date("%Y-%m-%d %H:%M:%S") + local logEntry = "[" .. timestamp .. "] " .. message + + -- Write to console + print(logEntry) + + -- Write to file + local file = fs.open(config.logFile, "a") + if file then + file.writeLine(logEntry) + file.close() + end + end +end + +--------------------------------------------- +-- ADDRESS UTILITIES +--------------------------------------------- + +function utils.addressToTable(address) + if type(address) == "table" then + return address + end + return {} +end + +function utils.compareAddresses(addr1, addr2) + addr1 = gate.addressToString(addr1) + addr2 = gate.addressToString(addr2) + return addr1 == addr2 +end + +function utils.deepcopy(orig) + local orig_type = type(orig) + local copy + if orig_type == 'table' then + copy = {} + for orig_key, orig_value in next, orig, nil do + copy[utils.deepcopy(orig_key)] = utils.deepcopy(orig_value) + end + setmetatable(copy, utils.deepcopy(getmetatable(orig))) + else + copy = orig + end + return copy +end + +function utils.isAddressInList(address, list) + for _, entry in ipairs(list) do + local listAddr = entry[2] + local tmp = utils.deepcopy(listAddr) + table.remove(tmp) -- Remove point of origin + if utils.compareAddresses(address, tmp) then + return true, entry[1] + end + end + return false, nil +end + +function utils.isAddressAllowed(address) + -- Check blacklist first + local isBlacklisted, blackName = utils.isAddressInList(address, config.blacklist) + if isBlacklisted then + return false, "Blacklisted: " .. blackName + end + + -- If whitelist is not empty, check whitelist + if #config.whitelist > 0 then + local isWhitelisted, whiteName = utils.isAddressInList(address, config.whitelist) + if isWhitelisted then + return true, "Whitelisted: " .. whiteName + else + return false, "Not on whitelist" + end + end + + -- If no whitelist, allow all non-blacklisted + return true, "Allowed" +end + +--------------------------------------------- +-- IRIS CONTROL +--------------------------------------------- + +function utils.closeIris() + if config.irisEnabled then + gate.closeIris() + utils.log("Iris closed") + return true + end + return false +end + +function utils.openIris() + if config.irisEnabled then + gate.openIris() + utils.log("Iris opened") + return true + end + return false +end + +function utils.getIrisState() + if config.irisEnabled then + local progress = gate.getIrisProgressPercentage() + if progress == 0 then + return "OPEN" + elseif progress == 100 then + return "CLOSED" + else + return "MOVING" + end + end + return "NO IRIS" +end + +return utils