View difference between Paste ID: 1sUhQdvL and fguScPBQ
SHOW: | | - or go back to the newest paste.
1
--[[
2
Program name: Lolmer's EZ-NUKE reactor control system
3-
Version: v0.3.17
3+
Version: v0.3.13
4
Programmer: Lolmer
5-
With reat assistance from @echaet and @thetaphi
5+
Great assistance by Mechaet
6-
Last update: 2015-04-08
6+
Last update: 2014-09-24
7
Pastebin: http://pastebin.com/fguScPBQ
8-
GitHub: https://github.com/sandalle/minecraft_bigreactor_control
8+
9
Description:
10
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.
11
12
This program was designed to work with the mods and versions installed on Never Stop Toasting (NST) Diet http://www.technicpack.net/modpack/details/never-stop-toasting-diet.254882 Endeavour: Never Stop Toasting: Diet official Minecraft server http://forums.somethingawful.com/showthread.php?threadid=3603757
13
14
To simplify the code and guesswork, I assume the following monitor layout, where each "monitor" listed below is a collection of 3 wide by two high Advanced Monitors:
15-
To simplify the code and guesswork, I assume the following monitor layout, where each "monitor" listed below is a collection of three wide by two high Advanced Monitors:
15+
16
	one or more Reactors plus
17
	none or more Turbines.
18
2) One Advanced Monitor for overall status display plus (furthest monitor from computer by cable length)
19
	one Advanced Monitor for each connected Reactor plus (subsequent found monitors)
20
	one Advanced Monitor for each connected Turbine (last group of monitors found).
21
If you enable debug mode, add one additional Advanced Monitor for #1 or #2.
22
23
Notes:
24-
Notes
24+
	Only one reactor and one, two, and three turbines have been tested with the above, but IN THEORY any number is supported.
25-
----------------------------
25+
	Devices are found in the reverse order they are plugged in, so monitor_10 will be found before monitor_9.
26-
- Only one reactor and one, two, and three turbines have been tested with the above, but IN THEORY any number is supported.
26+
	Two 15x15x14 Turbines can output 260K RF/t by just one 7^3 (four rods) reactor putting out 4k mB steam.
27-
- Devices are found in the reverse order they are plugged in, so monitor_10 will be found before monitor_9.
27+
28
When using actively cooled reactors with turbines, keep the following in mind:
29
	- 1 mB steam carries up to 10RF of potential energy to extract in a turbine.
30-
- 1 mB steam carries up to 10RF of potential energy to extract in a turbine.
30+
	- Actively cooled reactors produce steam, not power.
31-
- Actively cooled reactors produce steam, not power.
31+
	- You will need about 10 mB of water for each 1 mB of steam that you want to create in a 7^3 reactor.
32-
- You will need about 10 mB of water for each 1 mB of steam that you want to create in a 7^3 reactor.
32+
33-
- Two 15x15x14 Turbines can output 260K RF/t by just one 7^3 (four rods) reactor putting out 4k mB steam.
33+
Features:
34
	Configurable min/max energy buffer and min/max temperature via ReactorOptions file.
35-
Features
35+
	ReactorOptions is read on start and then current values are saved every program cycle.
36-
----------------------------
36+
	Rod Control value in ReactorOptions is only useful for initial start, after that the program saves the current Rod Control average over all Fuel Rods for next boot.
37-
- Configurable min/max energy buffer and min/max temperature via ReactorOptions file.
37+
	Auto-adjusts control rods per reactor to maintain temperature.
38-
- Disengages coils and minimizes flow for turbines over max energy buffer.
38+
	Will display reactor data to all attached monitors of correct dimensions.
39-
- ReactorOptions is read on start and then current values are saved every program cycle.
39+
		For multiple monitors, the first monitor (often last plugged in) is the overall status monitor.
40-
- Rod Control value in ReactorOptions is only useful for initial start, after that the program saves the current Rod Control average over all Fuel Rods for next boot.
40+
	For multiple monitors, the first monitor (often last plugged in) is the overall status monitor.
41-
- Auto-adjusts control rods per reactor to maintain temperature.
41+
	A new cruise mode from mechaet, ONLINE will be "blue" when active, to keep your actively cooled reactors running smoothly.
42-
- Will display reactor data to all attached monitors of correct dimensions.
42+
43-
	- For multiple monitors, the first monitor (often last plugged in) is the overall status monitor.
43+
GUI Usage:
44-
- For multiple monitors, the first monitor (often last plugged in) is the overall status monitor.
44+
	The "<" and ">" buttons, when right-clicked with the mouse, will decrease and increase, respectively, the values assigned to the monitor:
45-
- A new cruise mode from mechaet, ONLINE will be "blue" when active, to keep your actively cooled reactors running smoothly.
45+
		"Rod (%)" will lower/raise the Reactor Control Rods for that Reactor
46
		"mB/t" will lower/raise the Turbine Flow Rate maximum for that Turbine
47-
GUI Usage
47+
		"RPM" will lower/raise the target Turbine RPM for that Turbine
48-
----------------------------
48+
	Right-clicking between the "<" and ">" (not on them) will disable auto-adjust of that value for attached device.
49-
- Right-clicking between "< * >" of the last row of a monitor alternates the device selection between Reactor, Turbine, and Status output.
49+
		Right-clicking on the "Enabled" or "Disabled" text for auto-adjust will do the same.
50-
	- Right-clicking "<" and ">" switches between connected devices, starting with the currently selected type, but not limited to them.
50+
	Right-clicking on "ONLINE" or "OFFLINE" at the top-right will toggle the state of attached device.
51-
- The other "<" and ">" buttons, when right-clicked with the mouse, will decrease and increase, respectively, the values assigned to the monitor:
51+
52-
	- "Rod (%)" will lower/raise the Reactor Control Rods for that Reactor
52+
Default values:
53-
	- "mB/t" will lower/raise the Turbine Flow Rate maximum for that Turbine
53+
	Rod Control: 90% (Let's start off safe and then power up as we can)
54-
	- "RPM" will lower/raise the target Turbine RPM for that Turbine
54+
	Minimum Energy Buffer: 15% (will power on below this value)
55-
- Right-clicking between the "<" and ">" (not on them) will disable auto-adjust of that value for attached device.
55+
	Maximum Energy Buffer: 85% (will power off above this value)
56-
	- Right-clicking on the "Enabled" or "Disabled" text for auto-adjust will do the same.
56+
	Minimum Passive Cooling Temperature: 950^C (will raise control rods below this value)
57-
- Right-clicking on "ONLINE" or "OFFLINE" at the top-right will toggle the state of attached device.
57+
	Maximum Passive Cooling Temperature: 1,400^C (will lower control rods above this value)
58
	Minimum Active Cooling Temperature: 300^C (will raise the control rods below this value)
59-
Default values
59+
	Maximum Active Cooling Temperature: 420^C (will lower control rods above this value)
60-
----------------------------
60+
	Optimal Turbine RPM:  900, 1,800, or 2,700 (divisible by 900)
61-
- Rod Control: 90% (Let's start off safe and then power up as we can)
61+
	New user-controlled option for target speed of turbines, defaults to 2726RPM, which is high-optimal.
62-
- Minimum Energy Buffer: 15% (will power on below this value)
62+
63-
- Maximum Energy Buffer: 85% (will power off above this value)
63+
Requirements:
64-
- Minimum Passive Cooling Temperature: 950^C (will raise control rods below this value)
64+
	Advanced Monitor size is X: 29, Y: 12 with a 3x2 size
65-
- Maximum Passive Cooling Temperature: 1,400^C (will lower control rods above this value)
65+
	Computer or Advanced Computer
66-
- Minimum Active Cooling Temperature: 300^C (will raise the control rods below this value)
66+
	Modems (not wireless) connecting each of the Computer to both the Advanced Monitor and Reactor Computer Port.
67-
- Maximum Active Cooling Temperature: 420^C (will lower control rods above this value)
67+
	Big Reactors (http://www.big-reactors.com/) 0.3.2A+
68-
- Optimal Turbine RPM:  900, 1,800, or 2,700 (divisible by 900)
68+
	Computercraft (http://computercraft.info/) 1.63+
69-
	- New user-controlled option for target speed of turbines, defaults to 2726RPM, which is high-optimal.
69+
	Reset the computer any time number of connected devices change.
70
71-
Requirements
71+
Resources:
72-
----------------------------
72+
This script is available from:
73-
- Advanced Monitor size is X: 29, Y: 12 with a 3x2 size
73+
	http://pastebin.com/fguScPBQ
74-
- Computer or Advanced Computer
74+
	https://github.com/sandalle/minecraft_bigreactor_control
75-
- Modems (not wireless) connecting each of the Computer to both the Advanced Monitor and Reactor Computer Port.
75+
Start-up script is available from:
76-
- Big Reactors (http://www.big-reactors.com/) 0.3.2A+
76+
	http://pastebin.com/ZTMzRLez
77-
- Computercraft (http://computercraft.info/) 1.58, 1.63+, or 1.73+
77+
	https://github.com/sandalle/minecraft_bigreactor_control
78-
- Reset the computer any time number of connected devices change.
78+
Other reactor control program which I based my program on:
79
	http://pastebin.com/aMAu4X5J (ScatmanJohn)
80-
Resources
80+
	http://pastebin.com/HjUVNDau (version ScatmanJohn based his on)
81-
----------------------------
81+
A simpler Big Reactor control program is available from:
82-
- This script is available from:
82+
	http://pastebin.com/7S5xCvgL (IronClaymore only for passively cooled reactors)
83-
	- http://pastebin.com/fguScPBQ
83+
84-
	- https://github.com/sandalle/minecraft_bigreactor_control
84+
	Reactor Computer Port API: http://wiki.technicpack.net/Reactor_Computer_Port
85
	Computercraft API: http://computercraft.info/wiki/Category:APIs
86-
- Start-up script is available from:
86+
	Big Reactors Efficiency, Speculation and Questions! http://www.reddit.com/r/feedthebeast/comments/1vzds0/big_reactors_efficiency_speculation_and_questions/
87-
	- http://pastebin.com/ZTMzRLez
87+
	Big Reactors API code: https://github.com/erogenousbeef/BigReactors/blob/master/erogenousbeef/bigreactors/common/multiblock/tileentity/TileEntityReactorComputerPort.java
88-
	- https://github.com/sandalle/minecraft_bigreactor_control
88+
	Big Reactors API: http://big-reactors.com/cc_api.html
89-
- Other reactor control program which I based my program on:
89+
90-
	- http://pastebin.com/aMAu4X5J (ScatmanJohn)
90+
ChangeLog:
91-
	- http://pastebin.com/HjUVNDau (version ScatmanJohn based his on)
91+
- 0.3.13
92-
- A simpler Big Reactor control program is available from:
92+
	- Fix one reactor and one monitor incorrectly using status display instead of control display (Issue #35)
93-
	- http://pastebin.com/7S5xCvgL (IronClaymore only for passively cooled reactors)
93+
	- Fix concatenating a string and boolean, see http://stackoverflow.com/questions/6615572/how-to-format-a-lua-string-with-a-boolean-variable
94-
- Reactor Computer Port API: http://wiki.technicpack.net/Reactor_Computer_Port
94+
	- Hopefully fix concatenating string and nul in printLog (Issue #3)
95-
- Computercraft API: http://computercraft.info/wiki/Category:APIs
95+
96-
- Big Reactors Efficiency, Speculation and Questions! http://www.reddit.com/r/feedthebeast/comments/1vzds0/big_reactors_efficiency_speculation_and_questions/
96+
- 0.3.12
97-
- Big Reactors API code: https://github.com/erogenousbeef/BigReactors/blob/master/erogenousbeef/bigreactors/common/multiblock/tileentity/TileEntityReactorComputerPort.java
97+
	- Mechaet's changes:
98-
- Big Reactors API: http://big-reactors.com/cc_api.html
98+
	- Redid some typing to correct a bug where the reactors always started with rod control disabled.
99-
- Big Reactor Simulator from http://reddit.com/r/feedthebeast : http://br.sidoh.org/
99+
100-
- A tutorial from FTB's rhn : http://forum.feed-the-beast.com/threads/rhns-continued-adventures-a-build-journal-guide-collection-etc.42664/page-10#post-657819
100+
- 0.3.11
101
	- Mechaet's changes:
102-
ChangeLog
102+
	- Cleaned up global variables list
103-
============================
103+
	- Added in per-device naming (displays a friendly name on the bottom of the monitor if configured in the device options file)
104-
- 0.3.17
104+
	- Bigger bypasses of control routines when the control has been overridden
105-
	- Display how much steam (in mB/t) a Turbine is receiving on that Turbine's monitor.
105+
	- Individual config files for turbines and reactors. Persistent between reboots, remembers your last saved settings.
106-
	- Set monitor scale before checking size fixing Issue #50.
106+
	- Cruise mode override bypass
107-
	- Having a monitor is now optional, closing Issue #46.
107+
	- Changing flow rate no longer toggles flow rate override on and off. Changing the flow rate clearly indicates intent, so we put the override flag on and leave it there.
108-
	- Incorporate steam supply and demand in reactor control thanks to @thetaphi (Nicolas Kratz).
108+
	- Changed the rate at which the regular algorithm adjusts reactor rod control rates. Instead of being 1:1 we now move at 1:5 speed because there is a wide loophole where big adjustments can cause a swinging pendulum effect continually missing the target.
109-
	- Added turbine coil auto dis-/engage (Issue #51 and #55) thanks to @thetaphi (Nicolas Kratz).
109+
110-
	- Stop overzealous controlRodAdjustAmount adjustment amount adjustment Issue #56 thanks to @thetaphi (Nicolas Kratz).
110+
- 0.3.10
111-
	- Monitors can be reconfigured via GUI fixes Issue #34 thanks to @thetaphi (Nicolas Kratz).
111+
	- Turbine algorithm pass by Mechaet.
112
	- Updated turbine GUI.
113-
Prior ChangeLogs are posted at https://github.com/sandalle/minecraft_bigreactor_control/releases
113+
	- Fix single monitor (again) for Issue #22.
114
115-
TODO
115+
- 0.3.9
116-
============================
116+
	- Reactor algorithm pass by Mechaet.
117-
See https://github.com/sandalle/minecraft_bigreactor_control/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement :)
117+
	- Additional user config options by Mechaet.
118
	- Fix multiple reactors and none or more turbines with only one status monitor.
119
	- Fix monitor scaling after one was used as debug (or in case of other modifications).
120
	- Cruise mode implemented, defaults off but is saved between boots.
121
	- Fix energy/% displays to match Big Reactors' GUI (Issue #9).
122
	- Always write out found devices on computer terminal.
123-
local progVer = "0.3.17"
123+
	- Much improved round() function from mechaet (Issue #14).
124
	- Refactoring pass/algorithm change on the reactor temperature control. Should now adjust in increments to achieve the desired temperature range quicker and more accurately.
125
	- Optimal passive-cooled reactor temperature range changed from 850-900 to 950-1400.
126
	- Fix display Issue #15.
127
128
- 0.3.8
129
	- Update to ComputerCraft 1.6 API (only term.restore() -> term.native() required :)).
130
131
- 0.3.7
132
	- Fix typo when initializing TurbineNames array.
133
	- Fix Issue #1, turbine display is using the Reactor buffer size (10M RF) instead of the Turbine buffer size (1M RF).
134
135
- 0.3.6
136
	- Fix multi-reactors displaying on the correct monitors (thanks HybridFusion).
137
	- Fix rod auto-adjust text position.
138
	- Reactors store 10M RF and Turbines store 1M RF in their buffer.
139-
local monitorAssignments = {} -- Empty array of monitor - "what to display" assignments
139+
	- Add more colour to displayAllStatus().
140-
local monitorOptionFileName = "monitors.options" -- File for saving the monitor assignments
140+
	- Sleep for only two seconds instead of five.
141-
local knowlinglyOverride = false -- Issue #39 Allow the user to override safe values, currently only enabled for actively cooled reactor min/max temperature
141+
	- Fix getDeviceStoredEnergyBufferPercent() for Reactors storing 10M RF in buffer.
142-
local steamRequested = 0 -- Sum of Turbine Flow Rate in mB
142+
	- Keep actively cooled reactors between 0-300^C (non-configurable for now).
143-
local steamDelivered = 0 -- Sum of Active Reactor steam output in mB
143+
144
- 0.3.5
145-
-- Log levels
145+
	- Do not discover connected devices every loop - nicer on servers. Reset computer anytime number of connected devices change.
146-
local FATAL = 16
146+
	- Fix multi-reactor setups to display the additional reactors on monitors, rather than the last one found.
147-
local ERROR = 8
147+
	- Fix passive reactor display having auto-adjust and energy buffer overwrite each other (removes rod count).
148-
local WARN = 4
148+
149-
local INFO = 2
149+
- 0.3.4
150-
local DEBUG = 1
150+
	- Fix arithmetic for checking if we have enough monitors for the number of reactors.
151
	- Turbines are optimal at 900, 1800, *and* 2700 RPM
152
	- Increase loop timer from 1 to 5 to be nicer to servers
153
154
- 0.3.3
155
	- Add Big Reactors Turbine support.
156
	- First found monitor (appears to be last connected monitor) is used to display status of all found devices (if more than one valid monitor is found)
157
	- Display monitor number on top left of each monitor as "M#" to help find which monitor is which.
158
	- Enabling debug will use the last monitor found, if more than one, to print out debug info (also written to file)
159
	- Only clear monitors when we're about to use them (e.g. turbine monitors no longer clear, then wait for all reactors to update)
160
	- Fix getDeviceStoredEnergyBufferPercent(), was off by a decimal place
161
	- Just use first Control Rod level for entire reactor, they are no longer treated individually in BR 0.3
162
	- Allow for one monitor for n number of reactors and m number of turbines
163
	- Auto-adjust turbine flow rate by 25 mB to keep rotor speed at 900 or 1,800 RPM.
164
	- Clicks on monitors relate to what the monitor is showing (e.g. clicking on reactor 1's display won't modify turbine 1's nor reactor 2's values)
165
	- Print monitor name and device (reactor|turbine) name in blue to monitor associated for easier design by users.
166
	- Remove version number from monitors to free up space for monitor names.
167
	- Add option of right-clicking on "Enabled"/"Disabled" of auto-adjust to toggle it.
168
169
- 0.3.2
170-
local function termRestore()
170+
	- Allow for rod control to override (disable) auto-adjust via UI (Rhonyn)
171-
	local ccVersion = nil
171+
172-
	ccVersion = os.version()
172+
- 0.3.1
173
	- Add fuel consumption per tick to display
174-
	if ccVersion == "CraftOS 1.6" or "CraftOS 1.7" then
174+
175-
		term.redirect(term.native())
175+
- 0.3.0
176-
	elseif ccVersion == "CraftOS 1.5" then
176+
	- Add multi-monitor support! Sends one reactor's data to all monitors.
177-
		term.restore()
177+
	- print function now takes table to support optional specified monitor
178-
	else -- Default to older term.restore
178+
	- Set "numRods" every cycle for some people (mechaet)
179-
		printLog("Unsupported CraftOS found. Reported version is \""..ccVersion.."\".")
179+
	- Don't redirect terminal output with multiple monitor support
180-
		term.restore()
180+
	- Log troubleshooting data to reactorcontrol.log
181-
	end -- if ccVersion
181+
	- FC_API no longer used (copied and modified what I needed)
182-
end -- function termRestore()
182+
	- Multi-reactor support is theoretically implemented, but it is UNTESTED!
183
	- Updated for Big Reactor 0.3 (no longer works with 0.2)
184-
local function printLog(printStr, logLevel)
184+
	- BR getFuelTemperature() now returns many significant digits, just use math.ceil()
185-
	logLevel = logLevel or INFO
185+
	- BR 0.3 removed individual rod temperatures, now it's only reactor-level temperature
186-
	-- 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.
186+
187-
	-- Since you're already looking at it, you can adjust your preferred log level right here.
187+
- 0.2.4
188-
	if debugMode and (logLevel >= WARN) then
188+
	- Simplify math, don't divide by a simple large number and then multiply by 100 (#/10000000*100)
189-
		-- If multiple monitors, print to all of them
189+
	- Fix direct-connected (no modem) devices. getDeviceSide -> FC_API.getDeviceSide (simple as that :))
190-
		for monitorName, deviceData in pairs(monitorAssignments) do
190+
191-
			if deviceData.type == "Debug" then
191+
- 0.2.3
192-
				debugMonitor = monitorList[deviceData.index]
192+
	- Check bounds on reactor.setRodControlLevel(#,#), Big Reactor doesn't check for us.
193-
				if(not debugMonitor) or (not debugMonitor.getSize()) then
193+
194-
					term.write("printLog(): debug monitor "..monitorName.." failed")
194+
- 0.2.2
195
	- Do not auto-start the reactor if it was manually powered off (autoStart=false)
196-
					term.redirect(debugMonitor) -- Redirect to selected monitor
196+
197-
					debugMonitor.setTextScale(0.5) -- Fit more logs on screen
197+
- 0.2.1
198-
					local color = colors.lightGray
198+
	- Lower/raise only the hottest/coldest Control Rod while trying to control the reactor temperature.
199-
					if (logLevel == WARN) then
199+
	- "<" Rod Control buttons was off by one (to the left)
200-
						color = colors.white
200+
201-
					elseif (logLevel == ERROR) then
201+
- 0.2.0 - Lolmer Edition :)
202-
						color = colors.red
202+
	- Add min/max stored energy percentage (default is 15%/85%), configurable via ReactorOptions file.
203-
					elseif (logLevel == FATAL) then
203+
	- No reason to keep burning fuel if our power output is going nowhere. :)
204-
						color = colors.black
204+
	- Use variables variable for the title and version.
205-
						debugMonitor.setBackgroundColor(colors.red)
205+
	- Try to keep the temperature between configured values (default is 850^C-950^C)
206
	- Add Waste and number of Control/Fuel Rods to displayBards()
207-
					debugMonitor.setTextColor(color)
207+
208-
					write(printStr.."\n")   -- May need to use term.scroll(x) if we output too much, not sure
208+
TODO:
209-
					debugMonitor.setBackgroundColor(colors.black)
209+
- Save parameters per reactor instead of one global set for all reactors.
210-
					termRestore()
210+
- Add min/max RF/t output and have it override temperature concerns (maybe?).
211
- Add support for wireless modems, see http://computercraft.info/wiki/Modem_%28API%29, will not be secure (anyone can send/listen to your channels)!
212
- Add support for any sized monitor (minimum 3x3), dynamic allocation/alignment.
213-
		end -- for 
213+
- Lookup using pcall for better error handling http://www.computercraft.info/forums2/index.php?/topic/10992-using-pcall/ .
214
- Update cruise mode to work independently for each actively-cooled reactor.
215
216
]]--
217
218
219
-- Some global variables
220
local progVer = "0.3.13"
221
local progName = "EZ-NUKE"
222
local sideClick, xClick, yClick = nil, 0, 0
223
local loopTime = 2
224
local controlRodAdjustAmount = 1 -- Default Reactor Rod Control % adjustment amount
225
local flowRateAdjustAmount = 25 -- Default Turbine Flow Rate in mB adjustment amount
226
local debugMode = false
227
-- End multi-reactor cleanup section
228
local minStoredEnergyPercent = nil -- Max energy % to store before activate
229
local maxStoredEnergyPercent = nil -- Max energy % to store before shutdown
230
local monitorList = {} -- Empty monitor array
231-
-- Format number with [k,M,G,T,P,E] postfix or exponent, depending on how large it is
231+
232-
local function formatReadableSIUnit(num)
232+
233-
	printLog("formatReadableSIUnit("..num..")", DEBUG)
233+
234-
	num = tonumber(num)
234+
235-
	if(num < 1000) then return tostring(num) end
235+
236-
	local sizes = {"", "k", "M", "G", "T", "P", "E"}
236+
local turbineMonitorOffset = 0 -- Turbines are assigned monitors after reactors
237-
	local exponent = math.floor(math.log10(num))
237+
238-
	local group = math.floor(exponent / 3)
238+
239-
	if group > #sizes then
239+
240-
		return string.format("%e", num)
240+
241
write("Initializing program...\n")
242-
		local divisor = math.pow(10, (group - 1) * 3)
242+
243-
		return string.format("%i%s", num / divisor, sizes[group])
243+
244
-- File needs to exist for append "a" later and zero it out if it already exists
245-
end -- local function formatReadableSIUnit(num)
245+
246
local logFile = fs.open("reactorcontrol.log", "w")
247-
-- pretty printLog() a table
247+
248-
local function tprint (tbl, loglevel, indent)
248+
249-
	if not loglevel then loglevel = DEBUG end
249+
250-
	if not indent then indent = 0 end
250+
251-
	for k, v in pairs(tbl) do
251+
252-
		formatting = string.rep("  ", indent) .. k .. ": "
252+
253-
		if type(v) == "table" then
253+
254-
			printLog(formatting, loglevel)
254+
255-
			tprint(v, loglevel, indent+1)
255+
256-
		elseif type(v) == 'boolean' or type(v) == "function" then
256+
257-
			printLog(formatting .. tostring(v), loglevel)      
257+
258
local function printLog(printStr)
259-
			printLog(formatting .. v, loglevel)
259+
	if debugMode then
260
		-- If multiple monitors, use the last monitor for debugging if debug is enabled
261
		if #monitorList > 1 then
262-
end -- function tprint()
262+
			term.redirect(monitorList[#monitorList]) -- Redirect to last monitor for debugging
263
			monitorList[#monitorList].setTextScale(0.5) -- Fit more logs on screen
264
			write(printStr.."\n")   -- May need to use term.scroll(x) if we output too much, not sure
265
			term.native()
266
		end -- if #monitorList > 1 then
267
268
		local logFile = fs.open("reactorcontrol.log", "a") -- See http://computercraft.info/wiki/Fs.open
269
		if logFile then
270
			logFile.writeLine(printStr)
271
			logFile.close()
272
		else
273
			error("Cannot open file reactorcontrol.log for appending!")
274
		end -- if logFile then
275
	end -- if debugMode then
276
end -- function printLog(printStr)
277
278
-- Trim a string
279
function stringTrim(s)
280
	assert(s ~= nil, "String can't be nil")
281
	return(string.gsub(s, "^%s*(.-)%s*$", "%1"))
282
end
283
284
config = {}
285
286
-- Save a table into a config file
287
-- path: path of the file to write
288
-- tab: table to save
289
config.save = function(path, tab)
290
	printLog("Save function called for config for "..path.." EOL")
291
	assert(path ~= nil, "Path can't be nil")
292
	assert(type(tab) == "table", "Second parameter must be a table")
293
	local f = io.open(path, "w")
294
	local i = 0
295
	for key, value in pairs(tab) do
296
		if i ~= 0 then
297
			f:write("\n")
298
		end
299
		f:write("["..key.."]".."\n")
300
		for key2, value2 in pairs(tab[key]) do
301
			key2 = stringTrim(key2)
302
			--doesn't like boolean values
303
			if (type(value2) ~= "boolean") then
304
			value2 = stringTrim(value2)
305
			else
306-
		printLog("Successfully opened "..path.." for reading EOL")
306+
307
			end
308
			key2 = key2:gsub(";", "\\;")
309
			key2 = key2:gsub("=", "\\=")
310
			value2 = value2:gsub(";", "\\;")
311
			value2 = value2:gsub("=", "\\=")	
312
			f:write(key2.."="..value2.."\n")
313
		end
314
		i = i + 1
315
	end
316
	f:close()
317
end --config.save = function(path, tab)
318
319
-- Load a config file
320
-- path: path of the file to read
321
config.load = function(path)
322
	printLog("Load function called for config for "..path.." EOL")
323
	assert(path ~= nil, "Path can't be nil")
324
	local f = fs.open(path, "r")
325
	if f ~= nil then
326
		local tab = {}
327
		local line = ""
328
		local newLine
329
		local i
330
		local currentTag = nil
331
		local found = false
332
		local pos = 0
333
		while line ~= nil do
334
			found = false		
335
			line = line:gsub("\\;", "#_!36!_#") -- to keep \;
336
			line = line:gsub("\\=", "#_!71!_#") -- to keep \=
337
			if line ~= "" then
338
				-- Delete comments
339
				newLine = line
340
				line = ""
341
				for i=1, string.len(newLine) do				
342
					if string.sub(newLine, i, i) ~= ";" then
343
						line = line..newLine:sub(i, i)						
344
					else				
345
						break
346
					end
347
				end
348
				line = stringTrim(line)
349
				-- Find tag			
350
				if line:sub(1, 1) == "[" and line:sub(line:len(), line:len()) == "]" then
351
					currentTag = stringTrim(line:sub(2, line:len()-1))
352
					tab[currentTag] = {}
353
					found = true							
354
				end
355-
		printLog("Could NOT opened "..path.." for reading! EOL")
355+
356
				if not found and line ~= "" then				
357
					pos = line:find("=")				
358
					if pos == nil then
359
						error("Bad INI file structure")
360
					end
361
					line = line:gsub("#_!36!_#", ";")
362
					line = line:gsub("#_!71!_#", "=")
363
					tab[currentTag][stringTrim(line:sub(1, pos-1))] = stringTrim(line:sub(pos+1, line:len()))
364
					found = true			
365
				end			
366
			end
367
			line = f.readLine()
368
		end
369
		
370
		f:close()
371
		
372
		return tab
373
	else
374
		return nil
375
	end
376
end --config.load = function(path)
377
378
379
380
-- round() function from mechaet
381
local function round(num, places)
382
	local mult = 10^places
383
	local addon = nil
384
	if ((num * mult) < 0) then
385
		addon = -.5
386
	else
387
		addon = .5
388
	end
389
390
	local integer, decimal = math.modf(num*mult+addon)
391
	newNum = integer/mult
392
	printLog("Called round(num="..num..",places="..places..") returns \""..newNum.."\".")
393
	return newNum
394
end -- function round(num, places)
395
396
397
local function print(printParams)
398
	-- Default to xPos=1, yPos=1, and first monitor
399
	setmetatable(printParams,{__index={xPos=1, yPos=1, monitorIndex=1}})
400
	local printString, xPos, yPos, monitorIndex =
401
		printParams[1], -- Required parameter
402
		printParams[2] or printParams.xPos,
403
		printParams[3] or printParams.yPos,
404
		printParams[4] or printParams.monitorIndex
405
406
	local monitor = nil
407-
		printLog("monitor["..monitorIndex.."] in printCentered() is NOT a valid monitor.", ERROR)
407+
408
409
	if not monitor then
410
		printLog("monitor["..monitorIndex.."] in print() is NOT a valid monitor.")
411
		return -- Invalid monitorIndex
412
	end
413
414
	monitor.setCursorPos(xPos, yPos)
415
	monitor.write(printString)
416
end -- function print(printParams)
417
418-
		width = width - monitorNameLength -- add a space
418+
419
-- Replaces the one from FC_API (http://pastebin.com/A9hcbZWe) and adding multi-monitor support
420
local function printCentered(printString, yPos, monitorIndex)
421-
		if monitorAssignments[monitorNames[monitorIndex]].type ~= "Status" then
421+
422
	monitor = monitorList[monitorIndex]
423
424
	if not monitor then
425
		printLog("monitor["..monitorIndex.."] in printCentered() is NOT a valid monitor.")
426-
	monitor.setCursorPos(monitorNameLength + math.ceil((1 + width - printString:len())/2), yPos)
426+
427
	end
428
429
	local width, height = monitor.getSize()
430
	local monitorNameLength = 0
431
432
	-- Special changes for title bar
433
	if yPos == 1 then
434
		-- Add monitor name to first line
435
		monitorNameLength = monitorNames[monitorIndex]:len()
436
437
		-- Leave room for "offline" and "online" on the right except for overall status display
438-
		printLog("monitor["..monitorIndex.."] in printLeft() is NOT a valid monitor.", ERROR)
438+
		if (#monitorList ~= 1) and (monitorIndex ~= 1) then
439
			width = width - 7
440
		end
441
	end
442
443
	monitor.setCursorPos(math.floor(width/2) - math.ceil(printString:len()/2) +  monitorNameLength/2, yPos)
444
	monitor.clearLine()
445
	monitor.write(printString)
446
447
	monitor.setTextColor(colors.blue)
448
	print{monitorNames[monitorIndex], 1, 1, monitorIndex}
449
	monitor.setTextColor(colors.white)
450
end -- function printCentered(printString, yPos, monitorIndex)
451
452
453
-- Print text padded from the left side
454
-- Clear the left side of the screen
455
local function printLeft(printString, yPos, monitorIndex)
456
	local monitor = nil
457
	monitor = monitorList[monitorIndex]
458
459
	if not monitor then
460
		printLog("monitor["..monitorIndex.."] in printLeft() is NOT a valid monitor.")
461
		return -- Invalid monitorIndex
462
	end
463
464
	local gap = 1
465-
		printLog("monitor["..monitorIndex.."] in printRight() is NOT a valid monitor.", ERROR)
465+
466
467
	-- Clear left-half of the monitor
468
469
	for curXPos = 1, (width / 2) do
470
		monitor.setCursorPos(curXPos, yPos)
471
		monitor.write(" ")
472
	end
473
474
	-- Write our string left-aligned
475
	monitor.setCursorPos(1+gap, yPos)
476
	monitor.write(printString)
477
end
478
479
480
-- Print text padded from the right side
481
-- Clear the right side of the screen
482
local function printRight(printString, yPos, monitorIndex)
483
	local monitor = nil
484
	monitor = monitorList[monitorIndex]
485
486
	if not monitor then
487
		printLog("monitor["..monitorIndex.."] in printRight() is NOT a valid monitor.")
488
		return -- Invalid monitorIndex
489
	end
490
491
	-- Make sure printString is a string
492
	printString = tostring(printString)
493
494
	local gap = 1
495-
		printLog("monitor["..monitorIndex.."] in clearMonitor(printString="..printString..",monitorIndex="..monitorIndex..") is NOT a valid monitor.", ERROR)
495+
496
497
	-- Clear right-half of the monitor
498
	for curXPos = (width/2), width do
499
		monitor.setCursorPos(curXPos, yPos)
500
		monitor.write(" ")
501
	end
502
503
	-- Write our string right-aligned
504
	monitor.setCursorPos(math.floor(width) - math.ceil(printString:len()+gap), yPos)
505
	monitor.write(printString)
506
end
507
508
509
-- Replaces the one from FC_API (http://pastebin.com/A9hcbZWe) and adding multi-monitor support
510
local function clearMonitor(printString, monitorIndex)
511
	local monitor = nil
512
	monitor = monitorList[monitorIndex]
513
514
	printLog("Called as clearMonitor(printString="..printString..",monitorIndex="..monitorIndex..").")
515
516
	if not monitor then
517
		printLog("monitor["..monitorIndex.."] in clearMonitor(printString="..printString..",monitorIndex="..monitorIndex..") is NOT a valid monitor.")
518
		return -- Invalid monitorIndex
519
	end
520
521
	local gap = 2
522
	monitor.clear()
523
	local width, height = monitor.getSize()
524
	monitor.setTextScale(1.0) -- Make sure scale is correct
525
526
	printCentered(printString, 1, monitorIndex)
527
	monitor.setTextColor(colors.blue)
528
	print{monitorNames[monitorIndex], 1, 1, monitorIndex}
529
	monitor.setTextColor(colors.white)
530
531
	for i=1, width do
532
		monitor.setCursorPos(i, gap)
533
		monitor.write("-")
534
	end
535
536
	monitor.setCursorPos(1, gap+1)
537
end -- function clearMonitor(printString, monitorIndex)
538
539
540
-- Return a list of all connected (including via wired modems) devices of "deviceType"
541
local function getDevices(deviceType)
542
	printLog("Called as getDevices(deviceType="..deviceType..")")
543
544
	local deviceName = nil
545
	local deviceIndex = 1
546
	local deviceList, deviceNames = {}, {} -- Empty array, which grows as we need
547
	local peripheralList = peripheral.getNames() -- Get table of connected peripherals
548
549
	deviceType = deviceType:lower() -- Make sure we're matching case here
550
551
	for peripheralIndex = 1, #peripheralList do
552
		-- Log every device found
553
		-- printLog("Found "..peripheral.getType(peripheralList[peripheralIndex]).."["..peripheralIndex.."] attached as \""..peripheralList[peripheralIndex].."\".")
554
		if (string.lower(peripheral.getType(peripheralList[peripheralIndex])) == deviceType) then
555
			-- Log devices found which match deviceType and which device index we give them
556
			printLog("Found "..peripheral.getType(peripheralList[peripheralIndex]).."["..peripheralIndex.."] as index \"["..deviceIndex.."]\" attached as \""..peripheralList[peripheralIndex].."\".")
557
			write("Found "..peripheral.getType(peripheralList[peripheralIndex]).."["..peripheralIndex.."] as index \"["..deviceIndex.."]\" attached as \""..peripheralList[peripheralIndex].."\".\n")
558
			deviceNames[deviceIndex] = peripheralList[peripheralIndex]
559
			deviceList[deviceIndex] = peripheral.wrap(peripheralList[peripheralIndex])
560
			deviceIndex = deviceIndex + 1
561
		end
562
	end -- for peripheralIndex = 1, #peripheralList do
563
564
	return deviceList, deviceNames
565
end -- function getDevices(deviceType)
566
567
-- Draw a line across the entire x-axis
568
local function drawLine(yPos, monitorIndex)
569
	local monitor = nil
570
	monitor = monitorList[monitorIndex]
571
572
	if not monitor then
573
		printLog("monitor["..monitorIndex.."] in drawLine() is NOT a valid monitor.")
574
		return -- Invalid monitorIndex
575
	end
576
577
	local width, height = monitor.getSize()
578-
	termRestore()
578+
579
	for i=1, width do
580
		monitor.setCursorPos(i, yPos)
581
		monitor.write("-")
582
	end
583
end -- function drawLine(yPos,monitorIndex)
584
585
586
-- Display a solid bar of specified color
587
local function drawBar(startXPos, startYPos, endXPos, endYPos, color, monitorIndex)
588
	local monitor = nil
589
	monitor = monitorList[monitorIndex]
590
591
	if not monitor then
592
		printLog("monitor["..monitorIndex.."] in drawBar() is NOT a valid monitor.")
593
		return -- Invalid monitorIndex
594
	end
595
596
	-- PaintUtils only outputs to term., not monitor.
597-
	termRestore()
597+
598
	term.redirect(monitor)
599
	paintutils.drawLine(startXPos, startYPos, endXPos, endYPos, color)
600-
local function saveMonitorAssignments()
600+
601-
	local assignments = {}
601+
	term.native()
602-
	for monitor, data in pairs(monitorAssignments) do
602+
603-
		local name = nil
603+
604-
		if (data.type == "Reactor") then
604+
605-
			name = data.reactorName
605+
606-
		elseif (data.type == "Turbine") then
606+
607-
			name = data.turbineName
607+
608
	monitor = monitorList[monitorIndex]
609-
			name = data.type
609+
610
	if not monitor then
611-
		assignments[monitor] = name
611+
612
		return -- Invalid monitorIndex
613-
	config.save(monitorOptionFileName, {Monitors = assignments})
613+
614
615
	-- PaintUtils only outputs to term., not monitor.
616-
UI = {
616+
617-
	monitorIndex = 1,
617+
618-
	reactorIndex = 1,
618+
619-
	turbineIndex = 1
619+
620-
}
620+
	term.native()
621
end -- function drawPixel(xPos, yPos, color, monitorIndex)
622-
UI.handlePossibleClick = function(self)
622+
623-
	local monitorData = monitorAssignments[sideClick]
623+
624-
	if monitorData == nil then
624+
625-
		printLog("UI.handlePossibleClick(): "..sideClick.." is unassigned, can't handle click", WARN)
625+
626-
		return
626+
627
-- Then initialize the monitors
628
local function findMonitors()
629-
	self.monitorIndex = monitorData.index
629+
630-
	local width, height = monitorList[self.monitorIndex].getSize()
630+
631-
	-- All the last line are belong to us
631+
632-
	if (yClick == height) then
632+
633-
		if (monitorData.type == "Reactor") then
633+
634-
			if (xClick == 1) then
634+
635-
				self:selectPrevReactor()
635+
636-
			elseif (xClick == width) then
636+
		printLog("No monitors found!")
637-
				self:selectNextReactor()
637+
		error("Can't find any monitors!")
638-
			elseif (3 <= xClick and xClick <= width - 2) then
638+
639-
				self:selectTurbine()
639+
640
			local monitor, monitorX, monitorY = nil, nil, nil
641-
		elseif (monitorData.type == "Turbine") then
641+
642-
			if (xClick == 1) then
642+
643-
				self:selectPrevTurbine()
643+
644-
			elseif (xClick == width) then
644+
645-
				self:selectNextTurbine()
645+
646-
			elseif (3 <= xClick and xClick <= width - 2) then
646+
647-
				self:selectStatus()
647+
648
					monitorIndex = monitorIndex - 1 -- We just removed an element
649-
		elseif (monitorData.type == "Status") then
649+
650-
			if (xClick == 1) then
650+
651-
				self.turbineIndex = #turbineList
651+
652-
				self:selectTurbine()
652+
653-
			elseif (xClick == width) then
653+
654-
				self.reactorIndex = 1
654+
655-
				self:selectReactor()
655+
656-
			elseif (3 <= xClick and xClick <= width - 2) then
656+
657-
				self:selectReactor()
657+
658
						monitorIndex = monitorIndex - 1 -- We just removed an element
659
					end -- if monitorIndex == #monitorList then
660-
			self:selectStatus()
660+
661
662-
		-- Yes, that means we're skipping Debug. I figure everyone who wants that is
662+
663-
		-- bound to use the console key commands anyway, and that way we don't have
663+
664-
		-- it interfere with regular use.
664+
665
					monitor.clear()
666
					printLog("Removing monitor "..monitorIndex.." for being too small.")
667
					monitor.setCursorPos(1,2)
668-
		if (monitorData.type == "Turbine") then
668+
669-
			self:handleTurbineMonitorClick(monitorData.turbineIndex, monitorData.index)
669+
670-
		elseif (monitorData.type == "Reactor") then
670+
					term.native()
671-
			self:handleReactorMonitorClick(monitorData.reactorIndex, monitorData.index)
671+
672
					table.remove(monitorList, monitorIndex) -- Remove invalid monitor from list
673
					if monitorIndex == #monitorList then    -- If we're at the end already, break from loop
674-
end -- UI.handlePossibleClick()
674+
675
					else
676-
UI.logChange = function(self, messageText)
676+
677-
	printLog("UI: "..messageText)
677+
678-
	termRestore()
678+
679-
	write(messageText.."\n")
679+
680
			end -- if not monitor then
681
682-
UI.selectNextMonitor = function(self)
682+
683-
	self.monitorIndex = self.monitorIndex + 1
683+
684-
	if self.monitorIndex > #monitorList then
684+
685-
		self.monitorIndex = 1
685+
686
	printLog("Found "..#monitorList.." monitor(s) in findMonitors().")
687-
	local messageText = "Selected monitor "..monitorNames[self.monitorIndex]
687+
688-
	self:logChange(messageText)
688+
689-
end -- UI.selectNextMonitor()
689+
690
-- Initialize all Big Reactors - Reactors
691
local function findReactors()
692-
UI.selectReactor = function(self)
692+
693-
	monitorAssignments[monitorNames[self.monitorIndex]] = {type="Reactor", index=self.monitorIndex, reactorName=reactorNames[self.reactorIndex], reactorIndex=self.reactorIndex}
693+
694-
	saveMonitorAssignments()
694+
695-
	local messageText = "Selected reactor "..reactorNames[self.reactorIndex].." for display on "..monitorNames[self.monitorIndex]
695+
696-
	self:logChange(messageText)
696+
697-
end -- UI.selectReactor()
697+
698
		printLog("No reactors found!")
699-
UI.selectPrevReactor = function(self)
699+
700-
	if self.reactorIndex <= 1 then
700+
701-
		self.reactorIndex = #reactorList
701+
702-
		self:selectStatus()
702+
703
			reactor = newReactorList[reactorIndex]
704-
		self.reactorIndex = self.reactorIndex - 1
704+
705-
		self:selectReactor()
705+
706
				printLog("reactorList["..reactorIndex.."] in findReactors() is NOT a valid Big Reactor.")
707-
end -- UI.selectPrevReactor()
707+
708
				table.remove(newReactorList, reactorIndex) -- Remove invalid reactor from list
709-
UI.selectNextReactor = function(self)
709+
710-
	if self.reactorIndex >= #reactorList then
710+
711-
		self.reactorIndex = 1
711+
712-
		self.turbineIndex = 1
712+
713-
		self:selectTurbine()
713+
714
				printLog("reactor["..reactorIndex.."] in findReactors() is a valid Big Reactor.")
715-
		self.reactorIndex = self.reactorIndex + 1
715+
716-
		self:selectReactor()
716+
717
				_G[reactorNames[reactorIndex]]["ReactorOptions"] = {}
718-
end -- UI.selectNextReactor()
718+
719
				_G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"] = 0
720
				_G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] = true
721-
UI.selectTurbine = function(self)
721+
722-
	monitorAssignments[monitorNames[self.monitorIndex]] = {type="Turbine", index=self.monitorIndex, turbineName=turbineNames[self.turbineIndex], turbineIndex=self.turbineIndex}
722+
723-
	saveMonitorAssignments()
723+
724-
	local messageText = "Selected turbine "..turbineNames[self.turbineIndex].." for display on "..monitorNames[self.monitorIndex]
724+
725-
	self:logChange(messageText)
725+
726-
end -- UI.selectTurbine()
726+
727
				if reactor.getConnected() then
728-
UI.selectPrevTurbine = function(self)
728+
729-
	if self.turbineIndex <= 1 then
729+
730-
		self.turbineIndex = #turbineList
730+
731-
		self.reactorIndex = #reactorList
731+
732-
		self:selectReactor()
732+
733
			end
734-
		self.turbineIndex = self.turbineIndex - 1
734+
735-
		self:selectTurbine()
735+
736
			local tempTable = _G[reactorNames[reactorIndex]]
737-
end -- UI.selectPrevTurbine()
737+
738
			--check to make sure we get a valid config
739-
UI.selectNextTurbine = function(self)
739+
740-
	if self.turbineIndex >= #turbineList then
740+
741-
		self.turbineIndex = 1
741+
742-
		self:selectStatus()
742+
743
				config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]])
744-
		self.turbineIndex = self.turbineIndex + 1
744+
745-
		self:selectTurbine()
745+
746
			--load values from tempTable, checking for nil values along the way
747-
end -- UI.selectNextTurbine()
747+
748
				_G[reactorNames[reactorIndex]]["ReactorOptions"]["baseControlRodLevel"] = tempTable["ReactorOptions"]["baseControlRodLevel"]
749
			end
750-
UI.selectStatus = function(self)
750+
751-
	monitorAssignments[monitorNames[self.monitorIndex]] = {type="Status", index=self.monitorIndex}
751+
752-
	saveMonitorAssignments()
752+
753-
	local messageText = "Selected status summary for display on "..monitorNames[self.monitorIndex]
753+
754-
	self:logChange(messageText)
754+
755-
end -- UI.selectStatus()
755+
756
				_G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] = tempTable["ReactorOptions"]["autoStart"]
757-
UI.selectDebug = function(self)
757+
758-
	monitorAssignments[monitorNames[self.monitorIndex]] = {type="Debug", index=self.monitorIndex}
758+
759-
	saveMonitorAssignments()
759+
760-
	monitorList[self.monitorIndex].clear()
760+
761-
	local messageText = "Selected debug output for display on "..monitorNames[self.monitorIndex]
761+
762-
	self:logChange(messageText)
762+
763-
end -- UI.selectDebug()
763+
764
				_G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorMaxTemp"] = tempTable["ReactorOptions"]["reactorMaxTemp"]
765-
-- Allow controlling Reactor Control Rod Level from GUI
765+
766-
UI.handleReactorMonitorClick = function(self, reactorIndex, monitorIndex)
766+
767
			if tempTable["ReactorOptions"]["reactorMinTemp"] ~= nil then
768
				_G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorMinTemp"] = tempTable["ReactorOptions"]["reactorMinTemp"]
769
			end
770
			
771
			if tempTable["ReactorOptions"]["rodOverride"] ~= nil then
772
				printLog("Got value from config file for Rod Override, the value is: "..tostring(tempTable["ReactorOptions"]["rodOverride"]).." EOL")
773
				_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] = tempTable["ReactorOptions"]["rodOverride"]
774
			end
775
			
776
			if tempTable["ReactorOptions"]["reactorName"] ~= nil then
777
				_G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorName"] = tempTable["ReactorOptions"]["reactorName"]
778
			end
779
			
780
			if tempTable["ReactorOptions"]["reactorCruising"] ~= nil then
781
				_G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorCruising"] = tempTable["ReactorOptions"]["reactorCruising"]
782
			end
783-
		printLog("reactor["..reactorIndex.."] in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT a valid Big Reactor.")
783+
784
			--stricter typing, let's set these puppies up with the right type of value.
785
			_G[reactorNames[reactorIndex]]["ReactorOptions"]["baseControlRodLevel"] = tonumber(_G[reactorNames[reactorIndex]]["ReactorOptions"]["baseControlRodLevel"])
786-
		printLog("reactor["..reactorIndex.."] in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is a valid Big Reactor.")
786+
787
			_G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"] = tonumber(_G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"])
788-
			printLog("reactor["..reactorIndex.."] in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is connected.")
788+
789
			if (tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"]) == "true") then
790-
			printLog("reactor["..reactorIndex.."] in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT connected.")
790+
791
			else
792
				_G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] = false
793
			end
794
			
795-
	local reactorStatus = _G[reactorNames[reactorIndex]]["ReactorOptions"]["Status"]
795+
796
				_G[reactorNames[reactorIndex]]["ReactorOptions"]["activeCooled"] = true
797
			else
798-
	if xClick >= (width - string.len(reactorStatus) - 1) and xClick <= (width-1) and (sideClick == monitorNames[monitorIndex]) then
798+
799-
		if yClick == 1 then
799+
800-
			reactor.setActive(not reactor.getActive()) -- Toggle reactor status
800+
801-
			_G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] = reactor.getActive()
801+
802
			
803
			_G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorMinTemp"] = tonumber(_G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorMinTemp"])
804
			
805-
			-- If someone offlines the reactor (offline after a status click was detected), then disable autoStart
805+
806-
			if not reactor.getActive() then
806+
807
				_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] = true
808
			else
809-
		end -- if yClick == 1 then
809+
810-
	end -- if (xClick >= (width - string.len(reactorStatus) - 1) and xClick <= (width-1)) and (sideClick == monitorNames[monitorIndex]) then
810+
811
			end
812-
	-- Allow disabling rod level auto-adjust and only manual rod level control
812+
813-
	if ((xClick > 23 and xClick < 28 and yClick == 4)
813+
814-
			or (xClick > 20 and xClick < 27 and yClick == 9))
814+
815-
			and (sideClick == monitorNames[monitorIndex]) then
815+
816-
		_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] = not _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]
816+
817
			end
818-
		sideClick, xClick, yClick = 0, 0, 0 -- Reset click after we register it
818+
819-
	end -- if (xClick > 23) and (xClick < 28) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
819+
820
			config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]])
821
		end -- for reactorIndex = 1, #newReactorList do
822-
	local newRodPercentage = rodPercentage
822+
823
824-
		printLog("Decreasing Rod Levels in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
824+
825
	reactorList = newReactorList
826
827
	-- Start turbine monitor offset after reactors get monitors
828
	-- This assumes that there is a monitor for each turbine and reactor, plus the overall monitor display
829
	turbineMonitorOffset = #reactorList + 1 -- #turbineList will start at "1" if turbines found and move us just beyond #reactorList and status monitor range
830
831
	printLog("Found "..#reactorList.." reactor(s) in findReactors().")
832-
		printLog("Setting reactor["..reactorIndex.."] Rod Levels to "..newRodPercentage.."% in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
832+
	printLog("Set turbineMonitorOffset to "..turbineMonitorOffset.." in findReactors().")
833
end -- function findReactors()
834
835
836
-- Initialize all Big Reactors - Turbines
837
local function findTurbines()
838
	-- Empty out old list of turbines
839
	newTurbineList = {}
840-
		printLog("Increasing Rod Levels in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
840+
841
	printLog("Finding turbines...")
842
	newTurbineList, turbineNames = getDevices("BigReactors-Turbine")
843
844
	if #newTurbineList == 0 then
845
		printLog("No turbines found") -- Not an error
846
	else
847
		for turbineIndex = 1, #newTurbineList do
848-
		printLog("Setting reactor["..reactorIndex.."] Rod Levels to "..newRodPercentage.."% in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
848+
849
			turbine = newTurbineList[turbineIndex]
850
851
			if not turbine then
852
				printLog("turbineList["..turbineIndex.."] in findTurbines() is NOT a valid Big Reactors Turbine.")
853
854
				table.remove(newTurbineList, turbineIndex) -- Remove invalid turbine from list
855
				if turbineIndex ~= #newTurbineList then    -- If we're not at the end, clean up
856-
		printLog("No change to Rod Levels requested by "..progName.." GUI in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
856+
857
				end -- turbineIndex ~= #newTurbineList then
858-
end -- UI.handleReactorMonitorClick = function(self, reactorIndex, monitorIndex)
858+
859
				return -- Invalid turbineIndex
860-
-- Allow controlling Turbine Flow Rate from GUI
860+
861-
UI.handleTurbineMonitorClick = function(self, turbineIndex, monitorIndex)
861+
862
				_G[turbineNames[turbineIndex]] = {}
863
				_G[turbineNames[turbineIndex]]["TurbineOptions"] = {}
864
				_G[turbineNames[turbineIndex]]["TurbineOptions"]["LastSpeed"] = 0
865
				_G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"] = 2726
866
				_G[turbineNames[turbineIndex]]["TurbineOptions"]["autoStart"] = true
867
				_G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"] = 2000 --open up with all the steam wide open
868
				_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] = false
869
				_G[turbineNames[turbineIndex]]["TurbineOptions"]["turbineName"] = turbineNames[turbineIndex]
870
				printLog("turbineList["..turbineIndex.."] in findTurbines() is a valid Big Reactors Turbine.")
871
				if turbine.getConnected() then
872
					printLog("turbine["..turbineIndex.."] in findTurbines() is connected.")
873
				else
874
					printLog("turbine["..turbineIndex.."] in findTurbines() is NOT connected.")
875
					return -- Disconnected turbine
876
				end
877
			end
878-
		printLog("turbine["..turbineIndex.."] in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid Big Turbine.")
878+
879
			--failsafe
880
			local tempTable = _G[turbineNames[turbineIndex]]
881-
		printLog("turbine["..turbineIndex.."] in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is a valid Big Turbine.")
881+
882
			--check to make sure we get a valid config
883-
			printLog("turbine["..turbineIndex.."] in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is connected.")
883+
884
				tempTable = config.load(turbineNames[turbineIndex]..".options")
885-
			printLog("turbine["..turbineIndex.."] in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT connected.")
885+
886
				--if we don't have a valid config from disk, make a valid config
887
				config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
888
			end
889
			
890
			--load values from tempTable, checking for nil values along the way
891
			if tempTable["TurbineOptions"]["LastSpeed"] ~= nil then
892-
	local turbineStatus = _G[turbineNames[turbineIndex]]["TurbineOptions"]["Status"]
892+
893
			end
894
			
895-
	if (xClick >= (width - string.len(turbineStatus) - 1)) and (xClick <= (width-1)) and (sideClick == monitorNames[monitorIndex]) then
895+
896-
		if yClick == 1 then
896+
897-
			turbine.setActive(not turbine.getActive()) -- Toggle turbine status
897+
898-
			_G[turbineNames[turbineIndex]]["TurbineOptions"]["autoStart"] = turbine.getActive()
898+
899
			if tempTable["TurbineOptions"]["autoStart"] ~= nil then
900
				_G[turbineNames[turbineIndex]]["TurbineOptions"]["autoStart"] = tempTable["TurbineOptions"]["autoStart"]
901
			end
902-
		end -- if yClick == 1 then
902+
903-
	end -- if (xClick >= (width - string.len(turbineStatus) - 1)) and (xClick <= (width-1)) and (sideClick == monitorNames[monitorIndex]) then
903+
904
				_G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"] = tempTable["TurbineOptions"]["LastFlow"]
905-
	-- Allow disabling/enabling flow rate auto-adjust
905+
906-
	if (xClick > 23 and xClick < 28 and yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
906+
907-
		_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] = true
907+
908-
		sideClick, xClick, yClick = 0, 0, 0 -- Reset click after we register it
908+
909-
	elseif (xClick > 20 and xClick < 27 and yClick == 10) and (sideClick == monitorNames[monitorIndex]) then
909+
910
			
911-
		if ((_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"]) or (_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] == "true")) then
911+
912-
			_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] = false
912+
913
			end
914
			
915
			--save once more just to make sure we got it
916-
		sideClick, xClick, yClick = 0, 0, 0 -- Reset click after we register it
916+
917
		end -- for turbineIndex = 1, #newTurbineList do
918
919
		-- Overwrite old turbine list with the now updated list
920
		turbineList = newTurbineList
921-
		printLog("Decrease to Flow Rate requested by "..progName.." GUI in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
921+
922
923
	printLog("Found "..#turbineList.." turbine(s) in findTurbines().")
924
end -- function findTurbines()
925
926
927
-- Return current energy buffer in a specific reactor by %
928
local function getReactorStoredEnergyBufferPercent(reactor)
929
	printLog("Called as getReactorStoredEnergyBufferPercent(reactor).")
930
931
	if not reactor then
932
		printLog("getReactorStoredEnergyBufferPercent() did NOT receive a valid Big Reactor Reactor.")
933
		return -- Invalid reactorIndex
934
	else
935
		printLog("getReactorStoredEnergyBufferPercent() did receive a valid Big Reactor Reactor.")
936
	end
937
938
	local energyBufferStorage = reactor.getEnergyStored()
939
	return round(energyBufferStorage/100000, 1) -- (buffer/10000000 RF)*100%
940
end -- function getReactorStoredEnergyBufferPercent(reactor)
941
942-
		printLog("Increase to Flow Rate requested by "..progName.." GUI in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
942+
943
-- Return current energy buffer in a specific Turbine by %
944
local function getTurbineStoredEnergyBufferPercent(turbine)
945
	printLog("Called as getTurbineStoredEnergyBufferPercent(turbine)")
946
947
	if not turbine then
948
		printLog("getTurbineStoredEnergyBufferPercent() did NOT receive a valid Big Reactor Turbine.")
949
		return -- Invalid reactorIndex
950
	else
951
		printLog("getTurbineStoredEnergyBufferPercent() did receive a valid Big Reactor Turbine.")
952
	end
953
954
	local energyBufferStorage = turbine.getEnergyStored()
955
	return round(energyBufferStorage/10000, 1) -- (buffer/1000000 RF)*100%
956
end -- function getTurbineStoredEnergyBufferPercent(turbine)
957
958
local function reactorCruise(cruiseMaxTemp, cruiseMinTemp, reactorIndex)
959
	printLog("Called as reactorCruise(cruiseMaxTemp="..cruiseMaxTemp..",cruiseMinTemp="..cruiseMinTemp..",lastPolledTemp=".._G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"]..",reactorIndex="..reactorIndex..").")
960
	
961
	--sanitization
962
	local lastPolledTemp = tonumber(_G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"])
963
	cruiseMaxTemp = tonumber(cruiseMaxTemp)
964-
		printLog("No change to Flow Rate requested by "..progName.." GUI in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
964+
965
	
966
	if ((lastPolledTemp < cruiseMaxTemp) and (lastPolledTemp > cruiseMinTemp)) then
967
		local reactor = nil
968-
		printLog("Decrease to Turbine RPM requested by "..progName.." GUI in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
968+
969
		if not reactor then
970
			printLog("reactor["..reactorIndex.."] in reactorCruise(cruiseMaxTemp="..cruiseMaxTemp..",cruiseMinTemp="..cruiseMinTemp..",lastPolledTemp="..lastPolledTemp..",reactorIndex="..reactorIndex..") is NOT a valid Big Reactor.")
971
			return -- Invalid reactorIndex
972
		else
973
			printLog("reactor["..reactorIndex.."] in reactorCruise(cruiseMaxTemp="..cruiseMaxTemp..",cruiseMinTemp="..cruiseMinTemp..",lastPolledTemp="..lastPolledTemp..",reactorIndex="..reactorIndex..") is a valid Big Reactor.")
974
			if reactor.getConnected() then
975
				printLog("reactor["..reactorIndex.."] in reactorCruise(cruiseMaxTemp="..cruiseMaxTemp..",cruiseMinTemp="..cruiseMinTemp..",lastPolledTemp="..lastPolledTemp..",reactorIndex="..reactorIndex..") is connected.")
976
			else
977
				printLog("reactor["..reactorIndex.."] in reactorCruise(cruiseMaxTemp="..cruiseMaxTemp..",cruiseMinTemp="..cruiseMinTemp..",lastPolledTemp="..lastPolledTemp..",reactorIndex="..reactorIndex..") is NOT connected.")
978-
		printLog("Increase to Turbine RPM requested by "..progName.." GUI in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
978+
979
			end -- if reactor.getConnected() then
980
		end -- if not reactor then
981
982
		local rodPercentage = math.ceil(reactor.getControlRodLevel(0))
983
		local reactorTemp = math.ceil(reactor.getFuelTemperature())
984
		_G[reactorNames[reactorIndex]]["ReactorOptions"]["baseControlRodLevel"] = rodPercentage
985
		
986
		if ((reactorTemp < cruiseMaxTemp) and (reactorTemp > cruiseMinTemp)) then
987
			if (reactorTemp < lastPolledTemp) then
988-
		printLog("No change to Turbine RPM requested by "..progName.." GUI in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
988+
989
				--Boundary check
990-
end -- function handleTurbineMonitorClick(turbineIndex, monitorIndex)
990+
991
					reactor.setAllControlRodLevels(0)
992
				else
993
					reactor.setAllControlRodLevels(rodPercentage)
994
				end
995
			else
996
				rodPercentage = (rodPercentage + 1)
997
				--Boundary check
998
				if rodPercentage > 99 then
999
					reactor.setAllControlRodLevels(99)
1000
				else
1001
					reactor.setAllControlRodLevels(rodPercentage)
1002
				end
1003
			end -- if (reactorTemp > lastPolledTemp) then
1004
		else
1005-
		printLog("No monitors found, continuing headless")
1005+
1006
			_G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorCruising"] = false
1007
		end -- if ((reactorTemp < cruiseMaxTemp) and (reactorTemp > cruiseMinTemp)) then
1008
	else
1009
		--I don't know how we'd get here, but let's turn the cruise mode off
1010
		_G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorCruising"] = false
1011
	end -- if ((lastPolledTemp < cruiseMaxTemp) and (lastPolledTemp > cruiseMinTemp)) then
1012
	_G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"] = reactorTemp
1013
	_G[reactorNames[reactorIndex]]["ReactorOptions"]["activeCooled"] = true
1014
	_G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorMaxTemp"] = cruiseMaxTemp
1015
	_G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorMinTemp"] = cruiseMinTemp
1016
	config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]])
1017
end -- function reactorCruise(cruiseMaxTemp, cruiseMinTemp, lastPolledTemp, reactorIndex)
1018
1019
-- Modify reactor control rod levels to keep temperature with defined parameters, but
1020-
				monitor.setTextScale(1.0) -- Make sure scale is correct
1020+
1021
local function temperatureControl(reactorIndex)
1022
	printLog("Called as temperatureControl(reactorIndex="..reactorIndex..")")
1023
1024
	local reactor = nil
1025
	reactor = reactorList[reactorIndex]
1026
	if not reactor then
1027
		printLog("reactor["..reactorIndex.."] in temperatureControl(reactorIndex="..reactorIndex..") is NOT a valid Big Reactor.")
1028
		return -- Invalid reactorIndex
1029
	else
1030
		printLog("reactor["..reactorIndex.."] in temperatureControl(reactorIndex="..reactorIndex..") is a valid Big Reactor.")
1031
1032
		if reactor.getConnected() then
1033
			printLog("reactor["..reactorIndex.."] in temperatureControl(reactorIndex="..reactorIndex..") is connected.")
1034
		else
1035
			printLog("reactor["..reactorIndex.."] in temperatureControl(reactorIndex="..reactorIndex..") is NOT connected.")
1036
			return -- Disconnected reactor
1037
		end -- if reactor.getConnected() then
1038
	end
1039
1040-
					termRestore()
1040+
1041
	local rodPercentage = math.ceil(reactor.getControlRodLevel(0))
1042
	local reactorTemp = math.ceil(reactor.getFuelTemperature())
1043
	local localMinReactorTemp, localMaxReactorTemp = _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorMinTemp"], _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorMaxTemp"]
1044
1045
	--bypass if the reactor itself is set to not be auto-controlled
1046
	if ((not _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]) or (_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] == "false")) then
1047
		-- No point modifying control rod levels for temperature if the reactor is offline
1048
		if reactor.getActive() then
1049
			-- Actively cooled reactors should range between 0^C-300^C
1050
			-- Actually, active-cooled reactors should range between 300 and 420C (Mechaet)
1051
			-- Accordingly I changed the below lines
1052
			if reactor.isActivelyCooled() then
1053
				-- below was 0
1054
				localMinReactorTemp = 300
1055
				-- below was 300
1056
				localMaxReactorTemp = 420
1057
			else
1058
				localMinReactorTemp = _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorMinTemp"]
1059
				localMaxReactorTemp = _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorMaxTemp"]
1060
			end
1061
			local lastTempPoll = _G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"]
1062
			if _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorCruising"] then
1063
				--let's bypass all this math and hit the much-more-subtle cruise feature
1064
				--printLog("min: "..localMinReactorTemp..", max: "..localMaxReactorTemp..", lasttemp: "..lastTempPoll..", ri: "..reactorIndex.."  EOL")
1065
				reactorCruise(localMaxReactorTemp, localMinReactorTemp, reactorIndex)
1066
			else
1067
				-- Don't bring us to 100, that's effectively a shutdown
1068
				if (reactorTemp > localMaxReactorTemp) and (rodPercentage ~= 99) then
1069
					--increase the rods, but by how much?
1070
					if (reactorTemp > lastTempPoll) then
1071
						--we're climbing, we need to get this to decrease
1072
						if ((reactorTemp - lastTempPoll) > 100) then
1073
							--we're climbing really fast, arrest it
1074
							if (rodPercentage + (10 * controlRodAdjustAmount)) > 99 then
1075
								reactor.setAllControlRodLevels(99)
1076
							else
1077
								reactor.setAllControlRodLevels(rodPercentage + (10 * controlRodAdjustAmount))
1078
							end
1079
						else
1080
							--we're not climbing by leaps and bounds, let's give it a rod adjustment based on temperature increase
1081
							local diffAmount = reactorTemp - lastTempPoll
1082
							diffAmount = (round(diffAmount/10, 0))/5
1083
							controlRodAdjustAmount = diffAmount
1084
							if (rodPercentage + controlRodAdjustAmount) > 99 then
1085
								reactor.setAllControlRodLevels(99)
1086
							else
1087
								reactor.setAllControlRodLevels(rodPercentage + controlRodAdjustAmount)
1088
							end
1089
						end --if ((reactorTemp - lastTempPoll) > 100) then
1090
					elseif (reactorTemp == lastTempPoll) then
1091
						--temperature has stagnated, kick it very lightly
1092
						local controlRodAdjustment = 1
1093
						if (rodPercentage + controlRodAdjustment) > 99 then
1094
							reactor.setAllControlRodLevels(99)
1095-
				_G[reactorNames[reactorIndex]]["ReactorOptions"]["controlRodAdjustAmount"] = controlRodAdjustAmount
1095+
1096
							reactor.setAllControlRodLevels(rodPercentage + controlRodAdjustment)
1097
						end
1098
					end --if (reactorTemp > lastTempPoll) then
1099
						--worth noting that if we're above temp but decreasing, we do nothing. let it continue decreasing.
1100
1101
				elseif (reactorTemp < localMinReactorTemp) and (rodPercentage ~=0) then
1102
					--we're too cold. time to warm up, but by how much?
1103
					if (reactorTemp < lastTempPoll) then
1104
						--we're descending, let's stop that.
1105
						if ((lastTempPoll - reactorTemp) > 100) then
1106
							--we're headed for a new ice age, bring the heat
1107
							if (rodPercentage - (10 * controlRodAdjustAmount)) < 0 then
1108
								reactor.setAllControlRodLevels(0)
1109
							else
1110
								reactor.setAllControlRodLevels(rodPercentage - (10 * controlRodAdjustAmount))
1111
							end
1112
						else
1113
							--we're not descending quickly, let's bump it based on descent rate
1114
							local diffAmount = lastTempPoll - reactorTemp
1115
							diffAmount = (round(diffAmount/10, 0))/5
1116
							controlRodAdjustAmount = diffAmount
1117
							if (rodPercentage - controlRodAdjustAmount) < 0 then
1118
								reactor.setAllControlRodLevels(0)
1119
							else
1120
								reactor.setAllControlRodLevels(rodPercentage - controlRodAdjustAmount)
1121
							end
1122
						end --if ((lastTempPoll - reactorTemp) > 100) then
1123
					elseif (reactorTemp == lastTempPoll) then
1124
						--temperature has stagnated, kick it very lightly
1125
						local controlRodAdjustment = 1
1126
						if (rodPercentage - controlRodAdjustment) < 0 then
1127
							reactor.setAllControlRodLevels(0)
1128
						else
1129
							reactor.setAllControlRodLevels(rodPercentage - controlRodAdjustment)
1130
						end --if (rodPercentage - controlRodAdjustment) < 0 then
1131
1132
					end --if (reactorTemp < lastTempPoll) then
1133
					--if we're below temp but increasing, do nothing and let it continue to rise.
1134
				end --if (reactorTemp > localMaxReactorTemp) and (rodPercentage ~= 99) then
1135
1136
				if ((reactorTemp > localMinReactorTemp) and (reactorTemp < localMaxReactorTemp)) then
1137
					--engage cruise mode
1138
					_G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorCruising"] = true
1139
				end -- if ((reactorTemp > localMinReactorTemp) and (reactorTemp < localMaxReactorTemp)) then
1140
			end -- if reactorCruising then
1141
			--always set this number
1142
			_G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"] = reactorTemp
1143
			config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]])
1144
		end -- if reactor.getActive() then
1145
	else
1146
		printLog("Bypassed temperature control due to rodOverride being "..tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]).." EOL")
1147-
			if tempTable["ReactorOptions"]["controlRodAdjustAmount"] ~= nil then
1147+
1148-
				_G[reactorNames[reactorIndex]]["ReactorOptions"]["controlRodAdjustAmount"] = tempTable["ReactorOptions"]["controlRodAdjustAmount"]
1148+
1149
1150
-- Load saved reactor parameters if ReactorOptions file exists
1151
local function loadReactorOptions()
1152
	local reactorOptions = fs.open("ReactorOptions", "r") -- See http://computercraft.info/wiki/Fs.open
1153
1154
	if reactorOptions then
1155
		-- The following values were added by Lolmer
1156
		minStoredEnergyPercent = reactorOptions.readLine()
1157
		maxStoredEnergyPercent = reactorOptions.readLine()
1158
		--added by Mechaet
1159
		-- If we succeeded in reading a string, convert it to a number
1160
1161
		if minStoredEnergyPercent ~= nil then
1162
			minStoredEnergyPercent = tonumber(minStoredEnergyPercent)
1163
		end
1164
1165
		if maxStoredEnergyPercent ~= nil then
1166
			maxStoredEnergyPercent = tonumber(maxStoredEnergyPercent)
1167
		end
1168
1169
		reactorOptions.close()
1170
	end -- if reactorOptions then
1171
1172
	-- Set default values if we failed to read any of the above
1173
	if minStoredEnergyPercent == nil then
1174
		minStoredEnergyPercent = 15
1175
	end
1176
1177
	if maxStoredEnergyPercent == nil then
1178
		maxStoredEnergyPercent = 85
1179
	end
1180
1181
end -- function loadReactorOptions()
1182
1183
1184
-- Save our reactor parameters
1185
local function saveReactorOptions()
1186
	local reactorOptions = fs.open("ReactorOptions", "w") -- See http://computercraft.info/wiki/Fs.open
1187
1188-
			_G[reactorNames[reactorIndex]]["ReactorOptions"]["controlRodAdjustAmount"] = tonumber(_G[reactorNames[reactorIndex]]["ReactorOptions"]["controlRodAdjustAmount"])
1188+
1189
	if reactorOptions then
1190
		local reactorIndex = 1
1191
		-- The following values were added by Lolmer
1192
		reactorOptions.writeLine(minStoredEnergyPercent)
1193
		reactorOptions.writeLine(maxStoredEnergyPercent)
1194
		reactorOptions.close()
1195
	else
1196
		printLog("Failed to open file ReactorOptions for writing!")
1197
	end -- if reactorOptions then
1198
end -- function saveReactorOptions()
1199
1200
1201
local function displayReactorBars(barParams)
1202
	-- Default to first reactor and first monitor
1203
	setmetatable(barParams,{__index={reactorIndex=1, monitorIndex=1}})
1204
	local reactorIndex, monitorIndex =
1205
		barParams[1] or barParams.reactorIndex,
1206
		barParams[2] or barParams.monitorIndex
1207
1208
	printLog("Called as displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
1209
1210
	-- Grab current monitor
1211
	local monitor = nil
1212
	monitor = monitorList[monitorIndex]
1213
	if not monitor then
1214
		printLog("monitor["..monitorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT a valid monitor.")
1215
		return -- Invalid monitorIndex
1216
	end
1217
1218
	-- Grab current reactor
1219
	local reactor = nil
1220
	reactor = reactorList[reactorIndex]
1221
	if not reactor then
1222
		printLog("reactor["..reactorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT a valid Big Reactor.")
1223
		return -- Invalid reactorIndex
1224
	else
1225
		printLog("reactor["..reactorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is a valid Big Reactor.")
1226
		if reactor.getConnected() then
1227
			printLog("reactor["..reactorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is connected.")
1228
		else
1229
			printLog("reactor["..reactorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT connected.")
1230
			return -- Disconnected reactor
1231
		end -- if reactor.getConnected() then
1232
	end -- if not reactor then
1233
1234
	-- Draw border lines
1235
	local width, height = monitor.getSize()
1236
	printLog("Size of monitor is "..width.."w x"..height.."h in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..")")
1237
1238
	for i=3, 5 do
1239
		monitor.setCursorPos(22, i)
1240
		monitor.write("|")
1241
	end
1242
1243
	drawLine(2, monitorIndex)
1244
	drawLine(6, monitorIndex)
1245
1246
	-- Draw some text
1247
	local fuelString = "Fuel: "
1248
	local tempString = "Temp: "
1249
	local energyBufferString = ""
1250
1251
	if reactor.isActivelyCooled() then
1252
		energyBufferString = "Steam: "
1253
	else
1254
		energyBufferString = "Energy: "
1255
	end
1256
1257
	local padding = math.max(string.len(fuelString), string.len(tempString), string.len(energyBufferString))
1258
1259
	local fuelPercentage = round(reactor.getFuelAmount()/reactor.getFuelAmountMax()*100,1)
1260
	print{fuelString,2,3,monitorIndex}
1261
	print{fuelPercentage.." %",padding+2,3,monitorIndex}
1262
1263
	local reactorTemp = math.ceil(reactor.getFuelTemperature())
1264
	print{tempString,2,5,monitorIndex}
1265
	print{reactorTemp.." C",padding+2,5,monitorIndex}
1266
1267
	local rodPercentage = math.ceil(reactor.getControlRodLevel(0))
1268
	printLog("Current Rod Percentage for reactor["..reactorIndex.."] is "..rodPercentage.."% in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
1269
	-- Allow controlling Reactor Control Rod Level from GUI
1270
	-- Decrease rod button: 23X, 4Y
1271
	-- Increase rod button: 28X, 4Y
1272
	if (xClick == 23) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
1273
		printLog("Decreasing Rod Levels in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
1274
		--Decrease rod level by amount
1275
		newRodPercentage = rodPercentage - (5 * controlRodAdjustAmount)
1276
		if newRodPercentage < 0 then
1277
			newRodPercentage = 0
1278
		end
1279
		sideClick, xClick, yClick = 0, 0, 0
1280
1281
		printLog("Setting reactor["..reactorIndex.."] Rod Levels to "..newRodPercentage.."% in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
1282
		reactor.setAllControlRodLevels(newRodPercentage)
1283
		_G[reactorNames[reactorIndex]]["ReactorOptions"]["baseControlRodLevel"] = newRodPercentage
1284
1285
		-- Save updated rod percentage
1286
		config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]])
1287
		rodPercentage = newRodPercentage
1288
	elseif (xClick == 29) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
1289
		printLog("Increasing Rod Levels in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
1290
		--Increase rod level by amount
1291
		newRodPercentage = rodPercentage + (5 * controlRodAdjustAmount)
1292
		if newRodPercentage > 100 then
1293
			newRodPercentage = 100
1294
		end
1295
		sideClick, xClick, yClick = 0, 0, 0
1296
1297
		printLog("Setting reactor["..reactorIndex.."] Rod Levels to "..newRodPercentage.."% in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
1298-
-- Assign status, reactors, turbines and debug output to the monitors that shall display them
1298+
1299-
-- Depends on the [monitor,reactor,turbine]Lists being populated already
1299+
1300-
local function assignMonitors()
1300+
1301
		-- Save updated rod percentage
1302-
	local monitors = {}
1302+
1303-
	monitorAssignments = {}
1303+
1304
	else
1305-
	printLog("Assigning monitors...")
1305+
		printLog("No change to Rod Levels requested by "..progName.." GUI in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
1306
	end -- if (xClick == 29) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
1307-
	local m = config.load(monitorOptionFileName) 
1307+
1308-
	if (m ~= nil) then
1308+
1309-
		-- first, merge the detected and the configured monitor lists
1309+
1310-
		-- this is to ensure we pick up new additions to the network
1310+
1311-
		for monitorIndex, monitorName in ipairs(monitorNames) do
1311+
1312-
			monitors[monitorName] = m.Monitors[monitorName] or ""
1312+
1313
	-- getEnergyProducedLastTick() is used for both RF/t (passively cooled) and mB/t (actively cooled)
1314-
		-- then, go through all of it again to build our runtime data structure
1314+
1315-
		for monitorName, assignedName in pairs(monitors) do
1315+
1316-
			printLog("Looking for monitor and device named "..monitorName.." and "..assignedName)
1316+
1317-
			for monitorIndex = 1, #monitorNames do
1317+
1318-
				printLog("if "..monitorName.." == "..monitorNames[monitorIndex].." then", DEBUG)
1318+
1319-
				
1319+
1320-
				if monitorName == monitorNames[monitorIndex] then
1320+
1321-
					printLog("Found "..monitorName.." at index "..monitorIndex, DEBUG)
1321+
1322-
					if assignedName == "Status" then
1322+
1323-
						monitorAssignments[monitorName] = {type="Status", index=monitorIndex}
1323+
1324-
					elseif assignedName == "Debug" then
1324+
1325-
						monitorAssignments[monitorName] = {type="Debug", index=monitorIndex}
1325+
1326
		printLog("reactor["..reactorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT an actively cooled reactor.")
1327-
						local maxListLen = math.max(#reactorNames, #turbineNames)
1327+
1328-
						for i = 1, maxListLen do
1328+
1329-
							if assignedName == reactorNames[i] then
1329+
1330-
								monitorAssignments[monitorName] = {type="Reactor", index=monitorIndex, reactorName=reactorNames[i], reactorIndex=i}
1330+
1331-
								break
1331+
1332-
							elseif assignedName == turbineNames[i] then
1332+
1333-
								monitorAssignments[monitorName] = {type="Turbine", index=monitorIndex, turbineName=turbineNames[i], turbineIndex=i}
1333+
1334-
								break
1334+
1335-
							elseif i == maxListLen then
1335+
1336-
								printLog("assignMonitors(): Monitor "..monitorName.." was configured to display nonexistant device "..assignedName..". Setting inactive.", WARN)
1336+
1337-
								monitorAssignments[monitorName] = {type="Inactive", index=monitorIndex}
1337+
1338
		print{"Energy Buffer",2,7,monitorIndex}
1339
		print{curStoredEnergyPercent, width-(string.len(curStoredEnergyPercent)+2),7,monitorIndex}
1340
		print{"%",28,7,monitorIndex}
1341-
					break
1341+
1342-
				elseif monitorIndex == #monitorNames then
1342+
1343-
					printLog("assignMonitors(): Monitor "..monitorName.." not found. It was configured to display device "..assignedName..". Discarding.", WARN)
1343+
1344
		printLog("reactor["..reactorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is an actively cooled reactor.")
1345
		print{math.ceil(energyBuffer).." mB/t",padding+2,4,monitorIndex}
1346
	end -- if not reactor.isActivelyCooled() then
1347
1348-
		printLog("No valid monitor configuration found, generating...")
1348+
1349
	local reactorRodOverrideStatus = ""
1350-
		-- create assignments that reflect the setup before 0.3.17
1350+
1351
	print{"Rod Auto-adjust:",2,9,monitorIndex}
1352-
		monitorAssignments[monitorNames[1]] = {type="Status", index=1}
1352+
1353-
		monitorIndex = monitorIndex + 1
1353+
1354
		printLog("Reactor Rod Override status is: "..tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]).." EOL")
1355-
			if monitorIndex > #monitorList then
1355+
1356-
				break
1356+
1357
	else
1358-
			monitorAssignments[monitorNames[monitorIndex]] = {type="Reactor", index=monitorIndex, reactorName=reactorNames[reactorIndex], reactorIndex=reactorIndex}
1358+
1359-
			printLog(monitorNames[monitorIndex].." -> "..reactorNames[reactorIndex])
1359+
1360
		monitor.setTextColor(colors.red)
1361-
			monitorIndex = monitorIndex + 1
1361+
1362
	printLog("reactorRodOverrideStatus is \""..reactorRodOverrideStatus.."\" in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
1363
1364-
			if monitorIndex > #monitorList then
1364+
1365-
				break
1365+
1366
1367-
			monitorAssignments[monitorNames[monitorIndex]] = {type="Turbine", index=monitorIndex, turbineName=turbineNames[turbineIndex], turbineIndex=turbineIndex}
1367+
1368-
			printLog(monitorNames[monitorIndex].." -> "..turbineNames[turbineIndex])
1368+
1369
	print{"Waste: "..reactor.getWasteAmount().." mB", width-(string.len(reactor.getWasteAmount())+10), 11, monitorIndex}
1370-
			monitorIndex = monitorIndex + 1
1370+
1371
	monitor.setTextColor(colors.blue)
1372-
		if monitorIndex <= #monitorList then
1372+
1373-
			monitorAssignments[monitorNames[#monitorList]] = {type="Debug", index=#monitorList}
1373+
1374
end -- function displayReactorBars(barParams)
1375
1376
1377-
	tprint(monitorAssignments)
1377+
1378
	-- Default to first reactor and first monitor
1379-
	saveMonitorAssignments()
1379+
1380
	local reactorIndex, monitorIndex =
1381-
end -- function assignMonitors()
1381+
1382
		statusParams[2] or statusParams.monitorIndex
1383-
local eventHandler
1383+
1384-
-- Replacement for sleep, which passes on events instead of dropping themo
1384+
1385-
-- Straight from http://computercraft.info/wiki/Os.sleep
1385+
1386-
local function wait(time)
1386+
1387-
	local timer = os.startTimer(time)
1387+
1388
	if not monitor then
1389-
	while true do
1389+
1390-
		local event = {os.pullEvent()}
1390+
1391
	end
1392-
		if (event[1] == "timer" and event[2] == timer) then
1392+
1393-
			break
1393+
1394
	local reactor = nil
1395-
			eventHandler(event[1], event[2], event[3], event[4])
1395+
1396
	if not reactor then
1397
		printLog("reactor["..reactorIndex.."] in reactorStatus(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT a valid Big Reactor.")
1398
		return -- Invalid reactorIndex
1399
	else
1400
		printLog("reactor["..reactorIndex.."] in reactorStatus(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is a valid Big Reactor.")
1401
	end
1402
1403
	local width, height = monitor.getSize()
1404
	local reactorStatus = ""
1405
1406
	if reactor.getConnected() then
1407
		printLog("reactor["..reactorIndex.."] in reactorStatus(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is connected.")
1408
1409
		if reactor.getActive() then
1410
			reactorStatus = "ONLINE"
1411
1412
			-- Set "ONLINE" to blue if the actively cooled reactor is both in cruise mode and online
1413
			if _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorCruising"] and reactor.isActivelyCooled() then
1414
				monitor.setTextColor(colors.blue)
1415
			else
1416
				monitor.setTextColor(colors.green)
1417
			end -- if reactorCruising and reactor.isActivelyCooled() then
1418
		else
1419
			reactorStatus = "OFFLINE"
1420
			monitor.setTextColor(colors.red)
1421
		end -- if reactor.getActive() then
1422
1423
		if xClick >= (width - string.len(reactorStatus) - 1) and xClick <= (width-1) and (sideClick == monitorNames[monitorIndex]) then
1424
			if yClick == 1 then
1425
				reactor.setActive(not reactor.getActive()) -- Toggle reactor status
1426
				_G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] = reactor.getActive()
1427
				config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]])
1428
				sideClick, xClick, yClick = 0, 0, 0 -- Reset click after we register it
1429
1430
				-- If someone offlines the reactor (offline after a status click was detected), then disable autoStart
1431
				if not reactor.getActive() then
1432
					_G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] = false
1433
				end
1434
			end -- if yClick == 1 then
1435
		end -- if (xClick >= (width - string.len(reactorStatus) - 1) and xClick <= (width-1)) and (sideClick == monitorNames[monitorIndex]) then
1436
1437
		-- Allow disabling rod level auto-adjust and only manual rod level control
1438
		if ((xClick > 23 and xClick < 28 and yClick == 4)
1439
				or (xClick > 20 and xClick < 27 and yClick == 9))
1440
				and (sideClick == monitorNames[monitorIndex]) then
1441
			_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] = not _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]
1442
			config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]])
1443
			sideClick, xClick, yClick = 0, 0, 0 -- Reset click after we register it
1444
		end -- if (xClick > 23) and (xClick < 28) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
1445
1446
	else
1447
		printLog("reactor["..reactorIndex.."] in reactorStatus(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT connected.")
1448
		reactorStatus = "DISCONNECTED"
1449
		monitor.setTextColor(colors.red)
1450
	end -- if reactor.getConnected() then
1451
1452
	print{reactorStatus, width - string.len(reactorStatus) - 1, 1, monitorIndex}
1453
	monitor.setTextColor(colors.white)
1454
end -- function reactorStatus(statusParams)
1455
1456
1457
-- Display all found reactors' status to monitor 1
1458
-- This is only called if multiple reactors and/or a reactor plus at least one turbine are found
1459
local function displayAllStatus()
1460
	local reactor, turbine = nil, nil
1461
	local onlineReactor, onlineTurbine = 0, 0
1462
	local totalReactorRF, totalReactorSteam, totalTurbineRF = 0, 0, 0
1463
	local totalReactorFuelConsumed = 0
1464
	local totalCoolantStored, totalSteamStored, totalEnergy, totalMaxEnergyStored = 0, 0, 0, 0 -- Total turbine and reactor energy buffer and overall capacity
1465
	local maxSteamStored = (2000*#turbineList)+(5000*#reactorList)
1466
	local maxCoolantStored = (2000*#turbineList)+(5000*#reactorList)
1467
1468
	local monitor, monitorIndex = nil, 1
1469
	monitor = monitorList[monitorIndex]
1470
	if not monitor then
1471
		printLog("monitor["..monitorIndex.."] in displayAllStatus() is NOT a valid monitor.")
1472
		return -- Invalid monitorIndex
1473
	end
1474
1475
	for reactorIndex = 1, #reactorList do
1476
		reactor = reactorList[reactorIndex]
1477
		if not reactor then
1478
			printLog("reactor["..reactorIndex.."] in displayAllStatus() is NOT a valid Big Reactor.")
1479
			break -- Invalid reactorIndex
1480
		else
1481
			printLog("reactor["..reactorIndex.."] in displayAllStatus() is a valid Big Reactor.")
1482
		end -- if not reactor then
1483
1484
		if reactor.getConnected() then
1485
			printLog("reactor["..reactorIndex.."] in displayAllStatus() is connected.")
1486
			if reactor.getActive() then
1487
				onlineReactor = onlineReactor + 1
1488
				totalReactorFuelConsumed = totalReactorFuelConsumed + reactor.getFuelConsumedLastTick()
1489
			end -- reactor.getActive() then
1490
1491
			-- Actively cooled reactors do not produce or store energy
1492
			if not reactor.isActivelyCooled() then
1493
				totalMaxEnergyStored = totalMaxEnergyStored + 10000000 -- Reactors store 10M RF
1494
				totalEnergy = totalEnergy + reactor.getEnergyStored()
1495
				totalReactorRF = totalReactorRF + reactor.getEnergyProducedLastTick()
1496
			else
1497
				totalReactorSteam = totalReactorSteam + reactor.getEnergyProducedLastTick()
1498
				totalSteamStored = totalSteamStored + reactor.getHotFluidAmount()
1499
				totalCoolantStored = totalCoolantStored + reactor.getCoolantAmount()
1500
			end -- if not reactor.isActivelyCooled() then
1501
		else
1502
			printLog("reactor["..reactorIndex.."] in displayAllStatus() is NOT connected.")
1503
		end -- if reactor.getConnected() then
1504
	end -- for reactorIndex = 1, #reactorList do
1505
1506
	for turbineIndex = 1, #turbineList do
1507
		turbine = turbineList[turbineIndex]
1508
		if not turbine then
1509
			printLog("turbine["..turbineIndex.."] in displayAllStatus() is NOT a valid Turbine.")
1510
			break -- Invalid turbineIndex
1511
		else
1512
			printLog("turbine["..turbineIndex.."] in displayAllStatus() is a valid Turbine.")
1513
		end -- if not turbine then
1514
1515
		if turbine.getConnected() then
1516
			printLog("turbine["..turbineIndex.."] in displayAllStatus() is connected.")
1517
			if turbine.getActive() then
1518
				onlineTurbine = onlineTurbine + 1
1519
			end
1520
1521
			totalMaxEnergyStored = totalMaxEnergyStored + 1000000 -- Turbines store 1M RF
1522
			totalEnergy = totalEnergy + turbine.getEnergyStored()
1523
			totalTurbineRF = totalTurbineRF + turbine.getEnergyProducedLastTick()
1524
			totalSteamStored = totalSteamStored + turbine.getInputAmount()
1525
			totalCoolantStored = totalCoolantStored + turbine.getOutputAmount()
1526-
			if reactor.isActivelyCooled() and not knowlinglyOverride then
1526+
1527
			printLog("turbine["..turbineIndex.."] in displayAllStatus() is NOT connected.")
1528
		end -- if turbine.getConnected() then
1529
	end -- for turbineIndex = 1, #turbineList do
1530
1531
	print{"Reactors online/found: "..onlineReactor.."/"..#reactorList, 2, 3, monitorIndex}
1532
	print{"Turbines online/found: "..onlineTurbine.."/"..#turbineList, 2, 4, monitorIndex}
1533
1534
	if totalReactorRF ~= 0 then
1535
		monitor.setTextColor(colors.blue)
1536
		printRight("Reactor", 9, monitorIndex)
1537
		monitor.setTextColor(colors.white)
1538
		printRight(math.ceil(totalReactorRF).." (RF/t)", 10, monitorIndex)
1539
	end
1540
1541-
				local localControlRodAdjustAmount = _G[reactorNames[reactorIndex]]["ReactorOptions"]["controlRodAdjustAmount"]
1541+
1542
		-- Display liquids
1543
		monitor.setTextColor(colors.blue)
1544
		printLeft("Steam (mB)", 6, monitorIndex)
1545
		monitor.setTextColor(colors.white)
1546
		printLeft(math.ceil(totalSteamStored).."/"..maxSteamStored, 7, monitorIndex)
1547
		printLeft(math.ceil(totalReactorSteam).." mB/t", 8, monitorIndex)
1548
		monitor.setTextColor(colors.blue)
1549-
							if (rodPercentage + (10 * localControlRodAdjustAmount)) > 99 then
1549+
1550
		monitor.setTextColor(colors.white)
1551
		printRight(math.ceil(totalCoolantStored).."/"..maxCoolantStored, 7, monitorIndex)
1552-
								reactor.setAllControlRodLevels(rodPercentage + (10 * localControlRodAdjustAmount))
1552+
1553
		monitor.setTextColor(colors.blue)
1554
		printLeft("Turbine", 9, monitorIndex)
1555
		monitor.setTextColor(colors.white)
1556
		printLeft(math.ceil(totalTurbineRF).." RF/t", 10, monitorIndex)
1557
	end -- if #turbineList then
1558-
							_G[reactorNames[reactorIndex]]["ReactorOptions"]["controlRodAdjustAmount"] = diffAmount
1558+
1559-
							if (rodPercentage + diffAmount) > 99 then
1559+
1560
	print{"Buffer: "..math.ceil(totalEnergy,3).."/"..totalMaxEnergyStored.." RF", 2, 12, monitorIndex}
1561
end -- function displayAllStatus()
1562-
								reactor.setAllControlRodLevels(rodPercentage + diffAmount)
1562+
1563
1564
-- Get turbine status
1565-
					elseif ((lastTempPoll - reactorTemp) < (reactorTemp * 0.005)) then
1565+
1566
	printLog("Called as displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
1567
1568
	-- Grab current monitor
1569
	local monitor = nil
1570
	monitor = monitorList[monitorIndex]
1571
	if not monitor then
1572
		printLog("monitor["..monitorIndex.."] in displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid monitor.")
1573
		return -- Invalid monitorIndex
1574
	end
1575
1576-
				elseif ((reactorTemp < localMinReactorTemp) and (rodPercentage ~=0)) or (steamRequested - steamDelivered > 0) then
1576+
1577
	local turbine = nil
1578-
					if (steamRequested > (steamDelivered*2)) then
1578+
1579-
						-- Bridge to machine room: Full steam ahead!
1579+
1580-
						reactor.setAllControlRodLevels(0)
1580+
1581-
					elseif (reactorTemp < lastTempPoll) then
1581+
1582
	else
1583
		printLog("turbine["..turbineIndex.."] in displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is a valid Big Turbine.")
1584
		if turbine.getConnected() then
1585-
							if (rodPercentage - (10 * localControlRodAdjustAmount)) < 0 then
1585+
1586
		else
1587
			printLog("turbine["..turbineIndex.."] in displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT connected.")
1588-
								reactor.setAllControlRodLevels(rodPercentage - (10 * localControlRodAdjustAmount))
1588+
1589
		end -- if turbine.getConnected() then
1590
	end -- if not turbine then
1591
1592
	--local variable to match the view on the monitor
1593
	local turbineBaseSpeed = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"])
1594-
							_G[reactorNames[reactorIndex]]["ReactorOptions"]["controlRodAdjustAmount"] = diffAmount
1594+
1595-
							if (rodPercentage - diffAmount) < 0 then
1595+
1596
	local width, height = monitor.getSize()
1597
1598-
								reactor.setAllControlRodLevels(rodPercentage - diffAmount)
1598+
1599
		monitor.setCursorPos(21, i)
1600
		monitor.write("|")
1601
	end
1602
1603
	drawLine(2,monitorIndex)
1604
	drawLine(7,monitorIndex)
1605
1606
	-- Allow controlling Turbine Flow Rate from GUI
1607
	-- Decrease flow rate button: 22X, 4Y
1608
	-- Increase flow rate button: 28X, 4Y
1609
	local turbineFlowRate = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"])
1610
	if (xClick == 22) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
1611
		printLog("Decrease to Flow Rate requested by "..progName.." GUI in displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
1612
		--Decrease rod level by amount
1613
		newTurbineFlowRate = turbineFlowRate - flowRateAdjustAmount
1614-
				if ((reactorTemp > localMinReactorTemp) and (reactorTemp < localMaxReactorTemp)) and not (steamRequested - steamDelivered > 0) then
1614+
1615
			newTurbineFlowRate = 0
1616
		end
1617
		sideClick, xClick, yClick = 0, 0, 0
1618
1619
		-- Check bounds [0,2000]
1620
		if newTurbineFlowRate > 2000 then
1621
			newTurbineFlowRate = 2000
1622
		elseif newTurbineFlowRate < 0 then
1623
			newTurbineFlowRate = 25 -- Don't go to zero, might as well power off
1624
		end
1625
1626
		turbine.setFluidFlowRateMax(newTurbineFlowRate)
1627
		_G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"] = newTurbineFlowRate
1628
		-- Save updated Turbine Flow Rate
1629
		turbineFlowRate = newTurbineFlowRate
1630
		config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
1631
	elseif (xClick == 29) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
1632
		printLog("Increase to Flow Rate requested by "..progName.." GUI in displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
1633
		--Increase rod level by amount
1634
		newTurbineFlowRate = turbineFlowRate + flowRateAdjustAmount
1635
		if newTurbineFlowRate > 2000 then
1636
			newTurbineFlowRate = 2000
1637
		end
1638
		sideClick, xClick, yClick = 0, 0, 0
1639
1640
		-- Check bounds [0,2000]
1641
		if newTurbineFlowRate > 2000 then
1642
			newTurbineFlowRate = 2000
1643
		elseif newTurbineFlowRate < 0 then
1644
			newTurbineFlowRate = 25 -- Don't go to zero, might as well power off
1645
		end
1646
1647
		turbine.setFluidFlowRateMax(newTurbineFlowRate)
1648
		
1649
		-- Save updated Turbine Flow Rate
1650
		turbineFlowRate = math.ceil(newTurbineFlowRate)
1651
		_G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"] = turbineFlowRate
1652
		config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
1653
	else
1654
		printLog("No change to Flow Rate requested by "..progName.." GUI in displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
1655
	end -- if (xClick == 29) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
1656
1657
	if (xClick == 22) and (yClick == 6) and (sideClick == monitorNames[monitorIndex]) then
1658
		printLog("Decrease to Turbine RPM requested by "..progName.." GUI in displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
1659
		rpmRateAdjustment = 909
1660
		newTurbineBaseSpeed = turbineBaseSpeed - rpmRateAdjustment
1661
		if newTurbineBaseSpeed < 908 then
1662
			newTurbineBaseSpeed = 908
1663
		end
1664
		sideClick, xClick, yClick = 0, 0, 0
1665
		_G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"] = newTurbineBaseSpeed
1666
		config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
1667
	elseif (xClick == 29) and (yClick == 6) and (sideClick == monitorNames[monitorIndex]) then
1668
		printLog("Increase to Turbine RPM requested by "..progName.." GUI in displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
1669
		rpmRateAdjustment = 909
1670
		newTurbineBaseSpeed = turbineBaseSpeed + rpmRateAdjustment
1671
		if newTurbineBaseSpeed > 2726 then
1672
			newTurbineBaseSpeed = 2726
1673
		end
1674
		sideClick, xClick, yClick = 0, 0, 0
1675
		_G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"] = newTurbineBaseSpeed
1676
		config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
1677
	else
1678
		printLog("No change to Turbine RPM requested by "..progName.." GUI in displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
1679
	end -- if (xClick == 29) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
1680
	print{"  mB/t",22,3,monitorIndex}
1681
	print{"<      >",22,4,monitorIndex}
1682
	print{stringTrim(turbineFlowRate),24,4,monitorIndex}
1683
	print{"  RPM",22,5,monitorIndex}
1684
	print{"<      >",22,6,monitorIndex}
1685
	print{stringTrim(tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"])),24,6,monitorIndex}
1686
	local rotorSpeedString = "Speed: "
1687
	local energyBufferString = "Energy: "
1688
	local padding = math.max(string.len(rotorSpeedString), string.len(energyBufferString))
1689
1690
	local energyBuffer = turbine.getEnergyProducedLastTick()
1691
	print{energyBufferString,1,4,monitorIndex}
1692
	print{math.ceil(energyBuffer).." RF/t",padding+1,4,monitorIndex}
1693
1694
	local rotorSpeed = math.ceil(turbine.getRotorSpeed())
1695
	print{rotorSpeedString,1,5,monitorIndex}
1696
	print{rotorSpeed.." RPM",padding+1,5,monitorIndex}
1697
1698
	-- PaintUtils only outputs to term., not monitor.
1699
	-- See http://www.computercraft.info/forums2/index.php?/topic/15540-paintutils-on-a-monitor/
1700
1701
	-- Draw stored energy buffer bar
1702
	drawBar(1,9,28,9,colors.gray,monitorIndex)
1703
1704
	local curStoredEnergyPercent = getTurbineStoredEnergyBufferPercent(turbine)
1705
	if curStoredEnergyPercent > 4 then
1706
		drawBar(1, 9, math.floor(26*curStoredEnergyPercent/100)+2, 9, colors.yellow,monitorIndex)
1707
	elseif curStoredEnergyPercent > 0 then
1708
		drawPixel(1, 9, colors.yellow, monitorIndex)
1709
	end -- if curStoredEnergyPercent > 4 then
1710
1711
	print{"Energy Buffer",1,8,monitorIndex}
1712
	print{curStoredEnergyPercent, width-(string.len(curStoredEnergyPercent)+2),8,monitorIndex}
1713
	print{"%",28,8,monitorIndex}
1714
1715
	-- Print rod override status
1716
	local turbineFlowRateOverrideStatus = ""
1717
1718
	print{"Flow Auto-adjust:",2,10,monitorIndex}
1719
1720
	if ((not _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"]) or (_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] == "false")) then
1721
		turbineFlowRateOverrideStatus = "Enabled"
1722-
	monitor.setCursorPos(1, height)
1722+
1723-
	monitor.write("< ")
1723+
1724-
	monitor.setCursorPos(width-1, height)
1724+
1725-
	monitor.write(" >")
1725+
1726
	end -- if not reactorRodOverride then
1727
1728
	print{turbineFlowRateOverrideStatus, width - string.len(turbineFlowRateOverrideStatus) - 1, 10, monitorIndex}
1729
	monitor.setTextColor(colors.white)
1730
1731
	monitor.setTextColor(colors.blue)
1732
	printCentered(_G[turbineNames[turbineIndex]]["TurbineOptions"]["turbineName"],12,monitorIndex)
1733
	monitor.setTextColor(colors.white)
1734
1735
	-- Need equation to figure out rotor efficiency and display
1736
end -- function displayTurbineBars(statusParams)
1737
1738
1739
-- Display turbine status
1740
local function turbineStatus(turbineIndex, monitorIndex)
1741
	-- Grab current monitor
1742
	local monitor = nil
1743
1744
	printLog("Called as turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
1745
1746
	monitor = monitorList[monitorIndex]
1747
	if not monitor then
1748
		printLog("monitor["..monitorIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid monitor.")
1749
		return -- Invalid monitorIndex
1750
	end
1751
1752
	-- Grab current turbine
1753
	local turbine = nil
1754
	turbine = turbineList[turbineIndex]
1755
	if not turbine then
1756
		printLog("turbine["..turbineIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid Big Turbine.")
1757
		return -- Invalid turbineIndex
1758
	else
1759
		printLog("turbine["..turbineIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is a valid Big Turbine.")
1760
	end
1761
1762
	local width, height = monitor.getSize()
1763
	local turbineStatus = ""
1764
1765
	if turbine.getConnected() then
1766
		printLog("turbine["..turbineIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is connected.")
1767
		if turbine.getActive() then
1768
			turbineStatus = "ONLINE"
1769
			monitor.setTextColor(colors.green)
1770
		else
1771
			turbineStatus = "OFFLINE"
1772
			monitor.setTextColor(colors.red)
1773
		end -- if turbine.getActive() then
1774
1775
		if (xClick >= (width - string.len(turbineStatus) - 1)) and (xClick <= (width-1)) and (sideClick == monitorNames[monitorIndex]) then
1776
			if yClick == 1 then
1777
				turbine.setActive(not turbine.getActive()) -- Toggle turbine status
1778
				_G[turbineNames[turbineIndex]]["TurbineOptions"]["autoStart"] = turbine.getActive()
1779
				config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
1780
				sideClick, xClick, yClick = 0, 0, 0 -- Reset click after we register it
1781
			end -- if yClick == 1 then
1782
		end -- if (xClick >= (width - string.len(turbineStatus) - 1)) and (xClick <= (width-1)) and (sideClick == monitorNames[monitorIndex]) then
1783
1784
		-- Allow disabling/enabling flow rate auto-adjust
1785
		if (xClick > 23 and xClick < 28 and yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
1786
			_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] = true
1787
			sideClick, xClick, yClick = 0, 0, 0 -- Reset click after we register it
1788
		elseif (xClick > 20 and xClick < 27 and yClick == 10) and (sideClick == monitorNames[monitorIndex]) then
1789
			
1790
			if ((_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"]) or (_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] == "true")) then
1791
				_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] = false
1792
			else
1793
				_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] = true
1794
			end
1795
			sideClick, xClick, yClick = 0, 0, 0 -- Reset click after we register it
1796
		end
1797
		config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
1798
1799
	else
1800
		printLog("turbine["..turbineIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT connected.")
1801
		turbineStatus = "DISCONNECTED"
1802
		monitor.setTextColor(colors.red)
1803
	end -- if turbine.getConnected() then
1804
1805
	print{turbineStatus, width - string.len(turbineStatus) - 1, 1, monitorIndex}
1806
	monitor.setTextColor(colors.white)
1807
end -- function function turbineStatus(turbineIndex, monitorIndex)
1808
1809
1810
-- Maintain Turbine flow rate at 900 or 1,800 RPM
1811
local function flowRateControl(turbineIndex)
1812
	if ((not _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"]) or (_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] == "false")) then
1813
		
1814
		printLog("Called as flowRateControl(turbineIndex="..turbineIndex..").")
1815
1816
		-- Grab current turbine
1817-
	-- monitor switch controls
1817+
1818-
	monitor.setCursorPos(1, height)
1818+
1819-
	monitor.write("<")
1819+
1820-
	monitor.setCursorPos(width, height)
1820+
1821-
	monitor.write(">")
1821+
1822
		local turbineBaseSpeed = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"])
1823
1824
		if not turbine then
1825
			printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is NOT a valid Big Turbine.")
1826
			return -- Invalid turbineIndex
1827
		else
1828
			printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is a valid Big Turbine.")
1829
1830
			if turbine.getConnected() then
1831
				printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is connected.")
1832
			else
1833
				printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is NOT connected.")
1834
			end -- if turbine.getConnected() then
1835
		end -- if not turbine then
1836
1837
		-- No point modifying control rod levels for temperature if the turbine is offline
1838
		if turbine.getActive() then
1839
			printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is active.")
1840
1841
			local flowRate = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"])
1842
			local flowRateUserMax = math.ceil(turbine.getFluidFlowRateMax())
1843
			local rotorSpeed = math.ceil(turbine.getRotorSpeed())
1844
			local newFlowRate = 0
1845
1846
			-- Going to control the turbine based on target RPM since changing the target flow rate bypasses this function
1847
			if (rotorSpeed < turbineBaseSpeed) then
1848
				printLog("BELOW COMMANDED SPEED")
1849
				if (rotorSpeed > lastTurbineSpeed) then
1850
					--we're still increasing, let's let it level off
1851
					--also lets the first control pass go by on startup
1852
				elseif (rotorSpeed < lastTurbineSpeed) then
1853
					--we're decreasing where we should be increasing, do something
1854
					if ((lastTurbineSpeed - rotorSpeed) > 100) then
1855
						--kick it harder
1856
						newFlowRate = 2000
1857
						printLog("HARD KICK")
1858
					else
1859
						--let's adjust based on proximity
1860
						flowAdjustment = (turbineBaseSpeed - rotorSpeed)/5
1861
						newFlowRate = flowRate + flowAdjustment
1862
						printLog("Light Kick: new flow rate is "..newFlowRate.." mB/t and flowAdjustment was "..flowAdjustment.." EOL")
1863
					end
1864
				else
1865
					--we've stagnated, kick it.
1866
					flowAdjustment = (turbineBaseSpeed - lastTurbineSpeed)
1867
					newFlowRate = flowRate + flowAdjustment
1868
					printLog("Stagnated: new flow rate is "..newFlowRate.." mB/t and flowAdjustment was "..flowAdjustment.." EOL")
1869
				end --if (rotorSpeed > lastTurbineSpeed) then
1870
			else
1871
				--we're above commanded turbine speed
1872
				printLog("ABOVE COMMANDED SPEED")
1873
				if (rotorSpeed < lastTurbineSpeed) then
1874
				--we're decreasing, let it level off
1875
				--also bypasses first control pass on startup
1876
				elseif (rotorSpeed > lastTurbineSpeed) then
1877-
	_G[reactorNames[reactorIndex]]["ReactorOptions"]["Status"] = reactorStatus
1877+
1878
					if ((rotorSpeed - lastTurbineSpeed) > 100) then
1879
						--halt
1880
						newFlowRate = 25
1881
					else
1882
						--let's adjust based on proximity
1883
						flowAdjustment = (rotorSpeed - turbineBaseSpeed)/5
1884-
-- Display all found reactors' status to selected monitor
1884+
1885
						printLog("Light Kick: new flow rate is "..newFlowRate.." mB/t and flowAdjustment was "..flowAdjustment.." EOL")
1886-
local function displayAllStatus(monitorIndex)
1886+
1887
				else
1888
					--we've stagnated, kick it.
1889
					flowAdjustment = (lastTurbineSpeed - turbineBaseSpeed)
1890
					newFlowRate = flowRate - flowAdjustment
1891
					printLog("Stagnated: new flow rate is "..newFlowRate.." mB/t and flowAdjustment was "..flowAdjustment.." EOL")
1892
				end --if (rotorSpeed < lastTurbineSpeed) then
1893
			end --if (rotorSpeed < turbineBaseSpeed)
1894
1895-
	local monitor = monitorList[monitorIndex]
1895+
1896
			if (newFlowRate == 0) then
1897
				--do nothing, we didn't ask for anything this pass
1898
			else
1899
				--boundary check
1900
				if newFlowRate > 2000 then
1901
					newFlowRate = 2000
1902
				elseif newFlowRate < 25 then
1903
					newFlowRate = 25 -- Don't go to zero, might as well power off
1904
				end -- if newFlowRate > 2000 then
1905
				--no sense running an adjustment if it's not necessary
1906
				if ((newFlowRate < flowRate) or (newFlowRate > flowRate)) then
1907
					printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is being commanded to "..newFlowRate.." mB/t flow")
1908
					newFlowRate = round(newFlowRate, 0)
1909
					turbine.setFluidFlowRateMax(newFlowRate)
1910
					_G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"] = newFlowRate
1911
					config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
1912
				end
1913
			end
1914
			--always set this
1915
			_G[turbineNames[turbineIndex]]["TurbineOptions"]["LastSpeed"] = rotorSpeed
1916
			config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
1917
		else
1918
			printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is NOT active.")
1919
		end -- if turbine.getActive() then
1920
	else
1921
		printLog("turbine["..turbineIndex.."] has flow override set to "..tostring(_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"])..", bypassing flow control.")
1922
	end -- if not _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] then
1923
end -- function flowRateControl(turbineIndex)
1924
1925
1926
function main()
1927
	-- Load reactor parameters and initialize systems
1928
	loadReactorOptions()
1929
1930
	-- Get our initial list of connected monitors and reactors
1931
	-- and initialize every cycle in case the connected devices change
1932
	findMonitors()
1933
	findReactors()
1934
	findTurbines()
1935
1936
	while not finished do
1937
		local reactor = nil
1938
		local monitorIndex = 1
1939
1940
		-- For multiple reactors/monitors, monitor #1 is reserved for overall status
1941
		-- or for multiple reactors/turbines and only one monitor
1942
		if ( ( ((#reactorList + #turbineList) > 1) and (#monitorList >= 1) )   or
1943
		     ( ((#reactorList + #turbineList) >=1) and (#monitorList >  1) ) ) then
1944
			local monitor = nil
1945
			monitor = monitorList[monitorIndex]
1946
			if not monitor then
1947
				printLog("monitor["..monitorIndex.."] in main() is NOT a valid monitor.")
1948
				return -- Invalid monitorIndex
1949
			end
1950
1951
			clearMonitor(progName.." "..progVer, monitorIndex) -- Clear monitor and draw borders
1952
			printCentered(progName.." "..progVer, 1, monitorIndex)
1953
			displayAllStatus()
1954
			monitorIndex = 2 -- Next monitor, #1 is reserved for overall status
1955
		end
1956
1957
		-- Iterate through reactors, continue to run even if not enough monitors are connected
1958
		for reactorIndex = 1, #reactorList do
1959
			local monitor = nil
1960
			local reactorMonitorIndex = monitorIndex + reactorIndex - 1 -- reactorIndex starts at 1
1961
1962
			printLog("Attempting to display reactor["..reactorIndex.."] on monitor["..reactorMonitorIndex.."]...")
1963
1964
			reactor = reactorList[reactorIndex]
1965
			if not reactor then
1966
				printLog("reactor["..reactorIndex.."] in main() is NOT a valid Big Reactor.")
1967
				break -- Invalid reactorIndex
1968
			else
1969
				printLog("reactor["..reactorIndex.."] in main() is a valid Big Reactor.")
1970
			end --  if not reactor then
1971
1972
			-- Only attempt to assign a monitor if we have a monitor for this reactor
1973
			if (reactorMonitorIndex <= #monitorList) then
1974
				printLog("Displaying reactor["..reactorIndex.."] on monitor["..reactorMonitorIndex.."].")
1975
				monitor = monitorList[reactorMonitorIndex]
1976
1977
				if not monitor then
1978
					printLog("monitor["..reactorMonitorIndex.."] in main() is NOT a valid monitor.")
1979
				else
1980
					clearMonitor(progName, reactorMonitorIndex) -- Clear monitor and draw borders
1981
					printCentered(progName, 1, reactorMonitorIndex)
1982
1983
					-- Display reactor status, includes "Disconnected" but found reactors
1984
					reactorStatus{reactorIndex, reactorMonitorIndex}
1985
1986-
	printCentered("Buffer: "..formatReadableSIUnit(math.ceil(totalEnergy)).."/"..formatReadableSIUnit(totalMaxEnergyStored).." RF", 12, monitorIndex)
1986+
					-- Draw the borders and bars for the current reactor on the current monitor
1987
					displayReactorBars{reactorIndex, reactorMonitorIndex}
1988-
	-- monitor switch controls
1988+
				end -- if not monitor
1989
			else
1990-
	monitor.setCursorPos(1, height)
1990+
				printLog("You may want "..(#reactorList + #turbineList + 1).." monitors for your "..#reactorList.." connected reactors and "..#turbineList.." connected turbines.")
1991-
	monitor.write("<")
1991+
			end -- if (#monitorList ~= 1) and (reactorMonitorIndex < #monitorList) then
1992-
	monitor.setCursorPos(width, height)
1992+
1993-
	monitor.write(">")
1993+
1994
				printLog("reactor["..reactorIndex.."] is connected.")
1995
				local curStoredEnergyPercent = getReactorStoredEnergyBufferPercent(reactor)
1996
1997
				-- Shutdown reactor if current stored energy % is >= desired level, otherwise activate
1998
				-- First pass will have curStoredEnergyPercent=0 until displayBars() is run once
1999
				if curStoredEnergyPercent >= maxStoredEnergyPercent then
2000
					reactor.setActive(false)
2001
				-- Do not auto-start the reactor if it was manually powered off (autoStart=false)
2002
				elseif (curStoredEnergyPercent <= minStoredEnergyPercent) and (_G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] == true) then
2003
					reactor.setActive(true)
2004
				end -- if curStoredEnergyPercent >= maxStoredEnergyPercent then
2005
2006
				-- Don't try to auto-adjust control rods if manual control is requested
2007
				if not _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] then
2008
					temperatureControl(reactorIndex)
2009
				end -- if not reactorRodOverride then
2010
			else
2011
				printLog("reactor["..reactorIndex.."] is NOT connected.")
2012
			end -- if reactor.getConnected() then
2013
		end -- for reactorIndex = 1, #reactorList do
2014
2015
		-- Monitors for turbines start after turbineMonitorOffset
2016
		for turbineIndex = 1, #turbineList do
2017
			local monitor = nil
2018
			local turbineMonitorIndex = turbineIndex + turbineMonitorOffset
2019
2020
			printLog("Attempting to display turbine["..turbineIndex.."] on monitor["..turbineMonitorIndex.."]...")
2021
2022
			-- Only attempt to assign a monitor if we found a monitor for this turbine
2023
			if (turbineMonitorIndex <= #monitorList) then
2024
				printLog("Displaying turbine["..turbineIndex.."] on monitor["..turbineMonitorIndex.."].")
2025
				monitor = monitorList[turbineMonitorIndex]
2026
				if not monitor then
2027
					printLog("monitor["..turbineMonitorIndex.."] in main() is NOT a valid monitor.")
2028
				else
2029
					clearMonitor(progName, turbineMonitorIndex) -- Clear monitor and draw borders
2030
					printCentered(progName, 1, turbineMonitorIndex)
2031
2032
					-- Display turbine status, includes "Disconnected" but found turbines
2033
					turbineStatus(turbineIndex, turbineMonitorIndex)
2034
2035
					-- Draw the borders and bars for the current turbine on the current monitor
2036
					displayTurbineBars(turbineIndex, turbineMonitorIndex)
2037
				end -- if not monitor
2038-
	monitor.setCursorPos(1, height)
2038+
2039-
	monitor.write("< ")
2039+
				printLog("You may want "..(#reactorList + #turbineList + 1).." monitors for your "..#reactorList.." connected reactors and "..#turbineList.." connected turbines.")
2040-
	monitor.setCursorPos(width-1, height)
2040+
			end -- if (#monitorList ~= 1) and (turbineMonitorIndex < #monitorList) then
2041-
	monitor.write(" >")
2041+
2042
			turbine = turbineList[turbineIndex]
2043
			if not turbine then
2044
				printLog("turbine["..turbineIndex.."] in main() is NOT a valid Big Turbine.")
2045
				break -- Invalid turbineIndex
2046
			else
2047
				printLog("turbine["..turbineIndex.."] in main() is a valid Big Turbine.")
2048
			end -- if not turbine then
2049
2050
			if turbine.getConnected() then
2051
				printLog("turbine["..turbineIndex.."] is connected.")
2052-
	local steamBufferString = "Steam: "
2052+
2053-
	local padding = math.max(string.len(rotorSpeedString), string.len(energyBufferString), string.len(steamBufferString))
2053+
				if not _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] then
2054
					flowRateControl(turbineIndex)
2055
				end -- if not turbineFlowRateOverride[turbineIndex] then
2056
			else
2057
				printLog("turbine["..turbineIndex.."] is NOT connected.")
2058
			end -- if turbine.getConnected() then
2059
		end -- for reactorIndex = 1, #reactorList do
2060
2061
		sleep(loopTime) -- Sleep
2062
		saveReactorOptions()
2063-
	local steamBuffer = turbine.getFluidFlowRate()
2063+
2064-
	print{steamBufferString,1,6,monitorIndex}
2064+
2065-
	print{steamBuffer.." mB/t",padding+1,6,monitorIndex}
2065+
2066
2067
local function eventHandler()
2068
	while not finished do
2069
		-- http://computercraft.info/wiki/Os.pullEvent
2070
		-- http://www.computercraft.info/forums2/index.php?/topic/1516-ospullevent-what-is-it-and-how-is-it-useful/
2071
		event, arg1, arg2, arg3 = os.pullEvent()
2072
2073
		if event == "monitor_touch" then
2074
			sideClick, xClick, yClick = arg1, math.floor(arg2), math.floor(arg3)
2075
			printLog("Side: "..arg1.." Monitor touch X: "..xClick.." Y: "..yClick)
2076
		elseif event == "char" and not inManualMode then
2077
			local ch = string.lower(arg1)
2078
			if ch == "q" then
2079
				finished = true
2080
			elseif ch == "r" then
2081
				finished = true
2082
				os.reboot()
2083
			end -- if ch == "q" then
2084
		end -- if event == "monitor_touch" then
2085
	end -- while not finished do
2086
end -- function eventHandler()
2087
2088
2089
while not finished do
2090
	parallel.waitForAny(eventHandler, main)
2091
	sleep(loopTime)
2092
end -- while not finished do
2093
2094
2095
-- Clear up after an exit
2096
term.clear()
2097
term.setCursorPos(1,1)