Yahve83

Reactor Control Yahvouille

Jan 25th, 2022 (edited)
268
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 103.33 KB
  1. --[[
  2. Program name: reactor control system
  3. Version: v0.3.17
  4. Programmer: Yahvouille
  5. Last update: 2015-04-08
  6. Pastebin: http://pastebin.com/if5ZTc4G
  7.  
  8. Description:
  9. This program controls a Big Reactors nuclear reactor in Minecraft with a Computercraft computer, using Computercraft's own wired modem connected to the reactors computer control port.
  10.  
  11.  
  12. GUI Usage
  13. ----------------------------
  14. - Right-clicking between "< * >" of the last row of a monitor alternates the device selection between Reactor, Turbine, and Status output.
  15.     - Right-clicking "<" and ">" switches between connected devices, starting with the currently selected type, but not limited to them.
  16. - The other "<" and ">" buttons, when right-clicked with the mouse, will decrease and increase, respectively, the values assigned to the monitor:
  17.     - "Rod (%)" will lower/raise the Reactor Control Rods for that Reactor
  18.     - "mB/t" will lower/raise the Turbine Flow Rate maximum for that Turbine
  19.     - "RPM" will lower/raise the target Turbine RPM for that Turbine
  20. - Right-clicking between the "<" and ">" (not on them) will disable auto-adjust of that value for attached device.
  21.     - Right-clicking on the "Enabled" or "Disabled" text for auto-adjust will do the same.
  22. - Right-clicking on "ONLINE" or "OFFLINE" at the top-right will toggle the state of attached device.
  23.  
  24. Default values
  25. ----------------------------
  26. - Rod Control: 90% (Let's start off safe and then power up as we can)
  27. - Minimum Energy Buffer: 15% (will power on below this value)
  28. - Maximum Energy Buffer: 85% (will power off above this value)
  29. - Minimum Passive Cooling Temperature: 950^C (will raise control rods below this value)
  30. - Maximum Passive Cooling Temperature: 1,400^C (will lower control rods above this value)
  31. - Minimum Active Cooling Temperature: 300^C (will raise the control rods below this value)
  32. - Maximum Active Cooling Temperature: 420^C (will lower control rods above this value)
  33. - Optimal Turbine RPM:  900, 1,800, or 2,700 (divisible by 900)
  34.     - New user-controlled option for target speed of turbines, defaults to 2726RPM, which is high-optimal.
  35.  
  36. Requirements
  37. ----------------------------
  38. - Advanced Monitor size is X: 29, Y: 12 with a 3x2 size
  39. - Computer or Advanced Computer
  40. - Modems (not wireless) connecting each of the Computer to both the Advanced Monitor and Reactor Computer Port.
  41. - Big Reactors (http://www.big-reactors.com/) 0.3.2A+
  42. - Computercraft (http://computercraft.info/) 1.58, 1.63+, or 1.73+
  43. - Reset the computer any time number of connected devices change.
  44. ]]--
  45.  
  46.  
  47. -- Some global variables
  48. local progVer = "0.3.17"
  49. local progName = "YahvouilleCorp"
  50. local sideClick, xClick, yClick = nil, 0, 0
  51. local loopTime = 2
  52. local controlRodAdjustAmount = 1 -- Default Reactor Rod Control % adjustment amount
  53. local flowRateAdjustAmount = 25 -- Default Turbine Flow Rate in mB adjustment amount
  54. local debugMode = false
  55. -- End multi-reactor cleanup section
  56. local minStoredEnergyPercent = nil -- Max energy % to store before activate
  57. local maxStoredEnergyPercent = nil -- Max energy % to store before shutdown
  58. local monitorList = {} -- Empty monitor array
  59. local monitorNames = {} -- Empty array of monitor names
  60. local reactorList = {} -- Empty reactor array
  61. local reactorNames = {} -- Empty array of reactor names
  62. local turbineList = {} -- Empty turbine array
  63. local turbineNames = {} -- Empty array of turbine names
  64. local monitorAssignments = {} -- Empty array of monitor - "what to display" assignments
  65. local monitorOptionFileName = "monitors.options" -- File for saving the monitor assignments
  66. local knowlinglyOverride = false -- Issue #39 Allow the user to override safe values, currently only enabled for actively cooled reactor min/max temperature
  67. local steamRequested = 0 -- Sum of Turbine Flow Rate in mB
  68. local steamDelivered = 0 -- Sum of Active Reactor steam output in mB
  69.  
  70. -- Log levels
  71. local FATAL = 16
  72. local ERROR = 8
  73. local WARN = 4
  74. local INFO = 2
  75. local DEBUG = 1
  76.  
  77. term.clear()
  78. term.setCursorPos(2,1)
  79. write("Initializing program...\n")
  80.  
  81.  
  82. -- File needs to exist for append "a" later and zero it out if it already exists
  83. -- Always initalize this file to avoid confusion with old files and the latest run
  84. local logFile = fs.open("reactorcontrol.log", "w")
  85. if logFile then
  86.     logFile.writeLine("Minecraft time: Day "..os.day().." at "..textutils.formatTime(os.time(),true))
  87.     logFile.close()
  88. else
  89.     error("Could not open file reactorcontrol.log for writing.")
  90. end
  91.  
  92.  
  93. -- Helper functions
  94.  
  95. local function termRestore()
  96.     local ccVersion = nil
  97.     ccVersion = os.version()
  98.  
  99.     if ccVersion == "CraftOS 1.6" or "CraftOS 1.7" then
  100.         term.redirect(term.native())
  101.     elseif ccVersion == "CraftOS 1.5" then
  102.         term.restore()
  103.     else -- Default to older term.restore
  104.         printLog("Unsupported CraftOS found. Reported version is \""..ccVersion.."\".")
  105.         term.restore()
  106.     end -- if ccVersion
  107. end -- function termRestore()
  108.  
  109. local function printLog(printStr, logLevel)
  110.     logLevel = logLevel or INFO
  111.     -- No, I'm not going to write full syslog style levels. But this makes it a little easier filtering and finding stuff in the logfile.
  112.     -- Since you're already looking at it, you can adjust your preferred log level right here.
  113.     if debugMode and (logLevel >= WARN) then
  114.         -- If multiple monitors, print to all of them
  115.         for monitorName, deviceData in pairs(monitorAssignments) do
  116.             if deviceData.type == "Debug" then
  117.                 debugMonitor = monitorList[deviceData.index]
  118.                 if(not debugMonitor) or (not debugMonitor.getSize()) then
  119.                     term.write("printLog(): debug monitor "..monitorName.." failed")
  120.                 else
  121.                     term.redirect(debugMonitor) -- Redirect to selected monitor
  122.                     debugMonitor.setTextScale(0.5) -- Fit more logs on screen
  123.                     local color = colors.lightGray
  124.                     if (logLevel == WARN) then
  125.                         color = colors.white
  126.                     elseif (logLevel == ERROR) then
  127.                         color = colors.red
  128.                     elseif (logLevel == FATAL) then
  129.                         color = colors.black
  130.                         debugMonitor.setBackgroundColor(colors.red)
  131.                     end
  132.                     debugMonitor.setTextColor(color)
  133.                     write(printStr.."\n")   -- May need to use term.scroll(x) if we output too much, not sure
  134.                     debugMonitor.setBackgroundColor(colors.black)
  135.                     termRestore()
  136.                 end
  137.             end
  138.         end -- for
  139.  
  140.         local logFile = fs.open("reactorcontrol.log", "a") -- See http://computercraft.info/wiki/Fs.open
  141.         if logFile then
  142.             logFile.writeLine(printStr)
  143.             logFile.close()
  144.         else
  145.             error("Cannot open file reactorcontrol.log for appending!")
  146.         end -- if logFile then
  147.     end -- if debugMode then
  148. end -- function printLog(printStr)
  149.  
  150. -- Trim a string
  151. function stringTrim(s)
  152.     assert(s ~= nil, "String can't be nil")
  153.     return(string.gsub(s, "^%s*(.-)%s*$", "%1"))
  154. end
  155.  
  156. -- Format number with [k,M,G,T,P,E] postfix or exponent, depending on how large it is
  157. local function formatReadableSIUnit(num)
  158.     printLog("formatReadableSIUnit("..num..")", DEBUG)
  159.     num = tonumber(num)
  160.     if(num < 1000) then return tostring(num) end
  161.     local sizes = {"", "k", "M", "G", "T", "P", "E"}
  162.     local exponent = math.floor(math.log10(num))
  163.     local group = math.floor(exponent / 3)
  164.     if group > #sizes then
  165.         return string.format("%e", num)
  166.     else
  167.         local divisor = math.pow(10, (group - 1) * 3)
  168.         return string.format("%i%s", num / divisor, sizes[group])
  169.     end
  170. end -- local function formatReadableSIUnit(num)
  171.  
  172. -- pretty printLog() a table
  173. local function tprint (tbl, loglevel, indent)
  174.     if not loglevel then loglevel = DEBUG end
  175.     if not indent then indent = 0 end
  176.     for k, v in pairs(tbl) do
  177.         formatting = string.rep("  ", indent) .. k .. ": "
  178.         if type(v) == "table" then
  179.             printLog(formatting, loglevel)
  180.             tprint(v, loglevel, indent+1)
  181.         elseif type(v) == 'boolean' or type(v) == "function" then
  182.             printLog(formatting .. tostring(v), loglevel)      
  183.         else
  184.             printLog(formatting .. v, loglevel)
  185.         end
  186.     end
  187. end -- function tprint()
  188.  
  189. config = {}
  190.  
  191. -- Save a table into a config file
  192. -- path: path of the file to write
  193. -- tab: table to save
  194. config.save = function(path, tab)
  195.     printLog("Save function called for config for "..path.." EOL")
  196.     assert(path ~= nil, "Path can't be nil")
  197.     assert(type(tab) == "table", "Second parameter must be a table")
  198.     local f = io.open(path, "w")
  199.     local i = 0
  200.     for key, value in pairs(tab) do
  201.         if i ~= 0 then
  202.             f:write("\n")
  203.         end
  204.         f:write("["..key.."]".."\n")
  205.         for key2, value2 in pairs(tab[key]) do
  206.             key2 = stringTrim(key2)
  207.             --doesn't like boolean values
  208.             if (type(value2) ~= "boolean") then
  209.             value2 = stringTrim(value2)
  210.             else
  211.             value2 = tostring(value2)
  212.             end
  213.             key2 = key2:gsub(";", "\\;")
  214.             key2 = key2:gsub("=", "\\=")
  215.             value2 = value2:gsub(";", "\\;")
  216.             value2 = value2:gsub("=", "\\=")   
  217.             f:write(key2.."="..value2.."\n")
  218.         end
  219.         i = i + 1
  220.     end
  221.     f:close()
  222. end --config.save = function(path, tab)
  223.  
  224. -- Load a config file
  225. -- path: path of the file to read
  226. config.load = function(path)
  227.     printLog("Load function called for config for "..path.." EOL")
  228.     assert(path ~= nil, "Path can't be nil")
  229.     local f = fs.open(path, "r")
  230.     if f ~= nil then
  231.         printLog("Successfully opened "..path.." for reading EOL")
  232.         local tab = {}
  233.         local line = ""
  234.         local newLine
  235.         local i
  236.         local currentTag = nil
  237.         local found = false
  238.         local pos = 0
  239.         while line ~= nil do
  240.             found = false      
  241.             line = line:gsub("\\;", "#_!36!_#") -- to keep \;
  242.             line = line:gsub("\\=", "#_!71!_#") -- to keep \=
  243.             if line ~= "" then
  244.                 -- Delete comments
  245.                 newLine = line
  246.                 line = ""
  247.                 for i=1, string.len(newLine) do            
  248.                     if string.sub(newLine, i, i) ~= ";" then
  249.                         line = line..newLine:sub(i, i)                     
  250.                     else               
  251.                         break
  252.                     end
  253.                 end
  254.                 line = stringTrim(line)
  255.                 -- Find tag        
  256.                 if line:sub(1, 1) == "[" and line:sub(line:len(), line:len()) == "]" then
  257.                     currentTag = stringTrim(line:sub(2, line:len()-1))
  258.                     tab[currentTag] = {}
  259.                     found = true                           
  260.                 end
  261.                 -- Find key and values
  262.                 if not found and line ~= "" then               
  263.                     pos = line:find("=")               
  264.                     if pos == nil then
  265.                         error("Bad INI file structure")
  266.                     end
  267.                     line = line:gsub("#_!36!_#", ";")
  268.                     line = line:gsub("#_!71!_#", "=")
  269.                     tab[currentTag][stringTrim(line:sub(1, pos-1))] = stringTrim(line:sub(pos+1, line:len()))
  270.                     found = true           
  271.                 end        
  272.             end
  273.             line = f.readLine()
  274.         end
  275.        
  276.         f:close()
  277.        
  278.         return tab
  279.     else
  280.         printLog("Could NOT opened "..path.." for reading! EOL")
  281.         return nil
  282.     end
  283. end --config.load = function(path)
  284.  
  285.  
  286.  
  287. -- round() function from mechaet
  288. local function round(num, places)
  289.     local mult = 10^places
  290.     local addon = nil
  291.     if ((num * mult) < 0) then
  292.         addon = -.5
  293.     else
  294.         addon = .5
  295.     end
  296.  
  297.     local integer, decimal = math.modf(num*mult+addon)
  298.     newNum = integer/mult
  299.     printLog("Called round(num="..num..",places="..places..") returns \""..newNum.."\".")
  300.     return newNum
  301. end -- function round(num, places)
  302.  
  303.  
  304. local function print(printParams)
  305.     -- Default to xPos=1, yPos=1, and first monitor
  306.     setmetatable(printParams,{__index={xPos=1, yPos=1, monitorIndex=1}})
  307.     local printString, xPos, yPos, monitorIndex =
  308.         printParams[1], -- Required parameter
  309.         printParams[2] or printParams.xPos,
  310.         printParams[3] or printParams.yPos,
  311.         printParams[4] or printParams.monitorIndex
  312.  
  313.     local monitor = nil
  314.     monitor = monitorList[monitorIndex]
  315.  
  316.     if not monitor then
  317.         printLog("monitor["..monitorIndex.."] in print() is NOT a valid monitor.")
  318.         return -- Invalid monitorIndex
  319.     end
  320.  
  321.     monitor.setCursorPos(xPos, yPos)
  322.     monitor.write(printString)
  323. end -- function print(printParams)
  324.  
  325.  
  326. -- Replaces the one from FC_API (http://pastebin.com/A9hcbZWe) and adding multi-monitor support
  327. local function printCentered(printString, yPos, monitorIndex)
  328.     local monitor = nil
  329.     monitor = monitorList[monitorIndex]
  330.  
  331.     if not monitor then
  332.         printLog("monitor["..monitorIndex.."] in printCentered() is NOT a valid monitor.", ERROR)
  333.         return -- Invalid monitorIndex
  334.     end
  335.  
  336.     local width, height = monitor.getSize()
  337.     local monitorNameLength = 0
  338.  
  339.     -- Special changes for title bar
  340.     if yPos == 1 then
  341.         -- Add monitor name to first line
  342.         monitorNameLength = monitorNames[monitorIndex]:len()
  343.         width = width - monitorNameLength -- add a space
  344.  
  345.         -- Leave room for "offline" and "online" on the right except for overall status display
  346.         if monitorAssignments[monitorNames[monitorIndex]].type ~= "Status" then
  347.             width = width - 7
  348.         end
  349.     end
  350.  
  351.     monitor.setCursorPos(monitorNameLength + math.ceil((1 + width - printString:len())/2), yPos)
  352.     monitor.write(printString)
  353. end -- function printCentered(printString, yPos, monitorIndex)
  354.  
  355.  
  356. -- Print text padded from the left side
  357. -- Clear the left side of the screen
  358. local function printLeft(printString, yPos, monitorIndex)
  359.     local monitor = nil
  360.     monitor = monitorList[monitorIndex]
  361.  
  362.     if not monitor then
  363.         printLog("monitor["..monitorIndex.."] in printLeft() is NOT a valid monitor.", ERROR)
  364.         return -- Invalid monitorIndex
  365.     end
  366.  
  367.     local gap = 1
  368.     local width = monitor.getSize()
  369.  
  370.     -- Clear left-half of the monitor
  371.  
  372.     for curXPos = 1, (width / 2) do
  373.         monitor.setCursorPos(curXPos, yPos)
  374.         monitor.write(" ")
  375.     end
  376.  
  377.     -- Write our string left-aligned
  378.     monitor.setCursorPos(1+gap, yPos)
  379.     monitor.write(printString)
  380. end
  381.  
  382.  
  383. -- Print text padded from the right side
  384. -- Clear the right side of the screen
  385. local function printRight(printString, yPos, monitorIndex)
  386.     local monitor = nil
  387.     monitor = monitorList[monitorIndex]
  388.  
  389.     if not monitor then
  390.         printLog("monitor["..monitorIndex.."] in printRight() is NOT a valid monitor.", ERROR)
  391.         return -- Invalid monitorIndex
  392.     end
  393.  
  394.     -- Make sure printString is a string
  395.     printString = tostring(printString)
  396.  
  397.     local gap = 1
  398.     local width = monitor.getSize()
  399.  
  400.     -- Clear right-half of the monitor
  401.     for curXPos = (width/2), width do
  402.         monitor.setCursorPos(curXPos, yPos)
  403.         monitor.write(" ")
  404.     end
  405.  
  406.     -- Write our string right-aligned
  407.     monitor.setCursorPos(math.floor(width) - math.ceil(printString:len()+gap), yPos)
  408.     monitor.write(printString)
  409. end
  410.  
  411.  
  412. -- Replaces the one from FC_API (http://pastebin.com/A9hcbZWe) and adding multi-monitor support
  413. local function clearMonitor(printString, monitorIndex)
  414.     local monitor = nil
  415.     monitor = monitorList[monitorIndex]
  416.  
  417.     printLog("Called as clearMonitor(printString="..printString..",monitorIndex="..monitorIndex..").")
  418.  
  419.     if not monitor then
  420.         printLog("monitor["..monitorIndex.."] in clearMonitor(printString="..printString..",monitorIndex="..monitorIndex..") is NOT a valid monitor.", ERROR)
  421.         return -- Invalid monitorIndex
  422.     end
  423.  
  424.     local gap = 2
  425.     monitor.clear()
  426.     local width, height = monitor.getSize()
  427.  
  428.     printCentered(printString, 1, monitorIndex)
  429.     monitor.setTextColor(colors.blue)
  430.     print{monitorNames[monitorIndex], 1, 1, monitorIndex}
  431.     monitor.setTextColor(colors.white)
  432.  
  433.     for i=1, width do
  434.         monitor.setCursorPos(i, gap)
  435.         monitor.write("-")
  436.     end
  437.  
  438.     monitor.setCursorPos(1, gap+1)
  439. end -- function clearMonitor(printString, monitorIndex)
  440.  
  441.  
  442. -- Return a list of all connected (including via wired modems) devices of "deviceType"
  443. local function getDevices(deviceType)
  444.     printLog("Called as getDevices(deviceType="..deviceType..")")
  445.  
  446.     local deviceName = nil
  447.     local deviceIndex = 1
  448.     local deviceList, deviceNames = {}, {} -- Empty array, which grows as we need
  449.     local peripheralList = peripheral.getNames() -- Get table of connected peripherals
  450.  
  451.     deviceType = deviceType:lower() -- Make sure we're matching case here
  452.  
  453.     for peripheralIndex = 1, #peripheralList do
  454.         -- Log every device found
  455.         -- printLog("Found "..peripheral.getType(peripheralList[peripheralIndex]).."["..peripheralIndex.."] attached as \""..peripheralList[peripheralIndex].."\".")
  456.         if (string.lower(peripheral.getType(peripheralList[peripheralIndex])) == deviceType) then
  457.             -- Log devices found which match deviceType and which device index we give them
  458.             printLog("Found "..peripheral.getType(peripheralList[peripheralIndex]).."["..peripheralIndex.."] as index \"["..deviceIndex.."]\" attached as \""..peripheralList[peripheralIndex].."\".")
  459.             write("Found "..peripheral.getType(peripheralList[peripheralIndex]).."["..peripheralIndex.."] as index \"["..deviceIndex.."]\" attached as \""..peripheralList[peripheralIndex].."\".\n")
  460.             deviceNames[deviceIndex] = peripheralList[peripheralIndex]
  461.             deviceList[deviceIndex] = peripheral.wrap(peripheralList[peripheralIndex])
  462.             deviceIndex = deviceIndex + 1
  463.         end
  464.     end -- for peripheralIndex = 1, #peripheralList do
  465.  
  466.     return deviceList, deviceNames
  467. end -- function getDevices(deviceType)
  468.  
  469. -- Draw a line across the entire x-axis
  470. local function drawLine(yPos, monitorIndex)
  471.     local monitor = nil
  472.     monitor = monitorList[monitorIndex]
  473.  
  474.     if not monitor then
  475.         printLog("monitor["..monitorIndex.."] in drawLine() is NOT a valid monitor.")
  476.         return -- Invalid monitorIndex
  477.     end
  478.  
  479.     local width, height = monitor.getSize()
  480.  
  481.     for i=1, width do
  482.         monitor.setCursorPos(i, yPos)
  483.         monitor.write("-")
  484.     end
  485. end -- function drawLine(yPos,monitorIndex)
  486.  
  487.  
  488. -- Display a solid bar of specified color
  489. local function drawBar(startXPos, startYPos, endXPos, endYPos, color, monitorIndex)
  490.     local monitor = nil
  491.     monitor = monitorList[monitorIndex]
  492.  
  493.     if not monitor then
  494.         printLog("monitor["..monitorIndex.."] in drawBar() is NOT a valid monitor.")
  495.         return -- Invalid monitorIndex
  496.     end
  497.  
  498.     -- PaintUtils only outputs to term., not monitor.
  499.     -- See http://www.computercraft.info/forums2/index.php?/topic/15540-paintutils-on-a-monitor/
  500.     term.redirect(monitor)
  501.     paintutils.drawLine(startXPos, startYPos, endXPos, endYPos, color)
  502.     monitor.setBackgroundColor(colors.black) -- PaintUtils doesn't restore the color
  503.     termRestore()
  504. end -- function drawBar(startXPos, startYPos,endXPos,endYPos,color,monitorIndex)
  505.  
  506.  
  507. -- Display single pixel color
  508. local function drawPixel(xPos, yPos, color, monitorIndex)
  509.     local monitor = nil
  510.     monitor = monitorList[monitorIndex]
  511.  
  512.     if not monitor then
  513.         printLog("monitor["..monitorIndex.."] in drawPixel() is NOT a valid monitor.")
  514.         return -- Invalid monitorIndex
  515.     end
  516.  
  517.     -- PaintUtils only outputs to term., not monitor.
  518.     -- See http://www.computercraft.info/forums2/index.php?/topic/15540-paintutils-on-a-monitor/
  519.     term.redirect(monitor)
  520.     paintutils.drawPixel(xPos, yPos, color)
  521.     monitor.setBackgroundColor(colors.black) -- PaintUtils doesn't restore the color
  522.     termRestore()
  523. end -- function drawPixel(xPos, yPos, color, monitorIndex)
  524.  
  525. local function saveMonitorAssignments()
  526.     local assignments = {}
  527.     for monitor, data in pairs(monitorAssignments) do
  528.         local name = nil
  529.         if (data.type == "Reactor") then
  530.             name = data.reactorName
  531.         elseif (data.type == "Turbine") then
  532.             name = data.turbineName
  533.         else
  534.             name = data.type
  535.         end
  536.         assignments[monitor] = name
  537.     end
  538.     config.save(monitorOptionFileName, {Monitors = assignments})
  539. end
  540.  
  541. UI = {
  542.     monitorIndex = 1,
  543.     reactorIndex = 1,
  544.     turbineIndex = 1
  545. }
  546.  
  547. UI.handlePossibleClick = function(self)
  548.     local monitorData = monitorAssignments[sideClick]
  549.     if monitorData == nil then
  550.         printLog("UI.handlePossibleClick(): "..sideClick.." is unassigned, can't handle click", WARN)
  551.         return
  552.     end
  553.  
  554.     self.monitorIndex = monitorData.index
  555.     local width, height = monitorList[self.monitorIndex].getSize()
  556.     -- All the last line are belong to us
  557.     if (yClick == height) then
  558.         if (monitorData.type == "Reactor") then
  559.             if (xClick == 1) then
  560.                 self:selectPrevReactor()
  561.             elseif (xClick == width) then
  562.                 self:selectNextReactor()
  563.             elseif (3 <= xClick and xClick <= width - 2) then
  564.                 self:selectTurbine()
  565.             end
  566.         elseif (monitorData.type == "Turbine") then
  567.             if (xClick == 1) then
  568.                 self:selectPrevTurbine()
  569.             elseif (xClick == width) then
  570.                 self:selectNextTurbine()
  571.             elseif (3 <= xClick and xClick <= width - 2) then
  572.                 self:selectStatus()
  573.             end
  574.         elseif (monitorData.type == "Status") then
  575.             if (xClick == 1) then
  576.                 self.turbineIndex = #turbineList
  577.                 self:selectTurbine()
  578.             elseif (xClick == width) then
  579.                 self.reactorIndex = 1
  580.                 self:selectReactor()
  581.             elseif (3 <= xClick and xClick <= width - 2) then
  582.                 self:selectReactor()
  583.             end
  584.         else
  585.             self:selectStatus()
  586.         end
  587.         -- Yes, that means we're skipping Debug. I figure everyone who wants that is
  588.         -- bound to use the console key commands anyway, and that way we don't have
  589.         -- it interfere with regular use.
  590.  
  591.         sideClick, xClick, yClick = 0, 0, 0
  592.     else
  593.         if (monitorData.type == "Turbine") then
  594.             self:handleTurbineMonitorClick(monitorData.turbineIndex, monitorData.index)
  595.         elseif (monitorData.type == "Reactor") then
  596.             self:handleReactorMonitorClick(monitorData.reactorIndex, monitorData.index)
  597.         end
  598.     end
  599. end -- UI.handlePossibleClick()
  600.  
  601. UI.logChange = function(self, messageText)
  602.     printLog("UI: "..messageText)
  603.     termRestore()
  604.     write(messageText.."\n")
  605. end
  606.  
  607. UI.selectNextMonitor = function(self)
  608.     self.monitorIndex = self.monitorIndex + 1
  609.     if self.monitorIndex > #monitorList then
  610.         self.monitorIndex = 1
  611.     end
  612.     local messageText = "Selected monitor "..monitorNames[self.monitorIndex]
  613.     self:logChange(messageText)
  614. end -- UI.selectNextMonitor()
  615.  
  616.    
  617. UI.selectReactor = function(self)
  618.     monitorAssignments[monitorNames[self.monitorIndex]] = {type="Reactor", index=self.monitorIndex, reactorName=reactorNames[self.reactorIndex], reactorIndex=self.reactorIndex}
  619.     saveMonitorAssignments()
  620.     local messageText = "Selected reactor "..reactorNames[self.reactorIndex].." for display on "..monitorNames[self.monitorIndex]
  621.     self:logChange(messageText)
  622. end -- UI.selectReactor()
  623.    
  624. UI.selectPrevReactor = function(self)
  625.     if self.reactorIndex <= 1 then
  626.         self.reactorIndex = #reactorList
  627.         self:selectStatus()
  628.     else
  629.         self.reactorIndex = self.reactorIndex - 1
  630.         self:selectReactor()
  631.     end
  632. end -- UI.selectPrevReactor()
  633.  
  634. UI.selectNextReactor = function(self)
  635.     if self.reactorIndex >= #reactorList then
  636.         self.reactorIndex = 1
  637.         self.turbineIndex = 1
  638.         self:selectTurbine()
  639.     else
  640.         self.reactorIndex = self.reactorIndex + 1
  641.         self:selectReactor()
  642.     end
  643. end -- UI.selectNextReactor()
  644.  
  645.  
  646. UI.selectTurbine = function(self)
  647.     monitorAssignments[monitorNames[self.monitorIndex]] = {type="Turbine", index=self.monitorIndex, turbineName=turbineNames[self.turbineIndex], turbineIndex=self.turbineIndex}
  648.     saveMonitorAssignments()
  649.     local messageText = "Selected turbine "..turbineNames[self.turbineIndex].." for display on "..monitorNames[self.monitorIndex]
  650.     self:logChange(messageText)
  651. end -- UI.selectTurbine()
  652.    
  653. UI.selectPrevTurbine = function(self)
  654.     if self.turbineIndex <= 1 then
  655.         self.turbineIndex = #turbineList
  656.         self.reactorIndex = #reactorList
  657.         self:selectReactor()
  658.     else
  659.         self.turbineIndex = self.turbineIndex - 1
  660.         self:selectTurbine()
  661.     end
  662. end -- UI.selectPrevTurbine()
  663.    
  664. UI.selectNextTurbine = function(self)
  665.     if self.turbineIndex >= #turbineList then
  666.         self.turbineIndex = 1
  667.         self:selectStatus()
  668.     else
  669.         self.turbineIndex = self.turbineIndex + 1
  670.         self:selectTurbine()
  671.     end
  672. end -- UI.selectNextTurbine()
  673.    
  674.  
  675. UI.selectStatus = function(self)
  676.     monitorAssignments[monitorNames[self.monitorIndex]] = {type="Status", index=self.monitorIndex}
  677.     saveMonitorAssignments()
  678.     local messageText = "Selected status summary for display on "..monitorNames[self.monitorIndex]
  679.     self:logChange(messageText)
  680. end -- UI.selectStatus()
  681.    
  682. UI.selectDebug = function(self)
  683.     monitorAssignments[monitorNames[self.monitorIndex]] = {type="Debug", index=self.monitorIndex}
  684.     saveMonitorAssignments()
  685.     monitorList[self.monitorIndex].clear()
  686.     local messageText = "Selected debug output for display on "..monitorNames[self.monitorIndex]
  687.     self:logChange(messageText)
  688. end -- UI.selectDebug()
  689.    
  690. -- Allow controlling Reactor Control Rod Level from GUI
  691. UI.handleReactorMonitorClick = function(self, reactorIndex, monitorIndex)
  692.  
  693.     -- Decrease rod button: 23X, 4Y
  694.     -- Increase rod button: 28X, 4Y
  695.  
  696.     -- Grab current monitor
  697.     local monitor = nil
  698.     monitor = monitorList[monitorIndex]
  699.     if not monitor then
  700.         printLog("monitor["..monitorIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid monitor.")
  701.         return -- Invalid monitorIndex
  702.     end
  703.  
  704.     -- Grab current reactor
  705.     local reactor = nil
  706.     reactor = reactorList[reactorIndex]
  707.     if not reactor then
  708.         printLog("reactor["..reactorIndex.."] in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT a valid Big Reactor.")
  709.         return -- Invalid reactorIndex
  710.     else
  711.         printLog("reactor["..reactorIndex.."] in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is a valid Big Reactor.")
  712.         if reactor.getConnected() then
  713.             printLog("reactor["..reactorIndex.."] in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is connected.")
  714.         else
  715.             printLog("reactor["..reactorIndex.."] in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT connected.")
  716.             return -- Disconnected reactor
  717.         end -- if reactor.getConnected() then
  718.     end -- if not reactor then
  719.  
  720.     local reactorStatus = _G[reactorNames[reactorIndex]]["ReactorOptions"]["Status"]
  721.  
  722.     local width, height = monitor.getSize()
  723.     if xClick >= (width - string.len(reactorStatus) - 1) and xClick <= (width-1) and (sideClick == monitorNames[monitorIndex]) then
  724.         if yClick == 1 then
  725.             reactor.setActive(not reactor.getActive()) -- Toggle reactor status
  726.             _G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] = reactor.getActive()
  727.             config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]])
  728.             sideClick, xClick, yClick = 0, 0, 0 -- Reset click after we register it
  729.  
  730.             -- If someone offlines the reactor (offline after a status click was detected), then disable autoStart
  731.             if not reactor.getActive() then
  732.                 _G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] = false
  733.             end
  734.         end -- if yClick == 1 then
  735.     end -- if (xClick >= (width - string.len(reactorStatus) - 1) and xClick <= (width-1)) and (sideClick == monitorNames[monitorIndex]) then
  736.  
  737.     -- Allow disabling rod level auto-adjust and only manual rod level control
  738.     if ((xClick > 23 and xClick < 28 and yClick == 4)
  739.             or (xClick > 20 and xClick < 27 and yClick == 9))
  740.             and (sideClick == monitorNames[monitorIndex]) then
  741.         _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] = not _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]
  742.         config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]])
  743.         sideClick, xClick, yClick = 0, 0, 0 -- Reset click after we register it
  744.     end -- if (xClick > 23) and (xClick < 28) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
  745.  
  746.     local rodPercentage = math.ceil(reactor.getControlRodLevel(0))
  747.     local newRodPercentage = rodPercentage
  748.     if (xClick == 23) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
  749.         printLog("Decreasing Rod Levels in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
  750.         --Decrease rod level by amount
  751.         newRodPercentage = rodPercentage - (5 * controlRodAdjustAmount)
  752.         if newRodPercentage < 0 then
  753.             newRodPercentage = 0
  754.         end
  755.         sideClick, xClick, yClick = 0, 0, 0
  756.  
  757.         printLog("Setting reactor["..reactorIndex.."] Rod Levels to "..newRodPercentage.."% in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
  758.         reactor.setAllControlRodLevels(newRodPercentage)
  759.         _G[reactorNames[reactorIndex]]["ReactorOptions"]["baseControlRodLevel"] = newRodPercentage
  760.  
  761.         -- Save updated rod percentage
  762.         config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]])
  763.         rodPercentage = newRodPercentage
  764.     elseif (xClick == 29) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
  765.         printLog("Increasing Rod Levels in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
  766.         --Increase rod level by amount
  767.         newRodPercentage = rodPercentage + (5 * controlRodAdjustAmount)
  768.         if newRodPercentage > 100 then
  769.             newRodPercentage = 100
  770.         end
  771.         sideClick, xClick, yClick = 0, 0, 0
  772.  
  773.         printLog("Setting reactor["..reactorIndex.."] Rod Levels to "..newRodPercentage.."% in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
  774.         reactor.setAllControlRodLevels(newRodPercentage)
  775.         _G[reactorNames[reactorIndex]]["ReactorOptions"]["baseControlRodLevel"] = newRodPercentage
  776.        
  777.         -- Save updated rod percentage
  778.         config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]])
  779.         rodPercentage = round(newRodPercentage,0)
  780.     else
  781.         printLog("No change to Rod Levels requested by "..progName.." GUI in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
  782.     end -- if (xClick == 29) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
  783. end -- UI.handleReactorMonitorClick = function(self, reactorIndex, monitorIndex)
  784.  
  785. -- Allow controlling Turbine Flow Rate from GUI
  786. UI.handleTurbineMonitorClick = function(self, turbineIndex, monitorIndex)
  787.  
  788.     -- Decrease flow rate button: 22X, 4Y
  789.     -- Increase flow rate button: 28X, 4Y
  790.  
  791.     -- Grab current monitor
  792.     local monitor = nil
  793.     monitor = monitorList[monitorIndex]
  794.     if not monitor then
  795.         printLog("monitor["..monitorIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid monitor.")
  796.         return -- Invalid monitorIndex
  797.     end
  798.  
  799.     -- Grab current turbine
  800.     local turbine = nil
  801.     turbine = turbineList[turbineIndex]
  802.     if not turbine then
  803.         printLog("turbine["..turbineIndex.."] in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid Big Turbine.")
  804.         return -- Invalid turbineIndex
  805.     else
  806.         printLog("turbine["..turbineIndex.."] in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is a valid Big Turbine.")
  807.         if turbine.getConnected() then
  808.             printLog("turbine["..turbineIndex.."] in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is connected.")
  809.         else
  810.             printLog("turbine["..turbineIndex.."] in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT connected.")
  811.             return -- Disconnected turbine
  812.         end -- if turbine.getConnected() then
  813.     end
  814.  
  815.     local turbineBaseSpeed = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"])
  816.     local turbineFlowRate = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"])
  817.     local turbineStatus = _G[turbineNames[turbineIndex]]["TurbineOptions"]["Status"]
  818.     local width, height = monitor.getSize()
  819.  
  820.     if (xClick >= (width - string.len(turbineStatus) - 1)) and (xClick <= (width-1)) and (sideClick == monitorNames[monitorIndex]) then
  821.         if yClick == 1 then
  822.             turbine.setActive(not turbine.getActive()) -- Toggle turbine status
  823.             _G[turbineNames[turbineIndex]]["TurbineOptions"]["autoStart"] = turbine.getActive()
  824.             config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
  825.             sideClick, xClick, yClick = 0, 0, 0 -- Reset click after we register it
  826.             config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
  827.         end -- if yClick == 1 then
  828.     end -- if (xClick >= (width - string.len(turbineStatus) - 1)) and (xClick <= (width-1)) and (sideClick == monitorNames[monitorIndex]) then
  829.  
  830.     -- Allow disabling/enabling flow rate auto-adjust
  831.     if (xClick > 23 and xClick < 28 and yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
  832.         _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] = true
  833.         sideClick, xClick, yClick = 0, 0, 0 -- Reset click after we register it
  834.     elseif (xClick > 20 and xClick < 27 and yClick == 10) and (sideClick == monitorNames[monitorIndex]) then
  835.        
  836.         if ((_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"]) or (_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] == "true")) then
  837.             _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] = false
  838.         else
  839.             _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] = true
  840.         end
  841.         sideClick, xClick, yClick = 0, 0, 0 -- Reset click after we register it
  842.         config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
  843.     end
  844.  
  845.     if (xClick == 22) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
  846.         printLog("Decrease to Flow Rate requested by "..progName.." GUI in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
  847.         --Decrease rod level by amount
  848.         newTurbineFlowRate = turbineFlowRate - flowRateAdjustAmount
  849.         if newTurbineFlowRate < 0 then
  850.             newTurbineFlowRate = 0
  851.         end
  852.         sideClick, xClick, yClick = 0, 0, 0
  853.  
  854.         -- Check bounds [0,2000]
  855.         if newTurbineFlowRate > 2000 then
  856.             newTurbineFlowRate = 2000
  857.         elseif newTurbineFlowRate < 0 then
  858.             newTurbineFlowRate = 0
  859.         end
  860.  
  861.         turbine.setFluidFlowRateMax(newTurbineFlowRate)
  862.         _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"] = newTurbineFlowRate
  863.         -- Save updated Turbine Flow Rate
  864.         turbineFlowRate = newTurbineFlowRate
  865.         config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
  866.     elseif (xClick == 29) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
  867.         printLog("Increase to Flow Rate requested by "..progName.." GUI in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
  868.         --Increase rod level by amount
  869.         newTurbineFlowRate = turbineFlowRate + flowRateAdjustAmount
  870.         if newTurbineFlowRate > 2000 then
  871.             newTurbineFlowRate = 2000
  872.         end
  873.         sideClick, xClick, yClick = 0, 0, 0
  874.  
  875.         -- Check bounds [0,2000]
  876.         if newTurbineFlowRate > 2000 then
  877.             newTurbineFlowRate = 2000
  878.         elseif newTurbineFlowRate < 0 then
  879.             newTurbineFlowRate = 0
  880.         end
  881.  
  882.         turbine.setFluidFlowRateMax(newTurbineFlowRate)
  883.        
  884.         -- Save updated Turbine Flow Rate
  885.         turbineFlowRate = math.ceil(newTurbineFlowRate)
  886.         _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"] = turbineFlowRate
  887.         config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
  888.     else
  889.         printLog("No change to Flow Rate requested by "..progName.." GUI in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
  890.     end -- if (xClick == 29) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
  891.  
  892.     if (xClick == 22) and (yClick == 6) and (sideClick == monitorNames[monitorIndex]) then
  893.         printLog("Decrease to Turbine RPM requested by "..progName.." GUI in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
  894.         rpmRateAdjustment = 909
  895.         newTurbineBaseSpeed = turbineBaseSpeed - rpmRateAdjustment
  896.         if newTurbineBaseSpeed < 908 then
  897.             newTurbineBaseSpeed = 908
  898.         end
  899.         sideClick, xClick, yClick = 0, 0, 0
  900.         _G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"] = newTurbineBaseSpeed
  901.         config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
  902.     elseif (xClick == 29) and (yClick == 6) and (sideClick == monitorNames[monitorIndex]) then
  903.         printLog("Increase to Turbine RPM requested by "..progName.." GUI in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
  904.         rpmRateAdjustment = 909
  905.         newTurbineBaseSpeed = turbineBaseSpeed + rpmRateAdjustment
  906.         if newTurbineBaseSpeed > 2726 then
  907.             newTurbineBaseSpeed = 2726
  908.         end
  909.         sideClick, xClick, yClick = 0, 0, 0
  910.         _G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"] = newTurbineBaseSpeed
  911.         config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
  912.     else
  913.         printLog("No change to Turbine RPM requested by "..progName.." GUI in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
  914.     end -- if (xClick == 29) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
  915. end -- function handleTurbineMonitorClick(turbineIndex, monitorIndex)
  916.  
  917.  
  918. -- End helper functions
  919.  
  920.  
  921. -- Then initialize the monitors
  922. local function findMonitors()
  923.     -- Empty out old list of monitors
  924.     monitorList = {}
  925.  
  926.     printLog("Finding monitors...")
  927.     monitorList, monitorNames = getDevices("monitor")
  928.  
  929.     if #monitorList == 0 then
  930.         printLog("No monitors found, continuing headless")
  931.     else
  932.         for monitorIndex = 1, #monitorList do
  933.             local monitor, monitorX, monitorY = nil, nil, nil
  934.             monitor = monitorList[monitorIndex]
  935.  
  936.             if not monitor then
  937.                 printLog("monitorList["..monitorIndex.."] in findMonitors() is NOT a valid monitor.")
  938.  
  939.                 table.remove(monitorList, monitorIndex) -- Remove invalid monitor from list
  940.                 if monitorIndex ~= #monitorList then    -- If we're not at the end, clean up
  941.                     monitorIndex = monitorIndex - 1 -- We just removed an element
  942.                 end -- if monitorIndex == #monitorList then
  943.                 break -- Invalid monitorIndex
  944.             else -- valid monitor
  945.                 monitor.setTextScale(1.0) -- Make sure scale is correct
  946.                 monitorX, monitorY = monitor.getSize()
  947.  
  948.                 if (monitorX == nil) or (monitorY == nil) then -- somehow a valid monitor, but non-existent sizes? Maybe fixes Issue #3
  949.                     printLog("monitorList["..monitorIndex.."] in findMonitors() is NOT a valid sized monitor.")
  950.  
  951.                     table.remove(monitorList, monitorIndex) -- Remove invalid monitor from list
  952.                     if monitorIndex ~= #monitorList then    -- If we're not at the end, clean up
  953.                         monitorIndex = monitorIndex - 1 -- We just removed an element
  954.                     end -- if monitorIndex == #monitorList then
  955.                     break -- Invalid monitorIndex
  956.  
  957.                 -- Check for minimum size to allow for monitor.setTextScale(0.5) to work for 3x2 debugging monitor, changes getSize()
  958.                 elseif monitorX < 29 or monitorY < 12 then
  959.                     term.redirect(monitor)
  960.                     monitor.clear()
  961.                     printLog("Removing monitor "..monitorIndex.." for being too small.")
  962.                     monitor.setCursorPos(1,2)
  963.                     write("Monitor is the wrong size!\n")
  964.                     write("Needs to be at least 3x2.")
  965.                     termRestore()
  966.  
  967.                     table.remove(monitorList, monitorIndex) -- Remove invalid monitor from list
  968.                     if monitorIndex == #monitorList then    -- If we're at the end already, break from loop
  969.                         break
  970.                     else
  971.                         monitorIndex = monitorIndex - 1 -- We just removed an element
  972.                     end -- if monitorIndex == #monitorList then
  973.  
  974.                 end -- if monitorX < 29 or monitorY < 12 then
  975.             end -- if not monitor then
  976.  
  977.             printLog("Monitor["..monitorIndex.."] named \""..monitorNames[monitorIndex].."\" is a valid monitor of size x:"..monitorX.." by y:"..monitorY..".")
  978.         end -- for monitorIndex = 1, #monitorList do
  979.     end -- if #monitorList == 0 then
  980.  
  981.     printLog("Found "..#monitorList.." monitor(s) in findMonitors().")
  982. end -- local function findMonitors()
  983.  
  984.  
  985. -- Initialize all Big Reactors - Reactors
  986. local function findReactors()
  987.     -- Empty out old list of reactors
  988.     newReactorList = {}
  989.     printLog("Finding reactors...")
  990.     newReactorList, reactorNames = getDevices("BigReactors-Reactor")
  991.  
  992.     if #newReactorList == 0 then
  993.         printLog("No reactors found!")
  994.         error("Can't find any reactors!")
  995.     else  -- Placeholder
  996.         for reactorIndex = 1, #newReactorList do
  997.             local reactor = nil
  998.             reactor = newReactorList[reactorIndex]
  999.  
  1000.             if not reactor then
  1001.                 printLog("reactorList["..reactorIndex.."] in findReactors() is NOT a valid Big Reactor.")
  1002.  
  1003.                 table.remove(newReactorList, reactorIndex) -- Remove invalid reactor from list
  1004.                 if reactorIndex ~= #newReactorList then    -- If we're not at the end, clean up
  1005.                     reactorIndex = reactorIndex - 1 -- We just removed an element
  1006.                 end -- reactorIndex ~= #newReactorList then
  1007.                 return -- Invalid reactorIndex
  1008.             else
  1009.                 printLog("reactor["..reactorIndex.."] in findReactors() is a valid Big Reactor.")
  1010.                 --initialize the default table
  1011.                 _G[reactorNames[reactorIndex]] = {}
  1012.                 _G[reactorNames[reactorIndex]]["ReactorOptions"] = {}
  1013.                 _G[reactorNames[reactorIndex]]["ReactorOptions"]["baseControlRodLevel"] = 80
  1014.                 _G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"] = 0
  1015.                 _G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] = true
  1016.                 _G[reactorNames[reactorIndex]]["ReactorOptions"]["activeCooled"] = true
  1017.                 _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorMaxTemp"] = 1400 --set for passive-cooled, the active-cooled subroutine will correct it
  1018.                 _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorMinTemp"] = 1000
  1019.                 _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] = false
  1020.                 _G[reactorNames[reactorIndex]]["ReactorOptions"]["controlRodAdjustAmount"] = controlRodAdjustAmount
  1021.                 _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorName"] = reactorNames[reactorIndex]
  1022.                 _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorCruising"] = false
  1023.             end
  1024.            
  1025.             --failsafe
  1026.             local tempTable = _G[reactorNames[reactorIndex]]
  1027.            
  1028.             --check to make sure we get a valid config
  1029.             if (config.load(reactorNames[reactorIndex]..".options")) ~= nil then
  1030.                 tempTable = config.load(reactorNames[reactorIndex]..".options")
  1031.             else
  1032.                 --if we don't have a valid config from disk, make a valid config
  1033.                 config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]])
  1034.             end
  1035.            
  1036.             --load values from tempTable, checking for nil values along the way
  1037.             if tempTable["ReactorOptions"]["baseControlRodLevel"] ~= nil then
  1038.                 _G[reactorNames[reactorIndex]]["ReactorOptions"]["baseControlRodLevel"] = tempTable["ReactorOptions"]["baseControlRodLevel"]
  1039.             end
  1040.            
  1041.             if tempTable["ReactorOptions"]["lastTempPoll"] ~= nil then
  1042.                 _G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"] = tempTable["ReactorOptions"]["lastTempPoll"]
  1043.             end
  1044.            
  1045.             if tempTable["ReactorOptions"]["autoStart"] ~= nil then
  1046.                 _G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] = tempTable["ReactorOptions"]["autoStart"]
  1047.             end
  1048.            
  1049.             if tempTable["ReactorOptions"]["activeCooled"] ~= nil then
  1050.                 _G[reactorNames[reactorIndex]]["ReactorOptions"]["activeCooled"] = tempTable["ReactorOptions"]["activeCooled"]
  1051.             end
  1052.            
  1053.             if tempTable["ReactorOptions"]["reactorMaxTemp"] ~= nil then
  1054.                 _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorMaxTemp"] = tempTable["ReactorOptions"]["reactorMaxTemp"]
  1055.             end
  1056.            
  1057.             if tempTable["ReactorOptions"]["reactorMinTemp"] ~= nil then
  1058.                 _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorMinTemp"] = tempTable["ReactorOptions"]["reactorMinTemp"]
  1059.             end
  1060.            
  1061.             if tempTable["ReactorOptions"]["rodOverride"] ~= nil then
  1062.                 printLog("Got value from config file for Rod Override, the value is: "..tostring(tempTable["ReactorOptions"]["rodOverride"]).." EOL")
  1063.                 _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] = tempTable["ReactorOptions"]["rodOverride"]
  1064.             end
  1065.            
  1066.             if tempTable["ReactorOptions"]["controlRodAdjustAmount"] ~= nil then
  1067.                 _G[reactorNames[reactorIndex]]["ReactorOptions"]["controlRodAdjustAmount"] = tempTable["ReactorOptions"]["controlRodAdjustAmount"]
  1068.             end
  1069.            
  1070.             if tempTable["ReactorOptions"]["reactorName"] ~= nil then
  1071.                 _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorName"] = tempTable["ReactorOptions"]["reactorName"]
  1072.             end
  1073.            
  1074.             if tempTable["ReactorOptions"]["reactorCruising"] ~= nil then
  1075.                 _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorCruising"] = tempTable["ReactorOptions"]["reactorCruising"]
  1076.             end
  1077.            
  1078.             --stricter typing, let's set these puppies up with the right type of value.
  1079.             _G[reactorNames[reactorIndex]]["ReactorOptions"]["baseControlRodLevel"] = tonumber(_G[reactorNames[reactorIndex]]["ReactorOptions"]["baseControlRodLevel"])
  1080.            
  1081.             _G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"] = tonumber(_G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"])
  1082.            
  1083.             if (tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"]) == "true") then
  1084.                 _G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] = true
  1085.             else
  1086.                 _G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] = false
  1087.             end
  1088.            
  1089.             if (tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["activeCooled"]) == "true") then
  1090.                 _G[reactorNames[reactorIndex]]["ReactorOptions"]["activeCooled"] = true
  1091.             else
  1092.                 _G[reactorNames[reactorIndex]]["ReactorOptions"]["activeCooled"] = false
  1093.             end
  1094.            
  1095.             _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorMaxTemp"] = tonumber(_G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorMaxTemp"])
  1096.            
  1097.             _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorMinTemp"] = tonumber(_G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorMinTemp"])
  1098.            
  1099.             if (tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]) == "true") then
  1100.                 printLog("Setting Rod Override for  "..reactorNames[reactorIndex].." to true because value was "..tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]).." EOL")
  1101.                 _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] = true
  1102.             else
  1103.                 printLog("Setting Rod Override for  "..reactorNames[reactorIndex].." to false because value was "..tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]).." EOL")
  1104.                 _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] = false
  1105.             end
  1106.            
  1107.             _G[reactorNames[reactorIndex]]["ReactorOptions"]["controlRodAdjustAmount"] = tonumber(_G[reactorNames[reactorIndex]]["ReactorOptions"]["controlRodAdjustAmount"])
  1108.  
  1109.             if (tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorCruising"]) == "true") then
  1110.                 _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorCruising"] = true
  1111.             else
  1112.                 _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorCruising"] = false
  1113.             end
  1114.                        
  1115.             --save one more time, in case we didn't have a complete config file before
  1116.             config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]])
  1117.         end -- for reactorIndex = 1, #newReactorList do
  1118.     end -- if #newReactorList == 0 then
  1119.  
  1120.     -- Overwrite old reactor list with the now updated list
  1121.     reactorList = newReactorList
  1122.  
  1123.     printLog("Found "..#reactorList.." reactor(s) in findReactors().")
  1124. end -- function findReactors()
  1125.  
  1126.  
  1127. -- Initialize all Big Reactors - Turbines
  1128. local function findTurbines()
  1129.     -- Empty out old list of turbines
  1130.     newTurbineList = {}
  1131.  
  1132.     printLog("Finding turbines...")
  1133.     newTurbineList, turbineNames = getDevices("BigReactors-Turbine")
  1134.  
  1135.     if #newTurbineList == 0 then
  1136.         printLog("No turbines found") -- Not an error
  1137.     else
  1138.         for turbineIndex = 1, #newTurbineList do
  1139.             local turbine = nil
  1140.             turbine = newTurbineList[turbineIndex]
  1141.  
  1142.             if not turbine then
  1143.                 printLog("turbineList["..turbineIndex.."] in findTurbines() is NOT a valid Big Reactors Turbine.")
  1144.  
  1145.                 table.remove(newTurbineList, turbineIndex) -- Remove invalid turbine from list
  1146.                 if turbineIndex ~= #newTurbineList then    -- If we're not at the end, clean up
  1147.                     turbineIndex = turbineIndex - 1 -- We just removed an element
  1148.                 end -- turbineIndex ~= #newTurbineList then
  1149.  
  1150.                 return -- Invalid turbineIndex
  1151.             else
  1152.            
  1153.                 _G[turbineNames[turbineIndex]] = {}
  1154.                 _G[turbineNames[turbineIndex]]["TurbineOptions"] = {}
  1155.                 _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastSpeed"] = 0
  1156.                 _G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"] = 2726
  1157.                 _G[turbineNames[turbineIndex]]["TurbineOptions"]["autoStart"] = true
  1158.                 _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"] = 2000 --open up with all the steam wide open
  1159.                 _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] = false
  1160.                 _G[turbineNames[turbineIndex]]["TurbineOptions"]["turbineName"] = turbineNames[turbineIndex]
  1161.                 printLog("turbineList["..turbineIndex.."] in findTurbines() is a valid Big Reactors Turbine.")
  1162.             end
  1163.            
  1164.             --failsafe
  1165.             local tempTable = _G[turbineNames[turbineIndex]]
  1166.            
  1167.             --check to make sure we get a valid config
  1168.             if (config.load(turbineNames[turbineIndex]..".options")) ~= nil then
  1169.                 tempTable = config.load(turbineNames[turbineIndex]..".options")
  1170.             else
  1171.                 --if we don't have a valid config from disk, make a valid config
  1172.                 config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
  1173.             end
  1174.            
  1175.             --load values from tempTable, checking for nil values along the way
  1176.             if tempTable["TurbineOptions"]["LastSpeed"] ~= nil then
  1177.                 _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastSpeed"] = tempTable["TurbineOptions"]["LastSpeed"]
  1178.             end
  1179.            
  1180.             if tempTable["TurbineOptions"]["BaseSpeed"] ~= nil then
  1181.                 _G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"] = tempTable["TurbineOptions"]["BaseSpeed"]
  1182.             end
  1183.            
  1184.             if tempTable["TurbineOptions"]["autoStart"] ~= nil then
  1185.                 _G[turbineNames[turbineIndex]]["TurbineOptions"]["autoStart"] = tempTable["TurbineOptions"]["autoStart"]
  1186.             end
  1187.            
  1188.             if tempTable["TurbineOptions"]["LastFlow"] ~= nil then
  1189.                 _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"] = tempTable["TurbineOptions"]["LastFlow"]
  1190.             end
  1191.            
  1192.             if tempTable["TurbineOptions"]["flowOverride"] ~= nil then
  1193.                 _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] = tempTable["TurbineOptions"]["flowOverride"]
  1194.             end
  1195.            
  1196.             if tempTable["TurbineOptions"]["turbineName"] ~= nil then
  1197.                 _G[turbineNames[turbineIndex]]["TurbineOptions"]["turbineName"] = tempTable["TurbineOptions"]["turbineName"]
  1198.             end
  1199.            
  1200.             --save once more just to make sure we got it
  1201.             config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
  1202.         end -- for turbineIndex = 1, #newTurbineList do
  1203.  
  1204.         -- Overwrite old turbine list with the now updated list
  1205.         turbineList = newTurbineList
  1206.     end -- if #newTurbineList == 0 then
  1207.  
  1208.     printLog("Found "..#turbineList.." turbine(s) in findTurbines().")
  1209. end -- function findTurbines()
  1210.  
  1211. -- Assign status, reactors, turbines and debug output to the monitors that shall display them
  1212. -- Depends on the [monitor,reactor,turbine]Lists being populated already
  1213. local function assignMonitors()
  1214.  
  1215.     local monitors = {}
  1216.     monitorAssignments = {}
  1217.  
  1218.     printLog("Assigning monitors...")
  1219.  
  1220.     local m = config.load(monitorOptionFileName)
  1221.     if (m ~= nil) then
  1222.         -- first, merge the detected and the configured monitor lists
  1223.         -- this is to ensure we pick up new additions to the network
  1224.         for monitorIndex, monitorName in ipairs(monitorNames) do
  1225.             monitors[monitorName] = m.Monitors[monitorName] or ""
  1226.         end
  1227.         -- then, go through all of it again to build our runtime data structure
  1228.         for monitorName, assignedName in pairs(monitors) do
  1229.             printLog("Looking for monitor and device named "..monitorName.." and "..assignedName)
  1230.             for monitorIndex = 1, #monitorNames do
  1231.                 printLog("if "..monitorName.." == "..monitorNames[monitorIndex].." then", DEBUG)
  1232.                
  1233.                 if monitorName == monitorNames[monitorIndex] then
  1234.                     printLog("Found "..monitorName.." at index "..monitorIndex, DEBUG)
  1235.                     if assignedName == "Status" then
  1236.                         monitorAssignments[monitorName] = {type="Status", index=monitorIndex}
  1237.                     elseif assignedName == "Debug" then
  1238.                         monitorAssignments[monitorName] = {type="Debug", index=monitorIndex}
  1239.                     else
  1240.                         local maxListLen = math.max(#reactorNames, #turbineNames)
  1241.                         for i = 1, maxListLen do
  1242.                             if assignedName == reactorNames[i] then
  1243.                                 monitorAssignments[monitorName] = {type="Reactor", index=monitorIndex, reactorName=reactorNames[i], reactorIndex=i}
  1244.                                 break
  1245.                             elseif assignedName == turbineNames[i] then
  1246.                                 monitorAssignments[monitorName] = {type="Turbine", index=monitorIndex, turbineName=turbineNames[i], turbineIndex=i}
  1247.                                 break
  1248.                             elseif i == maxListLen then
  1249.                                 printLog("assignMonitors(): Monitor "..monitorName.." was configured to display nonexistant device "..assignedName..". Setting inactive.", WARN)
  1250.                                 monitorAssignments[monitorName] = {type="Inactive", index=monitorIndex}
  1251.                             end
  1252.                         end
  1253.                     end
  1254.                     break
  1255.                 elseif monitorIndex == #monitorNames then
  1256.                     printLog("assignMonitors(): Monitor "..monitorName.." not found. It was configured to display device "..assignedName..". Discarding.", WARN)
  1257.                 end
  1258.             end
  1259.         end
  1260.     else
  1261.         printLog("No valid monitor configuration found, generating...")
  1262.  
  1263.         -- create assignments that reflect the setup before 0.3.17
  1264.         local monitorIndex = 1
  1265.         monitorAssignments[monitorNames[1]] = {type="Status", index=1}
  1266.         monitorIndex = monitorIndex + 1
  1267.         for reactorIndex = 1, #reactorList do
  1268.             if monitorIndex > #monitorList then
  1269.                 break
  1270.             end
  1271.             monitorAssignments[monitorNames[monitorIndex]] = {type="Reactor", index=monitorIndex, reactorName=reactorNames[reactorIndex], reactorIndex=reactorIndex}
  1272.             printLog(monitorNames[monitorIndex].." -> "..reactorNames[reactorIndex])
  1273.  
  1274.             monitorIndex = monitorIndex + 1
  1275.         end
  1276.         for turbineIndex = 1, #turbineList do
  1277.             if monitorIndex > #monitorList then
  1278.                 break
  1279.             end
  1280.             monitorAssignments[monitorNames[monitorIndex]] = {type="Turbine", index=monitorIndex, turbineName=turbineNames[turbineIndex], turbineIndex=turbineIndex}
  1281.             printLog(monitorNames[monitorIndex].." -> "..turbineNames[turbineIndex])
  1282.  
  1283.             monitorIndex = monitorIndex + 1
  1284.         end
  1285.         if monitorIndex <= #monitorList then
  1286.             monitorAssignments[monitorNames[#monitorList]] = {type="Debug", index=#monitorList}
  1287.         end
  1288.     end
  1289.  
  1290.     tprint(monitorAssignments)
  1291.  
  1292.     saveMonitorAssignments()
  1293.  
  1294. end -- function assignMonitors()
  1295.  
  1296. local eventHandler
  1297. -- Replacement for sleep, which passes on events instead of dropping themo
  1298. -- Straight from http://computercraft.info/wiki/Os.sleep
  1299. local function wait(time)
  1300.     local timer = os.startTimer(time)
  1301.  
  1302.     while true do
  1303.         local event = {os.pullEvent()}
  1304.  
  1305.         if (event[1] == "timer" and event[2] == timer) then
  1306.             break
  1307.         else
  1308.             eventHandler(event[1], event[2], event[3], event[4])
  1309.         end
  1310.     end
  1311. end
  1312.  
  1313.  
  1314. -- Return current energy buffer in a specific reactor by %
  1315. local function getReactorStoredEnergyBufferPercent(reactor)
  1316.     printLog("Called as getReactorStoredEnergyBufferPercent(reactor).")
  1317.  
  1318.     if not reactor then
  1319.         printLog("getReactorStoredEnergyBufferPercent() did NOT receive a valid Big Reactor Reactor.")
  1320.         return -- Invalid reactorIndex
  1321.     else
  1322.         printLog("getReactorStoredEnergyBufferPercent() did receive a valid Big Reactor Reactor.")
  1323.     end
  1324.  
  1325.     local energyBufferStorage = reactor.getEnergyStored()
  1326.     return round(energyBufferStorage/100000, 1) -- (buffer/10000000 RF)*100%
  1327. end -- function getReactorStoredEnergyBufferPercent(reactor)
  1328.  
  1329.  
  1330. -- Return current energy buffer in a specific Turbine by %
  1331. local function getTurbineStoredEnergyBufferPercent(turbine)
  1332.     printLog("Called as getTurbineStoredEnergyBufferPercent(turbine)")
  1333.  
  1334.     if not turbine then
  1335.         printLog("getTurbineStoredEnergyBufferPercent() did NOT receive a valid Big Reactor Turbine.")
  1336.         return -- Invalid reactorIndex
  1337.     else
  1338.         printLog("getTurbineStoredEnergyBufferPercent() did receive a valid Big Reactor Turbine.")
  1339.     end
  1340.  
  1341.     local energyBufferStorage = turbine.getEnergyStored()
  1342.     return round(energyBufferStorage/10000, 1) -- (buffer/1000000 RF)*100%
  1343. end -- function getTurbineStoredEnergyBufferPercent(turbine)
  1344.  
  1345. local function reactorCruise(cruiseMaxTemp, cruiseMinTemp, reactorIndex)
  1346.     printLog("Called as reactorCruise(cruiseMaxTemp="..cruiseMaxTemp..",cruiseMinTemp="..cruiseMinTemp..",lastPolledTemp=".._G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"]..",reactorIndex="..reactorIndex..").")
  1347.    
  1348.     --sanitization
  1349.     local lastPolledTemp = tonumber(_G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"])
  1350.     cruiseMaxTemp = tonumber(cruiseMaxTemp)
  1351.     cruiseMinTemp = tonumber(cruiseMinTemp)
  1352.    
  1353.     if ((lastPolledTemp < cruiseMaxTemp) and (lastPolledTemp > cruiseMinTemp)) then
  1354.         local reactor = nil
  1355.         reactor = reactorList[reactorIndex]
  1356.         if not reactor then
  1357.             printLog("reactor["..reactorIndex.."] in reactorCruise(cruiseMaxTemp="..cruiseMaxTemp..",cruiseMinTemp="..cruiseMinTemp..",lastPolledTemp="..lastPolledTemp..",reactorIndex="..reactorIndex..") is NOT a valid Big Reactor.")
  1358.             return -- Invalid reactorIndex
  1359.         else
  1360.             printLog("reactor["..reactorIndex.."] in reactorCruise(cruiseMaxTemp="..cruiseMaxTemp..",cruiseMinTemp="..cruiseMinTemp..",lastPolledTemp="..lastPolledTemp..",reactorIndex="..reactorIndex..") is a valid Big Reactor.")
  1361.             if reactor.getConnected() then
  1362.                 printLog("reactor["..reactorIndex.."] in reactorCruise(cruiseMaxTemp="..cruiseMaxTemp..",cruiseMinTemp="..cruiseMinTemp..",lastPolledTemp="..lastPolledTemp..",reactorIndex="..reactorIndex..") is connected.")
  1363.             else
  1364.                 printLog("reactor["..reactorIndex.."] in reactorCruise(cruiseMaxTemp="..cruiseMaxTemp..",cruiseMinTemp="..cruiseMinTemp..",lastPolledTemp="..lastPolledTemp..",reactorIndex="..reactorIndex..") is NOT connected.")
  1365.                 return -- Disconnected reactor
  1366.             end -- if reactor.getConnected() then
  1367.         end -- if not reactor then
  1368.  
  1369.         local rodPercentage = math.ceil(reactor.getControlRodLevel(0))
  1370.         local reactorTemp = math.ceil(reactor.getFuelTemperature())
  1371.         _G[reactorNames[reactorIndex]]["ReactorOptions"]["baseControlRodLevel"] = rodPercentage
  1372.        
  1373.         if ((reactorTemp < cruiseMaxTemp) and (reactorTemp > cruiseMinTemp)) then
  1374.             if (reactorTemp < lastPolledTemp) then
  1375.                 rodPercentage = (rodPercentage - 1)
  1376.                 --Boundary check
  1377.                 if rodPercentage < 0 then
  1378.                     reactor.setAllControlRodLevels(0)
  1379.                 else
  1380.                     reactor.setAllControlRodLevels(rodPercentage)
  1381.                 end
  1382.             else
  1383.                 rodPercentage = (rodPercentage + 1)
  1384.                 --Boundary check
  1385.                 if rodPercentage > 99 then
  1386.                     reactor.setAllControlRodLevels(99)
  1387.                 else
  1388.                     reactor.setAllControlRodLevels(rodPercentage)
  1389.                 end
  1390.             end -- if (reactorTemp > lastPolledTemp) then
  1391.         else
  1392.             --disengage cruise, we've fallen out of the ideal temperature range
  1393.             _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorCruising"] = false
  1394.         end -- if ((reactorTemp < cruiseMaxTemp) and (reactorTemp > cruiseMinTemp)) then
  1395.     else
  1396.         --I don't know how we'd get here, but let's turn the cruise mode off
  1397.         _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorCruising"] = false
  1398.     end -- if ((lastPolledTemp < cruiseMaxTemp) and (lastPolledTemp > cruiseMinTemp)) then
  1399.     _G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"] = reactorTemp
  1400.     _G[reactorNames[reactorIndex]]["ReactorOptions"]["activeCooled"] = true
  1401.     _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorMaxTemp"] = cruiseMaxTemp
  1402.     _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorMinTemp"] = cruiseMinTemp
  1403.     config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]])
  1404. end -- function reactorCruise(cruiseMaxTemp, cruiseMinTemp, lastPolledTemp, reactorIndex)
  1405.  
  1406. -- Modify reactor control rod levels to keep temperature with defined parameters, but
  1407. -- wait an in-game half-hour for the temperature to stabalize before modifying again
  1408. local function temperatureControl(reactorIndex)
  1409.     printLog("Called as temperatureControl(reactorIndex="..reactorIndex..")")
  1410.  
  1411.     local reactor = nil
  1412.     reactor = reactorList[reactorIndex]
  1413.     if not reactor then
  1414.         printLog("reactor["..reactorIndex.."] in temperatureControl(reactorIndex="..reactorIndex..") is NOT a valid Big Reactor.")
  1415.         return -- Invalid reactorIndex
  1416.     else
  1417.         printLog("reactor["..reactorIndex.."] in temperatureControl(reactorIndex="..reactorIndex..") is a valid Big Reactor.")
  1418.  
  1419.         if reactor.getConnected() then
  1420.             printLog("reactor["..reactorIndex.."] in temperatureControl(reactorIndex="..reactorIndex..") is connected.")
  1421.         else
  1422.             printLog("reactor["..reactorIndex.."] in temperatureControl(reactorIndex="..reactorIndex..") is NOT connected.")
  1423.             return -- Disconnected reactor
  1424.         end -- if reactor.getConnected() then
  1425.     end
  1426.  
  1427.     local reactorNum = reactorIndex
  1428.     local rodPercentage = math.ceil(reactor.getControlRodLevel(0))
  1429.     local reactorTemp = math.ceil(reactor.getFuelTemperature())
  1430.     local localMinReactorTemp, localMaxReactorTemp = _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorMinTemp"], _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorMaxTemp"]
  1431.  
  1432.     --bypass if the reactor itself is set to not be auto-controlled
  1433.     if ((not _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]) or (_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] == "false")) then
  1434.         -- No point modifying control rod levels for temperature if the reactor is offline
  1435.         if reactor.getActive() then
  1436.             -- Actively cooled reactors should range between 0^C-300^C
  1437.             -- Actually, active-cooled reactors should range between 300 and 420C (Mechaet)
  1438.             -- Accordingly I changed the below lines
  1439.             if reactor.isActivelyCooled() and not knowlinglyOverride then
  1440.                 -- below was 0
  1441.                 localMinReactorTemp = 300
  1442.                 -- below was 300
  1443.                 localMaxReactorTemp = 420
  1444.             else
  1445.                 localMinReactorTemp = _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorMinTemp"]
  1446.                 localMaxReactorTemp = _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorMaxTemp"]
  1447.             end
  1448.             local lastTempPoll = _G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"]
  1449.             if _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorCruising"] then
  1450.                 --let's bypass all this math and hit the much-more-subtle cruise feature
  1451.                 --printLog("min: "..localMinReactorTemp..", max: "..localMaxReactorTemp..", lasttemp: "..lastTempPoll..", ri: "..reactorIndex.."  EOL")
  1452.                 reactorCruise(localMaxReactorTemp, localMinReactorTemp, reactorIndex)
  1453.             else
  1454.                 local localControlRodAdjustAmount = _G[reactorNames[reactorIndex]]["ReactorOptions"]["controlRodAdjustAmount"]
  1455.                 -- Don't bring us to 100, that's effectively a shutdown
  1456.                 if (reactorTemp > localMaxReactorTemp) and (rodPercentage ~= 99) then
  1457.                     --increase the rods, but by how much?
  1458.                     if (reactorTemp > lastTempPoll) then
  1459.                         --we're climbing, we need to get this to decrease
  1460.                         if ((reactorTemp - lastTempPoll) > 100) then
  1461.                             --we're climbing really fast, arrest it
  1462.                             if (rodPercentage + (10 * localControlRodAdjustAmount)) > 99 then
  1463.                                 reactor.setAllControlRodLevels(99)
  1464.                             else
  1465.                                 reactor.setAllControlRodLevels(rodPercentage + (10 * localControlRodAdjustAmount))
  1466.                             end
  1467.                         else
  1468.                             --we're not climbing by leaps and bounds, let's give it a rod adjustment based on temperature increase
  1469.                             local diffAmount = reactorTemp - lastTempPoll
  1470.                             diffAmount = (round(diffAmount/10, 0))/5
  1471.                             _G[reactorNames[reactorIndex]]["ReactorOptions"]["controlRodAdjustAmount"] = diffAmount
  1472.                             if (rodPercentage + diffAmount) > 99 then
  1473.                                 reactor.setAllControlRodLevels(99)
  1474.                             else
  1475.                                 reactor.setAllControlRodLevels(rodPercentage + diffAmount)
  1476.                             end
  1477.                         end --if ((reactorTemp - lastTempPoll) > 100) then
  1478.                     elseif ((lastTempPoll - reactorTemp) < (reactorTemp * 0.005)) then
  1479.                         --temperature has stagnated, kick it very lightly
  1480.                         local controlRodAdjustment = 1
  1481.                         if (rodPercentage + controlRodAdjustment) > 99 then
  1482.                             reactor.setAllControlRodLevels(99)
  1483.                         else
  1484.                             reactor.setAllControlRodLevels(rodPercentage + controlRodAdjustment)
  1485.                         end
  1486.                     end --if (reactorTemp > lastTempPoll) then
  1487.                         --worth noting that if we're above temp but decreasing, we do nothing. let it continue decreasing.
  1488.  
  1489.                 elseif ((reactorTemp < localMinReactorTemp) and (rodPercentage ~=0)) or (steamRequested - steamDelivered > 0) then
  1490.                     --we're too cold. time to warm up, but by how much?
  1491.                     if (steamRequested > (steamDelivered*2)) then
  1492.                         -- Bridge to machine room: Full steam ahead!
  1493.                         reactor.setAllControlRodLevels(0)
  1494.                     elseif (reactorTemp < lastTempPoll) then
  1495.                         --we're descending, let's stop that.
  1496.                         if ((lastTempPoll - reactorTemp) > 100) then
  1497.                             --we're headed for a new ice age, bring the heat
  1498.                             if (rodPercentage - (10 * localControlRodAdjustAmount)) < 0 then
  1499.                                 reactor.setAllControlRodLevels(0)
  1500.                             else
  1501.                                 reactor.setAllControlRodLevels(rodPercentage - (10 * localControlRodAdjustAmount))
  1502.                             end
  1503.                         else
  1504.                             --we're not descending quickly, let's bump it based on descent rate
  1505.                             local diffAmount = lastTempPoll - reactorTemp
  1506.                             diffAmount = (round(diffAmount/10, 0))/5
  1507.                             _G[reactorNames[reactorIndex]]["ReactorOptions"]["controlRodAdjustAmount"] = diffAmount
  1508.                             if (rodPercentage - diffAmount) < 0 then
  1509.                                 reactor.setAllControlRodLevels(0)
  1510.                             else
  1511.                                 reactor.setAllControlRodLevels(rodPercentage - diffAmount)
  1512.                             end
  1513.                         end --if ((lastTempPoll - reactorTemp) > 100) then
  1514.                     elseif (reactorTemp == lastTempPoll) then
  1515.                         --temperature has stagnated, kick it very lightly
  1516.                         local controlRodAdjustment = 1
  1517.                         if (rodPercentage - controlRodAdjustment) < 0 then
  1518.                             reactor.setAllControlRodLevels(0)
  1519.                         else
  1520.                             reactor.setAllControlRodLevels(rodPercentage - controlRodAdjustment)
  1521.                         end --if (rodPercentage - controlRodAdjustment) < 0 then
  1522.  
  1523.                     end --if (reactorTemp < lastTempPoll) then
  1524.                     --if we're below temp but increasing, do nothing and let it continue to rise.
  1525.                 end --if (reactorTemp > localMaxReactorTemp) and (rodPercentage ~= 99) then
  1526.  
  1527.                 if ((reactorTemp > localMinReactorTemp) and (reactorTemp < localMaxReactorTemp)) and not (steamRequested - steamDelivered > 0) then
  1528.                     --engage cruise mode
  1529.                     _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorCruising"] = true
  1530.                 end
  1531.             end -- if reactorCruising then
  1532.             --always set this number
  1533.             _G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"] = reactorTemp
  1534.             config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]])
  1535.         end -- if reactor.getActive() then
  1536.     else
  1537.         printLog("Bypassed temperature control due to rodOverride being "..tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]).." EOL")
  1538.     end -- if not _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] then
  1539. end -- function temperatureControl(reactorIndex)
  1540.  
  1541. -- Load saved reactor parameters if ReactorOptions file exists
  1542. local function loadReactorOptions()
  1543.     local reactorOptions = fs.open("ReactorOptions", "r") -- See http://computercraft.info/wiki/Fs.open
  1544.  
  1545.     if reactorOptions then
  1546.         -- The following values were added by Lolmer
  1547.         minStoredEnergyPercent = reactorOptions.readLine()
  1548.         maxStoredEnergyPercent = reactorOptions.readLine()
  1549.         --added by Mechaet
  1550.         -- If we succeeded in reading a string, convert it to a number
  1551.  
  1552.         if minStoredEnergyPercent ~= nil then
  1553.             minStoredEnergyPercent = tonumber(minStoredEnergyPercent)
  1554.         end
  1555.  
  1556.         if maxStoredEnergyPercent ~= nil then
  1557.             maxStoredEnergyPercent = tonumber(maxStoredEnergyPercent)
  1558.         end
  1559.  
  1560.         reactorOptions.close()
  1561.     end -- if reactorOptions then
  1562.  
  1563.     -- Set default values if we failed to read any of the above
  1564.     if minStoredEnergyPercent == nil then
  1565.         minStoredEnergyPercent = 15
  1566.     end
  1567.  
  1568.     if maxStoredEnergyPercent == nil then
  1569.         maxStoredEnergyPercent = 85
  1570.     end
  1571.  
  1572. end -- function loadReactorOptions()
  1573.  
  1574.  
  1575. -- Save our reactor parameters
  1576. local function saveReactorOptions()
  1577.     local reactorOptions = fs.open("ReactorOptions", "w") -- See http://computercraft.info/wiki/Fs.open
  1578.  
  1579.     -- If we can save the files, save them
  1580.     if reactorOptions then
  1581.         local reactorIndex = 1
  1582.         -- The following values were added by Lolmer
  1583.         reactorOptions.writeLine(minStoredEnergyPercent)
  1584.         reactorOptions.writeLine(maxStoredEnergyPercent)
  1585.         reactorOptions.close()
  1586.     else
  1587.         printLog("Failed to open file ReactorOptions for writing!")
  1588.     end -- if reactorOptions then
  1589. end -- function saveReactorOptions()
  1590.  
  1591.  
  1592. local function displayReactorBars(barParams)
  1593.     -- Default to first reactor and first monitor
  1594.     setmetatable(barParams,{__index={reactorIndex=1, monitorIndex=1}})
  1595.     local reactorIndex, monitorIndex =
  1596.         barParams[1] or barParams.reactorIndex,
  1597.         barParams[2] or barParams.monitorIndex
  1598.  
  1599.     printLog("Called as displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
  1600.  
  1601.     -- Grab current monitor
  1602.     local monitor = nil
  1603.     monitor = monitorList[monitorIndex]
  1604.     if not monitor then
  1605.         printLog("monitor["..monitorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT a valid monitor.")
  1606.         return -- Invalid monitorIndex
  1607.     end
  1608.  
  1609.     -- Grab current reactor
  1610.     local reactor = nil
  1611.     reactor = reactorList[reactorIndex]
  1612.     if not reactor then
  1613.         printLog("reactor["..reactorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT a valid Big Reactor.")
  1614.         return -- Invalid reactorIndex
  1615.     else
  1616.         printLog("reactor["..reactorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is a valid Big Reactor.")
  1617.         if reactor.getConnected() then
  1618.             printLog("reactor["..reactorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is connected.")
  1619.         else
  1620.             printLog("reactor["..reactorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT connected.")
  1621.             return -- Disconnected reactor
  1622.         end -- if reactor.getConnected() then
  1623.     end -- if not reactor then
  1624.  
  1625.     -- Draw border lines
  1626.     local width, height = monitor.getSize()
  1627.     printLog("Size of monitor is "..width.."w x"..height.."h in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..")")
  1628.  
  1629.     for i=3, 5 do
  1630.         monitor.setCursorPos(22, i)
  1631.         monitor.write("|")
  1632.     end
  1633.  
  1634.     drawLine(6, monitorIndex)
  1635.     monitor.setCursorPos(1, height)
  1636.     monitor.write("< ")
  1637.     monitor.setCursorPos(width-1, height)
  1638.     monitor.write(" >")
  1639.  
  1640.     -- Draw some text
  1641.     local fuelString = "Fuel: "
  1642.     local tempString = "Temp: "
  1643.     local energyBufferString = ""
  1644.  
  1645.     if reactor.isActivelyCooled() then
  1646.         energyBufferString = "Steam: "
  1647.     else
  1648.         energyBufferString = "Energy: "
  1649.     end
  1650.  
  1651.     local padding = math.max(string.len(fuelString), string.len(tempString), string.len(energyBufferString))
  1652.  
  1653.     local fuelPercentage = round(reactor.getFuelAmount()/reactor.getFuelAmountMax()*100,1)
  1654.     print{fuelString,2,3,monitorIndex}
  1655.     print{fuelPercentage.." %",padding+2,3,monitorIndex}
  1656.  
  1657.     local reactorTemp = math.ceil(reactor.getFuelTemperature())
  1658.     print{tempString,2,5,monitorIndex}
  1659.     print{reactorTemp.." C",padding+2,5,monitorIndex}
  1660.  
  1661.     local rodPercentage = math.ceil(reactor.getControlRodLevel(0))
  1662.     printLog("Current Rod Percentage for reactor["..reactorIndex.."] is "..rodPercentage.."% in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
  1663.     print{"Rod (%)",23,3,monitorIndex}
  1664.     print{"<     >",23,4,monitorIndex}
  1665.     print{stringTrim(rodPercentage),25,4,monitorIndex}
  1666.  
  1667.  
  1668.     -- getEnergyProducedLastTick() is used for both RF/t (passively cooled) and mB/t (actively cooled)
  1669.     local energyBuffer = reactor.getEnergyProducedLastTick()
  1670.     if reactor.isActivelyCooled() then
  1671.         printLog("reactor["..reactorIndex.."] produced "..energyBuffer.." mB last tick in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
  1672.     else
  1673.         printLog("reactor["..reactorIndex.."] produced "..energyBuffer.." RF last tick in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
  1674.     end
  1675.  
  1676.     print{energyBufferString,2,4,monitorIndex}
  1677.  
  1678.     -- Actively cooled reactors do not produce energy, only hot fluid mB/t to be used in a turbine
  1679.     -- still uses getEnergyProducedLastTick for mB/t of hot fluid generated
  1680.     if not reactor.isActivelyCooled() then
  1681.         printLog("reactor["..reactorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT an actively cooled reactor.")
  1682.  
  1683.         -- Draw stored energy buffer bar
  1684.         drawBar(2,8,28,8,colors.gray,monitorIndex)
  1685.  
  1686.         local curStoredEnergyPercent = getReactorStoredEnergyBufferPercent(reactor)
  1687.         if curStoredEnergyPercent > 4 then
  1688.             drawBar(2, 8, math.floor(26*curStoredEnergyPercent/100)+2, 8, colors.yellow, monitorIndex)
  1689.         elseif curStoredEnergyPercent > 0 then
  1690.             drawPixel(2, 8, colors.yellow, monitorIndex)
  1691.         end -- if curStoredEnergyPercent > 4 then
  1692.  
  1693.         print{"Energy Buffer",2,7,monitorIndex}
  1694.         print{curStoredEnergyPercent, width-(string.len(curStoredEnergyPercent)+2),7,monitorIndex}
  1695.         print{"%",28,7,monitorIndex}
  1696.  
  1697.         print{math.ceil(energyBuffer).." RF/t",padding+2,4,monitorIndex}
  1698.     else
  1699.         printLog("reactor["..reactorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is an actively cooled reactor.")
  1700.         print{math.ceil(energyBuffer).." mB/t",padding+2,4,monitorIndex}
  1701.     end -- if not reactor.isActivelyCooled() then
  1702.  
  1703.     -- Print rod override status
  1704.     local reactorRodOverrideStatus = ""
  1705.  
  1706.     print{"Rod Auto-adjust:",2,9,monitorIndex}
  1707.  
  1708.     if not _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] then
  1709.         printLog("Reactor Rod Override status is: "..tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]).." EOL")
  1710.         reactorRodOverrideStatus = "Enabled"
  1711.         monitor.setTextColor(colors.green)
  1712.     else
  1713.         printLog("Reactor Rod Override status is: "..tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]).." EOL")
  1714.         reactorRodOverrideStatus = "Disabled"
  1715.         monitor.setTextColor(colors.red)
  1716.     end -- if not reactorRodOverride then
  1717.     printLog("reactorRodOverrideStatus is \""..reactorRodOverrideStatus.."\" in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
  1718.  
  1719.     print{reactorRodOverrideStatus, width - string.len(reactorRodOverrideStatus) - 1, 9, monitorIndex}
  1720.     monitor.setTextColor(colors.white)
  1721.  
  1722.     print{"Reactivity: "..math.ceil(reactor.getFuelReactivity()).." %", 2, 10, monitorIndex}
  1723.     print{"Fuel: "..round(reactor.getFuelConsumedLastTick(),3).." mB/t", 2, 11, monitorIndex}
  1724.     print{"Waste: "..reactor.getWasteAmount().." mB", width-(string.len(reactor.getWasteAmount())+10), 11, monitorIndex}
  1725.  
  1726.     monitor.setTextColor(colors.blue)
  1727.     printCentered(_G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorName"],12,monitorIndex)
  1728.     monitor.setTextColor(colors.white)
  1729.  
  1730.     -- monitor switch controls
  1731.     monitor.setCursorPos(1, height)
  1732.     monitor.write("<")
  1733.     monitor.setCursorPos(width, height)
  1734.     monitor.write(">")
  1735.  
  1736. end -- function displayReactorBars(barParams)
  1737.  
  1738.  
  1739. local function reactorStatus(statusParams)
  1740.     -- Default to first reactor and first monitor
  1741.     setmetatable(statusParams,{__index={reactorIndex=1, monitorIndex=1}})
  1742.     local reactorIndex, monitorIndex =
  1743.         statusParams[1] or statusParams.reactorIndex,
  1744.         statusParams[2] or statusParams.monitorIndex
  1745.     printLog("Called as reactorStatus(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..")")
  1746.  
  1747.     -- Grab current monitor
  1748.     local monitor = nil
  1749.     monitor = monitorList[monitorIndex]
  1750.     if not monitor then
  1751.         printLog("monitor["..monitorIndex.."] in reactorStatus(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT a valid monitor.")
  1752.         return -- Invalid monitorIndex
  1753.     end
  1754.  
  1755.     -- Grab current reactor
  1756.     local reactor = nil
  1757.     reactor = reactorList[reactorIndex]
  1758.     if not reactor then
  1759.         printLog("reactor["..reactorIndex.."] in reactorStatus(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT a valid Big Reactor.")
  1760.         return -- Invalid reactorIndex
  1761.     else
  1762.         printLog("reactor["..reactorIndex.."] in reactorStatus(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is a valid Big Reactor.")
  1763.     end
  1764.  
  1765.     local width, height = monitor.getSize()
  1766.     local reactorStatus = ""
  1767.  
  1768.     if reactor.getConnected() then
  1769.         printLog("reactor["..reactorIndex.."] in reactorStatus(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is connected.")
  1770.  
  1771.         if reactor.getActive() then
  1772.             reactorStatus = "ONLINE"
  1773.  
  1774.             -- Set "ONLINE" to blue if the actively cooled reactor is both in cruise mode and online
  1775.             if _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorCruising"] and reactor.isActivelyCooled() then
  1776.                 monitor.setTextColor(colors.blue)
  1777.             else
  1778.                 monitor.setTextColor(colors.green)
  1779.             end -- if reactorCruising and reactor.isActivelyCooled() then
  1780.         else
  1781.             reactorStatus = "OFFLINE"
  1782.             monitor.setTextColor(colors.red)
  1783.         end -- if reactor.getActive() then
  1784.  
  1785.     else
  1786.         printLog("reactor["..reactorIndex.."] in reactorStatus(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT connected.")
  1787.         reactorStatus = "DISCONNECTED"
  1788.         monitor.setTextColor(colors.red)
  1789.     end -- if reactor.getConnected() then
  1790.     _G[reactorNames[reactorIndex]]["ReactorOptions"]["Status"] = reactorStatus
  1791.  
  1792.     print{reactorStatus, width - string.len(reactorStatus) - 1, 1, monitorIndex}
  1793.     monitor.setTextColor(colors.white)
  1794. end -- function reactorStatus(statusParams)
  1795.  
  1796.  
  1797. -- Display all found reactors' status to selected monitor
  1798. -- This is only called if multiple reactors and/or a reactor plus at least one turbine are found
  1799. local function displayAllStatus(monitorIndex)
  1800.     local reactor, turbine = nil, nil
  1801.     local onlineReactor, onlineTurbine = 0, 0
  1802.     local totalReactorRF, totalReactorSteam, totalTurbineRF = 0, 0, 0
  1803.     local totalReactorFuelConsumed = 0
  1804.     local totalCoolantStored, totalSteamStored, totalEnergy, totalMaxEnergyStored = 0, 0, 0, 0 -- Total turbine and reactor energy buffer and overall capacity
  1805.     local maxSteamStored = (2000*#turbineList)+(5000*#reactorList)
  1806.     local maxCoolantStored = (2000*#turbineList)+(5000*#reactorList)
  1807.  
  1808.     local monitor = monitorList[monitorIndex]
  1809.     if not monitor then
  1810.         printLog("monitor["..monitorIndex.."] in displayAllStatus() is NOT a valid monitor.")
  1811.         return -- Invalid monitorIndex
  1812.     end
  1813.  
  1814.     for reactorIndex = 1, #reactorList do
  1815.         reactor = reactorList[reactorIndex]
  1816.         if not reactor then
  1817.             printLog("reactor["..reactorIndex.."] in displayAllStatus() is NOT a valid Big Reactor.")
  1818.             break -- Invalid reactorIndex
  1819.         else
  1820.             printLog("reactor["..reactorIndex.."] in displayAllStatus() is a valid Big Reactor.")
  1821.         end -- if not reactor then
  1822.  
  1823.        
  1824.             if reactor.getActive() then
  1825.                 onlineReactor = onlineReactor + 1
  1826.                 totalReactorFuelConsumed = totalReactorFuelConsumed + reactor.getFuelConsumedLastTick()
  1827.             end -- reactor.getActive() then
  1828.  
  1829.             -- Actively cooled reactors do not produce or store energy
  1830.             if not reactor.isActivelyCooled() then
  1831.                 totalMaxEnergyStored = totalMaxEnergyStored + 10000000 -- Reactors store 10M RF
  1832.                 totalEnergy = totalEnergy + reactor.getEnergyStored()
  1833.                 totalReactorRF = totalReactorRF + reactor.getEnergyProducedLastTick()
  1834.             else
  1835.                 totalReactorSteam = totalReactorSteam + reactor.getEnergyProducedLastTick()
  1836.                 totalSteamStored = totalSteamStored + reactor.getHotFluidAmount()
  1837.                 totalCoolantStored = totalCoolantStored + reactor.getCoolantAmount()
  1838.             end -- if not reactor.isActivelyCooled() then
  1839.        
  1840.             printLog("reactor["..reactorIndex.."] in displayAllStatus() is NOT connected.")
  1841.         end -- if reactor.getConnected() then
  1842.     end -- for reactorIndex = 1, #reactorList do
  1843.  
  1844.     for turbineIndex = 1, #turbineList do
  1845.         turbine = turbineList[turbineIndex]
  1846.         if not turbine then
  1847.             printLog("turbine["..turbineIndex.."] in displayAllStatus() is NOT a valid Turbine.")
  1848.             break -- Invalid turbineIndex
  1849.         else
  1850.             printLog("turbine["..turbineIndex.."] in displayAllStatus() is a valid Turbine.")
  1851.         end -- if not turbine then
  1852.  
  1853.         if turbine.getConnected() then
  1854.             printLog("turbine["..turbineIndex.."] in displayAllStatus() is connected.")
  1855.             if turbine.getActive() then
  1856.                 onlineTurbine = onlineTurbine + 1
  1857.             end
  1858.  
  1859.             totalMaxEnergyStored = totalMaxEnergyStored + 1000000 -- Turbines store 1M RF
  1860.             totalEnergy = totalEnergy + turbine.getEnergyStored()
  1861.             totalTurbineRF = totalTurbineRF + turbine.getEnergyProducedLastTick()
  1862.             totalSteamStored = totalSteamStored + turbine.getInputAmount()
  1863.             totalCoolantStored = totalCoolantStored + turbine.getOutputAmount()
  1864.         else
  1865.             printLog("turbine["..turbineIndex.."] in displayAllStatus() is NOT connected.")
  1866.         end -- if turbine.getConnected() then
  1867.     end -- for turbineIndex = 1, #turbineList do
  1868.  
  1869.     print{"Reactors online/found: "..onlineReactor.."/"..#reactorList, 2, 3, monitorIndex}
  1870.     print{"Turbines online/found: "..onlineTurbine.."/"..#turbineList, 2, 4, monitorIndex}
  1871.  
  1872.     if totalReactorRF ~= 0 then
  1873.         monitor.setTextColor(colors.blue)
  1874.         printRight("Reactor", 9, monitorIndex)
  1875.         monitor.setTextColor(colors.white)
  1876.         printRight(math.ceil(totalReactorRF).." (RF/t)", 10, monitorIndex)
  1877.     end
  1878.  
  1879.     if #turbineList then
  1880.         -- Display liquids
  1881.         monitor.setTextColor(colors.blue)
  1882.         printLeft("Steam (mB)", 6, monitorIndex)
  1883.         monitor.setTextColor(colors.white)
  1884.         printLeft(math.ceil(totalSteamStored).."/"..maxSteamStored, 7, monitorIndex)
  1885.         printLeft(math.ceil(totalReactorSteam).." mB/t", 8, monitorIndex)
  1886.         monitor.setTextColor(colors.blue)
  1887.         printRight("Coolant (mB)", 6, monitorIndex)
  1888.         monitor.setTextColor(colors.white)
  1889.         printRight(math.ceil(totalCoolantStored).."/"..maxCoolantStored, 7, monitorIndex)
  1890.  
  1891.         monitor.setTextColor(colors.blue)
  1892.         printLeft("Turbine", 9, monitorIndex)
  1893.         monitor.setTextColor(colors.white)
  1894.         printLeft(math.ceil(totalTurbineRF).." RF/t", 10, monitorIndex)
  1895.     end -- if #turbineList then
  1896.  
  1897.     printCentered("Fuel: "..round(totalReactorFuelConsumed,3).." mB/t", 11, monitorIndex)
  1898.     printCentered("Buffer: "..formatReadableSIUnit(math.ceil(totalEnergy)).."/"..formatReadableSIUnit(totalMaxEnergyStored).." RF", 12, monitorIndex)
  1899.  
  1900.     -- monitor switch controls
  1901.     local width, height = monitor.getSize()
  1902.     monitor.setCursorPos(1, height)
  1903.     monitor.write("<")
  1904.     monitor.setCursorPos(width, height)
  1905.     monitor.write(">")
  1906.  
  1907. end -- function displayAllStatus()
  1908.  
  1909.  
  1910. -- Get turbine status
  1911. local function displayTurbineBars(turbineIndex, monitorIndex)
  1912.     printLog("Called as displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
  1913.  
  1914.     -- Grab current monitor
  1915.     local monitor = nil
  1916.     monitor = monitorList[monitorIndex]
  1917.     if not monitor then
  1918.         printLog("monitor["..monitorIndex.."] in displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid monitor.")
  1919.         return -- Invalid monitorIndex
  1920.     end
  1921.  
  1922.     -- Grab current turbine
  1923.     local turbine = nil
  1924.     turbine = turbineList[turbineIndex]
  1925.     if not turbine then
  1926.         printLog("turbine["..turbineIndex.."] in displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid Big Turbine.")
  1927.         return -- Invalid turbineIndex
  1928.     else
  1929.         printLog("turbine["..turbineIndex.."] in displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is a valid Big Turbine.")
  1930.         if turbine.getConnected() then
  1931.             printLog("turbine["..turbineIndex.."] in displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is connected.")
  1932.         else
  1933.             printLog("turbine["..turbineIndex.."] in displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT connected.")
  1934.             return -- Disconnected turbine
  1935.         end -- if turbine.getConnected() then
  1936.     end -- if not turbine then
  1937.  
  1938.     --local variable to match the view on the monitor
  1939.     local turbineBaseSpeed = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"])
  1940.  
  1941.     -- Draw border lines
  1942.     local width, height = monitor.getSize()
  1943.  
  1944.     for i=3, 6 do
  1945.         monitor.setCursorPos(21, i)
  1946.         monitor.write("|")
  1947.     end
  1948.  
  1949.     drawLine(7,monitorIndex)
  1950.     monitor.setCursorPos(1, height)
  1951.     monitor.write("< ")
  1952.     monitor.setCursorPos(width-1, height)
  1953.     monitor.write(" >")
  1954.  
  1955.     local turbineFlowRate = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"])
  1956.     print{"  mB/t",22,3,monitorIndex}
  1957.     print{"<      >",22,4,monitorIndex}
  1958.     print{stringTrim(turbineFlowRate),24,4,monitorIndex}
  1959.     print{"  RPM",22,5,monitorIndex}
  1960.     print{"<      >",22,6,monitorIndex}
  1961.     print{stringTrim(tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"])),24,6,monitorIndex}
  1962.     local rotorSpeedString = "Speed: "
  1963.     local energyBufferString = "Energy: "
  1964.     local steamBufferString = "Steam: "
  1965.     local padding = math.max(string.len(rotorSpeedString), string.len(energyBufferString), string.len(steamBufferString))
  1966.  
  1967.     local energyBuffer = turbine.getEnergyProducedLastTick()
  1968.     print{energyBufferString,1,4,monitorIndex}
  1969.     print{math.ceil(energyBuffer).." RF/t",padding+1,4,monitorIndex}
  1970.  
  1971.     local rotorSpeed = math.ceil(turbine.getRotorSpeed())
  1972.     print{rotorSpeedString,1,5,monitorIndex}
  1973.     print{rotorSpeed.." RPM",padding+1,5,monitorIndex}
  1974.  
  1975.     local steamBuffer = turbine.getFluidFlowRate()
  1976.     print{steamBufferString,1,6,monitorIndex}
  1977.     print{steamBuffer.." mB/t",padding+1,6,monitorIndex}
  1978.  
  1979.     -- PaintUtils only outputs to term., not monitor.
  1980.     -- See http://www.computercraft.info/forums2/index.php?/topic/15540-paintutils-on-a-monitor/
  1981.  
  1982.     -- Draw stored energy buffer bar
  1983.     drawBar(1,9,28,9,colors.gray,monitorIndex)
  1984.  
  1985.     local curStoredEnergyPercent = getTurbineStoredEnergyBufferPercent(turbine)
  1986.     if curStoredEnergyPercent > 4 then
  1987.         drawBar(1, 9, math.floor(26*curStoredEnergyPercent/100)+2, 9, colors.yellow,monitorIndex)
  1988.     elseif curStoredEnergyPercent > 0 then
  1989.         drawPixel(1, 9, colors.yellow, monitorIndex)
  1990.     end -- if curStoredEnergyPercent > 4 then
  1991.  
  1992.     print{"Energy Buffer",1,8,monitorIndex}
  1993.     print{curStoredEnergyPercent, width-(string.len(curStoredEnergyPercent)+2),8,monitorIndex}
  1994.     print{"%",28,8,monitorIndex}
  1995.  
  1996.     -- Print rod override status
  1997.     local turbineFlowRateOverrideStatus = ""
  1998.  
  1999.     print{"Flow Auto-adjust:",2,10,monitorIndex}
  2000.  
  2001.     if ((not _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"]) or (_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] == "false")) then
  2002.         turbineFlowRateOverrideStatus = "Enabled"
  2003.         monitor.setTextColor(colors.green)
  2004.     else
  2005.         turbineFlowRateOverrideStatus = "Disabled"
  2006.         monitor.setTextColor(colors.red)
  2007.     end -- if not reactorRodOverride then
  2008.  
  2009.     print{turbineFlowRateOverrideStatus, width - string.len(turbineFlowRateOverrideStatus) - 1, 10, monitorIndex}
  2010.     monitor.setTextColor(colors.white)
  2011.  
  2012.     -- Print coil status
  2013.     local turbineCoilStatus = ""
  2014.  
  2015.     print{"Turbine coils:",2,11,monitorIndex}
  2016.  
  2017.     if ((_G[turbineNames[turbineIndex]]["TurbineOptions"]["CoilsEngaged"]) or (_G[turbineNames[turbineIndex]]["TurbineOptions"]["CoilsEngaged"] == "true")) then
  2018.         turbineCoilStatus = "Engaged"
  2019.         monitor.setTextColor(colors.green)
  2020.     else
  2021.         turbineCoilStatus = "Disengaged"
  2022.         monitor.setTextColor(colors.red)
  2023.     end
  2024.  
  2025.     print{turbineCoilStatus, width - string.len(turbineCoilStatus) - 1, 11, monitorIndex}
  2026.     monitor.setTextColor(colors.white)
  2027.  
  2028.     monitor.setTextColor(colors.blue)
  2029.     printCentered(_G[turbineNames[turbineIndex]]["TurbineOptions"]["turbineName"],12,monitorIndex)
  2030.     monitor.setTextColor(colors.white)
  2031.  
  2032.     -- monitor switch controls
  2033.     monitor.setCursorPos(1, height)
  2034.     monitor.write("<")
  2035.     monitor.setCursorPos(width, height)
  2036.     monitor.write(">")
  2037.  
  2038.     -- Need equation to figure out rotor efficiency and display
  2039. end -- function displayTurbineBars(statusParams)
  2040.  
  2041.  
  2042. -- Display turbine status
  2043. local function turbineStatus(turbineIndex, monitorIndex)
  2044.     printLog("Called as turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
  2045.  
  2046.     -- Grab current monitor
  2047.     local monitor = nil
  2048.     monitor = monitorList[monitorIndex]
  2049.     if not monitor then
  2050.         printLog("monitor["..monitorIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid monitor.")
  2051.         return -- Invalid monitorIndex
  2052.     end
  2053.  
  2054.     -- Grab current turbine
  2055.     local turbine = nil
  2056.     turbine = turbineList[turbineIndex]
  2057.     if not turbine then
  2058.         printLog("turbine["..turbineIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid Big Turbine.")
  2059.         return -- Invalid turbineIndex
  2060.     else
  2061.         printLog("turbine["..turbineIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is a valid Big Turbine.")
  2062.     end
  2063.  
  2064.     local width, height = monitor.getSize()
  2065.     local turbineStatus = ""
  2066.  
  2067.     if turbine.getConnected() then
  2068.         printLog("turbine["..turbineIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is connected.")
  2069.         if turbine.getActive() then
  2070.             turbineStatus = "ONLINE"
  2071.             monitor.setTextColor(colors.green)
  2072.         else
  2073.             turbineStatus = "OFFLINE"
  2074.             monitor.setTextColor(colors.red)
  2075.         end -- if turbine.getActive() then
  2076.         _G[turbineNames[turbineIndex]]["TurbineOptions"]["Status"] = turbineStatus
  2077.     else
  2078.         printLog("turbine["..turbineIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT connected.")
  2079.         turbineStatus = "DISCONNECTED"
  2080.         monitor.setTextColor(colors.red)
  2081.     end -- if turbine.getConnected() then
  2082.  
  2083.     print{turbineStatus, width - string.len(turbineStatus) - 1, 1, monitorIndex}
  2084.     monitor.setTextColor(colors.white)
  2085. end -- function function turbineStatus(turbineIndex, monitorIndex)
  2086.  
  2087.  
  2088. -- Adjust Turbine flow rate to maintain 900 or 1,800 RPM, and disengage coils when buffer full
  2089. local function flowRateControl(turbineIndex)
  2090.     if ((not _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"]) or (_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] == "false")) then
  2091.        
  2092.         printLog("Called as flowRateControl(turbineIndex="..turbineIndex..").")
  2093.  
  2094.         -- Grab current turbine
  2095.         local turbine = nil
  2096.         turbine = turbineList[turbineIndex]
  2097.  
  2098.         -- assign for the duration of this run
  2099.         local lastTurbineSpeed = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["LastSpeed"])
  2100.         local turbineBaseSpeed = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"])
  2101.         local coilsEngaged = _G[turbineNames[turbineIndex]]["TurbineOptions"]["CoilsEngaged"] or _G[turbineNames[turbineIndex]]["TurbineOptions"]["CoilsEngaged"] == "true"
  2102.  
  2103.         if not turbine then
  2104.             printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is NOT a valid Big Turbine.")
  2105.             return -- Invalid turbineIndex
  2106.         else
  2107.             printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is a valid Big Turbine.")
  2108.  
  2109.             if turbine.getConnected() then
  2110.                 printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is connected.")
  2111.             else
  2112.                 printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is NOT connected.")
  2113.             end -- if turbine.getConnected() then
  2114.         end -- if not turbine then
  2115.  
  2116.         -- No point modifying control rod levels for temperature if the turbine is offline
  2117.         if turbine.getActive() then
  2118.             printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is active.")
  2119.  
  2120.             local flowRate = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"])
  2121.             local flowRateUserMax = math.ceil(turbine.getFluidFlowRateMax())
  2122.             local rotorSpeed = math.ceil(turbine.getRotorSpeed())
  2123.             local newFlowRate = -1
  2124.  
  2125.             local currentStoredEnergyPercent = getTurbineStoredEnergyBufferPercent(turbine)
  2126.             if (currentStoredEnergyPercent >= maxStoredEnergyPercent) then
  2127.                 if (coilsEngaged) then
  2128.                     printLog("turbine["..turbineIndex.."]: Disengaging coils, energy buffer at "..currentStoredEnergyPercent.." (>="..maxStoredEnergyPercent..").")
  2129.                     newFlowRate = 0
  2130.                     coilsEngaged = false
  2131.                 end
  2132.             elseif (currentStoredEnergyPercent < minStoredEnergyPercent) then
  2133.                 if (not coilsEngaged) then
  2134.                     printLog("turbine["..turbineIndex.."]: Engaging coils, energy buffer at "..currentStoredEnergyPercent.." (<"..minStoredEnergyPercent..").")
  2135.                     -- set flow rate to what's probably the max load flow for the desired RPM
  2136.                     newFlowRate = 2000 / (1817 / turbineBaseSpeed)
  2137.                     coilsEngaged = true
  2138.                 end
  2139.             end
  2140.  
  2141.             -- Going to control the turbine based on target RPM since changing the target flow rate bypasses this function
  2142.             if (rotorSpeed < turbineBaseSpeed) then
  2143.                 printLog("BELOW COMMANDED SPEED")
  2144.  
  2145.                 local diffSpeed = rotorSpeed - lastTurbineSpeed
  2146.                 local diffBaseSpeed = turbineBaseSpeed - rotorSpeed
  2147.                 if (diffSpeed > 0) then
  2148.                     if (diffBaseSpeed > turbineBaseSpeed * 0.05) then
  2149.                         -- let's speed this up. DOUBLE TIME!
  2150.                         coilsEngaged = false
  2151.                         printLog("COILS DISENGAGED")
  2152.                     elseif (diffSpeed > diffBaseSpeed * 0.05) then
  2153.                         --we're still increasing, let's let it level off
  2154.                         --also lets the first control pass go by on startup
  2155.                         printLog("Leveling off...")
  2156.                     end
  2157.                 elseif (rotorSpeed < lastTurbineSpeed) then
  2158.                     --we're decreasing where we should be increasing, do something
  2159.                     if ((lastTurbineSpeed - rotorSpeed) > 100) then
  2160.                         --kick it harder
  2161.                         newFlowRate = 2000
  2162.                         printLog("HARD KICK")
  2163.                     else
  2164.                         --let's adjust based on proximity
  2165.                         flowAdjustment = (turbineBaseSpeed - rotorSpeed)/5
  2166.                         newFlowRate = flowRate + flowAdjustment
  2167.                         printLog("Light Kick: new flow rate is "..newFlowRate.." mB/t and flowAdjustment was "..flowAdjustment.." EOL")
  2168.                     end
  2169.                 else
  2170.                     --we've stagnated, kick it.
  2171.                     flowAdjustment = (turbineBaseSpeed - lastTurbineSpeed)
  2172.                     newFlowRate = flowRate + flowAdjustment
  2173.                     printLog("Stagnated: new flow rate is "..newFlowRate.." mB/t and flowAdjustment was "..flowAdjustment.." EOL")
  2174.                 end --if (rotorSpeed > lastTurbineSpeed) then
  2175.             else
  2176.                 --we're above commanded turbine speed
  2177.                 printLog("ABOVE COMMANDED SPEED")
  2178.                 if (rotorSpeed < lastTurbineSpeed) then
  2179.                 --we're decreasing, let it level off
  2180.                 --also bypasses first control pass on startup
  2181.                 elseif (rotorSpeed > lastTurbineSpeed) then
  2182.                     --we're above and ascending.
  2183.                     if ((rotorSpeed - lastTurbineSpeed) > 100) then
  2184.                         --halt
  2185.                         newFlowRate = 0
  2186.                     else
  2187.                         --let's adjust based on proximity
  2188.                         flowAdjustment = (rotorSpeed - turbineBaseSpeed)/5
  2189.                         newFlowRate = flowRate - flowAdjustment
  2190.                         printLog("Light Kick: new flow rate is "..newFlowRate.." mB/t and flowAdjustment was "..flowAdjustment.." EOL")
  2191.                     end
  2192.                     -- With coils disengaged, we have no chance of slowing. More importantly, this stops DOUBLE TIME.
  2193.                     coilsEngaged = true
  2194.                 else
  2195.                     --we've stagnated, kick it.
  2196.                     flowAdjustment = (lastTurbineSpeed - turbineBaseSpeed)
  2197.                     newFlowRate = flowRate - flowAdjustment
  2198.                     printLog("Stagnated: new flow rate is "..newFlowRate.." mB/t and flowAdjustment was "..flowAdjustment.." EOL")
  2199.                 end --if (rotorSpeed < lastTurbineSpeed) then
  2200.             end --if (rotorSpeed < turbineBaseSpeed)
  2201.  
  2202.             --check to make sure an adjustment was made
  2203.             if (newFlowRate == -1) then
  2204.                 --do nothing, we didn't ask for anything this pass
  2205.             else
  2206.                 --boundary check
  2207.                 if newFlowRate > 2000 then
  2208.                     newFlowRate = 2000
  2209.                 elseif newFlowRate < 0 then
  2210.                     newFlowRate = 0
  2211.                 end -- if newFlowRate > 2000 then
  2212.                 --no sense running an adjustment if it's not necessary
  2213.                 if ((newFlowRate < flowRate) or (newFlowRate > flowRate)) then
  2214.                     printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is being commanded to "..newFlowRate.." mB/t flow")
  2215.                     newFlowRate = round(newFlowRate, 0)
  2216.                     turbine.setFluidFlowRateMax(newFlowRate)
  2217.                     _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"] = newFlowRate
  2218.                     config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
  2219.                 end
  2220.             end
  2221.  
  2222.             turbine.setInductorEngaged(coilsEngaged)
  2223.  
  2224.             --always set this
  2225.             _G[turbineNames[turbineIndex]]["TurbineOptions"]["CoilsEngaged"] = coilsEngaged
  2226.             _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastSpeed"] = rotorSpeed
  2227.             config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
  2228.         else
  2229.             printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is NOT active.")
  2230.         end -- if turbine.getActive() then
  2231.     else
  2232.         printLog("turbine["..turbineIndex.."] has flow override set to "..tostring(_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"])..", bypassing flow control.")
  2233.     end -- if not _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] then
  2234. end -- function flowRateControl(turbineIndex)
  2235.  
  2236.  
  2237. local function helpText()
  2238.  
  2239.     -- these keys are actually defined in eventHandler(), check there
  2240.     return [[Keyboard commands:
  2241.             m   Select next monitor
  2242.             s   Make selected monitor display global status
  2243.             x   Make selected monitor display debug information
  2244.  
  2245.             d   Toggle debug mode
  2246.  
  2247.             q   Quit
  2248.             r   Quit and reboot
  2249.             h   Print this help
  2250. ]]
  2251.  
  2252. end -- function helpText()
  2253.  
  2254. local function initializePeripherals()
  2255.     monitorAssignments = {}
  2256.     -- Get our list of connected monitors and reactors
  2257.     findMonitors()
  2258.     findReactors()
  2259.     findTurbines()
  2260.     assignMonitors()
  2261. end
  2262.  
  2263.  
  2264. local function updateMonitors()
  2265.  
  2266.     -- Display overall status on selected monitors
  2267.     for monitorName, deviceData in pairs(monitorAssignments) do
  2268.         local monitor = nil
  2269.         local monitorIndex = deviceData.index
  2270.         local monitorType =  deviceData.type
  2271.         monitor = monitorList[monitorIndex]
  2272.  
  2273.         printLog("main(): Trying to display "..monitorType.." on "..monitorNames[monitorIndex].."["..monitorIndex.."]", DEBUG)
  2274.  
  2275.         if #monitorList < (#reactorList + #turbineList + 1) then
  2276.             printLog("You may want "..(#reactorList + #turbineList + 1).." monitors for your "..#reactorList.." connected reactors and "..#turbineList.." connected turbines.")
  2277.         end
  2278.  
  2279.         if (not monitor) or (not monitor.getSize()) then
  2280.  
  2281.             printLog("monitor["..monitorIndex.."] in main() is NOT a valid monitor, discarding", ERROR)
  2282.             monitorAssignments[monitorName] = nil
  2283.             -- we need to get out of the for loop now, or it will dereference x.next (where x is the element we just killed) and crash
  2284.             break
  2285.  
  2286.         elseif monitorType == "Status" then
  2287.  
  2288.             -- General status display
  2289.             clearMonitor(progName.." "..progVer, monitorIndex) -- Clear monitor and draw borders
  2290.             printCentered(progName.." "..progVer, 1, monitorIndex)
  2291.             displayAllStatus(monitorIndex)
  2292.  
  2293.         elseif monitorType == "Reactor" then
  2294.  
  2295.             -- Reactor display
  2296.             local reactorMonitorIndex = monitorIndex
  2297.             for reactorIndex = 1, #reactorList do
  2298.  
  2299.                 if deviceData.reactorName == reactorNames[reactorIndex] then
  2300.  
  2301.                     printLog("Attempting to display reactor["..reactorIndex.."] on monitor["..monitorIndex.."]...", DEBUG)
  2302.                     -- Only attempt to assign a monitor if we have a monitor for this reactor
  2303.                     if (reactorMonitorIndex <= #monitorList) then
  2304.                         printLog("Displaying reactor["..reactorIndex.."] on monitor["..reactorMonitorIndex.."].")
  2305.  
  2306.                         clearMonitor(progName, reactorMonitorIndex) -- Clear monitor and draw borders
  2307.                         printCentered(progName, 1, reactorMonitorIndex)
  2308.  
  2309.                         -- Display reactor status, includes "Disconnected" but found reactors
  2310.                         reactorStatus{reactorIndex, reactorMonitorIndex}
  2311.  
  2312.                         -- Draw the borders and bars for the current reactor on the current monitor
  2313.                         displayReactorBars{reactorIndex, reactorMonitorIndex}
  2314.                     end
  2315.  
  2316.                 end -- if deviceData.reactorName == reactorNames[reactorIndex] then
  2317.  
  2318.             end -- for reactorIndex = 1, #reactorList do
  2319.  
  2320.         elseif monitorType == "Turbine" then
  2321.  
  2322.             -- Turbine display
  2323.             local turbineMonitorIndex = monitorIndex
  2324.             for turbineIndex = 1, #turbineList do
  2325.  
  2326.                 if deviceData.turbineName == turbineNames[turbineIndex] then
  2327.                     printLog("Attempting to display turbine["..turbineIndex.."] on monitor["..turbineMonitorIndex.."]...", DEBUG)
  2328.                     -- Only attempt to assign a monitor if we have a monitor for this turbine
  2329.                     if (turbineMonitorIndex <= #monitorList) then
  2330.                         printLog("Displaying turbine["..turbineIndex.."] on monitor["..turbineMonitorIndex.."].")
  2331.                         clearMonitor(progName, turbineMonitorIndex) -- Clear monitor and draw borders
  2332.                         printCentered(progName, 1, turbineMonitorIndex)
  2333.  
  2334.                         -- Display turbine status, includes "Disconnected" but found turbines
  2335.                         turbineStatus(turbineIndex, turbineMonitorIndex)
  2336.  
  2337.                         -- Draw the borders and bars for the current turbine on the current monitor
  2338.                         displayTurbineBars(turbineIndex, turbineMonitorIndex)
  2339.                     end
  2340.                 end
  2341.             end
  2342.  
  2343.         elseif monitorType == "Debug" then
  2344.  
  2345.             -- do nothing, printLog() outputs to here
  2346.  
  2347.         else
  2348.  
  2349.             clearMonitor(progName, monitorIndex)
  2350.             print{"Monitor  inactive", 7, 7, monitorIndex}
  2351.  
  2352.         end -- if monitorType == [...]
  2353.     end
  2354. end
  2355.  
  2356. function main()
  2357.     -- Load reactor parameters and initialize systems
  2358.     loadReactorOptions()
  2359.     initializePeripherals()
  2360.  
  2361.     write(helpText())
  2362.  
  2363.     while not finished do
  2364.  
  2365.         updateMonitors()
  2366.  
  2367.         local reactor = nil
  2368.         local sd = 0
  2369.  
  2370.         -- Iterate through reactors
  2371.         for reactorIndex = 1, #reactorList do
  2372.             local monitor = nil
  2373.  
  2374.             reactor = reactorList[reactorIndex]
  2375.             if not reactor then
  2376.                 printLog("reactor["..reactorIndex.."] in main() is NOT a valid Big Reactor.")
  2377.                 break -- Invalid reactorIndex
  2378.             else
  2379.                 printLog("reactor["..reactorIndex.."] in main() is a valid Big Reactor.")
  2380.             end --  if not reactor then
  2381.  
  2382.             if reactor.getConnected() then
  2383.                 printLog("reactor["..reactorIndex.."] is connected.")
  2384.                 local curStoredEnergyPercent = getReactorStoredEnergyBufferPercent(reactor)
  2385.  
  2386.                 -- Shutdown reactor if current stored energy % is >= desired level, otherwise activate
  2387.                 -- First pass will have curStoredEnergyPercent=0 until displayBars() is run once
  2388.                 if curStoredEnergyPercent >= maxStoredEnergyPercent then
  2389.                     reactor.setActive(false)
  2390.                 -- Do not auto-start the reactor if it was manually powered off (autoStart=false)
  2391.                 elseif (curStoredEnergyPercent <= minStoredEnergyPercent) and (_G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] == true) then
  2392.                     reactor.setActive(true)
  2393.                 end -- if curStoredEnergyPercent >= maxStoredEnergyPercent then
  2394.  
  2395.                 -- Don't try to auto-adjust control rods if manual control is requested
  2396.                 if not _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] then
  2397.                     temperatureControl(reactorIndex)
  2398.                 end -- if not reactorRodOverride then
  2399.  
  2400.                 -- Collect steam production data
  2401.                 if reactor.isActivelyCooled() then
  2402.                     sd = sd + reactor.getHotFluidProducedLastTick()
  2403.                 end
  2404.             else
  2405.                 printLog("reactor["..reactorIndex.."] is NOT connected.")
  2406.             end -- if reactor.getConnected() then
  2407.         end -- for reactorIndex = 1, #reactorList do
  2408.  
  2409.         -- Now that temperatureControl() had a chance to use it, reset/calculate steam data for next iteration
  2410.         printLog("Steam requested: "..steamRequested.." mB")
  2411.         printLog("Steam delivered: "..steamDelivered.." mB")
  2412.         steamDelivered = sd
  2413.         steamRequested = 0
  2414.  
  2415.         -- Turbine control
  2416.         for turbineIndex = 1, #turbineList do
  2417.  
  2418.             turbine = turbineList[turbineIndex]
  2419.             if not turbine then
  2420.                 printLog("turbine["..turbineIndex.."] in main() is NOT a valid Big Turbine.")
  2421.                 break -- Invalid turbineIndex
  2422.             else
  2423.                 printLog("turbine["..turbineIndex.."] in main() is a valid Big Turbine.")
  2424.             end -- if not turbine then
  2425.  
  2426.             if turbine.getConnected() then
  2427.                 printLog("turbine["..turbineIndex.."] is connected.")
  2428.  
  2429.                 if ((not _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"]) or (_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] == "false")) then
  2430.                     flowRateControl(turbineIndex)
  2431.                 end -- if not turbineFlowRateOverride[turbineIndex] then
  2432.  
  2433.                 -- Collect steam consumption data
  2434.                 if turbine.getActive() then
  2435.                     steamRequested = steamRequested + turbine.getFluidFlowRateMax()
  2436.                 end
  2437.             else
  2438.                 printLog("turbine["..turbineIndex.."] is NOT connected.")
  2439.             end -- if turbine.getConnected() then
  2440.         end -- for reactorIndex = 1, #reactorList do
  2441.  
  2442.         wait(loopTime) -- Sleep. No, wait...
  2443.         saveReactorOptions()
  2444.     end -- while not finished do
  2445. end -- main()
  2446.  
  2447. -- handle all the user interaction events
  2448. eventHandler = function(event, arg1, arg2, arg3)
  2449.  
  2450.         printLog(string.format("handleEvent(%s, %s, %s, %s)", tostring(event), tostring(arg1), tostring(arg2), tostring(arg3)), DEBUG)
  2451.  
  2452.         if event == "monitor_touch" then
  2453.             sideClick, xClick, yClick = arg1, math.floor(arg2), math.floor(arg3)
  2454.             UI:handlePossibleClick()
  2455.         elseif (event == "peripheral") or (event == "peripheral_detach") then
  2456.             printLog("Change in network detected. Reinitializing peripherals. We will be back shortly.", WARN)
  2457.             initializePeripherals()
  2458.         elseif event == "char" and not inManualMode then
  2459.             local ch = string.lower(arg1)
  2460.             -- remember to update helpText() when you edit these
  2461.             if ch == "q" then
  2462.                 finished = true
  2463.             elseif ch == "d" then
  2464.                 debugMode = not debugMode
  2465.                 local modeText
  2466.                 if debugMode then
  2467.                     modeText = "on"
  2468.                 else
  2469.                     modeText = "off"
  2470.                 end
  2471.                 termRestore()
  2472.                 write("debugMode "..modeText.."\n")
  2473.             elseif ch == "m" then
  2474.                 UI:selectNextMonitor()
  2475.             elseif ch == "s" then
  2476.                 UI:selectStatus()
  2477.             elseif ch == "x" then
  2478.                 UI:selectDebug()
  2479.             elseif ch == "r" then
  2480.                 finished = true
  2481.                 os.reboot()
  2482.             elseif ch == "h" then
  2483.                 write(helpText())
  2484.             end -- if ch == "q" then
  2485.         end -- if event == "monitor_touch" then
  2486.  
  2487.         updateMonitors()
  2488.  
  2489. end -- function eventHandler()
  2490.  
  2491. main()
  2492.  
  2493. -- Clear up after an exit
  2494. term.clear()
  2495. term.setCursorPos(1,1)
  2496.  
RAW Paste Data Copied