Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --[[
- Mekanism Monitor for ComputerCraft (v86.49-Fixes)
- Author: DarknessARises1
- This version corrects several visual glitches in the UI for a cleaner and more consistent look.
- Features:
- - Multi-Monitor Support: The script now detects and uses all connected monitors, with each monitor having independent page controls.
- - Asymmetric Display: Terminal shows a single large panel, while monitors can show a 4-panel grid.
- - Synchronized Config Menu: The configuration menu is mirrored on all screens for setup.
- - All device types are fully supported with detailed drawing functions.
- - Robust, non-compacted code for better stability and readability.
- FIXES (v86.45 - UI & Event Loop):
- - Fixed issue with Config and Go button overlapping, causing config to launch on go press. Also noticed when go button was working the go menu would disappear and act like it was still there. Also fixed
- - Fixed unresponsive keypad input by prioritizing its events in the main event loop.
- - Fixed a crash in the configuration menu when the "Industrial Alarm" was disabled.
- - Corrected a visual glitch where the "Power Down Button" label had the wrong background color.
- - Fixed a crash when opening the keypad while a Fission Reactor was on screen.
- - Fixed a Bug that caused program to freeze when dragging fusion reactor control slider on the terminal, removed it to avoid waiting silent crashes.
- - Fixed redstone firing requiring two clicks of ignite button to send signal
- FIXES (v86.44 - SCRAM Control & Heated Coolant):
- - Added a progress bar for Heated Coolant to the Fission Reactor panel.
- - SCRAM state now automatically resets when coolant levels reach 50% or more.
- - Added a "[Cancel SCRAM]" button that appears on the Fission Reactor panel during a SCRAM event, allowing for manual override.
- - Fixed the "END" and "Quit" buttons to properly terminate the program from any screen.
- - Auto-SCRAM will no longer trigger on an idle reactor, preventing SCRAM loops after a manual cancel.
- Setup:
- 1. Place ComputerCraft computer adjacent to Mekanism devices.
- 2. Place one or more Monitor peripherals next to the computer.
- 3. (Optional) Place one or more Speaker peripherals next to the computer for audible alerts.
- 4. Run the script. If it's your first time, the config menu will appear.
- 5. To stop: Use the on-screen "END" button or press Ctrl+T.
- --]]
- -- =================================================================
- -- Configuration
- -- =================================================================
- -- if you want to increase how fast it reads and updates the display change the updateInterval and displayRefreshRate to smaller numbers
- local configFile = "mek_monitor.cfg"
- local debugMode = false
- local updateInterval = 0.5
- local joulesToFeConversionFactor = 2.5
- local TICKS_PER_SECOND = 20
- local devicesPerPageMonitor = 4
- local devicesPerPageTerminal = 1 -- Asymmetric display setting
- local turbineNamePrefix = "turbineValve"
- local fusionReactorNamePrefix = "fusionReactorLogicAdapter"
- local inductionMatrixNamePrefix = "inductionPort"
- local boilerNamePrefix = "boilerValve"
- local thermalEvaporationNamePrefix = "thermalEvaporationValve"
- local fissionReactorNamePrefix = "fissionReactorLogicAdapter"
- local displayRefreshDelay = 0.05
- local globalButtonFeedbackDuration = 2
- local minInjectionRate = 2
- local maxInjectionRate = 98
- local injectionRateStep = 2
- local matrixRateThreshold = 0.001
- local scanForNewDevicesInterval = 5
- -- Fission Reactor Safety
- local fissionCriticalTemp = 1800 -- Temp (K) to trigger automatic SCRAM. Meltdown is at 2000K.
- local fissionCriticalDamage = 90 -- Damage % to trigger automatic SCRAM.
- local fissionLowCoolantPercent = 0.10
- local fissionSafeCoolantPercent = 0.50 -- Coolant % to auto-reset SCRAM.
- local fissionScramResetTempK = 300
- local dumpingModes = {
- { apiName = "IDLE", displayName = "Idle" },
- { apiName = "DUMPING_EXCESS", displayName = "Excess" },
- { apiName = "DUMPING", displayName = "Dump" },
- }
- local skipIsFormedValidation = false
- local dynamicColorThresholds = {
- caseTemp = {
- noDt = { yellow = 60e6, orange = 1.875e9, red = 2.25e9 },
- dtFuel = { yellow = 60e6, orange = 1.0875e10, red = 1.305e10 }
- },
- plasmaTemp = {
- noDt = { yellow = 160e6, orange = 5.665e9, red = 6.78e9 },
- dtFuel = { yellow = 160e6, orange = 2.9665e10, red = 3.558e10 }
- },
- energy = {
- noDt = { yellow = 7.8e6, orange = 11.7e6, red = 14.0e6 },
- dtFuel = { yellow = 39.8e6, orange = 59.7e6, red = 71.6e6 }
- },
- boilerTemp = { yellow = 400, orange = 600, red = 1000 },
- thermalTemp = { yellow = 320, orange = 350, red = 400 },
- fissionTemp = { yellow = 1000, orange = 1500, red = 1800 }
- }
- -- =================================================================
- -- Global State and Initialization
- -- =================================================================
- local monitors = {}
- -- State management for asymmetric display
- local sharedData = {
- programRunning = true,
- isGoToMenuOpen = false,
- monitorRunning = false,
- isMenuOpen = false,
- deviceStats = {},
- lastError = {},
- currentPageTerm = 1,
- totalPagesTerm = 1,
- monitorStates = {},
- totalPagesMon = 1,
- pageButtonRegions = {},
- quadrantRegions = {},
- fissionScrollOffsets = {},
- actionQueue = {},
- scrammingReactors = {},
- speakers = {},
- flashToggle = true,
- sirenBuffers = {},
- overallTurbineStats = { energyStored = 0, maxEnergy = 0, energyFilledPercentage = 0, productionRate = 0, maxProduction = 0, steamAmount = 0, steamCapacity = 0, steamFilledPercentage = 0, formedCount = 0, totalTurbineCount = 0 },
- globalModeButtonFeedback = { modeAPIName = nil, colorKey = nil, expiryTime = 0 },
- globallyConsistentTurbineMode = nil,
- foundDevices = {},
- config = {
- tempUnit = "K",
- deviceOrder = { "sps", "fusionReactor", "fissionReactor", "inductionMatrix", "dynamicTank", "thermalEvaporation", "boiler", "turbine", "turbineSummary" },
- useColoredBorders = true,
- useMoreRed = false,
- moreRedOutputSide = "back",
- spsRedstoneControlSide = "back",
- fusionIgnitionSide = "top",
- fusionPowerDownSide = "right",
- industrialAlarmSide = "bottom",
- spsRedstoneColor = "white",
- fusionIgnitionColor = "orange",
- fusionPowerDownColor = "red",
- industrialAlarmColor = "pink",
- enableFusionIgnition = true,
- enableFusionPowerDown = true,
- useIndustrialAlarm = false,
- },
- configMenu = {
- page = 1,
- scrollState = { offset = 0 },
- dragging = nil,
- tempConfig = nil,
- },
- keypad = {
- isOpen = false,
- targetDevice = nil,
- targetWindow = nil,
- inputValue = "",
- message = "",
- qx = 0, qy = 0, qW = 0, qH = 0
- },
- needsRedraw = true
- }
- -- =================================================================
- -- Helper Functions
- -- =================================================================
- local function centeredPrint(text, y)
- local w, _ = term.getSize()
- if not text or not w then return end
- local x = math.floor((w - #text) / 2) + 1
- if y then term.setCursorPos(x, y) else term.setCursorPos(x, term.getCursorPos()) end
- term.write(text)
- end
- local function centeredWrapPrint(text, y)
- local w, _ = term.getSize()
- local maxWidth = w - 4
- local words = {}
- for word in text:gmatch("[^%s]+") do
- table.insert(words, word)
- end
- local lines = {}
- local currentLine = ""
- for _, word in ipairs(words) do
- if #currentLine == 0 then
- currentLine = word
- elseif #currentLine + 1 + #word <= maxWidth then
- currentLine = currentLine .. " " .. word
- else
- table.insert(lines, currentLine)
- currentLine = word
- end
- end
- table.insert(lines, currentLine)
- for i, line in ipairs(lines) do
- centeredPrint(line, y + i - 1)
- end
- end
- local function debugPrint(...)
- if debugMode then
- centeredPrint(table.concat({ ... }, " "))
- end
- end
- local function drawTerminalHeader()
- local w, _ = term.getSize()
- local title = "Mekanism Monitor"
- local currentCursorX, currentCursorY = term.getCursorPos()
- centeredPrint(title, 1)
- term.setTextColor(colors.white)
- term.setCursorPos(currentCursorX, currentCursorY)
- end
- local tempUnits = {
- K = { symbol = "K", convert = function(k) return k end, name = "Kelvin" },
- C = { symbol = "C", convert = function(k) return k - 273.15 end, name = "Celsius" },
- F = { symbol = "F", convert = function(k) return (k - 273.15) * 1.8 + 32 end, name = "Fahrenheit" },
- R = { symbol = "R", convert = function(k) return k * 1.8 end, name = "Rankine" },
- STP = { symbol = "xSTP", convert = function(k) return k / 298.15 end, name = "Mekanism STP" }
- }
- local function convertTemperature(kelvin, unit)
- if not kelvin or not unit or not tempUnits[unit] then
- return "N/A", ""
- end
- local convertedValue = tempUnits[unit].convert(kelvin)
- local symbol = tempUnits[unit].symbol
- return convertedValue, symbol
- end
- if not table.copy then
- function table.copy(original)
- if type(original) ~= 'table' then return original end
- local copy = {}
- for k, v in pairs(original) do
- copy[k] = table.copy(v)
- end
- setmetatable(copy, getmetatable(original))
- return copy
- end
- end
- local function countKeys(t)
- local count = 0
- for _ in pairs(t) do count = count + 1 end
- return count
- end
- local function getSafeColor(colorName, defaultColorValue)
- local default = defaultColorValue or colors.black
- if not colors or type(colors) ~= "table" then return default end
- local colorValue = colors[colorName]
- if type(colorValue) ~= "number" then local fallback = colors.white; if type(fallback) ~= "number" then fallback = default end; return fallback end
- return colorValue
- end
- local function getDynamicColor(value, thresholdSet)
- if value == nil or type(value) ~= "number" or not thresholdSet then return "white" end
- if value >= thresholdSet.red then return "red"
- elseif value >= thresholdSet.orange then return "orange"
- elseif value >= thresholdSet.yellow then return "yellow"
- else return "green"
- end
- end
- local function formatFluidName(fullName)
- if fullName == nil or fullName == "" or fullName:find("empty") then return "Empty" end
- local name = fullName:match(":(.+)") or fullName
- name = name:gsub("_", " ")
- return name:gsub("^%l", string.upper)
- end
- local function getBorderColors(deviceType)
- if not sharedData.config.useColoredBorders then
- return { border = "gray", text = "white" }
- end
- local colorMap = {
- turbine = { border = "gray", text = "orange" },
- fusionReactor = { border = "brown", text = "white" },
- fissionReactor = { border = "green", text = "black" },
- inductionMatrix = { border = "cyan", text = "black" },
- boiler = { border = "orange", text = "black" },
- thermalEvaporation = { border = "lightBlue", text = "black" },
- dynamicTank = { border = "magenta", text = "black" },
- sps = { border = "purple", text = "white" },
- turbineSummary = { border = "yellow", text = "black" }
- }
- return colorMap[deviceType] or { border = "gray", text = "white" }
- end
- local function formatNumber(n,p)if n==nil then return"N/A"end;p=p or 1;if type(n)~="number"then return tostring(n)end;local s={"","k","M","G","T","P","E"};local i=1;local aN=math.abs(n);while aN>=1000 and i<#s do aN=aN/1000;i=i+1 end;local d=1000^(i-1);local sN=(d~=0)and(n/d)or 0;return string.format("%."..p.."f%s",sN,s[i])end
- local function formatPercentage(num) if num==nil then return "N/A" end; if type(num)~="number"then return tostring(num) end; return string.format("%.1f%%",num*100) end
- function formatLargeEnergyFE(n) if n == nil or type(n) ~= "number" then return "N/A" end; if n == 0 then return "0" end; local s={"","K","M","G","T","P","E","Z","Y"}; local i=1; local sg=""; if n<0 then sg="-"; n=-n end; while n>=1000 and i<#s do n=n/1000;i=i+1 end; if i==1 then return sg..string.format("%d",n)..s[i] else if n==math.floor(n) then return sg..string.format("%d",n)..s[i] else return sg..string.format("%.2f",n)..s[i] end end end
- function formatDuration(ts) if ts==nil or type(ts)~="number" or ts<0 then return"N/A" end; if ts==math.huge or ts>(3600*24*365*10)then return"Infinite" end; if ts<0.1 then return"~0s" end; local sm=60;local sh=sm*60;local sd=sh*24; local d=math.floor(ts/sd);local rs=ts%sd; local h=math.floor(rs/sh);rs=rs%sh; local m=math.floor(rs/sm);local sc=math.floor(rs%sm); local str=""; if d>0 then str=str..d.."d " end; if h>0 or(d>0 and(m>0 or sc>0))then str=str..h.."h " end; if m>0 or((d>0 or h>0)and sc>0)then str=str..m.."m " end; if sc>0 or str==""then str=str..sc.."s" end; str=str:gsub("%s$",""); return str==""and"~0s"or str end
- local function pulseRedstone(side)
- if side and (side == "top" or side == "bottom" or side == "left" or side == "right" or side == "back" or side == "front") then
- redstone.setOutput(side, true)
- sleep(0.2)
- redstone.setOutput(side, false)
- end
- end
- local function pulseBundledRedstone(side, colorName)
- local colorValue = colors[colorName]
- if not side or not colorValue then return end
- local originalOutput = redstone.getBundledOutput(side)
- local pulsedOutput = bit.bor(originalOutput, colorValue)
- redstone.setBundledOutput(side, pulsedOutput)
- sleep(0.2)
- redstone.setBundledOutput(side, originalOutput)
- end
- local function setBundledBit(side, colorName, state)
- local colorValue = colors[colorName]
- if not side or not colorValue then return end
- local currentOutput = redstone.getBundledOutput(side)
- local newOutput
- if state then
- newOutput = bit.bor(currentOutput, colorValue)
- else
- newOutput = bit.band(currentOutput, bit.bnot(colorValue))
- end
- redstone.setBundledOutput(side, newOutput)
- end
- -- =================================================================
- -- Layout and Data Management
- -- =================================================================
- local function calculateMonitorPages()
- local allItems = sharedData.drawableItems
- if #allItems == 0 then
- sharedData.pagedItemsMon = {}
- sharedData.totalPagesMon = 1
- return
- end
- local pages = {}
- local placedStatus = {}
- while countKeys(placedStatus) < #allItems do
- local currentPageItems = {}
- local slotsOnPage = {}
- for i = 1, #allItems do
- if not placedStatus[i] then
- local item = allItems[i]
- local isDouble = false
- if item.drawType == "device" then
- if item.info.type == "fissionReactor" then
- isDouble = true
- elseif item.info.type == "fusionReactor" then
- local stats = sharedData.deviceStats[item.info.name]
- if stats and stats.fuel and (stats.waterAmount or 0) > 0 and (stats.steamAmount or 0) > 0 and not stats.fuel.hasDTFuel then
- isDouble = true
- end
- end
- end
- if isDouble then
- if countKeys(slotsOnPage) <= (devicesPerPageMonitor - 2) then
- if not slotsOnPage[1] and not slotsOnPage[3] then
- table.insert(currentPageItems, {item=item, layout={gridX=1, gridY=1, gridW=1, gridH=2}})
- slotsOnPage[1] = true; slotsOnPage[3] = true
- placedStatus[i] = true
- elseif not slotsOnPage[2] and not slotsOnPage[4] then
- table.insert(currentPageItems, {item=item, layout={gridX=2, gridY=1, gridW=1, gridH=2}})
- slotsOnPage[2] = true; slotsOnPage[4] = true
- placedStatus[i] = true
- end
- end
- end
- end
- end
- for i = 1, #allItems do
- if not placedStatus[i] then
- if countKeys(slotsOnPage) < devicesPerPageMonitor then
- for slot_idx=1, devicesPerPageMonitor do
- if not slotsOnPage[slot_idx] then
- local gridX = (slot_idx % 2 == 1) and 1 or 2
- local gridY = (slot_idx <= 2) and 1 or 2
- table.insert(currentPageItems, {item=allItems[i], layout={gridX=gridX, gridY=gridY, gridW=1, gridH=1}})
- slotsOnPage[slot_idx] = true
- placedStatus[i] = true
- break
- end
- end
- else
- break
- end
- end
- end
- table.insert(pages, currentPageItems)
- if countKeys(placedStatus) == #allItems then break end
- end
- sharedData.pagedItemsMon = pages
- sharedData.totalPagesMon = math.max(1, #pages)
- for _, state in pairs(sharedData.monitorStates) do
- if state.currentPage > sharedData.totalPagesMon then
- state.currentPage = sharedData.totalPagesMon
- end
- end
- end
- local function reinitializeLayout()
- local allDrawableItems = {}
- local turbinesFound = 0
- for _, dev in ipairs(sharedData.foundDevices) do
- if dev.type == "turbine" then
- turbinesFound = turbinesFound + 1
- end
- end
- local devicesByType = {}
- for _, dev in ipairs(sharedData.foundDevices) do
- if not devicesByType[dev.type] then devicesByType[dev.type] = {} end
- table.insert(devicesByType[dev.type], dev)
- end
- for _, devices in pairs(devicesByType) do
- table.sort(devices, function(a, b) return a.name < b.name end)
- end
- local typeCounters = { turbine = 0, fusionReactor = 0, fissionReactor = 0, inductionMatrix = 0, boiler = 0, thermalEvaporation = 0, dynamicTank = 0, sps = 0 }
- for _, itemType in ipairs(sharedData.config.deviceOrder) do
- if itemType == "turbineSummary" then
- if turbinesFound > 1 then
- table.insert(allDrawableItems, { drawType = "summary" })
- end
- else
- if devicesByType[itemType] then
- for _, devInfo in ipairs(devicesByType[itemType]) do
- typeCounters[devInfo.type] = typeCounters[devInfo.type] + 1
- devInfo.typeId = typeCounters[devInfo.type]
- table.insert(allDrawableItems, { drawType = "device", info = devInfo })
- end
- end
- end
- end
- sharedData.drawableItems = allDrawableItems
- sharedData.totalPagesTerm = math.max(1, math.ceil(#sharedData.drawableItems / devicesPerPageTerminal))
- if sharedData.currentPageTerm > sharedData.totalPagesTerm then sharedData.currentPageTerm = sharedData.totalPagesTerm end
- local currentFissionReactors = {}
- for _, item in ipairs(allDrawableItems) do
- if item.drawType == "device" and item.info.type == "fissionReactor" then
- currentFissionReactors[item.info.name] = true
- if sharedData.fissionScrollOffsets[item.info.name] == nil then
- sharedData.fissionScrollOffsets[item.info.name] = 0
- end
- end
- end
- for deviceName, _ in pairs(sharedData.fissionScrollOffsets) do
- if not currentFissionReactors[deviceName] then
- sharedData.fissionScrollOffsets[deviceName] = nil
- end
- end
- debugPrint("Layout reinitialized. Items: " .. #sharedData.drawableItems)
- sharedData.needsRedraw = true
- end
- -- =================================================================
- -- Drawing Functions
- -- =================================================================
- local function drawQuadrantBorder(window, x, y, w, h, bBCK, t, tTCK)
- if type(x) ~= "number" or type(y) ~= "number" or type(w) ~= "number" or type(h) ~= "number" or w < 1 or h < 1 then return end
- local bC = getSafeColor(bBCK, colors.gray)
- local tTC = getSafeColor(tTCK or "black", colors.black)
- local oBg, oTxt = window.getBackgroundColor(), window.getTextColor()
- pcall(function()
- window.setBackgroundColor(bC)
- if t and t ~= "" then
- local tL = string.len(t); local sPL = math.floor((w - tL) / 2); sPL = math.max(0, sPL); local rS = w - tL - (sPL * 2); local lP = string.rep(" ", sPL); local rP = string.rep(" ", sPL + rS)
- window.setCursorPos(x, y); window.write(lP); window.setTextColor(tTC); window.write(t); window.setTextColor(oTxt); window.write(rP)
- else
- window.setCursorPos(x, y); window.write(string.rep(" ", w))
- end
- window.setCursorPos(x, y + h - 1); window.write(string.rep(" ", w))
- for i = 1, h - 2 do
- window.setCursorPos(x, y + i); window.write(" ")
- window.setCursorPos(x + w - 1, y + i); window.write(" ")
- end
- end)
- window.setBackgroundColor(oBg); window.setTextColor(oTxt)
- end
- local function drawDetailedProgressBar(window, cV, mV, dW, x, y, fCK, eCK, oT)
- local oBg, oTxt = window.getBackgroundColor(), window.getTextColor()
- local barX = x + 1
- local barW = dW - 2
- if type(cV) ~= "number" or type(mV) ~= "number" or mV == 0 or barW < 1 then
- window.setCursorPos(x,y); window.setBackgroundColor(getSafeColor("black")); window.write(string.rep(" ", dW)); window.setBackgroundColor(oBg); return
- end
- window.setCursorPos(x, y); window.setBackgroundColor(getSafeColor("black")); window.write(" ")
- window.setCursorPos(x + dW - 1, y); window.setBackgroundColor(getSafeColor("black")); window.write(" ")
- local p = cV / mV; p = math.max(0, math.min(1, p)); local fC = math.floor(p * barW)
- for i = 1, barW do
- window.setCursorPos(barX + i - 1, y)
- if i <= fC then window.setBackgroundColor(getSafeColor(fCK)) else window.setBackgroundColor(getSafeColor(eCK, colors.gray)) end
- window.write(" ")
- end
- if oT and oT ~= "" then
- local tL = string.len(oT); local tSX = barX + math.max(0, math.floor((barW - tL) / 2))
- for i = 1, tL do
- local cX = tSX + i - 1
- if cX >= barX and cX < barX + barW then
- window.setCursorPos(cX, y)
- local bSF = (cX - barX + 1) <= fC
- if bSF then window.setBackgroundColor(getSafeColor(fCK)) else window.setBackgroundColor(getSafeColor(eCK, colors.gray)) end
- window.setTextColor(getSafeColor("black")); window.write(string.sub(oT, i, i))
- end
- end
- end
- window.setBackgroundColor(oBg); window.setTextColor(oTxt)
- end
- local function drawTurbineInQuadrant(window, targetName, devInfo,sts,lE,qx,qy,qW,qH)
- pcall(function()
- local title="Turbine "..(devInfo.typeId or "N/A"); local borderColors = getBorderColors("turbine")
- drawQuadrantBorder(window, qx,qy,qW,qH,borderColors.border,title,borderColors.text)
- window.setBackgroundColor(colors.black); local iX, cY, cW, bDTW = qx+2, qy+2, qW-4, qW-4
- if lE then window.setTextColor(colors.red);window.setCursorPos(iX,cY);window.write(("Error: "..lE):sub(1,cW)); return end
- if not sts then window.setTextColor(colors.gray);window.setCursorPos(iX,cY);window.write("Waiting..."); return end
- window.setTextColor(colors.lightBlue);window.setCursorPos(iX,cY);window.write("FE:");window.setTextColor(colors.white);window.setCursorPos(iX+4,cY);window.write(("%s/%s (%s)"):format(formatNumber(sts.energyStored),formatNumber(sts.maxEnergy),formatPercentage(sts.energyFilledPercentage)):sub(1,cW-4));cY=cY+1
- drawDetailedProgressBar(window,sts.energyStored,sts.maxEnergy,bDTW,iX,cY,"green","gray","("..formatPercentage(sts.energyFilledPercentage)..")");cY=cY+2
- window.setTextColor(colors.lightGray);window.setCursorPos(iX,cY);window.write("Steam:");window.setTextColor(colors.white);window.setCursorPos(iX+7,cY);window.write(("%s/%s (%s)"):format(formatNumber(sts.steamAmount,0),formatNumber(sts.steamCapacity,0),formatPercentage(sts.steamFilledPercentage)):sub(1,cW-7));cY=cY+1
- drawDetailedProgressBar(window,sts.steamAmount,sts.steamCapacity,bDTW,iX,cY,"lightGray","gray","("..formatPercentage(sts.steamFilledPercentage)..")");cY=cY+2
- window.setTextColor(colors.green);window.setCursorPos(iX,cY);window.write("Energy:");window.setTextColor(colors.white);window.setCursorPos(iX+8,cY);window.write(("%s/%s FE/t"):format(formatNumber(sts.productionRate),formatNumber(sts.maxProduction)):sub(1,cW-8));cY=cY+2
- window.setTextColor(colors.yellow);window.setCursorPos(iX,cY);local sT=(sts.width and string.format("%dx%dx%d",sts.width,sts.length,sts.height))or"N/A";window.write(("Size: %s"):format(sT):sub(1,cW));cY=cY+1
- local buttonY=qy+qH-2
- local bE={};local totBW=0;for _,mI in ipairs(dumpingModes)do local iA=sts.dumpingMode==mI.apiName;local iGSL=sharedData.globalModeButtonFeedback.modeAPIName==mI.apiName and sharedData.globalModeButtonFeedback.expiryTime>os.time();local bT=iA and("["..mI.displayName.."]")or(iGSL and("["..mI.displayName.."]")or(" "..mI.displayName.." "));table.insert(bE,{text=bT,mode=mI,active=iA,globallySetLime=iGSL});totBW=totBW+#bT end;totBW=totBW+#dumpingModes-1;local bSX=iX+math.max(0,math.floor((cW-totBW)/2));local cBX=bSX;for _,bEl in ipairs(bE)do local cK=bEl.active and"yellow"or(bEl.globallySetLime and"lime"or"white");window.setTextColor(getSafeColor(cK));if cBX+#bEl.text-1<qx+qW-1 then window.setCursorPos(cBX,buttonY);window.write(bEl.text);table.insert(sharedData.pageButtonRegions,{type="dump_mode",target=targetName,turbineName=devInfo.name,modeToSet=bEl.mode.apiName,xStart=cBX,xEnd=cBX+#bEl.text-1,pageY=buttonY});cBX=cBX+#bEl.text+1 else break end end
- end)
- end
- local function drawFusionReactorQuadrant(window, targetName, devInfo,sts,lE,qx,qy,qW,qH)
- pcall(function()
- local title="Fusion Reactor "..(devInfo.typeId or "N/A"); local borderColors = getBorderColors("fusionReactor")
- drawQuadrantBorder(window, qx,qy,qW,qH,borderColors.border,title,borderColors.text)
- window.setBackgroundColor(colors.black); local iX, cY, cW, bDTW = qx+2, qy+2, qW-4, qW-4
- if lE then window.setTextColor(colors.red);window.setCursorPos(iX,cY);window.write(("Error: "..lE):sub(1,cW)); return end
- if not sts or not sts.fuel then window.setTextColor(colors.gray);window.setCursorPos(iX,cY);window.write("Waiting..."); return end
- local useSpacedLayout = (qH > 15)
- local verticalPadding = useSpacedLayout and 2 or 1
- local mPX=iX+math.floor(cW/2); local hCW=math.floor(cW/2)-1
- if sts.waterAmount and sts.waterAmount>0 then
- window.setTextColor(colors.blue);window.setCursorPos(iX,cY);window.write("Water:");window.setTextColor(colors.white);window.setCursorPos(iX+7,cY);window.write(("%s/%s (%s)"):format(formatNumber(sts.waterAmount,0),formatNumber(sts.waterCapacity,0),formatPercentage(sts.waterFilledPercentage)):sub(1,cW-7));cY=cY+1
- drawDetailedProgressBar(window,sts.waterAmount,sts.waterCapacity,bDTW,iX,cY,"blue","gray","("..formatPercentage(sts.waterFilledPercentage)..")");cY=cY+verticalPadding
- end
- if sts.steamAmount and sts.steamAmount>0 then
- window.setTextColor(colors.lightGray);window.setCursorPos(iX,cY);window.write("Steam:");window.setTextColor(colors.white);window.setCursorPos(iX+7,cY);window.write(("%s/%s (%s)"):format(formatNumber(sts.steamAmount,0),formatNumber(sts.steamCapacity,0),formatPercentage(sts.steamFilledPercentage)):sub(1,cW-7));cY=cY+1
- drawDetailedProgressBar(window,sts.steamAmount,sts.steamCapacity,bDTW,iX,cY,"lightGray","gray","("..formatPercentage(sts.steamFilledPercentage)..")");cY=cY+verticalPadding
- end
- if sts.fuel.hasDTFuel then
- window.setTextColor(colors.purple);window.setCursorPos(iX,cY);window.write("DTFuel:");window.setTextColor(colors.white);window.setCursorPos(iX+8,cY);window.write(("%s/%s (%s)"):format(formatNumber(sts.fuel.dtFuelAmount,0),formatNumber(sts.fuel.dtFuelCapacity,0),formatPercentage(sts.fuel.dtFuelFilledPercentage)):sub(1,cW-8));cY=cY+1
- drawDetailedProgressBar(window,sts.fuel.dtFuelAmount,sts.fuel.dtFuelCapacity,bDTW,iX,cY,"purple","gray","("..formatPercentage(sts.fuel.dtFuelFilledPercentage)..")");cY=cY+verticalPadding
- else
- window.setTextColor(colors.red);window.setCursorPos(iX,cY);window.write("Deuterium:");window.setTextColor(colors.white);window.setCursorPos(iX+11,cY);window.write(("%s/%s"):format(formatNumber(sts.fuel.deuteriumAmount,0),formatNumber(sts.fuel.deuteriumCapacity,0)):sub(1,cW-11));cY=cY+1
- drawDetailedProgressBar(window,sts.fuel.deuteriumAmount,sts.fuel.deuteriumCapacity,bDTW,iX,cY,"red","gray",formatPercentage(sts.fuel.deuteriumFilledPercentage));cY=cY+verticalPadding
- window.setTextColor(colors.green);window.setCursorPos(iX,cY);window.write("Tritium:");window.setTextColor(colors.white);window.setCursorPos(iX+9,cY);window.write(("%s/%s"):format(formatNumber(sts.fuel.tritiumAmount,0),formatNumber(sts.fuel.tritiumCapacity,0)):sub(1,cW-9));cY=cY+1
- drawDetailedProgressBar(window,sts.fuel.tritiumAmount,sts.fuel.tritiumCapacity,bDTW,iX,cY,"green","gray",formatPercentage(sts.fuel.tritiumFilledPercentage));cY=cY+verticalPadding
- end
- local caseTempThresh = sts.fuel.hasDTFuel and dynamicColorThresholds.caseTemp.dtFuel or dynamicColorThresholds.caseTemp.noDt
- local plasmaTempThresh = sts.fuel.hasDTFuel and dynamicColorThresholds.plasmaTemp.dtFuel or dynamicColorThresholds.plasmaTemp.noDt
- local energyThresh = sts.fuel.hasDTFuel and dynamicColorThresholds.energy.dtFuel or dynamicColorThresholds.energy.noDt
- local caseTempColor = getDynamicColor(sts.caseTemp, caseTempThresh)
- local plasmaTempColor = getDynamicColor(sts.plasmaTemp, plasmaTempThresh)
- local energyColor = getDynamicColor(sts.productionRate, energyThresh)
- if cY < qy + qH - 2 then
- local caseT, caseU = convertTemperature(sts.caseTemp, sharedData.config.tempUnit)
- local plasmaT, plasmaU = convertTemperature(sts.plasmaTemp, sharedData.config.tempUnit)
- local caseStr = ("Case: %s%s"):format(formatNumber(caseT,1), caseU)
- window.setTextColor(getSafeColor(caseTempColor)); window.setCursorPos(iX,cY); window.write(caseStr:sub(1,hCW-1))
- local plasmaStr = ("Plasma: %s%s"):format(formatNumber(plasmaT,1), plasmaU)
- window.setTextColor(getSafeColor(plasmaTempColor)); window.setCursorPos(mPX,cY); window.write(plasmaStr:sub(1,cW-mPX+iX-1))
- cY = cY+1
- end
- if cY < qy + qH - 1 then
- local energyStr = ("Energy: %s FE/t"):format(formatNumber(sts.productionRate))
- window.setTextColor(getSafeColor(energyColor)); window.setCursorPos(iX,cY); window.write(energyStr:sub(1,hCW-1))
- local iC=sts.isIgnited and colors.lime or colors.red; local iT=sts.isIgnited and "Ignited" or "Off"; local sT="Status: "..iT
- window.setTextColor(iC); window.setCursorPos(mPX+math.max(0,math.floor((hCW-#sT)/2)),cY); window.write(sT:sub(1,hCW-1))
- end
- local buttonY=qy+qH-2
- local tempB, tempT = window.getBackgroundColor(), window.getTextColor()
- window.setBackgroundColor(colors.black)
- window.setCursorPos(qx + 1, buttonY); window.write(string.rep(" ", qW - 2))
- window.setBackgroundColor(tempB); window.setTextColor(tempT)
- local btnE={};
- if sharedData.config.enableFusionPowerDown then table.insert(btnE, {t="[Power Down]", a="fuel_off", c="red"}) end
- table.insert(btnE,{t="[MIN]",a="set_min",c="blue"});
- table.insert(btnE,{t="[-]",a="dec_rate",c="red"});
- table.insert(btnE,{t=" "..string.format("%2d",sts.injectionRate or 0).." ",a="display_only",c="white"});
- table.insert(btnE,{t="[+]",a="inc_rate",c="green"});
- table.insert(btnE,{t="[MAX]",a="set_max",c="blue"});
- if sharedData.config.enableFusionIgnition then table.insert(btnE, {t="[Ignite]", a="ignite", c="orange"}) end
- local totBW=0; for _,el in ipairs(btnE)do totBW=totBW+#el.t end;
- totBW = totBW + #btnE - 1;
- local bSX=iX+math.max(0,math.floor((cW-totBW)/2));
- local cBX=bSX;
- for _,bEl in ipairs(btnE)do
- window.setTextColor(getSafeColor(bEl.c));
- if cBX+#bEl.t-1<qx+qW-1 then
- window.setCursorPos(cBX,buttonY);
- window.write(bEl.t);
- if bEl.a~="display_only"then
- if bEl.a == "ignite" or bEl.a == "fuel_off" then
- table.insert(sharedData.pageButtonRegions,{type= (bEl.a == "ignite" and "fusion_ignite" or "fusion_fuel_toggle"),target=targetName, deviceName=devInfo.name, xStart=cBX,xEnd=cBX+#bEl.t-1,pageY=buttonY})
- else
- table.insert(sharedData.pageButtonRegions,{type="set_injection_rate",target=targetName,deviceName=devInfo.name,action=bEl.a,currentRate=sts.injectionRate,xStart=cBX,xEnd=cBX+#bEl.t-1,pageY=buttonY})
- end
- end;
- cBX=cBX+#bEl.t+1
- else break end
- end
- end)
- end
- local function drawInductionMatrixQuadrant(window, targetName, devInfo,sts,lE,qx,qy,qW,qH)
- pcall(function()
- local title="Induction Matrix "..(devInfo.typeId or "N/A"); local borderColors = getBorderColors("inductionMatrix")
- drawQuadrantBorder(window, qx,qy,qW,qH,borderColors.border,title,borderColors.text)
- window.setBackgroundColor(colors.black); local iX, cY, cW, bDTW = qx+2, qy+2, qW-4, qW-4
- if lE then window.setTextColor(colors.red);window.setCursorPos(iX,cY);window.write(("Error: "..lE):sub(1,cW)); return end
- if not sts then window.setTextColor(colors.gray);window.setCursorPos(iX,cY);window.write("Waiting..."); return end
- local halfWidth = math.floor(cW / 2)
- window.setTextColor(colors.white);window.setCursorPos(iX,cY);window.write("Formed: ");window.setTextColor(sts.isFormed and getSafeColor("lime")or getSafeColor("red"));window.write(sts.isFormed and"Yes"or"No");cY=cY+1
- local energyStr = string.format("%s/%s", formatLargeEnergyFE(sts.energyFE), formatLargeEnergyFE(sts.maxEnergyFE))
- window.setTextColor(colors.yellow); window.setCursorPos(iX, cY); window.write(("Energy: " .. energyStr):sub(1, cW)); cY=cY+1
- drawDetailedProgressBar(window, sts.energyFE,sts.maxEnergyFE,bDTW,iX,cY,"green","gray", formatPercentage(sts.energyFilledPercentage)); cY=cY+2
- local rt=sts.energyChangeRatePerTick or 0;local rtP, rtCK;
- if rt>matrixRateThreshold then rtP="Input: ";rtCK="green" elseif rt<-matrixRateThreshold then rtP="Output: ";rtCK="red" else rtP="Net: ";rt=0;rtCK="gray" end
- local rateDisplay = (rtP .. formatLargeEnergyFE(rt) .. "FE/t")
- local tES="N/A";local tEL="Status: ";local timeColorKey="gray";
- if not sts.isFormed then tES="Not Formed";timeColorKey="lightGray"
- elseif sts.energyFE >= sts.maxEnergyFE then tES="Full";timeColorKey="lime"
- elseif rt > matrixRateThreshold then tEL="To Fill: ";timeColorKey="yellow";tES=formatDuration((sts.maxEnergyFE - sts.energyFE)/rt/TICKS_PER_SECOND)
- elseif rt < -matrixRateThreshold then tEL="To Empty: ";timeColorKey="orange";tES=formatDuration(sts.energyFE/math.abs(rt)/TICKS_PER_SECOND)
- elseif sts.energyFE <= 0 then tES="Empty";timeColorKey="red"
- else tES="Stable";timeColorKey="lightGray" end
- window.setTextColor(getSafeColor(rtCK,colors.gray));window.setCursorPos(iX,cY);window.write(rateDisplay:sub(1, halfWidth -1 ))
- window.setCursorPos(iX + halfWidth,cY)
- window.setTextColor(colors.white); window.write(tEL:sub(1, #tEL))
- window.setTextColor(getSafeColor(timeColorKey,colors.gray)); window.write(tES:sub(1, cW - halfWidth - #tEL)); cY=cY+2
- local cellsStr = ("Cells: " .. tostring(sts.installedCells or "N/A")); local providersStr = ("Prov: " .. tostring(sts.installedProviders or "N/A"))
- local dS= sts.isFormed and (sts.width.."x"..sts.height.."x"..sts.length) or "N/A"
- local sizeStr = ("Size: " .. dS)
- local combinedStatsStr = cellsStr .. " " .. providersStr .. " " .. sizeStr
- local statsStartX = iX + math.max(0, math.floor((cW - #combinedStatsStr) / 2))
- window.setTextColor(colors.white);
- window.setCursorPos(statsStartX, cY); window.write(cellsStr)
- window.setCursorPos(statsStartX + #cellsStr + 2, cY); window.write(providersStr)
- window.setCursorPos(statsStartX + #cellsStr + 2 + #providersStr + 2, cY); window.write(sizeStr)
- end)
- end
- local function drawBoilerQuadrant(window, targetName, devInfo,sts,lE,qx,qy,qW,qH)
- pcall(function()
- local title="Boiler "..(devInfo.typeId or "N/A"); local borderColors = getBorderColors("boiler")
- drawQuadrantBorder(window, qx,qy,qW,qH,borderColors.border,title,borderColors.text)
- window.setBackgroundColor(colors.black); local iX, cY, cW, bDTW = qx+2, qy+2, qW-4, qW-4
- if lE then window.setTextColor(colors.red);window.setCursorPos(iX,cY);window.write(("Error: "..lE):sub(1,cW)); return end
- if not sts then window.setTextColor(colors.gray);window.setCursorPos(iX,cY);window.write("Waiting..."); return end
- window.setTextColor(colors.white); window.setCursorPos(iX, cY); window.write("Formed: "); window.setTextColor(sts.isFormed and colors.lime or colors.red); window.write(sts.isFormed and "Yes" or "No"); cY=cY+2
- window.setTextColor(colors.blue); window.setCursorPos(iX,cY); window.write("Water:"); window.setTextColor(colors.white); window.setCursorPos(iX+7,cY); window.write(("%s/%s mB"):format(formatNumber(sts.waterAmount,0),formatNumber(sts.waterCapacity,0)):sub(1,cW-7)); cY=cY+1
- drawDetailedProgressBar(window,sts.waterAmount,sts.waterCapacity,bDTW,iX,cY,"blue","gray",formatPercentage(sts.waterFilledPercentage)); cY=cY+2
- window.setTextColor(colors.lightGray); window.setCursorPos(iX,cY); window.write("Steam:"); window.setTextColor(colors.white); window.setCursorPos(iX+7,cY); window.write(("%s/%s mB"):format(formatNumber(sts.steamAmount,0),formatNumber(sts.steamCapacity,0)):sub(1,cW-7)); cY=cY+1
- drawDetailedProgressBar(window,sts.steamAmount,sts.steamCapacity,bDTW,iX,cY,"lightGray","gray",formatPercentage(sts.steamFilledPercentage)); cY=cY+2
- local tempColor = getDynamicColor(sts.temperature, dynamicColorThresholds.boilerTemp)
- local halfWidth = math.floor(cW / 2)
- local tempVal, tempUnit = convertTemperature(sts.temperature, sharedData.config.tempUnit)
- window.setTextColor(getSafeColor(tempColor)); window.setCursorPos(iX,cY); window.write("Temp:"); window.setTextColor(colors.white); window.write(("%s%s"):format(formatNumber(tempVal,1), tempUnit):sub(1,halfWidth-6));
- window.setTextColor(colors.green); window.setCursorPos(iX+halfWidth,cY); window.write("Boil:"); window.setTextColor(colors.white); window.write(("%s/%s"):format(formatNumber(sts.boilRate,1),formatNumber(sts.maxBoilRate,1)):sub(1,cW-halfWidth-6)); cY=cY+2
- window.setTextColor(colors.red); window.setCursorPos(iX,cY); window.write("Env Loss:"); window.setTextColor(colors.white); window.write(("%sK/t"):format(formatNumber(sts.envLoss,2)):sub(1,cW-10)); cY=cY+1
- end)
- end
- local function drawThermalEvaporationQuadrant(window, targetName, devInfo,sts,lE,qx,qy,qW,qH)
- pcall(function()
- local title="Thermal Evaporation "..(devInfo.typeId or "N/A");
- local borderColors = getBorderColors("thermalEvaporation")
- drawQuadrantBorder(window, qx,qy,qW,qH,borderColors.border,title,borderColors.text)
- window.setBackgroundColor(colors.black); local iX, cY, cW, bDTW = qx+2, qy+2, qW-4, qW-4
- if lE then window.setTextColor(colors.red);window.setCursorPos(iX,cY);window.write(("Error: "..lE):sub(1,cW)); return end
- if not sts then window.setTextColor(colors.gray);window.setCursorPos(iX,cY);window.write("Waiting..."); return end
- local tempColor = getDynamicColor(sts.temperature, dynamicColorThresholds.thermalTemp)
- local tempVal, tempUnit = convertTemperature(sts.temperature, sharedData.config.tempUnit)
- local tempStr = ("Temp: "..formatNumber(tempVal, 1)..tempUnit)
- window.setTextColor(getSafeColor(tempColor)); window.setCursorPos(iX, cY); window.write(tempStr); cY=cY+2
- local inputName = formatFluidName(sts.inputName)
- window.setTextColor(colors.cyan); window.setCursorPos(iX,cY); window.write(inputName..":"); window.setTextColor(colors.white); window.setCursorPos(iX+#inputName+2,cY); window.write(("%s/%s"):format(formatNumber(sts.inputAmount,0),formatNumber(sts.inputCapacity,0)):sub(1,cW-#inputName-2)); cY=cY+1
- drawDetailedProgressBar(window,sts.inputAmount,sts.inputCapacity,bDTW,iX,cY,"cyan","gray",formatPercentage(sts.inputFilledPercentage)); cY=cY+2
- local outputName = formatFluidName(sts.outputName)
- window.setTextColor(colors.yellow); window.setCursorPos(iX,cY); window.write(outputName..":"); window.setTextColor(colors.white); window.setCursorPos(iX+#outputName+2,cY); window.write(("%s/%s"):format(formatNumber(sts.outputAmount,0),formatNumber(sts.outputCapacity,0)):sub(1,cW-#outputName-2)); cY=cY+1
- drawDetailedProgressBar(window,sts.outputAmount,sts.outputCapacity,bDTW,iX,cY,"yellow","gray",formatPercentage(sts.outputFilledPercentage)); cY=cY+2
- local prodStr = ("Production: " .. string.format("%s mB/t", formatNumber(sts.production, 2)))
- local heightStr = ("Height: " .. sts.height)
- window.setTextColor(colors.white); window.setCursorPos(iX, cY); window.write(prodStr)
- window.setCursorPos(iX + cW - #heightStr, cY); window.write(heightStr)
- end)
- end
- local function drawDynamicTankQuadrant(window, targetName, devInfo,sts,lE,qx,qy,qW,qH)
- pcall(function()
- local title="Dynamic Tank "..(devInfo.typeId or "N/A"); local borderColors = getBorderColors("dynamicTank")
- drawQuadrantBorder(window, qx,qy,qW,qH,borderColors.border,title,borderColors.text)
- window.setBackgroundColor(colors.black); local iX, cY, cW, bDTW = qx+2, qy+2, qW-4, qW-4
- if lE then window.setTextColor(colors.red);window.setCursorPos(iX,cY);window.write(("Error: "..lE):sub(1,cW)); return end
- if not sts or not sts.stored then window.setTextColor(colors.gray);window.setCursorPos(iX,cY);window.write("Waiting..."); return end
- local storedName = formatFluidName(sts.stored.name)
- window.setTextColor(colors.white); window.setCursorPos(iX, cY); window.write(storedName..":"); cY=cY+1
- local amountStr = ("%s/%s (%s)"):format(formatNumber(sts.stored.amount, 1), formatNumber(sts.capacity, 1), formatPercentage(sts.filledPercentage))
- window.setCursorPos(iX, cY); window.write(amountStr:sub(1, cW)); cY=cY+1
- drawDetailedProgressBar(window, sts.stored.amount, sts.capacity, bDTW, iX, cY, "yellow", "gray", formatPercentage(sts.filledPercentage)); cY=cY+2
- window.setTextColor(colors.yellow);
- local sT = (sts.width and string.format("Size: %dx%dx%d",sts.width,sts.length,sts.height)) or "Size: N/A"
- window.setCursorPos(iX, cY); window.write(sT:sub(1, cW)); cY=cY+2
- local bY = qy+qH-2
- if bY >= cY then
- local prevText = "< "
- local modeText = " " .. (sts.editMode or "N/A") .. " "
- local nextText = " >"
- local fullBar = prevText .. modeText .. nextText
- local startX = iX + math.floor((cW - #fullBar)/2)
- window.setTextColor(colors.white); window.setCursorPos(startX, bY); window.write(prevText)
- table.insert(sharedData.pageButtonRegions, {type="container_edit_mode", target=targetName, deviceName=devInfo.name, action="dec", xStart=startX, xEnd=startX+#prevText-1, pageY=bY})
- startX = startX + #prevText
- window.setTextColor(colors.yellow); window.setCursorPos(startX, bY); window.write(modeText)
- startX = startX + #modeText
- window.setTextColor(colors.white); window.setCursorPos(startX, bY); window.write(nextText)
- table.insert(sharedData.pageButtonRegions, {type="container_edit_mode", target=targetName, deviceName=devInfo.name, action="inc", xStart=startX, xEnd=startX+#nextText-1, pageY=bY})
- end
- end)
- end
- local function drawSPSQuadrant(window, targetName, devInfo,sts,lE,qx,qy,qW,qH)
- pcall(function()
- local title="SPS "..(devInfo.typeId or "N/A"); local borderColors = getBorderColors("sps")
- drawQuadrantBorder(window, qx,qy,qW,qH,borderColors.border,title,borderColors.text)
- window.setBackgroundColor(colors.black); local iX, cY, cW, bDTW = qx+2, qy+3, qW-4, qW-4
- if lE then window.setTextColor(colors.red);window.setCursorPos(iX,cY);window.write(("Error: "..lE):sub(1,cW)); return end
- if not sts then window.setTextColor(colors.gray);window.setCursorPos(iX,cY);window.write("Waiting..."); return end
- window.setTextColor(colors.cyan); window.setCursorPos(iX,cY); window.write("Polonium:"); window.setTextColor(colors.white); window.setCursorPos(iX+10,cY); window.write(("%s/%s"):format(formatNumber(sts.input.amount,0),formatNumber(sts.inputCapacity,0)):sub(1,cW-10)); cY=cY+1
- drawDetailedProgressBar(window,sts.input.amount,sts.inputCapacity,bDTW,iX,cY,"cyan","gray",formatPercentage(sts.inputFilledPercentage)); cY=cY+2
- window.setTextColor(colors.purple); window.setCursorPos(iX,cY); window.write("Antimatter:"); window.setTextColor(colors.white); window.setCursorPos(iX+12,cY); window.write(("%s/%s"):format(formatNumber(sts.output.amount,0),formatNumber(sts.outputCapacity,0)):sub(1,cW-12)); cY=cY+1
- drawDetailedProgressBar(window,sts.output.amount,sts.outputCapacity,bDTW,iX,cY,"purple","gray",formatPercentage(sts.outputFilledPercentage)); cY=cY+2
- local isActive = sts.processRate > 0
- local statusText = isActive and "Status: Active" or "Status: Idle"
- local statusColor = isActive and colors.lime or colors.red
- local statusX = iX + math.floor((cW - #statusText) / 2)
- window.setTextColor(statusColor)
- window.setCursorPos(statusX, cY); window.write(statusText); cY=cY+1
- local sizeStr = (sts.width and string.format("Size: %dx%dx%d",sts.width,sts.height,sts.length)) or "Size: N/A"
- local coilsStr = ("Coils: " .. (sts.coils or "N/A"))
- local rateStr = ("Rate: " .. string.format("%s mB/t", formatNumber(sts.processRate, 2)))
- window.setTextColor(colors.yellow); window.setCursorPos(iX, cY); window.write(sizeStr)
- window.setCursorPos(iX + cW - #coilsStr, cY); window.write(coilsStr); cY=cY+1
- local rateX = iX + math.floor((cW - #rateStr) / 2)
- window.setTextColor(colors.white); window.setCursorPos(rateX, cY); window.write(rateStr); cY=cY+1
- local bY = qy+qH-2
- if bY >= cY then
- local isRedstoneOn
- if sharedData.config.useMoreRed then
- local side = sharedData.config.moreRedOutputSide
- local colorValue = colors[sharedData.config.spsRedstoneColor]
- if colorValue then
- isRedstoneOn = bit.band(redstone.getBundledOutput(side), colorValue) > 0
- end
- else
- isRedstoneOn = redstone.getOutput(sharedData.config.spsRedstoneControlSide)
- end
- local buttonText = isRedstoneOn and " [ OFF ] " or " [ ON ] "
- local buttonTextColor = isRedstoneOn and colors.red or colors.lime
- local startX = iX + math.floor((cW - #buttonText) / 2)
- window.setBackgroundColor(colors.black)
- window.setTextColor(buttonTextColor)
- window.setCursorPos(startX, bY); window.write(buttonText)
- table.insert(sharedData.pageButtonRegions, {
- type="sps_redstone_toggle",
- target=targetName,
- xStart=startX,
- xEnd=startX+#buttonText-1,
- pageY=bY
- })
- end
- end)
- end
- local function drawFissionReactorQuadrant(window, targetName, devInfo,sts,lE,qx,qy,qW,qH)
- pcall(function()
- local isScrammed = sharedData.scrammingReactors[devInfo.name]
- local borderColors = getBorderColors("fissionReactor")
- local currentBorderColor = borderColors.border
- if isScrammed and sharedData.flashToggle then
- currentBorderColor = "red"
- end
- local title="Fission Reactor "..(devInfo.typeId or "N/A");
- drawQuadrantBorder(window, qx,qy,qW,qH,currentBorderColor,title,borderColors.text)
- window.setBackgroundColor(colors.black)
- local iX, cW = qx+2, qW-4
- local contentBoxX, contentBoxY, contentBoxW, contentBoxH = qx+1, qy+1, qW-2, qH-6
- if lE then window.setTextColor(colors.red);window.setCursorPos(iX,qy+2);window.write(("Error: "..lE):sub(1,cW)); return end
- if not sts then window.setTextColor(colors.gray);window.setCursorPos(iX,cY);window.write("Waiting..."); return end
- local offset = sharedData.fissionScrollOffsets[devInfo.name] or 0
- local contentHeight = 21 -- Increased for new lines and heated coolant
- local viewHeight = contentBoxH
- local maxOffset = math.max(0, contentHeight - viewHeight)
- local clampedOffset = math.max(0, math.min(offset, maxOffset))
- local vY = 1
- local function drawLine(text, textColor)
- local actualY = contentBoxY + vY - clampedOffset
- if actualY >= contentBoxY and actualY < contentBoxY + viewHeight then
- window.setTextColor(getSafeColor(textColor, colors.white))
- window.setCursorPos(iX, actualY)
- window.write(text:sub(1, cW-1))
- end
- vY = vY + 1
- end
- local function drawProgressLine(value, max, fillColor, emptyColor, text)
- local actualY = contentBoxY + vY - clampedOffset
- if actualY >= contentBoxY and actualY < contentBoxY + viewHeight then
- drawDetailedProgressBar(window, value, max, cW-1, iX, actualY, fillColor, emptyColor, text)
- end
- vY = vY + 1
- end
- local tempColor = getDynamicColor(sts.temperature, dynamicColorThresholds.fissionTemp)
- local tempVal, tempUnit = convertTemperature(sts.temperature, sharedData.config.tempUnit)
- local tempStr = ("Temp: "..formatNumber(tempVal, 1)..tempUnit)
- local damageStr = ("Dmg: %.1f%%"):format(sts.damagePercent or 0)
- local combinedStr = tempStr .. string.rep(" ", cW - #tempStr - #damageStr) .. damageStr
- drawLine(combinedStr, tempColor)
- local statusColor = "gray"
- if sts.status == "ACTIVE" then
- statusColor = "lime"
- elseif sts.status == "IDLE" then
- statusColor = "yellow"
- elseif sts.status == "SCRAM" then
- statusColor = "red"
- elseif sts.status == "MELTDOWN" then
- statusColor = sharedData.flashToggle and "red" or "white"
- end
- local statusText = "Status: " .. (sts.status or "N/A")
- drawLine(statusText, statusColor)
- drawLine("Heating Rate: " .. string.format("%.2f mB/t", sts.heatingRate or 0), "white")
- vY = vY+1
- drawLine("Fuel: " .. formatNumber((sts.fuel and sts.fuel.amount) or 0, 1) .. "/" .. formatNumber(sts.fuelCapacity or 1, 1), "yellow")
- drawProgressLine((sts.fuel and sts.fuel.amount) or 0, sts.fuelCapacity or 1, "yellow", "gray", formatPercentage(sts.fuelFilledPercentage or 0))
- vY=vY+1
- drawLine("Coolant: " .. formatNumber((sts.coolant and sts.coolant.amount) or 0, 1) .. "/" .. formatNumber(sts.coolantCapacity or 1, 1), "blue")
- drawProgressLine((sts.coolant and sts.coolant.amount) or 0, sts.coolantCapacity or 1, "blue", "gray", formatPercentage(sts.coolantFilledPercentage or 0))
- vY=vY+1
- local heatedCoolantColor = "lightGray" -- Default for steam
- if sts.heatedCoolant and sts.heatedCoolant.name and string.find(sts.heatedCoolant.name, "sodium") then
- heatedCoolantColor = "orange"
- end
- drawLine("Heated: " .. formatNumber((sts.heatedCoolant and sts.heatedCoolant.amount) or 0, 1) .. "/" .. formatNumber(sts.heatedCoolantCapacity or 1, 1), heatedCoolantColor)
- drawProgressLine((sts.heatedCoolant and sts.heatedCoolant.amount) or 0, sts.heatedCoolantCapacity or 1, heatedCoolantColor, "gray", formatPercentage(sts.heatedCoolantFilledPercentage or 0))
- vY=vY+1
- drawLine("Waste: " .. formatNumber((sts.waste and sts.waste.amount) or 0, 1) .. "/" .. formatNumber(sts.wasteCapacity or 1, 1), "red")
- drawProgressLine((sts.waste and sts.waste.amount) or 0, sts.wasteCapacity or 1, "red", "gray", formatPercentage(sts.wasteFilledPercentage or 0))
- vY=vY+1
- if isScrammed then
- local cancelText = " [Cancel SCRAM] "
- local cancelY = qy + math.floor((qH - 6) / 2) + 2
- local cancelX = iX + math.floor((cW - #cancelText) / 2)
- if cancelY >= contentBoxY and cancelY < contentBoxY + viewHeight then
- window.setCursorPos(cancelX, cancelY)
- window.setBackgroundColor(colors.orange)
- window.setTextColor(colors.black)
- window.write(cancelText)
- table.insert(sharedData.pageButtonRegions, {
- type="fission_control",
- action="cancel_scram",
- deviceName=devInfo.name,
- target=targetName,
- xStart=cancelX,
- xEnd=cancelX + #cancelText - 1,
- pageY=cancelY
- })
- end
- end
- for i, quad in ipairs(sharedData.quadrantRegions[targetName] or {}) do
- if quad.deviceName == devInfo.name then
- quad.maxOffset = maxOffset
- break
- end
- end
- if maxOffset > 0 then
- local scrollX = qx + qW - 2
- local upArrowY = qy + 1
- local downArrowY = qy + qH - 6
- window.setCursorPos(scrollX, upArrowY); window.setBackgroundColor(colors.black); window.setTextColor(colors.white); window.write("^")
- table.insert(sharedData.pageButtonRegions, {type="fission_scroll", deviceName=devInfo.name, direction=-1, maxOffset=maxOffset, xStart=scrollX, xEnd=scrollX, pageY=upArrowY, target=targetName})
- window.setCursorPos(scrollX, downArrowY); window.setBackgroundColor(colors.black); window.setTextColor(colors.white); window.write("v")
- table.insert(sharedData.pageButtonRegions, {type="fission_scroll", deviceName=devInfo.name, direction=1, maxOffset=maxOffset, xStart=scrollX, xEnd=scrollX, pageY=downArrowY, target=targetName})
- local trackY = upArrowY + 1
- local trackHeight = downArrowY - trackY
- local handleHeight = math.max(1, math.floor(trackHeight * (viewHeight / contentHeight)))
- local handleY = trackY + math.floor((trackHeight - handleHeight) * (clampedOffset / maxOffset))
- for i=0, trackHeight-1 do
- window.setCursorPos(scrollX, trackY + i)
- if trackY + i >= handleY and trackY + i < handleY + handleHeight then
- window.setBackgroundColor(colors.lightGray)
- else
- window.setBackgroundColor(colors.gray)
- end
- window.write(" ")
- end
- table.insert(sharedData.pageButtonRegions, {
- type="fission_scroll_handle", deviceName=devInfo.name,
- xStart=scrollX, xEnd=scrollX, yStart=handleY, yEnd=handleY+handleHeight-1,
- trackY=trackY, trackH=trackHeight, maxOffset=maxOffset,
- target=targetName
- })
- end
- local burnRateY = qy + qH - 4
- local buttonY_1 = qy + qH - 3
- local buttonY_2 = qy + qH - 2
- local tempB, tempT = window.getBackgroundColor(), window.getTextColor()
- window.setBackgroundColor(colors.black)
- window.setCursorPos(qx + 1, burnRateY); window.write(string.rep(" ", qW - 2))
- window.setCursorPos(qx + 1, buttonY_1); window.write(string.rep(" ", qW - 2))
- window.setCursorPos(qx + 1, buttonY_2); window.write(string.rep(" ", qW - 2))
- window.setBackgroundColor(tempB); window.setTextColor(tempT)
- local burnRateStr = string.format("Burn Rate: %.2f mB/t / %.2f mB/t", sts.burnRate or 0, sts.maxBurnRate or 0)
- local burnRateX = iX + math.floor((cW - #burnRateStr) / 2)
- window.setBackgroundColor(colors.black)
- window.setCursorPos(burnRateX, burnRateY)
- window.setTextColor(colors.white)
- window.write(burnRateStr)
- local topRowButtons = {
- {t="[ACTIVATE]", a="activate", c="lime"},
- {t="[Set Rate]", a="open_keypad", c="blue"},
- {t="[SCRAM]", a="scram", c="red"}
- }
- local totalTopWidth = 0
- for _, btnData in ipairs(topRowButtons) do totalTopWidth = totalTopWidth + #btnData.t + 1 end
- totalTopWidth = totalTopWidth - 1
- local currentButtonX = iX + math.floor((cW - totalTopWidth) / 2)
- for _, btnData in ipairs(topRowButtons) do
- window.setBackgroundColor(colors.black)
- window.setTextColor(getSafeColor(btnData.c))
- window.setCursorPos(currentButtonX, buttonY_1); window.write(btnData.t)
- local regionType = (btnData.a == "open_keypad") and "fission_open_keypad" or "fission_control"
- table.insert(sharedData.pageButtonRegions, {
- type=regionType, target=targetName, deviceName=devInfo.name, action=btnData.a,
- xStart=currentButtonX, xEnd=currentButtonX+#btnData.t-1, pageY=buttonY_1
- })
- currentButtonX = currentButtonX + #btnData.t + 1
- end
- local btnElements = { {t="[<<<]",a="dec_rate_10",c="darkRed"}, {t="[<<]",a="dec_rate_1",c="red"}, {t="[<]",a="dec_rate_01",c="orange"}, {t="[>]",a="inc_rate_01",c="lime"}, {t="[>>]",a="inc_rate_1",c="green"}, {t="[>>>]",a="inc_rate_10",c="darkGreen"} }
- local totalButtonsWidth = 0
- for _,el in ipairs(btnElements) do totalButtonsWidth = totalButtonsWidth + #el.t + 1 end; totalButtonsWidth = totalButtonsWidth -1
- currentButtonX = iX + math.floor((cW - totalButtonsWidth) / 2)
- for _,btnData in ipairs(btnElements) do
- window.setBackgroundColor(colors.black); window.setTextColor(getSafeColor(btnData.c))
- window.setCursorPos(currentButtonX, buttonY_2); window.write(btnData.t)
- table.insert(sharedData.pageButtonRegions,{type="fission_control",target=targetName,deviceName=devInfo.name,action=btnData.a,xStart=currentButtonX,xEnd=currentButtonX+#btnData.t-1,pageY=buttonY_2})
- currentButtonX = currentButtonX + #btnData.t + 1
- end
- window.setBackgroundColor(colors.black)
- end)
- end
- local function drawSummaryPanel(window, targetName, qx,qy,qW,qH)
- pcall(function()
- local oS = sharedData.overallTurbineStats; local borderColors = getBorderColors("turbineSummary")
- drawQuadrantBorder(window, qx,qy,qW,qH,borderColors.border, "Turbine Main Panel", borderColors.text)
- window.setBackgroundColor(colors.black); local iX, cY, cW, bDTW = qx+2, qy+2, qW-4, qW-4
- if not oS then window.setTextColor(colors.gray);window.setCursorPos(iX,cY);window.write("Calculating..."); return end
- window.setTextColor(colors.white);window.setCursorPos(iX,cY);window.write(("Total Turbines: "..(oS.totalTurbineCount or "N/A")):sub(1,cW));cY=cY+2
- window.setTextColor(colors.lightBlue);window.setCursorPos(iX,cY);window.write("Total FE:");window.setTextColor(colors.white);window.setCursorPos(iX+10,cY);window.write(("%s/%s (%s)"):format(formatNumber(oS.energyStored),formatNumber(oS.maxEnergy),formatPercentage(oS.energyFilledPercentage)):sub(1,cW-10));cY=cY+1
- drawDetailedProgressBar(window,oS.energyStored,oS.maxEnergy,bDTW,iX,cY,"green","gray","("..formatPercentage(oS.energyFilledPercentage)..")");cY=cY+2
- window.setTextColor(colors.lightGray);window.setCursorPos(iX,cY);window.write("Total Steam:");window.setTextColor(colors.white);window.setCursorPos(iX+13,cY);window.write(("%s/%s (%s)"):format(formatNumber(oS.steamAmount,0),formatNumber(oS.steamCapacity,0),formatPercentage(oS.steamFilledPercentage)):sub(1,cW-13));cY=cY+1
- drawDetailedProgressBar(window,oS.steamAmount,oS.steamCapacity,bDTW,iX,cY,"lightGray","gray","("..formatPercentage(oS.steamFilledPercentage)..")");cY=cY+2
- window.setTextColor(colors.green);window.setCursorPos(iX,cY);window.write("Total Prod:");window.setTextColor(colors.white);window.setCursorPos(iX+12,cY);window.write(("%s/%s FE/t"):format(formatNumber(oS.productionRate),formatNumber(oS.maxProduction)):sub(1,cW-12));cY=cY+1
- local bY=qy+qH-2;if bY>=cY then local totBW=0;local bE={};for _,mI in ipairs(dumpingModes)do local iF=sharedData.globalModeButtonFeedback.modeAPIName==mI.apiName and sharedData.globalModeButtonFeedback.expiryTime~=0;local iC=sharedData.globallyConsistentTurbineMode==mI.apiName and not iF;local btT=(iF or iC)and("["..mI.displayName.."]")or(" "..mI.displayName.." ");table.insert(bE,{text=btT,mode=mI,isFeedback=iF,isConsistent=iC});totBW=totBW+#btT end;totBW=totBW+(#dumpingModes-1);local bSX=iX+math.max(0,math.floor((cW-totBW)/2));local cBX=bSX;for _,bEl in ipairs(bE)do local cTCK="white";if bEl.isFeedback then cTCK=sharedData.globalModeButtonFeedback.colorKey or"blue"elseif bEl.isConsistent then cTCK="yellow"end;window.setTextColor(getSafeColor(cTCK,colors.white));if cBX+#bEl.text-1<qx+qW-1 then window.setCursorPos(cBX,bY);window.write(bEl.text);table.insert(sharedData.pageButtonRegions,{type="global_dump_mode",target=targetName,modeToSet=bEl.mode.apiName,xStart=cBX,xEnd=cBX+#bEl.text-1,pageY=bY});cBX=cBX+#bEl.text+1 else break end end end
- end)
- end
- local function drawDeviceQuadrant(window, targetName, itemToDraw, qx, qy, qW, qH)
- if not itemToDraw then return end
- if not sharedData.quadrantRegions[targetName] then sharedData.quadrantRegions[targetName] = {} end
- table.insert(sharedData.quadrantRegions[targetName], {
- x1 = qx, y1 = qy, x2 = qx + qW - 1, y2 = qy + qH - 1,
- deviceType = (itemToDraw.drawType == "device") and itemToDraw.info.type or nil,
- deviceName = (itemToDraw.drawType == "device") and itemToDraw.info.name or nil
- })
- if itemToDraw.drawType == "summary" then
- drawSummaryPanel(window, targetName, qx, qy, qW, qH)
- return
- end
- if itemToDraw.drawType == "device" then
- local devInfo = itemToDraw.info
- local stats = sharedData.deviceStats[devInfo.name]
- local err = sharedData.lastError[devInfo.name]
- if devInfo.type == "turbine" then
- drawTurbineInQuadrant(window, targetName, devInfo, stats, err, qx, qy, qW, qH)
- elseif devInfo.type == "inductionMatrix" then
- drawInductionMatrixQuadrant(window, targetName, devInfo, stats, err, qx, qy, qW, qH)
- elseif devInfo.type == "fusionReactor" then
- drawFusionReactorQuadrant(window, targetName, devInfo, stats, err, qx, qy, qW, qH)
- elseif devInfo.type == "boiler" then
- drawBoilerQuadrant(window, targetName, devInfo, stats, err, qx, qy, qW, qH)
- elseif devInfo.type == "thermalEvaporation" then
- drawThermalEvaporationQuadrant(window, targetName, devInfo, stats, err, qx, qy, qW, qH)
- elseif devInfo.type == "dynamicTank" then
- drawDynamicTankQuadrant(window, targetName, devInfo, stats, err, qx, qy, qW, qH)
- elseif devInfo.type == "sps" then
- drawSPSQuadrant(window, targetName, devInfo, stats, err, qx, qy, qW, qH)
- elseif devInfo.type == "fissionReactor" then
- drawFissionReactorQuadrant(window, targetName, devInfo, stats, err, qx, qy, qW, qH)
- else
- local title = devInfo.type .. " " .. (devInfo.typeId or "")
- local bC = getBorderColors(devInfo.type)
- drawQuadrantBorder(window, qx, qy, qW, qH, bC.border, title, bC.text)
- end
- end
- end
- local function drawKeypad(window)
- if not sharedData.keypad.isOpen then return end
- local keypadWidth = 23
- local keypadHeight = 11
- local qx, qy, qW, qH = sharedData.keypad.qx, sharedData.keypad.qy, sharedData.keypad.qW, sharedData.keypad.qH
- if not qW or qW == 0 or not qH or qH == 0 then
- qW, qH = window.getSize()
- qx, qy = 1, 1
- end
- local kx = qx + math.floor((qW - keypadWidth) / 2)
- local ky = qy + math.floor((qH - keypadHeight) / 2)
- window.setBackgroundColor(colors.black)
- for y = ky, ky + keypadHeight - 1 do
- window.setCursorPos(kx, y)
- window.write(string.rep(" ", keypadWidth))
- end
- drawQuadrantBorder(window, kx, ky, keypadWidth, keypadHeight, "cyan", "Set Burn Rate", "black")
- window.setBackgroundColor(colors.white)
- window.setTextColor(colors.black)
- window.setCursorPos(kx + 2, ky + 2)
- window.write(string.rep(" ", keypadWidth - 4))
- window.setCursorPos(kx + 2, ky + 2)
- window.write(sharedData.keypad.inputValue)
- if sharedData.keypad.message and sharedData.keypad.message ~= "" then
- window.setBackgroundColor(colors.black)
- window.setTextColor(colors.red)
- local msg = sharedData.keypad.message:sub(1, keypadWidth - 4)
- local msgX = kx + 2 + math.floor((keypadWidth - 4 - #msg) / 2)
- window.setCursorPos(msgX, ky + 3)
- window.write(msg)
- end
- local buttonLayout = {
- { {t='1',w=4}, {t='2',w=4}, {t='3',w=4}, {t='C',w=4} },
- { {t='4',w=4}, {t='5',w=4}, {t='6',w=4}, {t='<-',w=4} },
- { {t='7',w=4}, {t='8',w=4}, {t='9',w=4}, {t='OK',w=4} },
- { {t='.',w=4}, {t='0',w=4} }
- }
- local buttonY = ky + 4
- for _, row in ipairs(buttonLayout) do
- local buttonX = kx + 2
- for _, btn in ipairs(row) do
- local btnText = btn.t
- local btnWidth = btn.w
- local isControl = btnText == 'C' or btnText == '<-' or btnText == 'OK'
- local isConfirm = btnText == 'OK'
- if buttonX + btnWidth -1 <= kx + keypadWidth - 2 then
- window.setBackgroundColor(isConfirm and colors.green or (isControl and colors.red or colors.gray))
- window.setTextColor(colors.white)
- window.setCursorPos(buttonX, buttonY)
- local centerPad = math.floor((btnWidth - #btnText) / 2)
- local paddedText = string.rep(" ", centerPad) .. btnText .. string.rep(" ", btnWidth - #btnText - centerPad)
- window.write(paddedText)
- local regionType
- if btnText == 'C' then regionType = "keypad_clear"
- elseif btnText == '<-' then regionType = "keypad_backspace"
- elseif btnText == 'OK' then regionType = "keypad_confirm"
- else regionType = "keypad_input" end
- table.insert(sharedData.pageButtonRegions, {
- type = regionType, value = btnText, target = sharedData.keypad.targetWindow,
- xStart = buttonX, y = buttonY, xEnd = buttonX + btnWidth - 1, pageY = buttonY
- })
- buttonX = buttonX + btnWidth + 1
- end
- end
- buttonY = buttonY + 1
- end
- local closeText = "[X]"
- window.setBackgroundColor(colors.red); window.setTextColor(colors.white)
- window.setCursorPos(kx + keypadWidth - 1 - #closeText, ky); window.write(closeText)
- table.insert(sharedData.pageButtonRegions, {
- type="keypad_close", target=sharedData.keypad.targetWindow,
- xStart=kx + keypadWidth - 1 - #closeText, xEnd=kx + keypadWidth - 2, pageY=ky
- })
- end
- local function drawScreen(window, targetName, itemsPerPage, currentPage, totalPages)
- local w, h = window.getSize()
- window.setCursorBlink(false); window.setBackgroundColor(colors.black); window.clear()
- sharedData.quadrantRegions[targetName] = {}
- local isMonitor = (window ~= term)
- if #sharedData.drawableItems == 0 then
- if pcall(window.setTextScale, 1) then end
- local msg = "Scanning for Mekanism Devices..."; window.setCursorPos(math.floor((w - #msg)/2) + 1, math.floor(h/2)); window.setTextColor(colors.yellow); window.write(msg)
- else
- local itemsToDraw = {}
- if not isMonitor then
- local startIndex = (currentPage - 1) * itemsPerPage + 1
- for i = 0, itemsPerPage - 1 do
- if startIndex + i <= #sharedData.drawableItems then
- table.insert(itemsToDraw, sharedData.drawableItems[startIndex + i])
- end
- end
- end
- if isMonitor then
- if #sharedData.drawableItems > 1 then
- pcall(function() window.setTextScale(0.5) end)
- else
- pcall(function() window.setTextScale(1) end)
- end
- else
- pcall(function() window.setTextScale(1) end)
- end
- local title = " Mekanism Device Monitor "; window.setCursorPos(math.max(1, math.floor((w - #title) / 2) + 1), 1); window.setTextColor(colors.yellow); window.write(title)
- local contentX = 2
- local contentY = 2
- local contentW = w - 2
- local contentH = h - 3
- if isMonitor then
- if #sharedData.drawableItems == 1 then
- drawDeviceQuadrant(window, targetName, sharedData.drawableItems[1], contentX, contentY, contentW, contentH)
- else
- local qW = math.floor(contentW / 2)
- local qH = math.floor(contentH / 2)
- local pageLayout = sharedData.pagedItemsMon[currentPage]
- if pageLayout then
- for _, panelData in ipairs(pageLayout) do
- local layout = panelData.layout
- local drawX = (layout.gridX == 1) and contentX or (contentX + qW)
- local drawY = (layout.gridY == 1) and contentY or (contentY + qH)
- local drawW = (layout.gridX == 2) and (contentW - qW) or qW
- local drawH = qH
- if layout.gridY == 2 then
- drawH = contentH - qH
- end
- if layout.gridH > 1 then
- drawH = contentH
- end
- drawDeviceQuadrant(window, targetName, panelData.item, drawX, drawY, drawW, drawH)
- end
- end
- end
- else
- drawDeviceQuadrant(window, targetName, itemsToDraw[1], contentX, contentY, contentW, contentH)
- end
- if totalPages > 1 then
- local pageY = h
- if isMonitor then
- local elements = {}
- local maxVisiblePages = math.floor((w - 20) / 4)
- table.insert(elements, { text = "< ", type = "prev" })
- if totalPages <= maxVisiblePages then
- for i = 1, totalPages do
- table.insert(elements, { text = (i == currentPage) and ("["..i.."]") or (" "..i.." "), type = "page", pageNum = i})
- end
- else
- local showPages = {}
- table.insert(showPages, 1)
- if currentPage > 3 then table.insert(showPages, -1) end
- for i = -1, 1 do
- if currentPage + i > 1 and currentPage + i < totalPages then
- table.insert(showPages, currentPage + i)
- end
- end
- if currentPage < totalPages - 2 then table.insert(showPages, -1) end
- table.insert(showPages, totalPages)
- local uniquePages = {}; local seen = {}
- for _, p in ipairs(showPages) do if not seen[p] then table.insert(uniquePages, p); seen[p] = true end end
- for _, p in ipairs(uniquePages) do
- if p == -1 then
- table.insert(elements, { text = "... ", type = "ellipsis" })
- else
- table.insert(elements, { text = (p == currentPage) and ("["..p.."]") or (" "..p.." "), type = "page", pageNum = p})
- end
- end
- end
- table.insert(elements, { text = " >", type = "next" })
- local totalBarW = 0
- for _, elData in ipairs(elements) do totalBarW = totalBarW + #elData.text end
- local cDX = math.max(1, math.floor((w - totalBarW) / 2) + 1)
- for _, elData in ipairs(elements) do
- if elData.type ~= "ellipsis" then
- window.setTextColor((elData.type == "page" and elData.pageNum == currentPage) and colors.yellow or colors.white)
- window.setCursorPos(cDX, pageY-1); window.write(elData.text)
- table.insert(sharedData.pageButtonRegions, { type = elData.type, target = targetName, pageNum = elData.pageNum, xStart = cDX, xEnd = cDX + #elData.text - 1, pageY = pageY-1 })
- else
- window.setTextColor(colors.white)
- window.setCursorPos(cDX, pageY-1); window.write(elData.text)
- end
- cDX = cDX + #elData.text
- end
- else
- local statusText = string.format("Page %d of %d", currentPage, totalPages)
- local statusX = math.floor((w - #statusText) / 2) + 1
- window.setCursorPos(statusX, pageY - 1)
- window.setTextColor(colors.yellow)
- window.write(statusText)
- window.setTextColor(colors.white)
- if targetName == "term" and #sharedData.drawableItems >= 2 then
- local prevText = "< "
- local nextText = " >"
- local goTxt = " [Go] "
- local totalWidth = #prevText + #goTxt + #nextText + 2
- local cDX = math.floor((w - totalWidth) / 2) + 1
- window.setCursorPos(cDX, pageY); window.write(prevText)
- table.insert(sharedData.pageButtonRegions, { type = "prev", target = targetName, xStart = cDX, xEnd = cDX + #prevText - 1, pageY = pageY })
- cDX = cDX + #prevText + 1
- window.setCursorPos(cDX, pageY); window.setBackgroundColor(colors.blue); window.write(goTxt)
- table.insert(sharedData.pageButtonRegions, {type = "goto", target = targetName, xStart=cDX, xEnd=cDX+#goTxt-1, pageY=pageY})
- cDX = cDX + #goTxt + 1
- window.setBackgroundColor(colors.black)
- window.setCursorPos(cDX, pageY); window.write(nextText)
- table.insert(sharedData.pageButtonRegions, { type = "next", target = targetName, xStart = cDX, xEnd = cDX + #nextText - 1, pageY = pageY })
- else
- local prevText = "< "
- local nextText = " >"
- local fullBarText = prevText .. " " .. nextText
- local cDX = math.floor((w - #fullBarText) / 2) + 1
- window.setCursorPos(cDX, pageY); window.write(prevText)
- table.insert(sharedData.pageButtonRegions, { type = "prev", target = targetName, xStart = cDX, xEnd = cDX + #prevText - 1, pageY = pageY })
- cDX = cDX + #prevText + string.len(" ")
- window.setCursorPos(cDX, pageY); window.write(nextText)
- table.insert(sharedData.pageButtonRegions, { type = "next", target = targetName, xStart = cDX, xEnd = cDX + #nextText - 1, pageY = pageY })
- end
- end
- end
- end
- local cfgTxt=" CONFIG "; local endTxt=" END ";
- window.setCursorPos(1,h); window.setBackgroundColor(colors.yellow); window.setTextColor(colors.black); window.write(cfgTxt)
- table.insert(sharedData.pageButtonRegions,{type="config",target=targetName,xStart=1,xEnd=#cfgTxt,pageY=h})
- window.setCursorPos(w-#endTxt+1,h); window.setBackgroundColor(colors.red); window.setTextColor(colors.black); window.write(endTxt)
- table.insert(sharedData.pageButtonRegions,{type="end",target=targetName,xStart=w-#endTxt+1,xEnd=w,pageY=h})
- window.setBackgroundColor(colors.black)
- end
- -- =================================================================
- -- Core Tasks
- -- =================================================================
- local function fetchSingleDeviceData(devInfo)
- local devP=devInfo.peripheral; local devType=devInfo.type; local s,d
- if devType == "turbine" then
- s,d=pcall(function() local sD=devP.getSteam(); local sA=0; if sD and type(sD)=="table" and sD.amount then sA=sD.amount end; local eS,mE,eF,pR,mP,sC,sF,iF,dM,w,l,h = devP.getEnergy(),devP.getMaxEnergy(),devP.getEnergyFilledPercentage(),devP.getProductionRate(),devP.getMaxProduction(),devP.getSteamCapacity(),devP.getSteamFilledPercentage(),devP.isFormed(),devP.getDumpingMode(),devP.getWidth(),devP.getLength(),devP.getHeight(); return {energyStored=(eS or 0)/joulesToFeConversionFactor,maxEnergy=(mE or 0)/joulesToFeConversionFactor,energyFilledPercentage=eF,productionRate=(pR or 0)/joulesToFeConversionFactor,maxProduction=(mP or 0)/joulesToFeConversionFactor,steamAmount=sA,steamCapacity=sC,steamFilledPercentage=sF,isFormed=iF,dumpingMode=dM,width=w,length=l,height=h} end)
- elseif devType == "fusionReactor" then
- s,d=pcall(function() local w=devP.getWater();local st=devP.getSteam();local dtF=devP.getDTFuel();local deu=devP.getDeuterium();local tri=devP.getTritium();local fD={};if dtF and dtF.name~="mekanism:empty"and(dtF.amount or 0)>0 then fD.hasDTFuel=true;fD.dtFuelAmount=dtF.amount or 0;fD.dtFuelCapacity=devP.getDTFuelCapacity();fD.dtFuelFilledPercentage=devP.getDTFuelFilledPercentage()else fD.hasDTFuel=false;fD.deuteriumAmount=deu.amount or 0;fD.deuteriumCapacity=devP.getDeuteriumCapacity();fD.deuteriumFilledPercentage=devP.getDeuteriumFilledPercentage();fD.tritiumAmount=tri.amount or 0;fD.tritiumCapacity=devP.getTritiumCapacity();fD.tritiumFilledPercentage=devP.getTritiumFilledPercentage()end;return{waterAmount=(w and w.amount or 0),waterCapacity=devP.getWaterCapacity(),waterFilledPercentage=devP.getWaterFilledPercentage(),steamAmount=(st and st.amount or 0),steamCapacity=devP.getSteamCapacity(),steamFilledPercentage=devP.getSteamFilledPercentage(),fuel=fD,caseTemp=devP.getCaseTemperature(),plasmaTemp=devP.getPlasmaTemperature(),injectionRate=devP.getInjectionRate(),productionRate=(devP.getProductionRate()or 0)/joulesToFeConversionFactor,isIgnited=devP.isIgnited()}end)
- elseif devType == "inductionMatrix" then
- s,d=pcall(function()
- local stats={}
- stats.isFormed = devP.isFormed()
- -- REMOVED: stats.mode = devP.getMode() (This was causing the crash)
- stats.energyFE = (devP.getEnergy() or 0) / joulesToFeConversionFactor
- stats.maxEnergyFE = (devP.getMaxEnergy() or 0) / joulesToFeConversionFactor
- stats.energyFilledPercentage = devP.getEnergyFilledPercentage() or 0
- -- Calculate I/O Rate
- local lastInput = devP.getLastInput() or 0
- local lastOutput = devP.getLastOutput() or 0
- stats.energyChangeRatePerTick = (lastInput - lastOutput) / joulesToFeConversionFactor
- -- Get structural info
- stats.installedCells = devP.getInstalledCells and devP.getInstalledCells() or "N/A"
- stats.installedProviders = devP.getInstalledProviders and devP.getInstalledProviders() or "N/A"
- if stats.isFormed then
- stats.width = devP.getWidth()
- stats.height = devP.getHeight()
- stats.length = devP.getLength()
- end
- return stats
- end)
- elseif devType == "boiler" then
- s,d=pcall(function() local waterStack=devP.getWater(); local steamStack=devP.getSteam(); return {isFormed=devP.isFormed(),temperature=devP.getTemperature(),waterAmount=waterStack.amount or 0,waterCapacity=devP.getWaterCapacity(),waterFilledPercentage=devP.getWaterFilledPercentage(),steamAmount=steamStack.amount or 0,steamCapacity=devP.getSteamCapacity(),steamFilledPercentage=devP.getSteamFilledPercentage(),boilRate=devP.getBoilRate(),maxBoilRate=devP.getMaxBoilRate(),envLoss=devP.getEnvironmentalLoss()} end)
- elseif devType == "thermalEvaporation" then
- s,d=pcall(function() local data={}; data.isFormed=devP.isFormed(); if data.isFormed then local iF=devP.getInput();local oF=devP.getOutput(); data.temperature=devP.getTemperature();data.height=devP.getHeight(); data.inputName=iF.name;data.inputAmount=iF.amount or 0;data.inputCapacity=devP.getInputCapacity();data.inputFilledPercentage=devP.getInputFilledPercentage(); data.outputName=oF.name;data.outputAmount=oF.amount or 0;data.outputCapacity=devP.getOutputCapacity();data.outputFilledPercentage=devP.getOutputFilledPercentage(); data.production=devP.getProductionAmount(); else for k,v in pairs({temperature=0,height=0,inputName="mekanism:empty",inputAmount=0,inputCapacity=0,inputFilledPercentage=0,outputName="mekanism:empty",outputAmount=0,outputCapacity=0,outputFilledPercentage=0,production=0})do data[k]=v end end; return data end)
- elseif devType == "dynamicTank" then
- s,d=pcall(function()
- local stats = {}
- stats.isFormed = devP.isFormed()
- stats.stored = devP.getStored()
- local isChemical = stats.stored and stats.stored.name and not string.find(stats.stored.name, "minecraft:") and string.find(stats.stored.name, ":")
- if isChemical then
- stats.capacity = devP.getChemicalTankCapacity() or 0
- else
- local s_fluid, fluid_cap = pcall(devP.getTankCapacity)
- if s_fluid then
- stats.capacity = fluid_cap
- else
- stats.capacity = devP.getChemicalTankCapacity() or 0
- end
- end
- stats.filledPercentage = (stats.stored and stats.capacity > 0 and stats.stored.amount) and stats.stored.amount / stats.capacity or 0
- if stats.isFormed then
- stats.width=devP.getWidth()
- stats.height=devP.getHeight()
- stats.length=devP.getLength()
- end
- stats.editMode = devP.getContainerEditMode()
- return stats
- end)
- elseif devType == "sps" then
- s,d=pcall(function()
- local stats = {}
- stats.isFormed = devP.isFormed()
- stats.input = devP.getInput()
- stats.inputCapacity = devP.getInputCapacity()
- stats.inputFilledPercentage = devP.getInputFilledPercentage()
- stats.output = devP.getOutput()
- stats.outputCapacity = devP.getOutputCapacity()
- stats.outputFilledPercentage = devP.getOutputFilledPercentage()
- stats.processRate = devP.getProcessRate()
- if stats.isFormed then
- stats.coils = devP.getCoils()
- stats.width = devP.getWidth()
- stats.height = devP.getHeight()
- stats.length = devP.getLength()
- end
- return stats
- end)
- elseif devType == "fissionReactor" then
- s,d=pcall(function()
- local stats = {}
- stats.isFormed = devP.isFormed()
- if stats.isFormed then
- stats.temperature = tonumber(devP.getTemperature()) or 0
- stats.damagePercent = tonumber(devP.getDamagePercent()) or 0
- stats.burnRate = tonumber(devP.getBurnRate()) or 0
- stats.maxBurnRate = tonumber(devP.getFuelAssemblies()) or 0
- stats.actualBurnRate = tonumber(devP.getActualBurnRate()) or 0
- stats.fuel = devP.getFuel() or {amount = 0}
- stats.fuelCapacity = tonumber(devP.getFuelCapacity()) or 1
- stats.fuelFilledPercentage = tonumber(devP.getFuelFilledPercentage()) or 0
- stats.coolant = devP.getCoolant() or {amount = 0}
- stats.coolantCapacity = tonumber(devP.getCoolantCapacity()) or 1
- stats.coolantFilledPercentage = tonumber(devP.getCoolantFilledPercentage()) or 0
- stats.heatedCoolant = devP.getHeatedCoolant() or {amount = 0}
- stats.heatedCoolantCapacity = tonumber(devP.getHeatedCoolantCapacity()) or 1
- stats.heatedCoolantFilledPercentage = tonumber(devP.getHeatedCoolantFilledPercentage()) or 0
- stats.waste = devP.getWaste() or {amount = 0}
- stats.wasteCapacity = tonumber(devP.getWasteCapacity()) or 1
- stats.wasteFilledPercentage = tonumber(devP.getWasteFilledPercentage()) or 0
- stats.heatCapacity = tonumber(devP.getHeatCapacity()) or 1
- stats.heatingRate = tonumber(devP.getHeatingRate()) or 0
- if sharedData.scrammingReactors[devInfo.name] then
- stats.status = "SCRAM"
- elseif stats.damagePercent >= 100 then
- stats.status = "MELTDOWN"
- elseif stats.actualBurnRate > 0 then
- stats.status = "ACTIVE"
- else
- stats.status = "IDLE"
- end
- else
- stats.temperature = 0; stats.damagePercent = 0; stats.burnRate = 0; stats.maxBurnRate = 0; stats.actualBurnRate = 0;
- stats.fuel = {amount = 0}; stats.fuelCapacity = 1; stats.fuelFilledPercentage = 0;
- stats.coolant = {amount = 0}; stats.coolantCapacity = 1; stats.coolantFilledPercentage = 0;
- stats.waste = {amount = 0}; stats.wasteCapacity = 1; stats.wasteFilledPercentage = 0;
- stats.status = "UNFORMED"; stats.heatCapacity = 1; stats.heatingRate = 0;
- end
- return stats
- end)
- end
- if s then sharedData.deviceStats[devInfo.name]=d; sharedData.lastError[devInfo.name]=nil else sharedData.lastError[devInfo.name]="Fetch Error"; debugPrint("[ERROR] Fetching "..devInfo.name..": "..tostring(d)) end
- end
- local function fetchTask()
- while sharedData.programRunning do
- local fetchFunctions = {}
- for _, devInfo in ipairs(sharedData.foundDevices) do
- table.insert(fetchFunctions, function() fetchSingleDeviceData(devInfo) end)
- end
- if #fetchFunctions > 0 then
- parallel.waitForAll(unpack(fetchFunctions))
- end
- for _, devInfo in ipairs(sharedData.foundDevices) do
- if devInfo.type == "fissionReactor" then
- local devName = devInfo.name
- local stats = sharedData.deviceStats[devName]
- if stats and not sharedData.scrammingReactors[devName] and stats.actualBurnRate > 0 then
- local needsScram = false
- local reason = ""
- if stats.coolantFilledPercentage and stats.coolantFilledPercentage <= fissionLowCoolantPercent then
- needsScram = true
- reason = "low coolant"
- elseif stats.temperature > fissionCriticalTemp then
- needsScram = true
- reason = "critical temperature"
- elseif stats.damagePercent > fissionCriticalDamage then
- needsScram = true
- reason = "critical damage"
- end
- if needsScram then
- local dev = peripheral.wrap(devName)
- if dev then
- pcall(dev.scram)
- sharedData.scrammingReactors[devName] = true
- term.setCursorPos(1,1); term.clearLine(); term.setTextColor(colors.red)
- print("[CRITICAL] Fission Reactor "..devName.." auto-scrammed: "..reason.."!")
- term.setTextColor(colors.white)
- end
- end
- end
- if sharedData.scrammingReactors[devName] and stats and (stats.temperature < fissionScramResetTempK or stats.coolantFilledPercentage >= fissionSafeCoolantPercent) then
- sharedData.scrammingReactors[devName] = nil
- end
- end
- end
- local T={energyStored=0,maxEnergy=0,productionRate=0,maxProduction=0,steamAmount=0,steamCapacity=0,formedCount=0,totalTurbineCount=0};local fTM=nil;local aTMS=true;local tFFMC=false
- for _,dI in ipairs(sharedData.foundDevices)do local S=sharedData.deviceStats[dI.name];if S and dI.type=="turbine"then T.totalTurbineCount=T.totalTurbineCount+1;T.energyStored=T.energyStored+(S.energyStored or 0);T.maxEnergy=T.maxEnergy+(S.maxEnergy or 0);T.productionRate=T.productionRate+(S.productionRate or 0);T.maxProduction=T.maxProduction+(S.maxProduction or 0);T.steamAmount=T.steamAmount+(S.steamAmount or 0);T.steamCapacity=T.steamCapacity+(S.steamCapacity or 0);if S.isFormed then T.formedCount=T.formedCount+1 end;if not tFFMC then fTM=S.dumpingMode;tFFMC=true elseif aTMS and S.dumpingMode~=fTM then aTMS=false end elseif not S and dI.type=="turbine"then aTMS=false end end;if T.totalTurbineCount==0 then aTMS=false end;if aTMS and fTM~=nil then sharedData.globallyConsistentTurbineMode=fTM else sharedData.globallyConsistentTurbineMode=nil end;if T.maxEnergy>0 then T.energyFilledPercentage=T.energyStored/T.maxEnergy else T.energyFilledPercentage=0 end;if T.steamCapacity>0 then T.steamFilledPercentage=T.steamAmount/T.steamCapacity else T.steamFilledPercentage=0 end;sharedData.overallTurbineStats=T
- sharedData.needsRedraw = true
- for i = 1, math.ceil(updateInterval / 0.1) do
- if not sharedData.programRunning then break end
- sleep(0.1)
- end
- end
- end
- local function drawGoToMenu(window, totalPages)
- local w, h = window.getSize()
- local numPages = totalPages
- local pagesPerColumn = math.ceil(numPages / 2)
- local menuHeight = pagesPerColumn + 4
- local menuWidth = 20
- local startX = math.floor((w - menuWidth) / 2) + 1
- local startY = math.floor((h - menuHeight) / 2) + 1
- -- Draw the menu background and border
- window.setBackgroundColor(colors.black)
- for y = startY, startY + menuHeight - 1 do
- window.setCursorPos(startX, y)
- window.write(string.rep(" ", menuWidth))
- end
- drawQuadrantBorder(window, startX, startY, menuWidth, menuHeight, "cyan", "Go to Page", "black")
- -- Draw the page number buttons
- local col1X = startX + 3
- local col2X = startX + 11
- window.setTextColor(colors.white)
- for i = 1, pagesPerColumn do
- local page1 = i
- local page2 = i + pagesPerColumn
- if page1 <= numPages then
- window.setCursorPos(col1X, startY + 1 + i)
- window.write(string.format("Page %-3d", page1))
- table.insert(sharedData.pageButtonRegions, { type = "goto_page", page = page1, target = "term", pageY = startY + 1 + i, xStart = col1X, xEnd = col1X + 8 })
- end
- if page2 <= numPages then
- window.setCursorPos(col2X, startY + 1 + i)
- window.write(string.format("Page %-3d", page2))
- table.insert(sharedData.pageButtonRegions, { type = "goto_page", page = page2, target = "term", pageY = startY + 1 + i, xStart = col2X, xEnd = col2X + 8 })
- end
- end
- -- Draw the close button
- local closeText = "[ Close ]"
- local closeX = startX + math.floor((menuWidth - #closeText) / 2)
- local closeY = startY + menuHeight - 2
- window.setCursorPos(closeX, closeY)
- window.setBackgroundColor(colors.red)
- window.setTextColor(colors.white)
- window.write(closeText)
- table.insert(sharedData.pageButtonRegions, { type = "goto_close", target = "term", pageY = closeY, xStart = closeX, xEnd = closeX + #closeText - 1 })
- window.setBackgroundColor(colors.black)
- end
- local function displayTask()
- local allWindows = { term = term }
- for _, mon in ipairs(monitors) do
- allWindows[peripheral.getName(mon)] = mon
- end
- while sharedData.programRunning do
- if sharedData.needsRedraw then
- -- [FIX] Save old buttons to backup so clicks still work during the redraw pause
- if sharedData.pageButtonRegions and #sharedData.pageButtonRegions > 0 then
- sharedData.backupRegions = sharedData.pageButtonRegions
- end
- sharedData.pageButtonRegions = {}
- if sharedData.isMenuOpen then
- drawConfigurationMenu()
- elseif sharedData.isGoToMenuOpen then
- drawGoToMenu(term, sharedData.totalPagesTerm)
- else
- calculateMonitorPages()
- sharedData.flashToggle = not sharedData.flashToggle
- local winCount = 0
- for targetName, window in pairs(allWindows) do
- if window then
- local isMonitor = (window ~= term)
- local itemsPerPage = isMonitor and devicesPerPageMonitor or devicesPerPageTerminal
- local currentPage = isMonitor and sharedData.monitorStates[targetName].currentPage or sharedData.currentPageTerm
- local totalPages = isMonitor and sharedData.totalPagesMon or sharedData.totalPagesTerm
- pcall(drawScreen, window, targetName, itemsPerPage, currentPage, totalPages)
- winCount = winCount + 1
- if winCount % 2 == 0 then sleep(0) end
- end
- end
- end
- sharedData.needsRedraw = false
- end
- if sharedData.keypad.isOpen then
- local targetName = sharedData.keypad.targetWindow
- local window = (targetName == "term") and term or allWindows[targetName]
- if window then
- -- Clear keypad regions from previous draw to prevent ghost clicks
- for i = #sharedData.pageButtonRegions, 1, -1 do
- if string.find(sharedData.pageButtonRegions[i].type, "keypad_") then
- table.remove(sharedData.pageButtonRegions, i)
- end
- end
- drawKeypad(window)
- end
- end
- sleep(displayRefreshDelay)
- end
- end
- local function actionQueueProcessorTask()
- while sharedData.programRunning do
- if #sharedData.actionQueue > 0 then
- local task = table.remove(sharedData.actionQueue, 1)
- if task.type == "fusion_ignite" then
- if sharedData.config.useMoreRed then
- setBundledBit(sharedData.config.moreRedOutputSide, sharedData.config.fusionPowerDownColor, false)
- pulseBundledRedstone(sharedData.config.moreRedOutputSide, sharedData.config.fusionIgnitionColor)
- else
- redstone.setOutput(sharedData.config.fusionPowerDownSide, false)
- pulseRedstone(sharedData.config.fusionIgnitionSide)
- end
- elseif task.type == "fusion_power_down" then
- if sharedData.config.useMoreRed then
- setBundledBit(sharedData.config.moreRedOutputSide, sharedData.config.fusionPowerDownColor, task.state)
- else
- redstone.setOutput(sharedData.config.fusionPowerDownSide, task.state)
- end
- local dev = peripheral.wrap(task.deviceName)
- if dev then pcall(dev.setInjectionRate, 0) end
- elseif task.type == "fusion_injection" then
- if sharedData.config.useMoreRed then
- setBundledBit(sharedData.config.moreRedOutputSide, sharedData.config.fusionPowerDownColor, false)
- else
- redstone.setOutput(sharedData.config.fusionPowerDownSide, false)
- end
- local dev = peripheral.wrap(task.deviceName)
- if dev then
- local s, cRate = pcall(dev.getInjectionRate)
- if s and cRate then
- local newRate = cRate
- if task.action=="set_min"then newRate=minInjectionRate
- elseif task.action=="set_max"then newRate=maxInjectionRate
- elseif task.action=="inc_rate"then newRate=math.min(maxInjectionRate,cRate+injectionRateStep)
- elseif task.action=="dec_rate"then newRate=math.max(minInjectionRate,cRate-injectionRateStep)
- end
- if newRate ~= cRate then pcall(dev.setInjectionRate, newRate) end
- end
- end
- elseif task.type == "fission_control" then
- local dev = peripheral.wrap(task.deviceName)
- if dev then
- if task.action == "scram" then
- pcall(dev.scram); sharedData.scrammingReactors[task.deviceName] = true
- elseif task.action == "cancel_scram" then
- sharedData.scrammingReactors[task.deviceName] = nil
- elseif task.action == "activate" then
- pcall(dev.activate); sharedData.scrammingReactors[task.deviceName] = nil
- elseif task.action == "set_rate" then
- pcall(dev.setBurnRate, task.value)
- else
- local s, cRate = pcall(dev.getBurnRate)
- if s and cRate then
- local newRate = cRate
- if task.action == "inc_rate_01" then newRate = cRate + 0.1
- elseif task.action == "inc_rate_1" then newRate = cRate + 1.0
- elseif task.action == "inc_rate_10" then newRate = cRate + 10.0
- elseif task.action == "dec_rate_01" then newRate = cRate - 0.1
- elseif task.action == "dec_rate_1" then newRate = cRate - 1.0
- elseif task.action == "dec_rate_10" then newRate = cRate - 10.0
- end
- pcall(dev.setBurnRate, newRate)
- end
- end
- end
- end
- sleep(0.05)
- else
- sleep(0.05)
- end
- end
- end
- local function setupSirenAudio()
- if #sharedData.speakers == 0 then return end
- local function generateSlidingWave(start_freq, end_freq, duration)
- local rate = 48000
- local bufferSize = math.floor(rate * duration)
- local buffer = {}
- local t = 0
- for i = 1, bufferSize do
- local progress = i / bufferSize
- local current_freq = start_freq + (end_freq - start_freq) * progress
- local dt = 2 * math.pi * current_freq / rate
- t = (t + dt) % (math.pi * 2)
- buffer[i] = math.floor(math.sin(t) * 127)
- end
- return buffer
- end
- local whoopUp = generateSlidingWave(400, 800, 0.25)
- local whoopDown = generateSlidingWave(800, 400, 0.25)
- sharedData.sirenBuffers.alarm = {}
- for i=1, #whoopUp do table.insert(sharedData.sirenBuffers.alarm, whoopUp[i]) end
- for i=1, #whoopDown do table.insert(sharedData.sirenBuffers.alarm, whoopDown[i]) end
- end
- local function sirenTask()
- local lastAlarmState = false
- while sharedData.programRunning do
- local isScramActive = countKeys(sharedData.scrammingReactors) > 0
- if sharedData.config.useIndustrialAlarm then
- if isScramActive ~= lastAlarmState then
- if sharedData.config.useMoreRed then
- setBundledBit(sharedData.config.moreRedOutputSide, sharedData.config.industrialAlarmColor, isScramActive)
- else
- redstone.setOutput(sharedData.config.industrialAlarmSide, isScramActive)
- end
- lastAlarmState = isScramActive
- end
- for i = 1, 5 do
- if not sharedData.programRunning then break end
- sleep(0.1)
- end
- else
- if isScramActive then
- local audioTasks = {}
- for _, speaker in ipairs(sharedData.speakers) do
- table.insert(audioTasks, function()
- speaker.playAudio(sharedData.sirenBuffers.alarm)
- end)
- end
- if #audioTasks > 0 then
- parallel.waitForAll(unpack(audioTasks))
- end
- for i = 1, 15 do
- if not sharedData.programRunning then break end
- sleep(0.1)
- end
- else
- for i = 1, 5 do
- if not sharedData.programRunning then break end
- sleep(0.1)
- end
- end
- end
- end
- end
- local function eventListenerTask()
- while sharedData.programRunning do
- local event, p1, p2, p3, p4 = os.pullEvent()
- if event == "key" and p1 == keys.terminate then
- sharedData.programRunning = false
- break
- elseif event == "term_resize" then
- sharedData.needsRedraw = true
- end
- if sharedData.keypad.isOpen then
- if event == "mouse_click" or event == "monitor_touch" then
- local clickType, clickX, clickY
- if event == "mouse_click" then clickType, clickX, clickY = "term", p2, p3
- else clickType, clickX, clickY = p1, p2, p3 end
- for i = #sharedData.pageButtonRegions, 1, -1 do
- local region = sharedData.pageButtonRegions[i]
- if region and region.target == clickType and clickY >= (region.pageY or region.yStart or region.y) and clickY <= (region.pageY or region.yEnd or region.y) and clickX >= (region.xStart or region.x1) and clickX <= (region.xEnd or region.x2) then
- if region.type == "keypad_close" then
- sharedData.keypad.isOpen = false
- sharedData.needsRedraw = true
- break
- elseif region.type == "keypad_input" then
- if #sharedData.keypad.inputValue < 10 then
- sharedData.keypad.inputValue = sharedData.keypad.inputValue .. region.value
- sharedData.keypad.message = ""
- end
- break
- elseif region.type == "keypad_backspace" then
- sharedData.keypad.inputValue = string.sub(sharedData.keypad.inputValue, 1, -2)
- sharedData.keypad.message = ""
- break
- elseif region.type == "keypad_clear" then
- sharedData.keypad.inputValue = ""
- sharedData.keypad.message = ""
- break
- elseif region.type == "keypad_confirm" then
- local val = tonumber(sharedData.keypad.inputValue)
- if val and val >= 0 then
- table.insert(sharedData.actionQueue, {
- type = "fission_control",
- deviceName = sharedData.keypad.targetDevice,
- action = "set_rate",
- value = val
- })
- sharedData.keypad.isOpen = false
- sharedData.needsRedraw = true
- else
- sharedData.keypad.message = "Invalid number!"
- sharedData.keypad.inputValue = ""
- end
- break
- end
- end
- end
- end
- elseif sharedData.isMenuOpen then
- -- Config menu handling
- if event == "mouse_up" then
- sharedData.configMenu.dragging = nil
- elseif event == "mouse_drag" then
- if sharedData.configMenu.dragging then
- local dragY = p3
- local yDelta = dragY - sharedData.configMenu.dragging.initialMouseY
- local offsetDelta = (yDelta / sharedData.configMenu.dragging.trackH) * sharedData.configMenu.dragging.maxOffset
- local newOffset = sharedData.configMenu.dragging.initialOffset + offsetDelta
- local clamped = math.floor(math.max(0, math.min(newOffset, sharedData.configMenu.dragging.maxOffset)) + 0.5)
- if sharedData.configMenu.scrollState.offset ~= clamped then
- sharedData.configMenu.scrollState.offset = clamped
- sharedData.needsRedraw = true
- end
- end
- elseif event == "mouse_scroll" and sharedData.configMenu.page == 4 and sharedData.configMenu.scrollState.maxOffset > 0 then
- local scrollDir = p1
- if scrollDir then
- local newOffset = sharedData.configMenu.scrollState.offset + scrollDir
- local clamped = math.max(0, math.min(sharedData.configMenu.scrollState.maxOffset, newOffset))
- if sharedData.configMenu.scrollState.offset ~= clamped then
- sharedData.configMenu.scrollState.offset = clamped
- sharedData.needsRedraw = true
- end
- end
- elseif event == "mouse_click" or event == "monitor_touch" then
- local clickType, clickX, clickY
- if event == "mouse_click" then clickType, clickX, clickY = "term", p2, p3
- else clickType, clickX, clickY = p1, p2, p3 end
- for i = #sharedData.pageButtonRegions, 1, -1 do
- local r = sharedData.pageButtonRegions[i]
- local yStart = r.y or r.yStart or r.pageY
- local yEnd = r.y or r.yEnd or r.pageY
- local xStart = r.x1 or r.xStart
- local xEnd = r.x2 or r.xEnd
- if r and r.target == clickType and clickY >= yStart and clickY <= yEnd and clickX >= xStart and clickX <= xEnd then
- sharedData.needsRedraw = true
- if r.type == "config_scroll_handle" then
- sharedData.configMenu.dragging = {
- initialMouseY = clickY,
- initialOffset = sharedData.configMenu.scrollState.offset,
- trackH = r.trackH,
- maxOffset = r.maxOffset
- }
- sharedData.needsRedraw = false
- break
- elseif r.type == "config_scroll" then
- local newOffset = sharedData.configMenu.scrollState.offset + r.direction
- sharedData.configMenu.scrollState.offset = math.max(0, math.min(newOffset, r.maxOffset))
- break
- elseif r.type == "nav_next" then
- sharedData.configMenu.page = sharedData.configMenu.page + 1
- sharedData.configMenu.scrollState.offset = 0
- break
- elseif r.type == "nav_back" then
- sharedData.configMenu.page = sharedData.configMenu.page - 1
- sharedData.configMenu.scrollState.offset = 0
- break
- elseif r.type == "save" then
- sharedData.config = sharedData.configMenu.tempConfig
- saveConfig()
- sharedData.isMenuOpen = false
- reinitializeLayout()
- break
- elseif r.type == "quit" then
- sharedData.programRunning = false
- break
- elseif r.type == "temp" then sharedData.configMenu.tempConfig.tempUnit = r.unit; break
- elseif r.type == "border" then sharedData.configMenu.tempConfig.useColoredBorders = r.value; break
- elseif r.type == "use_morered" then sharedData.configMenu.tempConfig.useMoreRed = r.value; break
- elseif r.type == "use_alarm" then sharedData.configMenu.tempConfig.useIndustrialAlarm = r.value; break
- elseif r.type == "set_side" then sharedData.configMenu.tempConfig[r.key] = r.value; break
- elseif r.type == "set_color" then sharedData.configMenu.tempConfig[r.key] = r.value; break
- elseif r.type == "enable_ignition" then sharedData.configMenu.tempConfig.enableFusionIgnition = r.value; break
- elseif r.type == "enable_power_down" then sharedData.configMenu.tempConfig.enableFusionPowerDown = r.value; break
- elseif r.type == "order_up" and r.index > 1 then local t=sharedData.configMenu.tempConfig.deviceOrder[r.index]; sharedData.configMenu.tempConfig.deviceOrder[r.index]=sharedData.configMenu.tempConfig.deviceOrder[r.index-1]; sharedData.configMenu.tempConfig.deviceOrder[r.index-1]=t; break
- elseif r.type == "order_down" and r.index < #sharedData.configMenu.tempConfig.deviceOrder then local t=sharedData.configMenu.tempConfig.deviceOrder[r.index]; sharedData.configMenu.tempConfig.deviceOrder[r.index]=sharedData.configMenu.tempConfig.deviceOrder[r.index+1]; sharedData.configMenu.tempConfig.deviceOrder[r.index+1]=t; break
- else
- sharedData.needsRedraw = false
- end
- end
- end
- end
- else -- Not in menu or keypad
- if event == "mouse_scroll" then
- local scrollDir, scrollX, scrollY
- local targetName
- if type(p1) == "string" then
- targetName = p1; scrollDir = p2; scrollX = p3; scrollY = p4
- else
- targetName = "term"; scrollDir = p1; scrollX = p2; scrollY = p3
- end
- local region = nil
- for _, quad in ipairs(sharedData.quadrantRegions[targetName] or {}) do
- if scrollX >= quad.x1 and scrollX <= quad.x2 and scrollY >= quad.y1 and scrollY <= quad.y2 then
- region = quad
- break
- end
- end
- if region and region.deviceType == "fissionReactor" then
- local offset = sharedData.fissionScrollOffsets[region.deviceName] or 0
- local newOffset = offset + scrollDir
- local clampedOffset = math.max(0, math.min(newOffset, region.maxOffset or 0))
- if clampedOffset ~= offset then
- sharedData.fissionScrollOffsets[region.deviceName] = clampedOffset
- sharedData.needsRedraw = true
- end
- end
- elseif event == "mouse_click" or event == "monitor_touch" then
- local clickType, clickX, clickY
- if event == "mouse_click" then clickType, clickX, clickY = "term", p2, p3
- else clickType, clickX, clickY = p1, p2, p3 end
- -- [FIX] Check BOTH the main list and the backup list (for double-buffer effect)
- local listsToCheck = {sharedData.pageButtonRegions, sharedData.backupRegions}
- local actionTaken = false
- for _, currentList in ipairs(listsToCheck) do
- if actionTaken then break end -- Don't click twice if found
- if currentList then
- for i = #currentList, 1, -1 do
- local region = currentList[i]
- local yStart = region.pageY or region.yStart
- local yEnd = region.pageY or region.yEnd
- local xStart = region.x1 or region.xStart
- local xEnd = region.x2 or region.xEnd
- if region and region.target == clickType and clickY >= yStart and clickY <= yEnd and clickX >= xStart and clickX <= xEnd then
- if region.type == "config" then
- sharedData.isMenuOpen = true
- sharedData.configMenu.page = 1
- sharedData.configMenu.scrollState = { offset = 0, maxOffset = 0 }
- sharedData.configMenu.tempConfig = table.copy(sharedData.config)
- sharedData.needsRedraw = true
- actionTaken = true; break
- elseif region.type == "end" then sharedData.programRunning = false; actionTaken = true; break
- elseif region.type == "dump_mode" then
- local dev = peripheral.wrap(region.turbineName); if dev then pcall(dev.setDumpingMode, region.modeToSet) end; sharedData.needsRedraw = true; actionTaken = true; break
- elseif region.type == "global_dump_mode" then
- for _,dI in ipairs(sharedData.foundDevices) do if dI.type=="turbine" then pcall(dI.peripheral.setDumpingMode, region.modeToSet) end end; sharedData.needsRedraw = true; actionTaken = true; break
- elseif region.type == "fusion_ignite" then
- table.insert(sharedData.actionQueue, { type = "fusion_ignite" }); actionTaken = true; break
- elseif region.type == "fusion_fuel_toggle" then
- table.insert(sharedData.actionQueue, { type = "fusion_power_down", deviceName = region.deviceName, state = true }); actionTaken = true; break
- elseif region.type == "set_injection_rate" then
- table.insert(sharedData.actionQueue, { type = "fusion_injection", deviceName = region.deviceName, action = region.action }); actionTaken = true; break
- elseif region.type == "fission_scroll" then
- local offset = sharedData.fissionScrollOffsets[region.deviceName] or 0
- local newOffset = offset + region.direction
- sharedData.fissionScrollOffsets[region.deviceName] = math.max(0, math.min(newOffset, region.maxOffset))
- sharedData.needsRedraw = true
- actionTaken = true; break
- elseif region.type == "container_edit_mode" then
- local dev = peripheral.wrap(region.deviceName);
- if dev then
- if region.action == "inc" then pcall(dev.incrementContainerEditMode)
- elseif region.action == "dec" then pcall(dev.decrementContainerEditMode) end
- end; sharedData.needsRedraw = true; actionTaken = true; break
- elseif region.type == "sps_redstone_toggle" then
- if sharedData.config.useMoreRed then
- setBundledBit(sharedData.config.moreRedOutputSide, sharedData.config.spsRedstoneColor, not (bit.band(redstone.getBundledOutput(sharedData.config.moreRedOutputSide), colors[sharedData.config.spsRedstoneColor]) > 0))
- else
- local side = sharedData.config.spsRedstoneControlSide
- local currentState = redstone.getOutput(side)
- redstone.setOutput(side, not currentState)
- end
- sharedData.needsRedraw = true; actionTaken = true; break
- elseif region.type == "fission_control" then
- table.insert(sharedData.actionQueue, {type="fission_control", deviceName=region.deviceName, action=region.action})
- sharedData.needsRedraw = true
- actionTaken = true; break
- elseif region.type == "fission_open_keypad" then
- sharedData.pageButtonRegions = {}
- sharedData.keypad.isOpen = true
- sharedData.keypad.targetDevice = region.deviceName
- sharedData.keypad.targetWindow = region.target
- sharedData.keypad.inputValue = ""
- sharedData.keypad.message = ""
- for _, quad in ipairs(sharedData.quadrantRegions[region.target] or {}) do
- if quad.deviceName == region.deviceName then
- sharedData.keypad.qx = quad.x1
- sharedData.keypad.qy = quad.y1
- sharedData.keypad.qW = quad.x2 - quad.x1 + 1
- sharedData.keypad.qH = quad.y2 - quad.y1 + 1
- break
- end
- end
- actionTaken = true; break
- elseif region.type == "goto" and region.target == "term" then
- sharedData.isGoToMenuOpen = true
- sharedData.needsRedraw = true
- actionTaken = true; break
- elseif region.type == "goto_page" then
- sharedData.currentPageTerm = region.page
- sharedData.isGoToMenuOpen = false
- sharedData.needsRedraw = true
- actionTaken = true; break
- elseif region.type == "goto_close" then
- sharedData.isGoToMenuOpen = false
- sharedData.needsRedraw = true
- actionTaken = true; break
- else
- local isMonitor = (region.target ~= "term")
- local currentPage = isMonitor and sharedData.monitorStates[region.target].currentPage or sharedData.currentPageTerm
- local totalPages = isMonitor and sharedData.totalPagesMon or sharedData.totalPagesTerm
- local newPage = currentPage
- if region.type == "prev" then newPage = currentPage - 1; if newPage < 1 then newPage = totalPages end
- elseif region.type == "next" then newPage = currentPage + 1; if newPage > totalPages then newPage = 1 end
- elseif region.type == "page" then newPage = region.pageNum end
- if newPage ~= currentPage then
- if isMonitor then sharedData.monitorStates[region.target].currentPage = newPage
- else sharedData.currentPageTerm = newPage end
- sharedData.needsRedraw = true
- end
- actionTaken = true; break
- end
- end
- end
- end
- end
- end
- end
- end
- end
- local function scannerTask()
- while sharedData.programRunning do
- if not sharedData.isMenuOpen then
- pcall(scanAndApplyChanges)
- end
- for i = 1, math.ceil(scanForNewDevicesInterval / 0.1) do
- if not sharedData.programRunning then break end
- sleep(0.1)
- end
- end
- debugPrint("Scanner task stopped.")
- end
- -- =================================================================
- -- Configuration and Main Loop
- -- =================================================================
- function saveConfig()
- local s = textutils.serialize(sharedData.config)
- local file, err = fs.open(configFile, "w")
- if file then
- file.write(s)
- file.close()
- debugPrint("Configuration saved.")
- return true
- else
- centeredPrint("ERROR: Could not save config file: " .. tostring(err))
- return false
- end
- end
- function scanAndApplyChanges()
- local peripherals = peripheral.getNames(); local allPeripheralNames = {}
- for _,pName in ipairs(peripherals) do allPeripheralNames[pName] = true end
- local changed = false
- for i = #sharedData.foundDevices, 1, -1 do
- local devInfo = sharedData.foundDevices[i]
- if not allPeripheralNames[devInfo.name] then
- debugPrint("-> Device removed: "..devInfo.type.." - "..devInfo.name)
- table.remove(sharedData.foundDevices, i)
- sharedData.deviceStats[devInfo.name] = nil
- sharedData.lastError[devInfo.name] = nil
- changed = true
- end
- end
- local currentDeviceNames = {}; for _, dev in ipairs(sharedData.foundDevices) do currentDeviceNames[dev.name] = true end
- for _, peripheralName in ipairs(peripherals) do
- if not currentDeviceNames[peripheralName] then
- local p = peripheral.wrap(peripheralName)
- if p and peripheral.getType(p) ~= "monitor" then
- local deviceType, isValid = nil, false
- if string.sub(peripheralName, 1, #turbineNamePrefix) == turbineNamePrefix then
- deviceType = "turbine"; local s,r = pcall(p.isFormed); if (s and r) or skipIsFormedValidation then isValid = true end
- elseif string.sub(peripheralName, 1, #fusionReactorNamePrefix) == fusionReactorNamePrefix then
- deviceType = "fusionReactor"; if pcall(p.getWaterCapacity) then isValid = true end
- elseif string.sub(peripheralName, 1, #fissionReactorNamePrefix) == fissionReactorNamePrefix then
- deviceType = "fissionReactor"; if pcall(p.getFuelAssemblies) then isValid = true end
- elseif string.sub(peripheralName, 1, #inductionMatrixNamePrefix) == inductionMatrixNamePrefix then
- deviceType = "inductionMatrix"; if pcall(p.getMaxEnergy) then isValid = true end
- elseif string.sub(peripheralName, 1, #boilerNamePrefix) == boilerNamePrefix then
- deviceType = "boiler"; if pcall(p.getTemperature) then isValid = true end
- elseif string.sub(peripheralName, 1, #thermalEvaporationNamePrefix) == thermalEvaporationNamePrefix then
- deviceType = "thermalEvaporation"; if pcall(p.isFormed) then isValid = true end
- elseif pcall(p.getContainerEditMode) and pcall(p.getStored) then
- deviceType = "dynamicTank"
- local s, r = pcall(p.isFormed)
- if (s and r) or not s then
- isValid = true
- end
- elseif pcall(p.getCoils) and pcall(p.getProcessRate) then
- deviceType = "sps"
- local s, r = pcall(p.isFormed)
- if (s and r) or not s then
- isValid = true
- end
- end
- if isValid and deviceType then
- debugPrint("-> New device: "..deviceType.." - "..peripheralName)
- table.insert(sharedData.foundDevices, { name = peripheralName, peripheral = p, type = deviceType })
- changed = true
- end
- end
- end
- end
- if changed then
- debugPrint("Device list changed.")
- local oldTypes = {}; for _, devName in ipairs(sharedData.config.deviceOrder) do oldTypes[devName] = true end
- local newTypes = {}; local newTurbineCount = 0
- for _, dev in ipairs(sharedData.foundDevices) do newTypes[dev.type] = true; if dev.type == "turbine" then newTurbineCount = newTurbineCount + 1 end end
- if newTurbineCount > 1 then newTypes["turbineSummary"] = true end
- local typesChanged = false
- if countKeys(oldTypes) ~= countKeys(newTypes) then
- typesChanged = true
- else
- for k in pairs(oldTypes) do
- if not newTypes[k] then
- typesChanged = true
- break
- end
- end
- if not typesChanged then
- for k in pairs(newTypes) do
- if not oldTypes[k] then
- typesChanged = true
- break
- end
- end
- end
- end
- if typesChanged then
- debugPrint("Device TYPE list changed. Updating config.")
- local newOrder = {}; local seenInNew = {}
- for _, devName in ipairs(sharedData.config.deviceOrder) do if newTypes[devName] then table.insert(newOrder, devName); seenInNew[devName] = true end end
- for devName in pairs(newTypes) do if not seenInNew[devName] then table.insert(newOrder, devName) end end
- sharedData.config.deviceOrder = newOrder
- saveConfig()
- end
- reinitializeLayout()
- end
- end
- function loadConfig()
- if fs.exists(configFile) then
- local file, err_open = fs.open(configFile, "r")
- if not file then centeredPrint("Error opening config: "..tostring(err_open)); return false end
- local s = file.readAll(); file.close()
- local config, err_unserialize = textutils.unserialize(s)
- if config and type(config) == "table" and config.tempUnit and config.deviceOrder and config.useColoredBorders ~= nil then
- sharedData.config = config
- debugPrint("Configuration loaded successfully.")
- return true
- else
- debugPrint("Warning: Failed to load config. Using defaults. "..tostring(err_unserialize))
- return false
- end
- end
- debugPrint("No config file found. Will run initial setup.")
- return false
- end
- function drawConfigurationMenu()
- local tempConfig = sharedData.configMenu.tempConfig
- local configPage = sharedData.configMenu.page
- local scrollState = sharedData.configMenu.scrollState
- local allDeviceTypes = {}; local seenTypes = {}; for _, d in ipairs(sharedData.foundDevices) do if not seenTypes[d.type] then table.insert(allDeviceTypes, d.type); seenTypes[d.type] = true end end; local turbineCount = 0; for _, d in ipairs(sharedData.foundDevices) do if d.type == "turbine" then turbineCount = turbineCount + 1 end end; if turbineCount > 1 and not seenTypes["turbineSummary"] then table.insert(allDeviceTypes, "turbineSummary") end
- local validOrder = {}; local seen = {}; for _, name in ipairs(tempConfig.deviceOrder) do for _, vName in ipairs(allDeviceTypes) do if name == vName and not seen[name] then table.insert(validOrder, name); seen[name] = true end end end; for _, vName in ipairs(allDeviceTypes) do if not seen[vName] then table.insert(validOrder, vName) end end; tempConfig.deviceOrder = validOrder
- local allWindows = { term = term }
- for _, mon in ipairs(monitors) do
- allWindows[peripheral.getName(mon)] = mon
- end
- local regions = sharedData.pageButtonRegions
- for name, window in pairs(allWindows) do
- local w, h = window.getSize()
- local totalConfigPages = 6
- if configPage > totalConfigPages then sharedData.configMenu.page = totalConfigPages; configPage = totalConfigPages end
- window.setBackgroundColor(colors.black); window.clear()
- local headerText = "--- Mekanism Monitor: Configuration (Page " .. configPage .. "/" .. totalConfigPages .. ") ---"
- window.setCursorPos(math.floor((w - #headerText) / 2) + 1, 1); window.setTextColor(colors.yellow); window.write(headerText); window.setTextColor(colors.white)
- if configPage == 4 then
- local items = {}
- if tempConfig.useMoreRed then
- table.insert(items, {type="side_selector", title="Bundled Cable Output Side", key="moreRedOutputSide", enabled=true})
- table.insert(items, {type="color_selector", title="SPS Control Color", key="spsRedstoneColor", enabled=true})
- table.insert(items, {type="color_selector", title="Fusion Ignition Color", key="fusionIgnitionColor", enabled=tempConfig.enableFusionIgnition})
- table.insert(items, {type="color_selector", title="Fusion Power Down Color", key="fusionPowerDownColor", enabled=tempConfig.enableFusionPowerDown})
- if tempConfig.useIndustrialAlarm then
- table.insert(items, {type="color_selector", title="Industrial Alarm Color", key="industrialAlarmColor", enabled=true})
- end
- else
- table.insert(items, {type="side_selector", title="SPS Control Side", key="spsRedstoneControlSide", enabled=true})
- table.insert(items, {type="side_selector", title="Fusion Ignition Side", key="fusionIgnitionSide", enabled=tempConfig.enableFusionIgnition})
- table.insert(items, {type="side_selector", title="Fusion Power Down Side", key="fusionPowerDownSide", enabled=tempConfig.enableFusionPowerDown})
- if tempConfig.useIndustrialAlarm then
- table.insert(items, {type="side_selector", title="Industrial Alarm Side", key="industrialAlarmSide", enabled=true})
- end
- end
- local contentHeight = 0
- for _, item in ipairs(items) do contentHeight = contentHeight + (item.type == "side_selector" and 3 or 4) end
- local viewHeight = h - 5
- scrollState.maxOffset = math.max(0, contentHeight - viewHeight)
- local clampedOffset = math.max(0, math.min(scrollState.offset, scrollState.maxOffset))
- local currentY = 3 - clampedOffset
- for _, item in ipairs(items) do
- if item.type == "side_selector" then
- if currentY > 1 and currentY < h - 2 then
- local sides={"top","bottom","left","right","front","back"}; local sideButtonsWidth=0
- for _,side in ipairs(sides) do sideButtonsWidth=sideButtonsWidth+#(" "..side.." ")+1 end; sideButtonsWidth=sideButtonsWidth-1
- local sideStartX=math.floor((w-sideButtonsWidth-1)/2)+1
- window.setBackgroundColor(colors.black); window.setCursorPos(1, currentY); window.write(string.rep(" ", w))
- window.setCursorPos(math.floor((w-#item.title)/2)+1, currentY); window.setTextColor(item.enabled and colors.white or colors.gray); window.write(item.title)
- if currentY + 1 < h - 2 then
- local currentX = sideStartX
- for _, side in ipairs(sides) do
- local btnText=" "..side.." "; local isActive=(tempConfig[item.key]==side)
- window.setCursorPos(currentX,currentY+1);
- local bgColor = colors.darkGray
- if item.enabled then bgColor = colors.gray end
- if isActive and item.enabled then bgColor = colors.green end
- window.setBackgroundColor(bgColor)
- window.setTextColor(colors.black); window.write(btnText)
- if item.enabled then table.insert(regions,{type="set_side",key=item.key,value=side,x1=currentX,y=currentY+1,x2=currentX+#btnText-1,target=name}) end
- currentX=currentX+#btnText+1
- end
- end
- end
- currentY = currentY + 3
- elseif item.type == "color_selector" then
- if currentY > 1 and currentY < h-2 then
- local colorNames={"white","orange","magenta","lightBlue","yellow","lime","pink","gray","lightGray","cyan","purple","blue","brown","green","red","black"}
- window.setBackgroundColor(colors.black); window.setCursorPos(1, currentY); window.write(string.rep(" ", w))
- window.setCursorPos(math.floor((w-#item.title)/2)+1, currentY); window.setTextColor(item.enabled and colors.white or colors.gray); window.write(item.title)
- local row1,row2={}, {}; for i=1,#colorNames do if i<=8 then table.insert(row1,colorNames[i]) else table.insert(row2,colorNames[i]) end end
- for r,row in ipairs({row1,row2}) do
- if currentY+r < h-2 then
- local rowWidth=0; for _, c in ipairs(row)do rowWidth=rowWidth+4 end; rowWidth=rowWidth-1
- local currentX=math.floor((w-rowWidth-1)/2)+1
- for _, cName in ipairs(row)do
- window.setCursorPos(currentX,currentY+r);
- if not item.enabled then
- window.setBackgroundColor(colors.darkGray)
- window.write(" ")
- else
- window.setBackgroundColor(colors[cName])
- if tempConfig[item.key]==cName then window.setTextColor(cName=="black" and colors.white or colors.black); window.write("[X]") else window.write(" ") end
- table.insert(regions,{type="set_color",key=item.key,value=cName,x1=currentX,y=currentY+r,x2=currentX+2,target=name})
- end
- currentX=currentX+4
- end
- end
- end
- end
- currentY = currentY + 4
- end
- end
- if scrollState.maxOffset > 0 then
- local scrollX = w - 1
- window.setCursorPos(scrollX, 2); window.setBackgroundColor(colors.black); window.setTextColor(colors.white); window.write("^")
- table.insert(regions, {type="config_scroll", direction=-1, maxOffset=scrollState.maxOffset, x1=scrollX, x2=scrollX, y=2, target=name})
- window.setCursorPos(scrollX, h-2); window.setBackgroundColor(colors.black); window.setTextColor(colors.white); window.write("v")
- table.insert(regions, {type="config_scroll", direction=1, maxOffset=scrollState.maxOffset, x1=scrollX, x2=scrollX, y=h-2, target=name})
- local trackY=3; local trackHeight=h-5;
- local handleSize=math.max(1,math.floor(trackHeight*(viewHeight/contentHeight)))
- local handlePos
- if scrollState.maxOffset > 0 then
- handlePos=trackY+math.floor((trackHeight-handleSize)*(clampedOffset/scrollState.maxOffset))
- else
- handlePos=trackY
- end
- for i=0,trackHeight-1 do
- window.setCursorPos(scrollX,trackY+i);
- if trackY+i>=handlePos and trackY+i<handlePos+handleSize then window.setBackgroundColor(colors.lightGray)
- else window.setBackgroundColor(colors.gray) end
- window.write(" ")
- end
- table.insert(regions, {type="config_scroll_handle",x1=scrollX,x2=scrollX,yStart=handlePos,yEnd=handlePos+handleSize-1,trackY=trackY,trackH=trackHeight,maxOffset=scrollState.maxOffset,target=name})
- end
- elseif configPage == 1 then
- local tempTitleY = 5; local borderTitleY = 9
- local tempUnitTitle = "Temperature Unit"
- window.setCursorPos(math.floor((w - #tempUnitTitle) / 2) + 1, tempTitleY); window.write(tempUnitTitle)
- local tempOrder = { "K", "C", "F", "R", "STP" }; local tempButtonsWidth = 0
- for _, unitKey in ipairs(tempOrder) do tempButtonsWidth = tempButtonsWidth + #(" " .. unitKey .. " ") + 1 end
- tempButtonsWidth = tempButtonsWidth - 1; local tempStartX = math.floor((w - tempButtonsWidth) / 2) + 1; local currentX = tempStartX
- for _, unitKey in ipairs(tempOrder) do
- local btnText = " " .. unitKey .. " "; local isActive = (tempConfig.tempUnit == unitKey)
- window.setCursorPos(currentX, tempTitleY + 1); window.setBackgroundColor(isActive and colors.green or colors.gray); window.setTextColor(colors.black); window.write(btnText)
- table.insert(regions, { type = "temp", unit = unitKey, x1 = currentX, y = tempTitleY + 1, x2 = currentX + #btnText - 1, target = name })
- currentX = currentX + #btnText + 1
- end
- local bordersTitle = "UI Borders"
- window.setBackgroundColor(colors.black)
- window.setTextColor(colors.white)
- window.setCursorPos(math.floor((w - #bordersTitle) / 2) + 1, borderTitleY); window.write(bordersTitle)
- local coloredBtnText = " Colored "; local grayBtnText = " Gray "; local bordersButtonsWidth = #coloredBtnText + #grayBtnText + 1
- local borderStartX = math.floor((w - bordersButtonsWidth) / 2) + 1; currentX = borderStartX
- window.setCursorPos(currentX, borderTitleY + 1); window.setBackgroundColor(tempConfig.useColoredBorders and colors.green or colors.gray); window.setTextColor(colors.black); window.write(coloredBtnText)
- table.insert(regions, { type = "border", value = true, x1 = currentX, y = borderTitleY + 1, x2 = currentX + #coloredBtnText - 1, target = name })
- currentX = currentX + #coloredBtnText + 1
- window.setCursorPos(currentX, borderTitleY + 1); window.setBackgroundColor(not tempConfig.useColoredBorders and colors.green or colors.gray); window.setTextColor(colors.black); window.write(grayBtnText)
- table.insert(regions, { type = "border", value = false, x1 = currentX, y = borderTitleY + 1, x2 = currentX + #grayBtnText - 1, target = name })
- elseif configPage == 2 then
- local moreRedY = 7; local title = "Redstone Control Method"
- window.setCursorPos(math.floor((w-#title)/2)+1, moreRedY-1); window.write(title)
- local yesBtn = " MoreRed (Bundled) "; local noBtn = " Standard (Per Side) "; local ynWidth = #yesBtn + #noBtn + 1
- local ynStartX = math.floor((w - ynWidth) / 2) + 1; local currentX = ynStartX
- window.setCursorPos(currentX, moreRedY + 1); window.setBackgroundColor(tempConfig.useMoreRed and colors.green or colors.gray); window.setTextColor(colors.black); window.write(yesBtn)
- table.insert(regions, { type = "use_morered", value = true, x1 = currentX, y = moreRedY + 1, x2 = currentX + #yesBtn - 1, target = name })
- currentX = currentX + #yesBtn + 1
- window.setCursorPos(currentX, moreRedY + 1); window.setBackgroundColor(not tempConfig.useMoreRed and colors.green or colors.gray); window.setTextColor(colors.black); window.write(noBtn)
- table.insert(regions, { type = "use_morered", value = false, x1 = currentX, y = moreRedY + 1, x2 = currentX + #noBtn - 1, target = name })
- elseif configPage == 3 then
- local alarmTypeY = 7; local title = "SCRAM Alert Method"
- window.setCursorPos(math.floor((w-#title)/2)+1, alarmTypeY-1); window.write(title)
- local speakerBtn = " CC Speakers "; local alarmBtn = " Industrial Alarm "; local btnWidth = #speakerBtn + #alarmBtn + 1
- local startX = math.floor((w - btnWidth) / 2) + 1; local currentX = startX
- window.setCursorPos(currentX, alarmTypeY + 1); window.setBackgroundColor(not tempConfig.useIndustrialAlarm and colors.green or colors.gray); window.setTextColor(colors.black); window.write(speakerBtn)
- table.insert(regions, { type = "use_alarm", value = false, x1 = currentX, y = alarmTypeY + 1, x2 = currentX + #speakerBtn - 1, target = name })
- currentX = currentX + #speakerBtn + 1
- window.setCursorPos(currentX, alarmTypeY + 1); window.setBackgroundColor(tempConfig.useIndustrialAlarm and colors.green or colors.gray); window.setTextColor(colors.black); window.write(alarmBtn)
- table.insert(regions, { type = "use_alarm", value = true, x1 = currentX, y = alarmTypeY + 1, x2 = currentX + #alarmBtn - 1, target = name })
- elseif configPage == 5 then
- local ignitionEnableY = 5; local powerDownEnableY = 10; local title = "Fusion Reactor Features"
- window.setCursorPos(math.floor((w-#title)/2)+1, 3); window.write(title)
- local enabledBtnText = " Enabled "; local disabledBtnText = " Disabled "; local enableButtonsWidth = #enabledBtnText + #disabledBtnText + 1
- local enableStartX = math.floor((w - enableButtonsWidth) / 2) + 1
- local ignitionEnableTitle = "Ignition Button"
- window.setCursorPos(math.floor((w-#ignitionEnableTitle)/2)+1, ignitionEnableY); window.write(ignitionEnableTitle)
- local currentX = enableStartX
- window.setCursorPos(currentX, ignitionEnableY + 1); window.setBackgroundColor(tempConfig.enableFusionIgnition and colors.green or colors.gray); window.setTextColor(colors.black); window.write(enabledBtnText)
- table.insert(regions, { type = "enable_ignition", value = true, x1 = currentX, y = ignitionEnableY + 1, x2 = currentX + #enabledBtnText - 1, target = name })
- currentX = currentX + #enabledBtnText + 1
- window.setCursorPos(currentX, ignitionEnableY + 1); window.setBackgroundColor(not tempConfig.enableFusionIgnition and colors.green or colors.gray); window.setTextColor(colors.black); window.write(disabledBtnText)
- table.insert(regions, { type = "enable_ignition", value = false, x1 = currentX, y = ignitionEnableY + 1, x2 = currentX + #disabledBtnText - 1, target = name })
- local powerDownEnableTitle = "Power Down Button"
- window.setBackgroundColor(colors.black)
- window.setTextColor(colors.white)
- window.setCursorPos(math.floor((w-#powerDownEnableTitle)/2)+1, powerDownEnableY); window.write(powerDownEnableTitle)
- currentX = enableStartX
- window.setCursorPos(currentX, powerDownEnableY + 1); window.setBackgroundColor(tempConfig.enableFusionPowerDown and colors.green or colors.gray); window.setTextColor(colors.black); window.write(enabledBtnText)
- table.insert(regions, { type = "enable_power_down", value = true, x1 = currentX, y = powerDownEnableY + 1, x2 = currentX + #enabledBtnText - 1, target = name })
- currentX = currentX + #enabledBtnText + 1
- window.setCursorPos(currentX, powerDownEnableY + 1); window.setBackgroundColor(not tempConfig.enableFusionPowerDown and colors.green or colors.gray); window.setTextColor(colors.black); window.write(disabledBtnText)
- table.insert(regions, { type = "enable_power_down", value = false, x1 = currentX, y = powerDownEnableY + 1, x2 = currentX + #disabledBtnText - 1, target = name })
- elseif configPage == 6 then
- local pageY = (h > 22) and math.floor((h - #tempConfig.deviceOrder) / 2) or 3
- local maxNameLen = 0
- for _, dName in ipairs(tempConfig.deviceOrder) do if #dName > maxNameLen then maxNameLen = #dName end end
- local orderBlockWidth = 3 + 1 + maxNameLen + 1 + 3; local orderStartX = math.floor((w - orderBlockWidth) / 2) + 1
- local orderTitle = "Device Display Order"; window.setCursorPos(orderStartX + math.floor((orderBlockWidth - #orderTitle)/2), pageY - 2); window.setTextColor(colors.white); window.write(orderTitle)
- local upArrowX = orderStartX; local nameFieldWidth = maxNameLen; local downArrowX = orderStartX + 4 + nameFieldWidth + 1
- for i, deviceName in ipairs(tempConfig.deviceOrder) do
- local yPos = pageY + i - 1; if yPos >= h - 2 then break end; local nameStartX = orderStartX + 4 + math.floor((nameFieldWidth - #deviceName) / 2)
- window.setCursorPos(upArrowX, yPos); window.setTextColor(i > 1 and colors.white or colors.gray); window.write("[^]"); table.insert(regions, { type = "order_up", index = i, x1 = upArrowX, y = yPos, x2 = upArrowX + 2, target = name })
- window.setCursorPos(nameStartX, yPos); window.setTextColor(colors.yellow); window.write(deviceName)
- window.setCursorPos(downArrowX, yPos); window.setTextColor(i < #tempConfig.deviceOrder and colors.white or colors.gray); window.write("[v]"); table.insert(regions, { type = "order_down", index = i, x1 = downArrowX, y = yPos, x2 = downArrowX + 2, target = name })
- end
- end
- local saveText = " Save & Close "; local quitText = " Discard "
- local backText = " < Back "; local nextText = " Next > "
- window.setBackgroundColor(colors.black)
- window.setCursorPos(2, h);
- window.setBackgroundColor(colors.green); window.setTextColor(colors.black); window.write(saveText)
- table.insert(regions, { type = "save", x1 = 2, y = h, x2 = 2 + #saveText - 1, target = name })
- local currentNavX = 2 + #saveText + 2
- if configPage > 1 then
- window.setCursorPos(currentNavX, h)
- window.setBackgroundColor(colors.blue); window.setTextColor(colors.white); window.write(backText)
- table.insert(regions, { type = "nav_back", x1 = currentNavX, y = h, x2 = currentNavX + #backText - 1, target = name })
- end
- local quitX = w - #quitText
- window.setCursorPos(quitX, h);
- window.setBackgroundColor(colors.red); window.setTextColor(colors.black); window.write(quitText)
- table.insert(regions, { type = "quit", x1 = quitX, y = h, x2 = w, target = name })
- if configPage < totalConfigPages then
- local nextX = quitX - #nextText - 2
- window.setCursorPos(nextX, h)
- window.setBackgroundColor(colors.blue); window.setTextColor(colors.white); window.write(nextText)
- table.insert(regions, { type = "nav_next", x1 = nextX, y = h, x2 = nextX + #nextText - 1, target = name })
- end
- window.setBackgroundColor(colors.black); window.setTextColor(colors.white)
- end
- end
- function main()
- term.clear()
- for _, name in ipairs(peripheral.getNames()) do
- if peripheral.getType(name) == "monitor" then
- table.insert(monitors, peripheral.wrap(name))
- sharedData.monitorStates[name] = { currentPage = 1 }
- elseif peripheral.getType(name) == "speaker" then
- table.insert(sharedData.speakers, peripheral.wrap(name))
- end
- end
- if #monitors == 0 then
- centeredPrint("Exiting: No monitors found.", 2)
- return
- end
- setupSirenAudio()
- loadConfig()
- sharedData.configMenu.tempConfig = table.copy(sharedData.config)
- if sharedData.config.useMoreRed then
- local side = sharedData.config.moreRedOutputSide
- setBundledBit(side, sharedData.config.spsRedstoneColor, true)
- setBundledBit(side, sharedData.config.industrialAlarmColor, false)
- else
- redstone.setOutput(sharedData.config.spsRedstoneControlSide, true)
- redstone.setOutput(sharedData.config.industrialAlarmSide, false)
- end
- if not fs.exists(configFile) then
- term.clear(); drawTerminalHeader()
- centeredPrint("No config file found. Scanning for devices...", 3)
- scanAndApplyChanges()
- local deviceTypesFound = {}
- for _, dev in ipairs(sharedData.foundDevices) do
- deviceTypesFound[dev.type] = true
- end
- local defaultOrder = {"sps", "fusionReactor", "fissionReactor", "inductionMatrix", "dynamicTank", "thermalEvaporation", "boiler", "turbine"}
- local finalOrder = {}
- local turbineCount = 0; for _, dev in ipairs(sharedData.foundDevices) do if dev.type == "turbine" then turbineCount = turbineCount + 1 end end
- if turbineCount > 1 and deviceTypesFound.turbine then
- table.insert(finalOrder, "turbineSummary")
- end
- for _, typeName in ipairs(defaultOrder) do
- if deviceTypesFound[typeName] then
- table.insert(finalOrder, typeName)
- end
- end
- sharedData.config.deviceOrder = finalOrder
- centeredPrint("Scan complete. Found " .. #sharedData.foundDevices .. " devices.", 5)
- centeredWrapPrint("Launching configuration menu...", 6)
- sleep(2)
- sharedData.isMenuOpen = true
- sharedData.configMenu.tempConfig = table.copy(sharedData.config)
- saveConfig()
- end
- term.clear(); drawTerminalHeader()
- centeredPrint("Starting monitoring session...", 3)
- if not debugMode then
- centeredWrapPrint("Debug mode is off. Press Ctrl+T or use END button to stop.", 5)
- end
- sleep(1)
- sharedData.monitorRunning = true
- reinitializeLayout()
- parallel.waitForAll(displayTask, fetchTask, eventListenerTask, scannerTask, actionQueueProcessorTask, sirenTask)
- for _, mon in ipairs(monitors) do
- pcall(function() mon.setBackgroundColor(colors.black); mon.setTextColor(colors.white); mon.clear() end)
- end
- term.setBackgroundColor(colors.black); term.setTextColor(colors.white); term.clear(); term.setCursorPos(1,1);
- centeredPrint("Mekanism Monitor stopped.")
- print()
- end
- main()
Advertisement
Add Comment
Please, Sign In to add comment