Advertisement
iPlayG

Untitled

May 11th, 2025 (edited)
213
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 16.75 KB | None | 0 0
  1. ---------------------------------------------------
  2. -- Mekanism Fission Reactor Monitoring Program   --
  3. -- Created for CC:Tweaked and Mekanism           --
  4. -- Originally created by InternetUnexplorer on   --
  5. -- GitHub. Modified for personal use, by         --
  6. -- iPlay_G on Minecraft, Derrick355 on GitHub,   --
  7. -- iplayg on Discord. As the original program    --
  8. -- did not contain any license, this will not    --
  9. -- contain any license. However, this software   --
  10. -- is provided "AS-IS", with NO WARRANTY of ANY  --
  11. -- kind. There is no guarentee of fitness for a  --
  12. -- particular purpose.                           --
  13. -- Feel free to use it, but I, nor the           --
  14. -- original author are responsible for any       --
  15. -- issues arising from the use of this program.  --
  16. --                    -=@=-                      --
  17. -- Thank you for downloading this program.       --
  18. ---------------------------------------------------
  19.  
  20. version = "1.3"
  21.  
  22.  
  23. -- NOTES
  24. -- If you see 'CRITICAL' on your monitor as the
  25. -- state, that is OK. Critical is what real
  26. -- reactors are considered when operating normally.
  27. -- The word 'SCRAM' generally refers to an
  28. -- emergency shutdown, either automatically or
  29. -- manually.
  30. --
  31. -- It is generally recommended to edit settings
  32. -- in an IDE or text editor for ease of use.
  33. --
  34. -- It is assumed you have a 6 wide, 4 tall monitor
  35. -- attached via a modem.
  36. --
  37. -- References to an analog lever are common, since
  38. -- I used the Create mod, however if you don't
  39. -- have it, any way to input a variable-power
  40. -- redstone signal should work.
  41. --
  42. -- Binary levers are just vanillia levers.
  43. --
  44. -- I recommend putting this program on a disk,
  45. -- and having a startup script run this program.
  46. -- I have a second computer that I use to edit
  47. -- the program in-game so that the main one
  48. -- only runs this program, and nothing else.
  49. --
  50. -- Current rules:
  51. -- Reactor temp > 1100K
  52. -- Reactor damage > 10%
  53. -- Reactor waste level > 10%
  54. -- Coolant (cold) level < 95%
  55. -- Coolant (hot) level > 10%
  56. -- Turbine energy level > 95%
  57. -- Reactor fuel level < 10%
  58. --
  59. --
  60. -- RECOMMENDED & ASSUMED COMPUTER I/O SETUP
  61. -- TOP: Binary lever, used to enable/disable manual
  62. --  burn rate control
  63. -- LEFT: Analog lever, used as coarse adjust on
  64. --  burn control
  65. -- RIGHT: Analog lever, used as fine adjust on
  66. -- burn control
  67. -- BOTTOM: Binary lever, used as reactor on/off
  68. -- control
  69. -- BACK: Modem to connect to reactor, turbine, and
  70. -- monitor
  71. -- FRONT: Alarm or siren. Outputs a redstone signal
  72. --  if turbine power falls below a set threshold.
  73. --
  74. -- KNOWN BUGS:
  75. -- Reactor fuel level shows as NIL however the
  76. --  safety rules still causes a shutdown at
  77. --  appropiate setting
  78. -- Some formatting changes I want to mess with
  79.  
  80. --- VARIABLE SETTINGS ---
  81. -- Set the multiplier for the burn rate coarse adj.
  82. -- Default = 75
  83. burn_rate_coarse_multi = 15
  84.  
  85. -- Set the multiplier for the burn rate fine adj.
  86. -- Default = 5
  87. burn_rate_fine_multi = 1
  88.  
  89. -- Maximum auto burn rate to establish
  90. -- Default = 120
  91. burn_rate_auto_max = 225
  92.  
  93. -- What turbine power percent value should burn
  94. --  rate be max at? For this value and all lower,
  95. --  burn rate will be at the max defined above.
  96. -- Default = 0.5 (for 50%)
  97. min_turbine_power = 0.8
  98.  
  99. -- What turbine power percent value should burn
  100. --  rate be zero at? For this value and all higher,
  101. --  burn rate will be at zero.
  102. -- Default = 0.75 (for 75%)
  103. max_turbine_power = 0.9
  104.  
  105. -- What is the monitor called on the network?
  106. -- Default = monitor_0
  107. monitor_name = "monitor_1"
  108.  
  109. -- Are you playing on a server with Advanced
  110. --  peripherals? This is used so that if a server
  111. --  is restarting the computer can scram the
  112. --  reactor. Note, the server must issue some kind
  113. --  of restart warning beforehand.
  114. -- Doesn't work too well, the server I play on
  115. --  does not issue a warning so I can't test.
  116. -- Default = true
  117. on_server = false
  118.  
  119. -- If the above is true, you must configure this line.
  120. -- What message in chat should the program look for
  121. --  to see if the server
  122. -- is restarting?
  123. -- Default = "SCRAM"
  124. restart_message = "SCRAM"
  125.  
  126. -- Do not modify anything past this line unless you
  127. --  know what you are doing.
  128.  
  129. ------------------------------------------------
  130.  
  131. local state, data, reactor, turbine, info_window, rules_window, heartbeat_bool, heartbeat_counter
  132. heartbeat_counter = 0
  133.  
  134. local STATES = {
  135.     READY = 1, -- Reactor is off and can be started with the lever
  136.     RUNNING = 2, -- Reactor is running and all rules are met
  137.     ESTOP = 3, -- Reactor is stopped due to rule(s) being violated
  138.     UNKNOWN = 4, -- Reactor or turbine peripherals are missing
  139. }
  140.  
  141. ------------------------------------------------
  142.  
  143. local function redstone_restart()
  144.     if fs.exists("disk/temp/notify_restart") then
  145.         redstone.setOutput("front", true)
  146.     else
  147.         redstone.setOutput("front", false)
  148.     end
  149. end
  150.  
  151. redstone_restart()
  152.  
  153. local function check_for_restart_scram()
  154.     if fs.exists("disk/temp/scrammed") then
  155.         incoming_restart = true
  156.         fs.delete("disk/temp/scrammed")
  157.         fs.makeDir("disk/temp/notify_restart")
  158.         fs.makeDir("disk/temp/redstone_restart")
  159.     else
  160.         incoming_restart = false
  161.     end
  162.     return incoming_restart
  163. end
  164.  
  165. incoming_restart = check_for_restart_scram()
  166.  
  167. local function check_for_restart_notify()
  168.     if fs.exists("disk/temp/notify_restart") then
  169.         notify_restart = true
  170.         fs.delete("disk/temp/notify_restart")
  171.     else
  172.         notify_restart = false
  173.     end
  174.     return notify_restart
  175. end
  176.  
  177. notify_restart = check_for_restart_notify()
  178.  
  179. local function pollChatbox()
  180.     while true do
  181.         local box = peripheral.find('chatBox', function(_, chatbox) return chatbox.getOperationCooldown('chatMessage') == 0 end)
  182.         if box then
  183.             return box
  184.         end
  185.         sleep(0.1)
  186.     end
  187. end
  188.  
  189. ------------------------------------------------
  190.  
  191. local rules = {}
  192.  
  193. local function add_rule(name, fn)
  194.     table.insert(rules, function()
  195.         local ok, rule_met, value = pcall(fn)
  196.         if ok and value ~= false then
  197.             return rule_met, string.format("%s ( %s)", name, value)
  198.     elseif ok then
  199.         return rule_met, string.format("%s (%s)", name, value)
  200.         else
  201.             return false, name
  202.         end
  203.     end)
  204. end
  205.  
  206. local function update_data()
  207.     coarse_adjust = (tonumber(redstone.getAnalogInput("left")) * burn_rate_coarse_multi)
  208.     fine_adjust = (tonumber(redstone.getAnalogInput("right")) * burn_rate_fine_multi)
  209.     set_burn_rate = (coarse_adjust + fine_adjust)
  210.  
  211.     data = {
  212.         lever_on = redstone.getInput("bottom"),
  213.         burn_rate_limit = set_burn_rate,
  214.         burn_rate_limited = redstone.getInput("top"),
  215.  
  216.         reactor_on = reactor.getStatus(),
  217.         reactor_burn_rate = reactor.getBurnRate(),
  218.         reactor_max_burn_rate = reactor.getMaxBurnRate(),
  219.         reactor_temp = reactor.getTemperature(),
  220.         reactor_damage = reactor.getDamagePercent(),
  221.         reactor_coolant = reactor.getCoolantFilledPercentage(),
  222.         reactor_hot_coolant = reactor.getHeatedCoolantFilledPercentage(),
  223.         reactor_waste = reactor.getWasteFilledPercentage(),
  224.         reactor_fuel = reactor.getFuelFilledPercentage(),
  225.         reactor_max_fuel = reactor.getFuelCapacity(),
  226.         reactor_fuel_level = reactor.getFuel(),
  227.  
  228.         turbine_energy = turbine.getEnergyFilledPercentage(),
  229.         turbine_power = turbine.getEnergy(),
  230.         max_turbine_power = turbine.getMaxEnergy()
  231.     }
  232.  
  233. end
  234.  
  235. add_rule("REACTOR TEMPERATURE      <=  1100K  ", function()
  236.     local value = string.format("%3dK", math.ceil(data.reactor_temp))
  237.     return data.reactor_temp <= 1100, value
  238. end)
  239.  
  240. add_rule("REACTOR DAMAGE           <=    10%  ", function()
  241.     local value = string.format("%3d%%", math.ceil(data.reactor_damage * 1))
  242.     return data.reactor_damage <= 10, value
  243. end)
  244.  
  245. add_rule("REACTOR FUEL LEVEL       >=    10%  ", function()
  246.     local value = string.format("%3d%%", math.floor(data.reactor_fuel * 100))
  247.     return data.reactor_fuel >= .10, valueA
  248. end)
  249.  
  250. add_rule("REACTOR WASTE LEVEL      <=    90%  ", function()
  251.     local value = string.format("%3d%%", math.ceil(data.reactor_waste * 100))
  252.     return data.reactor_waste <= 0.90, value
  253. end)
  254.  
  255. add_rule("COOLANT (COLD) LEVEL     >=    50%  ", function()
  256.     local value = string.format("%3d%%", math.floor(data.reactor_coolant * 100))
  257.     return data.reactor_coolant >= 0.50, value
  258. end)
  259.  
  260. add_rule("COOLANT (HOT) LEVEL      <=    10%  ", function()
  261.     local value = string.format("%3d%%", math.ceil(data.reactor_hot_coolant * 100))
  262.     return data.reactor_hot_coolant <= 0.10, value
  263. end)
  264.  
  265. add_rule("TURBINE ENERGY LEVEL     <=    95%  ", function()
  266.     local value = string.format("%3d%%", math.ceil(data.turbine_energy * 100))
  267.     return data.turbine_energy <= 0.95, value
  268. end)
  269.  
  270. if on_server then
  271.     add_rule("INCOMING SERVER RESTART  ==  FALSE  ", function()
  272.         return not incoming_restart, notify_restart
  273.     end)
  274. end
  275.  
  276. local function all_rules_met()
  277.     for i, rule in ipairs(rules) do
  278.         if not rule() then
  279.             return false
  280.         end
  281.     end
  282.     -- Allow manual emergency stop with SCRAM button
  283.     return state ~= STATES.RUNNING or data.reactor_on
  284. end
  285.  
  286. ------------------------------------------------
  287.  
  288. ------------------------------------------------
  289.  
  290. local monitor = peripheral.wrap(monitor_name)
  291.  
  292. term.redirect(monitor)
  293.  
  294. local function colored(text, fg, bg)
  295.     term.setTextColor(fg or colors.white)
  296.     term.setBackgroundColor(bg or colors.black)
  297.     term.write(string.upper(text))
  298. end
  299.  
  300. local function make_section(name, x, y, w, h, color)
  301.     for row = 1, h do
  302.         term.setCursorPos(x, y + row - 1)
  303.         local char = (row == 1 or row == h) and "\127" or " "
  304.         colored("\127" .. string.rep(char, w - 2) .. "\127", color or colors.gray)
  305.     end
  306.  
  307.     term.setCursorPos(x + 2, y)
  308.     colored(" " .. name .. " ")
  309.  
  310.     return window.create(term.current(), x + 2, y + 2, w - 4, h - 4)
  311. end
  312.  
  313. local function update_info()
  314.     local prev_term = term.redirect(info_window)
  315.  
  316.     term.clear()
  317.     term.setCursorPos(1, 1)
  318.  
  319.     if state == STATES.UNKNOWN then
  320.         colored("ERROR RETRIEVING DATA", colors.red)
  321.         return
  322.     end
  323.  
  324.     colored("REACTOR: ")
  325.     colored(data.reactor_on and "CRITICAL" or "SHUTDOWN", data.reactor_on and colors.green or colors.red)
  326.     colored(" LEVER: ")
  327.     colored(data.lever_on and "POWERED" or "SECURED", data.lever_on and colors.green or colors.red)
  328.     colored(" BURN RATE: ")
  329.     if data.burn_rate_limited == false then
  330.         colored(string.format("%4.1f", data.reactor_burn_rate), colors.blue)
  331.     else
  332.         colored(string.format("%4.1f", data.reactor_burn_rate), colors.yellow)
  333.     end
  334.     colored("/", colors.lightGray)
  335.     colored(string.format("%4.0f", data.reactor_max_burn_rate), colors.blue)
  336.  
  337.     term.setCursorPos(34, 2)
  338.     colored("SET LIMIT: ")
  339.     if data.burn_rate_limited then
  340.         colored(string.format("%4.1f", data.burn_rate_limit), colors.green)
  341.     else
  342.         colored(string.format("%4.1f", data.burn_rate_limit), colors.gray)
  343.     end
  344.  
  345.     term.setCursorPos(1, 4)
  346.  
  347.     colored("STATUS: ")
  348.     if state == STATES.READY then
  349.         colored("READY   - PULL LEVER TO STARTUP", colors.blue)
  350.     elseif state == STATES.RUNNING then
  351.         colored("RUNNING - PULL LEVER TO SHUTDOWN", colors.green)
  352.     elseif state == STATES.ESTOP and not all_rules_met() then
  353.         colored("SCRAM   - SAFETY RULE VIOLATED", colors.red)
  354.     elseif state == STATES.ESTOP then
  355.         colored("SCRAM   - TOGGLE LEVER TO RESET", colors.red)
  356.     end -- STATES.UNKNOWN cases handled above
  357.  
  358.     term.setCursorPos(1, 6)
  359.    
  360.     colored("STORED POWER: ")
  361.     colored(string.format("%4.0f", (data.turbine_power*4/1000000)), colors.green)
  362.     colored(" MFE", colors.green)
  363.     colored("/", colors.lightGray)
  364.  
  365.     colored(string.format("%4.0f", (data.max_turbine_power*4/1000000)), colors.blue)
  366.     colored(" MFE", colors.blue)
  367.    
  368.     term.setCursorPos(38, 6)
  369.     colored("(")
  370.     colored(string.format("%6.2f", (data.turbine_energy*100)) .. "%", colors.green)
  371.     colored(")")
  372.  
  373.     term.setCursorPos(1, 7)
  374.    
  375.     colored("STORED FUEL:  ")
  376.     colored(string.format("%4.0f", (data.reactor_fuel_level["amount"])), colors.green)
  377.     colored(" mB", colors.green)
  378.     colored("/", colors.lightGray)
  379.  
  380.     colored(string.format("%4.0f", (data.reactor_max_fuel)), colors.blue)
  381.     colored(" mB", colors.blue)
  382.  
  383.     term.setCursorPos(38, 7)
  384.     colored("(")
  385.     colored(string.format("%5.2f", (data.reactor_fuel*100)).. "%", colors.green)
  386.     colored(")")
  387.  
  388.     term.setCursorPos(1, 8)
  389.     colored("FUEL TYPE:    ", colors.white)
  390.     fuel_name = data.reactor_fuel_level["name"]
  391.     fuel_name = fuel_name:match"^.-:(.+)"
  392.     fuel_name = fuel_name.gsub(fuel_name, "_", " ")
  393.     colored(fuel_name, colors.green)
  394.  
  395.     term.redirect(prev_term)
  396.  
  397.     -- This can be commented to hide credits at the bottom
  398.     -- Would be nice if you didn't though
  399.     term.setCursorPos(5, 26)
  400.     colored("Made by InternetUnexplorer, forked by iPlay_G. v" .. version, colors.gray)
  401. end
  402.  
  403. local function heartbeat()
  404.     term.setCursorPos(59, 12)
  405.     if heartbeat_bool then
  406.         heartbeat_bool = false
  407.         colored(string.format("%c", 0x25A3), colors.red)
  408.     else
  409.         heartbeat_bool = true
  410.         colored(string.format("%c", 0x25A3), colors.gray)
  411.     end
  412. end
  413.  
  414.  
  415. local estop_reasons = {}
  416.  
  417. local function update_rules()
  418.     local prev_term = term.redirect(rules_window)
  419.  
  420.     term.clear()
  421.  
  422.     if state ~= STATES.ESTOP then
  423.         estop_reasons = {}
  424.     end
  425.  
  426.     for i, rule in ipairs(rules) do
  427.         local ok, text = rule()
  428.         term.setCursorPos(1, i)
  429.         if ok and not estop_reasons[i] then
  430.             colored("[ SAFE ] ", colors.green)
  431.             colored(text, colors.lightGray)
  432.         else
  433.             colored("[ FAIL ] ", colors.red)
  434.             colored(text, colors.red)
  435.             estop_reasons[i] = true
  436.         end
  437.     end
  438.  
  439.     term.redirect(prev_term)
  440. end
  441.  
  442. ------------------------------------------------
  443.  
  444. local function main_loop()
  445.     -- Search for peripherals again if one or both are missing
  446.     if not state or state == STATES.UNKNOWN then
  447.         reactor = peripheral.find("fissionReactorLogicAdapter")
  448.         turbine = peripheral.find("turbineValve")
  449.     end
  450.  
  451.     if not pcall(update_data) then
  452.         -- Error getting data (either reactor or turbine is nil?)
  453.         data = {}
  454.         state = STATES.UNKNOWN
  455.     elseif data.reactor_on == nil then
  456.         -- Reactor is not connected
  457.         state = STATES.UNKNOWN
  458.     elseif data.turbine_energy == nil then
  459.         -- Turbine is not connected
  460.         state = STATES.UNKNOWN
  461.     elseif not state then
  462.         -- Program just started, get current state from lever
  463.         state = data.lever_on and STATES.RUNNING or STATES.READY
  464.     elseif state == STATES.READY and data.lever_on then
  465.         -- READY -> RUNNING
  466.         state = STATES.RUNNING
  467.         -- Activate reactor
  468.         pcall(reactor.activate)
  469.         data.reactor_on = true
  470.     elseif state == STATES.RUNNING and not data.lever_on then
  471.         -- RUNNING -> READY
  472.         state = STATES.READY
  473.     elseif state == STATES.ESTOP and not data.lever_on then
  474.         -- ESTOP -> READY
  475.         state = STATES.READY
  476.     elseif state == STATES.UNKNOWN then
  477.         -- UNKNOWN -> ESTOP
  478.         state = data.lever_on and STATES.ESTOP or STATES.READY
  479.         estop_reasons = {}
  480.     end
  481.  
  482.     -- Always enter ESTOP if safety rules are not met
  483.     if state ~= STATES.UNKNOWN and not all_rules_met() then
  484.         state = STATES.ESTOP
  485.     end
  486.  
  487.     -- SCRAM reactor if not running
  488.     if state ~= STATES.RUNNING and reactor then
  489.         pcall(reactor.scram)
  490.     end
  491.  
  492.     -- Make Windows
  493.     local width = term.getSize()
  494.     if state == STATES.ESTOP then
  495.         window_color = colors.red
  496.     elseif state == STATES.READY then
  497.         window_color = colors.green
  498.     else
  499.         window_color = colors.gray
  500.     end
  501.     info_window = make_section("STATUS", 2, 2, width - 2, 12, window_color)
  502.     rules_window = make_section("SAFETY RULES", 2, 15, width - 2, 11, window_color)
  503.  
  504.     -- Update info and rules windows
  505.     pcall(update_info)
  506.     pcall(update_rules)
  507.     pcall(heartbeat)
  508.  
  509.     -- Update max burn rate to keep turbine below set energy.
  510.     if state ~= STATES.UNKNOWN and data.burn_rate_limited == false then
  511.    
  512.         local tgt_burn_pct = math.min(math.max(0.0, 1 - (data.turbine_energy - min_turbine_power) / (max_turbine_power - min_turbine_power)), 1.0)
  513.         pcall(reactor.setBurnRate, tgt_burn_pct * burn_rate_auto_max)
  514.     elseif state ~= STATES.UNKNOWN and data.burn_rate_limited == true then
  515.         pcall(reactor.setBurnRate, data.burn_rate_limit) -- Let burn rate be limited by an analog lever
  516.     end
  517.  
  518.     sleep() -- Other calls should already yield, this is just in case
  519.     return main_loop()
  520. end
  521.  
  522. term.setPaletteColor(colors.black, 0x000000)
  523. term.setPaletteColor(colors.gray, 0x343434)
  524. term.setPaletteColor(colors.lightGray, 0xababab)
  525. term.setPaletteColor(colors.red, 0xdb2d20)
  526. term.setPaletteColor(colors.green, 0x01a252)
  527. term.setPaletteColor(colors.blue, 0x01a0e4)
  528. term.setPaletteColor(colors.pink, 0xA9A9A9)
  529. term.clear()
  530.  
  531.  
  532. function scram_on_restart()
  533.     chatBox = peripheral.find("chatBox")
  534.     local event, username, message, uuid, isHidden = os.pullEvent("chat")
  535.     pollChatbox().sendMessage("username: " .. username .. " message: `".. message .. "`")
  536.     if message == restart_message and username == "sayCommand" then
  537.         --fs.copy("/disk/temp/scram.lua", "/disk/temp/scrammed.lua")
  538.         fs.makeDir("/disk/temp/scrammed")
  539.         pollChatbox().sendMessage("SCRAM aye")
  540.     end
  541. end
  542.  
  543. if on_server then
  544.     parallel.waitForAny(main_loop, scram_on_restart)
  545. else
  546.     parallel.waitForAny(main_loop, function()
  547.         os.pullEventRaw("terminate")
  548.     end)
  549. end
  550.  
  551. os.reboot()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement