-- 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()