From db18efe46132882a27f91385e55c764dc652d66d Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Thu, 2 Jan 2025 15:29:21 -0500 Subject: [PATCH 01/38] initial steps towards v1 --- .gitignore | 1 + classes.lua | 133 ++ draw.lua | 74 + graph.lua | 101 + monitor.lua | 75 + reactorController.lua | 2071 +++++++++++---------- startup | 5 + updater.lua => update_reactor.lua | 192 +- touchpoint.lua => usr/apis/touchpoint.lua | 373 ++-- versions.lua | 5 + 10 files changed, 1802 insertions(+), 1228 deletions(-) create mode 100644 .gitignore create mode 100644 classes.lua create mode 100644 draw.lua create mode 100644 graph.lua create mode 100644 monitor.lua create mode 100644 startup rename updater.lua => update_reactor.lua (96%) rename touchpoint.lua => usr/apis/touchpoint.lua (95%) create mode 100644 versions.lua diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9a97cfb --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +reactorConfigSerialized.txt \ No newline at end of file diff --git a/classes.lua b/classes.lua new file mode 100644 index 0000000..3927903 --- /dev/null +++ b/classes.lua @@ -0,0 +1,133 @@ +---@class Peripheral +local Peripheral = { + ---@type string + id = nil, + ---@type string + type = nil, + ---@type table + wrap = nil, +} +---comment +---@param id string +---@param type string +---@return Peripheral +local function newPeripheral(id, type) + + local peripheralInstance = { + id = id, + type = type, + wrap = peripheral.wrap(id), + } + setmetatable(peripheralInstance, {__index = Peripheral}) + return peripheralInstance +end + +_G.Peripheral = { + new = newPeripheral +} + +---@class ReactorStatistics +local ReactorStatistics = { + +} +---comment +---@param id string +---@param type string +---@return ReactorStatistics +local function newReactorStatistics(id, type) + + local reactorStatisticsInstance = { + } + setmetatable(reactorStatisticsInstance, {__index = ReactorStatistics}) + return reactorStatisticsInstance +end + +_G.ReactorStatistics = { + new = newReactorStatistics +} + +-- local function newVector2(x, y) + +-- local Vector2Instance = { +-- x = x, +-- y = y, +-- } +-- setmetatable(Vector2Instance, {__index = Vector2, __add = Vector2.__add, __sub = Vector2.__sub}) +-- return Vector2Instance +-- end + +-- _G.Vector2 = { +-- new = newVector2, +-- zero = newVector2(0, 0), +-- one = newVector2(1, 1), + +-- __add = function (a, b) +-- local t = type(b) +-- if t == "table" then +-- return Vector2.new(a.x + b.x, a.y + b.y) +-- elseif t == "number" then +-- return Vector2.new(a.x + b, a.y + b) +-- end +-- end, + +-- __sub = function (a, b) +-- local t = type(b) +-- if t == "table" then +-- return Vector2.new(a.x - b.x, a.y - b.y) +-- elseif t == "number" then +-- return Vector2.new(a.x - b, a.y - b) +-- end +-- end, +-- } + +---@class Vector2 +local Vector2 = { + ---@type number + x = nil, + ---@type number + y = nil, +} + +Vector2.mt = {} + +Vector2.mt.__index = Vector2 + +---comment +---@param x number +---@param y number +---@return Vector2 +function Vector2.new(x, y) + local instance = {x = x, y = y} + return setmetatable(instance, Vector2.mt) +end + +Vector2.mt.__add = function(self, other) + local t = type(other) + if t == "table" then + return Vector2.new(self.x + other.x, self.y + other.y) + elseif t == "number" then + return Vector2.new(self.x + other, self.y + other) + end +end + +Vector2.mt.__sub = function(self, other) + local t = type(other) + if t == "table" then + return Vector2.new(self.x - other.x, self.y - other.y) + elseif t == "number" then + return Vector2.new(self.x - other, self.y - other) + end +end + +_G.Vector2 = { + new = Vector2.new, + zero = Vector2.new(0, 0), + one = Vector2.new(1, 1), +} + +local test = _G.Vector2.one + _G.Vector2.one +print(test.x, test.y) +test = test + 1 +print(test.x, test.y) +test = test - 2 +print(test.x, test.y) \ No newline at end of file diff --git a/draw.lua b/draw.lua new file mode 100644 index 0000000..72b1f71 --- /dev/null +++ b/draw.lua @@ -0,0 +1,74 @@ + +---comment +---@param mon table +---@param drawFunction function +local function executeAndRestoreMonitorSettings(mon, drawFunction) + if (mon == nil) then + error("Error! Mon is nil") + return + end + + local originalCursorPos = Vector2.new(mon.getCursorPos()) + local originalBackgroundColor = mon.getBackgroundColor() + local originalTextColor = mon.getTextColor() + + drawFunction() + + mon.setCursorPos(originalCursorPos.x, originalCursorPos.y) + mon.setBackgroundColor(originalBackgroundColor) + mon.setTextColor(originalTextColor) +end + +---comment Draws a filled rectangle +---@param mon table +---@param color any +---@param offset Vector2 +---@param size Vector2 +local function drawFilledRectangle(mon, color, offset, size) + executeAndRestoreMonitorSettings( + mon, + function () + local horizLine = string.rep(" ", size.x) + mon.setBackgroundColor(color) + for i=0, size.y - 1 do + mon.setCursorPos(offset.x, offset.y + i) + mon.write(horizLine) + end + end + ) +end + +---comment Draws a rectangle with a fill color and border +---@param mon table +---@param innerColor any +---@param outerColor any +---@param offset Vector2 +---@param size Vector2 +local function drawRectangle(mon, innerColor, outerColor, offset, size) + drawFilledRectangle(mon, outerColor, offset, size) + drawFilledRectangle(mon, innerColor, offset + 1, size - 2) +end + +---comment Draws text on the screen +---@param mon table +---@param text string +---@param pos Vector2 +---@param backgroundColor any +---@param textColor any +local function drawText(mon, text, pos, backgroundColor, textColor) + executeAndRestoreMonitorSettings( + mon, + function () + mon.setCursorPos(pos.x, pos.y) + mon.setBackgroundColor(backgroundColor) + mon.setTextColor(textColor) + mon.write(text) + end + ) +end + +_G.DrawUtil = { + drawRectangle = drawRectangle, + drawFilledRectangle = drawFilledRectangle, + drawText = drawText, +} \ No newline at end of file diff --git a/graph.lua b/graph.lua new file mode 100644 index 0000000..567738d --- /dev/null +++ b/graph.lua @@ -0,0 +1,101 @@ +---@class Graph +local Graph = { + ---@type string + id = nil, + ---@type string + name = nil, + ---@type Vector2 + offset = nil, + ---@type Vector2 + size = nil, + ---@type function + drawCallback = nil, + ---@param self Graph + ---@param mon table + ---@param statistics ReactorStatistics + draw = function(self, mon, statistics) + self.drawCallback(mon, self.offset, self.size, statistics) + end, +} + +---comment +---@param id string +---@param name string +---@param offset Vector2 +---@param size Vector2 +---@param drawCallback function +---@return Graph +local function newGraph(id, name, offset, size, drawCallback) + + local graphInstance = { + id = id, + name = name, + offset = offset, + size = size, + drawCallback = drawCallback, + } + setmetatable(graphInstance, {__index = Graph}) + return graphInstance +end + +_G.Graph = { + new = newGraph +} + +---@param mon table +---@param offset Vector2 +---@param size Vector2 +---@param statistics ReactorStatistics +local function drawControlGraph(mon, offset, size, averageRod) + local controlRodLength0To1 = averageRod / 100 + local controlRodMaxLengthOnScreen = size.y - 2 + local controlRodLengthOnScreen = math.ceil(controlRodLength0To1 * (controlRodMaxLengthOnScreen)) + + + DrawUtil.drawText( + mon, + "Control Level", + offset + Vector2.new(1, 0), + colors.black, + colors.orange + + ) + DrawUtil.drawRectangle( + mon, + colors.yellow, + colors.gray, + offset + Vector2.new(0, 1), + size + ) + DrawUtil.drawFilledRectangle( + mon, + colors.white, + offset + Vector2.new(3, 2), + Vector2.new(9, controlRodLengthOnScreen) + ) + + local controlRodPercentTextPosition, color + if controlRodLengthOnScreen > 0 then + color = colors.white + controlRodPercentTextPosition = offset + Vector2.new(4, 1 + controlRodLengthOnScreen) + else + color = colors.yellow + controlRodPercentTextPosition = offset + Vector2.new(4, 2) + end + + DrawUtil.drawText( + mon, + string.format("%6.2f%%", averageRod), + controlRodPercentTextPosition, + color, + colors.black + ) +end +local sizey = 1 +local controlGraph = _G.Graph.new( + "Control Level", + "Control Level", + Vector2.new(27, 4), + Vector2.new(15, sizey - 7), + drawControlGraph +) \ No newline at end of file diff --git a/monitor.lua b/monitor.lua new file mode 100644 index 0000000..ab91f66 --- /dev/null +++ b/monitor.lua @@ -0,0 +1,75 @@ +---@class Monitor +local Monitor = { + ---@type string + name = nil, + ---@type table + mon = nil, + ---@type table + touch = nil, + ---@type Vector2 + size = nil, + ---@type integer + oo = nil, + ---@type integer + dim = nil, + + ---comment + ---@param self Monitor + clear = function(self) + self.mon.setBackgroundColor(colors.black) + self.mon.clear() + self.mon.setTextScale(0.5) + self.mon.setCursorPos(1,1) + end, + + ---@param self Monitor + drawScene = function(self) + if (invalidDim) then + self.mon.write("Invalid Monitor Dimensions") + return + end + + if (displayingGraphMenu) then + drawGraphButtons() + end + drawControls() + drawStatus() + drawStatistics() + self.touch:draw() + end, + + ---comment + ---@param self Monitor + ---@param reactorStats ReactorStatistics + update = function(self, reactorStats) + + end +} + +---comment +---@param peripheralId string +---@return Monitor +local function new(peripheralId) + local mon = peripheral.wrap(peripheralId) + local touch = touchpoint.new(peripheralId) + local sizex, sizey = mon.getSize() + local oo = sizey - 37 + local dim = sizex - 33 + + local monitorInstance = { + name = peripheralId, + mon = mon, + touch = touch, + sizex = sizex, + sizey = sizey, + oo = oo, + dim = dim, + } + + setmetatable(monitorInstance, {__index = Monitor}) + return monitorInstance +end + +_G.Monitor = { + new = new +} \ No newline at end of file diff --git a/reactorController.lua b/reactorController.lua index 503ae74..db9617b 100644 --- a/reactorController.lua +++ b/reactorController.lua @@ -1,946 +1,1125 @@ -local version = "0.51" -local tag = "reactorConfig" ---[[ -Program made by DrunkenKas - See github: https://github.com/Kasra-G/ReactorController/#readme - -The MIT License (MIT) - -Copyright (c) 2021 Kasra Ghaffari - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -]] - -dofile("/usr/apis/touchpoint.lua") - -local reactorVersion, reactor -local mon, monSide -local sizex, sizey, dim, oo, offy -local btnOn, btnOff, invalidDim -local minb, maxb -local rod, rfLost -local storedLastTick, storedThisTick, lastRFT = 0,0,0 -local fuelTemp, caseTemp, fuelUsage, waste, capacity = 0,0,0,0,1 -local t -local displayingGraphMenu = false - -local secondsToAverage = 2 - -local averageStoredThisTick = 0 -local averageLastRFT = 0 -local averageRod = 0 -local averageFuelUsage = 0 -local averageWaste = 0 -local averageFuelTemp = 0 -local averageCaseTemp = 0 -local averageRfLost = 0 - --- table of which graphs to draw -local graphsToDraw = {} - --- table of all the graphs -local graphs = -{ - "Energy Buffer", - "Control Level", - "Temperatures", -} - --- marks the offsets for each graph position --- { XOffset, } -local XOffs = -{ - { 4, true}, - {27, true}, - {50, true}, - {73, true}, - {96, true}, -} - --- Draw a box with no fill -local function drawBox(size, xoff, yoff, color) - if (monSide == nil) then - return - end - local x,y = mon.getCursorPos() - mon.setBackgroundColor(color) - local horizLine = string.rep(" ", size[1]) - mon.setCursorPos(xoff + 1, yoff + 1) - mon.write(horizLine) - mon.setCursorPos(xoff + 1, yoff + size[2]) - mon.write(horizLine) - - -- Draw vertical lines - for i=0, size[2] - 1 do - mon.setCursorPos(xoff + 1, yoff + i + 1) - mon.write(" ") - mon.setCursorPos(xoff + size[1], yoff + i +1) - mon.write(" ") - end - mon.setCursorPos(x,y) - mon.setBackgroundColor(colors.black) -end - ---Draw a filled box -local function drawFilledBox(size, xoff, yoff, colorOut, colorIn) - if (monSide == nil) then - return - end - local horizLine = string.rep(" ", size[1] - 2) - drawBox(size, xoff, yoff, colorOut) - local x,y = mon.getCursorPos() - mon.setBackgroundColor(colorIn) - for i=2, size[2] - 1 do - mon.setCursorPos(xoff + 2, yoff + i) - mon.write(horizLine) - end - mon.setBackgroundColor(colors.black) - mon.setCursorPos(x,y) -end - ---Draws text on the screen -local function drawText(text, x1, y1, backColor, textColor) - if (monSide == nil) then - return - end - local x, y = mon.getCursorPos() - mon.setCursorPos(x1, y1) - mon.setBackgroundColor(backColor) - mon.setTextColor(textColor) - mon.write(text) - mon.setTextColor(colors.white) - mon.setBackgroundColor(colors.black) - mon.setCursorPos(x,y) -end - ---Helper method for adding buttons -local function addButt(name, callBack, size, xoff, yoff, color1, color2) - t:add(name, callBack, - xoff + 1, yoff + 1, - size[1] + xoff, size[2] + yoff, - color1, color2) -end - -local function minAdd10() - minb = math.min(maxb - 10, minb + 10) -end -local function minSub10() - minb = math.max(0, minb - 10) -end -local function maxAdd10() - maxb = math.min(100, maxb + 10) -end -local function maxSub10() - maxb = math.max(minb + 10, maxb - 10) -end - -local function turnOff() - if (btnOn) then - t:toggleButton("Off") - t:toggleButton("On") - btnOff = true - btnOn = false - reactor.setActive(false) - end -end - -local function turnOn() - if (btnOff) then - t:toggleButton("Off") - t:toggleButton("On") - btnOff = false - btnOn = true - reactor.setActive(true) - end -end - ---adds buttons -local function addButtons() - if (sizey == 24) then - oo = 1 - end - addButt("On", turnOn, {8, 3}, dim + 7, 3 + oo, - colors.red, colors.lime) - addButt("Off", turnOff, {8, 3}, dim + 19, 3 + oo, - colors.red, colors.lime) - if (btnOn) then - t:toggleButton("On", true) - else - t:toggleButton("Off", true) - end - if (sizey > 24) then - addButt("+ 10", minAdd10, {8, 3}, dim + 7, 14 + oo, - colors.purple, colors.pink) - addButt(" + 10 ", maxAdd10, {8, 3}, dim + 19, 14 + oo, - colors.magenta, colors.pink) - addButt("- 10", minSub10, {8, 3}, dim + 7, 18 + oo, - colors.purple, colors.pink) - addButt(" - 10 ", maxSub10, {8, 3}, dim + 19, 18 + oo, - colors.magenta, colors.pink) - end -end - ---Resets the monitor -local function resetMon() - if (monSide == nil) then - return - end - mon.setBackgroundColor(colors.black) - mon.clear() - mon.setTextScale(0.5) - mon.setCursorPos(1,1) -end - -local function getPercPower() - return averageStoredThisTick / capacity * 100 -end - -local function rnd(num, dig) - return math.floor(10 ^ dig * num) / (10 ^ dig) -end - -local function getEfficiency() - return averageLastRFT / averageFuelUsage -end - -local function format(num) - if (num >= 1000000000) then - return string.format("%7.3f G", num / 1000000000) - elseif (num >= 1000000) then - return string.format("%7.3f M", num / 1000000) - elseif (num >= 1000) then - return string.format("%7.3f K", num / 1000) - elseif (num >= 1) then - return string.format("%7.3f ", num) - elseif (num >= .001) then - return string.format("%7.3f m", num * 1000) - elseif (num >= .000001) then - return string.format("%7.3f u", num * 1000000) - else - return string.format("%7.3f ", 0) - end -end - - -local function getAvailableXOff() - for i,v in pairs(XOffs) do - if (v[2] and v[1] < dim) then - v[2] = false - return v[1] - end - end - return -1 -end - -local function getXOff(num) - for i,v in pairs(XOffs) do - if (v[1] == num) then - return v - end - end - return nil -end - -local function enableGraph(name) - if (graphsToDraw[name] ~= nil) then - return - end - local e = getAvailableXOff() - if (e ~= -1) then - graphsToDraw[name] = e - if (displayingGraphMenu) then - t:toggleButton(name) - end - end -end - -local function disableGraph(name) - if (graphsToDraw[name] == nil) then - return - end - if (displayingGraphMenu) then - t:toggleButton(name) - end - getXOff(graphsToDraw[name])[2] = true - graphsToDraw[name] = nil -end - -local function toggleGraph(name) - if (graphsToDraw[name] == nil) then - enableGraph(name) - else - disableGraph(name) - end -end - -local function addGraphButtons() - offy = oo - 14 - for i,v in pairs(graphs) do - addButt(v, function() toggleGraph(v) end, {20, 3}, - dim + 7, offy + i * 3 - 1, - colors.red, colors.lime) - if (graphsToDraw[v] ~= nil) then - t:toggleButton(v, true) - end - end -end - -local function drawGraphButtons() - drawBox({sizex - dim - 3, oo - offy - 1}, - dim + 2, offy, colors.orange) - drawText(" Graph Controls ", - dim + 7, offy + 1, - colors.black, colors.orange) -end - -local function drawEnergyBuffer(xoff) - local srf = sizey - 9 - local off = xoff - local right = off + 19 < dim - local poff = right and off + 15 or off - 6 - - drawBox({15, srf + 2}, off - 1, 4, colors.gray) - local pwr = math.floor(getPercPower() / 100 - * (srf)) - drawFilledBox({13, srf}, off, 5, - colors.red, colors.red) - local rndpw = rnd(getPercPower(), 2) - local color = (rndpw < maxb and rndpw > minb) and colors.green - or (rndpw >= maxb and colors.orange or colors.blue) - if (pwr > 0) then - drawFilledBox({13, pwr + 1}, off, srf + 4 - pwr, - color, color) - end - --drawPoint(off + 14, srf + 5 - pwr, pwr > 0 and color or colors.red) - drawText(string.format(right and "%.2f%%" or "%5.2f%%", rndpw), poff, srf + 5 - pwr, - colors.black, color) - drawText("Energy Buffer", off + 1, 4, - colors.black, colors.orange) - drawText(format(averageStoredThisTick).."RF", off + 1, srf + 5 - pwr, - pwr > 0 and color or colors.red, colors.black) -end - -local function drawControlLevel(xoff) - local srf = sizey - 9 - local off = xoff - drawBox({15, srf + 2}, off - 1, 4, colors.gray) - drawFilledBox({13, srf}, off, 5, - colors.yellow, colors.yellow) - local rodTr = math.floor(averageRod / 100 - * (srf)) - drawText("Control Level", off + 1, 4, - colors.black, colors.orange) - if (rodTr > 0) then - drawFilledBox({9, rodTr}, off + 2, 5, - colors.white, colors.white) - end - drawText(string.format("%6.2f%%", averageRod), off + 4, rodTr > 0 and rodTr + 5 or 6, - rodTr > 0 and colors.white or colors.yellow, colors.black) - -end - -local function drawTemperatures(xoff) - local srf = sizey - 9 - local off = xoff - drawBox({15, srf + 2}, off, 4, colors.gray) - --drawFilledBox({12, srf}, off, 5, - -- colors.red, colors.red) - - local tempUnit = (reactorVersion == "Bigger Reactors") and "K" or "C" - local tempFormat = "%4s"..tempUnit - - local fuelRnd = math.floor(averageFuelTemp) - local caseRnd = math.floor(averageCaseTemp) - local fuelTr = math.floor(fuelRnd / 2000 - * (srf)) - local caseTr = math.floor(caseRnd / 2000 - * (srf)) - drawText(" Case ", off + 2, 5, - colors.gray, colors.lightBlue) - drawText(" Fuel ", off + 9, 5, - colors.gray, colors.magenta) - if (fuelTr > 0) then - fuelTr = math.min(fuelTr, srf) - drawFilledBox({6, fuelTr}, off + 8, srf + 5 - fuelTr, - colors.magenta, colors.magenta) - - drawText(string.format(tempFormat, fuelRnd..""), - off + 10, srf + 6 - fuelTr, - colors.magenta, colors.black) - else - drawText(string.format(tempFormat, fuelRnd..""), - off + 10, srf + 5, - colors.black, colors.magenta) - end - - if (caseTr > 0) then - caseTr = math.min(caseTr, srf) - drawFilledBox({6, caseTr}, off + 1, srf + 5 - caseTr, - colors.lightBlue, colors.lightBlue) - drawText(string.format(tempFormat, caseRnd..""), - off + 3, srf + 6 - caseTr, - colors.lightBlue, colors.black) - else - drawText(string.format(tempFormat, caseRnd..""), - off + 3, srf + 5, - colors.black, colors.lightBlue) - end - - drawText("Temperatures", off + 2, 4, - colors.black, colors.orange) - drawBox({1, srf}, off + 7, 5, - colors.gray) -end - -local function drawGraph(name, offset) - if (name == "Energy Buffer") then - drawEnergyBuffer(offset) - elseif (name == "Control Level") then - drawControlLevel(offset) - elseif (name == "Temperatures") then - drawTemperatures(offset) - end -end - -local function drawGraphs() - for i,v in pairs(graphsToDraw) do - if (v + 15 < dim) then - drawGraph(i,v) - end - end -end - -local function drawStatus() - if (dim <= -1) then - return - end - drawBox({dim, sizey - 2}, - 1, 1, colors.lightBlue) - drawText(" Reactor Graphs ", dim - 18, 2, - colors.black, colors.lightBlue) - drawGraphs() -end - -local function drawControls() - if (sizey == 24) then - drawBox({sizex - dim - 3, 9}, dim + 2, oo, - colors.cyan) - drawText(" Reactor Controls ", dim + 7, oo + 1, - colors.black, colors.cyan) - drawText("Reactor "..(btnOn and "Online" or "Offline"), - dim + 10, 3 + oo, - colors.black, btnOn and colors.green or colors.red) - return - end - - drawBox({sizex - dim - 3, 23}, dim + 2, oo, - colors.cyan) - drawText(" Reactor Controls ", dim + 7, oo + 1, - colors.black, colors.cyan) - drawFilledBox({20, 3}, dim + 7, 8 + oo, - colors.red, colors.red) - drawFilledBox({(maxb - minb) / 5, 3}, - dim + 7 + minb / 5, 8 + oo, - colors.green, colors.green) - drawText(string.format("%3s", minb.."%"), dim + 6 + minb / 5, 12 + oo, - colors.black, colors.purple) - drawText(maxb.."%", dim + 8 + maxb / 5, 12 + oo, - colors.black, colors.magenta) - drawText("Buffer Target Range", dim + 8, 8 + oo, - colors.black, colors.orange) - drawText("Min", dim + 10, 14 + oo, - colors.black, colors.purple) - drawText("Max", dim + 22, 14 + oo, - colors.black, colors.magenta) - drawText("Reactor ".. (btnOn and "Online" or "Offline"), - dim + 10, 3 + oo, - colors.black, btnOn and colors.green or colors.red) -end - -local function drawStatistics() - local oS = sizey - 13 - drawBox({sizex - dim - 3, sizey - oS - 1}, dim + 2, oS, - colors.blue) - drawText(" Reactor Statistics ", dim + 7, oS + 1, - colors.black, colors.blue) - - --statistics - drawText("Generating : " - ..format(averageLastRFT).."RF/t", dim + 5, oS + 3, - colors.black, colors.green) - drawText("RF Drain " - ..(averageStoredThisTick <= averageLastRFT and "> " or ": ") - ..format(averageRfLost) - .."RF/t", dim + 5, oS + 5, - colors.black, colors.red) - drawText("Efficiency : " - ..format(getEfficiency()).."RF/B", - dim + 5, oS + 7, - colors.black, colors.green) - drawText("Fuel Usage : " - ..format(averageFuelUsage) - .."B/t", dim + 5, oS + 9, - colors.black, colors.green) - drawText("Waste : " - ..string.format("%7d mB", waste), - dim + 5, oS + 11, - colors.black, colors.green) -end - ---Draw a scene -local function drawScene() - if (monSide == nil) then - return - end - if (invalidDim) then - mon.write("Invalid Monitor Dimensions") - return - end - - if (displayingGraphMenu) then - drawGraphButtons() - end - drawControls() - drawStatus() - drawStatistics() - t:draw() -end - ---returns the side that a given peripheral type is connected to -local function getPeripheral(name) - for i,v in pairs(peripheral.getNames()) do - if (peripheral.getType(v) == name) then - return v - end - end - return "" -end - ---Creates all the buttons and determines monitor size -local function initMon() - monSide = getPeripheral("monitor") - if (monSide == nil or monSide == "") then - monSide = nil - return - end - - mon = peripheral.wrap(monSide) - - if mon == nil then - monSide = nil - return - end - - resetMon() - t = touchpoint.new(monSide) - sizex, sizey = mon.getSize() - oo = sizey - 37 - dim = sizex - 33 - - if (sizex == 36) then - dim = -1 - end - if (pcall(addGraphButtons)) then - displayingGraphMenu = true - else - t = touchpoint.new(monSide) - displayingGraphMenu = false - end - local rtn = pcall(addButtons) - if (not rtn) then - t = touchpoint.new(monSide) - invalidDim = true - else - invalidDim = false - end -end - -local function setRods(level) - level = math.max(level, 0) - level = math.min(level, 100) - reactor.setAllControlRodLevels(level) -end - -local function lerp(start, finish, t) - -- Ensure t is in the range [0, 1] - t = math.max(0, math.min(1, t)) - - -- Calculate the linear interpolation - return (1 - t) * start + t * finish -end - --- Function to calculate the average of an array of values -local function calculateAverage(array) - local sum = 0 - for _, value in ipairs(array) do - sum = sum + value - end - return sum / #array -end - --- Define PID controller parameters -local pid = { - setpointRFT = 0, -- Target RFT - setpointRF = 0, -- Target RF - Kp = -.08, -- Proportional gain - Ki = -.0015, -- Integral gain - Kd = -.01, -- Derivative gain - integral = 0, -- Integral term accumulator - lastError = 0, -- Last error for derivative term -} - -local function iteratePID(pid, error) - -- Proportional term - local P = pid.Kp * error - - -- Integral term - pid.integral = pid.integral + pid.Ki * error - pid.integral = math.max(math.min(100, pid.integral), -100) - - -- Derivative term - local derivative = pid.Kd * (error - pid.lastError) - - -- Calculate control rod level - local rodLevel = math.max(math.min(P + pid.integral + derivative, 100), 0) - - -- Update PID controller state - pid.lastError = error - return rodLevel -end - -local function updateRods() - if (not btnOn) then - return - end - local currentRF = storedThisTick - local diffb = maxb - minb - local minRF = minb / 100 * capacity - local diffRF = diffb / 100 * capacity - local diffr = diffb / 100 - local targetRFT = rfLost - local currentRFT = lastRFT - local targetRF = diffRF / 2 + minRF - - pid.setpointRFT = targetRFT - pid.setpointRF = targetRF / capacity * 1000 - - local errorRFT = pid.setpointRFT - currentRFT - local errorRF = pid.setpointRF - currentRF / capacity * 1000 - - local W_RFT = lerp(1, 0, (math.abs(targetRF - currentRF) / capacity / (diffr / 4))) - W_RFT = math.max(math.min(W_RFT, 1), 0) - - local W_RF = (1 - W_RFT) -- Adjust the weight for energy error - - -- Combine the errors with weights - local combinedError = W_RFT * errorRFT + W_RF * errorRF - local error = combinedError - local rftRodLevel = iteratePID(pid, error) - - -- Set control rod levels - setRods(rftRodLevel) -end - --- Saves the configuration of the reactor controller -local function saveToConfig() - local file = fs.open(tag.."Serialized.txt", "w") - local configs = { - maxb = maxb, - minb = minb, - rod = rod, - btnOn = btnOn, - graphsToDraw = graphsToDraw, - XOffs = XOffs, - } - local serialized = textutils.serialize(configs) - file.write(serialized) - file.close() -end - -local storedThisTickValues = {} -local lastRFTValues = {} -local rodValues = {} -local fuelUsageValues = {} -local wasteValues = {} -local fuelTempValues = {} -local caseTempValues = {} -local rfLostValues = {} - -local function updateStats() - storedLastTick = storedThisTick - if (reactorVersion == "Big Reactors") then - storedThisTick = reactor.getEnergyStored() - lastRFT = reactor.getEnergyProducedLastTick() - rod = reactor.getControlRodLevel(0) - fuelUsage = reactor.getFuelConsumedLastTick() / 1000 - waste = reactor.getWasteAmount() - fuelTemp = reactor.getFuelTemperature() - caseTemp = reactor.getCasingTemperature() - -- Big Reactors doesn't give us a way to directly query RF capacity through CC APIs - capacity = math.max(capacity, reactor.getEnergyStored) - elseif (reactorVersion == "Extreme Reactors") then - local bat = reactor.getEnergyStats() - local fuel = reactor.getFuelStats() - - storedThisTick = bat.energyStored - lastRFT = bat.energyProducedLastTick - capacity = bat.energyCapacity - rod = reactor.getControlRodLevel(0) - fuelUsage = fuel.fuelConsumedLastTick / 1000 - waste = reactor.getWasteAmount() - fuelTemp = reactor.getFuelTemperature() - caseTemp = reactor.getCasingTemperature() - elseif (reactorVersion == "Bigger Reactors") then - storedThisTick = reactor.battery().stored() - lastRFT = reactor.battery().producedLastTick() - capacity = reactor.battery().capacity() - rod = reactor.getControlRod(0).level() - fuelUsage = reactor.fuelTank().burnedLastTick() / 1000 - waste = reactor.fuelTank().waste() - fuelTemp = reactor.fuelTemperature() - caseTemp = reactor.casingTemperature() - end - rfLost = lastRFT + storedLastTick - storedThisTick - -- Add the values to the arrays - table.insert(storedThisTickValues, storedThisTick) - table.insert(lastRFTValues, lastRFT) - table.insert(rodValues, rod) - table.insert(fuelUsageValues, fuelUsage) - table.insert(wasteValues, waste) - table.insert(fuelTempValues, fuelTemp) - table.insert(caseTempValues, caseTemp) - table.insert(rfLostValues, rfLost) - - local maxIterations = 20 * secondsToAverage - while #storedThisTickValues > maxIterations do - table.remove(storedThisTickValues, 1) - table.remove(lastRFTValues, 1) - table.remove(rodValues, 1) - table.remove(fuelUsageValues, 1) - table.remove(wasteValues, 1) - table.remove(fuelTempValues, 1) - table.remove(caseTempValues, 1) - table.remove(rfLostValues, 1) - end - - -- Calculate running averages - averageStoredThisTick = calculateAverage(storedThisTickValues) - averageLastRFT = calculateAverage(lastRFTValues) - averageRod = calculateAverage(rodValues) - averageFuelUsage = calculateAverage(fuelUsageValues) - averageWaste = calculateAverage(wasteValues) - averageFuelTemp = calculateAverage(fuelTempValues) - averageCaseTemp = calculateAverage(caseTempValues) - averageRfLost = calculateAverage(rfLostValues) -end - ---Initialize variables from either a config file or the defaults -local function loadFromConfig() - invalidDim = false - local legacyConfigExists = fs.exists(tag..".txt") - local newConfigExists = fs.exists(tag.."Serialized.txt") - if (newConfigExists) then - local file = fs.open(tag.."Serialized.txt", "r") - print("Config file "..tag.."Serialized.txt found! Using configurated settings") - - local serialized = file.readAll() - local deserialized = textutils.unserialise(serialized) - - maxb = deserialized.maxb - minb = deserialized.minb - rod = deserialized.rod - btnOn = deserialized.btnOn - graphsToDraw = deserialized.graphsToDraw - XOffs = deserialized.XOffs - elseif (legacyConfigExists) then - local file = fs.open(tag..".txt", "r") - local calibrated = file.readLine() == "true" - - --read calibration information - if (calibrated) then - _ = tonumber(file.readLine()) - _ = tonumber(file.readLine()) - end - maxb = tonumber(file.readLine()) - minb = tonumber(file.readLine()) - rod = tonumber(file.readLine()) - btnOn = file.readLine() == "true" - - --read Graph data - for i in pairs(XOffs) do - local graph = file.readLine() - local v1 = tonumber(file.readLine()) - local v2 = true - if (graph ~= "nil") then - v2 = false - graphsToDraw[graph] = v1 - end - - XOffs[i] = {v1, v2} - - end - file.close() - else - print("Config file not found, generating default settings!") - - maxb = 70 - minb = 30 - rod = 80 - btnOn = false - if (monSide == nil) then - btnOn = true - end - sizex, sizey = 100, 52 - dim = sizex - 33 - oo = sizey - 37 - enableGraph("Energy Buffer") - enableGraph("Control Level") - enableGraph("Temperatures") - end - btnOff = not btnOn - reactor.setActive(btnOn) -end - -local function startTimer(ticksToUpdate, callback) - local timeToUpdate = ticksToUpdate * 0.05 - local id = os.startTimer(timeToUpdate) - local fun = function(event) - if (event[1] == "timer" and event[2] == id) then - id = os.startTimer(timeToUpdate) - callback() - end - end - return fun -end - - --- Main loop, handles all the events -local function loop() - local ticksToUpdateStats = 1 - local ticksToRedraw = 4 - - local hasClicked = false - - local updateStatsTick = startTimer( - ticksToUpdateStats, - function() - updateStats() - updateRods() - end - ) - local redrawTick = startTimer( - ticksToRedraw, - function() - if (not hasClicked) then - resetMon() - drawScene() - end - hasClicked = false - end - ) - local handleResize = function(event) - if (event[1] == "monitor_resize") then - initMon() - end - end - local handleClick = function(event) - if (event[1] == "button_click") then - t.buttonList[event[2]].func() - saveToConfig() - resetMon() - drawScene() - hasClicked = true - end - end - while (true) do - local event = (monSide == nil) and { os.pullEvent() } or { t:handleEvents() } - - updateStatsTick(event) - redrawTick(event) - handleResize(event) - handleClick(event) - end -end - -local function detectReactor() - -- Bigger Reactors V1. - local reactor_bigger_v1 = getPeripheral("bigger-reactor") - reactor = reactor_bigger_v1 ~= nil and peripheral.wrap(reactor_bigger_v1) - if (reactor ~= nil) then - reactorVersion = "Bigger Reactors" - return true - end - - -- Bigger Reactors V2 - local reactor_bigger_v2 = getPeripheral("BiggerReactors_Reactor") - reactor = reactor_bigger_v2 ~= nil and peripheral.wrap(reactor_bigger_v2) - if (reactor ~= nil) then - reactorVersion = "Bigger Reactors" - return true - end - - -- Big Reactors or Extreme Reactors - local reactor_extreme_or_big = getPeripheral("BigReactors-Reactor") - reactor = reactor_extreme_or_big ~= nil and peripheral.wrap(reactor_extreme_or_big) - if (reactor ~= nil) then - reactorVersion = (reactor.mbIsConnected ~= nil) and "Extreme Reactors" or "Big Reactors" - return true - end - return false -end - ---Entry point -local function main() - term.setBackgroundColor(colors.black) - term.clear() - term.setCursorPos(1,1) - - local reactorDetected = false - while (not reactorDetected) do - reactorDetected = detectReactor() - if (not reactorDetected) then - print("Reactor not detected! Trying again...") - sleep(1) - end - end - - print("Reactor detected! Proceeding with initialization ") - - print("Loading config...") - loadFromConfig() - print("Initializing monitor if connected...") - initMon() - print("Writing config to disk...") - saveToConfig() - print("Reactor initialization done! Starting controller") - sleep(2) - - term.clear() - term.setCursorPos(1,1) - print("Reactor Controller Version "..version) - print("Reactor Mod: "..reactorVersion) - --main loop - - loop() -end - -main() - -print("script exited") -sleep(1) +local version = "0.51" +local tag = "reactorConfig" +--[[ +Program made by DrunkenKas + See github: https://github.com/Kasra-G/ReactorController/#readme + +The MIT License (MIT) + +Copyright (c) 2021 Kasra Ghaffari + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +]] + +dofile("/usr/apis/touchpoint.lua") +dofile("classes.lua") +dofile("draw.lua") +dofile("graph.lua") +dofile("monitor.lua") + +local reactorVersion, reactor +local mon, monSide +local sizex, sizey, dividerXCoord, oo, offy +local btnOn, btnOff, invalidDim +local minb, maxb +local rod, rfLost +local storedLastTick, storedThisTick, lastRFT = 0,0,0 +local fuelTemp, caseTemp, fuelUsage, waste, capacity = 0,0,0,0,1 +local t +local displayingGraphMenu = false + +local SECONDS_TO_AVERAGE = 2 + +local averageStoredThisTick = 0 +local averageLastRFT = 0 +local averageRod = 0 +local averageFuelUsage = 0 +local averageWaste = 0 +local averageFuelTemp = 0 +local averageCaseTemp = 0 +local averageRfLost = 0 + +local MINIMUM_DIVIDER_X_VALUE = 3 + +-- table of which graphs to draw +local graphsToDraw = {} + +-- table of all the graphs +local graphs = +{ + "Energy Buffer", + "Control Level", + "Temperatures", +} + +-- marks the offsets for each graph position +-- { XOffset, } +local XOffs = +{ + { 4, true}, + {27, true}, + {50, true}, + {73, true}, + {96, true}, +} + +--Helper method for adding buttons +local function addButton(touch, name, callBack, offset, size, color1, color2) + local buttonTopLeftCorner = offset + Vector2.one + local buttonBottomRightCorner = offset + size + touch:add( + name, + callBack, + buttonTopLeftCorner.x, + buttonTopLeftCorner.y, + buttonBottomRightCorner.x, + buttonBottomRightCorner.y, + color1, + color2 + ) +end + +local function minAdd10() + minb = math.min(maxb - 10, minb + 10) +end +local function minSub10() + minb = math.max(0, minb - 10) +end +local function maxAdd10() + maxb = math.min(100, maxb + 10) +end +local function maxSub10() + maxb = math.max(minb + 10, maxb - 10) +end + +local function turnOff() + if (btnOn) then + t:toggleButton("Off") + t:toggleButton("On") + btnOff = true + btnOn = false + reactor.setActive(false) + end +end + +local function turnOn() + if (btnOff) then + t:toggleButton("Off") + t:toggleButton("On") + btnOff = false + btnOn = true + reactor.setActive(true) + end +end + +--adds buttons +local function addButtons() + if (sizey == 24) then + oo = 1 + end + local buttonSize = Vector2.new(8, 3) + local offsetOnOff = Vector2.new(dividerXCoord + 5, 3 + oo) + + addButton( + t, + "On", + turnOn, + offsetOnOff, + buttonSize, + colors.red, + colors.lime + ) + + addButton( + t, + "Off", + turnOff, + offsetOnOff + Vector2.new(12, 0), + buttonSize, + colors.red, + colors.lime + ) + + if (btnOn) then + t:toggleButton("On", true) + else + t:toggleButton("Off", true) + end + + local offset = Vector2.new(dividerXCoord, oo) + + if (sizey > 24) then + addButton( + t, + "+ 10", + minAdd10, + offset + Vector2.new(5, 14), + buttonSize, + colors.purple, + colors.pink + ) + addButton( + t, + " + 10 ", + maxAdd10, + offset + Vector2.new(17, 14), + buttonSize, + colors.magenta, + colors.pink + ) + addButton( + t, + "- 10", + minSub10, + offset + Vector2.new(5, 18), + buttonSize, + colors.purple, + colors.pink + ) + addButton( + t, + " - 10 ", + maxSub10, + offset + Vector2.new(17, 18), + buttonSize, + colors.magenta, + colors.pink + ) + end +end + +--Resets the monitor +local function resetMon() + if (monSide == nil) then + return + end + mon.setBackgroundColor(colors.black) + mon.clear() + mon.setTextScale(0.5) + mon.setCursorPos(1,1) +end + +local function getPercPower() + return averageStoredThisTick / capacity * 100 +end + +local function rnd(num, dig) + return math.floor(10 ^ dig * num) / (10 ^ dig) +end + +local function getEfficiency() + return averageLastRFT / averageFuelUsage +end + +local function format(num) + if (num >= 1000000000) then + return string.format("%7.3f G", num / 1000000000) + elseif (num >= 1000000) then + return string.format("%7.3f M", num / 1000000) + elseif (num >= 1000) then + return string.format("%7.3f K", num / 1000) + elseif (num >= 1) then + return string.format("%7.3f ", num) + elseif (num >= .001) then + return string.format("%7.3f m", num * 1000) + elseif (num >= .000001) then + return string.format("%7.3f u", num * 1000000) + else + return string.format("%7.3f ", 0) + end +end + + +local function getAvailableXOff() + for i,v in pairs(XOffs) do + if (v[2] and v[1] < dividerXCoord - 1) then + v[2] = false + return v[1] + end + end + return -1 +end + +local function getXOff(num) + for i,v in pairs(XOffs) do + if (v[1] == num) then + return v + end + end + return nil +end + +local function enableGraph(name) + if (graphsToDraw[name] ~= nil) then + return + end + local e = getAvailableXOff() + if (e ~= -1) then + graphsToDraw[name] = e + if (displayingGraphMenu) then + t:toggleButton(name) + end + end +end + +local function disableGraph(name) + if (graphsToDraw[name] == nil) then + return + end + if (displayingGraphMenu) then + t:toggleButton(name) + end + getXOff(graphsToDraw[name])[2] = true + graphsToDraw[name] = nil +end + +local function toggleGraph(name) + if (graphsToDraw[name] == nil) then + enableGraph(name) + else + disableGraph(name) + end +end + +local function addGraphButtons() + offy = oo - 14 + local graphButtonSize = Vector2.new(20, 3) + local graphButtonOffset = Vector2.new(dividerXCoord + 5, offy) + for i,graphName in pairs(graphs) do + + addButton( + t, + graphName, + function() toggleGraph(graphName) end, + graphButtonOffset + Vector2.new(0, i * 3 - 1), + graphButtonSize, + colors.red, + colors.lime + ) + if (graphsToDraw[graphName] ~= nil) then + t:toggleButton(graphName, true) + end + end +end + +local function drawGraphButtons(mon, offset, size) + + DrawUtil.drawRectangle(mon, colors.black, colors.orange, offset, size) + local textPos = offset + Vector2.new(4, 0) + DrawUtil.drawText( + mon, + " Graph Controls ", + textPos, + colors.black, + colors.orange + ) +end + +local function drawEnergyBuffer(mon, offset, graphSize, drawPercentLabelOnRight) + DrawUtil.drawText(mon, "Energy Buffer", offset, colors.black, colors.orange) + DrawUtil.drawRectangle(mon, colors.red, colors.gray, offset + Vector2.new(0, 1), graphSize) + + local energyBufferMaxHeight = graphSize.y - 2 + local unitEnergyLevel = getPercPower() / 100 + local energyBufferHeight = math.floor(unitEnergyLevel * energyBufferMaxHeight + 0.5) + local rndpw = rnd(getPercPower(), 2) + + local energyBufferColor + if rndpw < maxb and rndpw > minb then + energyBufferColor = colors.green + elseif rndpw >= maxb then + energyBufferColor = colors.orange + elseif rndpw <= minb then + energyBufferColor = colors.blue + end + + local energyBufferTipOffset = offset + Vector2.new(1, 2 + energyBufferMaxHeight - energyBufferHeight) + local energyBufferSize = Vector2.new(graphSize.x - 2, energyBufferHeight) + + DrawUtil.drawFilledRectangle(mon, energyBufferColor, energyBufferTipOffset, energyBufferSize) + + local energyBufferTextOffset = energyBufferTipOffset + local rfLabelBackgroundColor = energyBufferColor + + if energyBufferHeight <= 0 then + energyBufferTextOffset = energyBufferTipOffset + Vector2.new(0, -1) + rfLabelBackgroundColor = colors.red + end + + local percentLabelXOffset = offset.x - 6 + if drawPercentLabelOnRight then + percentLabelXOffset = offset.x + 15 + end + + DrawUtil.drawText( + mon, + string.format(drawPercentLabelOnRight and "%.2f%%" or "%5.2f%%", rndpw), + Vector2.new(percentLabelXOffset, energyBufferTextOffset.y), + colors.black, + energyBufferColor + ) + DrawUtil.drawText( + mon, + format(averageStoredThisTick).."RF", + energyBufferTextOffset, + rfLabelBackgroundColor, + colors.black + ) +end + +local function drawControlGraph(mon, offset, size, averageRod) + local unitRodLevel = averageRod / 100 + local controlRodMaxPixelHeight = size.y - 2 + local controlRodPixelHeight = math.ceil(unitRodLevel * controlRodMaxPixelHeight) + + DrawUtil.drawText( + mon, + "Control Level", + offset + Vector2.new(1, 0), + colors.black, + colors.orange + + ) + DrawUtil.drawRectangle( + mon, + colors.yellow, + colors.gray, + offset + Vector2.new(0, 1), + size + ) + DrawUtil.drawFilledRectangle( + mon, + colors.white, + offset + Vector2.new(3, 2), + Vector2.new(9, controlRodPixelHeight) + ) + + local controlRodLevelTextPos, color + if controlRodPixelHeight > 0 then + color = colors.white + controlRodLevelTextPos = offset + Vector2.new(4, 1 + controlRodPixelHeight) + else + color = colors.yellow + controlRodLevelTextPos = offset + Vector2.new(4, 2) + end + + DrawUtil.drawText( + mon, + string.format("%6.2f%%", averageRod), + controlRodLevelTextPos, + color, + colors.black + ) +end + +local function drawTemperatures(mon, offset, size) + + DrawUtil.drawRectangle(mon, colors.black, colors.gray, offset + Vector2.new(1, 1), size) + + local CASE_TEMP_COLOR = colors.lightBlue + local FUEL_TEMP_COLOR = colors.magenta + local BACKGROUND_COLOR = colors.black + + local assumedMaxCaseTemperature = 3000 + local assumedMaxFuelTemperature = 3000 + local temperatureMaxHeight = size.y - 2 + + local tempUnit = (reactorVersion == "Bigger Reactors") and "K" or "C" + local tempFormat = "%4s"..tempUnit + + DrawUtil.drawText(mon, "Temperatures", offset + Vector2.new(2, 0), BACKGROUND_COLOR, colors.orange) + DrawUtil.drawFilledRectangle(mon, colors.gray, offset + Vector2.new(8, 2), Vector2.new(1, temperatureMaxHeight)) + + -- case temp + DrawUtil.drawText(mon, "Case", offset + Vector2.new(3, 1), colors.gray, colors.lightBlue) + local caseUnit = math.min(averageCaseTemp / assumedMaxCaseTemperature, 1) + local caseTempHeight = math.floor(caseUnit * temperatureMaxHeight + 0.5) + + local caseTempOffset = offset + Vector2.new(2, 2 + temperatureMaxHeight - caseTempHeight) + local caseTempSize = Vector2.new(6, caseTempHeight) + + DrawUtil.drawFilledRectangle(mon, CASE_TEMP_COLOR, caseTempOffset, caseTempSize) + + local caseTempTextOffset = caseTempOffset + local caseTempTextBackgroundColor = CASE_TEMP_COLOR + local caseTempTextColor = BACKGROUND_COLOR + + if caseTempHeight <= 0 then + caseTempTextOffset = caseTempOffset + Vector2.new(0, -1) + caseTempTextColor, caseTempTextBackgroundColor = caseTempTextBackgroundColor, caseTempTextColor + end + + local caseRnd = math.floor(averageCaseTemp + 0.5) + DrawUtil.drawText(mon, string.format(tempFormat, caseRnd..""), caseTempTextOffset, caseTempTextBackgroundColor, caseTempTextColor) + + -- fuel temp + DrawUtil.drawText(mon, "Fuel", offset + Vector2.new(10, 1), colors.gray, colors.lightBlue) + local fuelUnit = math.min(averageFuelTemp / assumedMaxFuelTemperature, 1) + local fuelTempHeight = math.floor(fuelUnit * temperatureMaxHeight + 0.5) + + local fuelTempOffset = offset + Vector2.new(9, 2 + temperatureMaxHeight - fuelTempHeight) + local fuelTempSize = Vector2.new(6, fuelTempHeight) + + DrawUtil.drawFilledRectangle(mon, FUEL_TEMP_COLOR, fuelTempOffset, fuelTempSize) + + local fuelTempTextOffset = fuelTempOffset + local fuelTempTextBackgroundColor = FUEL_TEMP_COLOR + local fuelTempTextColor = BACKGROUND_COLOR + + if fuelTempHeight <= 0 then + fuelTempTextOffset = fuelTempOffset + Vector2.new(0, -1) + fuelTempTextColor, fuelTempTextBackgroundColor = fuelTempTextBackgroundColor, fuelTempTextColor + end + + local fuelRnd = math.floor(averageFuelTemp + 0.5) + DrawUtil.drawText(mon, string.format(tempFormat, fuelRnd..""), fuelTempTextOffset, fuelTempTextBackgroundColor, fuelTempTextColor) +end + +local function drawGraph(name, graphOffset, graphSize) + if (name == "Energy Buffer") then + local drawPercentLabelOnRight = graphOffset.x + 19 < dividerXCoord - 1 + drawEnergyBuffer(mon, graphOffset, graphSize, drawPercentLabelOnRight) + elseif (name == "Control Level") then + drawControlGraph(mon, graphOffset, graphSize, averageRod) + elseif (name == "Temperatures") then + drawTemperatures(mon, graphOffset, graphSize) + end +end + +local function drawGraphs(mon, graphsToDraw, dividerXCoord, offset, size) + DrawUtil.drawRectangle(mon, colors.black, colors.lightBlue, offset, size) + local label = " Reactor Graphs " + DrawUtil.drawText( + mon, + label, + offset + Vector2.new(dividerXCoord - (#label + 5) - 1, 0), + colors.black, + colors.lightBlue + ) + + local graphSize = Vector2.new(15, sizey - 7) + local graphYOffset = 4 + for graphName, graphXOffset in pairs(graphsToDraw) do + if (graphXOffset + graphSize.x < dividerXCoord) then + drawGraph(graphName, Vector2.new(graphXOffset, graphYOffset), graphSize) + end + end +end + +local function drawControls(mon, offset, size, drawBufferVisualization) + + DrawUtil.drawRectangle(mon, colors.black, colors.cyan, offset, size) + DrawUtil.drawText(mon, " Reactor Controls ", offset + Vector2.new(4, 0), colors.black, colors.cyan) + + local reactorOnOffLabel = "Reactor "..(btnOn and "Online" or "Offline") + local reactorOnOffLabelColor = btnOn and colors.green or colors.red + DrawUtil.drawText(mon, reactorOnOffLabel, offset + Vector2.new(7, 2), colors.black, reactorOnOffLabelColor) + + if not drawBufferVisualization then + return + end + + local bufferMinInPixels = minb / 5 + local bufferMaxInPixels = maxb / 5 + local bufferRangePixelWidth = bufferMaxInPixels - bufferMinInPixels + + local bufferVisualOffset = offset + Vector2.new(5, 8) + local bufferVisualSize = Vector2.new(20, 3) + + DrawUtil.drawText(mon, "Buffer Target Range", bufferVisualOffset + Vector2.new(0, -1), colors.black, colors.orange) + DrawUtil.drawFilledRectangle(mon, colors.red, bufferVisualOffset, bufferVisualSize) + DrawUtil.drawFilledRectangle(mon, colors.green, bufferVisualOffset + Vector2.new(bufferMinInPixels, 0), Vector2.new(bufferRangePixelWidth, 3)) + + DrawUtil.drawText( + mon, + string.format("%3s", minb.."%"), + bufferVisualOffset + Vector2.new(bufferMinInPixels - 3, bufferVisualSize.y), + colors.black, + colors.purple + ) + DrawUtil.drawText( + mon, + maxb.."%", + bufferVisualOffset + Vector2.new(bufferMaxInPixels, bufferVisualSize.y), + colors.black, + colors.magenta + ) + DrawUtil.drawText(mon, "Min", offset + Vector2.new(7, 13), colors.black, colors.purple) + DrawUtil.drawText(mon, "Max", offset + Vector2.new(19, 13), colors.black, colors.purple) +end + +local function drawStatistics(mon, offset, size) + DrawUtil.drawRectangle(mon, colors.black, colors.blue, offset, size) + DrawUtil.drawText( + mon, + " Reactor Statistics ", + offset + Vector2.new(4, 0), + colors.black, + colors.blue + ) + + DrawUtil.drawText( + mon, + "Generating : "..format(averageLastRFT).."RF/t", + offset + Vector2.new(2, 2), + colors.black, + colors.green + ) + + DrawUtil.drawText( + mon, + "RF Drain "..(averageStoredThisTick <= averageLastRFT and "> " or ": ")..format(averageRfLost).."RF/t", + offset + Vector2.new(2, 4), + colors.black, + colors.red + ) + + DrawUtil.drawText( + mon, + "Efficiency : "..format(getEfficiency()).."RF/B", + offset + Vector2.new(2, 6), + colors.black, + colors.green + ) + + DrawUtil.drawText( + mon, + "Fuel Usage : "..format(averageFuelUsage).."B/t", + offset + Vector2.new(2, 8), + colors.black, + colors.green + ) + + DrawUtil.drawText( + mon, + "Waste : "..string.format("%7d mB", waste), + offset + Vector2.new(2, 10), + colors.black, + colors.green + ) +end + +--Draw a scene +local function drawScene() + if (monSide == nil) then + return + end + if (invalidDim) then + mon.write("Invalid Monitor Dimensions") + return + end + + local offset, size + + if (displayingGraphMenu) then + offset = Vector2.new(dividerXCoord + 1, offy + 1) + size = Vector2.new(30, 13) + drawGraphButtons(mon, offset, size) + end + + offset = Vector2.new(dividerXCoord + 1, oo + 1) + size = Vector2.new(30, 23) + local drawBufferVisualization = true + if (sizey <= 24) then + size = Vector2.new(30, 9) + drawBufferVisualization = false + end + drawControls(mon, offset, size, drawBufferVisualization) + + if (dividerXCoord > MINIMUM_DIVIDER_X_VALUE) then + offset = Vector2.new(2, 2) + size = Vector2.new(dividerXCoord - 2, sizey - 2) + drawGraphs(mon, graphsToDraw, dividerXCoord, offset, size) + end + + offset = Vector2.new(dividerXCoord + 1, sizey - 12) + size = Vector2.new(30, 12) + drawStatistics(mon, offset, size) + + t:draw() +end + +local function getAllPeripheralIdsForType(targetType) + ---@type string[] + local peripheralIds = {} + for _, id in pairs(peripheral.getNames()) do + if (peripheral.getType(id) == targetType) then + table.insert(peripheralIds, id) + end + end + return peripheralIds +end + +---@type table +local monitors = {} + +local function initMon2(id) + local monitor = Monitor.new(id) + monSide = id + mon = monitor.mon + monitor:clear() + t = monitor.touch + + sizex, sizey = mon.getSize() + oo = sizey - 37 + dividerXCoord = sizex - 31 + + if (sizex == 36) then + dividerXCoord = MINIMUM_DIVIDER_X_VALUE + end + displayingGraphMenu = pcall(function() addGraphButtons() end) + _, invalidDim = pcall(function() addButtons() end) + + return monitor +end + +local function updateMonitors() + for _, monitor in pairs(monitors) do + ---@type ReactorStatistics + local reactorStats = {} + monitor:update(reactorStats) + end +end + +local function initMonitors() + monitors = {} + local ids = getAllPeripheralIdsForType("monitor") + + for _, id in pairs(ids) do + monitors[id] = initMon2(id) + end +end +-- DELETE LATER! +initMonitors() + +--returns the side that a given peripheral type is connected to +local function getPeripheral(targetType) + for _, name in pairs(peripheral.getNames()) do + if (peripheral.getType(name) == targetType) then + return name + end + end + return "" +end + +--Creates all the buttons and determines monitor size +local function initMon() + monSide = getPeripheral("monitor") + if (monSide == nil or monSide == "") then + monSide = nil + return + end + + local monitor = Monitor.new(monSide) + mon = monitor.mon + + if mon == nil then + monSide = nil + return + end + + resetMon() + t = touchpoint.new(monSide) + sizex, sizey = mon.getSize() + oo = sizey - 37 + dividerXCoord = sizex - 31 + + if (sizex == 36) then + dividerXCoord = MINIMUM_DIVIDER_X_VALUE + end + displayingGraphMenu = pcall(function() addGraphButtons() end) + _, invalidDim = pcall(function() addButtons() end) +end + +local function setRods(level) + level = math.max(level, 0) + level = math.min(level, 100) + reactor.setAllControlRodLevels(level) +end + +local function lerp(start, finish, t) + t = math.max(0, math.min(1, t)) + + return (1 - t) * start + t * finish +end + +-- Function to calculate the average of an array of values +local function calculateAverage(array) + local sum = 0 + for _, value in ipairs(array) do + sum = sum + value + end + return sum / #array +end + +-- Define PID controller parameters +local pid = { + setpointRFT = 0, -- Target RFT + setpointRF = 0, -- Target RF + Kp = -.08, -- Proportional gain + Ki = -.0015, -- Integral gain + Kd = -.01, -- Derivative gain + integral = 0, -- Integral term accumulator + lastError = 0, -- Last error for derivative term +} + +local function iteratePID(pid, error) + -- Proportional term + local P = pid.Kp * error + + -- Integral term + pid.integral = pid.integral + pid.Ki * error + pid.integral = math.max(math.min(100, pid.integral), -100) + + -- Derivative term + local derivative = pid.Kd * (error - pid.lastError) + + -- Calculate control rod level + local rodLevel = math.max(math.min(P + pid.integral + derivative, 100), 0) + + -- Update PID controller state + pid.lastError = error + return rodLevel +end + +local function updateRods() + if (not btnOn) then + return + end + local currentRF = storedThisTick + local diffb = maxb - minb + local minRF = minb / 100 * capacity + local diffRF = diffb / 100 * capacity + local diffr = diffb / 100 + local targetRFT = rfLost + local currentRFT = lastRFT + local targetRF = diffRF / 2 + minRF + + pid.setpointRFT = targetRFT + pid.setpointRF = targetRF / capacity * 1000 + + local errorRFT = pid.setpointRFT - currentRFT + local errorRF = pid.setpointRF - currentRF / capacity * 1000 + + local W_RFT = lerp(1, 0, (math.abs(targetRF - currentRF) / capacity / (diffr / 4))) + W_RFT = math.max(math.min(W_RFT, 1), 0) + + local W_RF = (1 - W_RFT) -- Adjust the weight for energy error + + -- Combine the errors with weights + local combinedError = W_RFT * errorRFT + W_RF * errorRF + local error = combinedError + local rftRodLevel = iteratePID(pid, error) + + -- Set control rod levels + setRods(rftRodLevel) +end + +-- Saves the configuration of the reactor controller +local function saveToConfig() + local file = fs.open(tag.."Serialized.txt", "w") + local configs = { + maxb = maxb, + minb = minb, + rod = rod, + btnOn = btnOn, + graphsToDraw = graphsToDraw, + XOffs = XOffs, + } + local serialized = textutils.serialize(configs) + file.write(serialized) + file.close() +end + +local storedThisTickValues = {} +local lastRFTValues = {} +local rodValues = {} +local fuelUsageValues = {} +local wasteValues = {} +local fuelTempValues = {} +local caseTempValues = {} +local rfLostValues = {} + +local function updateStats() + storedLastTick = storedThisTick + if (reactorVersion == "Big Reactors") then + storedThisTick = reactor.getEnergyStored() + lastRFT = reactor.getEnergyProducedLastTick() + rod = reactor.getControlRodLevel(0) + fuelUsage = reactor.getFuelConsumedLastTick() / 1000 + waste = reactor.getWasteAmount() + fuelTemp = reactor.getFuelTemperature() + caseTemp = reactor.getCasingTemperature() + -- Big Reactors doesn't give us a way to directly query RF capacity through CC APIs + capacity = math.max(capacity, reactor.getEnergyStored) + elseif (reactorVersion == "Extreme Reactors") then + local bat = reactor.getEnergyStats() + local fuel = reactor.getFuelStats() + + storedThisTick = bat.energyStored + lastRFT = bat.energyProducedLastTick + capacity = bat.energyCapacity + rod = reactor.getControlRodLevel(0) + fuelUsage = fuel.fuelConsumedLastTick / 1000 + waste = reactor.getWasteAmount() + fuelTemp = reactor.getFuelTemperature() + caseTemp = reactor.getCasingTemperature() + elseif (reactorVersion == "Bigger Reactors") then + storedThisTick = reactor.battery().stored() + lastRFT = reactor.battery().producedLastTick() + capacity = reactor.battery().capacity() + rod = reactor.getControlRod(0).level() + fuelUsage = reactor.fuelTank().burnedLastTick() / 1000 + waste = reactor.fuelTank().waste() + fuelTemp = reactor.fuelTemperature() + caseTemp = reactor.casingTemperature() + end + rfLost = lastRFT + storedLastTick - storedThisTick + -- Add the values to the arrays + table.insert(storedThisTickValues, storedThisTick) + table.insert(lastRFTValues, lastRFT) + table.insert(rodValues, rod) + table.insert(fuelUsageValues, fuelUsage) + table.insert(wasteValues, waste) + table.insert(fuelTempValues, fuelTemp) + table.insert(caseTempValues, caseTemp) + table.insert(rfLostValues, rfLost) + + local maxIterations = 20 * SECONDS_TO_AVERAGE + while #storedThisTickValues > maxIterations do + table.remove(storedThisTickValues, 1) + table.remove(lastRFTValues, 1) + table.remove(rodValues, 1) + table.remove(fuelUsageValues, 1) + table.remove(wasteValues, 1) + table.remove(fuelTempValues, 1) + table.remove(caseTempValues, 1) + table.remove(rfLostValues, 1) + end + + -- Calculate running averages + averageStoredThisTick = calculateAverage(storedThisTickValues) + averageLastRFT = calculateAverage(lastRFTValues) + averageRod = calculateAverage(rodValues) + averageFuelUsage = calculateAverage(fuelUsageValues) + averageWaste = calculateAverage(wasteValues) + averageFuelTemp = calculateAverage(fuelTempValues) + averageCaseTemp = calculateAverage(caseTempValues) + averageRfLost = calculateAverage(rfLostValues) +end + +--Initialize variables from either a config file or the defaults +local function loadFromConfig() + invalidDim = false + local legacyConfigExists = fs.exists(tag..".txt") + local newConfigExists = fs.exists(tag.."Serialized.txt") + if (newConfigExists) then + local file = fs.open(tag.."Serialized.txt", "r") + print("Config file "..tag.."Serialized.txt found! Using configurated settings") + + local serialized = file.readAll() + local deserialized = textutils.unserialise(serialized) + + maxb = deserialized.maxb + minb = deserialized.minb + rod = deserialized.rod + btnOn = deserialized.btnOn + graphsToDraw = deserialized.graphsToDraw + XOffs = deserialized.XOffs + elseif (legacyConfigExists) then + local file = fs.open(tag..".txt", "r") + local calibrated = file.readLine() == "true" + + --read calibration information + if (calibrated) then + _ = tonumber(file.readLine()) + _ = tonumber(file.readLine()) + end + maxb = tonumber(file.readLine()) + minb = tonumber(file.readLine()) + rod = tonumber(file.readLine()) + btnOn = file.readLine() == "true" + + --read Graph data + for i in pairs(XOffs) do + local graph = file.readLine() + local v1 = tonumber(file.readLine()) + local v2 = true + if (graph ~= "nil") then + v2 = false + graphsToDraw[graph] = v1 + end + + XOffs[i] = {v1, v2} + + end + file.close() + else + print("Config file not found, generating default settings!") + + maxb = 70 + minb = 30 + rod = 80 + btnOn = false + if (monSide == nil) then + btnOn = true + end + sizex, sizey = 100, 52 + dividerXCoord = sizex - 31 + oo = sizey - 37 + enableGraph("Energy Buffer") + enableGraph("Control Level") + enableGraph("Temperatures") + end + btnOff = not btnOn + reactor.setActive(btnOn) +end + +local function startTimer(ticksToUpdate, callback) + local timeToUpdate = ticksToUpdate * 0.05 + local id = os.startTimer(timeToUpdate) + local fun = function(event) + if (event[1] == "timer" and event[2] == id) then + id = os.startTimer(timeToUpdate) + callback() + end + end + return fun +end + + +-- Main loop, handles all the events +local function loop() + local ticksToUpdateStats = 1 + local ticksToRedraw = 4 + + local hasClicked = false + + local updateStatsTick = startTimer( + ticksToUpdateStats, + function() + updateStats() + updateRods() + end + ) + local redrawTick = startTimer( + ticksToRedraw, + function() + if (not hasClicked) then + resetMon() + drawScene() + end + hasClicked = false + end + ) + local handleResize = function(event) + if (event[1] == "monitor_resize") then + local peripheralId = event[2] + initMon() + end + end + local handleClick = function(event) + if (event[1] == "button_click") then + t.buttonList[event[2]].func() + saveToConfig() + resetMon() + drawScene() + hasClicked = true + end + end + while (true) do + local event = { os.pullEvent() } + + if monSide ~= nil then + event = { t:handleEvents(unpack(event)) } + end + + updateStatsTick(event) + redrawTick(event) + handleResize(event) + handleClick(event) + end +end + +local function detectReactor() + -- Bigger Reactors V1. + local reactor_bigger_v1 = getPeripheral("bigger-reactor") + reactor = reactor_bigger_v1 ~= nil and peripheral.wrap(reactor_bigger_v1) + if (reactor ~= nil) then + reactorVersion = "Bigger Reactors" + return true + end + + -- Bigger Reactors V2 + local reactor_bigger_v2 = getPeripheral("BiggerReactors_Reactor") + reactor = reactor_bigger_v2 ~= nil and peripheral.wrap(reactor_bigger_v2) + if (reactor ~= nil) then + reactorVersion = "Bigger Reactors" + return true + end + + -- Big Reactors or Extreme Reactors + local reactor_extreme_or_big = getPeripheral("BigReactors-Reactor") + reactor = reactor_extreme_or_big ~= nil and peripheral.wrap(reactor_extreme_or_big) + if (reactor ~= nil) then + reactorVersion = (reactor.mbIsConnected ~= nil) and "Extreme Reactors" or "Big Reactors" + return true + end + return false +end + +--Entry point +local function main() + term.setBackgroundColor(colors.black) + term.clear() + term.setCursorPos(1,1) + + local reactorDetected = false + while (not reactorDetected) do + reactorDetected = detectReactor() + if (not reactorDetected) then + print("Reactor not detected! Trying again...") + sleep(1) + end + end + + print("Reactor detected! Proceeding with initialization ") + + print("Loading config...") + loadFromConfig() + print("Initializing monitor if connected...") + initMon() + print("Writing config to disk...") + saveToConfig() + print("Reactor initialization done! Starting controller") + sleep(2) + + term.clear() + term.setCursorPos(1,1) + print("Reactor Controller Version "..version) + print("Reactor Mod: "..reactorVersion) + --main loop + + loop() +end + +main() + +print("script exited") +sleep(1) \ No newline at end of file diff --git a/startup b/startup new file mode 100644 index 0000000..f6cb9c1 --- /dev/null +++ b/startup @@ -0,0 +1,5 @@ +shell.run("update_reactor.lua") +while (true) do + shell.run("reactorController.lua") + sleep(2) +end diff --git a/updater.lua b/update_reactor.lua similarity index 96% rename from updater.lua rename to update_reactor.lua index ea99219..26ae000 100644 --- a/updater.lua +++ b/update_reactor.lua @@ -1,96 +1,96 @@ -local version = "1.0" - ---Github: https://github.com/Kasra-G/ReactorController/#readme - --- ["filename"] = "pastebinCode" -local filesToUpdate = { - ["/reactorController.lua"] = "b17hfTqe", - ["/usr/apis/touchpoint.lua"] = "nx9pkLbJ", - ["/update_reactor.lua"] = "w6vVtrLb", -} - -local function getPastebinFileContents(filename, pastebinCode) - local tempFilename = "/temp" .. filename - - -- Avoid calling pastebin API directly to be more robust towards any future API changes - shell.run("pastebin", "get", pastebinCode, tempFilename) - local tempFile = fs.open(tempFilename, "r") - - if not tempFile then - return nil - end - - local fileContents = tempFile.readAll() - tempFile.close() - fs.delete(tempFilename) - return fileContents -end - --- Requires HTTP to be enabled -local function getVersion(fileContents) - if not fileContents then - return nil - end - - local _, numberChars = fileContents:lower():find('local version = "') - - if not numberChars then - return nil - end - - local fileVersion = "" - local char = "" - - while char ~= '"' do - numberChars = numberChars + 1 - char = fileContents:sub(numberChars,numberChars) - fileVersion = fileVersion .. char - end - - fileVersion = fileVersion:sub(1,#fileVersion-1) -- Remove quotes around the version number - return fileVersion -end - -local function updateFile(filename, pastebinCode) - if fs.isDir(filename) then - print("[Error] " .. filename .. " is a directory") - return - end - local pastebinContents = getPastebinFileContents(filename, pastebinCode) - - if not pastebinContents then - print("[Error] " .. filename .. " has an invalid link") - end - - local pastebinVersion = getVersion(pastebinContents) - if not pastebinVersion then - print("[Error] the pastebin code for " .. filename .. " does not have a version variable") - return - end - - local localVersion = nil - if fs.exists(filename) then - local localFile = fs.open(filename,"r") - localVersion = getVersion(localFile.readAll()) - localFile.close() - end - - if localVersion ~= pastebinVersion then - local localFile = fs.open(filename,"w") - localFile.write(pastebinContents) - localFile.close() - print("[Success] " .. filename .. " has been updated to version " .. pastebinVersion) - elseif pastebinVersion == localVersion then - print("[Success] No update required: " .. filename .. " is already the latest version") - end -end - -local function main() - fs.makeDir("/usr") - fs.makeDir("/usr/apis") - for filename, pastebinCode in pairs(filesToUpdate) do - updateFile(filename, pastebinCode) - end -end - -main() +local version = "1.0" + +--Github: https://github.com/Kasra-G/ReactorController/#readme + +-- ["filename"] = "pastebinCode" +local filesToUpdate = { + ["/reactorController.lua"] = "b17hfTqe", + ["/usr/apis/touchpoint.lua"] = "nx9pkLbJ", + ["/update_reactor.lua"] = "w6vVtrLb", +} + +local function getPastebinFileContents(filename, pastebinCode) + local tempFilename = "/temp" .. filename + + -- Avoid calling pastebin API directly to be more robust towards any future API changes + shell.run("pastebin", "get", pastebinCode, tempFilename) + local tempFile = fs.open(tempFilename, "r") + + if not tempFile then + return nil + end + + local fileContents = tempFile.readAll() + tempFile.close() + fs.delete(tempFilename) + return fileContents +end + +-- Requires HTTP to be enabled +local function getVersion(fileContents) + if not fileContents then + return nil + end + + local _, numberChars = fileContents:lower():find('local version = "') + + if not numberChars then + return nil + end + + local fileVersion = "" + local char = "" + + while char ~= '"' do + numberChars = numberChars + 1 + char = fileContents:sub(numberChars,numberChars) + fileVersion = fileVersion .. char + end + + fileVersion = fileVersion:sub(1,#fileVersion-1) -- Remove quotes around the version number + return fileVersion +end + +local function updateFile(filename, pastebinCode) + if fs.isDir(filename) then + print("[Error] " .. filename .. " is a directory") + return + end + local pastebinContents = getPastebinFileContents(filename, pastebinCode) + + if not pastebinContents then + print("[Error] " .. filename .. " has an invalid link") + end + + local pastebinVersion = getVersion(pastebinContents) + if not pastebinVersion then + print("[Error] the pastebin code for " .. filename .. " does not have a version variable") + return + end + + local localVersion = nil + if fs.exists(filename) then + local localFile = fs.open(filename,"r") + localVersion = getVersion(localFile.readAll()) + localFile.close() + end + + if localVersion ~= pastebinVersion then + local localFile = fs.open(filename,"w") + localFile.write(pastebinContents) + localFile.close() + print("[Success] " .. filename .. " has been updated to version " .. pastebinVersion) + elseif pastebinVersion == localVersion then + print("[Success] No update required: " .. filename .. " is already the latest version") + end +end + +local function main() + fs.makeDir("/usr") + fs.makeDir("/usr/apis") + for filename, pastebinCode in pairs(filesToUpdate) do + updateFile(filename, pastebinCode) + end +end + +main() \ No newline at end of file diff --git a/touchpoint.lua b/usr/apis/touchpoint.lua similarity index 95% rename from touchpoint.lua rename to usr/apis/touchpoint.lua index 87656b6..8801092 100644 --- a/touchpoint.lua +++ b/usr/apis/touchpoint.lua @@ -1,186 +1,187 @@ -local version = "1.01" ---[[ -The MIT License (MIT) - -Copyright (c) 2013 Lyqyd - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -Edited by DrunkenKas. - See Github: https://github.com/Kasra-G/ReactorController/#readme ---]] - -local function setupLabel(buttonLen, minY, maxY, name) - local labelTable = {} - if type(name) == "table" then - for i = 1, #name do - labelTable[i] = name[i] - end - name = name.label - elseif type(name) == "string" then - local buttonText = string.sub(name, 1, buttonLen - 2) - if #buttonText < #name then - buttonText = " "..buttonText.." " - else - local labelLine = string.rep(" ", math.floor((buttonLen - #buttonText) / 2))..buttonText - buttonText = labelLine..string.rep(" ", buttonLen - #labelLine) - end - for i = 1, maxY - minY + 1 do - if maxY == minY or i == math.floor((maxY - minY) / 2) + 1 then - labelTable[i] = buttonText - else - labelTable[i] = string.rep(" ", buttonLen) - end - end - end - return labelTable, name -end - -local Button = { - draw = function(self) - local old = term.redirect(self.mon) - term.setTextColor(colors.white) - term.setBackgroundColor(colors.black) - for name, buttonData in pairs(self.buttonList) do - if buttonData.active then - term.setBackgroundColor(buttonData.activeColor) - term.setTextColor(buttonData.activeText) - else - term.setBackgroundColor(buttonData.inactiveColor) - term.setTextColor(buttonData.inactiveText) - end - for i = buttonData.yMin, buttonData.yMax do - term.setCursorPos(buttonData.xMin, i) - term.write(buttonData.label[i - buttonData.yMin + 1]) - end - end - if old then - term.redirect(old) - else - term.restore() - end - end, - add = function(self, name, func, xMin, yMin, xMax, yMax, inactiveColor, activeColor, inactiveText, activeText) - local label, name = setupLabel(xMax - xMin + 1, yMin, yMax, name) - if self.buttonList[name] then error("button already exists", 2) end - local x, y = self.mon.getSize() - if xMin < 1 or yMin < 1 or xMax > x or yMax > y then error("button out of bounds", 2) end - self.buttonList[name] = { - func = func, - xMin = xMin, - yMin = yMin, - xMax = xMax, - yMax = yMax, - active = false, - inactiveColor = inactiveColor or colors.red, - activeColor = activeColor or colors.lime, - inactiveText = inactiveText or colors.white, - activeText = activeText or colors.white, - label = label, - } - for i = xMin, xMax do - for j = yMin, yMax do - if self.clickMap[i][j] ~= nil then - --undo changes - for k = xMin, xMax do - for l = yMin, yMax do - if self.clickMap[k][l] == name then - self.clickMap[k][l] = nil - end - end - end - self.buttonList[name] = nil - error("overlapping button", 2) - end - self.clickMap[i][j] = name - end - end - end, - remove = function(self, name) - if self.buttonList[name] then - local button = self.buttonList[name] - for i = button.xMin, button.xMax do - for j = button.yMin, button.yMax do - self.clickMap[i][j] = nil - end - end - self.buttonList[name] = nil - end - end, - run = function(self) - while true do - self:draw() - local event = {self:handleEvents(os.pullEvent(self.side == "term" and "mouse_click" or "monitor_touch"))} - if event[1] == "button_click" then - self.buttonList[event[2]].func() - end - end - end, - handleEvents = function(self, ...) - local event = {...} - if #event == 0 then event = {os.pullEvent()} end - if (self.side == "term" and event[1] == "mouse_click") or (self.side ~= "term" and event[1] == "monitor_touch" and event[2] == self.side) then - local clicked = self.clickMap[event[3]][event[4]] - if clicked and self.buttonList[clicked] then - return "button_click", clicked - end - end - return unpack(event) - end, - toggleButton = function(self, name, noDraw) - self.buttonList[name].active = not self.buttonList[name].active - if not noDraw then self:draw() end - end, - flash = function(self, name, duration) - self:toggleButton(name) - sleep(tonumber(duration) or 0.15) - self:toggleButton(name) - end, - rename = function(self, name, newName) - self.buttonList[name].label, newName = setupLabel(self.buttonList[name].xMax - self.buttonList[name].xMin + 1, self.buttonList[name].yMin, self.buttonList[name].yMax, newName) - if not self.buttonList[name] then error("no such button", 2) end - if name ~= newName then - self.buttonList[newName] = self.buttonList[name] - self.buttonList[name] = nil - for i = self.buttonList[newName].xMin, self.buttonList[newName].xMax do - for j = self.buttonList[newName].yMin, self.buttonList[newName].yMax do - self.clickMap[i][j] = newName - end - end - end - self:draw() - end, -} - -function new(monSide) - local buttonInstance = { - side = monSide or "term", - mon = monSide and peripheral.wrap(monSide) or term.current(), - buttonList = {}, - clickMap = {}, - } - local x, y = buttonInstance.mon.getSize() - for i = 1, x do - buttonInstance.clickMap[i] = {} - end - setmetatable(buttonInstance, {__index = Button}) - return buttonInstance -end - -touchpoint = {new = new} +local version = "1.01" +--[[ +The MIT License (MIT) + +Copyright (c) 2013 Lyqyd + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +Edited by DrunkenKas. + See Github: https://github.com/Kasra-G/ReactorController/#readme +--]] + +local function setupLabel(buttonLen, minY, maxY, name) + local labelTable = {} + if type(name) == "table" then + for i = 1, #name do + labelTable[i] = name[i] + end + name = name.label + elseif type(name) == "string" then + local buttonText = string.sub(name, 1, buttonLen - 2) + if #buttonText < #name then + buttonText = " "..buttonText.." " + else + local labelLine = string.rep(" ", math.floor((buttonLen - #buttonText) / 2))..buttonText + buttonText = labelLine..string.rep(" ", buttonLen - #labelLine) + end + for i = 1, maxY - minY + 1 do + if maxY == minY or i == math.floor((maxY - minY) / 2) + 1 then + labelTable[i] = buttonText + else + labelTable[i] = string.rep(" ", buttonLen) + end + end + end + return labelTable, name +end + +---@class Button +local Button = { + draw = function(self) + local old = term.redirect(self.mon) + term.setTextColor(colors.white) + term.setBackgroundColor(colors.black) + for name, buttonData in pairs(self.buttonList) do + if buttonData.active then + term.setBackgroundColor(buttonData.activeColor) + term.setTextColor(buttonData.activeText) + else + term.setBackgroundColor(buttonData.inactiveColor) + term.setTextColor(buttonData.inactiveText) + end + for i = buttonData.yMin, buttonData.yMax do + term.setCursorPos(buttonData.xMin, i) + term.write(buttonData.label[i - buttonData.yMin + 1]) + end + end + if old then + term.redirect(old) + else + term.restore() + end + end, + add = function(self, name, func, xMin, yMin, xMax, yMax, inactiveColor, activeColor, inactiveText, activeText) + local label, name = setupLabel(xMax - xMin + 1, yMin, yMax, name) + if self.buttonList[name] then error("button already exists", 2) end + local x, y = self.mon.getSize() + if xMin < 1 or yMin < 1 or xMax > x or yMax > y then error("button out of bounds", 2) end + self.buttonList[name] = { + func = func, + xMin = xMin, + yMin = yMin, + xMax = xMax, + yMax = yMax, + active = false, + inactiveColor = inactiveColor or colors.red, + activeColor = activeColor or colors.lime, + inactiveText = inactiveText or colors.white, + activeText = activeText or colors.white, + label = label, + } + for i = xMin, xMax do + for j = yMin, yMax do + if self.clickMap[i][j] ~= nil then + --undo changes + for k = xMin, xMax do + for l = yMin, yMax do + if self.clickMap[k][l] == name then + self.clickMap[k][l] = nil + end + end + end + self.buttonList[name] = nil + error("overlapping button", 2) + end + self.clickMap[i][j] = name + end + end + end, + remove = function(self, name) + if self.buttonList[name] then + local button = self.buttonList[name] + for i = button.xMin, button.xMax do + for j = button.yMin, button.yMax do + self.clickMap[i][j] = nil + end + end + self.buttonList[name] = nil + end + end, + run = function(self) + while true do + self:draw() + local event = {self:handleEvents(os.pullEvent(self.side == "term" and "mouse_click" or "monitor_touch"))} + if event[1] == "button_click" then + self.buttonList[event[2]].func() + end + end + end, + handleEvents = function(self, ...) + local event = {...} + if #event == 0 then event = {os.pullEvent()} end + if (self.side == "term" and event[1] == "mouse_click") or (self.side ~= "term" and event[1] == "monitor_touch" and event[2] == self.side) then + local clicked = self.clickMap[event[3]][event[4]] + if clicked and self.buttonList[clicked] then + return "button_click", clicked + end + end + return unpack(event) + end, + toggleButton = function(self, name, noDraw) + self.buttonList[name].active = not self.buttonList[name].active + if not noDraw then self:draw() end + end, + flash = function(self, name, duration) + self:toggleButton(name) + sleep(tonumber(duration) or 0.15) + self:toggleButton(name) + end, + rename = function(self, name, newName) + self.buttonList[name].label, newName = setupLabel(self.buttonList[name].xMax - self.buttonList[name].xMin + 1, self.buttonList[name].yMin, self.buttonList[name].yMax, newName) + if not self.buttonList[name] then error("no such button", 2) end + if name ~= newName then + self.buttonList[newName] = self.buttonList[name] + self.buttonList[name] = nil + for i = self.buttonList[newName].xMin, self.buttonList[newName].xMax do + for j = self.buttonList[newName].yMin, self.buttonList[newName].yMax do + self.clickMap[i][j] = newName + end + end + end + self:draw() + end, +} + +local function new(monSide) + local buttonInstance = { + side = monSide or "term", + mon = monSide and peripheral.wrap(monSide) or term.current(), + buttonList = {}, + clickMap = {}, + } + local x, y = buttonInstance.mon.getSize() + for i = 1, x do + buttonInstance.clickMap[i] = {} + end + setmetatable(buttonInstance, {__index = Button}) + return buttonInstance +end + +_G.touchpoint = {new = new} \ No newline at end of file diff --git a/versions.lua b/versions.lua new file mode 100644 index 0000000..8f4a793 --- /dev/null +++ b/versions.lua @@ -0,0 +1,5 @@ +_G.filesToUpdate = { + version = 0.52, + files = { + } +} \ No newline at end of file From 4b3cf8270cfaf0293fc7f09849af542a344a9140 Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Thu, 2 Jan 2025 17:45:50 -0500 Subject: [PATCH 02/38] update script refactors --- classes.lua | 133 ------- draw.lua | 74 ---- installer.lua | 13 - monitor.lua | 75 ---- reactorController.lua | 747 +++++++++++++++------------------------- startup | 6 +- usr/apis/touchpoint.lua | 5 +- versions.lua | 5 - 8 files changed, 287 insertions(+), 771 deletions(-) delete mode 100644 classes.lua delete mode 100644 draw.lua delete mode 100644 installer.lua delete mode 100644 monitor.lua delete mode 100644 versions.lua diff --git a/classes.lua b/classes.lua deleted file mode 100644 index 3927903..0000000 --- a/classes.lua +++ /dev/null @@ -1,133 +0,0 @@ ----@class Peripheral -local Peripheral = { - ---@type string - id = nil, - ---@type string - type = nil, - ---@type table - wrap = nil, -} ----comment ----@param id string ----@param type string ----@return Peripheral -local function newPeripheral(id, type) - - local peripheralInstance = { - id = id, - type = type, - wrap = peripheral.wrap(id), - } - setmetatable(peripheralInstance, {__index = Peripheral}) - return peripheralInstance -end - -_G.Peripheral = { - new = newPeripheral -} - ----@class ReactorStatistics -local ReactorStatistics = { - -} ----comment ----@param id string ----@param type string ----@return ReactorStatistics -local function newReactorStatistics(id, type) - - local reactorStatisticsInstance = { - } - setmetatable(reactorStatisticsInstance, {__index = ReactorStatistics}) - return reactorStatisticsInstance -end - -_G.ReactorStatistics = { - new = newReactorStatistics -} - --- local function newVector2(x, y) - --- local Vector2Instance = { --- x = x, --- y = y, --- } --- setmetatable(Vector2Instance, {__index = Vector2, __add = Vector2.__add, __sub = Vector2.__sub}) --- return Vector2Instance --- end - --- _G.Vector2 = { --- new = newVector2, --- zero = newVector2(0, 0), --- one = newVector2(1, 1), - --- __add = function (a, b) --- local t = type(b) --- if t == "table" then --- return Vector2.new(a.x + b.x, a.y + b.y) --- elseif t == "number" then --- return Vector2.new(a.x + b, a.y + b) --- end --- end, - --- __sub = function (a, b) --- local t = type(b) --- if t == "table" then --- return Vector2.new(a.x - b.x, a.y - b.y) --- elseif t == "number" then --- return Vector2.new(a.x - b, a.y - b) --- end --- end, --- } - ----@class Vector2 -local Vector2 = { - ---@type number - x = nil, - ---@type number - y = nil, -} - -Vector2.mt = {} - -Vector2.mt.__index = Vector2 - ----comment ----@param x number ----@param y number ----@return Vector2 -function Vector2.new(x, y) - local instance = {x = x, y = y} - return setmetatable(instance, Vector2.mt) -end - -Vector2.mt.__add = function(self, other) - local t = type(other) - if t == "table" then - return Vector2.new(self.x + other.x, self.y + other.y) - elseif t == "number" then - return Vector2.new(self.x + other, self.y + other) - end -end - -Vector2.mt.__sub = function(self, other) - local t = type(other) - if t == "table" then - return Vector2.new(self.x - other.x, self.y - other.y) - elseif t == "number" then - return Vector2.new(self.x - other, self.y - other) - end -end - -_G.Vector2 = { - new = Vector2.new, - zero = Vector2.new(0, 0), - one = Vector2.new(1, 1), -} - -local test = _G.Vector2.one + _G.Vector2.one -print(test.x, test.y) -test = test + 1 -print(test.x, test.y) -test = test - 2 -print(test.x, test.y) \ No newline at end of file diff --git a/draw.lua b/draw.lua deleted file mode 100644 index 72b1f71..0000000 --- a/draw.lua +++ /dev/null @@ -1,74 +0,0 @@ - ----comment ----@param mon table ----@param drawFunction function -local function executeAndRestoreMonitorSettings(mon, drawFunction) - if (mon == nil) then - error("Error! Mon is nil") - return - end - - local originalCursorPos = Vector2.new(mon.getCursorPos()) - local originalBackgroundColor = mon.getBackgroundColor() - local originalTextColor = mon.getTextColor() - - drawFunction() - - mon.setCursorPos(originalCursorPos.x, originalCursorPos.y) - mon.setBackgroundColor(originalBackgroundColor) - mon.setTextColor(originalTextColor) -end - ----comment Draws a filled rectangle ----@param mon table ----@param color any ----@param offset Vector2 ----@param size Vector2 -local function drawFilledRectangle(mon, color, offset, size) - executeAndRestoreMonitorSettings( - mon, - function () - local horizLine = string.rep(" ", size.x) - mon.setBackgroundColor(color) - for i=0, size.y - 1 do - mon.setCursorPos(offset.x, offset.y + i) - mon.write(horizLine) - end - end - ) -end - ----comment Draws a rectangle with a fill color and border ----@param mon table ----@param innerColor any ----@param outerColor any ----@param offset Vector2 ----@param size Vector2 -local function drawRectangle(mon, innerColor, outerColor, offset, size) - drawFilledRectangle(mon, outerColor, offset, size) - drawFilledRectangle(mon, innerColor, offset + 1, size - 2) -end - ----comment Draws text on the screen ----@param mon table ----@param text string ----@param pos Vector2 ----@param backgroundColor any ----@param textColor any -local function drawText(mon, text, pos, backgroundColor, textColor) - executeAndRestoreMonitorSettings( - mon, - function () - mon.setCursorPos(pos.x, pos.y) - mon.setBackgroundColor(backgroundColor) - mon.setTextColor(textColor) - mon.write(text) - end - ) -end - -_G.DrawUtil = { - drawRectangle = drawRectangle, - drawFilledRectangle = drawFilledRectangle, - drawText = drawText, -} \ No newline at end of file diff --git a/installer.lua b/installer.lua deleted file mode 100644 index f67c17b..0000000 --- a/installer.lua +++ /dev/null @@ -1,13 +0,0 @@ ---pastebin run kSkwEchg ---Github: https://github.com/Kasra-G/ReactorController#readme - ---Overwrite startup file -local file = fs.open("startup", "w") -file.writeLine("shell.run(\"update_reactor.lua\")") -file.writeLine("while (true) do") -file.writeLine(" shell.run(\"reactorController.lua\")") -file.writeLine(" sleep(2)") -file.writeLine("end") -file.close() -shell.run("pastebin get w6vVtrLb update_reactor.lua") -shell.run("reboot") diff --git a/monitor.lua b/monitor.lua deleted file mode 100644 index ab91f66..0000000 --- a/monitor.lua +++ /dev/null @@ -1,75 +0,0 @@ ----@class Monitor -local Monitor = { - ---@type string - name = nil, - ---@type table - mon = nil, - ---@type table - touch = nil, - ---@type Vector2 - size = nil, - ---@type integer - oo = nil, - ---@type integer - dim = nil, - - ---comment - ---@param self Monitor - clear = function(self) - self.mon.setBackgroundColor(colors.black) - self.mon.clear() - self.mon.setTextScale(0.5) - self.mon.setCursorPos(1,1) - end, - - ---@param self Monitor - drawScene = function(self) - if (invalidDim) then - self.mon.write("Invalid Monitor Dimensions") - return - end - - if (displayingGraphMenu) then - drawGraphButtons() - end - drawControls() - drawStatus() - drawStatistics() - self.touch:draw() - end, - - ---comment - ---@param self Monitor - ---@param reactorStats ReactorStatistics - update = function(self, reactorStats) - - end -} - ----comment ----@param peripheralId string ----@return Monitor -local function new(peripheralId) - local mon = peripheral.wrap(peripheralId) - local touch = touchpoint.new(peripheralId) - local sizex, sizey = mon.getSize() - local oo = sizey - 37 - local dim = sizex - 33 - - local monitorInstance = { - name = peripheralId, - mon = mon, - touch = touch, - sizex = sizex, - sizey = sizey, - oo = oo, - dim = dim, - } - - setmetatable(monitorInstance, {__index = Monitor}) - return monitorInstance -end - -_G.Monitor = { - new = new -} \ No newline at end of file diff --git a/reactorController.lua b/reactorController.lua index db9617b..d600a68 100644 --- a/reactorController.lua +++ b/reactorController.lua @@ -28,14 +28,10 @@ THE SOFTWARE. ]] dofile("/usr/apis/touchpoint.lua") -dofile("classes.lua") -dofile("draw.lua") -dofile("graph.lua") -dofile("monitor.lua") local reactorVersion, reactor local mon, monSide -local sizex, sizey, dividerXCoord, oo, offy +local sizex, sizey, dim, oo, offy local btnOn, btnOff, invalidDim local minb, maxb local rod, rfLost @@ -44,7 +40,7 @@ local fuelTemp, caseTemp, fuelUsage, waste, capacity = 0,0,0,0,1 local t local displayingGraphMenu = false -local SECONDS_TO_AVERAGE = 2 +local secondsToAverage = 2 local averageStoredThisTick = 0 local averageLastRFT = 0 @@ -55,8 +51,6 @@ local averageFuelTemp = 0 local averageCaseTemp = 0 local averageRfLost = 0 -local MINIMUM_DIVIDER_X_VALUE = 3 - -- table of which graphs to draw local graphsToDraw = {} @@ -79,20 +73,68 @@ local XOffs = {96, true}, } +-- Draw a box with no fill +local function drawBox(size, xoff, yoff, color) + if (monSide == nil) then + return + end + local x,y = mon.getCursorPos() + mon.setBackgroundColor(color) + local horizLine = string.rep(" ", size[1]) + mon.setCursorPos(xoff + 1, yoff + 1) + mon.write(horizLine) + mon.setCursorPos(xoff + 1, yoff + size[2]) + mon.write(horizLine) + + -- Draw vertical lines + for i=0, size[2] - 1 do + mon.setCursorPos(xoff + 1, yoff + i + 1) + mon.write(" ") + mon.setCursorPos(xoff + size[1], yoff + i +1) + mon.write(" ") + end + mon.setCursorPos(x,y) + mon.setBackgroundColor(colors.black) +end + +--Draw a filled box +local function drawFilledBox(size, xoff, yoff, colorOut, colorIn) + if (monSide == nil) then + return + end + local horizLine = string.rep(" ", size[1] - 2) + drawBox(size, xoff, yoff, colorOut) + local x,y = mon.getCursorPos() + mon.setBackgroundColor(colorIn) + for i=2, size[2] - 1 do + mon.setCursorPos(xoff + 2, yoff + i) + mon.write(horizLine) + end + mon.setBackgroundColor(colors.black) + mon.setCursorPos(x,y) +end + +--Draws text on the screen +local function drawText(text, x1, y1, backColor, textColor) + if (monSide == nil) then + return + end + local x, y = mon.getCursorPos() + mon.setCursorPos(x1, y1) + mon.setBackgroundColor(backColor) + mon.setTextColor(textColor) + mon.write(text) + mon.setTextColor(colors.white) + mon.setBackgroundColor(colors.black) + mon.setCursorPos(x,y) +end + --Helper method for adding buttons -local function addButton(touch, name, callBack, offset, size, color1, color2) - local buttonTopLeftCorner = offset + Vector2.one - local buttonBottomRightCorner = offset + size - touch:add( - name, - callBack, - buttonTopLeftCorner.x, - buttonTopLeftCorner.y, - buttonBottomRightCorner.x, - buttonBottomRightCorner.y, - color1, - color2 - ) +local function addButt(name, callBack, size, xoff, yoff, color1, color2) + t:add(name, callBack, + xoff + 1, yoff + 1, + size[1] + xoff, size[2] + yoff, + color1, color2) end local function minAdd10() @@ -133,74 +175,24 @@ local function addButtons() if (sizey == 24) then oo = 1 end - local buttonSize = Vector2.new(8, 3) - local offsetOnOff = Vector2.new(dividerXCoord + 5, 3 + oo) - - addButton( - t, - "On", - turnOn, - offsetOnOff, - buttonSize, - colors.red, - colors.lime - ) - - addButton( - t, - "Off", - turnOff, - offsetOnOff + Vector2.new(12, 0), - buttonSize, - colors.red, - colors.lime - ) - + addButt("On", turnOn, {8, 3}, dim + 7, 3 + oo, + colors.red, colors.lime) + addButt("Off", turnOff, {8, 3}, dim + 19, 3 + oo, + colors.red, colors.lime) if (btnOn) then t:toggleButton("On", true) else t:toggleButton("Off", true) end - - local offset = Vector2.new(dividerXCoord, oo) - if (sizey > 24) then - addButton( - t, - "+ 10", - minAdd10, - offset + Vector2.new(5, 14), - buttonSize, - colors.purple, - colors.pink - ) - addButton( - t, - " + 10 ", - maxAdd10, - offset + Vector2.new(17, 14), - buttonSize, - colors.magenta, - colors.pink - ) - addButton( - t, - "- 10", - minSub10, - offset + Vector2.new(5, 18), - buttonSize, - colors.purple, - colors.pink - ) - addButton( - t, - " - 10 ", - maxSub10, - offset + Vector2.new(17, 18), - buttonSize, - colors.magenta, - colors.pink - ) + addButt("+ 10", minAdd10, {8, 3}, dim + 7, 14 + oo, + colors.purple, colors.pink) + addButt(" + 10 ", maxAdd10, {8, 3}, dim + 19, 14 + oo, + colors.magenta, colors.pink) + addButt("- 10", minSub10, {8, 3}, dim + 7, 18 + oo, + colors.purple, colors.pink) + addButt(" - 10 ", maxSub10, {8, 3}, dim + 19, 18 + oo, + colors.magenta, colors.pink) end end @@ -248,7 +240,7 @@ end local function getAvailableXOff() for i,v in pairs(XOffs) do - if (v[2] and v[1] < dividerXCoord - 1) then + if (v[2] and v[1] < dim) then v[2] = false return v[1] end @@ -299,320 +291,216 @@ end local function addGraphButtons() offy = oo - 14 - local graphButtonSize = Vector2.new(20, 3) - local graphButtonOffset = Vector2.new(dividerXCoord + 5, offy) - for i,graphName in pairs(graphs) do - - addButton( - t, - graphName, - function() toggleGraph(graphName) end, - graphButtonOffset + Vector2.new(0, i * 3 - 1), - graphButtonSize, - colors.red, - colors.lime - ) - if (graphsToDraw[graphName] ~= nil) then - t:toggleButton(graphName, true) + for i,v in pairs(graphs) do + addButt(v, function() toggleGraph(v) end, {20, 3}, + dim + 7, offy + i * 3 - 1, + colors.red, colors.lime) + if (graphsToDraw[v] ~= nil) then + t:toggleButton(v, true) end end end -local function drawGraphButtons(mon, offset, size) - - DrawUtil.drawRectangle(mon, colors.black, colors.orange, offset, size) - local textPos = offset + Vector2.new(4, 0) - DrawUtil.drawText( - mon, - " Graph Controls ", - textPos, - colors.black, - colors.orange - ) +local function drawGraphButtons() + drawBox({sizex - dim - 3, oo - offy - 1}, + dim + 2, offy, colors.orange) + drawText(" Graph Controls ", + dim + 7, offy + 1, + colors.black, colors.orange) end -local function drawEnergyBuffer(mon, offset, graphSize, drawPercentLabelOnRight) - DrawUtil.drawText(mon, "Energy Buffer", offset, colors.black, colors.orange) - DrawUtil.drawRectangle(mon, colors.red, colors.gray, offset + Vector2.new(0, 1), graphSize) +local function drawEnergyBuffer(xoff) + local srf = sizey - 9 + local off = xoff + local right = off + 19 < dim + local poff = right and off + 15 or off - 6 - local energyBufferMaxHeight = graphSize.y - 2 - local unitEnergyLevel = getPercPower() / 100 - local energyBufferHeight = math.floor(unitEnergyLevel * energyBufferMaxHeight + 0.5) + drawBox({15, srf + 2}, off - 1, 4, colors.gray) + local pwr = math.floor(getPercPower() / 100 + * (srf)) + drawFilledBox({13, srf}, off, 5, + colors.red, colors.red) local rndpw = rnd(getPercPower(), 2) - - local energyBufferColor - if rndpw < maxb and rndpw > minb then - energyBufferColor = colors.green - elseif rndpw >= maxb then - energyBufferColor = colors.orange - elseif rndpw <= minb then - energyBufferColor = colors.blue - end - - local energyBufferTipOffset = offset + Vector2.new(1, 2 + energyBufferMaxHeight - energyBufferHeight) - local energyBufferSize = Vector2.new(graphSize.x - 2, energyBufferHeight) - - DrawUtil.drawFilledRectangle(mon, energyBufferColor, energyBufferTipOffset, energyBufferSize) - - local energyBufferTextOffset = energyBufferTipOffset - local rfLabelBackgroundColor = energyBufferColor - - if energyBufferHeight <= 0 then - energyBufferTextOffset = energyBufferTipOffset + Vector2.new(0, -1) - rfLabelBackgroundColor = colors.red - end - - local percentLabelXOffset = offset.x - 6 - if drawPercentLabelOnRight then - percentLabelXOffset = offset.x + 15 - end - - DrawUtil.drawText( - mon, - string.format(drawPercentLabelOnRight and "%.2f%%" or "%5.2f%%", rndpw), - Vector2.new(percentLabelXOffset, energyBufferTextOffset.y), - colors.black, - energyBufferColor - ) - DrawUtil.drawText( - mon, - format(averageStoredThisTick).."RF", - energyBufferTextOffset, - rfLabelBackgroundColor, - colors.black - ) -end - -local function drawControlGraph(mon, offset, size, averageRod) - local unitRodLevel = averageRod / 100 - local controlRodMaxPixelHeight = size.y - 2 - local controlRodPixelHeight = math.ceil(unitRodLevel * controlRodMaxPixelHeight) - - DrawUtil.drawText( - mon, - "Control Level", - offset + Vector2.new(1, 0), - colors.black, - colors.orange - - ) - DrawUtil.drawRectangle( - mon, - colors.yellow, - colors.gray, - offset + Vector2.new(0, 1), - size - ) - DrawUtil.drawFilledRectangle( - mon, - colors.white, - offset + Vector2.new(3, 2), - Vector2.new(9, controlRodPixelHeight) - ) - - local controlRodLevelTextPos, color - if controlRodPixelHeight > 0 then - color = colors.white - controlRodLevelTextPos = offset + Vector2.new(4, 1 + controlRodPixelHeight) - else - color = colors.yellow - controlRodLevelTextPos = offset + Vector2.new(4, 2) - end - - DrawUtil.drawText( - mon, - string.format("%6.2f%%", averageRod), - controlRodLevelTextPos, - color, - colors.black - ) -end - -local function drawTemperatures(mon, offset, size) - - DrawUtil.drawRectangle(mon, colors.black, colors.gray, offset + Vector2.new(1, 1), size) - - local CASE_TEMP_COLOR = colors.lightBlue - local FUEL_TEMP_COLOR = colors.magenta - local BACKGROUND_COLOR = colors.black - - local assumedMaxCaseTemperature = 3000 - local assumedMaxFuelTemperature = 3000 - local temperatureMaxHeight = size.y - 2 + local color = (rndpw < maxb and rndpw > minb) and colors.green + or (rndpw >= maxb and colors.orange or colors.blue) + if (pwr > 0) then + drawFilledBox({13, pwr + 1}, off, srf + 4 - pwr, + color, color) + end + --drawPoint(off + 14, srf + 5 - pwr, pwr > 0 and color or colors.red) + drawText(string.format(right and "%.2f%%" or "%5.2f%%", rndpw), poff, srf + 5 - pwr, + colors.black, color) + drawText("Energy Buffer", off + 1, 4, + colors.black, colors.orange) + drawText(format(averageStoredThisTick).."RF", off + 1, srf + 5 - pwr, + pwr > 0 and color or colors.red, colors.black) +end + +local function drawControlLevel(xoff) + local srf = sizey - 9 + local off = xoff + drawBox({15, srf + 2}, off - 1, 4, colors.gray) + drawFilledBox({13, srf}, off, 5, + colors.yellow, colors.yellow) + local rodTr = math.floor(averageRod / 100 + * (srf)) + drawText("Control Level", off + 1, 4, + colors.black, colors.orange) + if (rodTr > 0) then + drawFilledBox({9, rodTr}, off + 2, 5, + colors.white, colors.white) + end + drawText(string.format("%6.2f%%", averageRod), off + 4, rodTr > 0 and rodTr + 5 or 6, + rodTr > 0 and colors.white or colors.yellow, colors.black) + +end + +local function drawTemperatures(xoff) + local srf = sizey - 9 + local off = xoff + drawBox({15, srf + 2}, off, 4, colors.gray) + --drawFilledBox({12, srf}, off, 5, + -- colors.red, colors.red) local tempUnit = (reactorVersion == "Bigger Reactors") and "K" or "C" local tempFormat = "%4s"..tempUnit - DrawUtil.drawText(mon, "Temperatures", offset + Vector2.new(2, 0), BACKGROUND_COLOR, colors.orange) - DrawUtil.drawFilledRectangle(mon, colors.gray, offset + Vector2.new(8, 2), Vector2.new(1, temperatureMaxHeight)) - - -- case temp - DrawUtil.drawText(mon, "Case", offset + Vector2.new(3, 1), colors.gray, colors.lightBlue) - local caseUnit = math.min(averageCaseTemp / assumedMaxCaseTemperature, 1) - local caseTempHeight = math.floor(caseUnit * temperatureMaxHeight + 0.5) - - local caseTempOffset = offset + Vector2.new(2, 2 + temperatureMaxHeight - caseTempHeight) - local caseTempSize = Vector2.new(6, caseTempHeight) - - DrawUtil.drawFilledRectangle(mon, CASE_TEMP_COLOR, caseTempOffset, caseTempSize) - - local caseTempTextOffset = caseTempOffset - local caseTempTextBackgroundColor = CASE_TEMP_COLOR - local caseTempTextColor = BACKGROUND_COLOR - - if caseTempHeight <= 0 then - caseTempTextOffset = caseTempOffset + Vector2.new(0, -1) - caseTempTextColor, caseTempTextBackgroundColor = caseTempTextBackgroundColor, caseTempTextColor - end - - local caseRnd = math.floor(averageCaseTemp + 0.5) - DrawUtil.drawText(mon, string.format(tempFormat, caseRnd..""), caseTempTextOffset, caseTempTextBackgroundColor, caseTempTextColor) - - -- fuel temp - DrawUtil.drawText(mon, "Fuel", offset + Vector2.new(10, 1), colors.gray, colors.lightBlue) - local fuelUnit = math.min(averageFuelTemp / assumedMaxFuelTemperature, 1) - local fuelTempHeight = math.floor(fuelUnit * temperatureMaxHeight + 0.5) - - local fuelTempOffset = offset + Vector2.new(9, 2 + temperatureMaxHeight - fuelTempHeight) - local fuelTempSize = Vector2.new(6, fuelTempHeight) - - DrawUtil.drawFilledRectangle(mon, FUEL_TEMP_COLOR, fuelTempOffset, fuelTempSize) - - local fuelTempTextOffset = fuelTempOffset - local fuelTempTextBackgroundColor = FUEL_TEMP_COLOR - local fuelTempTextColor = BACKGROUND_COLOR - - if fuelTempHeight <= 0 then - fuelTempTextOffset = fuelTempOffset + Vector2.new(0, -1) - fuelTempTextColor, fuelTempTextBackgroundColor = fuelTempTextBackgroundColor, fuelTempTextColor + local fuelRnd = math.floor(averageFuelTemp) + local caseRnd = math.floor(averageCaseTemp) + local fuelTr = math.floor(fuelRnd / 2000 + * (srf)) + local caseTr = math.floor(caseRnd / 2000 + * (srf)) + drawText(" Case ", off + 2, 5, + colors.gray, colors.lightBlue) + drawText(" Fuel ", off + 9, 5, + colors.gray, colors.magenta) + if (fuelTr > 0) then + fuelTr = math.min(fuelTr, srf) + drawFilledBox({6, fuelTr}, off + 8, srf + 5 - fuelTr, + colors.magenta, colors.magenta) + + drawText(string.format(tempFormat, fuelRnd..""), + off + 10, srf + 6 - fuelTr, + colors.magenta, colors.black) + else + drawText(string.format(tempFormat, fuelRnd..""), + off + 10, srf + 5, + colors.black, colors.magenta) + end + + if (caseTr > 0) then + caseTr = math.min(caseTr, srf) + drawFilledBox({6, caseTr}, off + 1, srf + 5 - caseTr, + colors.lightBlue, colors.lightBlue) + drawText(string.format(tempFormat, caseRnd..""), + off + 3, srf + 6 - caseTr, + colors.lightBlue, colors.black) + else + drawText(string.format(tempFormat, caseRnd..""), + off + 3, srf + 5, + colors.black, colors.lightBlue) end - local fuelRnd = math.floor(averageFuelTemp + 0.5) - DrawUtil.drawText(mon, string.format(tempFormat, fuelRnd..""), fuelTempTextOffset, fuelTempTextBackgroundColor, fuelTempTextColor) + drawText("Temperatures", off + 2, 4, + colors.black, colors.orange) + drawBox({1, srf}, off + 7, 5, + colors.gray) end -local function drawGraph(name, graphOffset, graphSize) +local function drawGraph(name, offset) if (name == "Energy Buffer") then - local drawPercentLabelOnRight = graphOffset.x + 19 < dividerXCoord - 1 - drawEnergyBuffer(mon, graphOffset, graphSize, drawPercentLabelOnRight) + drawEnergyBuffer(offset) elseif (name == "Control Level") then - drawControlGraph(mon, graphOffset, graphSize, averageRod) + drawControlLevel(offset) elseif (name == "Temperatures") then - drawTemperatures(mon, graphOffset, graphSize) + drawTemperatures(offset) end end -local function drawGraphs(mon, graphsToDraw, dividerXCoord, offset, size) - DrawUtil.drawRectangle(mon, colors.black, colors.lightBlue, offset, size) - local label = " Reactor Graphs " - DrawUtil.drawText( - mon, - label, - offset + Vector2.new(dividerXCoord - (#label + 5) - 1, 0), - colors.black, - colors.lightBlue - ) - - local graphSize = Vector2.new(15, sizey - 7) - local graphYOffset = 4 - for graphName, graphXOffset in pairs(graphsToDraw) do - if (graphXOffset + graphSize.x < dividerXCoord) then - drawGraph(graphName, Vector2.new(graphXOffset, graphYOffset), graphSize) +local function drawGraphs() + for i,v in pairs(graphsToDraw) do + if (v + 15 < dim) then + drawGraph(i,v) end end end -local function drawControls(mon, offset, size, drawBufferVisualization) - - DrawUtil.drawRectangle(mon, colors.black, colors.cyan, offset, size) - DrawUtil.drawText(mon, " Reactor Controls ", offset + Vector2.new(4, 0), colors.black, colors.cyan) - - local reactorOnOffLabel = "Reactor "..(btnOn and "Online" or "Offline") - local reactorOnOffLabelColor = btnOn and colors.green or colors.red - DrawUtil.drawText(mon, reactorOnOffLabel, offset + Vector2.new(7, 2), colors.black, reactorOnOffLabelColor) - - if not drawBufferVisualization then +local function drawStatus() + if (dim <= -1) then return end - - local bufferMinInPixels = minb / 5 - local bufferMaxInPixels = maxb / 5 - local bufferRangePixelWidth = bufferMaxInPixels - bufferMinInPixels - - local bufferVisualOffset = offset + Vector2.new(5, 8) - local bufferVisualSize = Vector2.new(20, 3) - - DrawUtil.drawText(mon, "Buffer Target Range", bufferVisualOffset + Vector2.new(0, -1), colors.black, colors.orange) - DrawUtil.drawFilledRectangle(mon, colors.red, bufferVisualOffset, bufferVisualSize) - DrawUtil.drawFilledRectangle(mon, colors.green, bufferVisualOffset + Vector2.new(bufferMinInPixels, 0), Vector2.new(bufferRangePixelWidth, 3)) - - DrawUtil.drawText( - mon, - string.format("%3s", minb.."%"), - bufferVisualOffset + Vector2.new(bufferMinInPixels - 3, bufferVisualSize.y), - colors.black, - colors.purple - ) - DrawUtil.drawText( - mon, - maxb.."%", - bufferVisualOffset + Vector2.new(bufferMaxInPixels, bufferVisualSize.y), - colors.black, - colors.magenta - ) - DrawUtil.drawText(mon, "Min", offset + Vector2.new(7, 13), colors.black, colors.purple) - DrawUtil.drawText(mon, "Max", offset + Vector2.new(19, 13), colors.black, colors.purple) + drawBox({dim, sizey - 2}, + 1, 1, colors.lightBlue) + drawText(" Reactor Graphs ", dim - 18, 2, + colors.black, colors.lightBlue) + drawGraphs() end -local function drawStatistics(mon, offset, size) - DrawUtil.drawRectangle(mon, colors.black, colors.blue, offset, size) - DrawUtil.drawText( - mon, - " Reactor Statistics ", - offset + Vector2.new(4, 0), - colors.black, - colors.blue - ) - - DrawUtil.drawText( - mon, - "Generating : "..format(averageLastRFT).."RF/t", - offset + Vector2.new(2, 2), - colors.black, - colors.green - ) - - DrawUtil.drawText( - mon, - "RF Drain "..(averageStoredThisTick <= averageLastRFT and "> " or ": ")..format(averageRfLost).."RF/t", - offset + Vector2.new(2, 4), - colors.black, - colors.red - ) - - DrawUtil.drawText( - mon, - "Efficiency : "..format(getEfficiency()).."RF/B", - offset + Vector2.new(2, 6), - colors.black, - colors.green - ) - - DrawUtil.drawText( - mon, - "Fuel Usage : "..format(averageFuelUsage).."B/t", - offset + Vector2.new(2, 8), - colors.black, - colors.green - ) +local function drawControls() + if (sizey == 24) then + drawBox({sizex - dim - 3, 9}, dim + 2, oo, + colors.cyan) + drawText(" Reactor Controls ", dim + 7, oo + 1, + colors.black, colors.cyan) + drawText("Reactor "..(btnOn and "Online" or "Offline"), + dim + 10, 3 + oo, + colors.black, btnOn and colors.green or colors.red) + return + end - DrawUtil.drawText( - mon, - "Waste : "..string.format("%7d mB", waste), - offset + Vector2.new(2, 10), - colors.black, - colors.green - ) + drawBox({sizex - dim - 3, 23}, dim + 2, oo, + colors.cyan) + drawText(" Reactor Controls ", dim + 7, oo + 1, + colors.black, colors.cyan) + drawFilledBox({20, 3}, dim + 7, 8 + oo, + colors.red, colors.red) + drawFilledBox({(maxb - minb) / 5, 3}, + dim + 7 + minb / 5, 8 + oo, + colors.green, colors.green) + drawText(string.format("%3s", minb.."%"), dim + 6 + minb / 5, 12 + oo, + colors.black, colors.purple) + drawText(maxb.."%", dim + 8 + maxb / 5, 12 + oo, + colors.black, colors.magenta) + drawText("Buffer Target Range", dim + 8, 8 + oo, + colors.black, colors.orange) + drawText("Min", dim + 10, 14 + oo, + colors.black, colors.purple) + drawText("Max", dim + 22, 14 + oo, + colors.black, colors.magenta) + drawText("Reactor ".. (btnOn and "Online" or "Offline"), + dim + 10, 3 + oo, + colors.black, btnOn and colors.green or colors.red) +end + +local function drawStatistics() + local oS = sizey - 13 + drawBox({sizex - dim - 3, sizey - oS - 1}, dim + 2, oS, + colors.blue) + drawText(" Reactor Statistics ", dim + 7, oS + 1, + colors.black, colors.blue) + + --statistics + drawText("Generating : " + ..format(averageLastRFT).."RF/t", dim + 5, oS + 3, + colors.black, colors.green) + drawText("RF Drain " + ..(averageStoredThisTick <= averageLastRFT and "> " or ": ") + ..format(averageRfLost) + .."RF/t", dim + 5, oS + 5, + colors.black, colors.red) + drawText("Efficiency : " + ..format(getEfficiency()).."RF/B", + dim + 5, oS + 7, + colors.black, colors.green) + drawText("Fuel Usage : " + ..format(averageFuelUsage) + .."B/t", dim + 5, oS + 9, + colors.black, colors.green) + drawText("Waste : " + ..string.format("%7d mB", waste), + dim + 5, oS + 11, + colors.black, colors.green) end --Draw a scene @@ -625,94 +513,20 @@ local function drawScene() return end - local offset, size - if (displayingGraphMenu) then - offset = Vector2.new(dividerXCoord + 1, offy + 1) - size = Vector2.new(30, 13) - drawGraphButtons(mon, offset, size) - end - - offset = Vector2.new(dividerXCoord + 1, oo + 1) - size = Vector2.new(30, 23) - local drawBufferVisualization = true - if (sizey <= 24) then - size = Vector2.new(30, 9) - drawBufferVisualization = false + drawGraphButtons() end - drawControls(mon, offset, size, drawBufferVisualization) - - if (dividerXCoord > MINIMUM_DIVIDER_X_VALUE) then - offset = Vector2.new(2, 2) - size = Vector2.new(dividerXCoord - 2, sizey - 2) - drawGraphs(mon, graphsToDraw, dividerXCoord, offset, size) - end - - offset = Vector2.new(dividerXCoord + 1, sizey - 12) - size = Vector2.new(30, 12) - drawStatistics(mon, offset, size) - + drawControls() + drawStatus() + drawStatistics() t:draw() end -local function getAllPeripheralIdsForType(targetType) - ---@type string[] - local peripheralIds = {} - for _, id in pairs(peripheral.getNames()) do - if (peripheral.getType(id) == targetType) then - table.insert(peripheralIds, id) - end - end - return peripheralIds -end - ----@type table -local monitors = {} - -local function initMon2(id) - local monitor = Monitor.new(id) - monSide = id - mon = monitor.mon - monitor:clear() - t = monitor.touch - - sizex, sizey = mon.getSize() - oo = sizey - 37 - dividerXCoord = sizex - 31 - - if (sizex == 36) then - dividerXCoord = MINIMUM_DIVIDER_X_VALUE - end - displayingGraphMenu = pcall(function() addGraphButtons() end) - _, invalidDim = pcall(function() addButtons() end) - - return monitor -end - -local function updateMonitors() - for _, monitor in pairs(monitors) do - ---@type ReactorStatistics - local reactorStats = {} - monitor:update(reactorStats) - end -end - -local function initMonitors() - monitors = {} - local ids = getAllPeripheralIdsForType("monitor") - - for _, id in pairs(ids) do - monitors[id] = initMon2(id) - end -end --- DELETE LATER! -initMonitors() - --returns the side that a given peripheral type is connected to -local function getPeripheral(targetType) - for _, name in pairs(peripheral.getNames()) do - if (peripheral.getType(name) == targetType) then - return name +local function getPeripheral(name) + for i,v in pairs(peripheral.getNames()) do + if (peripheral.getType(v) == name) then + return v end end return "" @@ -726,8 +540,7 @@ local function initMon() return end - local monitor = Monitor.new(monSide) - mon = monitor.mon + mon = peripheral.wrap(monSide) if mon == nil then monSide = nil @@ -738,13 +551,24 @@ local function initMon() t = touchpoint.new(monSide) sizex, sizey = mon.getSize() oo = sizey - 37 - dividerXCoord = sizex - 31 + dim = sizex - 33 if (sizex == 36) then - dividerXCoord = MINIMUM_DIVIDER_X_VALUE + dim = -1 + end + if (pcall(addGraphButtons)) then + displayingGraphMenu = true + else + t = touchpoint.new(monSide) + displayingGraphMenu = false + end + local rtn = pcall(addButtons) + if (not rtn) then + t = touchpoint.new(monSide) + invalidDim = true + else + invalidDim = false end - displayingGraphMenu = pcall(function() addGraphButtons() end) - _, invalidDim = pcall(function() addButtons() end) end local function setRods(level) @@ -754,8 +578,10 @@ local function setRods(level) end local function lerp(start, finish, t) + -- Ensure t is in the range [0, 1] t = math.max(0, math.min(1, t)) + -- Calculate the linear interpolation return (1 - t) * start + t * finish end @@ -901,7 +727,7 @@ local function updateStats() table.insert(caseTempValues, caseTemp) table.insert(rfLostValues, rfLost) - local maxIterations = 20 * SECONDS_TO_AVERAGE + local maxIterations = 20 * secondsToAverage while #storedThisTickValues > maxIterations do table.remove(storedThisTickValues, 1) table.remove(lastRFTValues, 1) @@ -981,7 +807,7 @@ local function loadFromConfig() btnOn = true end sizex, sizey = 100, 52 - dividerXCoord = sizex - 31 + dim = sizex - 33 oo = sizey - 37 enableGraph("Energy Buffer") enableGraph("Control Level") @@ -1030,7 +856,6 @@ local function loop() ) local handleResize = function(event) if (event[1] == "monitor_resize") then - local peripheralId = event[2] initMon() end end @@ -1044,11 +869,7 @@ local function loop() end end while (true) do - local event = { os.pullEvent() } - - if monSide ~= nil then - event = { t:handleEvents(unpack(event)) } - end + local event = (monSide == nil) and { os.pullEvent() } or { t:handleEvents() } updateStatsTick(event) redrawTick(event) diff --git a/startup b/startup index f6cb9c1..2a451b3 100644 --- a/startup +++ b/startup @@ -1,5 +1 @@ -shell.run("update_reactor.lua") -while (true) do - shell.run("reactorController.lua") - sleep(2) -end +shell.run("/src/scripts/main.lua") diff --git a/usr/apis/touchpoint.lua b/usr/apis/touchpoint.lua index 8801092..155de0f 100644 --- a/usr/apis/touchpoint.lua +++ b/usr/apis/touchpoint.lua @@ -52,7 +52,6 @@ local function setupLabel(buttonLen, minY, maxY, name) return labelTable, name end ----@class Button local Button = { draw = function(self) local old = term.redirect(self.mon) @@ -169,7 +168,7 @@ local Button = { end, } -local function new(monSide) +function new(monSide) local buttonInstance = { side = monSide or "term", mon = monSide and peripheral.wrap(monSide) or term.current(), @@ -184,4 +183,4 @@ local function new(monSide) return buttonInstance end -_G.touchpoint = {new = new} \ No newline at end of file +touchpoint = {new = new} \ No newline at end of file diff --git a/versions.lua b/versions.lua deleted file mode 100644 index 8f4a793..0000000 --- a/versions.lua +++ /dev/null @@ -1,5 +0,0 @@ -_G.filesToUpdate = { - version = 0.52, - files = { - } -} \ No newline at end of file From 01c308ddcb8c60c0cded294b203dfe5184492f7e Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Thu, 2 Jan 2025 17:49:33 -0500 Subject: [PATCH 03/38] forgot to commit src --- src/classes/classes.lua | 133 +++++ src/classes/monitor.lua | 72 +++ src/classes/navbar.lua | 39 ++ src/classes/page.lua | 67 +++ src/classes/touchpoint.lua | 187 ++++++ src/scripts/controller.lua | 1113 ++++++++++++++++++++++++++++++++++++ src/scripts/installer.lua | 13 + src/scripts/main.lua | 13 + src/scripts/update.lua | 101 ++++ src/util/draw.lua | 74 +++ 10 files changed, 1812 insertions(+) create mode 100644 src/classes/classes.lua create mode 100644 src/classes/monitor.lua create mode 100644 src/classes/navbar.lua create mode 100644 src/classes/page.lua create mode 100644 src/classes/touchpoint.lua create mode 100644 src/scripts/controller.lua create mode 100644 src/scripts/installer.lua create mode 100644 src/scripts/main.lua create mode 100644 src/scripts/update.lua create mode 100644 src/util/draw.lua diff --git a/src/classes/classes.lua b/src/classes/classes.lua new file mode 100644 index 0000000..3927903 --- /dev/null +++ b/src/classes/classes.lua @@ -0,0 +1,133 @@ +---@class Peripheral +local Peripheral = { + ---@type string + id = nil, + ---@type string + type = nil, + ---@type table + wrap = nil, +} +---comment +---@param id string +---@param type string +---@return Peripheral +local function newPeripheral(id, type) + + local peripheralInstance = { + id = id, + type = type, + wrap = peripheral.wrap(id), + } + setmetatable(peripheralInstance, {__index = Peripheral}) + return peripheralInstance +end + +_G.Peripheral = { + new = newPeripheral +} + +---@class ReactorStatistics +local ReactorStatistics = { + +} +---comment +---@param id string +---@param type string +---@return ReactorStatistics +local function newReactorStatistics(id, type) + + local reactorStatisticsInstance = { + } + setmetatable(reactorStatisticsInstance, {__index = ReactorStatistics}) + return reactorStatisticsInstance +end + +_G.ReactorStatistics = { + new = newReactorStatistics +} + +-- local function newVector2(x, y) + +-- local Vector2Instance = { +-- x = x, +-- y = y, +-- } +-- setmetatable(Vector2Instance, {__index = Vector2, __add = Vector2.__add, __sub = Vector2.__sub}) +-- return Vector2Instance +-- end + +-- _G.Vector2 = { +-- new = newVector2, +-- zero = newVector2(0, 0), +-- one = newVector2(1, 1), + +-- __add = function (a, b) +-- local t = type(b) +-- if t == "table" then +-- return Vector2.new(a.x + b.x, a.y + b.y) +-- elseif t == "number" then +-- return Vector2.new(a.x + b, a.y + b) +-- end +-- end, + +-- __sub = function (a, b) +-- local t = type(b) +-- if t == "table" then +-- return Vector2.new(a.x - b.x, a.y - b.y) +-- elseif t == "number" then +-- return Vector2.new(a.x - b, a.y - b) +-- end +-- end, +-- } + +---@class Vector2 +local Vector2 = { + ---@type number + x = nil, + ---@type number + y = nil, +} + +Vector2.mt = {} + +Vector2.mt.__index = Vector2 + +---comment +---@param x number +---@param y number +---@return Vector2 +function Vector2.new(x, y) + local instance = {x = x, y = y} + return setmetatable(instance, Vector2.mt) +end + +Vector2.mt.__add = function(self, other) + local t = type(other) + if t == "table" then + return Vector2.new(self.x + other.x, self.y + other.y) + elseif t == "number" then + return Vector2.new(self.x + other, self.y + other) + end +end + +Vector2.mt.__sub = function(self, other) + local t = type(other) + if t == "table" then + return Vector2.new(self.x - other.x, self.y - other.y) + elseif t == "number" then + return Vector2.new(self.x - other, self.y - other) + end +end + +_G.Vector2 = { + new = Vector2.new, + zero = Vector2.new(0, 0), + one = Vector2.new(1, 1), +} + +local test = _G.Vector2.one + _G.Vector2.one +print(test.x, test.y) +test = test + 1 +print(test.x, test.y) +test = test - 2 +print(test.x, test.y) \ No newline at end of file diff --git a/src/classes/monitor.lua b/src/classes/monitor.lua new file mode 100644 index 0000000..376efc7 --- /dev/null +++ b/src/classes/monitor.lua @@ -0,0 +1,72 @@ +---@class Monitor +local Monitor = { + + navbar = nil, + ---@type Page + activePage = nil, + ---@type string + id = nil, + ---@type table + peripheral = nil, + ---@type table + touch = nil, + ---@type Vector2 + size = nil, + ---@type integer + oo = nil, + ---@type integer + dim = nil, + + ---comment + ---@param self Monitor + clear = function(self) + self.peripheral.setBackgroundColor(colors.black) + self.peripheral.clear() + self.peripheral.setTextScale(0.5) + self.peripheral.setCursorPos(1,1) + end, + + ---@param self Monitor + drawScene = function(self) + if (invalidDim) then + self.peripheral.write("Invalid Monitor Dimensions") + return + end + + if (displayingGraphMenu) then + drawGraphButtons() + end + drawControls() + drawStatus() + drawStatistics() + self.touch:draw() + end, + + ---comment + ---@param self Monitor + ---@param reactorStats ReactorStatistics + update = function(self, reactorStats) + + end +} + +---comment +---@param peripheralId string +---@return Monitor +local function new(peripheralId) + local monitorPeripheral = peripheral.wrap(peripheralId) + local touch = _G.Touchpoint.new(peripheralId) + + local monitorInstance = { + id = peripheralId, + peripheral = monitorPeripheral, + touch = touch, + } + + setmetatable(monitorInstance, {__index = Monitor}) + return monitorInstance +end + +_G.Monitor = { + new = new +} \ No newline at end of file diff --git a/src/classes/navbar.lua b/src/classes/navbar.lua new file mode 100644 index 0000000..7a86c17 --- /dev/null +++ b/src/classes/navbar.lua @@ -0,0 +1,39 @@ +---@class Navbar +local Navbar = { + ---@type table + pageNameMap = nil, + + touch = nil, + ---@type Vector2 + offset = nil, + ---@type Vector2 + size = nil, + + ---comment + ---@param self Navbar + ---@param reactorStats ReactorStatistics + draw = function(self, reactorStats) + + end +} + +---comment +---@param peripheralId string +---@return Navbar +local function new(peripheralId) + local monitorPeripheral = peripheral.wrap(peripheralId) + local touch = _G.Touchpoint.new(peripheralId) + + local monitorInstance = { + id = peripheralId, + peripheral = monitorPeripheral, + touch = touch, + } + + setmetatable(monitorInstance, {__index = Navbar}) + return monitorInstance +end + +_G.Monitor = { + new = new +} \ No newline at end of file diff --git a/src/classes/page.lua b/src/classes/page.lua new file mode 100644 index 0000000..1809372 --- /dev/null +++ b/src/classes/page.lua @@ -0,0 +1,67 @@ +---@class Page +local Page = { + ---@type string + name = nil, + ---@type table + peripheral = nil, + ---@type table + touch = nil, + ---@type Vector2 + offset = nil, + ---@type Vector2 + size = nil, + + + ---comment + ---@param self Page + clear = function(self) + self.peripheral.setBackgroundColor(colors.black) + self.peripheral.clear() + self.peripheral.setTextScale(0.5) + self.peripheral.setCursorPos(1,1) + end, + + ---@param self Page + drawScene = function(self) + if (invalidDim) then + self.peripheral.write("Invalid Monitor Dimensions") + return + end + + if (displayingGraphMenu) then + drawGraphButtons() + end + drawControls() + drawStatus() + drawStatistics() + self.touch:draw() + end, + + ---comment + ---@param self Page + ---@param reactorStats ReactorStatistics + update = function(self, reactorStats) + + end +} + +---comment +---@param peripheralId string +---@return Page +local function new(peripheralId) + local monitorPeripheral = peripheral.wrap(peripheralId) + local touch = _G.Touchpoint.new(peripheralId) + + local monitorInstance = { + id = peripheralId, + peripheral = monitorPeripheral, + touch = touch, + } + + setmetatable(monitorInstance, {__index = Page}) + return monitorInstance +end + +_G.Monitor = { + new = new +} \ No newline at end of file diff --git a/src/classes/touchpoint.lua b/src/classes/touchpoint.lua new file mode 100644 index 0000000..d0d25a6 --- /dev/null +++ b/src/classes/touchpoint.lua @@ -0,0 +1,187 @@ +local version = "1.01" +--[[ +The MIT License (MIT) + +Copyright (c) 2013 Lyqyd + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +Edited by DrunkenKas. + See Github: https://github.com/Kasra-G/ReactorController/#readme +--]] + +local function setupLabel(buttonLen, minY, maxY, name) + local labelTable = {} + if type(name) == "table" then + for i = 1, #name do + labelTable[i] = name[i] + end + name = name.label + elseif type(name) == "string" then + local buttonText = string.sub(name, 1, buttonLen - 2) + if #buttonText < #name then + buttonText = " "..buttonText.." " + else + local labelLine = string.rep(" ", math.floor((buttonLen - #buttonText) / 2))..buttonText + buttonText = labelLine..string.rep(" ", buttonLen - #labelLine) + end + for i = 1, maxY - minY + 1 do + if maxY == minY or i == math.floor((maxY - minY) / 2) + 1 then + labelTable[i] = buttonText + else + labelTable[i] = string.rep(" ", buttonLen) + end + end + end + return labelTable, name +end + +---@class Touchpoint +local Touchpoint = { + draw = function(self) + local old = term.redirect(self.mon) + term.setTextColor(colors.white) + term.setBackgroundColor(colors.black) + for name, buttonData in pairs(self.buttonList) do + if buttonData.active then + term.setBackgroundColor(buttonData.activeColor) + term.setTextColor(buttonData.activeText) + else + term.setBackgroundColor(buttonData.inactiveColor) + term.setTextColor(buttonData.inactiveText) + end + for i = buttonData.yMin, buttonData.yMax do + term.setCursorPos(buttonData.xMin, i) + term.write(buttonData.label[i - buttonData.yMin + 1]) + end + end + if old then + term.redirect(old) + else + term.restore() + end + end, + add = function(self, name, func, xMin, yMin, xMax, yMax, inactiveColor, activeColor, inactiveText, activeText) + local label, name = setupLabel(xMax - xMin + 1, yMin, yMax, name) + if self.buttonList[name] then error("button already exists", 2) end + local x, y = self.mon.getSize() + if xMin < 1 or yMin < 1 or xMax > x or yMax > y then error("button out of bounds", 2) end + self.buttonList[name] = { + func = func, + xMin = xMin, + yMin = yMin, + xMax = xMax, + yMax = yMax, + active = false, + inactiveColor = inactiveColor or colors.red, + activeColor = activeColor or colors.lime, + inactiveText = inactiveText or colors.white, + activeText = activeText or colors.white, + label = label, + } + for i = xMin, xMax do + for j = yMin, yMax do + if self.clickMap[i][j] ~= nil then + --undo changes + for k = xMin, xMax do + for l = yMin, yMax do + if self.clickMap[k][l] == name then + self.clickMap[k][l] = nil + end + end + end + self.buttonList[name] = nil + error("overlapping button", 2) + end + self.clickMap[i][j] = name + end + end + end, + remove = function(self, name) + if self.buttonList[name] then + local button = self.buttonList[name] + for i = button.xMin, button.xMax do + for j = button.yMin, button.yMax do + self.clickMap[i][j] = nil + end + end + self.buttonList[name] = nil + end + end, + run = function(self) + while true do + self:draw() + local event = {self:handleEvents(os.pullEvent(self.side == "term" and "mouse_click" or "monitor_touch"))} + if event[1] == "button_click" then + self.buttonList[event[2]].func() + end + end + end, + handleEvents = function(self, ...) + local event = {...} + if #event == 0 then event = {os.pullEvent()} end + if (self.side == "term" and event[1] == "mouse_click") or (self.side ~= "term" and event[1] == "monitor_touch" and event[2] == self.side) then + local clicked = self.clickMap[event[3]][event[4]] + if clicked and self.buttonList[clicked] then + return "button_click", clicked + end + end + return unpack(event) + end, + toggleButton = function(self, name, noDraw) + self.buttonList[name].active = not self.buttonList[name].active + if not noDraw then self:draw() end + end, + flash = function(self, name, duration) + self:toggleButton(name) + sleep(tonumber(duration) or 0.15) + self:toggleButton(name) + end, + rename = function(self, name, newName) + self.buttonList[name].label, newName = setupLabel(self.buttonList[name].xMax - self.buttonList[name].xMin + 1, self.buttonList[name].yMin, self.buttonList[name].yMax, newName) + if not self.buttonList[name] then error("no such button", 2) end + if name ~= newName then + self.buttonList[newName] = self.buttonList[name] + self.buttonList[name] = nil + for i = self.buttonList[newName].xMin, self.buttonList[newName].xMax do + for j = self.buttonList[newName].yMin, self.buttonList[newName].yMax do + self.clickMap[i][j] = newName + end + end + end + self:draw() + end, +} + +local function new(monSide) + local touchpointInstance = { + side = monSide or "term", + mon = monSide and peripheral.wrap(monSide) or term.current(), + buttonList = {}, + clickMap = {}, + } + local x, y = touchpointInstance.mon.getSize() + for i = 1, x do + touchpointInstance.clickMap[i] = {} + end + setmetatable(touchpointInstance, {__index = Touchpoint}) + return touchpointInstance +end + +_G.Touchpoint = {new = new} \ No newline at end of file diff --git a/src/scripts/controller.lua b/src/scripts/controller.lua new file mode 100644 index 0000000..f6119c1 --- /dev/null +++ b/src/scripts/controller.lua @@ -0,0 +1,1113 @@ +local version = "0.51" +local tag = "reactorConfig" +--[[ +Program made by DrunkenKas + See github: https://github.com/Kasra-G/ReactorController/#readme + +The MIT License (MIT) + +Copyright (c) 2021 Kasra Ghaffari + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +]] + +local reactorVersion, reactor +local mon, monSide +local sizex, sizey, dividerXCoord, oo, offy +local btnOn, btnOff, invalidDim +local minb, maxb +local rod, rfLost +local storedLastTick, storedThisTick, lastRFT = 0,0,0 +local fuelTemp, caseTemp, fuelUsage, waste, capacity = 0,0,0,0,1 +local t +local displayingGraphMenu = false + +local SECONDS_TO_AVERAGE = 2 + +local averageStoredThisTick = 0 +local averageLastRFT = 0 +local averageRod = 0 +local averageFuelUsage = 0 +local averageWaste = 0 +local averageFuelTemp = 0 +local averageCaseTemp = 0 +local averageRfLost = 0 + +local MINIMUM_DIVIDER_X_VALUE = 3 + +-- table of which graphs to draw +local graphsToDraw = {} + +-- table of all the graphs +local graphs = +{ + "Energy Buffer", + "Control Level", + "Temperatures", +} + +-- marks the offsets for each graph position +-- { XOffset, } +local XOffs = +{ + { 4, true}, + {27, true}, + {50, true}, + {73, true}, + {96, true}, +} + +--Helper method for adding buttons +local function addButton(touch, name, callBack, offset, size, color1, color2) + local buttonTopLeftCorner = offset + Vector2.one + local buttonBottomRightCorner = offset + size + touch:add( + name, + callBack, + buttonTopLeftCorner.x, + buttonTopLeftCorner.y, + buttonBottomRightCorner.x, + buttonBottomRightCorner.y, + color1, + color2 + ) +end + +local function minAdd10() + minb = math.min(maxb - 10, minb + 10) +end +local function minSub10() + minb = math.max(0, minb - 10) +end +local function maxAdd10() + maxb = math.min(100, maxb + 10) +end +local function maxSub10() + maxb = math.max(minb + 10, maxb - 10) +end + +local function turnOff() + if (btnOn) then + t:toggleButton("Off") + t:toggleButton("On") + btnOff = true + btnOn = false + reactor.setActive(false) + end +end + +local function turnOn() + if (btnOff) then + t:toggleButton("Off") + t:toggleButton("On") + btnOff = false + btnOn = true + reactor.setActive(true) + end +end + +--adds buttons +local function addButtons() + if (sizey == 24) then + oo = 1 + end + local buttonSize = Vector2.new(8, 3) + local offsetOnOff = Vector2.new(dividerXCoord + 5, 3 + oo) + + addButton( + t, + "On", + turnOn, + offsetOnOff, + buttonSize, + colors.red, + colors.lime + ) + + addButton( + t, + "Off", + turnOff, + offsetOnOff + Vector2.new(12, 0), + buttonSize, + colors.red, + colors.lime + ) + + if (btnOn) then + t:toggleButton("On", true) + else + t:toggleButton("Off", true) + end + + local offset = Vector2.new(dividerXCoord, oo) + + if (sizey > 24) then + addButton( + t, + "+ 10", + minAdd10, + offset + Vector2.new(5, 14), + buttonSize, + colors.purple, + colors.pink + ) + addButton( + t, + " + 10 ", + maxAdd10, + offset + Vector2.new(17, 14), + buttonSize, + colors.magenta, + colors.pink + ) + addButton( + t, + "- 10", + minSub10, + offset + Vector2.new(5, 18), + buttonSize, + colors.purple, + colors.pink + ) + addButton( + t, + " - 10 ", + maxSub10, + offset + Vector2.new(17, 18), + buttonSize, + colors.magenta, + colors.pink + ) + end +end + +--Resets the monitor +local function resetMon() + if (monSide == nil) then + return + end + mon.setBackgroundColor(colors.black) + mon.clear() + mon.setTextScale(0.5) + mon.setCursorPos(1,1) +end + +local function getPercPower() + return averageStoredThisTick / capacity * 100 +end + +local function rnd(num, dig) + return math.floor(10 ^ dig * num) / (10 ^ dig) +end + +local function getEfficiency() + return averageLastRFT / averageFuelUsage +end + +local function format(num) + if (num >= 1000000000) then + return string.format("%7.3f G", num / 1000000000) + elseif (num >= 1000000) then + return string.format("%7.3f M", num / 1000000) + elseif (num >= 1000) then + return string.format("%7.3f K", num / 1000) + elseif (num >= 1) then + return string.format("%7.3f ", num) + elseif (num >= .001) then + return string.format("%7.3f m", num * 1000) + elseif (num >= .000001) then + return string.format("%7.3f u", num * 1000000) + else + return string.format("%7.3f ", 0) + end +end + + +local function getAvailableXOff() + for i,v in pairs(XOffs) do + if (v[2] and v[1] < dividerXCoord - 1) then + v[2] = false + return v[1] + end + end + return -1 +end + +local function getXOff(num) + for i,v in pairs(XOffs) do + if (v[1] == num) then + return v + end + end + return nil +end + +local function enableGraph(name) + if (graphsToDraw[name] ~= nil) then + return + end + local e = getAvailableXOff() + if (e ~= -1) then + graphsToDraw[name] = e + if (displayingGraphMenu) then + t:toggleButton(name) + end + end +end + +local function disableGraph(name) + if (graphsToDraw[name] == nil) then + return + end + if (displayingGraphMenu) then + t:toggleButton(name) + end + getXOff(graphsToDraw[name])[2] = true + graphsToDraw[name] = nil +end + +local function toggleGraph(name) + if (graphsToDraw[name] == nil) then + enableGraph(name) + else + disableGraph(name) + end +end + +local function addGraphButtons() + offy = oo - 14 + local graphButtonSize = Vector2.new(20, 3) + local graphButtonOffset = Vector2.new(dividerXCoord + 5, offy) + for i,graphName in pairs(graphs) do + + addButton( + t, + graphName, + function() toggleGraph(graphName) end, + graphButtonOffset + Vector2.new(0, i * 3 - 1), + graphButtonSize, + colors.red, + colors.lime + ) + if (graphsToDraw[graphName] ~= nil) then + t:toggleButton(graphName, true) + end + end +end + +local function drawGraphButtons(mon, offset, size) + + DrawUtil.drawRectangle(mon, colors.black, colors.orange, offset, size) + local textPos = offset + Vector2.new(4, 0) + DrawUtil.drawText( + mon, + " Graph Controls ", + textPos, + colors.black, + colors.orange + ) +end + +local function drawEnergyBuffer(mon, offset, graphSize, drawPercentLabelOnRight) + DrawUtil.drawText(mon, "Energy Buffer", offset, colors.black, colors.orange) + DrawUtil.drawRectangle(mon, colors.red, colors.gray, offset + Vector2.new(0, 1), graphSize) + + local energyBufferMaxHeight = graphSize.y - 2 + local unitEnergyLevel = getPercPower() / 100 + local energyBufferHeight = math.floor(unitEnergyLevel * energyBufferMaxHeight + 0.5) + local rndpw = rnd(getPercPower(), 2) + + local energyBufferColor + if rndpw < maxb and rndpw > minb then + energyBufferColor = colors.green + elseif rndpw >= maxb then + energyBufferColor = colors.orange + elseif rndpw <= minb then + energyBufferColor = colors.blue + end + + local energyBufferTipOffset = offset + Vector2.new(1, 2 + energyBufferMaxHeight - energyBufferHeight) + local energyBufferSize = Vector2.new(graphSize.x - 2, energyBufferHeight) + + DrawUtil.drawFilledRectangle(mon, energyBufferColor, energyBufferTipOffset, energyBufferSize) + + local energyBufferTextOffset = energyBufferTipOffset + local rfLabelBackgroundColor = energyBufferColor + + if energyBufferHeight <= 0 then + energyBufferTextOffset = energyBufferTipOffset + Vector2.new(0, -1) + rfLabelBackgroundColor = colors.red + end + + local percentLabelXOffset = offset.x - 6 + if drawPercentLabelOnRight then + percentLabelXOffset = offset.x + 15 + end + + DrawUtil.drawText( + mon, + string.format(drawPercentLabelOnRight and "%.2f%%" or "%5.2f%%", rndpw), + Vector2.new(percentLabelXOffset, energyBufferTextOffset.y), + colors.black, + energyBufferColor + ) + DrawUtil.drawText( + mon, + format(averageStoredThisTick).."RF", + energyBufferTextOffset, + rfLabelBackgroundColor, + colors.black + ) +end + +local function drawControlGraph(mon, offset, size, averageRod) + local unitRodLevel = averageRod / 100 + local controlRodMaxPixelHeight = size.y - 2 + local controlRodPixelHeight = math.ceil(unitRodLevel * controlRodMaxPixelHeight) + + DrawUtil.drawText( + mon, + "Control Level", + offset + Vector2.new(1, 0), + colors.black, + colors.orange + + ) + DrawUtil.drawRectangle( + mon, + colors.yellow, + colors.gray, + offset + Vector2.new(0, 1), + size + ) + DrawUtil.drawFilledRectangle( + mon, + colors.white, + offset + Vector2.new(3, 2), + Vector2.new(9, controlRodPixelHeight) + ) + + local controlRodLevelTextPos, color + if controlRodPixelHeight > 0 then + color = colors.white + controlRodLevelTextPos = offset + Vector2.new(4, 1 + controlRodPixelHeight) + else + color = colors.yellow + controlRodLevelTextPos = offset + Vector2.new(4, 2) + end + + DrawUtil.drawText( + mon, + string.format("%6.2f%%", averageRod), + controlRodLevelTextPos, + color, + colors.black + ) +end + +local function drawTemperatures(mon, offset, size) + + DrawUtil.drawRectangle(mon, colors.black, colors.gray, offset + Vector2.new(1, 1), size) + + local CASE_TEMP_COLOR = colors.lightBlue + local FUEL_TEMP_COLOR = colors.magenta + local BACKGROUND_COLOR = colors.black + + local assumedMaxCaseTemperature = 3000 + local assumedMaxFuelTemperature = 3000 + local temperatureMaxHeight = size.y - 2 + + local tempUnit = (reactorVersion == "Bigger Reactors") and "K" or "C" + local tempFormat = "%4s"..tempUnit + + DrawUtil.drawText(mon, "Temperatures", offset + Vector2.new(2, 0), BACKGROUND_COLOR, colors.orange) + DrawUtil.drawFilledRectangle(mon, colors.gray, offset + Vector2.new(8, 2), Vector2.new(1, temperatureMaxHeight)) + + -- case temp + DrawUtil.drawText(mon, "Case", offset + Vector2.new(3, 1), colors.gray, colors.lightBlue) + local caseUnit = math.min(averageCaseTemp / assumedMaxCaseTemperature, 1) + local caseTempHeight = math.floor(caseUnit * temperatureMaxHeight + 0.5) + + local caseTempOffset = offset + Vector2.new(2, 2 + temperatureMaxHeight - caseTempHeight) + local caseTempSize = Vector2.new(6, caseTempHeight) + + DrawUtil.drawFilledRectangle(mon, CASE_TEMP_COLOR, caseTempOffset, caseTempSize) + + local caseTempTextOffset = caseTempOffset + local caseTempTextBackgroundColor = CASE_TEMP_COLOR + local caseTempTextColor = BACKGROUND_COLOR + + if caseTempHeight <= 0 then + caseTempTextOffset = caseTempOffset + Vector2.new(0, -1) + caseTempTextColor, caseTempTextBackgroundColor = caseTempTextBackgroundColor, caseTempTextColor + end + + local caseRnd = math.floor(averageCaseTemp + 0.5) + DrawUtil.drawText(mon, string.format(tempFormat, caseRnd..""), caseTempTextOffset, caseTempTextBackgroundColor, caseTempTextColor) + + -- fuel temp + DrawUtil.drawText(mon, "Fuel", offset + Vector2.new(10, 1), colors.gray, colors.lightBlue) + local fuelUnit = math.min(averageFuelTemp / assumedMaxFuelTemperature, 1) + local fuelTempHeight = math.floor(fuelUnit * temperatureMaxHeight + 0.5) + + local fuelTempOffset = offset + Vector2.new(9, 2 + temperatureMaxHeight - fuelTempHeight) + local fuelTempSize = Vector2.new(6, fuelTempHeight) + + DrawUtil.drawFilledRectangle(mon, FUEL_TEMP_COLOR, fuelTempOffset, fuelTempSize) + + local fuelTempTextOffset = fuelTempOffset + local fuelTempTextBackgroundColor = FUEL_TEMP_COLOR + local fuelTempTextColor = BACKGROUND_COLOR + + if fuelTempHeight <= 0 then + fuelTempTextOffset = fuelTempOffset + Vector2.new(0, -1) + fuelTempTextColor, fuelTempTextBackgroundColor = fuelTempTextBackgroundColor, fuelTempTextColor + end + + local fuelRnd = math.floor(averageFuelTemp + 0.5) + DrawUtil.drawText(mon, string.format(tempFormat, fuelRnd..""), fuelTempTextOffset, fuelTempTextBackgroundColor, fuelTempTextColor) +end + +local function drawGraph(name, graphOffset, graphSize) + if (name == "Energy Buffer") then + local drawPercentLabelOnRight = graphOffset.x + 19 < dividerXCoord - 1 + drawEnergyBuffer(mon, graphOffset, graphSize, drawPercentLabelOnRight) + elseif (name == "Control Level") then + drawControlGraph(mon, graphOffset, graphSize, averageRod) + elseif (name == "Temperatures") then + drawTemperatures(mon, graphOffset, graphSize) + end +end + +local function drawGraphs(mon, graphsToDraw, dividerXCoord, offset, size) + DrawUtil.drawRectangle(mon, colors.black, colors.lightBlue, offset, size) + local label = " Reactor Graphs " + DrawUtil.drawText( + mon, + label, + offset + Vector2.new(dividerXCoord - (#label + 5) - 1, 0), + colors.black, + colors.lightBlue + ) + + local graphSize = Vector2.new(15, sizey - 7) + local graphYOffset = 4 + for graphName, graphXOffset in pairs(graphsToDraw) do + if (graphXOffset + graphSize.x < dividerXCoord) then + drawGraph(graphName, Vector2.new(graphXOffset, graphYOffset), graphSize) + end + end +end + +local function drawControls(mon, offset, size, drawBufferVisualization) + + DrawUtil.drawRectangle(mon, colors.black, colors.cyan, offset, size) + DrawUtil.drawText(mon, " Reactor Controls ", offset + Vector2.new(4, 0), colors.black, colors.cyan) + + local reactorOnOffLabel = "Reactor "..(btnOn and "Online" or "Offline") + local reactorOnOffLabelColor = btnOn and colors.green or colors.red + DrawUtil.drawText(mon, reactorOnOffLabel, offset + Vector2.new(7, 2), colors.black, reactorOnOffLabelColor) + + if not drawBufferVisualization then + return + end + + local bufferMinInPixels = minb / 5 + local bufferMaxInPixels = maxb / 5 + local bufferRangePixelWidth = bufferMaxInPixels - bufferMinInPixels + + local bufferVisualOffset = offset + Vector2.new(5, 8) + local bufferVisualSize = Vector2.new(20, 3) + + DrawUtil.drawText(mon, "Buffer Target Range", bufferVisualOffset + Vector2.new(0, -1), colors.black, colors.orange) + DrawUtil.drawFilledRectangle(mon, colors.red, bufferVisualOffset, bufferVisualSize) + DrawUtil.drawFilledRectangle(mon, colors.green, bufferVisualOffset + Vector2.new(bufferMinInPixels, 0), Vector2.new(bufferRangePixelWidth, 3)) + + DrawUtil.drawText( + mon, + string.format("%3s", minb.."%"), + bufferVisualOffset + Vector2.new(bufferMinInPixels - 3, bufferVisualSize.y), + colors.black, + colors.purple + ) + DrawUtil.drawText( + mon, + maxb.."%", + bufferVisualOffset + Vector2.new(bufferMaxInPixels, bufferVisualSize.y), + colors.black, + colors.magenta + ) + DrawUtil.drawText(mon, "Min", offset + Vector2.new(7, 13), colors.black, colors.purple) + DrawUtil.drawText(mon, "Max", offset + Vector2.new(19, 13), colors.black, colors.purple) +end + +local function drawStatistics(mon, offset, size) + DrawUtil.drawRectangle(mon, colors.black, colors.blue, offset, size) + DrawUtil.drawText( + mon, + " Reactor Statistics ", + offset + Vector2.new(4, 0), + colors.black, + colors.blue + ) + + DrawUtil.drawText( + mon, + "Generating : "..format(averageLastRFT).."RF/t", + offset + Vector2.new(2, 2), + colors.black, + colors.green + ) + + DrawUtil.drawText( + mon, + "RF Drain "..(averageStoredThisTick <= averageLastRFT and "> " or ": ")..format(averageRfLost).."RF/t", + offset + Vector2.new(2, 4), + colors.black, + colors.red + ) + + DrawUtil.drawText( + mon, + "Efficiency : "..format(getEfficiency()).."RF/B", + offset + Vector2.new(2, 6), + colors.black, + colors.green + ) + + DrawUtil.drawText( + mon, + "Fuel Usage : "..format(averageFuelUsage).."B/t", + offset + Vector2.new(2, 8), + colors.black, + colors.green + ) + + DrawUtil.drawText( + mon, + "Waste : "..string.format("%7d mB", waste), + offset + Vector2.new(2, 10), + colors.black, + colors.green + ) +end + +--Draw a scene +local function drawScene() + if (monSide == nil) then + return + end + if (invalidDim) then + mon.write("Invalid Monitor Dimensions") + return + end + + local offset, size + + if (displayingGraphMenu) then + offset = Vector2.new(dividerXCoord + 1, offy + 1) + size = Vector2.new(30, 13) + drawGraphButtons(mon, offset, size) + end + + offset = Vector2.new(dividerXCoord + 1, oo + 1) + size = Vector2.new(30, 23) + local drawBufferVisualization = true + if (sizey <= 24) then + size = Vector2.new(30, 9) + drawBufferVisualization = false + end + drawControls(mon, offset, size, drawBufferVisualization) + + if (dividerXCoord > MINIMUM_DIVIDER_X_VALUE) then + offset = Vector2.new(2, 2) + size = Vector2.new(dividerXCoord - 2, sizey - 2) + drawGraphs(mon, graphsToDraw, dividerXCoord, offset, size) + end + + offset = Vector2.new(dividerXCoord + 1, sizey - 12) + size = Vector2.new(30, 12) + drawStatistics(mon, offset, size) + + t:draw() +end + +local function getAllPeripheralIdsForType(targetType) + ---@type string[] + local peripheralIds = {} + for _, id in pairs(peripheral.getNames()) do + if (peripheral.getType(id) == targetType) then + table.insert(peripheralIds, id) + end + end + return peripheralIds +end + +---@type table +local monitors = {} + +local function initMon2(id) + local monitor = Monitor.new(id) + monSide = id + mon = monitor.peripheral + monitor:clear() + t = monitor.touch + + sizex, sizey = mon.getSize() + oo = sizey - 37 + dividerXCoord = sizex - 31 + + if (sizex == 36) then + dividerXCoord = MINIMUM_DIVIDER_X_VALUE + end + displayingGraphMenu = pcall(function() addGraphButtons() end) + _, invalidDim = pcall(function() addButtons() end) + + return monitor +end + +local function updateMonitors() + for _, monitor in pairs(monitors) do + ---@type ReactorStatistics + local reactorStats = {} + monitor:update(reactorStats) + end +end + +local function initMonitors() + monitors = {} + local ids = getAllPeripheralIdsForType("monitor") + + for _, id in pairs(ids) do + monitors[id] = initMon2(id) + end +end +-- DELETE LATER! +initMonitors() + +--returns the side that a given peripheral type is connected to +local function getPeripheral(targetType) + for _, name in pairs(peripheral.getNames()) do + if (peripheral.getType(name) == targetType) then + return name + end + end + return "" +end + +--Creates all the buttons and determines monitor size +local function initMon() + monSide = getPeripheral("monitor") + if (monSide == nil or monSide == "") then + monSide = nil + return + end + + local monitor = Monitor.new(monSide) + mon = monitor.peripheral + + if mon == nil then + monSide = nil + return + end + + resetMon() + t = Touchpoint.new(monSide) + sizex, sizey = mon.getSize() + oo = sizey - 37 + dividerXCoord = sizex - 31 + + if (sizex == 36) then + dividerXCoord = MINIMUM_DIVIDER_X_VALUE + end + displayingGraphMenu = pcall(function() addGraphButtons() end) + _, invalidDim = pcall(function() addButtons() end) +end + +local function setRods(level) + level = math.max(level, 0) + level = math.min(level, 100) + reactor.setAllControlRodLevels(level) +end + +local function lerp(start, finish, t) + t = math.max(0, math.min(1, t)) + + return (1 - t) * start + t * finish +end + +-- Function to calculate the average of an array of values +local function calculateAverage(array) + local sum = 0 + for _, value in ipairs(array) do + sum = sum + value + end + return sum / #array +end + +-- Define PID controller parameters +local pid = { + setpointRFT = 0, -- Target RFT + setpointRF = 0, -- Target RF + Kp = -.08, -- Proportional gain + Ki = -.0015, -- Integral gain + Kd = -.01, -- Derivative gain + integral = 0, -- Integral term accumulator + lastError = 0, -- Last error for derivative term +} + +local function iteratePID(pid, error) + -- Proportional term + local P = pid.Kp * error + + -- Integral term + pid.integral = pid.integral + pid.Ki * error + pid.integral = math.max(math.min(100, pid.integral), -100) + + -- Derivative term + local derivative = pid.Kd * (error - pid.lastError) + + -- Calculate control rod level + local rodLevel = math.max(math.min(P + pid.integral + derivative, 100), 0) + + -- Update PID controller state + pid.lastError = error + return rodLevel +end + +local function updateRods() + if (not btnOn) then + return + end + local currentRF = storedThisTick + local diffb = maxb - minb + local minRF = minb / 100 * capacity + local diffRF = diffb / 100 * capacity + local diffr = diffb / 100 + local targetRFT = rfLost + local currentRFT = lastRFT + local targetRF = diffRF / 2 + minRF + + pid.setpointRFT = targetRFT + pid.setpointRF = targetRF / capacity * 1000 + + local errorRFT = pid.setpointRFT - currentRFT + local errorRF = pid.setpointRF - currentRF / capacity * 1000 + + local W_RFT = lerp(1, 0, (math.abs(targetRF - currentRF) / capacity / (diffr / 4))) + W_RFT = math.max(math.min(W_RFT, 1), 0) + + local W_RF = (1 - W_RFT) -- Adjust the weight for energy error + + -- Combine the errors with weights + local combinedError = W_RFT * errorRFT + W_RF * errorRF + local error = combinedError + local rftRodLevel = iteratePID(pid, error) + + -- Set control rod levels + setRods(rftRodLevel) +end + +-- Saves the configuration of the reactor controller +local function saveToConfig() + local file = fs.open(tag.."Serialized.txt", "w") + local configs = { + maxb = maxb, + minb = minb, + rod = rod, + btnOn = btnOn, + graphsToDraw = graphsToDraw, + XOffs = XOffs, + } + local serialized = textutils.serialize(configs) + file.write(serialized) + file.close() +end + +local storedThisTickValues = {} +local lastRFTValues = {} +local rodValues = {} +local fuelUsageValues = {} +local wasteValues = {} +local fuelTempValues = {} +local caseTempValues = {} +local rfLostValues = {} + +local function updateStats() + storedLastTick = storedThisTick + if (reactorVersion == "Big Reactors") then + storedThisTick = reactor.getEnergyStored() + lastRFT = reactor.getEnergyProducedLastTick() + rod = reactor.getControlRodLevel(0) + fuelUsage = reactor.getFuelConsumedLastTick() / 1000 + waste = reactor.getWasteAmount() + fuelTemp = reactor.getFuelTemperature() + caseTemp = reactor.getCasingTemperature() + -- Big Reactors doesn't give us a way to directly query RF capacity through CC APIs + capacity = math.max(capacity, reactor.getEnergyStored) + elseif (reactorVersion == "Extreme Reactors") then + local bat = reactor.getEnergyStats() + local fuel = reactor.getFuelStats() + + storedThisTick = bat.energyStored + lastRFT = bat.energyProducedLastTick + capacity = bat.energyCapacity + rod = reactor.getControlRodLevel(0) + fuelUsage = fuel.fuelConsumedLastTick / 1000 + waste = reactor.getWasteAmount() + fuelTemp = reactor.getFuelTemperature() + caseTemp = reactor.getCasingTemperature() + elseif (reactorVersion == "Bigger Reactors") then + storedThisTick = reactor.battery().stored() + lastRFT = reactor.battery().producedLastTick() + capacity = reactor.battery().capacity() + rod = reactor.getControlRod(0).level() + fuelUsage = reactor.fuelTank().burnedLastTick() / 1000 + waste = reactor.fuelTank().waste() + fuelTemp = reactor.fuelTemperature() + caseTemp = reactor.casingTemperature() + end + rfLost = lastRFT + storedLastTick - storedThisTick + -- Add the values to the arrays + table.insert(storedThisTickValues, storedThisTick) + table.insert(lastRFTValues, lastRFT) + table.insert(rodValues, rod) + table.insert(fuelUsageValues, fuelUsage) + table.insert(wasteValues, waste) + table.insert(fuelTempValues, fuelTemp) + table.insert(caseTempValues, caseTemp) + table.insert(rfLostValues, rfLost) + + local maxIterations = 20 * SECONDS_TO_AVERAGE + while #storedThisTickValues > maxIterations do + table.remove(storedThisTickValues, 1) + table.remove(lastRFTValues, 1) + table.remove(rodValues, 1) + table.remove(fuelUsageValues, 1) + table.remove(wasteValues, 1) + table.remove(fuelTempValues, 1) + table.remove(caseTempValues, 1) + table.remove(rfLostValues, 1) + end + + -- Calculate running averages + averageStoredThisTick = calculateAverage(storedThisTickValues) + averageLastRFT = calculateAverage(lastRFTValues) + averageRod = calculateAverage(rodValues) + averageFuelUsage = calculateAverage(fuelUsageValues) + averageWaste = calculateAverage(wasteValues) + averageFuelTemp = calculateAverage(fuelTempValues) + averageCaseTemp = calculateAverage(caseTempValues) + averageRfLost = calculateAverage(rfLostValues) +end + +--Initialize variables from either a config file or the defaults +local function loadFromConfig() + invalidDim = false + local legacyConfigExists = fs.exists(tag..".txt") + local newConfigExists = fs.exists(tag.."Serialized.txt") + if (newConfigExists) then + local file = fs.open(tag.."Serialized.txt", "r") + print("Config file "..tag.."Serialized.txt found! Using configurated settings") + + local serialized = file.readAll() + local deserialized = textutils.unserialise(serialized) + + maxb = deserialized.maxb + minb = deserialized.minb + rod = deserialized.rod + btnOn = deserialized.btnOn + graphsToDraw = deserialized.graphsToDraw + XOffs = deserialized.XOffs + elseif (legacyConfigExists) then + local file = fs.open(tag..".txt", "r") + local calibrated = file.readLine() == "true" + + --read calibration information + if (calibrated) then + _ = tonumber(file.readLine()) + _ = tonumber(file.readLine()) + end + maxb = tonumber(file.readLine()) + minb = tonumber(file.readLine()) + rod = tonumber(file.readLine()) + btnOn = file.readLine() == "true" + + --read Graph data + for i in pairs(XOffs) do + local graph = file.readLine() + local v1 = tonumber(file.readLine()) + local v2 = true + if (graph ~= "nil") then + v2 = false + graphsToDraw[graph] = v1 + end + + XOffs[i] = {v1, v2} + + end + file.close() + else + print("Config file not found, generating default settings!") + + maxb = 70 + minb = 30 + rod = 80 + btnOn = false + if (monSide == nil) then + btnOn = true + end + sizex, sizey = 100, 52 + dividerXCoord = sizex - 31 + oo = sizey - 37 + enableGraph("Energy Buffer") + enableGraph("Control Level") + enableGraph("Temperatures") + end + btnOff = not btnOn + reactor.setActive(btnOn) +end + +local function startTimer(ticksToUpdate, callback) + local timeToUpdate = ticksToUpdate * 0.05 + local id = os.startTimer(timeToUpdate) + local fun = function(event) + if (event[1] == "timer" and event[2] == id) then + id = os.startTimer(timeToUpdate) + callback() + end + end + return fun +end + + +-- Main loop, handles all the events +local function loop() + local ticksToUpdateStats = 1 + local ticksToRedraw = 4 + + local hasClicked = false + + local updateStatsTick = startTimer( + ticksToUpdateStats, + function() + updateStats() + updateRods() + end + ) + local redrawTick = startTimer( + ticksToRedraw, + function() + if (not hasClicked) then + resetMon() + drawScene() + end + hasClicked = false + end + ) + local handleResize = function(event) + if (event[1] == "monitor_resize") then + local peripheralId = event[2] + initMon() + end + end + local handleClick = function(event) + if (event[1] == "button_click") then + t.buttonList[event[2]].func() + saveToConfig() + resetMon() + drawScene() + hasClicked = true + end + end + while (true) do + local event = { os.pullEvent() } + + if monSide ~= nil then + event = { t:handleEvents(unpack(event)) } + end + + updateStatsTick(event) + redrawTick(event) + handleResize(event) + handleClick(event) + end +end + +local function detectReactor() + -- Bigger Reactors V1. + local reactor_bigger_v1 = getPeripheral("bigger-reactor") + reactor = reactor_bigger_v1 ~= nil and peripheral.wrap(reactor_bigger_v1) + if (reactor ~= nil) then + reactorVersion = "Bigger Reactors" + return true + end + + -- Bigger Reactors V2 + local reactor_bigger_v2 = getPeripheral("BiggerReactors_Reactor") + reactor = reactor_bigger_v2 ~= nil and peripheral.wrap(reactor_bigger_v2) + if (reactor ~= nil) then + reactorVersion = "Bigger Reactors" + return true + end + + -- Big Reactors or Extreme Reactors + local reactor_extreme_or_big = getPeripheral("BigReactors-Reactor") + reactor = reactor_extreme_or_big ~= nil and peripheral.wrap(reactor_extreme_or_big) + if (reactor ~= nil) then + reactorVersion = (reactor.mbIsConnected ~= nil) and "Extreme Reactors" or "Big Reactors" + return true + end + return false +end + +--Entry point +function _G.main() + term.setBackgroundColor(colors.black) + term.clear() + term.setCursorPos(1,1) + + local reactorDetected = false + while (not reactorDetected) do + reactorDetected = detectReactor() + if (not reactorDetected) then + print("Reactor not detected! Trying again...") + sleep(1) + end + end + + print("Reactor detected! Proceeding with initialization ") + + print("Loading config...") + loadFromConfig() + print("Initializing monitor if connected...") + initMon() + print("Writing config to disk...") + saveToConfig() + print("Reactor initialization done! Starting controller") + + term.clear() + term.setCursorPos(1,1) + print("Reactor Controller Version "..version) + print("Reactor Mod: "..reactorVersion) + --main loop + + loop() +end \ No newline at end of file diff --git a/src/scripts/installer.lua b/src/scripts/installer.lua new file mode 100644 index 0000000..f67c17b --- /dev/null +++ b/src/scripts/installer.lua @@ -0,0 +1,13 @@ +--pastebin run kSkwEchg +--Github: https://github.com/Kasra-G/ReactorController#readme + +--Overwrite startup file +local file = fs.open("startup", "w") +file.writeLine("shell.run(\"update_reactor.lua\")") +file.writeLine("while (true) do") +file.writeLine(" shell.run(\"reactorController.lua\")") +file.writeLine(" sleep(2)") +file.writeLine("end") +file.close() +shell.run("pastebin get w6vVtrLb update_reactor.lua") +shell.run("reboot") diff --git a/src/scripts/main.lua b/src/scripts/main.lua new file mode 100644 index 0000000..b6b0aa8 --- /dev/null +++ b/src/scripts/main.lua @@ -0,0 +1,13 @@ +shell.run("/src/classes/touchpoint.lua") +shell.run("/src/classes/classes.lua") +shell.run("/src/util/draw.lua") +shell.run("/src/classes/page.lua") +shell.run("/src/classes/navbar.lua") +shell.run("/src/classes/monitor.lua") +shell.run("/src/scripts/controller.lua") +shell.run("/src/scripts/update.lua") + +-- For now, update() is in update.lua +update() +-- For now, main() is in controller.lua +main() \ No newline at end of file diff --git a/src/scripts/update.lua b/src/scripts/update.lua new file mode 100644 index 0000000..31250d1 --- /dev/null +++ b/src/scripts/update.lua @@ -0,0 +1,101 @@ +local OWNER = "Kasra-G" +local REPO = "ReactorController" +local BRANCH = "development" +local COMMIT_FILENAME = "/commit.txt" +local AUTOMATIC_UPDATE_ENABLED = true + +--Github: https://github.com/Kasra-G/ReactorController/#readme + +local function getRepoDetails(jsonString) + return textutils.unserialiseJSON(jsonString) +end + +local function getRemoteRepoJsonString() + local response = http.get("https://api.github.com/repos/"..OWNER.."/"..REPO.."/commits/"..BRANCH) + return response.readAll() +end + +local function getLocalRepoJsonString() + local file = fs.open(COMMIT_FILENAME, "r") + if file == nil then + print("Local version file not found! Assuming there is an update available.") + return "{}" + end + local contents = file.readAll() + file.close() + return contents +end + +local function checkForUpdate(localRepoDetails, remoteRepoDetails) + return localRepoDetails.sha ~= remoteRepoDetails.sha +end + +local function saveRepoDetails(repoDetails) + local serialized = textutils.serializeJSON(repoDetails) + local file = fs.open(COMMIT_FILENAME, "w") + file.write(serialized) + file.close() +end + +local function deleteExistingFiles() + fs.delete("/src") + fs.delete("startup") +end + +local function downloadGitHubFileContents(filepath) + local endpoint = "https://raw.githubusercontent.com/"..OWNER.."/"..REPO.."/refs/heads/"..BRANCH.."/"..filepath + local response = http.get(endpoint) + local contents = response.readAll() + local file = fs.open(filepath, "w") + file.write(contents) + file.close() +end + +local function getGitHubTreeDetails(treeSHA) + local endpoint = "https://api.github.com/repos/"..OWNER.."/"..REPO.."/git/trees/"..treeSHA + local response = http.get(endpoint) + local contents = response.readAll() + local treeDetails = textutils.unserialiseJSON(contents) + return treeDetails +end + +local function downloadGitHubTreeRecursively(path, treeSHA) + local treeDetails = getGitHubTreeDetails(treeSHA) + for _, treeEntry in pairs(treeDetails.tree) do + if treeEntry.type == "tree" then + downloadGitHubTreeRecursively(path.."/"..treeEntry.path, treeEntry.sha) + else + downloadGitHubFileContents(path.."/"..treeEntry.path) + end + end +end + +local function downloadRemoteSrcDirectory(remoteRepoRootTreeSHA) + local remoteRepoRootTreeDetails = getGitHubTreeDetails(remoteRepoRootTreeSHA) + local srcDirectoryName = "src" + + for _, treeEntry in pairs(remoteRepoRootTreeDetails.tree) do + print(treeEntry.path) + if treeEntry.path == srcDirectoryName then + downloadGitHubTreeRecursively(srcDirectoryName, treeEntry.sha) + end + end +end + +function _G.performUpdate(remoteRepoRootTreeSHA) + deleteExistingFiles() + downloadRemoteSrcDirectory(remoteRepoRootTreeSHA) + downloadGitHubFileContents("startup") +end + +local remoteRepoJson = getRemoteRepoJsonString() +local remoteRepoDetails = getRepoDetails(remoteRepoJson) + +local localRepoJson = getLocalRepoJsonString() +local localRepoDetails = getRepoDetails(localRepoJson) + +local update_available = checkForUpdate(localRepoDetails, remoteRepoDetails) + +if update_available and AUTOMATIC_UPDATE_ENABLED then + performUpdate(remoteRepoDetails.sha) +end \ No newline at end of file diff --git a/src/util/draw.lua b/src/util/draw.lua new file mode 100644 index 0000000..72b1f71 --- /dev/null +++ b/src/util/draw.lua @@ -0,0 +1,74 @@ + +---comment +---@param mon table +---@param drawFunction function +local function executeAndRestoreMonitorSettings(mon, drawFunction) + if (mon == nil) then + error("Error! Mon is nil") + return + end + + local originalCursorPos = Vector2.new(mon.getCursorPos()) + local originalBackgroundColor = mon.getBackgroundColor() + local originalTextColor = mon.getTextColor() + + drawFunction() + + mon.setCursorPos(originalCursorPos.x, originalCursorPos.y) + mon.setBackgroundColor(originalBackgroundColor) + mon.setTextColor(originalTextColor) +end + +---comment Draws a filled rectangle +---@param mon table +---@param color any +---@param offset Vector2 +---@param size Vector2 +local function drawFilledRectangle(mon, color, offset, size) + executeAndRestoreMonitorSettings( + mon, + function () + local horizLine = string.rep(" ", size.x) + mon.setBackgroundColor(color) + for i=0, size.y - 1 do + mon.setCursorPos(offset.x, offset.y + i) + mon.write(horizLine) + end + end + ) +end + +---comment Draws a rectangle with a fill color and border +---@param mon table +---@param innerColor any +---@param outerColor any +---@param offset Vector2 +---@param size Vector2 +local function drawRectangle(mon, innerColor, outerColor, offset, size) + drawFilledRectangle(mon, outerColor, offset, size) + drawFilledRectangle(mon, innerColor, offset + 1, size - 2) +end + +---comment Draws text on the screen +---@param mon table +---@param text string +---@param pos Vector2 +---@param backgroundColor any +---@param textColor any +local function drawText(mon, text, pos, backgroundColor, textColor) + executeAndRestoreMonitorSettings( + mon, + function () + mon.setCursorPos(pos.x, pos.y) + mon.setBackgroundColor(backgroundColor) + mon.setTextColor(textColor) + mon.write(text) + end + ) +end + +_G.DrawUtil = { + drawRectangle = drawRectangle, + drawFilledRectangle = drawFilledRectangle, + drawText = drawText, +} \ No newline at end of file From 2fed8bd0e6a74fa4abcd54333014333f5eb9bfc8 Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Thu, 2 Jan 2025 18:19:31 -0500 Subject: [PATCH 04/38] more changes --- src/scripts/main.lua | 17 ++++++++++++++-- src/scripts/update.lua | 44 ++++++++++++++++++++---------------------- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/scripts/main.lua b/src/scripts/main.lua index b6b0aa8..cfeee3a 100644 --- a/src/scripts/main.lua +++ b/src/scripts/main.lua @@ -1,3 +1,5 @@ +local AUTOMATIC_UPDATE_ENABLED = false + shell.run("/src/classes/touchpoint.lua") shell.run("/src/classes/classes.lua") shell.run("/src/util/draw.lua") @@ -7,7 +9,18 @@ shell.run("/src/classes/monitor.lua") shell.run("/src/scripts/controller.lua") shell.run("/src/scripts/update.lua") --- For now, update() is in update.lua -update() +-- For now, do update check here +local updateAvailable = _G.UpdateScript.checkForUpdate() +if updateAvailable then + print("Update available!") + + if AUTOMATIC_UPDATE_ENABLED then + print("Automatic update is enabled! Updating...") + _G.UpdateScript.performUpdate() + else + print("Automatic update skipped because it's not enabled!") + end + +end -- For now, main() is in controller.lua main() \ No newline at end of file diff --git a/src/scripts/update.lua b/src/scripts/update.lua index 31250d1..65b6945 100644 --- a/src/scripts/update.lua +++ b/src/scripts/update.lua @@ -2,20 +2,16 @@ local OWNER = "Kasra-G" local REPO = "ReactorController" local BRANCH = "development" local COMMIT_FILENAME = "/commit.txt" -local AUTOMATIC_UPDATE_ENABLED = true --Github: https://github.com/Kasra-G/ReactorController/#readme -local function getRepoDetails(jsonString) - return textutils.unserialiseJSON(jsonString) -end - -local function getRemoteRepoJsonString() +local function getRemoteRepoDetails() local response = http.get("https://api.github.com/repos/"..OWNER.."/"..REPO.."/commits/"..BRANCH) - return response.readAll() + local responseJSON = response.readAll() + return textutils.unserialiseJSON(responseJSON) end -local function getLocalRepoJsonString() +local function getLocalRepoDetails() local file = fs.open(COMMIT_FILENAME, "r") if file == nil then print("Local version file not found! Assuming there is an update available.") @@ -23,11 +19,7 @@ local function getLocalRepoJsonString() end local contents = file.readAll() file.close() - return contents -end - -local function checkForUpdate(localRepoDetails, remoteRepoDetails) - return localRepoDetails.sha ~= remoteRepoDetails.sha + return textutils.unserialiseJSON(contents) end local function saveRepoDetails(repoDetails) @@ -82,20 +74,26 @@ local function downloadRemoteSrcDirectory(remoteRepoRootTreeSHA) end end -function _G.performUpdate(remoteRepoRootTreeSHA) +local remoteRepoDetails + +local function performUpdate() deleteExistingFiles() - downloadRemoteSrcDirectory(remoteRepoRootTreeSHA) + downloadRemoteSrcDirectory(remoteRepoDetails.sha) downloadGitHubFileContents("startup") end -local remoteRepoJson = getRemoteRepoJsonString() -local remoteRepoDetails = getRepoDetails(remoteRepoJson) +local function checkForUpdate() + remoteRepoDetails = getRemoteRepoDetails() + + local localRepoDetails = getLocalRepoDetails() -local localRepoJson = getLocalRepoJsonString() -local localRepoDetails = getRepoDetails(localRepoJson) + local updateAvailable = localRepoDetails.sha ~= remoteRepoDetails.sha -local update_available = checkForUpdate(localRepoDetails, remoteRepoDetails) + return updateAvailable +end -if update_available and AUTOMATIC_UPDATE_ENABLED then - performUpdate(remoteRepoDetails.sha) -end \ No newline at end of file +_G.UpdateScript = { + checkForUpdate = checkForUpdate, + performUpdate = performUpdate, + saveRepoDetails = saveRepoDetails, +} \ No newline at end of file From 344126f9e919a7d5fda01e56aee628a891d5a304 Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Thu, 2 Jan 2025 20:31:36 -0500 Subject: [PATCH 05/38] update script tweaks --- src/scripts/main.lua | 18 +++++++---- src/scripts/update.lua | 68 ++++++++++++++++++++++-------------------- 2 files changed, 48 insertions(+), 38 deletions(-) diff --git a/src/scripts/main.lua b/src/scripts/main.lua index cfeee3a..a83bf2d 100644 --- a/src/scripts/main.lua +++ b/src/scripts/main.lua @@ -1,8 +1,17 @@ -local AUTOMATIC_UPDATE_ENABLED = false +_G.GITHUB_CONFIG = { + OWNER = "Kasra-G", + REPO = "ReactorController", + BRANCH = "development", +} + +_G.UPDATE_CONFIG = { + LOCAL_REPO_DETAILS_FILENAME = "/commit.txt", + AUTOUPDATE = false +} -shell.run("/src/classes/touchpoint.lua") shell.run("/src/classes/classes.lua") shell.run("/src/util/draw.lua") +shell.run("/src/classes/touchpoint.lua") shell.run("/src/classes/page.lua") shell.run("/src/classes/navbar.lua") shell.run("/src/classes/monitor.lua") @@ -13,14 +22,13 @@ shell.run("/src/scripts/update.lua") local updateAvailable = _G.UpdateScript.checkForUpdate() if updateAvailable then print("Update available!") - - if AUTOMATIC_UPDATE_ENABLED then + -- Set some state variable somewhere to tell us an update is available + if UPDATE_CONFIG.AUTOUPDATE then print("Automatic update is enabled! Updating...") _G.UpdateScript.performUpdate() else print("Automatic update skipped because it's not enabled!") end - end -- For now, main() is in controller.lua main() \ No newline at end of file diff --git a/src/scripts/update.lua b/src/scripts/update.lua index 65b6945..0fd33c9 100644 --- a/src/scripts/update.lua +++ b/src/scripts/update.lua @@ -1,18 +1,20 @@ -local OWNER = "Kasra-G" -local REPO = "ReactorController" -local BRANCH = "development" -local COMMIT_FILENAME = "/commit.txt" - ---Github: https://github.com/Kasra-G/ReactorController/#readme - +local cachedRemoteRepoDetails local function getRemoteRepoDetails() - local response = http.get("https://api.github.com/repos/"..OWNER.."/"..REPO.."/commits/"..BRANCH) + local response = http.get("https://api.github.com/repos/"..GITHUB_CONFIG.OWNER.."/"..GITHUB_CONFIG.REPO.."/commits/"..GITHUB_CONFIG.BRANCH) local responseJSON = response.readAll() - return textutils.unserialiseJSON(responseJSON) + cachedRemoteRepoDetails = textutils.unserialiseJSON(responseJSON) + return cachedRemoteRepoDetails end -local function getLocalRepoDetails() - local file = fs.open(COMMIT_FILENAME, "r") +local function getRemoteRepoDetailsCached() + if cachedRemoteRepoDetails == nil then + cachedRemoteRepoDetails = getRemoteRepoDetails() + end + return cachedRemoteRepoDetails +end + +local function getLocalRepoDetails(path) + local file = fs.open(path, "r") if file == nil then print("Local version file not found! Assuming there is an update available.") return "{}" @@ -22,9 +24,9 @@ local function getLocalRepoDetails() return textutils.unserialiseJSON(contents) end -local function saveRepoDetails(repoDetails) +local function saveRepoDetails(repoDetails, path) local serialized = textutils.serializeJSON(repoDetails) - local file = fs.open(COMMIT_FILENAME, "w") + local file = fs.open(path, "w") file.write(serialized) file.close() end @@ -34,8 +36,8 @@ local function deleteExistingFiles() fs.delete("startup") end -local function downloadGitHubFileContents(filepath) - local endpoint = "https://raw.githubusercontent.com/"..OWNER.."/"..REPO.."/refs/heads/"..BRANCH.."/"..filepath +local function downloadGitHubFileByPath(filepath) + local endpoint = "https://raw.githubusercontent.com/"..GITHUB_CONFIG.OWNER.."/"..GITHUB_CONFIG.REPO.."/refs/heads/"..GITHUB_CONFIG.BRANCH.."/"..filepath local response = http.get(endpoint) local contents = response.readAll() local file = fs.open(filepath, "w") @@ -44,7 +46,7 @@ local function downloadGitHubFileContents(filepath) end local function getGitHubTreeDetails(treeSHA) - local endpoint = "https://api.github.com/repos/"..OWNER.."/"..REPO.."/git/trees/"..treeSHA + local endpoint = "https://api.github.com/repos/"..GITHUB_CONFIG.OWNER.."/"..GITHUB_CONFIG.REPO.."/git/trees/"..treeSHA local response = http.get(endpoint) local contents = response.readAll() local treeDetails = textutils.unserialiseJSON(contents) @@ -54,10 +56,11 @@ end local function downloadGitHubTreeRecursively(path, treeSHA) local treeDetails = getGitHubTreeDetails(treeSHA) for _, treeEntry in pairs(treeDetails.tree) do + local subfilePath = path.."/"..treeEntry.path if treeEntry.type == "tree" then - downloadGitHubTreeRecursively(path.."/"..treeEntry.path, treeEntry.sha) - else - downloadGitHubFileContents(path.."/"..treeEntry.path) + downloadGitHubTreeRecursively(subfilePath, treeEntry.sha) + elseif treeEntry.type == "blob" then + downloadGitHubFileByPath(subfilePath) end end end @@ -67,29 +70,28 @@ local function downloadRemoteSrcDirectory(remoteRepoRootTreeSHA) local srcDirectoryName = "src" for _, treeEntry in pairs(remoteRepoRootTreeDetails.tree) do - print(treeEntry.path) - if treeEntry.path == srcDirectoryName then + if treeEntry.path == srcDirectoryName and treeEntry.type == "tree" then downloadGitHubTreeRecursively(srcDirectoryName, treeEntry.sha) end end end -local remoteRepoDetails +--- Checks if there is an update to install +---@return boolean +local function checkForUpdate() + local remoteRepoDetails = getRemoteRepoDetails() + local localRepoDetails = getLocalRepoDetails(UPDATE_CONFIG.LOCAL_REPO_DETAILS_FILENAME) + return localRepoDetails.sha ~= remoteRepoDetails.sha +end +--- Updates and saves the project. src and startup files are deleted and then redownloaded. local function performUpdate() + local remoteRepoDetails = getRemoteRepoDetailsCached() + deleteExistingFiles() downloadRemoteSrcDirectory(remoteRepoDetails.sha) - downloadGitHubFileContents("startup") -end - -local function checkForUpdate() - remoteRepoDetails = getRemoteRepoDetails() - - local localRepoDetails = getLocalRepoDetails() - - local updateAvailable = localRepoDetails.sha ~= remoteRepoDetails.sha - - return updateAvailable + downloadGitHubFileByPath("startup") + saveRepoDetails(remoteRepoDetails, UPDATE_CONFIG.LOCAL_REPO_DETAILS_FILENAME) end _G.UpdateScript = { From c0d641a0b5d616ffcc1b3d677438158fec2a8b3e Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Thu, 2 Jan 2025 20:35:06 -0500 Subject: [PATCH 06/38] enable autoupdate by default on dev --- src/scripts/main.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/main.lua b/src/scripts/main.lua index a83bf2d..3803f38 100644 --- a/src/scripts/main.lua +++ b/src/scripts/main.lua @@ -6,7 +6,7 @@ _G.GITHUB_CONFIG = { _G.UPDATE_CONFIG = { LOCAL_REPO_DETAILS_FILENAME = "/commit.txt", - AUTOUPDATE = false + AUTOUPDATE = true } shell.run("/src/classes/classes.lua") From 0f3b48949c46a9e24473af31fc06587eb40543a0 Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Thu, 2 Jan 2025 21:09:40 -0500 Subject: [PATCH 07/38] Disable autoupdate by default --- .gitignore | 3 +- reactorController.lua | 946 ---------------------------------------- src/scripts/main.lua | 5 +- src/scripts/update.lua | 34 +- update_reactor.lua | 96 ---- usr/apis/touchpoint.lua | 186 -------- 6 files changed, 18 insertions(+), 1252 deletions(-) delete mode 100644 reactorController.lua delete mode 100644 update_reactor.lua delete mode 100644 usr/apis/touchpoint.lua diff --git a/.gitignore b/.gitignore index 9a97cfb..3435b50 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -reactorConfigSerialized.txt \ No newline at end of file +reactorConfigSerialized.txt +commit.txt \ No newline at end of file diff --git a/reactorController.lua b/reactorController.lua deleted file mode 100644 index d600a68..0000000 --- a/reactorController.lua +++ /dev/null @@ -1,946 +0,0 @@ -local version = "0.51" -local tag = "reactorConfig" ---[[ -Program made by DrunkenKas - See github: https://github.com/Kasra-G/ReactorController/#readme - -The MIT License (MIT) - -Copyright (c) 2021 Kasra Ghaffari - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -]] - -dofile("/usr/apis/touchpoint.lua") - -local reactorVersion, reactor -local mon, monSide -local sizex, sizey, dim, oo, offy -local btnOn, btnOff, invalidDim -local minb, maxb -local rod, rfLost -local storedLastTick, storedThisTick, lastRFT = 0,0,0 -local fuelTemp, caseTemp, fuelUsage, waste, capacity = 0,0,0,0,1 -local t -local displayingGraphMenu = false - -local secondsToAverage = 2 - -local averageStoredThisTick = 0 -local averageLastRFT = 0 -local averageRod = 0 -local averageFuelUsage = 0 -local averageWaste = 0 -local averageFuelTemp = 0 -local averageCaseTemp = 0 -local averageRfLost = 0 - --- table of which graphs to draw -local graphsToDraw = {} - --- table of all the graphs -local graphs = -{ - "Energy Buffer", - "Control Level", - "Temperatures", -} - --- marks the offsets for each graph position --- { XOffset, } -local XOffs = -{ - { 4, true}, - {27, true}, - {50, true}, - {73, true}, - {96, true}, -} - --- Draw a box with no fill -local function drawBox(size, xoff, yoff, color) - if (monSide == nil) then - return - end - local x,y = mon.getCursorPos() - mon.setBackgroundColor(color) - local horizLine = string.rep(" ", size[1]) - mon.setCursorPos(xoff + 1, yoff + 1) - mon.write(horizLine) - mon.setCursorPos(xoff + 1, yoff + size[2]) - mon.write(horizLine) - - -- Draw vertical lines - for i=0, size[2] - 1 do - mon.setCursorPos(xoff + 1, yoff + i + 1) - mon.write(" ") - mon.setCursorPos(xoff + size[1], yoff + i +1) - mon.write(" ") - end - mon.setCursorPos(x,y) - mon.setBackgroundColor(colors.black) -end - ---Draw a filled box -local function drawFilledBox(size, xoff, yoff, colorOut, colorIn) - if (monSide == nil) then - return - end - local horizLine = string.rep(" ", size[1] - 2) - drawBox(size, xoff, yoff, colorOut) - local x,y = mon.getCursorPos() - mon.setBackgroundColor(colorIn) - for i=2, size[2] - 1 do - mon.setCursorPos(xoff + 2, yoff + i) - mon.write(horizLine) - end - mon.setBackgroundColor(colors.black) - mon.setCursorPos(x,y) -end - ---Draws text on the screen -local function drawText(text, x1, y1, backColor, textColor) - if (monSide == nil) then - return - end - local x, y = mon.getCursorPos() - mon.setCursorPos(x1, y1) - mon.setBackgroundColor(backColor) - mon.setTextColor(textColor) - mon.write(text) - mon.setTextColor(colors.white) - mon.setBackgroundColor(colors.black) - mon.setCursorPos(x,y) -end - ---Helper method for adding buttons -local function addButt(name, callBack, size, xoff, yoff, color1, color2) - t:add(name, callBack, - xoff + 1, yoff + 1, - size[1] + xoff, size[2] + yoff, - color1, color2) -end - -local function minAdd10() - minb = math.min(maxb - 10, minb + 10) -end -local function minSub10() - minb = math.max(0, minb - 10) -end -local function maxAdd10() - maxb = math.min(100, maxb + 10) -end -local function maxSub10() - maxb = math.max(minb + 10, maxb - 10) -end - -local function turnOff() - if (btnOn) then - t:toggleButton("Off") - t:toggleButton("On") - btnOff = true - btnOn = false - reactor.setActive(false) - end -end - -local function turnOn() - if (btnOff) then - t:toggleButton("Off") - t:toggleButton("On") - btnOff = false - btnOn = true - reactor.setActive(true) - end -end - ---adds buttons -local function addButtons() - if (sizey == 24) then - oo = 1 - end - addButt("On", turnOn, {8, 3}, dim + 7, 3 + oo, - colors.red, colors.lime) - addButt("Off", turnOff, {8, 3}, dim + 19, 3 + oo, - colors.red, colors.lime) - if (btnOn) then - t:toggleButton("On", true) - else - t:toggleButton("Off", true) - end - if (sizey > 24) then - addButt("+ 10", minAdd10, {8, 3}, dim + 7, 14 + oo, - colors.purple, colors.pink) - addButt(" + 10 ", maxAdd10, {8, 3}, dim + 19, 14 + oo, - colors.magenta, colors.pink) - addButt("- 10", minSub10, {8, 3}, dim + 7, 18 + oo, - colors.purple, colors.pink) - addButt(" - 10 ", maxSub10, {8, 3}, dim + 19, 18 + oo, - colors.magenta, colors.pink) - end -end - ---Resets the monitor -local function resetMon() - if (monSide == nil) then - return - end - mon.setBackgroundColor(colors.black) - mon.clear() - mon.setTextScale(0.5) - mon.setCursorPos(1,1) -end - -local function getPercPower() - return averageStoredThisTick / capacity * 100 -end - -local function rnd(num, dig) - return math.floor(10 ^ dig * num) / (10 ^ dig) -end - -local function getEfficiency() - return averageLastRFT / averageFuelUsage -end - -local function format(num) - if (num >= 1000000000) then - return string.format("%7.3f G", num / 1000000000) - elseif (num >= 1000000) then - return string.format("%7.3f M", num / 1000000) - elseif (num >= 1000) then - return string.format("%7.3f K", num / 1000) - elseif (num >= 1) then - return string.format("%7.3f ", num) - elseif (num >= .001) then - return string.format("%7.3f m", num * 1000) - elseif (num >= .000001) then - return string.format("%7.3f u", num * 1000000) - else - return string.format("%7.3f ", 0) - end -end - - -local function getAvailableXOff() - for i,v in pairs(XOffs) do - if (v[2] and v[1] < dim) then - v[2] = false - return v[1] - end - end - return -1 -end - -local function getXOff(num) - for i,v in pairs(XOffs) do - if (v[1] == num) then - return v - end - end - return nil -end - -local function enableGraph(name) - if (graphsToDraw[name] ~= nil) then - return - end - local e = getAvailableXOff() - if (e ~= -1) then - graphsToDraw[name] = e - if (displayingGraphMenu) then - t:toggleButton(name) - end - end -end - -local function disableGraph(name) - if (graphsToDraw[name] == nil) then - return - end - if (displayingGraphMenu) then - t:toggleButton(name) - end - getXOff(graphsToDraw[name])[2] = true - graphsToDraw[name] = nil -end - -local function toggleGraph(name) - if (graphsToDraw[name] == nil) then - enableGraph(name) - else - disableGraph(name) - end -end - -local function addGraphButtons() - offy = oo - 14 - for i,v in pairs(graphs) do - addButt(v, function() toggleGraph(v) end, {20, 3}, - dim + 7, offy + i * 3 - 1, - colors.red, colors.lime) - if (graphsToDraw[v] ~= nil) then - t:toggleButton(v, true) - end - end -end - -local function drawGraphButtons() - drawBox({sizex - dim - 3, oo - offy - 1}, - dim + 2, offy, colors.orange) - drawText(" Graph Controls ", - dim + 7, offy + 1, - colors.black, colors.orange) -end - -local function drawEnergyBuffer(xoff) - local srf = sizey - 9 - local off = xoff - local right = off + 19 < dim - local poff = right and off + 15 or off - 6 - - drawBox({15, srf + 2}, off - 1, 4, colors.gray) - local pwr = math.floor(getPercPower() / 100 - * (srf)) - drawFilledBox({13, srf}, off, 5, - colors.red, colors.red) - local rndpw = rnd(getPercPower(), 2) - local color = (rndpw < maxb and rndpw > minb) and colors.green - or (rndpw >= maxb and colors.orange or colors.blue) - if (pwr > 0) then - drawFilledBox({13, pwr + 1}, off, srf + 4 - pwr, - color, color) - end - --drawPoint(off + 14, srf + 5 - pwr, pwr > 0 and color or colors.red) - drawText(string.format(right and "%.2f%%" or "%5.2f%%", rndpw), poff, srf + 5 - pwr, - colors.black, color) - drawText("Energy Buffer", off + 1, 4, - colors.black, colors.orange) - drawText(format(averageStoredThisTick).."RF", off + 1, srf + 5 - pwr, - pwr > 0 and color or colors.red, colors.black) -end - -local function drawControlLevel(xoff) - local srf = sizey - 9 - local off = xoff - drawBox({15, srf + 2}, off - 1, 4, colors.gray) - drawFilledBox({13, srf}, off, 5, - colors.yellow, colors.yellow) - local rodTr = math.floor(averageRod / 100 - * (srf)) - drawText("Control Level", off + 1, 4, - colors.black, colors.orange) - if (rodTr > 0) then - drawFilledBox({9, rodTr}, off + 2, 5, - colors.white, colors.white) - end - drawText(string.format("%6.2f%%", averageRod), off + 4, rodTr > 0 and rodTr + 5 or 6, - rodTr > 0 and colors.white or colors.yellow, colors.black) - -end - -local function drawTemperatures(xoff) - local srf = sizey - 9 - local off = xoff - drawBox({15, srf + 2}, off, 4, colors.gray) - --drawFilledBox({12, srf}, off, 5, - -- colors.red, colors.red) - - local tempUnit = (reactorVersion == "Bigger Reactors") and "K" or "C" - local tempFormat = "%4s"..tempUnit - - local fuelRnd = math.floor(averageFuelTemp) - local caseRnd = math.floor(averageCaseTemp) - local fuelTr = math.floor(fuelRnd / 2000 - * (srf)) - local caseTr = math.floor(caseRnd / 2000 - * (srf)) - drawText(" Case ", off + 2, 5, - colors.gray, colors.lightBlue) - drawText(" Fuel ", off + 9, 5, - colors.gray, colors.magenta) - if (fuelTr > 0) then - fuelTr = math.min(fuelTr, srf) - drawFilledBox({6, fuelTr}, off + 8, srf + 5 - fuelTr, - colors.magenta, colors.magenta) - - drawText(string.format(tempFormat, fuelRnd..""), - off + 10, srf + 6 - fuelTr, - colors.magenta, colors.black) - else - drawText(string.format(tempFormat, fuelRnd..""), - off + 10, srf + 5, - colors.black, colors.magenta) - end - - if (caseTr > 0) then - caseTr = math.min(caseTr, srf) - drawFilledBox({6, caseTr}, off + 1, srf + 5 - caseTr, - colors.lightBlue, colors.lightBlue) - drawText(string.format(tempFormat, caseRnd..""), - off + 3, srf + 6 - caseTr, - colors.lightBlue, colors.black) - else - drawText(string.format(tempFormat, caseRnd..""), - off + 3, srf + 5, - colors.black, colors.lightBlue) - end - - drawText("Temperatures", off + 2, 4, - colors.black, colors.orange) - drawBox({1, srf}, off + 7, 5, - colors.gray) -end - -local function drawGraph(name, offset) - if (name == "Energy Buffer") then - drawEnergyBuffer(offset) - elseif (name == "Control Level") then - drawControlLevel(offset) - elseif (name == "Temperatures") then - drawTemperatures(offset) - end -end - -local function drawGraphs() - for i,v in pairs(graphsToDraw) do - if (v + 15 < dim) then - drawGraph(i,v) - end - end -end - -local function drawStatus() - if (dim <= -1) then - return - end - drawBox({dim, sizey - 2}, - 1, 1, colors.lightBlue) - drawText(" Reactor Graphs ", dim - 18, 2, - colors.black, colors.lightBlue) - drawGraphs() -end - -local function drawControls() - if (sizey == 24) then - drawBox({sizex - dim - 3, 9}, dim + 2, oo, - colors.cyan) - drawText(" Reactor Controls ", dim + 7, oo + 1, - colors.black, colors.cyan) - drawText("Reactor "..(btnOn and "Online" or "Offline"), - dim + 10, 3 + oo, - colors.black, btnOn and colors.green or colors.red) - return - end - - drawBox({sizex - dim - 3, 23}, dim + 2, oo, - colors.cyan) - drawText(" Reactor Controls ", dim + 7, oo + 1, - colors.black, colors.cyan) - drawFilledBox({20, 3}, dim + 7, 8 + oo, - colors.red, colors.red) - drawFilledBox({(maxb - minb) / 5, 3}, - dim + 7 + minb / 5, 8 + oo, - colors.green, colors.green) - drawText(string.format("%3s", minb.."%"), dim + 6 + minb / 5, 12 + oo, - colors.black, colors.purple) - drawText(maxb.."%", dim + 8 + maxb / 5, 12 + oo, - colors.black, colors.magenta) - drawText("Buffer Target Range", dim + 8, 8 + oo, - colors.black, colors.orange) - drawText("Min", dim + 10, 14 + oo, - colors.black, colors.purple) - drawText("Max", dim + 22, 14 + oo, - colors.black, colors.magenta) - drawText("Reactor ".. (btnOn and "Online" or "Offline"), - dim + 10, 3 + oo, - colors.black, btnOn and colors.green or colors.red) -end - -local function drawStatistics() - local oS = sizey - 13 - drawBox({sizex - dim - 3, sizey - oS - 1}, dim + 2, oS, - colors.blue) - drawText(" Reactor Statistics ", dim + 7, oS + 1, - colors.black, colors.blue) - - --statistics - drawText("Generating : " - ..format(averageLastRFT).."RF/t", dim + 5, oS + 3, - colors.black, colors.green) - drawText("RF Drain " - ..(averageStoredThisTick <= averageLastRFT and "> " or ": ") - ..format(averageRfLost) - .."RF/t", dim + 5, oS + 5, - colors.black, colors.red) - drawText("Efficiency : " - ..format(getEfficiency()).."RF/B", - dim + 5, oS + 7, - colors.black, colors.green) - drawText("Fuel Usage : " - ..format(averageFuelUsage) - .."B/t", dim + 5, oS + 9, - colors.black, colors.green) - drawText("Waste : " - ..string.format("%7d mB", waste), - dim + 5, oS + 11, - colors.black, colors.green) -end - ---Draw a scene -local function drawScene() - if (monSide == nil) then - return - end - if (invalidDim) then - mon.write("Invalid Monitor Dimensions") - return - end - - if (displayingGraphMenu) then - drawGraphButtons() - end - drawControls() - drawStatus() - drawStatistics() - t:draw() -end - ---returns the side that a given peripheral type is connected to -local function getPeripheral(name) - for i,v in pairs(peripheral.getNames()) do - if (peripheral.getType(v) == name) then - return v - end - end - return "" -end - ---Creates all the buttons and determines monitor size -local function initMon() - monSide = getPeripheral("monitor") - if (monSide == nil or monSide == "") then - monSide = nil - return - end - - mon = peripheral.wrap(monSide) - - if mon == nil then - monSide = nil - return - end - - resetMon() - t = touchpoint.new(monSide) - sizex, sizey = mon.getSize() - oo = sizey - 37 - dim = sizex - 33 - - if (sizex == 36) then - dim = -1 - end - if (pcall(addGraphButtons)) then - displayingGraphMenu = true - else - t = touchpoint.new(monSide) - displayingGraphMenu = false - end - local rtn = pcall(addButtons) - if (not rtn) then - t = touchpoint.new(monSide) - invalidDim = true - else - invalidDim = false - end -end - -local function setRods(level) - level = math.max(level, 0) - level = math.min(level, 100) - reactor.setAllControlRodLevels(level) -end - -local function lerp(start, finish, t) - -- Ensure t is in the range [0, 1] - t = math.max(0, math.min(1, t)) - - -- Calculate the linear interpolation - return (1 - t) * start + t * finish -end - --- Function to calculate the average of an array of values -local function calculateAverage(array) - local sum = 0 - for _, value in ipairs(array) do - sum = sum + value - end - return sum / #array -end - --- Define PID controller parameters -local pid = { - setpointRFT = 0, -- Target RFT - setpointRF = 0, -- Target RF - Kp = -.08, -- Proportional gain - Ki = -.0015, -- Integral gain - Kd = -.01, -- Derivative gain - integral = 0, -- Integral term accumulator - lastError = 0, -- Last error for derivative term -} - -local function iteratePID(pid, error) - -- Proportional term - local P = pid.Kp * error - - -- Integral term - pid.integral = pid.integral + pid.Ki * error - pid.integral = math.max(math.min(100, pid.integral), -100) - - -- Derivative term - local derivative = pid.Kd * (error - pid.lastError) - - -- Calculate control rod level - local rodLevel = math.max(math.min(P + pid.integral + derivative, 100), 0) - - -- Update PID controller state - pid.lastError = error - return rodLevel -end - -local function updateRods() - if (not btnOn) then - return - end - local currentRF = storedThisTick - local diffb = maxb - minb - local minRF = minb / 100 * capacity - local diffRF = diffb / 100 * capacity - local diffr = diffb / 100 - local targetRFT = rfLost - local currentRFT = lastRFT - local targetRF = diffRF / 2 + minRF - - pid.setpointRFT = targetRFT - pid.setpointRF = targetRF / capacity * 1000 - - local errorRFT = pid.setpointRFT - currentRFT - local errorRF = pid.setpointRF - currentRF / capacity * 1000 - - local W_RFT = lerp(1, 0, (math.abs(targetRF - currentRF) / capacity / (diffr / 4))) - W_RFT = math.max(math.min(W_RFT, 1), 0) - - local W_RF = (1 - W_RFT) -- Adjust the weight for energy error - - -- Combine the errors with weights - local combinedError = W_RFT * errorRFT + W_RF * errorRF - local error = combinedError - local rftRodLevel = iteratePID(pid, error) - - -- Set control rod levels - setRods(rftRodLevel) -end - --- Saves the configuration of the reactor controller -local function saveToConfig() - local file = fs.open(tag.."Serialized.txt", "w") - local configs = { - maxb = maxb, - minb = minb, - rod = rod, - btnOn = btnOn, - graphsToDraw = graphsToDraw, - XOffs = XOffs, - } - local serialized = textutils.serialize(configs) - file.write(serialized) - file.close() -end - -local storedThisTickValues = {} -local lastRFTValues = {} -local rodValues = {} -local fuelUsageValues = {} -local wasteValues = {} -local fuelTempValues = {} -local caseTempValues = {} -local rfLostValues = {} - -local function updateStats() - storedLastTick = storedThisTick - if (reactorVersion == "Big Reactors") then - storedThisTick = reactor.getEnergyStored() - lastRFT = reactor.getEnergyProducedLastTick() - rod = reactor.getControlRodLevel(0) - fuelUsage = reactor.getFuelConsumedLastTick() / 1000 - waste = reactor.getWasteAmount() - fuelTemp = reactor.getFuelTemperature() - caseTemp = reactor.getCasingTemperature() - -- Big Reactors doesn't give us a way to directly query RF capacity through CC APIs - capacity = math.max(capacity, reactor.getEnergyStored) - elseif (reactorVersion == "Extreme Reactors") then - local bat = reactor.getEnergyStats() - local fuel = reactor.getFuelStats() - - storedThisTick = bat.energyStored - lastRFT = bat.energyProducedLastTick - capacity = bat.energyCapacity - rod = reactor.getControlRodLevel(0) - fuelUsage = fuel.fuelConsumedLastTick / 1000 - waste = reactor.getWasteAmount() - fuelTemp = reactor.getFuelTemperature() - caseTemp = reactor.getCasingTemperature() - elseif (reactorVersion == "Bigger Reactors") then - storedThisTick = reactor.battery().stored() - lastRFT = reactor.battery().producedLastTick() - capacity = reactor.battery().capacity() - rod = reactor.getControlRod(0).level() - fuelUsage = reactor.fuelTank().burnedLastTick() / 1000 - waste = reactor.fuelTank().waste() - fuelTemp = reactor.fuelTemperature() - caseTemp = reactor.casingTemperature() - end - rfLost = lastRFT + storedLastTick - storedThisTick - -- Add the values to the arrays - table.insert(storedThisTickValues, storedThisTick) - table.insert(lastRFTValues, lastRFT) - table.insert(rodValues, rod) - table.insert(fuelUsageValues, fuelUsage) - table.insert(wasteValues, waste) - table.insert(fuelTempValues, fuelTemp) - table.insert(caseTempValues, caseTemp) - table.insert(rfLostValues, rfLost) - - local maxIterations = 20 * secondsToAverage - while #storedThisTickValues > maxIterations do - table.remove(storedThisTickValues, 1) - table.remove(lastRFTValues, 1) - table.remove(rodValues, 1) - table.remove(fuelUsageValues, 1) - table.remove(wasteValues, 1) - table.remove(fuelTempValues, 1) - table.remove(caseTempValues, 1) - table.remove(rfLostValues, 1) - end - - -- Calculate running averages - averageStoredThisTick = calculateAverage(storedThisTickValues) - averageLastRFT = calculateAverage(lastRFTValues) - averageRod = calculateAverage(rodValues) - averageFuelUsage = calculateAverage(fuelUsageValues) - averageWaste = calculateAverage(wasteValues) - averageFuelTemp = calculateAverage(fuelTempValues) - averageCaseTemp = calculateAverage(caseTempValues) - averageRfLost = calculateAverage(rfLostValues) -end - ---Initialize variables from either a config file or the defaults -local function loadFromConfig() - invalidDim = false - local legacyConfigExists = fs.exists(tag..".txt") - local newConfigExists = fs.exists(tag.."Serialized.txt") - if (newConfigExists) then - local file = fs.open(tag.."Serialized.txt", "r") - print("Config file "..tag.."Serialized.txt found! Using configurated settings") - - local serialized = file.readAll() - local deserialized = textutils.unserialise(serialized) - - maxb = deserialized.maxb - minb = deserialized.minb - rod = deserialized.rod - btnOn = deserialized.btnOn - graphsToDraw = deserialized.graphsToDraw - XOffs = deserialized.XOffs - elseif (legacyConfigExists) then - local file = fs.open(tag..".txt", "r") - local calibrated = file.readLine() == "true" - - --read calibration information - if (calibrated) then - _ = tonumber(file.readLine()) - _ = tonumber(file.readLine()) - end - maxb = tonumber(file.readLine()) - minb = tonumber(file.readLine()) - rod = tonumber(file.readLine()) - btnOn = file.readLine() == "true" - - --read Graph data - for i in pairs(XOffs) do - local graph = file.readLine() - local v1 = tonumber(file.readLine()) - local v2 = true - if (graph ~= "nil") then - v2 = false - graphsToDraw[graph] = v1 - end - - XOffs[i] = {v1, v2} - - end - file.close() - else - print("Config file not found, generating default settings!") - - maxb = 70 - minb = 30 - rod = 80 - btnOn = false - if (monSide == nil) then - btnOn = true - end - sizex, sizey = 100, 52 - dim = sizex - 33 - oo = sizey - 37 - enableGraph("Energy Buffer") - enableGraph("Control Level") - enableGraph("Temperatures") - end - btnOff = not btnOn - reactor.setActive(btnOn) -end - -local function startTimer(ticksToUpdate, callback) - local timeToUpdate = ticksToUpdate * 0.05 - local id = os.startTimer(timeToUpdate) - local fun = function(event) - if (event[1] == "timer" and event[2] == id) then - id = os.startTimer(timeToUpdate) - callback() - end - end - return fun -end - - --- Main loop, handles all the events -local function loop() - local ticksToUpdateStats = 1 - local ticksToRedraw = 4 - - local hasClicked = false - - local updateStatsTick = startTimer( - ticksToUpdateStats, - function() - updateStats() - updateRods() - end - ) - local redrawTick = startTimer( - ticksToRedraw, - function() - if (not hasClicked) then - resetMon() - drawScene() - end - hasClicked = false - end - ) - local handleResize = function(event) - if (event[1] == "monitor_resize") then - initMon() - end - end - local handleClick = function(event) - if (event[1] == "button_click") then - t.buttonList[event[2]].func() - saveToConfig() - resetMon() - drawScene() - hasClicked = true - end - end - while (true) do - local event = (monSide == nil) and { os.pullEvent() } or { t:handleEvents() } - - updateStatsTick(event) - redrawTick(event) - handleResize(event) - handleClick(event) - end -end - -local function detectReactor() - -- Bigger Reactors V1. - local reactor_bigger_v1 = getPeripheral("bigger-reactor") - reactor = reactor_bigger_v1 ~= nil and peripheral.wrap(reactor_bigger_v1) - if (reactor ~= nil) then - reactorVersion = "Bigger Reactors" - return true - end - - -- Bigger Reactors V2 - local reactor_bigger_v2 = getPeripheral("BiggerReactors_Reactor") - reactor = reactor_bigger_v2 ~= nil and peripheral.wrap(reactor_bigger_v2) - if (reactor ~= nil) then - reactorVersion = "Bigger Reactors" - return true - end - - -- Big Reactors or Extreme Reactors - local reactor_extreme_or_big = getPeripheral("BigReactors-Reactor") - reactor = reactor_extreme_or_big ~= nil and peripheral.wrap(reactor_extreme_or_big) - if (reactor ~= nil) then - reactorVersion = (reactor.mbIsConnected ~= nil) and "Extreme Reactors" or "Big Reactors" - return true - end - return false -end - ---Entry point -local function main() - term.setBackgroundColor(colors.black) - term.clear() - term.setCursorPos(1,1) - - local reactorDetected = false - while (not reactorDetected) do - reactorDetected = detectReactor() - if (not reactorDetected) then - print("Reactor not detected! Trying again...") - sleep(1) - end - end - - print("Reactor detected! Proceeding with initialization ") - - print("Loading config...") - loadFromConfig() - print("Initializing monitor if connected...") - initMon() - print("Writing config to disk...") - saveToConfig() - print("Reactor initialization done! Starting controller") - sleep(2) - - term.clear() - term.setCursorPos(1,1) - print("Reactor Controller Version "..version) - print("Reactor Mod: "..reactorVersion) - --main loop - - loop() -end - -main() - -print("script exited") -sleep(1) \ No newline at end of file diff --git a/src/scripts/main.lua b/src/scripts/main.lua index 3803f38..2b68487 100644 --- a/src/scripts/main.lua +++ b/src/scripts/main.lua @@ -6,7 +6,7 @@ _G.GITHUB_CONFIG = { _G.UPDATE_CONFIG = { LOCAL_REPO_DETAILS_FILENAME = "/commit.txt", - AUTOUPDATE = true + AUTOUPDATE = false } shell.run("/src/classes/classes.lua") @@ -26,6 +26,9 @@ if updateAvailable then if UPDATE_CONFIG.AUTOUPDATE then print("Automatic update is enabled! Updating...") _G.UpdateScript.performUpdate() + print("Finished update! Rebooting...") + sleep(1) + os.reboot() else print("Automatic update skipped because it's not enabled!") end diff --git a/src/scripts/update.lua b/src/scripts/update.lua index 0fd33c9..63e0035 100644 --- a/src/scripts/update.lua +++ b/src/scripts/update.lua @@ -1,23 +1,14 @@ -local cachedRemoteRepoDetails -local function getRemoteRepoDetails() +local function getRemoteRepoSHA() local response = http.get("https://api.github.com/repos/"..GITHUB_CONFIG.OWNER.."/"..GITHUB_CONFIG.REPO.."/commits/"..GITHUB_CONFIG.BRANCH) local responseJSON = response.readAll() - cachedRemoteRepoDetails = textutils.unserialiseJSON(responseJSON) - return cachedRemoteRepoDetails + return textutils.unserialiseJSON(responseJSON).sha end -local function getRemoteRepoDetailsCached() - if cachedRemoteRepoDetails == nil then - cachedRemoteRepoDetails = getRemoteRepoDetails() - end - return cachedRemoteRepoDetails -end - -local function getLocalRepoDetails(path) - local file = fs.open(path, "r") +local function getLocalRepoSHA(filepath) + local file = fs.open(filepath, "r") if file == nil then print("Local version file not found! Assuming there is an update available.") - return "{}" + return "" end local contents = file.readAll() file.close() @@ -49,8 +40,7 @@ local function getGitHubTreeDetails(treeSHA) local endpoint = "https://api.github.com/repos/"..GITHUB_CONFIG.OWNER.."/"..GITHUB_CONFIG.REPO.."/git/trees/"..treeSHA local response = http.get(endpoint) local contents = response.readAll() - local treeDetails = textutils.unserialiseJSON(contents) - return treeDetails + return textutils.unserialiseJSON(contents) end local function downloadGitHubTreeRecursively(path, treeSHA) @@ -79,19 +69,19 @@ end --- Checks if there is an update to install ---@return boolean local function checkForUpdate() - local remoteRepoDetails = getRemoteRepoDetails() - local localRepoDetails = getLocalRepoDetails(UPDATE_CONFIG.LOCAL_REPO_DETAILS_FILENAME) - return localRepoDetails.sha ~= remoteRepoDetails.sha + local remoteRepoSHA = getRemoteRepoSHA() + local localRepoSHA = getLocalRepoSHA(UPDATE_CONFIG.LOCAL_REPO_DETAILS_FILENAME) + return localRepoSHA ~= remoteRepoSHA end --- Updates and saves the project. src and startup files are deleted and then redownloaded. local function performUpdate() - local remoteRepoDetails = getRemoteRepoDetailsCached() + local remoteRepoSHA = getRemoteRepoSHA() deleteExistingFiles() - downloadRemoteSrcDirectory(remoteRepoDetails.sha) + downloadRemoteSrcDirectory(remoteRepoSHA) downloadGitHubFileByPath("startup") - saveRepoDetails(remoteRepoDetails, UPDATE_CONFIG.LOCAL_REPO_DETAILS_FILENAME) + saveRepoDetails(remoteRepoSHA, UPDATE_CONFIG.LOCAL_REPO_DETAILS_FILENAME) end _G.UpdateScript = { diff --git a/update_reactor.lua b/update_reactor.lua deleted file mode 100644 index 26ae000..0000000 --- a/update_reactor.lua +++ /dev/null @@ -1,96 +0,0 @@ -local version = "1.0" - ---Github: https://github.com/Kasra-G/ReactorController/#readme - --- ["filename"] = "pastebinCode" -local filesToUpdate = { - ["/reactorController.lua"] = "b17hfTqe", - ["/usr/apis/touchpoint.lua"] = "nx9pkLbJ", - ["/update_reactor.lua"] = "w6vVtrLb", -} - -local function getPastebinFileContents(filename, pastebinCode) - local tempFilename = "/temp" .. filename - - -- Avoid calling pastebin API directly to be more robust towards any future API changes - shell.run("pastebin", "get", pastebinCode, tempFilename) - local tempFile = fs.open(tempFilename, "r") - - if not tempFile then - return nil - end - - local fileContents = tempFile.readAll() - tempFile.close() - fs.delete(tempFilename) - return fileContents -end - --- Requires HTTP to be enabled -local function getVersion(fileContents) - if not fileContents then - return nil - end - - local _, numberChars = fileContents:lower():find('local version = "') - - if not numberChars then - return nil - end - - local fileVersion = "" - local char = "" - - while char ~= '"' do - numberChars = numberChars + 1 - char = fileContents:sub(numberChars,numberChars) - fileVersion = fileVersion .. char - end - - fileVersion = fileVersion:sub(1,#fileVersion-1) -- Remove quotes around the version number - return fileVersion -end - -local function updateFile(filename, pastebinCode) - if fs.isDir(filename) then - print("[Error] " .. filename .. " is a directory") - return - end - local pastebinContents = getPastebinFileContents(filename, pastebinCode) - - if not pastebinContents then - print("[Error] " .. filename .. " has an invalid link") - end - - local pastebinVersion = getVersion(pastebinContents) - if not pastebinVersion then - print("[Error] the pastebin code for " .. filename .. " does not have a version variable") - return - end - - local localVersion = nil - if fs.exists(filename) then - local localFile = fs.open(filename,"r") - localVersion = getVersion(localFile.readAll()) - localFile.close() - end - - if localVersion ~= pastebinVersion then - local localFile = fs.open(filename,"w") - localFile.write(pastebinContents) - localFile.close() - print("[Success] " .. filename .. " has been updated to version " .. pastebinVersion) - elseif pastebinVersion == localVersion then - print("[Success] No update required: " .. filename .. " is already the latest version") - end -end - -local function main() - fs.makeDir("/usr") - fs.makeDir("/usr/apis") - for filename, pastebinCode in pairs(filesToUpdate) do - updateFile(filename, pastebinCode) - end -end - -main() \ No newline at end of file diff --git a/usr/apis/touchpoint.lua b/usr/apis/touchpoint.lua deleted file mode 100644 index 155de0f..0000000 --- a/usr/apis/touchpoint.lua +++ /dev/null @@ -1,186 +0,0 @@ -local version = "1.01" ---[[ -The MIT License (MIT) - -Copyright (c) 2013 Lyqyd - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -Edited by DrunkenKas. - See Github: https://github.com/Kasra-G/ReactorController/#readme ---]] - -local function setupLabel(buttonLen, minY, maxY, name) - local labelTable = {} - if type(name) == "table" then - for i = 1, #name do - labelTable[i] = name[i] - end - name = name.label - elseif type(name) == "string" then - local buttonText = string.sub(name, 1, buttonLen - 2) - if #buttonText < #name then - buttonText = " "..buttonText.." " - else - local labelLine = string.rep(" ", math.floor((buttonLen - #buttonText) / 2))..buttonText - buttonText = labelLine..string.rep(" ", buttonLen - #labelLine) - end - for i = 1, maxY - minY + 1 do - if maxY == minY or i == math.floor((maxY - minY) / 2) + 1 then - labelTable[i] = buttonText - else - labelTable[i] = string.rep(" ", buttonLen) - end - end - end - return labelTable, name -end - -local Button = { - draw = function(self) - local old = term.redirect(self.mon) - term.setTextColor(colors.white) - term.setBackgroundColor(colors.black) - for name, buttonData in pairs(self.buttonList) do - if buttonData.active then - term.setBackgroundColor(buttonData.activeColor) - term.setTextColor(buttonData.activeText) - else - term.setBackgroundColor(buttonData.inactiveColor) - term.setTextColor(buttonData.inactiveText) - end - for i = buttonData.yMin, buttonData.yMax do - term.setCursorPos(buttonData.xMin, i) - term.write(buttonData.label[i - buttonData.yMin + 1]) - end - end - if old then - term.redirect(old) - else - term.restore() - end - end, - add = function(self, name, func, xMin, yMin, xMax, yMax, inactiveColor, activeColor, inactiveText, activeText) - local label, name = setupLabel(xMax - xMin + 1, yMin, yMax, name) - if self.buttonList[name] then error("button already exists", 2) end - local x, y = self.mon.getSize() - if xMin < 1 or yMin < 1 or xMax > x or yMax > y then error("button out of bounds", 2) end - self.buttonList[name] = { - func = func, - xMin = xMin, - yMin = yMin, - xMax = xMax, - yMax = yMax, - active = false, - inactiveColor = inactiveColor or colors.red, - activeColor = activeColor or colors.lime, - inactiveText = inactiveText or colors.white, - activeText = activeText or colors.white, - label = label, - } - for i = xMin, xMax do - for j = yMin, yMax do - if self.clickMap[i][j] ~= nil then - --undo changes - for k = xMin, xMax do - for l = yMin, yMax do - if self.clickMap[k][l] == name then - self.clickMap[k][l] = nil - end - end - end - self.buttonList[name] = nil - error("overlapping button", 2) - end - self.clickMap[i][j] = name - end - end - end, - remove = function(self, name) - if self.buttonList[name] then - local button = self.buttonList[name] - for i = button.xMin, button.xMax do - for j = button.yMin, button.yMax do - self.clickMap[i][j] = nil - end - end - self.buttonList[name] = nil - end - end, - run = function(self) - while true do - self:draw() - local event = {self:handleEvents(os.pullEvent(self.side == "term" and "mouse_click" or "monitor_touch"))} - if event[1] == "button_click" then - self.buttonList[event[2]].func() - end - end - end, - handleEvents = function(self, ...) - local event = {...} - if #event == 0 then event = {os.pullEvent()} end - if (self.side == "term" and event[1] == "mouse_click") or (self.side ~= "term" and event[1] == "monitor_touch" and event[2] == self.side) then - local clicked = self.clickMap[event[3]][event[4]] - if clicked and self.buttonList[clicked] then - return "button_click", clicked - end - end - return unpack(event) - end, - toggleButton = function(self, name, noDraw) - self.buttonList[name].active = not self.buttonList[name].active - if not noDraw then self:draw() end - end, - flash = function(self, name, duration) - self:toggleButton(name) - sleep(tonumber(duration) or 0.15) - self:toggleButton(name) - end, - rename = function(self, name, newName) - self.buttonList[name].label, newName = setupLabel(self.buttonList[name].xMax - self.buttonList[name].xMin + 1, self.buttonList[name].yMin, self.buttonList[name].yMax, newName) - if not self.buttonList[name] then error("no such button", 2) end - if name ~= newName then - self.buttonList[newName] = self.buttonList[name] - self.buttonList[name] = nil - for i = self.buttonList[newName].xMin, self.buttonList[newName].xMax do - for j = self.buttonList[newName].yMin, self.buttonList[newName].yMax do - self.clickMap[i][j] = newName - end - end - end - self:draw() - end, -} - -function new(monSide) - local buttonInstance = { - side = monSide or "term", - mon = monSide and peripheral.wrap(monSide) or term.current(), - buttonList = {}, - clickMap = {}, - } - local x, y = buttonInstance.mon.getSize() - for i = 1, x do - buttonInstance.clickMap[i] = {} - end - setmetatable(buttonInstance, {__index = Button}) - return buttonInstance -end - -touchpoint = {new = new} \ No newline at end of file From ee43e3db7b748abbce3f27fbeebc00599a15e16b Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Thu, 2 Jan 2025 21:11:51 -0500 Subject: [PATCH 08/38] rename --- src/scripts/update.lua | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/scripts/update.lua b/src/scripts/update.lua index 63e0035..1c679af 100644 --- a/src/scripts/update.lua +++ b/src/scripts/update.lua @@ -15,10 +15,9 @@ local function getLocalRepoSHA(filepath) return textutils.unserialiseJSON(contents) end -local function saveRepoDetails(repoDetails, path) - local serialized = textutils.serializeJSON(repoDetails) +local function saveRepoSHA(repoSHA, path) local file = fs.open(path, "w") - file.write(serialized) + file.write(textutils.serializeJSON(repoSHA)) file.close() end @@ -81,11 +80,11 @@ local function performUpdate() deleteExistingFiles() downloadRemoteSrcDirectory(remoteRepoSHA) downloadGitHubFileByPath("startup") - saveRepoDetails(remoteRepoSHA, UPDATE_CONFIG.LOCAL_REPO_DETAILS_FILENAME) + saveRepoSHA(remoteRepoSHA, UPDATE_CONFIG.LOCAL_REPO_DETAILS_FILENAME) end _G.UpdateScript = { checkForUpdate = checkForUpdate, performUpdate = performUpdate, - saveRepoDetails = saveRepoDetails, + saveRepoDetails = saveRepoSHA, } \ No newline at end of file From 3e263baa781f8b021591d29258deec3d93fd9bde Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Fri, 3 Jan 2025 00:12:35 -0500 Subject: [PATCH 09/38] config stuff --- .gitignore | 5 +- src/classes/classes.lua | 7 -- src/classes/monitor.lua | 2 +- src/classes/navbar.lua | 2 +- src/classes/page.lua | 2 +- src/classes/touchpoint.lua | 2 +- src/config/projectConfigs.lua | 3 + src/constants/projectConstants.lua | 5 ++ src/scripts/controller.lua | 2 +- src/scripts/main.lua | 17 ++-- src/scripts/update.lua | 14 ++-- src/util/config.lua | 121 +++++++++++++++++++++++++++++ src/util/draw.lua | 2 +- 13 files changed, 152 insertions(+), 32 deletions(-) create mode 100644 src/config/projectConfigs.lua create mode 100644 src/constants/projectConstants.lua create mode 100644 src/util/config.lua diff --git a/.gitignore b/.gitignore index 3435b50..52fd557 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ reactorConfigSerialized.txt -commit.txt \ No newline at end of file +commit.txt +/overrides +/defaults +/state \ No newline at end of file diff --git a/src/classes/classes.lua b/src/classes/classes.lua index 3927903..66c1886 100644 --- a/src/classes/classes.lua +++ b/src/classes/classes.lua @@ -124,10 +124,3 @@ _G.Vector2 = { zero = Vector2.new(0, 0), one = Vector2.new(1, 1), } - -local test = _G.Vector2.one + _G.Vector2.one -print(test.x, test.y) -test = test + 1 -print(test.x, test.y) -test = test - 2 -print(test.x, test.y) \ No newline at end of file diff --git a/src/classes/monitor.lua b/src/classes/monitor.lua index 376efc7..dc82a43 100644 --- a/src/classes/monitor.lua +++ b/src/classes/monitor.lua @@ -69,4 +69,4 @@ end _G.Monitor = { new = new -} \ No newline at end of file +} diff --git a/src/classes/navbar.lua b/src/classes/navbar.lua index 7a86c17..349a2d8 100644 --- a/src/classes/navbar.lua +++ b/src/classes/navbar.lua @@ -36,4 +36,4 @@ end _G.Monitor = { new = new -} \ No newline at end of file +} diff --git a/src/classes/page.lua b/src/classes/page.lua index 1809372..dfe8290 100644 --- a/src/classes/page.lua +++ b/src/classes/page.lua @@ -64,4 +64,4 @@ end _G.Monitor = { new = new -} \ No newline at end of file +} diff --git a/src/classes/touchpoint.lua b/src/classes/touchpoint.lua index d0d25a6..fe4f231 100644 --- a/src/classes/touchpoint.lua +++ b/src/classes/touchpoint.lua @@ -184,4 +184,4 @@ local function new(monSide) return touchpointInstance end -_G.Touchpoint = {new = new} \ No newline at end of file +_G.Touchpoint = {new = new} diff --git a/src/config/projectConfigs.lua b/src/config/projectConfigs.lua new file mode 100644 index 0000000..9e6cc2f --- /dev/null +++ b/src/config/projectConfigs.lua @@ -0,0 +1,3 @@ +_G.UPDATE_CONFIG = { + AUTOUPDATE = false, +} diff --git a/src/constants/projectConstants.lua b/src/constants/projectConstants.lua new file mode 100644 index 0000000..d3218c8 --- /dev/null +++ b/src/constants/projectConstants.lua @@ -0,0 +1,5 @@ +_G.GITHUB_CONSTANTS = { + OWNER = "Kasra-G", + REPO = "ReactorController", + BRANCH = "development", +} diff --git a/src/scripts/controller.lua b/src/scripts/controller.lua index f6119c1..b772803 100644 --- a/src/scripts/controller.lua +++ b/src/scripts/controller.lua @@ -1110,4 +1110,4 @@ function _G.main() --main loop loop() -end \ No newline at end of file +end diff --git a/src/scripts/main.lua b/src/scripts/main.lua index 2b68487..6551bc4 100644 --- a/src/scripts/main.lua +++ b/src/scripts/main.lua @@ -1,15 +1,6 @@ -_G.GITHUB_CONFIG = { - OWNER = "Kasra-G", - REPO = "ReactorController", - BRANCH = "development", -} - -_G.UPDATE_CONFIG = { - LOCAL_REPO_DETAILS_FILENAME = "/commit.txt", - AUTOUPDATE = false -} - shell.run("/src/classes/classes.lua") +shell.run("/src/config/projectConfigs.lua") +shell.run("/src/constants/projectConstants.lua") shell.run("/src/util/draw.lua") shell.run("/src/classes/touchpoint.lua") shell.run("/src/classes/page.lua") @@ -17,6 +8,7 @@ shell.run("/src/classes/navbar.lua") shell.run("/src/classes/monitor.lua") shell.run("/src/scripts/controller.lua") shell.run("/src/scripts/update.lua") +shell.run("/src/util/config.lua") -- For now, do update check here local updateAvailable = _G.UpdateScript.checkForUpdate() @@ -31,7 +23,8 @@ if updateAvailable then os.reboot() else print("Automatic update skipped because it's not enabled!") + sleep(1) end end -- For now, main() is in controller.lua -main() \ No newline at end of file +main() diff --git a/src/scripts/update.lua b/src/scripts/update.lua index 1c679af..eba2ad8 100644 --- a/src/scripts/update.lua +++ b/src/scripts/update.lua @@ -1,5 +1,7 @@ +local LOCAL_REPO_DETAILS_FILENAME = "/commit.txt" + local function getRemoteRepoSHA() - local response = http.get("https://api.github.com/repos/"..GITHUB_CONFIG.OWNER.."/"..GITHUB_CONFIG.REPO.."/commits/"..GITHUB_CONFIG.BRANCH) + local response = http.get("https://api.github.com/repos/"..GITHUB_CONSTANTS.OWNER.."/"..GITHUB_CONSTANTS.REPO.."/commits/"..GITHUB_CONSTANTS.BRANCH) local responseJSON = response.readAll() return textutils.unserialiseJSON(responseJSON).sha end @@ -27,7 +29,7 @@ local function deleteExistingFiles() end local function downloadGitHubFileByPath(filepath) - local endpoint = "https://raw.githubusercontent.com/"..GITHUB_CONFIG.OWNER.."/"..GITHUB_CONFIG.REPO.."/refs/heads/"..GITHUB_CONFIG.BRANCH.."/"..filepath + local endpoint = "https://raw.githubusercontent.com/"..GITHUB_CONSTANTS.OWNER.."/"..GITHUB_CONSTANTS.REPO.."/refs/heads/"..GITHUB_CONSTANTS.BRANCH.."/"..filepath local response = http.get(endpoint) local contents = response.readAll() local file = fs.open(filepath, "w") @@ -36,7 +38,7 @@ local function downloadGitHubFileByPath(filepath) end local function getGitHubTreeDetails(treeSHA) - local endpoint = "https://api.github.com/repos/"..GITHUB_CONFIG.OWNER.."/"..GITHUB_CONFIG.REPO.."/git/trees/"..treeSHA + local endpoint = "https://api.github.com/repos/"..GITHUB_CONSTANTS.OWNER.."/"..GITHUB_CONSTANTS.REPO.."/git/trees/"..treeSHA local response = http.get(endpoint) local contents = response.readAll() return textutils.unserialiseJSON(contents) @@ -69,7 +71,7 @@ end ---@return boolean local function checkForUpdate() local remoteRepoSHA = getRemoteRepoSHA() - local localRepoSHA = getLocalRepoSHA(UPDATE_CONFIG.LOCAL_REPO_DETAILS_FILENAME) + local localRepoSHA = getLocalRepoSHA(LOCAL_REPO_DETAILS_FILENAME) return localRepoSHA ~= remoteRepoSHA end @@ -80,11 +82,11 @@ local function performUpdate() deleteExistingFiles() downloadRemoteSrcDirectory(remoteRepoSHA) downloadGitHubFileByPath("startup") - saveRepoSHA(remoteRepoSHA, UPDATE_CONFIG.LOCAL_REPO_DETAILS_FILENAME) + saveRepoSHA(remoteRepoSHA, LOCAL_REPO_DETAILS_FILENAME) end _G.UpdateScript = { checkForUpdate = checkForUpdate, performUpdate = performUpdate, saveRepoDetails = saveRepoSHA, -} \ No newline at end of file +} diff --git a/src/util/config.lua b/src/util/config.lua new file mode 100644 index 0000000..3312008 --- /dev/null +++ b/src/util/config.lua @@ -0,0 +1,121 @@ + +local CONFIGS = {} +CONFIGS["update"] = UPDATE_CONFIG + +local DEFAULTS_PATH = "/defaults/" +local OVERRIDES_PATH = "/overrides/" +local STATE_PATH = "/state/" +local CONFIG_EXTENSION = ".default.conf" +local OVERRIDE_EXTENSION = ".override.conf" +local STATE_EXTENSION = ".state.conf" + +local function isTableEmpty(table) + for _, _ in pairs(table) do + return false + end + return true +end + +local function serializeTableAndWriteToFile(table, path) + local file = fs.open(path, "w") + file.write(textutils.serialize(table)) + file.close() +end + +local function readFileAndReturnDeserialized(path) + local file = fs.open(path, "r") + if file == nil then + return {} + end + local contents = file.readAll() + file.close() + return textutils.unserialise(contents) +end + +local function readState(stateID) + return readFileAndReturnDeserialized(STATE_PATH..stateID..STATE_EXTENSION) +end + +local function readConfigDefaults(configID) + return readFileAndReturnDeserialized(DEFAULTS_PATH..configID..CONFIG_EXTENSION) +end + +local function readConfigOverrides(configID) + return readFileAndReturnDeserialized(OVERRIDES_PATH..configID..OVERRIDE_EXTENSION) +end + +local function spread(source, destination) + for key, value in pairs(source) do + destination[key] = value + end +end + +local function readConfig(configID) + local configData = CONFIGS[configID] + local defaults = readConfigDefaults(configID) + local overrides = readConfigOverrides(configID) + spread(defaults, configData) + spread(overrides, configData) +end + +local function writeConfig(configID) + local configData = CONFIGS[configID] + local defaults = readConfigDefaults(configID) + local overrides = {} + for key, value in pairs(configData) do + if configData[key] ~= defaults[key] then + overrides[key] = value + end + end + + if isTableEmpty(overrides) then + fs.delete(OVERRIDES_PATH..configID..OVERRIDE_EXTENSION) + return + end + serializeTableAndWriteToFile(overrides, OVERRIDES_PATH..configID..OVERRIDE_EXTENSION) +end + +local function writeState(stateID, stateData) + serializeTableAndWriteToFile(stateData, STATE_PATH..stateID..STATE_EXTENSION) +end + +local function writeConfigAsDefault(configID) + local configData = CONFIGS[configID] + serializeTableAndWriteToFile(configData, DEFAULTS_PATH..configID..CONFIG_EXTENSION) +end + +local function writeDefaultConfigs() + for configID, _ in pairs(CONFIGS) do + writeConfigAsDefault(configID) + end +end + +local function readAllConfigs() + for configID, _ in pairs(CONFIGS) do + readConfig(configID) + end +end + +local function writeAllConfigs() + for configID, _ in pairs(CONFIGS) do + writeConfig(configID) + end +end + +local function resetConfig(configID) + fs.delete(OVERRIDES_PATH..configID..OVERRIDE_EXTENSION) + readConfig(configID) +end + +writeDefaultConfigs() +readAllConfigs() + +_G.ConfigUtil = { + writeAllConfigs = writeAllConfigs, + readAllConfigs = readAllConfigs, + writeConfig = writeConfig, + writeState = writeState, + readConfig = readConfig, + readState = readState, + resetConfig = resetConfig, +} diff --git a/src/util/draw.lua b/src/util/draw.lua index 72b1f71..5c65516 100644 --- a/src/util/draw.lua +++ b/src/util/draw.lua @@ -71,4 +71,4 @@ _G.DrawUtil = { drawRectangle = drawRectangle, drawFilledRectangle = drawFilledRectangle, drawText = drawText, -} \ No newline at end of file +} From 2d085af13daf9940da1e70b7514f0841875866d7 Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Fri, 3 Jan 2025 00:16:05 -0500 Subject: [PATCH 10/38] more config stuff --- src/scripts/update.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/scripts/update.lua b/src/scripts/update.lua index eba2ad8..214c265 100644 --- a/src/scripts/update.lua +++ b/src/scripts/update.lua @@ -25,6 +25,8 @@ end local function deleteExistingFiles() fs.delete("/src") + fs.delete("/defaults") + fs.delete("/state") fs.delete("startup") end From 3ed2ef41c95351c6e132b74b4113b581718d41b2 Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Fri, 3 Jan 2025 01:38:28 -0500 Subject: [PATCH 11/38] install stuff --- install.lua | 30 ++++++++++++++++++++++++++++ graph.lua => src/classes/graph.lua | 0 src/config/projectConfigs.lua | 3 ++- src/constants/projectConstants.lua | 5 ----- src/scripts/installer.lua | 13 ------------ src/scripts/main.lua | 32 +++++++++++++++++++++++++++++- src/scripts/update.lua | 5 +++++ 7 files changed, 68 insertions(+), 20 deletions(-) create mode 100644 install.lua rename graph.lua => src/classes/graph.lua (100%) delete mode 100644 src/scripts/installer.lua diff --git a/install.lua b/install.lua new file mode 100644 index 0000000..e3f382c --- /dev/null +++ b/install.lua @@ -0,0 +1,30 @@ +-- This file is in pastebin + +local GITHUB_CONSTANTS = { + OWNER = "Kasra-G", + REPO = "ReactorController", + BRANCH = "development", +} + +local function getRemoteRepoSHA() + local response = http.get("https://api.github.com/repos/"..GITHUB_CONSTANTS.OWNER.."/"..GITHUB_CONSTANTS.REPO.."/commits/"..GITHUB_CONSTANTS.BRANCH) + local responseJSON = response.readAll() + return textutils.unserialiseJSON(responseJSON).sha +end + +local function downloadGitHubFileByPath(filepath) + local endpoint = "https://raw.githubusercontent.com/"..GITHUB_CONSTANTS.OWNER.."/"..GITHUB_CONSTANTS.REPO.."/refs/heads/"..GITHUB_CONSTANTS.BRANCH.."/"..filepath + local response = http.get(endpoint) + local contents = response.readAll() + local file = fs.open(filepath, "w") + file.write(contents) + file.close() +end + +--- Download the update script and reboot +local function install() + local updateScriptPath = "src/scripts/update.lua" + downloadGitHubFileByPath(updateScriptPath) + shell.run(updateScriptPath) + _G.UpdateScript.performUpdate() +end diff --git a/graph.lua b/src/classes/graph.lua similarity index 100% rename from graph.lua rename to src/classes/graph.lua diff --git a/src/config/projectConfigs.lua b/src/config/projectConfigs.lua index 9e6cc2f..cb41cb9 100644 --- a/src/config/projectConfigs.lua +++ b/src/config/projectConfigs.lua @@ -1,3 +1,4 @@ _G.UPDATE_CONFIG = { + DO_FIRST_TIME_SETUP = true, AUTOUPDATE = false, -} +} \ No newline at end of file diff --git a/src/constants/projectConstants.lua b/src/constants/projectConstants.lua index d3218c8..e69de29 100644 --- a/src/constants/projectConstants.lua +++ b/src/constants/projectConstants.lua @@ -1,5 +0,0 @@ -_G.GITHUB_CONSTANTS = { - OWNER = "Kasra-G", - REPO = "ReactorController", - BRANCH = "development", -} diff --git a/src/scripts/installer.lua b/src/scripts/installer.lua deleted file mode 100644 index f67c17b..0000000 --- a/src/scripts/installer.lua +++ /dev/null @@ -1,13 +0,0 @@ ---pastebin run kSkwEchg ---Github: https://github.com/Kasra-G/ReactorController#readme - ---Overwrite startup file -local file = fs.open("startup", "w") -file.writeLine("shell.run(\"update_reactor.lua\")") -file.writeLine("while (true) do") -file.writeLine(" shell.run(\"reactorController.lua\")") -file.writeLine(" sleep(2)") -file.writeLine("end") -file.close() -shell.run("pastebin get w6vVtrLb update_reactor.lua") -shell.run("reboot") diff --git a/src/scripts/main.lua b/src/scripts/main.lua index 6551bc4..e696be2 100644 --- a/src/scripts/main.lua +++ b/src/scripts/main.lua @@ -1,8 +1,9 @@ -shell.run("/src/classes/classes.lua") shell.run("/src/config/projectConfigs.lua") shell.run("/src/constants/projectConstants.lua") shell.run("/src/util/draw.lua") shell.run("/src/classes/touchpoint.lua") +shell.run("/src/classes/classes.lua") +shell.run("/src/classes/graph.lua") shell.run("/src/classes/page.lua") shell.run("/src/classes/navbar.lua") shell.run("/src/classes/monitor.lua") @@ -10,6 +11,35 @@ shell.run("/src/scripts/controller.lua") shell.run("/src/scripts/update.lua") shell.run("/src/util/config.lua") +local function promptAndReadInputAndLoopUntilValid(prompt, validAnswersList) + local validAnswer + local validAnswersTable = {} + for _, answer in pairs(validAnswersList) do + validAnswersTable[answer] = true + end + while true do + print(prompt) + local input = read() + if validAnswersTable[input] then + validAnswer = input + break + end + print("Invalid response. Please try again!") + end + return validAnswer +end + +local function runFirstTimeSetup() + local response = promptAndReadInputAndLoopUntilValid("Do you want to automatically install updates? (y/n)", {"y", "n"}) + if response == "y" then + UPDATE_CONFIG.AUTOUPDATE = true + end +end + +if UPDATE_CONFIG.DO_FIRST_TIME_SETUP then + runFirstTimeSetup() +end + -- For now, do update check here local updateAvailable = _G.UpdateScript.checkForUpdate() if updateAvailable then diff --git a/src/scripts/update.lua b/src/scripts/update.lua index 214c265..f39258e 100644 --- a/src/scripts/update.lua +++ b/src/scripts/update.lua @@ -1,4 +1,9 @@ local LOCAL_REPO_DETAILS_FILENAME = "/commit.txt" +local GITHUB_CONSTANTS = { + OWNER = "Kasra-G", + REPO = "ReactorController", + BRANCH = "development", +} local function getRemoteRepoSHA() local response = http.get("https://api.github.com/repos/"..GITHUB_CONSTANTS.OWNER.."/"..GITHUB_CONSTANTS.REPO.."/commits/"..GITHUB_CONSTANTS.BRANCH) From 9ad3cb7202d1d6f3528d89c918f321cdf406b569 Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Fri, 3 Jan 2025 01:44:32 -0500 Subject: [PATCH 12/38] more install stuff --- install.lua | 11 +++++------ src/scripts/main.lua | 5 +++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/install.lua b/install.lua index e3f382c..b6b835a 100644 --- a/install.lua +++ b/install.lua @@ -6,12 +6,6 @@ local GITHUB_CONSTANTS = { BRANCH = "development", } -local function getRemoteRepoSHA() - local response = http.get("https://api.github.com/repos/"..GITHUB_CONSTANTS.OWNER.."/"..GITHUB_CONSTANTS.REPO.."/commits/"..GITHUB_CONSTANTS.BRANCH) - local responseJSON = response.readAll() - return textutils.unserialiseJSON(responseJSON).sha -end - local function downloadGitHubFileByPath(filepath) local endpoint = "https://raw.githubusercontent.com/"..GITHUB_CONSTANTS.OWNER.."/"..GITHUB_CONSTANTS.REPO.."/refs/heads/"..GITHUB_CONSTANTS.BRANCH.."/"..filepath local response = http.get(endpoint) @@ -26,5 +20,10 @@ local function install() local updateScriptPath = "src/scripts/update.lua" downloadGitHubFileByPath(updateScriptPath) shell.run(updateScriptPath) + print("Downloading files!") _G.UpdateScript.performUpdate() + print("Download complete. Rebooting...") + sleep(1) end + +install() \ No newline at end of file diff --git a/src/scripts/main.lua b/src/scripts/main.lua index e696be2..93b1c8e 100644 --- a/src/scripts/main.lua +++ b/src/scripts/main.lua @@ -1,3 +1,5 @@ +term.clear() + shell.run("/src/config/projectConfigs.lua") shell.run("/src/constants/projectConstants.lua") shell.run("/src/util/draw.lua") @@ -37,7 +39,10 @@ local function runFirstTimeSetup() end if UPDATE_CONFIG.DO_FIRST_TIME_SETUP then + print("First time startup detected!") runFirstTimeSetup() + UPDATE_CONFIG.DO_FIRST_TIME_SETUP = false + ConfigUtil.writeAllConfigs() end -- For now, do update check here From 8601c2cf0501a50c986c38e6570d03bab4553198 Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Fri, 3 Jan 2025 01:46:03 -0500 Subject: [PATCH 13/38] forgot to reboot --- install.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/install.lua b/install.lua index b6b835a..a742394 100644 --- a/install.lua +++ b/install.lua @@ -24,6 +24,7 @@ local function install() _G.UpdateScript.performUpdate() print("Download complete. Rebooting...") sleep(1) + os.reboot() end install() \ No newline at end of file From 85a52b73493d30fca45c3e42b3ac5007ed39ae82 Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Fri, 3 Jan 2025 01:47:31 -0500 Subject: [PATCH 14/38] sleepy --- src/scripts/main.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/src/scripts/main.lua b/src/scripts/main.lua index 93b1c8e..535dee1 100644 --- a/src/scripts/main.lua +++ b/src/scripts/main.lua @@ -1,4 +1,5 @@ term.clear() +term.setCursorPos(1,1) shell.run("/src/config/projectConfigs.lua") shell.run("/src/constants/projectConstants.lua") From e194cd337e7710cb0b29a43fb88ea1d57305edf6 Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Fri, 3 Jan 2025 01:55:40 -0500 Subject: [PATCH 15/38] test to see if auto update works? --- src/scripts/main.lua | 53 +++++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/src/scripts/main.lua b/src/scripts/main.lua index 535dee1..519ffab 100644 --- a/src/scripts/main.lua +++ b/src/scripts/main.lua @@ -1,6 +1,3 @@ -term.clear() -term.setCursorPos(1,1) - shell.run("/src/config/projectConfigs.lua") shell.run("/src/constants/projectConstants.lua") shell.run("/src/util/draw.lua") @@ -39,28 +36,34 @@ local function runFirstTimeSetup() end end -if UPDATE_CONFIG.DO_FIRST_TIME_SETUP then - print("First time startup detected!") - runFirstTimeSetup() - UPDATE_CONFIG.DO_FIRST_TIME_SETUP = false - ConfigUtil.writeAllConfigs() -end +local function start() + term.clear() + term.setCursorPos(1,1) + + if UPDATE_CONFIG.DO_FIRST_TIME_SETUP then + print("First time startup detected!") + runFirstTimeSetup() + UPDATE_CONFIG.DO_FIRST_TIME_SETUP = false + ConfigUtil.writeAllConfigs() + end --- For now, do update check here -local updateAvailable = _G.UpdateScript.checkForUpdate() -if updateAvailable then - print("Update available!") - -- Set some state variable somewhere to tell us an update is available - if UPDATE_CONFIG.AUTOUPDATE then - print("Automatic update is enabled! Updating...") - _G.UpdateScript.performUpdate() - print("Finished update! Rebooting...") - sleep(1) - os.reboot() - else - print("Automatic update skipped because it's not enabled!") - sleep(1) + local updateAvailable = _G.UpdateScript.checkForUpdate() + if updateAvailable then + print("Update available!") + -- Set some state variable somewhere to tell us an update is available + if UPDATE_CONFIG.AUTOUPDATE then + print("Automatic update is enabled! Updating...") + _G.UpdateScript.performUpdate() + print("Finished update! Rebooting...") + sleep(1) + os.reboot() + else + print("Automatic update skipped because it's not enabled!") + sleep(1) + end end + -- For now, main() is in controller.lua + main() end --- For now, main() is in controller.lua -main() + +start() From 161d08e4cddbea327113a995c1a7b59f400e6b85 Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Fri, 3 Jan 2025 02:04:22 -0500 Subject: [PATCH 16/38] rename --- src/config/projectConfigs.lua | 2 +- src/scripts/main.lua | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/config/projectConfigs.lua b/src/config/projectConfigs.lua index cb41cb9..b1198ba 100644 --- a/src/config/projectConfigs.lua +++ b/src/config/projectConfigs.lua @@ -1,4 +1,4 @@ _G.UPDATE_CONFIG = { - DO_FIRST_TIME_SETUP = true, + FIRST_TIME_SETUP_COMPLETE = false, AUTOUPDATE = false, } \ No newline at end of file diff --git a/src/scripts/main.lua b/src/scripts/main.lua index 519ffab..8c59ff6 100644 --- a/src/scripts/main.lua +++ b/src/scripts/main.lua @@ -40,10 +40,10 @@ local function start() term.clear() term.setCursorPos(1,1) - if UPDATE_CONFIG.DO_FIRST_TIME_SETUP then + if not UPDATE_CONFIG.FIRST_TIME_SETUP_COMPLETE then print("First time startup detected!") runFirstTimeSetup() - UPDATE_CONFIG.DO_FIRST_TIME_SETUP = false + UPDATE_CONFIG.FIRST_TIME_SETUP_COMPLETE = true ConfigUtil.writeAllConfigs() end From d8c95841206115689104338569531975e99ed602 Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Fri, 3 Jan 2025 10:53:01 -0500 Subject: [PATCH 17/38] fix for error on world load --- src/scripts/main.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/scripts/main.lua b/src/scripts/main.lua index 8c59ff6..4adf0a5 100644 --- a/src/scripts/main.lua +++ b/src/scripts/main.lua @@ -37,6 +37,9 @@ local function runFirstTimeSetup() end local function start() + -- Let reactors run for 1 second on world load. + sleep(1) + term.clear() term.setCursorPos(1,1) From 6c9e1815892c6f611ca42ac8611a8a8591909a31 Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Fri, 3 Jan 2025 11:25:56 -0500 Subject: [PATCH 18/38] dynamic list and loading of files in src --- src/scripts/main.lua | 36 ++++++++++++++++++++++++------------ src/scripts/update.lua | 19 +++++++++++-------- src/util/config.lua | 6 ++---- 3 files changed, 37 insertions(+), 24 deletions(-) diff --git a/src/scripts/main.lua b/src/scripts/main.lua index 4adf0a5..8328b8a 100644 --- a/src/scripts/main.lua +++ b/src/scripts/main.lua @@ -1,15 +1,23 @@ -shell.run("/src/config/projectConfigs.lua") -shell.run("/src/constants/projectConstants.lua") -shell.run("/src/util/draw.lua") -shell.run("/src/classes/touchpoint.lua") -shell.run("/src/classes/classes.lua") -shell.run("/src/classes/graph.lua") -shell.run("/src/classes/page.lua") -shell.run("/src/classes/navbar.lua") -shell.run("/src/classes/monitor.lua") -shell.run("/src/scripts/controller.lua") -shell.run("/src/scripts/update.lua") -shell.run("/src/util/config.lua") +local function insertAllFilepathsInDirectoryToTable(path, outputFilenames) + for _, file in pairs(fs.list(path)) do + local filepath = fs.combine(path, file) + if fs.isDir(filepath) then + insertAllFilepathsInDirectoryToTable(filepath, outputFilenames) + else + table.insert(outputFilenames, filepath) + end + end +end + +local function executeAllLuaFilesInSrcFolderExceptMain() + local filepaths = {} + insertAllFilepathsInDirectoryToTable("src", filepaths) + for _, filepath in pairs(filepaths) do + if filepath ~= "src/scripts/main.lua" then + shell.run(filepath) + end + end +end local function promptAndReadInputAndLoopUntilValid(prompt, validAnswersList) local validAnswer @@ -37,12 +45,16 @@ local function runFirstTimeSetup() end local function start() + executeAllLuaFilesInSrcFolderExceptMain() -- Let reactors run for 1 second on world load. sleep(1) term.clear() term.setCursorPos(1,1) + ConfigUtil.writeAllConfigsAsDefaults() + ConfigUtil.readAllConfigs() + if not UPDATE_CONFIG.FIRST_TIME_SETUP_COMPLETE then print("First time startup detected!") runFirstTimeSetup() diff --git a/src/scripts/update.lua b/src/scripts/update.lua index f39258e..3171eba 100644 --- a/src/scripts/update.lua +++ b/src/scripts/update.lua @@ -5,6 +5,13 @@ local GITHUB_CONSTANTS = { BRANCH = "development", } +local FILES_TO_DELETE_ON_UPDATE = { + "/src", + "/defaults", + "/state", + "startup", +} + local function getRemoteRepoSHA() local response = http.get("https://api.github.com/repos/"..GITHUB_CONSTANTS.OWNER.."/"..GITHUB_CONSTANTS.REPO.."/commits/"..GITHUB_CONSTANTS.BRANCH) local responseJSON = response.readAll() @@ -28,13 +35,6 @@ local function saveRepoSHA(repoSHA, path) file.close() end -local function deleteExistingFiles() - fs.delete("/src") - fs.delete("/defaults") - fs.delete("/state") - fs.delete("startup") -end - local function downloadGitHubFileByPath(filepath) local endpoint = "https://raw.githubusercontent.com/"..GITHUB_CONSTANTS.OWNER.."/"..GITHUB_CONSTANTS.REPO.."/refs/heads/"..GITHUB_CONSTANTS.BRANCH.."/"..filepath local response = http.get(endpoint) @@ -86,7 +86,10 @@ end local function performUpdate() local remoteRepoSHA = getRemoteRepoSHA() - deleteExistingFiles() + for _, filepath in pairs(FILES_TO_DELETE_ON_UPDATE) do + fs.delete(filepath) + end + downloadRemoteSrcDirectory(remoteRepoSHA) downloadGitHubFileByPath("startup") saveRepoSHA(remoteRepoSHA, LOCAL_REPO_DETAILS_FILENAME) diff --git a/src/util/config.lua b/src/util/config.lua index 3312008..86c2f40 100644 --- a/src/util/config.lua +++ b/src/util/config.lua @@ -84,7 +84,7 @@ local function writeConfigAsDefault(configID) serializeTableAndWriteToFile(configData, DEFAULTS_PATH..configID..CONFIG_EXTENSION) end -local function writeDefaultConfigs() +local function writeAllConfigsAsDefaults() for configID, _ in pairs(CONFIGS) do writeConfigAsDefault(configID) end @@ -107,10 +107,8 @@ local function resetConfig(configID) readConfig(configID) end -writeDefaultConfigs() -readAllConfigs() - _G.ConfigUtil = { + writeAllConfigsAsDefaults = writeAllConfigsAsDefaults, writeAllConfigs = writeAllConfigs, readAllConfigs = readAllConfigs, writeConfig = writeConfig, From a6c3922f0e76b001737dc21cf3cd23eb2a55d97b Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Fri, 3 Jan 2025 12:56:20 -0500 Subject: [PATCH 19/38] Renames --- src/classes/monitor.lua | 49 +++++++++++++++-------- src/classes/navbar.lua | 6 ++- src/classes/page.lua | 2 +- src/scripts/controller.lua | 82 +++++++++++++++++++++----------------- 4 files changed, 84 insertions(+), 55 deletions(-) diff --git a/src/classes/monitor.lua b/src/classes/monitor.lua index dc82a43..1732fdb 100644 --- a/src/classes/monitor.lua +++ b/src/classes/monitor.lua @@ -1,35 +1,50 @@ ---@class Monitor local Monitor = { - navbar = nil, ---@type Page activePage = nil, ---@type string id = nil, ---@type table - peripheral = nil, + mon = nil, ---@type table touch = nil, - ---@type Vector2 - size = nil, - ---@type integer - oo = nil, - ---@type integer - dim = nil, + + ---@param self Monitor + ---@return Vector2 + size = function(self) + return Vector2.new(self.mon.getSize()) + end, + + ---@param self Monitor + ---@return integer + dividerYCoord = function(self) + return self:size().y - 37 + end, + + ---@param self Monitor + ---@return integer + dividerXCoord = function(self) + return self:size().x - 31 + end, ---comment ---@param self Monitor clear = function(self) - self.peripheral.setBackgroundColor(colors.black) - self.peripheral.clear() - self.peripheral.setTextScale(0.5) - self.peripheral.setCursorPos(1,1) + self.mon.setBackgroundColor(colors.black) + self.mon.clear() + self.mon.setTextScale(0.5) + self.mon.setCursorPos(1,1) + end, + + handleEvents = function(self, event) + end, ---@param self Monitor drawScene = function(self) if (invalidDim) then - self.peripheral.write("Invalid Monitor Dimensions") + self.mon.write("Invalid Monitor Dimensions") return end @@ -54,13 +69,13 @@ local Monitor = { ---@param peripheralId string ---@return Monitor local function new(peripheralId) - local monitorPeripheral = peripheral.wrap(peripheralId) - local touch = _G.Touchpoint.new(peripheralId) + local mon = peripheral.wrap(peripheralId) + local touchHandler = _G.Touchpoint.new(peripheralId) local monitorInstance = { id = peripheralId, - peripheral = monitorPeripheral, - touch = touch, + mon = mon, + touch = touchHandler, } setmetatable(monitorInstance, {__index = Monitor}) diff --git a/src/classes/navbar.lua b/src/classes/navbar.lua index 349a2d8..fc4cf66 100644 --- a/src/classes/navbar.lua +++ b/src/classes/navbar.lua @@ -9,6 +9,10 @@ local Navbar = { ---@type Vector2 size = nil, + handleEvents = function() + + end, + ---comment ---@param self Navbar ---@param reactorStats ReactorStatistics @@ -34,6 +38,6 @@ local function new(peripheralId) return monitorInstance end -_G.Monitor = { +_G.Navbar = { new = new } diff --git a/src/classes/page.lua b/src/classes/page.lua index dfe8290..f13da33 100644 --- a/src/classes/page.lua +++ b/src/classes/page.lua @@ -62,6 +62,6 @@ local function new(peripheralId) return monitorInstance end -_G.Monitor = { +_G.Page = { new = new } diff --git a/src/scripts/controller.lua b/src/scripts/controller.lua index b772803..34b4bec 100644 --- a/src/scripts/controller.lua +++ b/src/scripts/controller.lua @@ -27,9 +27,11 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] + + local reactorVersion, reactor -local mon, monSide -local sizex, sizey, dividerXCoord, oo, offy +local mon, monitorID +local sizex, sizey, dividerXCoord, dividerYCoord, offy local btnOn, btnOff, invalidDim local minb, maxb local rod, rfLost @@ -50,6 +52,7 @@ local averageCaseTemp = 0 local averageRfLost = 0 local MINIMUM_DIVIDER_X_VALUE = 3 +local MINIMUM_DIVIDER_Y_VALUE = 1 -- table of which graphs to draw local graphsToDraw = {} @@ -124,11 +127,8 @@ end --adds buttons local function addButtons() - if (sizey == 24) then - oo = 1 - end local buttonSize = Vector2.new(8, 3) - local offsetOnOff = Vector2.new(dividerXCoord + 5, 3 + oo) + local offsetOnOff = Vector2.new(dividerXCoord + 5, 3 + dividerYCoord) addButton( t, @@ -156,7 +156,7 @@ local function addButtons() t:toggleButton("Off", true) end - local offset = Vector2.new(dividerXCoord, oo) + local offset = Vector2.new(dividerXCoord, dividerYCoord) if (sizey > 24) then addButton( @@ -200,7 +200,7 @@ end --Resets the monitor local function resetMon() - if (monSide == nil) then + if (monitorID == nil) then return end mon.setBackgroundColor(colors.black) @@ -292,7 +292,7 @@ local function toggleGraph(name) end local function addGraphButtons() - offy = oo - 14 + offy = dividerYCoord - 14 local graphButtonSize = Vector2.new(20, 3) local graphButtonOffset = Vector2.new(dividerXCoord + 5, offy) for i,graphName in pairs(graphs) do @@ -611,7 +611,7 @@ end --Draw a scene local function drawScene() - if (monSide == nil) then + if (monitorID == nil) then return end if (invalidDim) then @@ -627,7 +627,7 @@ local function drawScene() drawGraphButtons(mon, offset, size) end - offset = Vector2.new(dividerXCoord + 1, oo + 1) + offset = Vector2.new(dividerXCoord + 1, dividerYCoord + 1) size = Vector2.new(30, 23) local drawBufferVisualization = true if (sizey <= 24) then @@ -661,29 +661,31 @@ local function getAllPeripheralIdsForType(targetType) end ---@type table -local monitors = {} +-- local monitors = {} local function initMon2(id) local monitor = Monitor.new(id) - monSide = id - mon = monitor.peripheral - monitor:clear() + monitor.mon.clear() t = monitor.touch - sizex, sizey = mon.getSize() - oo = sizey - 37 - dividerXCoord = sizex - 31 + sizex, sizey = monitor.mon.getSize() + dividerYCoord = monitor:dividerYCoord() + dividerXCoord = monitor:dividerXCoord() - if (sizex == 36) then + if sizex <= 36 then dividerXCoord = MINIMUM_DIVIDER_X_VALUE end + if sizey <= 24 then + dividerYCoord = MINIMUM_DIVIDER_Y_VALUE + end + displayingGraphMenu = pcall(function() addGraphButtons() end) _, invalidDim = pcall(function() addButtons() end) return monitor end -local function updateMonitors() +local function updateMonitors(monitors) for _, monitor in pairs(monitors) do ---@type ReactorStatistics local reactorStats = {} @@ -691,16 +693,18 @@ local function updateMonitors() end end -local function initMonitors() - monitors = {} +local pretty = require "cc.pretty" + +local function discoverAndInitMonitors() + local monitors = {} local ids = getAllPeripheralIdsForType("monitor") + pretty.pretty_print(ids) for _, id in pairs(ids) do monitors[id] = initMon2(id) end + return monitors end --- DELETE LATER! -initMonitors() --returns the side that a given peripheral type is connected to local function getPeripheral(targetType) @@ -714,29 +718,33 @@ end --Creates all the buttons and determines monitor size local function initMon() - monSide = getPeripheral("monitor") - if (monSide == nil or monSide == "") then - monSide = nil + monitorID = getPeripheral("monitor") + if (monitorID == nil or monitorID == "") then + monitorID = nil return end - local monitor = Monitor.new(monSide) - mon = monitor.peripheral + local monitor = Monitor.new(monitorID) + mon = monitor.mon if mon == nil then - monSide = nil + monitorID = nil return end resetMon() - t = Touchpoint.new(monSide) + t = Touchpoint.new(monitorID) sizex, sizey = mon.getSize() - oo = sizey - 37 + dividerYCoord = sizey - 37 dividerXCoord = sizex - 31 - if (sizex == 36) then + if sizex <= 36 then dividerXCoord = MINIMUM_DIVIDER_X_VALUE end + if sizey <= 24 then + dividerYCoord = MINIMUM_DIVIDER_Y_VALUE + end + displayingGraphMenu = pcall(function() addGraphButtons() end) _, invalidDim = pcall(function() addButtons() end) end @@ -971,12 +979,12 @@ local function loadFromConfig() minb = 30 rod = 80 btnOn = false - if (monSide == nil) then + if (monitorID == nil) then btnOn = true end sizex, sizey = 100, 52 dividerXCoord = sizex - 31 - oo = sizey - 37 + dividerYCoord = sizey - 37 enableGraph("Energy Buffer") enableGraph("Control Level") enableGraph("Temperatures") @@ -1040,7 +1048,7 @@ local function loop() while (true) do local event = { os.pullEvent() } - if monSide ~= nil then + if monitorID ~= nil then event = { t:handleEvents(unpack(event)) } end @@ -1098,6 +1106,8 @@ function _G.main() print("Loading config...") loadFromConfig() print("Initializing monitor if connected...") + + -- local monitors = discoverAndInitMonitors() initMon() print("Writing config to disk...") saveToConfig() From b198fd09343fb083cea91e9e828196b9d989f6ad Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Sat, 4 Jan 2025 06:51:26 -0500 Subject: [PATCH 20/38] Multiple monitior support --- src/classes/graph.lua | 1 - src/classes/monitor.lua | 633 ++++++++++++++++++-- src/classes/page.lua | 21 +- src/classes/touchpoint.lua | 63 +- src/scripts/controller.lua | 1114 ++++++++---------------------------- src/util/draw.lua | 64 +-- 6 files changed, 888 insertions(+), 1008 deletions(-) diff --git a/src/classes/graph.lua b/src/classes/graph.lua index 567738d..5838a8f 100644 --- a/src/classes/graph.lua +++ b/src/classes/graph.lua @@ -45,7 +45,6 @@ _G.Graph = { ---@param mon table ---@param offset Vector2 ---@param size Vector2 ----@param statistics ReactorStatistics local function drawControlGraph(mon, offset, size, averageRod) local controlRodLength0To1 = averageRod / 100 local controlRodMaxLengthOnScreen = size.y - 2 diff --git a/src/classes/monitor.lua b/src/classes/monitor.lua index 1732fdb..883ba2d 100644 --- a/src/classes/monitor.lua +++ b/src/classes/monitor.lua @@ -1,3 +1,502 @@ +_G.MONITOR_CONSTANTS = { + MINIMUM_DIVIDER_X_VALUE = 3, + MINIMUM_DIVIDER_Y_VALUE = 1, +} + +-- table of which graphs to draw + +---@type table +local graphs = +{ + "Energy Buffer", + "Control Level", + "Temperatures", +} + +local function round(num, dig) + return math.floor(10 ^ dig * num + 0.5) / (10 ^ dig) +end + +local function format(num) + if (num >= 1000000000) then + return string.format("%7.3f G", num / 1000000000) + elseif (num >= 1000000) then + return string.format("%7.3f M", num / 1000000) + elseif (num >= 1000) then + return string.format("%7.3f K", num / 1000) + elseif (num >= 1) then + return string.format("%7.3f ", num) + elseif (num >= .001) then + return string.format("%7.3f m", num * 1000) + elseif (num >= .000001) then + return string.format("%7.3f u", num * 1000000) + else + return string.format("%7.3f ", 0) + end +end + +local function getPercPower() + return _G.averageStoredThisTick / _G.capacity * 100 +end + +local function getEfficiency() + return _G.averageLastRFT / _G.averageFuelUsage +end + +--Helper method for adding buttons +local function addButton(touch, name, callBack, offset, size, color1, color2) + local buttonTopLeftCorner = offset + Vector2.one + local buttonBottomRightCorner = offset + size + touch:add( + name, + callBack, + buttonTopLeftCorner.x, + buttonTopLeftCorner.y, + buttonBottomRightCorner.x, + buttonBottomRightCorner.y, + color1, + color2 + ) +end + +local function minAdd10() + _G.minb = math.min(_G.maxb - 10, _G.minb + 10) +end +local function minSub10() + _G.minb = math.max(0, _G.minb - 10) +end +local function maxAdd10() + _G.maxb = math.min(100, _G.maxb + 10) +end +local function maxSub10() + _G.maxb = math.max(_G.minb + 10, _G.maxb - 10) +end + +local function turnOff() + if (_G.btnOn) then + _G.btnOff = true + _G.btnOn = false + _G.reactor.setActive(false) + end +end + +local function turnOn() + if (_G.btnOff) then + _G.btnOff = false + _G.btnOn = true + _G.reactor.setActive(true) + end +end + +--adds buttons +local function addReactorControlButtons(touch, offset, shouldDrawBufferVisualization) + local buttonSize = Vector2.new(8, 3) + local offsetOnOff = offset + Vector2.new(5, 3) + + addButton(touch, "On", turnOn, offsetOnOff, buttonSize, colors.red, colors.lime) + addButton(touch, "Off", turnOff, offsetOnOff + Vector2.new(12, 0), buttonSize, colors.red, colors.lime) + + if shouldDrawBufferVisualization then + addButton(touch, "+ 10", minAdd10, offset + Vector2.new(5, 14), buttonSize, colors.purple, colors.pink) + addButton(touch, " + 10 ", maxAdd10, offset + Vector2.new(17, 14), buttonSize, colors.magenta, colors.pink) + addButton(touch, "- 10", minSub10, offset + Vector2.new(5, 18), buttonSize, colors.purple, colors.pink) + addButton(touch, " - 10 ", maxSub10, offset + Vector2.new(17, 18), buttonSize, colors.magenta, colors.pink) + end +end + +local GRAPH_SEPARATION_X = 23 +local GRAPH_FIRST_OFFSET_X = 4 + +local function getFirstAvailableGraphSlot(graphSlots) + local offset = GRAPH_FIRST_OFFSET_X + while graphSlots[offset] ~= nil do + offset = offset + GRAPH_SEPARATION_X + end + return offset +end + +local function getGraphXCoord(graphSlots, name) + for xCoord, graph in pairs(graphSlots) do + if graph.name == name then + return xCoord + end + end + return -1 +end + +local function createGraph(name) + return {name = name} +end + +local function enableGraph(graphSlots, name) + if getGraphXCoord(graphSlots, name) > -1 then + return + end + + local slotXCoord = getFirstAvailableGraphSlot(graphSlots) + + graphSlots[slotXCoord] = createGraph(name) +end + +local function disableGraph(graphSlots, name) + local graphXSlot = getGraphXCoord(graphSlots, name) + + graphSlots[graphXSlot] = nil +end + +local function toggleGraph(graphSlots, name) + + local graphSlotX = getGraphXCoord(graphSlots, name) + if graphSlotX == -1 then + enableGraph(graphSlots, name) + else + disableGraph(graphSlots, name) + end +end + +---comment +---@param monitor Monitor +local function addGraphButtons(monitor, graphSlots, offset, size) + for i, graphName in pairs(graphs) do + addButton( + monitor.touch, + graphName, + function() + toggleGraph(graphSlots, graphName) + end, + offset + Vector2.new(0, i * 3 - 1), + size, + colors.red, + colors.lime + ) + end +end + + +local function drawGraphButtons(mon, offset, size) + + DrawUtil.drawRectangle(mon, colors.black, colors.orange, offset, size) + local textPos = offset + Vector2.new(4, 0) + DrawUtil.drawText(mon, " Graph Controls ", textPos, colors.black, colors.orange + ) +end + +local function drawEnergyBuffer(mon, offset, graphSize, drawPercentLabelOnRight) + DrawUtil.drawText(mon, "Energy Buffer", offset, colors.black, colors.orange) + DrawUtil.drawRectangle(mon, colors.red, colors.gray, offset + Vector2.new(0, 1), graphSize) + + local energyBufferMaxHeight = graphSize.y - 2 + local unitEnergyLevel = getPercPower() / 100 + local energyBufferHeight = math.floor(unitEnergyLevel * energyBufferMaxHeight + 0.5) + local rndpw = round(getPercPower(), 2) + + local energyBufferColor + if rndpw < _G.maxb and rndpw > _G.minb then + energyBufferColor = colors.green + elseif rndpw >= _G.maxb then + energyBufferColor = colors.orange + elseif rndpw <= _G.minb then + energyBufferColor = colors.blue + end + + local energyBufferTipOffset = offset + Vector2.new(1, 2 + energyBufferMaxHeight - energyBufferHeight) + local energyBufferSize = Vector2.new(graphSize.x - 2, energyBufferHeight) + + DrawUtil.drawFilledRectangle(mon, energyBufferColor, energyBufferTipOffset, energyBufferSize) + + local energyBufferTextOffset = energyBufferTipOffset + local rfLabelBackgroundColor = energyBufferColor + + if energyBufferHeight <= 0 then + energyBufferTextOffset = energyBufferTipOffset + Vector2.new(0, -1) + rfLabelBackgroundColor = colors.red + end + + local percentLabelXOffset = offset.x - 6 + if drawPercentLabelOnRight then + percentLabelXOffset = offset.x + 15 + end + + DrawUtil.drawText( + mon, + string.format(drawPercentLabelOnRight and "%.2f%%" or "%5.2f%%", rndpw), + Vector2.new(percentLabelXOffset, energyBufferTextOffset.y), + colors.black, + energyBufferColor + ) + DrawUtil.drawText( + mon, + format(_G.averageStoredThisTick).."RF", + energyBufferTextOffset, + rfLabelBackgroundColor, + colors.black + ) +end + +local function drawControlGraph(mon, offset, size, averageRod) + local unitRodLevel = averageRod / 100 + local controlRodMaxPixelHeight = size.y - 2 + local controlRodPixelHeight = math.ceil(unitRodLevel * controlRodMaxPixelHeight) + + DrawUtil.drawText(mon, "Control Level", offset + Vector2.new(1, 0), colors.black, colors.orange) + DrawUtil.drawRectangle(mon, colors.yellow, colors.gray, offset + Vector2.new(0, 1), size) + DrawUtil.drawFilledRectangle(mon, colors.white, offset + Vector2.new(3, 2), Vector2.new(9, controlRodPixelHeight)) + + local controlRodLevelTextPos, color + if controlRodPixelHeight > 0 then + color = colors.white + controlRodLevelTextPos = offset + Vector2.new(3, 1 + controlRodPixelHeight) + else + color = colors.yellow + controlRodLevelTextPos = offset + Vector2.new(3, 2) + end + + DrawUtil.drawText(mon, string.format(" %6.2f%% ", averageRod), controlRodLevelTextPos, color, colors.black) +end + +local function drawTemperatures(mon, offset, size) + + DrawUtil.drawRectangle(mon, colors.black, colors.gray, offset + Vector2.new(1, 1), size) + + local CASE_TEMP_COLOR = colors.lightBlue + local FUEL_TEMP_COLOR = colors.magenta + local BACKGROUND_COLOR = colors.black + + local assumedMaxCaseTemperature = 3000 + local assumedMaxFuelTemperature = 3000 + local temperatureMaxHeight = size.y - 2 + + local tempUnit = (_G.reactorVersion == "Bigger Reactors") and "K" or "C" + local tempFormat = "%4s"..tempUnit + + DrawUtil.drawText(mon, "Temperatures", offset + Vector2.new(2, 0), BACKGROUND_COLOR, colors.orange) + DrawUtil.drawFilledRectangle(mon, colors.gray, offset + Vector2.new(8, 2), Vector2.new(1, temperatureMaxHeight)) + + -- case temp + DrawUtil.drawText(mon, "Case", offset + Vector2.new(3, 1), colors.gray, colors.lightBlue) + local caseUnit = math.min(_G.averageCaseTemp / assumedMaxCaseTemperature, 1) + local caseTempHeight = math.floor(caseUnit * temperatureMaxHeight + 0.5) + + local caseTempOffset = offset + Vector2.new(2, 2 + temperatureMaxHeight - caseTempHeight) + local caseTempSize = Vector2.new(6, caseTempHeight) + + DrawUtil.drawFilledRectangle(mon, CASE_TEMP_COLOR, caseTempOffset, caseTempSize) + + local caseTempTextOffset = caseTempOffset + local caseTempTextBackgroundColor = CASE_TEMP_COLOR + local caseTempTextColor = BACKGROUND_COLOR + + if caseTempHeight <= 0 then + caseTempTextOffset = caseTempOffset + Vector2.new(0, -1) + caseTempTextColor, caseTempTextBackgroundColor = caseTempTextBackgroundColor, caseTempTextColor + end + + local caseRnd = math.floor(_G.averageCaseTemp + 0.5) + DrawUtil.drawText(mon, string.format(tempFormat, caseRnd..""), caseTempTextOffset, caseTempTextBackgroundColor, caseTempTextColor) + + -- fuel temp + DrawUtil.drawText(mon, "Fuel", offset + Vector2.new(10, 1), colors.gray, colors.lightBlue) + local fuelUnit = math.min(_G.averageFuelTemp / assumedMaxFuelTemperature, 1) + local fuelTempHeight = math.floor(fuelUnit * temperatureMaxHeight + 0.5) + + local fuelTempOffset = offset + Vector2.new(9, 2 + temperatureMaxHeight - fuelTempHeight) + local fuelTempSize = Vector2.new(6, fuelTempHeight) + + DrawUtil.drawFilledRectangle(mon, FUEL_TEMP_COLOR, fuelTempOffset, fuelTempSize) + + local fuelTempTextOffset = fuelTempOffset + local fuelTempTextBackgroundColor = FUEL_TEMP_COLOR + local fuelTempTextColor = BACKGROUND_COLOR + + if fuelTempHeight <= 0 then + fuelTempTextOffset = fuelTempOffset + Vector2.new(0, -1) + fuelTempTextColor, fuelTempTextBackgroundColor = fuelTempTextBackgroundColor, fuelTempTextColor + end + + local fuelRnd = math.floor(_G.averageFuelTemp + 0.5) + DrawUtil.drawText(mon, string.format(tempFormat, fuelRnd..""), fuelTempTextOffset, fuelTempTextBackgroundColor, fuelTempTextColor) +end + +local function drawGraph(mon, dividerXCoord, name, graphOffset, graphSize) + if (name == "Energy Buffer") then + local drawPercentLabelOnRight = graphOffset.x + 19 < dividerXCoord - 1 + drawEnergyBuffer(mon, graphOffset, graphSize, drawPercentLabelOnRight) + elseif (name == "Control Level") then + drawControlGraph(mon, graphOffset, graphSize, _G.averageRod) + elseif (name == "Temperatures") then + drawTemperatures(mon, graphOffset, graphSize) + end +end + +local function drawGraphs(mon, monitorSize, graphSlots, dividerXCoord, offset, size) + DrawUtil.drawRectangle(mon, colors.black, colors.lightBlue, offset, size) + local label = " Reactor Graphs " + DrawUtil.drawText( + mon, + label, + offset + Vector2.new(dividerXCoord - (#label + 5) - 1, 0), + colors.black, + colors.lightBlue + ) + + local graphSize = Vector2.new(15, monitorSize.y - 7) + local graphYOffset = 4 + for graphXOffset, graph in pairs(graphSlots) do + if graphXOffset + graphSize.x < dividerXCoord then + drawGraph(mon, dividerXCoord, graph.name, Vector2.new(graphXOffset, graphYOffset), graphSize) + end + end +end + +local function drawControls(mon, offset, size, drawBufferVisualization) + if not drawBufferVisualization then + size = Vector2.new(30, 9) + end + + DrawUtil.drawRectangle(mon, colors.black, colors.cyan, offset, size) + DrawUtil.drawText(mon, " Reactor Controls ", offset + Vector2.new(4, 0), colors.black, colors.cyan) + + local reactorOnOffLabel = "Reactor "..(_G.btnOn and "Online" or "Offline") + local reactorOnOffLabelColor = _G.btnOn and colors.green or colors.red + DrawUtil.drawText(mon, reactorOnOffLabel, offset + Vector2.new(7, 2), colors.black, reactorOnOffLabelColor) + + if not drawBufferVisualization then + return + end + + local bufferMinInPixels = _G.minb / 5 + local bufferMaxInPixels = _G.maxb / 5 + local bufferRangePixelWidth = bufferMaxInPixels - bufferMinInPixels + + local bufferVisualOffset = offset + Vector2.new(5, 8) + local bufferVisualSize = Vector2.new(20, 3) + + DrawUtil.drawText(mon, "Buffer Target Range", bufferVisualOffset + Vector2.new(0, -1), colors.black, colors.orange) + DrawUtil.drawFilledRectangle(mon, colors.red, bufferVisualOffset, bufferVisualSize) + DrawUtil.drawFilledRectangle(mon, colors.green, bufferVisualOffset + Vector2.new(bufferMinInPixels, 0), Vector2.new(bufferRangePixelWidth, 3)) + + DrawUtil.drawText( + mon, + string.format("%3s", _G.minb.."%"), + bufferVisualOffset + Vector2.new(bufferMinInPixels - 3, bufferVisualSize.y), + colors.black, + colors.purple + ) + DrawUtil.drawText( + mon, + _G.maxb.."%", + bufferVisualOffset + Vector2.new(bufferMaxInPixels, bufferVisualSize.y), + colors.black, + colors.magenta + ) + DrawUtil.drawText(mon, "Min", offset + Vector2.new(7, 13), colors.black, colors.purple) + DrawUtil.drawText(mon, "Max", offset + Vector2.new(19, 13), colors.black, colors.purple) +end + +local function drawStatistics(mon, offset, size) + DrawUtil.drawRectangle(mon, colors.black, colors.blue, offset, size) + DrawUtil.drawText(mon, " Reactor Statistics ", offset + Vector2.new(4, 0), colors.black, colors.blue + ) + + DrawUtil.drawText( + mon, + "Generating : "..format(_G.averageLastRFT).."RF/t", + offset + Vector2.new(2, 2), + colors.black, + colors.green + ) + + DrawUtil.drawText( + mon, + "RF Drain "..(_G.averageStoredThisTick <= _G.averageLastRFT and "> " or ": ")..format(_G.averageRfLost).."RF/t", + offset + Vector2.new(2, 4), + colors.black, + colors.red + ) + + DrawUtil.drawText( + mon, + "Efficiency : "..format(getEfficiency()).."RF/B", + offset + Vector2.new(2, 6), + colors.black, + colors.green + ) + + DrawUtil.drawText( + mon, + "Fuel Usage : "..format(_G.averageFuelUsage).."B/t", + offset + Vector2.new(2, 8), + colors.black, + colors.green + ) + + DrawUtil.drawText( + mon, + "Waste : "..string.format("%7d mB", _G.waste), + offset + Vector2.new(2, 10), + colors.black, + colors.green + ) +end + +local function updateReactorControlButtonStates(touch) + if _G.btnOn then + touch:setButton("On", true) + touch:setButton("Off", false) + else + touch:setButton("On", false) + touch:setButton("Off", true) + end +end + +local function updateGraphMenuButtonStates(touch, graphSlots) + for _, graphName in pairs(graphs) do + touch:setButton(graphName, false) + end + for _, graph in pairs(graphSlots) do + touch:setButton(graph.name, true) + end +end + + ---@return integer +local function calculateDividerYCoord(sizey) + if sizey <= 24 then + return MONITOR_CONSTANTS.MINIMUM_DIVIDER_Y_VALUE + end + return sizey - 37 +end + +---@return integer +local function calculateDividerXCoord(sizex) + if sizex <= 36 then + return MONITOR_CONSTANTS.MINIMUM_DIVIDER_X_VALUE + end + return sizex - 31 +end + +local function greaterThanEqualTo(firstVector2, secondVector2) + return firstVector2.x >= secondVector2.x and firstVector2.y >= secondVector2.y +end + +local MINIMUM_SIZE_TO_DRAW = {x = 36, y = 24} +local REACTOR_CONTROL_MIN_SIZE = {x = 36, y = 24} +local REACTOR_CONTROL_BUFFER_VIS_MIN_SIZE = {x = 36, y = 38} +local GRAPH_MENU_MIN_SIZE = {x = 36, y = 52} +local GRAPHS_MIN_SIZE = {x = 57, y = 24} +local STATISTICS_MIN_SIZE = {x = 36, y = 24} + +local function getDrawOptions(monitorSize) + local drawOptions = { + drawInvalidMonitorDimensions = not greaterThanEqualTo(monitorSize, MINIMUM_SIZE_TO_DRAW), + drawReactorControls = greaterThanEqualTo(monitorSize, REACTOR_CONTROL_MIN_SIZE), + drawReactorControlsBufferVisualization = greaterThanEqualTo(monitorSize, REACTOR_CONTROL_BUFFER_VIS_MIN_SIZE), + drawGraphMenu = greaterThanEqualTo(monitorSize, GRAPH_MENU_MIN_SIZE), + drawGraphs = greaterThanEqualTo(monitorSize, GRAPHS_MIN_SIZE), + drawStatistics = greaterThanEqualTo(monitorSize, STATISTICS_MIN_SIZE), + } + return drawOptions +end + ---@class Monitor local Monitor = { navbar = nil, @@ -9,76 +508,130 @@ local Monitor = { mon = nil, ---@type table touch = nil, + monPeripheral = nil, - ---@param self Monitor - ---@return Vector2 - size = function(self) - return Vector2.new(self.mon.getSize()) - end, + -- mutable! + graphSlots = nil, - ---@param self Monitor - ---@return integer - dividerYCoord = function(self) - return self:size().y - 37 - end, - - ---@param self Monitor - ---@return integer - dividerXCoord = function(self) - return self:size().x - 31 - end, + -- Never edit these outside of handleResize()! + size = nil, + dividerYCoord = nil, + dividerXCoord = nil, + drawOptions = nil, ---comment ---@param self Monitor clear = function(self) self.mon.setBackgroundColor(colors.black) self.mon.clear() - self.mon.setTextScale(0.5) + self.monPeripheral.setTextScale(0.5) self.mon.setCursorPos(1,1) end, - handleEvents = function(self, event) + handleClick = function(self, buttonName) + + -- Enable when navbar and page is created + -- local button = self.navbar.touch.buttonList[buttonName] or self.activePage.touch.buttonList[buttonName] + -- button.func() + self.touch.buttonList[buttonName].func() + -- self.touch:drawButton(buttonName) + print(buttonName, "clicked on", self.id) end, + + handleResize = function(self) + self.size = Vector2.new(self.monPeripheral.getSize()) + self.mon = window.create(self.monPeripheral, 1, 1, self.size.x, self.size.y, false) + self.dividerXCoord = calculateDividerXCoord(self.size.x) + self.dividerYCoord = calculateDividerYCoord(self.size.y) + self.touch = _G.Touchpoint.new(self.id, self.mon) + self:clear() + + -- print(self.id) + self.drawOptions = getDrawOptions(self.size) + if self.drawOptions.drawGraphMenu then + local offset = Vector2.new(self.dividerXCoord + 5, self.dividerYCoord - 14) + local size = Vector2.new(20, 3) + addGraphButtons(self, self.graphSlots, offset, size) + end - ---@param self Monitor - drawScene = function(self) - if (invalidDim) then - self.mon.write("Invalid Monitor Dimensions") - return + if self.drawOptions.drawReactorControls then + local offset = Vector2.new(self.dividerXCoord, self.dividerYCoord) + addReactorControlButtons(self.touch, offset, self.drawOptions.drawReactorControlsBufferVisualization) end + end, - if (displayingGraphMenu) then - drawGraphButtons() + handleEvents = function(self, event) + if event[2] ~= self.id then + return + end + local touchpointEvent = { self.touch:handleEvents(unpack(event)) } + if touchpointEvent[1] == "button_click" then + local buttonName = touchpointEvent[3] + self:handleClick(buttonName) + end + if event[1] == "monitor_resize" then + self:handleResize() end - drawControls() - drawStatus() - drawStatistics() - self.touch:draw() end, - ---comment ---@param self Monitor - ---@param reactorStats ReactorStatistics - update = function(self, reactorStats) + draw = function(self) + self.mon.setVisible(false) + self:clear() + + if self.drawOptions.drawInvalidMonitorDimensions then + self.mon.write("Invalid Monitor Dimensions") + end + + if self.drawOptions.drawGraphMenu then + local offset = Vector2.new(self.dividerXCoord + 1, self.dividerYCoord - 14 + 1) + local size = Vector2.new(30, 13) + drawGraphButtons(self.mon, offset, size) + updateGraphMenuButtonStates(self.touch, self.graphSlots) + end + if self.drawOptions.drawReactorControls then + local offset = Vector2.new(self.dividerXCoord + 1, self.dividerYCoord + 1) + local size = Vector2.new(30, 23) + drawControls(self.mon, offset, size, self.drawOptions.drawReactorControlsBufferVisualization) + updateReactorControlButtonStates(self.touch) + end + + if self.drawOptions.drawGraphs then + local offset = Vector2.new(2, 2) + local size = Vector2.new(self.dividerXCoord - 2, self.size.y - 2) + drawGraphs(self.mon, self.size, self.graphSlots, self.dividerXCoord, offset, size) + end + + if self.drawOptions.drawStatistics then + local offset = Vector2.new(self.dividerXCoord + 1, self.size.y - 12) + local size = Vector2.new(30, 12) + drawStatistics(self.mon, offset, size) + end + + self.touch:drawAllButtons() + self.mon.setVisible(true) end } ---comment ----@param peripheralId string +---@param id string ---@return Monitor -local function new(peripheralId) - local mon = peripheral.wrap(peripheralId) - local touchHandler = _G.Touchpoint.new(peripheralId) +local function new(id) + local monPeripheral = peripheral.wrap(id) local monitorInstance = { - id = peripheralId, - mon = mon, - touch = touchHandler, + id = id, + monPeripheral = monPeripheral, + graphSlots = {}, } - setmetatable(monitorInstance, {__index = Monitor}) + monitorInstance:handleResize() + + enableGraph(monitorInstance.graphSlots, "Energy Buffer") + enableGraph(monitorInstance.graphSlots, "Control Level") + enableGraph(monitorInstance.graphSlots, "Temperatures") return monitorInstance end diff --git a/src/classes/page.lua b/src/classes/page.lua index f13da33..f69d886 100644 --- a/src/classes/page.lua +++ b/src/classes/page.lua @@ -3,7 +3,7 @@ local Page = { ---@type string name = nil, ---@type table - peripheral = nil, + mon = nil, ---@type table touch = nil, ---@type Vector2 @@ -11,24 +11,9 @@ local Page = { ---@type Vector2 size = nil, - - ---comment ---@param self Page - clear = function(self) - self.peripheral.setBackgroundColor(colors.black) - self.peripheral.clear() - self.peripheral.setTextScale(0.5) - self.peripheral.setCursorPos(1,1) - end, - - ---@param self Page - drawScene = function(self) - if (invalidDim) then - self.peripheral.write("Invalid Monitor Dimensions") - return - end - - if (displayingGraphMenu) then + draw = function(self) + if (_G.displayingGraphMenu) then drawGraphButtons() end drawControls() diff --git a/src/classes/touchpoint.lua b/src/classes/touchpoint.lua index fe4f231..db965d7 100644 --- a/src/classes/touchpoint.lua +++ b/src/classes/touchpoint.lua @@ -54,27 +54,28 @@ end ---@class Touchpoint local Touchpoint = { - draw = function(self) - local old = term.redirect(self.mon) - term.setTextColor(colors.white) - term.setBackgroundColor(colors.black) - for name, buttonData in pairs(self.buttonList) do - if buttonData.active then - term.setBackgroundColor(buttonData.activeColor) - term.setTextColor(buttonData.activeText) - else - term.setBackgroundColor(buttonData.inactiveColor) - term.setTextColor(buttonData.inactiveText) - end - for i = buttonData.yMin, buttonData.yMax do - term.setCursorPos(buttonData.xMin, i) - term.write(buttonData.label[i - buttonData.yMin + 1]) - end + drawButton = function(self, buttonName) + self.mon.setTextColor(colors.white) + self.mon.setBackgroundColor(colors.black) + local buttonData = self.buttonList[buttonName] + if buttonData == nil then + error("button does not exist") end - if old then - term.redirect(old) + if buttonData.active then + self.mon.setBackgroundColor(buttonData.activeColor) + self.mon.setTextColor(buttonData.activeText) else - term.restore() + self.mon.setBackgroundColor(buttonData.inactiveColor) + self.mon.setTextColor(buttonData.inactiveText) + end + for i = buttonData.yMin, buttonData.yMax do + self.mon.setCursorPos(buttonData.xMin, i) + self.mon.write(buttonData.label[i - buttonData.yMin + 1]) + end + end, + drawAllButtons = function(self) + for name, _ in pairs(self.buttonList) do + self:drawButton(name) end end, add = function(self, name, func, xMin, yMin, xMax, yMax, inactiveColor, activeColor, inactiveText, activeText) @@ -126,8 +127,8 @@ local Touchpoint = { end, run = function(self) while true do - self:draw() - local event = {self:handleEvents(os.pullEvent(self.side == "term" and "mouse_click" or "monitor_touch"))} + self:drawAllButtons() + local event = {self:handleEvents(os.pullEvent(self.id == "term" and "mouse_click" or "monitor_touch"))} if event[1] == "button_click" then self.buttonList[event[2]].func() end @@ -136,17 +137,21 @@ local Touchpoint = { handleEvents = function(self, ...) local event = {...} if #event == 0 then event = {os.pullEvent()} end - if (self.side == "term" and event[1] == "mouse_click") or (self.side ~= "term" and event[1] == "monitor_touch" and event[2] == self.side) then + if (self.id == "term" and event[1] == "mouse_click") or (self.id ~= "term" and event[1] == "monitor_touch" and event[2] == self.id) then local clicked = self.clickMap[event[3]][event[4]] if clicked and self.buttonList[clicked] then - return "button_click", clicked + return "button_click", self.id, clicked end end return unpack(event) end, - toggleButton = function(self, name, noDraw) + setButton = function(self, name, state) + self.buttonList[name].active = state + self:drawButton(name) + end, + toggleButton = function(self, name) self.buttonList[name].active = not self.buttonList[name].active - if not noDraw then self:draw() end + self:drawButton(name) end, flash = function(self, name, duration) self:toggleButton(name) @@ -165,14 +170,14 @@ local Touchpoint = { end end end - self:draw() + self:drawAllButtons() end, } -local function new(monSide) +local function new(id, mon) local touchpointInstance = { - side = monSide or "term", - mon = monSide and peripheral.wrap(monSide) or term.current(), + id = id, + mon = mon, buttonList = {}, clickMap = {}, } diff --git a/src/scripts/controller.lua b/src/scripts/controller.lua index 34b4bec..bd38562 100644 --- a/src/scripts/controller.lua +++ b/src/scripts/controller.lua @@ -28,683 +28,37 @@ THE SOFTWARE. ]] - -local reactorVersion, reactor -local mon, monitorID -local sizex, sizey, dividerXCoord, dividerYCoord, offy -local btnOn, btnOff, invalidDim -local minb, maxb -local rod, rfLost -local storedLastTick, storedThisTick, lastRFT = 0,0,0 -local fuelTemp, caseTemp, fuelUsage, waste, capacity = 0,0,0,0,1 -local t -local displayingGraphMenu = false - -local SECONDS_TO_AVERAGE = 2 - -local averageStoredThisTick = 0 -local averageLastRFT = 0 -local averageRod = 0 -local averageFuelUsage = 0 -local averageWaste = 0 -local averageFuelTemp = 0 -local averageCaseTemp = 0 -local averageRfLost = 0 - -local MINIMUM_DIVIDER_X_VALUE = 3 -local MINIMUM_DIVIDER_Y_VALUE = 1 - --- table of which graphs to draw -local graphsToDraw = {} - --- table of all the graphs -local graphs = -{ - "Energy Buffer", - "Control Level", - "Temperatures", -} - --- marks the offsets for each graph position --- { XOffset, } -local XOffs = -{ - { 4, true}, - {27, true}, - {50, true}, - {73, true}, - {96, true}, -} - ---Helper method for adding buttons -local function addButton(touch, name, callBack, offset, size, color1, color2) - local buttonTopLeftCorner = offset + Vector2.one - local buttonBottomRightCorner = offset + size - touch:add( - name, - callBack, - buttonTopLeftCorner.x, - buttonTopLeftCorner.y, - buttonBottomRightCorner.x, - buttonBottomRightCorner.y, - color1, - color2 - ) -end - -local function minAdd10() - minb = math.min(maxb - 10, minb + 10) -end -local function minSub10() - minb = math.max(0, minb - 10) -end -local function maxAdd10() - maxb = math.min(100, maxb + 10) -end -local function maxSub10() - maxb = math.max(minb + 10, maxb - 10) -end - -local function turnOff() - if (btnOn) then - t:toggleButton("Off") - t:toggleButton("On") - btnOff = true - btnOn = false - reactor.setActive(false) - end -end - -local function turnOn() - if (btnOff) then - t:toggleButton("Off") - t:toggleButton("On") - btnOff = false - btnOn = true - reactor.setActive(true) - end -end - ---adds buttons -local function addButtons() - local buttonSize = Vector2.new(8, 3) - local offsetOnOff = Vector2.new(dividerXCoord + 5, 3 + dividerYCoord) - - addButton( - t, - "On", - turnOn, - offsetOnOff, - buttonSize, - colors.red, - colors.lime - ) - - addButton( - t, - "Off", - turnOff, - offsetOnOff + Vector2.new(12, 0), - buttonSize, - colors.red, - colors.lime - ) - - if (btnOn) then - t:toggleButton("On", true) - else - t:toggleButton("Off", true) - end - - local offset = Vector2.new(dividerXCoord, dividerYCoord) - - if (sizey > 24) then - addButton( - t, - "+ 10", - minAdd10, - offset + Vector2.new(5, 14), - buttonSize, - colors.purple, - colors.pink - ) - addButton( - t, - " + 10 ", - maxAdd10, - offset + Vector2.new(17, 14), - buttonSize, - colors.magenta, - colors.pink - ) - addButton( - t, - "- 10", - minSub10, - offset + Vector2.new(5, 18), - buttonSize, - colors.purple, - colors.pink - ) - addButton( - t, - " - 10 ", - maxSub10, - offset + Vector2.new(17, 18), - buttonSize, - colors.magenta, - colors.pink - ) - end -end - ---Resets the monitor -local function resetMon() - if (monitorID == nil) then - return - end - mon.setBackgroundColor(colors.black) - mon.clear() - mon.setTextScale(0.5) - mon.setCursorPos(1,1) -end - -local function getPercPower() - return averageStoredThisTick / capacity * 100 -end - -local function rnd(num, dig) - return math.floor(10 ^ dig * num) / (10 ^ dig) -end - -local function getEfficiency() - return averageLastRFT / averageFuelUsage -end - -local function format(num) - if (num >= 1000000000) then - return string.format("%7.3f G", num / 1000000000) - elseif (num >= 1000000) then - return string.format("%7.3f M", num / 1000000) - elseif (num >= 1000) then - return string.format("%7.3f K", num / 1000) - elseif (num >= 1) then - return string.format("%7.3f ", num) - elseif (num >= .001) then - return string.format("%7.3f m", num * 1000) - elseif (num >= .000001) then - return string.format("%7.3f u", num * 1000000) - else - return string.format("%7.3f ", 0) - end -end - - -local function getAvailableXOff() - for i,v in pairs(XOffs) do - if (v[2] and v[1] < dividerXCoord - 1) then - v[2] = false - return v[1] - end - end - return -1 -end - -local function getXOff(num) - for i,v in pairs(XOffs) do - if (v[1] == num) then - return v - end - end - return nil -end - -local function enableGraph(name) - if (graphsToDraw[name] ~= nil) then - return - end - local e = getAvailableXOff() - if (e ~= -1) then - graphsToDraw[name] = e - if (displayingGraphMenu) then - t:toggleButton(name) - end - end -end - -local function disableGraph(name) - if (graphsToDraw[name] == nil) then - return - end - if (displayingGraphMenu) then - t:toggleButton(name) - end - getXOff(graphsToDraw[name])[2] = true - graphsToDraw[name] = nil -end - -local function toggleGraph(name) - if (graphsToDraw[name] == nil) then - enableGraph(name) - else - disableGraph(name) - end -end - -local function addGraphButtons() - offy = dividerYCoord - 14 - local graphButtonSize = Vector2.new(20, 3) - local graphButtonOffset = Vector2.new(dividerXCoord + 5, offy) - for i,graphName in pairs(graphs) do - - addButton( - t, - graphName, - function() toggleGraph(graphName) end, - graphButtonOffset + Vector2.new(0, i * 3 - 1), - graphButtonSize, - colors.red, - colors.lime - ) - if (graphsToDraw[graphName] ~= nil) then - t:toggleButton(graphName, true) - end - end -end - -local function drawGraphButtons(mon, offset, size) - - DrawUtil.drawRectangle(mon, colors.black, colors.orange, offset, size) - local textPos = offset + Vector2.new(4, 0) - DrawUtil.drawText( - mon, - " Graph Controls ", - textPos, - colors.black, - colors.orange - ) -end - -local function drawEnergyBuffer(mon, offset, graphSize, drawPercentLabelOnRight) - DrawUtil.drawText(mon, "Energy Buffer", offset, colors.black, colors.orange) - DrawUtil.drawRectangle(mon, colors.red, colors.gray, offset + Vector2.new(0, 1), graphSize) - - local energyBufferMaxHeight = graphSize.y - 2 - local unitEnergyLevel = getPercPower() / 100 - local energyBufferHeight = math.floor(unitEnergyLevel * energyBufferMaxHeight + 0.5) - local rndpw = rnd(getPercPower(), 2) - - local energyBufferColor - if rndpw < maxb and rndpw > minb then - energyBufferColor = colors.green - elseif rndpw >= maxb then - energyBufferColor = colors.orange - elseif rndpw <= minb then - energyBufferColor = colors.blue - end - - local energyBufferTipOffset = offset + Vector2.new(1, 2 + energyBufferMaxHeight - energyBufferHeight) - local energyBufferSize = Vector2.new(graphSize.x - 2, energyBufferHeight) - - DrawUtil.drawFilledRectangle(mon, energyBufferColor, energyBufferTipOffset, energyBufferSize) - - local energyBufferTextOffset = energyBufferTipOffset - local rfLabelBackgroundColor = energyBufferColor - - if energyBufferHeight <= 0 then - energyBufferTextOffset = energyBufferTipOffset + Vector2.new(0, -1) - rfLabelBackgroundColor = colors.red - end - - local percentLabelXOffset = offset.x - 6 - if drawPercentLabelOnRight then - percentLabelXOffset = offset.x + 15 - end - - DrawUtil.drawText( - mon, - string.format(drawPercentLabelOnRight and "%.2f%%" or "%5.2f%%", rndpw), - Vector2.new(percentLabelXOffset, energyBufferTextOffset.y), - colors.black, - energyBufferColor - ) - DrawUtil.drawText( - mon, - format(averageStoredThisTick).."RF", - energyBufferTextOffset, - rfLabelBackgroundColor, - colors.black - ) -end - -local function drawControlGraph(mon, offset, size, averageRod) - local unitRodLevel = averageRod / 100 - local controlRodMaxPixelHeight = size.y - 2 - local controlRodPixelHeight = math.ceil(unitRodLevel * controlRodMaxPixelHeight) - - DrawUtil.drawText( - mon, - "Control Level", - offset + Vector2.new(1, 0), - colors.black, - colors.orange - - ) - DrawUtil.drawRectangle( - mon, - colors.yellow, - colors.gray, - offset + Vector2.new(0, 1), - size - ) - DrawUtil.drawFilledRectangle( - mon, - colors.white, - offset + Vector2.new(3, 2), - Vector2.new(9, controlRodPixelHeight) - ) - - local controlRodLevelTextPos, color - if controlRodPixelHeight > 0 then - color = colors.white - controlRodLevelTextPos = offset + Vector2.new(4, 1 + controlRodPixelHeight) - else - color = colors.yellow - controlRodLevelTextPos = offset + Vector2.new(4, 2) - end - - DrawUtil.drawText( - mon, - string.format("%6.2f%%", averageRod), - controlRodLevelTextPos, - color, - colors.black - ) -end - -local function drawTemperatures(mon, offset, size) - - DrawUtil.drawRectangle(mon, colors.black, colors.gray, offset + Vector2.new(1, 1), size) - - local CASE_TEMP_COLOR = colors.lightBlue - local FUEL_TEMP_COLOR = colors.magenta - local BACKGROUND_COLOR = colors.black - - local assumedMaxCaseTemperature = 3000 - local assumedMaxFuelTemperature = 3000 - local temperatureMaxHeight = size.y - 2 - - local tempUnit = (reactorVersion == "Bigger Reactors") and "K" or "C" - local tempFormat = "%4s"..tempUnit - - DrawUtil.drawText(mon, "Temperatures", offset + Vector2.new(2, 0), BACKGROUND_COLOR, colors.orange) - DrawUtil.drawFilledRectangle(mon, colors.gray, offset + Vector2.new(8, 2), Vector2.new(1, temperatureMaxHeight)) - - -- case temp - DrawUtil.drawText(mon, "Case", offset + Vector2.new(3, 1), colors.gray, colors.lightBlue) - local caseUnit = math.min(averageCaseTemp / assumedMaxCaseTemperature, 1) - local caseTempHeight = math.floor(caseUnit * temperatureMaxHeight + 0.5) - - local caseTempOffset = offset + Vector2.new(2, 2 + temperatureMaxHeight - caseTempHeight) - local caseTempSize = Vector2.new(6, caseTempHeight) - - DrawUtil.drawFilledRectangle(mon, CASE_TEMP_COLOR, caseTempOffset, caseTempSize) - - local caseTempTextOffset = caseTempOffset - local caseTempTextBackgroundColor = CASE_TEMP_COLOR - local caseTempTextColor = BACKGROUND_COLOR - - if caseTempHeight <= 0 then - caseTempTextOffset = caseTempOffset + Vector2.new(0, -1) - caseTempTextColor, caseTempTextBackgroundColor = caseTempTextBackgroundColor, caseTempTextColor - end - - local caseRnd = math.floor(averageCaseTemp + 0.5) - DrawUtil.drawText(mon, string.format(tempFormat, caseRnd..""), caseTempTextOffset, caseTempTextBackgroundColor, caseTempTextColor) - - -- fuel temp - DrawUtil.drawText(mon, "Fuel", offset + Vector2.new(10, 1), colors.gray, colors.lightBlue) - local fuelUnit = math.min(averageFuelTemp / assumedMaxFuelTemperature, 1) - local fuelTempHeight = math.floor(fuelUnit * temperatureMaxHeight + 0.5) - - local fuelTempOffset = offset + Vector2.new(9, 2 + temperatureMaxHeight - fuelTempHeight) - local fuelTempSize = Vector2.new(6, fuelTempHeight) - - DrawUtil.drawFilledRectangle(mon, FUEL_TEMP_COLOR, fuelTempOffset, fuelTempSize) - - local fuelTempTextOffset = fuelTempOffset - local fuelTempTextBackgroundColor = FUEL_TEMP_COLOR - local fuelTempTextColor = BACKGROUND_COLOR - - if fuelTempHeight <= 0 then - fuelTempTextOffset = fuelTempOffset + Vector2.new(0, -1) - fuelTempTextColor, fuelTempTextBackgroundColor = fuelTempTextBackgroundColor, fuelTempTextColor - end - - local fuelRnd = math.floor(averageFuelTemp + 0.5) - DrawUtil.drawText(mon, string.format(tempFormat, fuelRnd..""), fuelTempTextOffset, fuelTempTextBackgroundColor, fuelTempTextColor) -end - -local function drawGraph(name, graphOffset, graphSize) - if (name == "Energy Buffer") then - local drawPercentLabelOnRight = graphOffset.x + 19 < dividerXCoord - 1 - drawEnergyBuffer(mon, graphOffset, graphSize, drawPercentLabelOnRight) - elseif (name == "Control Level") then - drawControlGraph(mon, graphOffset, graphSize, averageRod) - elseif (name == "Temperatures") then - drawTemperatures(mon, graphOffset, graphSize) - end -end - -local function drawGraphs(mon, graphsToDraw, dividerXCoord, offset, size) - DrawUtil.drawRectangle(mon, colors.black, colors.lightBlue, offset, size) - local label = " Reactor Graphs " - DrawUtil.drawText( - mon, - label, - offset + Vector2.new(dividerXCoord - (#label + 5) - 1, 0), - colors.black, - colors.lightBlue - ) - - local graphSize = Vector2.new(15, sizey - 7) - local graphYOffset = 4 - for graphName, graphXOffset in pairs(graphsToDraw) do - if (graphXOffset + graphSize.x < dividerXCoord) then - drawGraph(graphName, Vector2.new(graphXOffset, graphYOffset), graphSize) - end - end -end - -local function drawControls(mon, offset, size, drawBufferVisualization) - - DrawUtil.drawRectangle(mon, colors.black, colors.cyan, offset, size) - DrawUtil.drawText(mon, " Reactor Controls ", offset + Vector2.new(4, 0), colors.black, colors.cyan) - - local reactorOnOffLabel = "Reactor "..(btnOn and "Online" or "Offline") - local reactorOnOffLabelColor = btnOn and colors.green or colors.red - DrawUtil.drawText(mon, reactorOnOffLabel, offset + Vector2.new(7, 2), colors.black, reactorOnOffLabelColor) - - if not drawBufferVisualization then - return - end - - local bufferMinInPixels = minb / 5 - local bufferMaxInPixels = maxb / 5 - local bufferRangePixelWidth = bufferMaxInPixels - bufferMinInPixels - - local bufferVisualOffset = offset + Vector2.new(5, 8) - local bufferVisualSize = Vector2.new(20, 3) - - DrawUtil.drawText(mon, "Buffer Target Range", bufferVisualOffset + Vector2.new(0, -1), colors.black, colors.orange) - DrawUtil.drawFilledRectangle(mon, colors.red, bufferVisualOffset, bufferVisualSize) - DrawUtil.drawFilledRectangle(mon, colors.green, bufferVisualOffset + Vector2.new(bufferMinInPixels, 0), Vector2.new(bufferRangePixelWidth, 3)) - - DrawUtil.drawText( - mon, - string.format("%3s", minb.."%"), - bufferVisualOffset + Vector2.new(bufferMinInPixels - 3, bufferVisualSize.y), - colors.black, - colors.purple - ) - DrawUtil.drawText( - mon, - maxb.."%", - bufferVisualOffset + Vector2.new(bufferMaxInPixels, bufferVisualSize.y), - colors.black, - colors.magenta - ) - DrawUtil.drawText(mon, "Min", offset + Vector2.new(7, 13), colors.black, colors.purple) - DrawUtil.drawText(mon, "Max", offset + Vector2.new(19, 13), colors.black, colors.purple) -end - -local function drawStatistics(mon, offset, size) - DrawUtil.drawRectangle(mon, colors.black, colors.blue, offset, size) - DrawUtil.drawText( - mon, - " Reactor Statistics ", - offset + Vector2.new(4, 0), - colors.black, - colors.blue - ) - - DrawUtil.drawText( - mon, - "Generating : "..format(averageLastRFT).."RF/t", - offset + Vector2.new(2, 2), - colors.black, - colors.green - ) - - DrawUtil.drawText( - mon, - "RF Drain "..(averageStoredThisTick <= averageLastRFT and "> " or ": ")..format(averageRfLost).."RF/t", - offset + Vector2.new(2, 4), - colors.black, - colors.red - ) - - DrawUtil.drawText( - mon, - "Efficiency : "..format(getEfficiency()).."RF/B", - offset + Vector2.new(2, 6), - colors.black, - colors.green - ) - - DrawUtil.drawText( - mon, - "Fuel Usage : "..format(averageFuelUsage).."B/t", - offset + Vector2.new(2, 8), - colors.black, - colors.green - ) - - DrawUtil.drawText( - mon, - "Waste : "..string.format("%7d mB", waste), - offset + Vector2.new(2, 10), - colors.black, - colors.green - ) -end - ---Draw a scene -local function drawScene() - if (monitorID == nil) then - return - end - if (invalidDim) then - mon.write("Invalid Monitor Dimensions") - return - end - - local offset, size - - if (displayingGraphMenu) then - offset = Vector2.new(dividerXCoord + 1, offy + 1) - size = Vector2.new(30, 13) - drawGraphButtons(mon, offset, size) - end - - offset = Vector2.new(dividerXCoord + 1, dividerYCoord + 1) - size = Vector2.new(30, 23) - local drawBufferVisualization = true - if (sizey <= 24) then - size = Vector2.new(30, 9) - drawBufferVisualization = false - end - drawControls(mon, offset, size, drawBufferVisualization) - - if (dividerXCoord > MINIMUM_DIVIDER_X_VALUE) then - offset = Vector2.new(2, 2) - size = Vector2.new(dividerXCoord - 2, sizey - 2) - drawGraphs(mon, graphsToDraw, dividerXCoord, offset, size) - end - - offset = Vector2.new(dividerXCoord + 1, sizey - 12) - size = Vector2.new(30, 12) - drawStatistics(mon, offset, size) - - t:draw() -end - -local function getAllPeripheralIdsForType(targetType) - ---@type string[] - local peripheralIds = {} - for _, id in pairs(peripheral.getNames()) do - if (peripheral.getType(id) == targetType) then - table.insert(peripheralIds, id) - end - end - return peripheralIds -end - ----@type table --- local monitors = {} - -local function initMon2(id) - local monitor = Monitor.new(id) - monitor.mon.clear() - t = monitor.touch - - sizex, sizey = monitor.mon.getSize() - dividerYCoord = monitor:dividerYCoord() - dividerXCoord = monitor:dividerXCoord() - - if sizex <= 36 then - dividerXCoord = MINIMUM_DIVIDER_X_VALUE - end - if sizey <= 24 then - dividerYCoord = MINIMUM_DIVIDER_Y_VALUE - end - - displayingGraphMenu = pcall(function() addGraphButtons() end) - _, invalidDim = pcall(function() addButtons() end) - - return monitor -end - -local function updateMonitors(monitors) - for _, monitor in pairs(monitors) do - ---@type ReactorStatistics - local reactorStats = {} - monitor:update(reactorStats) - end -end - -local pretty = require "cc.pretty" - -local function discoverAndInitMonitors() - local monitors = {} - local ids = getAllPeripheralIdsForType("monitor") - pretty.pretty_print(ids) - - for _, id in pairs(ids) do - monitors[id] = initMon2(id) - end - return monitors -end +---@type table +local monitors = {} + +_G.reactorVersion = nil +_G.reactor = nil +_G.btnOn = nil +_G.btnOff = nil +_G.minb = nil +_G.maxb = nil +_G.rod = nil +_G.rfLost = nil +_G.storedLastTick = 0 +_G.storedThisTick = 0 +_G.lastRFT = 0 + +_G.fuelTemp = 0 +_G.caseTemp = 0 +_G.fuelUsage = 0 +_G.waste = 0 +_G.capacity = 1 + +_G.SECONDS_TO_AVERAGE = 2 + +_G.averageStoredThisTick = 0 +_G.averageLastRFT = 0 +_G.averageRod = 0 +_G.averageFuelUsage = 0 +_G.averageWaste = 0 +_G.averageFuelTemp = 0 +_G.averageCaseTemp = 0 +_G.averageRfLost = 0 --returns the side that a given peripheral type is connected to local function getPeripheral(targetType) @@ -716,43 +70,10 @@ local function getPeripheral(targetType) return "" end ---Creates all the buttons and determines monitor size -local function initMon() - monitorID = getPeripheral("monitor") - if (monitorID == nil or monitorID == "") then - monitorID = nil - return - end - - local monitor = Monitor.new(monitorID) - mon = monitor.mon - - if mon == nil then - monitorID = nil - return - end - - resetMon() - t = Touchpoint.new(monitorID) - sizex, sizey = mon.getSize() - dividerYCoord = sizey - 37 - dividerXCoord = sizex - 31 - - if sizex <= 36 then - dividerXCoord = MINIMUM_DIVIDER_X_VALUE - end - if sizey <= 24 then - dividerYCoord = MINIMUM_DIVIDER_Y_VALUE - end - - displayingGraphMenu = pcall(function() addGraphButtons() end) - _, invalidDim = pcall(function() addButtons() end) -end - local function setRods(level) level = math.max(level, 0) level = math.min(level, 100) - reactor.setAllControlRodLevels(level) + _G.reactor.setAllControlRodLevels(level) end local function lerp(start, finish, t) @@ -799,27 +120,28 @@ local function iteratePID(pid, error) pid.lastError = error return rodLevel end +local pretty = require("cc.pretty") local function updateRods() - if (not btnOn) then + if (not _G.btnOn) then return end - local currentRF = storedThisTick - local diffb = maxb - minb - local minRF = minb / 100 * capacity - local diffRF = diffb / 100 * capacity + local currentRF = _G.storedLastTick + local diffb = _G.maxb - _G.minb + local minRF = _G.minb / 100 * _G.capacity + local diffRF = diffb / 100 * _G.capacity local diffr = diffb / 100 - local targetRFT = rfLost - local currentRFT = lastRFT + local targetRFT = _G.rfLost + local currentRFT = _G.lastRFT local targetRF = diffRF / 2 + minRF pid.setpointRFT = targetRFT - pid.setpointRF = targetRF / capacity * 1000 + pid.setpointRF = targetRF / _G.capacity * 1000 local errorRFT = pid.setpointRFT - currentRFT - local errorRF = pid.setpointRF - currentRF / capacity * 1000 + local errorRF = pid.setpointRF - currentRF / _G.capacity * 1000 - local W_RFT = lerp(1, 0, (math.abs(targetRF - currentRF) / capacity / (diffr / 4))) + local W_RFT = lerp(1, 0, (math.abs(targetRF - currentRF) / _G.capacity / (diffr / 4))) W_RFT = math.max(math.min(W_RFT, 1), 0) local W_RF = (1 - W_RFT) -- Adjust the weight for energy error @@ -828,6 +150,16 @@ local function updateRods() local combinedError = W_RFT * errorRFT + W_RF * errorRF local error = combinedError local rftRodLevel = iteratePID(pid, error) + + -- if rftRodLevel == 0 then + -- pretty.pretty_print({ + -- newRod = rftRodLevel, + -- pid = pid, + -- error = error, + -- currentRFT = currentRFT, + -- targetRFT = targetRFT, + -- }) + -- end -- Set control rod levels setRods(rftRodLevel) @@ -837,10 +169,10 @@ end local function saveToConfig() local file = fs.open(tag.."Serialized.txt", "w") local configs = { - maxb = maxb, - minb = minb, - rod = rod, - btnOn = btnOn, + ["_G.maxb"] = _G.maxb, + ["_G.minb"] = _G.minb, + ["_G.rod"] = _G.rod, + ["_G.btnOn"] = _G.btnOn, graphsToDraw = graphsToDraw, XOffs = XOffs, } @@ -859,51 +191,50 @@ local caseTempValues = {} local rfLostValues = {} local function updateStats() - storedLastTick = storedThisTick - if (reactorVersion == "Big Reactors") then - storedThisTick = reactor.getEnergyStored() - lastRFT = reactor.getEnergyProducedLastTick() - rod = reactor.getControlRodLevel(0) - fuelUsage = reactor.getFuelConsumedLastTick() / 1000 - waste = reactor.getWasteAmount() - fuelTemp = reactor.getFuelTemperature() - caseTemp = reactor.getCasingTemperature() + if (_G.reactorVersion == "Big Reactors") then + _G.storedThisTick = _G.reactor.getEnergyStored() + _G.lastRFT = _G.reactor.getEnergyProducedLastTick() + _G.rod = _G.reactor.getControlRodLevel(0) + _G.fuelUsage = _G.reactor.getFuelConsumedLastTick() / 1000 + _G.waste = _G.reactor.getWasteAmount() + _G.fuelTemp = _G.reactor.getFuelTemperature() + _G.caseTemp = _G.reactor.getCasingTemperature() -- Big Reactors doesn't give us a way to directly query RF capacity through CC APIs - capacity = math.max(capacity, reactor.getEnergyStored) - elseif (reactorVersion == "Extreme Reactors") then - local bat = reactor.getEnergyStats() - local fuel = reactor.getFuelStats() - - storedThisTick = bat.energyStored - lastRFT = bat.energyProducedLastTick - capacity = bat.energyCapacity - rod = reactor.getControlRodLevel(0) - fuelUsage = fuel.fuelConsumedLastTick / 1000 - waste = reactor.getWasteAmount() - fuelTemp = reactor.getFuelTemperature() - caseTemp = reactor.getCasingTemperature() - elseif (reactorVersion == "Bigger Reactors") then - storedThisTick = reactor.battery().stored() - lastRFT = reactor.battery().producedLastTick() - capacity = reactor.battery().capacity() - rod = reactor.getControlRod(0).level() - fuelUsage = reactor.fuelTank().burnedLastTick() / 1000 - waste = reactor.fuelTank().waste() - fuelTemp = reactor.fuelTemperature() - caseTemp = reactor.casingTemperature() - end - rfLost = lastRFT + storedLastTick - storedThisTick + _G.capacity = math.max(_G.capacity, _G.reactor.getEnergyStored) + elseif (_G.reactorVersion == "Extreme Reactors") then + local bat = _G.reactor.getEnergyStats() + local fuel = _G.reactor.getFuelStats() + + _G.storedThisTick = bat.energyStored + _G.lastRFT = bat.energyProducedLastTick + _G.capacity = bat.energyCapacity + _G.rod = _G.reactor.getControlRodLevel(0) + _G.fuelUsage = fuel.fuelConsumedLastTick / 1000 + _G.waste = _G.reactor.getWasteAmount() + _G.fuelTemp = _G.reactor.getFuelTemperature() + _G.caseTemp = _G.reactor.getCasingTemperature() + elseif (_G.reactorVersion == "Bigger Reactors") then + _G.storedThisTick = _G.reactor.battery().stored() + _G.lastRFT = _G.reactor.battery().producedLastTick() + _G.capacity = _G.reactor.battery().capacity() + _G.rod = _G.reactor.getControlRod(0).level() + _G.fuelUsage = _G.reactor.fuelTank().burnedLastTick() / 1000 + _G.waste = _G.reactor.fuelTank().waste() + _G.fuelTemp = _G.reactor.fuelTemperature() + _G.caseTemp = _G.reactor.casingTemperature() + end + _G.rfLost = _G.lastRFT + _G.storedLastTick - _G.storedThisTick -- Add the values to the arrays - table.insert(storedThisTickValues, storedThisTick) - table.insert(lastRFTValues, lastRFT) - table.insert(rodValues, rod) - table.insert(fuelUsageValues, fuelUsage) - table.insert(wasteValues, waste) - table.insert(fuelTempValues, fuelTemp) - table.insert(caseTempValues, caseTemp) - table.insert(rfLostValues, rfLost) - - local maxIterations = 20 * SECONDS_TO_AVERAGE + table.insert(storedThisTickValues, _G.storedThisTick) + table.insert(lastRFTValues, _G.lastRFT) + table.insert(rodValues, _G.rod) + table.insert(fuelUsageValues, _G.fuelUsage) + table.insert(wasteValues, _G.waste) + table.insert(fuelTempValues, _G.fuelTemp) + table.insert(caseTempValues, _G.caseTemp) + table.insert(rfLostValues, _G.rfLost) + + local maxIterations = 20 * _G.SECONDS_TO_AVERAGE while #storedThisTickValues > maxIterations do table.remove(storedThisTickValues, 1) table.remove(lastRFTValues, 1) @@ -916,19 +247,19 @@ local function updateStats() end -- Calculate running averages - averageStoredThisTick = calculateAverage(storedThisTickValues) - averageLastRFT = calculateAverage(lastRFTValues) - averageRod = calculateAverage(rodValues) - averageFuelUsage = calculateAverage(fuelUsageValues) - averageWaste = calculateAverage(wasteValues) - averageFuelTemp = calculateAverage(fuelTempValues) - averageCaseTemp = calculateAverage(caseTempValues) - averageRfLost = calculateAverage(rfLostValues) + _G.averageStoredThisTick = calculateAverage(storedThisTickValues) + _G.averageLastRFT = calculateAverage(lastRFTValues) + _G.averageRod = calculateAverage(rodValues) + _G.averageFuelUsage = calculateAverage(fuelUsageValues) + _G.averageWaste = calculateAverage(wasteValues) + _G.averageFuelTemp = calculateAverage(fuelTempValues) + _G.averageCaseTemp = calculateAverage(caseTempValues) + _G.averageRfLost = calculateAverage(rfLostValues) end --Initialize variables from either a config file or the defaults local function loadFromConfig() - invalidDim = false + _G.invalidDim = false local legacyConfigExists = fs.exists(tag..".txt") local newConfigExists = fs.exists(tag.."Serialized.txt") if (newConfigExists) then @@ -938,10 +269,10 @@ local function loadFromConfig() local serialized = file.readAll() local deserialized = textutils.unserialise(serialized) - maxb = deserialized.maxb - minb = deserialized.minb - rod = deserialized.rod - btnOn = deserialized.btnOn + _G.maxb = deserialized.maxb + _G.minb = deserialized.minb + _G.rod = deserialized.rod + _G.btnOn = deserialized.btnOn graphsToDraw = deserialized.graphsToDraw XOffs = deserialized.XOffs elseif (legacyConfigExists) then @@ -953,10 +284,10 @@ local function loadFromConfig() _ = tonumber(file.readLine()) _ = tonumber(file.readLine()) end - maxb = tonumber(file.readLine()) - minb = tonumber(file.readLine()) - rod = tonumber(file.readLine()) - btnOn = file.readLine() == "true" + _G.maxb = tonumber(file.readLine()) + _G.minb = tonumber(file.readLine()) + _G.rod = tonumber(file.readLine()) + _G.btnOn = file.readLine() == "true" --read Graph data for i in pairs(XOffs) do @@ -975,112 +306,138 @@ local function loadFromConfig() else print("Config file not found, generating default settings!") - maxb = 70 - minb = 30 - rod = 80 - btnOn = false - if (monitorID == nil) then - btnOn = true - end - sizex, sizey = 100, 52 - dividerXCoord = sizex - 31 - dividerYCoord = sizey - 37 - enableGraph("Energy Buffer") - enableGraph("Control Level") - enableGraph("Temperatures") + _G.maxb = 70 + _G.minb = 30 + _G.rod = 80 + _G.btnOn = false end - btnOff = not btnOn - reactor.setActive(btnOn) + _G.btnOff = not _G.btnOn + _G.reactor.setActive(_G.btnOn) end -local function startTimer(ticksToUpdate, callback) - local timeToUpdate = ticksToUpdate * 0.05 - local id = os.startTimer(timeToUpdate) - local fun = function(event) - if (event[1] == "timer" and event[2] == id) then - id = os.startTimer(timeToUpdate) - callback() +local function getAllPeripheralIdsForType(targetType) + ---@type table + local peripheralIds = {} + for _, id in pairs(peripheral.getNames()) do + if (peripheral.getType(id) == targetType) then + peripheralIds[id] = true end end - return fun + return peripheralIds end +local function disconnectMonitor(monitorID) + if monitors[monitorID] == nil then + return + end --- Main loop, handles all the events -local function loop() - local ticksToUpdateStats = 1 - local ticksToRedraw = 4 - - local hasClicked = false + print("Monitor "..monitorID.." disconnected!") + monitors[monitorID] = nil +end - local updateStatsTick = startTimer( - ticksToUpdateStats, - function() - updateStats() - updateRods() - end - ) - local redrawTick = startTimer( - ticksToRedraw, - function() - if (not hasClicked) then - resetMon() - drawScene() +local function connectMonitor(monitorID) + print("Monitor "..monitorID.." connected!") + monitors[monitorID] = Monitor.new(monitorID) +end + +local function discoverAndConnectMonitors() + local ids = getAllPeripheralIdsForType("monitor") + for id, _ in pairs(ids) do + connectMonitor(id) + end +end + +local function redrawMonitors() + for _, monitor in pairs(monitors) do + monitor:draw() + -- ---@type ReactorStatistics + -- local reactorStats = {} + -- monitor:update(reactorStats) + end +end + +local function eventListener() + while true do + local event = { os.pullEvent() } + + if event[1] == "monitor_touch" or event[1] == "monitor_resize" then + local monitor = monitors[event[2]] + if monitor ~= nil then + monitor:handleEvents(event) end - hasClicked = false end - ) - local handleResize = function(event) - if (event[1] == "monitor_resize") then - local peripheralId = event[2] - initMon() + + if event[1] == "peripheral" and peripheral.getType(event[2]) == "monitor" then + connectMonitor(event[2]) end - end - local handleClick = function(event) - if (event[1] == "button_click") then - t.buttonList[event[2]].func() - saveToConfig() - resetMon() - drawScene() - hasClicked = true + + if event[1] == "peripheral_detach" then + disconnectMonitor(event[2]) end end - while (true) do - local event = { os.pullEvent() } +end + - if monitorID ~= nil then - event = { t:handleEvents(unpack(event)) } +-- Main loop, handles all the events +local function loop() + local pretty = require "cc.pretty" + os.startTimer(0) + + local lastTime = os.time() * 1000 + while true do + os.pullEvent("timer") + local curTime = math.floor(os.time() * 1000 + 0.5) + if curTime <= lastTime then + goto continue + end + + term.setCursorPos(41,1) + term.write("Tick: "..string.format("%5s", os.time() * 1000)) + _G.storedLastTick = _G.storedThisTick + _G.lastLastRFT = math.floor(_G.lastRFT + 0.5) + + + updateStats() + while _G.lastRFT == 0 and _G.lastLastRFT ~= 0 do + updateStats() end - updateStatsTick(event) - redrawTick(event) - handleResize(event) - handleClick(event) + + updateRods() + -- if os.time() * 1000 % 2 == 0 then + -- print(os.time() * 1000) + redrawMonitors() + -- end + + -- redrawTick(event) + lastTime = curTime + ::continue:: + os.startTimer(0) end end local function detectReactor() -- Bigger Reactors V1. local reactor_bigger_v1 = getPeripheral("bigger-reactor") - reactor = reactor_bigger_v1 ~= nil and peripheral.wrap(reactor_bigger_v1) - if (reactor ~= nil) then - reactorVersion = "Bigger Reactors" + _G.reactor = reactor_bigger_v1 ~= nil and peripheral.wrap(reactor_bigger_v1) + if (_G.reactor ~= nil) then + _G.reactorVersion = "Bigger Reactors" return true end -- Bigger Reactors V2 local reactor_bigger_v2 = getPeripheral("BiggerReactors_Reactor") - reactor = reactor_bigger_v2 ~= nil and peripheral.wrap(reactor_bigger_v2) - if (reactor ~= nil) then - reactorVersion = "Bigger Reactors" + _G.reactor = reactor_bigger_v2 ~= nil and peripheral.wrap(reactor_bigger_v2) + if (_G.reactor ~= nil) then + _G.reactorVersion = "Bigger Reactors" return true end -- Big Reactors or Extreme Reactors local reactor_extreme_or_big = getPeripheral("BigReactors-Reactor") - reactor = reactor_extreme_or_big ~= nil and peripheral.wrap(reactor_extreme_or_big) - if (reactor ~= nil) then - reactorVersion = (reactor.mbIsConnected ~= nil) and "Extreme Reactors" or "Big Reactors" + _G.reactor = reactor_extreme_or_big ~= nil and peripheral.wrap(reactor_extreme_or_big) + if (_G.reactor ~= nil) then + _G.reactorVersion = (_G.reactor.mbIsConnected ~= nil) and "Extreme Reactors" or "Big Reactors" return true end return false @@ -1093,31 +450,38 @@ function _G.main() term.setCursorPos(1,1) local reactorDetected = false - while (not reactorDetected) do + while not reactorDetected do reactorDetected = detectReactor() - if (not reactorDetected) then + if not reactorDetected then print("Reactor not detected! Trying again...") sleep(1) end end - - print("Reactor detected! Proceeding with initialization ") - print("Loading config...") - loadFromConfig() - print("Initializing monitor if connected...") + print("Reactor detected!") - -- local monitors = discoverAndInitMonitors() - initMon() - print("Writing config to disk...") - saveToConfig() - print("Reactor initialization done! Starting controller") + -- print("Loading config...") + -- loadFromConfig() + -- print("Initializing monitor if connected...") - term.clear() - term.setCursorPos(1,1) - print("Reactor Controller Version "..version) - print("Reactor Mod: "..reactorVersion) + _G.maxb = 70 + _G.minb = 30 + _G.rod = 80 + _G.btnOn = true + _G.btnOff = not _G.btnOn + _G.reactor.setActive(_G.btnOn) + discoverAndConnectMonitors() + + -- initMon() + -- print("Writing config to disk...") + -- saveToConfig() + -- print("Reactor initialization done! Starting controller") + + -- term.clear() + -- term.setCursorPos(1,1) + -- print("Reactor Controller Version "..version) + -- print("Reactor Mod: "..reactorVersion) --main loop - loop() + parallel.waitForAny(loop, eventListener) end diff --git a/src/util/draw.lua b/src/util/draw.lua index 5c65516..c95199e 100644 --- a/src/util/draw.lua +++ b/src/util/draw.lua @@ -1,41 +1,16 @@ ----comment ----@param mon table ----@param drawFunction function -local function executeAndRestoreMonitorSettings(mon, drawFunction) - if (mon == nil) then - error("Error! Mon is nil") - return - end - - local originalCursorPos = Vector2.new(mon.getCursorPos()) - local originalBackgroundColor = mon.getBackgroundColor() - local originalTextColor = mon.getTextColor() - - drawFunction() - - mon.setCursorPos(originalCursorPos.x, originalCursorPos.y) - mon.setBackgroundColor(originalBackgroundColor) - mon.setTextColor(originalTextColor) -end - ----comment Draws a filled rectangle +---comment Draws a rectangle with a border ---@param mon table ---@param color any ---@param offset Vector2 ---@param size Vector2 local function drawFilledRectangle(mon, color, offset, size) - executeAndRestoreMonitorSettings( - mon, - function () - local horizLine = string.rep(" ", size.x) - mon.setBackgroundColor(color) - for i=0, size.y - 1 do - mon.setCursorPos(offset.x, offset.y + i) - mon.write(horizLine) - end - end - ) + local old = term.redirect(mon) + size.x = math.max(size.x, 1) + size.y = math.max(size.y, 1) + local endCoord = offset + size - 1 + paintutils.drawFilledBox(offset.x, offset.y, endCoord.x, endCoord.y, color) + term.redirect(old) end ---comment Draws a rectangle with a fill color and border @@ -44,9 +19,14 @@ end ---@param outerColor any ---@param offset Vector2 ---@param size Vector2 -local function drawRectangle(mon, innerColor, outerColor, offset, size) - drawFilledRectangle(mon, outerColor, offset, size) - drawFilledRectangle(mon, innerColor, offset + 1, size - 2) +local function drawFilledBoxWithBorder(mon, innerColor, outerColor, offset, size) + local old = term.redirect(mon) + size.x = math.max(size.x, 1) + size.y = math.max(size.y, 1) + local endCoord = offset + size - 1 + paintutils.drawBox(offset.x, offset.y, endCoord.x, endCoord.y, outerColor) + paintutils.drawFilledBox(offset.x + 1, offset.y + 1, endCoord.x - 1, endCoord.y - 1, innerColor) + term.redirect(old) end ---comment Draws text on the screen @@ -56,19 +36,13 @@ end ---@param backgroundColor any ---@param textColor any local function drawText(mon, text, pos, backgroundColor, textColor) - executeAndRestoreMonitorSettings( - mon, - function () - mon.setCursorPos(pos.x, pos.y) - mon.setBackgroundColor(backgroundColor) - mon.setTextColor(textColor) - mon.write(text) - end - ) + mon.setCursorPos(pos.x, pos.y) + local len = #text + mon.blit(text, string.rep(colors.toBlit(textColor), len), string.rep(colors.toBlit(backgroundColor), len)) end _G.DrawUtil = { - drawRectangle = drawRectangle, + drawRectangle = drawFilledBoxWithBorder, drawFilledRectangle = drawFilledRectangle, drawText = drawText, } From bb95ed54869dc92ae51fb33dbbce8dc364d4bdf6 Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Sun, 5 Jan 2025 12:11:21 -0500 Subject: [PATCH 21/38] Fix various issues with multiple monitors and reactor statistics being incorrect --- src/classes/graph.lua | 2 +- src/classes/monitor.lua | 24 +++--- src/scripts/controller.lua | 164 +++++++++++++++++++------------------ src/util/draw.lua | 17 ++-- 4 files changed, 109 insertions(+), 98 deletions(-) diff --git a/src/classes/graph.lua b/src/classes/graph.lua index 5838a8f..92e83d5 100644 --- a/src/classes/graph.lua +++ b/src/classes/graph.lua @@ -59,7 +59,7 @@ local function drawControlGraph(mon, offset, size, averageRod) colors.orange ) - DrawUtil.drawRectangle( + DrawUtil.drawFilledBoxWithBorder( mon, colors.yellow, colors.gray, diff --git a/src/classes/monitor.lua b/src/classes/monitor.lua index 883ba2d..e747b71 100644 --- a/src/classes/monitor.lua +++ b/src/classes/monitor.lua @@ -95,7 +95,6 @@ local function addReactorControlButtons(touch, offset, shouldDrawBufferVisualiza addButton(touch, "On", turnOn, offsetOnOff, buttonSize, colors.red, colors.lime) addButton(touch, "Off", turnOff, offsetOnOff + Vector2.new(12, 0), buttonSize, colors.red, colors.lime) - if shouldDrawBufferVisualization then addButton(touch, "+ 10", minAdd10, offset + Vector2.new(5, 14), buttonSize, colors.purple, colors.pink) addButton(touch, " + 10 ", maxAdd10, offset + Vector2.new(17, 14), buttonSize, colors.magenta, colors.pink) @@ -175,7 +174,7 @@ end local function drawGraphButtons(mon, offset, size) - DrawUtil.drawRectangle(mon, colors.black, colors.orange, offset, size) + DrawUtil.drawFilledBoxWithBorder(mon, colors.black, colors.orange, offset, size) local textPos = offset + Vector2.new(4, 0) DrawUtil.drawText(mon, " Graph Controls ", textPos, colors.black, colors.orange ) @@ -183,7 +182,7 @@ end local function drawEnergyBuffer(mon, offset, graphSize, drawPercentLabelOnRight) DrawUtil.drawText(mon, "Energy Buffer", offset, colors.black, colors.orange) - DrawUtil.drawRectangle(mon, colors.red, colors.gray, offset + Vector2.new(0, 1), graphSize) + DrawUtil.drawFilledBoxWithBorder(mon, colors.red, colors.gray, offset + Vector2.new(0, 1), graphSize) local energyBufferMaxHeight = graphSize.y - 2 local unitEnergyLevel = getPercPower() / 100 @@ -239,24 +238,24 @@ local function drawControlGraph(mon, offset, size, averageRod) local controlRodPixelHeight = math.ceil(unitRodLevel * controlRodMaxPixelHeight) DrawUtil.drawText(mon, "Control Level", offset + Vector2.new(1, 0), colors.black, colors.orange) - DrawUtil.drawRectangle(mon, colors.yellow, colors.gray, offset + Vector2.new(0, 1), size) + DrawUtil.drawFilledBoxWithBorder(mon, colors.yellow, colors.gray, offset + Vector2.new(0, 1), size) DrawUtil.drawFilledRectangle(mon, colors.white, offset + Vector2.new(3, 2), Vector2.new(9, controlRodPixelHeight)) local controlRodLevelTextPos, color if controlRodPixelHeight > 0 then color = colors.white - controlRodLevelTextPos = offset + Vector2.new(3, 1 + controlRodPixelHeight) + controlRodLevelTextPos = offset + Vector2.new(4, 1 + controlRodPixelHeight) else color = colors.yellow - controlRodLevelTextPos = offset + Vector2.new(3, 2) + controlRodLevelTextPos = offset + Vector2.new(4, 2) end - DrawUtil.drawText(mon, string.format(" %6.2f%% ", averageRod), controlRodLevelTextPos, color, colors.black) + DrawUtil.drawText(mon, string.format("%6.2f%%", averageRod), controlRodLevelTextPos, color, colors.black) end local function drawTemperatures(mon, offset, size) - DrawUtil.drawRectangle(mon, colors.black, colors.gray, offset + Vector2.new(1, 1), size) + DrawUtil.drawFilledBoxWithBorder(mon, colors.black, colors.gray, offset + Vector2.new(1, 1), size) local CASE_TEMP_COLOR = colors.lightBlue local FUEL_TEMP_COLOR = colors.magenta @@ -329,7 +328,7 @@ local function drawGraph(mon, dividerXCoord, name, graphOffset, graphSize) end local function drawGraphs(mon, monitorSize, graphSlots, dividerXCoord, offset, size) - DrawUtil.drawRectangle(mon, colors.black, colors.lightBlue, offset, size) + DrawUtil.drawFilledBoxWithBorder(mon, colors.black, colors.lightBlue, offset, size) local label = " Reactor Graphs " DrawUtil.drawText( mon, @@ -353,7 +352,7 @@ local function drawControls(mon, offset, size, drawBufferVisualization) size = Vector2.new(30, 9) end - DrawUtil.drawRectangle(mon, colors.black, colors.cyan, offset, size) + DrawUtil.drawFilledBoxWithBorder(mon, colors.black, colors.cyan, offset, size) DrawUtil.drawText(mon, " Reactor Controls ", offset + Vector2.new(4, 0), colors.black, colors.cyan) local reactorOnOffLabel = "Reactor "..(_G.btnOn and "Online" or "Offline") @@ -394,7 +393,7 @@ local function drawControls(mon, offset, size, drawBufferVisualization) end local function drawStatistics(mon, offset, size) - DrawUtil.drawRectangle(mon, colors.black, colors.blue, offset, size) + DrawUtil.drawFilledBoxWithBorder(mon, colors.black, colors.blue, offset, size) DrawUtil.drawText(mon, " Reactor Statistics ", offset + Vector2.new(4, 0), colors.black, colors.blue ) @@ -524,7 +523,6 @@ local Monitor = { clear = function(self) self.mon.setBackgroundColor(colors.black) self.mon.clear() - self.monPeripheral.setTextScale(0.5) self.mon.setCursorPos(1,1) end, @@ -540,12 +538,12 @@ local Monitor = { end, handleResize = function(self) + self.monPeripheral.setTextScale(0.5) self.size = Vector2.new(self.monPeripheral.getSize()) self.mon = window.create(self.monPeripheral, 1, 1, self.size.x, self.size.y, false) self.dividerXCoord = calculateDividerXCoord(self.size.x) self.dividerYCoord = calculateDividerYCoord(self.size.y) self.touch = _G.Touchpoint.new(self.id, self.mon) - self:clear() -- print(self.id) self.drawOptions = getDrawOptions(self.size) diff --git a/src/scripts/controller.lua b/src/scripts/controller.lua index bd38562..b35d708 100644 --- a/src/scripts/controller.lua +++ b/src/scripts/controller.lua @@ -49,9 +49,10 @@ _G.fuelUsage = 0 _G.waste = 0 _G.capacity = 1 -_G.SECONDS_TO_AVERAGE = 2 +_G.SECONDS_TO_AVERAGE = 0.5 _G.averageStoredThisTick = 0 +_G.averageStoredLastTick = 0 _G.averageLastRFT = 0 _G.averageRod = 0 _G.averageFuelUsage = 0 @@ -73,7 +74,20 @@ end local function setRods(level) level = math.max(level, 0) level = math.min(level, 100) - _G.reactor.setAllControlRodLevels(level) + local count = reactor.getNumberOfControlRods() + + local numberToAddOneLevelTo = math.floor((level - math.floor(level)) * count + 0.5) + + local levelsMap = {} + for idx0, _ in pairs(reactor.getControlRodsLevels()) do + local rodLevel = math.floor(level) + if numberToAddOneLevelTo > 0 then + rodLevel = rodLevel + 1 + numberToAddOneLevelTo = numberToAddOneLevelTo - 1 + end + levelsMap[idx0] = rodLevel + end + _G.reactor.setControlRodsLevels(levelsMap) end local function lerp(start, finish, t) @@ -126,13 +140,13 @@ local function updateRods() if (not _G.btnOn) then return end - local currentRF = _G.storedLastTick + local currentRF = _G.averageStoredLastTick local diffb = _G.maxb - _G.minb local minRF = _G.minb / 100 * _G.capacity local diffRF = diffb / 100 * _G.capacity local diffr = diffb / 100 - local targetRFT = _G.rfLost - local currentRFT = _G.lastRFT + local targetRFT = _G.averageRfLost + local currentRFT = _G.averageLastRFT local targetRF = diffRF / 2 + minRF pid.setpointRFT = targetRFT @@ -150,16 +164,6 @@ local function updateRods() local combinedError = W_RFT * errorRFT + W_RF * errorRF local error = combinedError local rftRodLevel = iteratePID(pid, error) - - -- if rftRodLevel == 0 then - -- pretty.pretty_print({ - -- newRod = rftRodLevel, - -- pid = pid, - -- error = error, - -- currentRFT = currentRFT, - -- targetRFT = targetRFT, - -- }) - -- end -- Set control rod levels setRods(rftRodLevel) @@ -208,7 +212,7 @@ local function updateStats() _G.storedThisTick = bat.energyStored _G.lastRFT = bat.energyProducedLastTick _G.capacity = bat.energyCapacity - _G.rod = _G.reactor.getControlRodLevel(0) + _G.rod = calculateAverage(_G.reactor.getControlRodsLevels()) _G.fuelUsage = fuel.fuelConsumedLastTick / 1000 _G.waste = _G.reactor.getWasteAmount() _G.fuelTemp = _G.reactor.getFuelTemperature() @@ -223,38 +227,7 @@ local function updateStats() _G.fuelTemp = _G.reactor.fuelTemperature() _G.caseTemp = _G.reactor.casingTemperature() end - _G.rfLost = _G.lastRFT + _G.storedLastTick - _G.storedThisTick - -- Add the values to the arrays - table.insert(storedThisTickValues, _G.storedThisTick) - table.insert(lastRFTValues, _G.lastRFT) - table.insert(rodValues, _G.rod) - table.insert(fuelUsageValues, _G.fuelUsage) - table.insert(wasteValues, _G.waste) - table.insert(fuelTempValues, _G.fuelTemp) - table.insert(caseTempValues, _G.caseTemp) - table.insert(rfLostValues, _G.rfLost) - - local maxIterations = 20 * _G.SECONDS_TO_AVERAGE - while #storedThisTickValues > maxIterations do - table.remove(storedThisTickValues, 1) - table.remove(lastRFTValues, 1) - table.remove(rodValues, 1) - table.remove(fuelUsageValues, 1) - table.remove(wasteValues, 1) - table.remove(fuelTempValues, 1) - table.remove(caseTempValues, 1) - table.remove(rfLostValues, 1) - end - - -- Calculate running averages - _G.averageStoredThisTick = calculateAverage(storedThisTickValues) - _G.averageLastRFT = calculateAverage(lastRFTValues) - _G.averageRod = calculateAverage(rodValues) - _G.averageFuelUsage = calculateAverage(fuelUsageValues) - _G.averageWaste = calculateAverage(wasteValues) - _G.averageFuelTemp = calculateAverage(fuelTempValues) - _G.averageCaseTemp = calculateAverage(caseTempValues) - _G.averageRfLost = calculateAverage(rfLostValues) + _G.rfLost = math.floor(_G.lastRFT + _G.storedLastTick - _G.storedThisTick + 0.5) end --Initialize variables from either a config file or the defaults @@ -377,42 +350,81 @@ local function eventListener() end end +local function updateAverages() + table.insert(storedThisTickValues, _G.storedThisTick) + table.insert(lastRFTValues, _G.lastRFT) + table.insert(rodValues, _G.rod) + table.insert(fuelUsageValues, _G.fuelUsage) + table.insert(wasteValues, _G.waste) + table.insert(fuelTempValues, _G.fuelTemp) + table.insert(caseTempValues, _G.caseTemp) + table.insert(rfLostValues, _G.rfLost) + + local maxIterations = 20 * _G.SECONDS_TO_AVERAGE + while #storedThisTickValues > maxIterations do + table.remove(storedThisTickValues, 1) + table.remove(lastRFTValues, 1) + table.remove(rodValues, 1) + table.remove(fuelUsageValues, 1) + table.remove(wasteValues, 1) + table.remove(fuelTempValues, 1) + table.remove(caseTempValues, 1) + table.remove(rfLostValues, 1) + end + + -- Calculate running averages + _G.averageStoredThisTick = calculateAverage(storedThisTickValues) + _G.averageLastRFT = calculateAverage(lastRFTValues) + _G.averageRod = calculateAverage(rodValues) + _G.averageFuelUsage = calculateAverage(fuelUsageValues) + _G.averageWaste = calculateAverage(wasteValues) + _G.averageFuelTemp = calculateAverage(fuelTempValues) + _G.averageCaseTemp = calculateAverage(caseTempValues) + _G.averageRfLost = calculateAverage(rfLostValues) +end + -- Main loop, handles all the events local function loop() - local pretty = require "cc.pretty" - os.startTimer(0) - - local lastTime = os.time() * 1000 + ::begin:: + + local curTime = math.floor(os.clock() * 20) + local lastTime = curTime + local maxRetries = 10 + local tries = 0 + local cur = {} + local last = {} + sleep(0) while true do - os.pullEvent("timer") - local curTime = math.floor(os.time() * 1000 + 0.5) - if curTime <= lastTime then - goto continue + tries = 0 + curTime = math.floor(os.clock() * 20) + if curTime ~= lastTime + 1 then + print("lastTime "..lastTime..", curTime "..curTime) + goto begin end - term.setCursorPos(41,1) - term.write("Tick: "..string.format("%5s", os.time() * 1000)) - _G.storedLastTick = _G.storedThisTick - _G.lastLastRFT = math.floor(_G.lastRFT + 0.5) - - updateStats() - while _G.lastRFT == 0 and _G.lastLastRFT ~= 0 do - updateStats() - end - - - updateRods() - -- if os.time() * 1000 % 2 == 0 then - -- print(os.time() * 1000) + cur.rft = _G.reactor.getEnergyProducedLastTick() + cur.energy = _G.reactor.getEnergyStats().energyStored + if last.rft ~= nil and last.energy ~= nil then + while cur.rft == last.rft and cur.energy == last.energy or tries <= maxRetries do + updateStats() + cur.rft = _G.reactor.getEnergyProducedLastTick() + cur.energy = _G.reactor.getEnergyStats().energyStored + tries = tries + 1 + end + updateAverages() + updateRods() redrawMonitors() - -- end + _G.averageStoredLastTick = _G.averageStoredThisTick + end - -- redrawTick(event) + _G.storedLastTick = _G.storedThisTick + _G.lastRFTPrev = _G.lastRFT + last.rft = cur.rft + last.energy = cur.energy lastTime = curTime - ::continue:: - os.startTimer(0) + sleep(0) end end @@ -460,10 +472,6 @@ function _G.main() print("Reactor detected!") - -- print("Loading config...") - -- loadFromConfig() - -- print("Initializing monitor if connected...") - _G.maxb = 70 _G.minb = 30 _G.rod = 80 diff --git a/src/util/draw.lua b/src/util/draw.lua index c95199e..28028f4 100644 --- a/src/util/draw.lua +++ b/src/util/draw.lua @@ -5,9 +5,10 @@ ---@param offset Vector2 ---@param size Vector2 local function drawFilledRectangle(mon, color, offset, size) + if size.x <= 0 or size.y <= 0 then + return + end local old = term.redirect(mon) - size.x = math.max(size.x, 1) - size.y = math.max(size.y, 1) local endCoord = offset + size - 1 paintutils.drawFilledBox(offset.x, offset.y, endCoord.x, endCoord.y, color) term.redirect(old) @@ -20,12 +21,16 @@ end ---@param offset Vector2 ---@param size Vector2 local function drawFilledBoxWithBorder(mon, innerColor, outerColor, offset, size) + if size.x <= 0 or size.y <= 0 then + return + end local old = term.redirect(mon) - size.x = math.max(size.x, 1) - size.y = math.max(size.y, 1) local endCoord = offset + size - 1 paintutils.drawBox(offset.x, offset.y, endCoord.x, endCoord.y, outerColor) - paintutils.drawFilledBox(offset.x + 1, offset.y + 1, endCoord.x - 1, endCoord.y - 1, innerColor) + + if size.x > 2 and size.y > 2 then + paintutils.drawFilledBox(offset.x + 1, offset.y + 1, endCoord.x - 1, endCoord.y - 1, innerColor) + end term.redirect(old) end @@ -42,7 +47,7 @@ local function drawText(mon, text, pos, backgroundColor, textColor) end _G.DrawUtil = { - drawRectangle = drawFilledBoxWithBorder, + drawFilledBoxWithBorder = drawFilledBoxWithBorder, drawFilledRectangle = drawFilledRectangle, drawText = drawText, } From ae692270ea2a09e0e15babea2a5bc407221384c4 Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Sun, 5 Jan 2025 12:29:23 -0500 Subject: [PATCH 22/38] Fix for no internet and checking for update/trying to update --- install.lua | 12 +++++++++--- src/scripts/main.lua | 3 ++- src/scripts/update.lua | 36 ++++++++++++++++++++++++++++++------ 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/install.lua b/install.lua index a742394..8db7561 100644 --- a/install.lua +++ b/install.lua @@ -18,11 +18,17 @@ end --- Download the update script and reboot local function install() local updateScriptPath = "src/scripts/update.lua" - downloadGitHubFileByPath(updateScriptPath) + local succes, err = pcall(function() downloadGitHubFileByPath(updateScriptPath) end) + if not succes then + error("Failed to install the script! Do you have internet access?") + end shell.run(updateScriptPath) print("Downloading files!") - _G.UpdateScript.performUpdate() - print("Download complete. Rebooting...") + local success = _G.UpdateScript.performUpdate() + if not success then + error("Failed to install the script! Do you have internet access?") + end + print("Files downloaded successfully. Rebooting...") sleep(1) os.reboot() end diff --git a/src/scripts/main.lua b/src/scripts/main.lua index 8328b8a..4382480 100644 --- a/src/scripts/main.lua +++ b/src/scripts/main.lua @@ -62,7 +62,8 @@ local function start() ConfigUtil.writeAllConfigs() end - local updateAvailable = _G.UpdateScript.checkForUpdate() + -- local updateAvailable = _G.UpdateScript.checkForUpdate() + local updateAvailable = false if updateAvailable then print("Update available!") -- Set some state variable somewhere to tell us an update is available diff --git a/src/scripts/update.lua b/src/scripts/update.lua index 3171eba..d5e46dd 100644 --- a/src/scripts/update.lua +++ b/src/scripts/update.lua @@ -77,22 +77,46 @@ end --- Checks if there is an update to install ---@return boolean local function checkForUpdate() - local remoteRepoSHA = getRemoteRepoSHA() + local remoteRepoSHA + local success, err = pcall(function() remoteRepoSHA = getRemoteRepoSHA() end) + if not success then + print("Could not check for update with error", err) + return false + end local localRepoSHA = getLocalRepoSHA(LOCAL_REPO_DETAILS_FILENAME) return localRepoSHA ~= remoteRepoSHA end --- Updates and saves the project. src and startup files are deleted and then redownloaded. local function performUpdate() - local remoteRepoSHA = getRemoteRepoSHA() + local remoteRepoSHA + local success, err = pcall(function() remoteRepoSHA = getRemoteRepoSHA() end) + if not success then + print("Could not reach remote repository with error", err) + return false + end + local tempFolder = "temp" for _, filepath in pairs(FILES_TO_DELETE_ON_UPDATE) do - fs.delete(filepath) + fs.move(filepath, fs.combine(tempFolder, filepath)) end - downloadRemoteSrcDirectory(remoteRepoSHA) - downloadGitHubFileByPath("startup") - saveRepoSHA(remoteRepoSHA, LOCAL_REPO_DETAILS_FILENAME) + success, err = pcall( + function() + downloadRemoteSrcDirectory(remoteRepoSHA) + downloadGitHubFileByPath("startup") + end + ) + if success then + saveRepoSHA(remoteRepoSHA, LOCAL_REPO_DETAILS_FILENAME) + else + print("Repo download failed with error", err) + for _, filepath in pairs(FILES_TO_DELETE_ON_UPDATE) do + fs.move(fs.combine(tempFolder, filepath), filepath) + end + end + fs.delete("temp") + return success end _G.UpdateScript = { From a1af0cfcbb8485f5bae794a0184b1fb5a8983d21 Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Sun, 5 Jan 2025 12:31:31 -0500 Subject: [PATCH 23/38] Actually commit the changes this time --- src/scripts/update.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/src/scripts/update.lua b/src/scripts/update.lua index d5e46dd..1455185 100644 --- a/src/scripts/update.lua +++ b/src/scripts/update.lua @@ -42,6 +42,7 @@ local function downloadGitHubFileByPath(filepath) local file = fs.open(filepath, "w") file.write(contents) file.close() + print("File", filepath, "downloaded!") end local function getGitHubTreeDetails(treeSHA) From 532de2a2feea65c24d2f258e85676c8d7a57a7de Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Sun, 5 Jan 2025 12:48:59 -0500 Subject: [PATCH 24/38] Download update files to a temp folder now --- .gitignore | 3 ++- install.lua | 8 ++++---- src/scripts/update.lua | 45 ++++++++++++++++++++++++------------------ 3 files changed, 32 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index 52fd557..995a49e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ reactorConfigSerialized.txt commit.txt /overrides /defaults -/state \ No newline at end of file +/state +.DS_Store \ No newline at end of file diff --git a/install.lua b/install.lua index 8db7561..40e5bb5 100644 --- a/install.lua +++ b/install.lua @@ -18,9 +18,9 @@ end --- Download the update script and reboot local function install() local updateScriptPath = "src/scripts/update.lua" - local succes, err = pcall(function() downloadGitHubFileByPath(updateScriptPath) end) - if not succes then - error("Failed to install the script! Do you have internet access?") + local success, err = pcall(function() downloadGitHubFileByPath(updateScriptPath) end) + if not success then + error("Failed to install the script with error", err) end shell.run(updateScriptPath) print("Downloading files!") @@ -28,7 +28,7 @@ local function install() if not success then error("Failed to install the script! Do you have internet access?") end - print("Files downloaded successfully. Rebooting...") + print("Files downloaded successfully.") sleep(1) os.reboot() end diff --git a/src/scripts/update.lua b/src/scripts/update.lua index 1455185..8f7536f 100644 --- a/src/scripts/update.lua +++ b/src/scripts/update.lua @@ -35,11 +35,16 @@ local function saveRepoSHA(repoSHA, path) file.close() end -local function downloadGitHubFileByPath(filepath) +local function downloadGitHubFileByPath(filepath, tempFolder) local endpoint = "https://raw.githubusercontent.com/"..GITHUB_CONSTANTS.OWNER.."/"..GITHUB_CONSTANTS.REPO.."/refs/heads/"..GITHUB_CONSTANTS.BRANCH.."/"..filepath local response = http.get(endpoint) local contents = response.readAll() - local file = fs.open(filepath, "w") + local file + if tempFolder then + file = fs.open(filepath, "w") + else + file = fs.open(fs.combine(tempFolder, filepath), "w") + end file.write(contents) file.close() print("File", filepath, "downloaded!") @@ -52,25 +57,25 @@ local function getGitHubTreeDetails(treeSHA) return textutils.unserialiseJSON(contents) end -local function downloadGitHubTreeRecursively(path, treeSHA) +local function downloadGitHubTreeRecursively(path, treeSHA, tempFolder) local treeDetails = getGitHubTreeDetails(treeSHA) for _, treeEntry in pairs(treeDetails.tree) do local subfilePath = path.."/"..treeEntry.path if treeEntry.type == "tree" then - downloadGitHubTreeRecursively(subfilePath, treeEntry.sha) + downloadGitHubTreeRecursively(subfilePath, treeEntry.sha, tempFolder) elseif treeEntry.type == "blob" then - downloadGitHubFileByPath(subfilePath) + downloadGitHubFileByPath(subfilePath, tempFolder) end end end -local function downloadRemoteSrcDirectory(remoteRepoRootTreeSHA) +local function downloadRemoteSrcDirectory(remoteRepoRootTreeSHA, tempFolder) local remoteRepoRootTreeDetails = getGitHubTreeDetails(remoteRepoRootTreeSHA) local srcDirectoryName = "src" for _, treeEntry in pairs(remoteRepoRootTreeDetails.tree) do if treeEntry.path == srcDirectoryName and treeEntry.type == "tree" then - downloadGitHubTreeRecursively(srcDirectoryName, treeEntry.sha) + downloadGitHubTreeRecursively(srcDirectoryName, treeEntry.sha, tempFolder) end end end @@ -98,25 +103,27 @@ local function performUpdate() end local tempFolder = "temp" - for _, filepath in pairs(FILES_TO_DELETE_ON_UPDATE) do - fs.move(filepath, fs.combine(tempFolder, filepath)) - end success, err = pcall( function() - downloadRemoteSrcDirectory(remoteRepoSHA) - downloadGitHubFileByPath("startup") + downloadRemoteSrcDirectory(remoteRepoSHA, tempFolder) + downloadGitHubFileByPath("startup", tempFolder) end ) - if success then - saveRepoSHA(remoteRepoSHA, LOCAL_REPO_DETAILS_FILENAME) - else + if not success then print("Repo download failed with error", err) - for _, filepath in pairs(FILES_TO_DELETE_ON_UPDATE) do - fs.move(fs.combine(tempFolder, filepath), filepath) - end + fs.delete(tempFolder) + return false + end + for _, filepath in pairs(FILES_TO_DELETE_ON_UPDATE) do + fs.delete(filepath) + end + for _, filename in pairs(fs.list(tempFolder)) do + fs.move(fs.combine(tempFolder, filename), filename) end - fs.delete("temp") + + saveRepoSHA(remoteRepoSHA, LOCAL_REPO_DETAILS_FILENAME) + fs.delete(tempFolder) return success end From 78d92cb068e57fa11ebd9771b1e4ac6169c9298f Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Sun, 5 Jan 2025 12:56:12 -0500 Subject: [PATCH 25/38] Fix small logic error --- src/scripts/update.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/scripts/update.lua b/src/scripts/update.lua index 8f7536f..fdfde5c 100644 --- a/src/scripts/update.lua +++ b/src/scripts/update.lua @@ -41,9 +41,9 @@ local function downloadGitHubFileByPath(filepath, tempFolder) local contents = response.readAll() local file if tempFolder then - file = fs.open(filepath, "w") - else file = fs.open(fs.combine(tempFolder, filepath), "w") + else + file = fs.open(filepath, "w") end file.write(contents) file.close() @@ -121,7 +121,7 @@ local function performUpdate() for _, filename in pairs(fs.list(tempFolder)) do fs.move(fs.combine(tempFolder, filename), filename) end - + saveRepoSHA(remoteRepoSHA, LOCAL_REPO_DETAILS_FILENAME) fs.delete(tempFolder) return success From 017ab0364b5c2ad4a4b62e35f0d443ee40935593 Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Sun, 5 Jan 2025 12:59:51 -0500 Subject: [PATCH 26/38] test1 --- src/scripts/update.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/src/scripts/update.lua b/src/scripts/update.lua index fdfde5c..1c7d33e 100644 --- a/src/scripts/update.lua +++ b/src/scripts/update.lua @@ -41,6 +41,7 @@ local function downloadGitHubFileByPath(filepath, tempFolder) local contents = response.readAll() local file if tempFolder then + print(tempFolder) file = fs.open(fs.combine(tempFolder, filepath), "w") else file = fs.open(filepath, "w") From d87cab2ec6f83dcd6cbbd2421476387b079e01c8 Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Sun, 5 Jan 2025 13:04:01 -0500 Subject: [PATCH 27/38] grasping at straws here --- src/scripts/update.lua | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/scripts/update.lua b/src/scripts/update.lua index 1c7d33e..3331aaf 100644 --- a/src/scripts/update.lua +++ b/src/scripts/update.lua @@ -35,17 +35,15 @@ local function saveRepoSHA(repoSHA, path) file.close() end -local function downloadGitHubFileByPath(filepath, tempFolder) +local function downloadGitHubFileByPath(filepath, tempFoldername) + if tempFoldername == nil then + tempFoldername = "" + end + local endpoint = "https://raw.githubusercontent.com/"..GITHUB_CONSTANTS.OWNER.."/"..GITHUB_CONSTANTS.REPO.."/refs/heads/"..GITHUB_CONSTANTS.BRANCH.."/"..filepath local response = http.get(endpoint) local contents = response.readAll() - local file - if tempFolder then - print(tempFolder) - file = fs.open(fs.combine(tempFolder, filepath), "w") - else - file = fs.open(filepath, "w") - end + local file = fs.open(fs.combine(tempFoldername, filepath), "w") file.write(contents) file.close() print("File", filepath, "downloaded!") @@ -58,25 +56,25 @@ local function getGitHubTreeDetails(treeSHA) return textutils.unserialiseJSON(contents) end -local function downloadGitHubTreeRecursively(path, treeSHA, tempFolder) +local function downloadGitHubTreeRecursively(path, treeSHA, tempFoldername) local treeDetails = getGitHubTreeDetails(treeSHA) for _, treeEntry in pairs(treeDetails.tree) do local subfilePath = path.."/"..treeEntry.path if treeEntry.type == "tree" then - downloadGitHubTreeRecursively(subfilePath, treeEntry.sha, tempFolder) + downloadGitHubTreeRecursively(subfilePath, treeEntry.sha, tempFoldername) elseif treeEntry.type == "blob" then - downloadGitHubFileByPath(subfilePath, tempFolder) + downloadGitHubFileByPath(subfilePath, tempFoldername) end end end -local function downloadRemoteSrcDirectory(remoteRepoRootTreeSHA, tempFolder) +local function downloadRemoteSrcDirectory(remoteRepoRootTreeSHA, tempFoldername) local remoteRepoRootTreeDetails = getGitHubTreeDetails(remoteRepoRootTreeSHA) local srcDirectoryName = "src" for _, treeEntry in pairs(remoteRepoRootTreeDetails.tree) do if treeEntry.path == srcDirectoryName and treeEntry.type == "tree" then - downloadGitHubTreeRecursively(srcDirectoryName, treeEntry.sha, tempFolder) + downloadGitHubTreeRecursively(srcDirectoryName, treeEntry.sha, tempFoldername) end end end @@ -103,28 +101,28 @@ local function performUpdate() return false end - local tempFolder = "temp" + local tempFoldername = "temp" success, err = pcall( function() - downloadRemoteSrcDirectory(remoteRepoSHA, tempFolder) - downloadGitHubFileByPath("startup", tempFolder) + downloadRemoteSrcDirectory(remoteRepoSHA, tempFoldername) + downloadGitHubFileByPath("startup", tempFoldername) end ) if not success then print("Repo download failed with error", err) - fs.delete(tempFolder) + fs.delete(tempFoldername) return false end for _, filepath in pairs(FILES_TO_DELETE_ON_UPDATE) do fs.delete(filepath) end - for _, filename in pairs(fs.list(tempFolder)) do - fs.move(fs.combine(tempFolder, filename), filename) + for _, filename in pairs(fs.list(tempFoldername)) do + fs.move(fs.combine(tempFoldername, filename), filename) end saveRepoSHA(remoteRepoSHA, LOCAL_REPO_DETAILS_FILENAME) - fs.delete(tempFolder) + fs.delete(tempFoldername) return success end From 4f33f5fdc84767fb8e0227e69ea89d5dd1124703 Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Sun, 5 Jan 2025 13:06:57 -0500 Subject: [PATCH 28/38] grasping at straws here 2 --- src/scripts/update.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/src/scripts/update.lua b/src/scripts/update.lua index 3331aaf..0d6756e 100644 --- a/src/scripts/update.lua +++ b/src/scripts/update.lua @@ -105,6 +105,7 @@ local function performUpdate() success, err = pcall( function() + tempFoldername = "temp" downloadRemoteSrcDirectory(remoteRepoSHA, tempFoldername) downloadGitHubFileByPath("startup", tempFoldername) end From 2ac3b8a8977078b2a812517cf0298c4a8508e800 Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Sun, 5 Jan 2025 13:08:49 -0500 Subject: [PATCH 29/38] uh --- install.lua | 1 - src/scripts/update.lua | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/install.lua b/install.lua index 40e5bb5..0dcad1f 100644 --- a/install.lua +++ b/install.lua @@ -23,7 +23,6 @@ local function install() error("Failed to install the script with error", err) end shell.run(updateScriptPath) - print("Downloading files!") local success = _G.UpdateScript.performUpdate() if not success then error("Failed to install the script! Do you have internet access?") diff --git a/src/scripts/update.lua b/src/scripts/update.lua index 0d6756e..0e250d4 100644 --- a/src/scripts/update.lua +++ b/src/scripts/update.lua @@ -105,9 +105,8 @@ local function performUpdate() success, err = pcall( function() - tempFoldername = "temp" - downloadRemoteSrcDirectory(remoteRepoSHA, tempFoldername) - downloadGitHubFileByPath("startup", tempFoldername) + downloadRemoteSrcDirectory(remoteRepoSHA, "temp") + downloadGitHubFileByPath("startup", "temp") end ) if not success then From dad679b76235b43723c2ac22db27e571444a611a Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Sun, 5 Jan 2025 13:10:47 -0500 Subject: [PATCH 30/38] it's actually been working this whole time --- src/scripts/update.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scripts/update.lua b/src/scripts/update.lua index 0e250d4..3331aaf 100644 --- a/src/scripts/update.lua +++ b/src/scripts/update.lua @@ -105,8 +105,8 @@ local function performUpdate() success, err = pcall( function() - downloadRemoteSrcDirectory(remoteRepoSHA, "temp") - downloadGitHubFileByPath("startup", "temp") + downloadRemoteSrcDirectory(remoteRepoSHA, tempFoldername) + downloadGitHubFileByPath("startup", tempFoldername) end ) if not success then From 3060d67b0ef9ee6e51d2f846aa85a34a0bf40be4 Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Sun, 5 Jan 2025 13:13:29 -0500 Subject: [PATCH 31/38] update test --- install.lua | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/install.lua b/install.lua index 0dcad1f..90761e4 100644 --- a/install.lua +++ b/install.lua @@ -6,13 +6,18 @@ local GITHUB_CONSTANTS = { BRANCH = "development", } -local function downloadGitHubFileByPath(filepath) +local function downloadGitHubFileByPath(filepath, tempFoldername) + if tempFoldername == nil then + tempFoldername = "" + end + local endpoint = "https://raw.githubusercontent.com/"..GITHUB_CONSTANTS.OWNER.."/"..GITHUB_CONSTANTS.REPO.."/refs/heads/"..GITHUB_CONSTANTS.BRANCH.."/"..filepath local response = http.get(endpoint) local contents = response.readAll() - local file = fs.open(filepath, "w") + local file = fs.open(fs.combine(tempFoldername, filepath), "w") file.write(contents) file.close() + print("File", filepath, "downloaded!") end --- Download the update script and reboot From 1ebb78892c3ecc8d5f4b2a38bba9f436af983884 Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Sun, 5 Jan 2025 13:14:48 -0500 Subject: [PATCH 32/38] Trigger Update From d52645be56ba9dfa864c27c4e6a093670bbcd4f8 Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Sun, 5 Jan 2025 13:15:29 -0500 Subject: [PATCH 33/38] turn on update checking again maybe... --- src/scripts/main.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/scripts/main.lua b/src/scripts/main.lua index 4382480..8328b8a 100644 --- a/src/scripts/main.lua +++ b/src/scripts/main.lua @@ -62,8 +62,7 @@ local function start() ConfigUtil.writeAllConfigs() end - -- local updateAvailable = _G.UpdateScript.checkForUpdate() - local updateAvailable = false + local updateAvailable = _G.UpdateScript.checkForUpdate() if updateAvailable then print("Update available!") -- Set some state variable somewhere to tell us an update is available From d523a6ec5a677fcb26380888e82f8bb961a2b255 Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Sun, 5 Jan 2025 16:03:51 -0800 Subject: [PATCH 34/38] better update loop and some optimizations --- src/classes/graph.lua | 2 +- src/classes/monitor.lua | 30 ++++---- src/scripts/controller.lua | 152 ++++++++++++++----------------------- src/scripts/main.lua | 39 ++++++---- src/util/draw.lua | 15 +++- 5 files changed, 111 insertions(+), 127 deletions(-) diff --git a/src/classes/graph.lua b/src/classes/graph.lua index 92e83d5..21035c9 100644 --- a/src/classes/graph.lua +++ b/src/classes/graph.lua @@ -66,7 +66,7 @@ local function drawControlGraph(mon, offset, size, averageRod) offset + Vector2.new(0, 1), size ) - DrawUtil.drawFilledRectangle( + DrawUtil.drawFilledBox( mon, colors.white, offset + Vector2.new(3, 2), diff --git a/src/classes/monitor.lua b/src/classes/monitor.lua index e747b71..f41ef23 100644 --- a/src/classes/monitor.lua +++ b/src/classes/monitor.lua @@ -173,8 +173,7 @@ end local function drawGraphButtons(mon, offset, size) - - DrawUtil.drawFilledBoxWithBorder(mon, colors.black, colors.orange, offset, size) + DrawUtil.drawBox(mon, colors.orange, offset, size) local textPos = offset + Vector2.new(4, 0) DrawUtil.drawText(mon, " Graph Controls ", textPos, colors.black, colors.orange ) @@ -201,7 +200,7 @@ local function drawEnergyBuffer(mon, offset, graphSize, drawPercentLabelOnRight) local energyBufferTipOffset = offset + Vector2.new(1, 2 + energyBufferMaxHeight - energyBufferHeight) local energyBufferSize = Vector2.new(graphSize.x - 2, energyBufferHeight) - DrawUtil.drawFilledRectangle(mon, energyBufferColor, energyBufferTipOffset, energyBufferSize) + DrawUtil.drawFilledBox(mon, energyBufferColor, energyBufferTipOffset, energyBufferSize) local energyBufferTextOffset = energyBufferTipOffset local rfLabelBackgroundColor = energyBufferColor @@ -239,7 +238,7 @@ local function drawControlGraph(mon, offset, size, averageRod) DrawUtil.drawText(mon, "Control Level", offset + Vector2.new(1, 0), colors.black, colors.orange) DrawUtil.drawFilledBoxWithBorder(mon, colors.yellow, colors.gray, offset + Vector2.new(0, 1), size) - DrawUtil.drawFilledRectangle(mon, colors.white, offset + Vector2.new(3, 2), Vector2.new(9, controlRodPixelHeight)) + DrawUtil.drawFilledBox(mon, colors.white, offset + Vector2.new(3, 2), Vector2.new(9, controlRodPixelHeight)) local controlRodLevelTextPos, color if controlRodPixelHeight > 0 then @@ -269,7 +268,7 @@ local function drawTemperatures(mon, offset, size) local tempFormat = "%4s"..tempUnit DrawUtil.drawText(mon, "Temperatures", offset + Vector2.new(2, 0), BACKGROUND_COLOR, colors.orange) - DrawUtil.drawFilledRectangle(mon, colors.gray, offset + Vector2.new(8, 2), Vector2.new(1, temperatureMaxHeight)) + DrawUtil.drawFilledBox(mon, colors.gray, offset + Vector2.new(8, 2), Vector2.new(1, temperatureMaxHeight)) -- case temp DrawUtil.drawText(mon, "Case", offset + Vector2.new(3, 1), colors.gray, colors.lightBlue) @@ -279,7 +278,7 @@ local function drawTemperatures(mon, offset, size) local caseTempOffset = offset + Vector2.new(2, 2 + temperatureMaxHeight - caseTempHeight) local caseTempSize = Vector2.new(6, caseTempHeight) - DrawUtil.drawFilledRectangle(mon, CASE_TEMP_COLOR, caseTempOffset, caseTempSize) + DrawUtil.drawFilledBox(mon, CASE_TEMP_COLOR, caseTempOffset, caseTempSize) local caseTempTextOffset = caseTempOffset local caseTempTextBackgroundColor = CASE_TEMP_COLOR @@ -301,7 +300,7 @@ local function drawTemperatures(mon, offset, size) local fuelTempOffset = offset + Vector2.new(9, 2 + temperatureMaxHeight - fuelTempHeight) local fuelTempSize = Vector2.new(6, fuelTempHeight) - DrawUtil.drawFilledRectangle(mon, FUEL_TEMP_COLOR, fuelTempOffset, fuelTempSize) + DrawUtil.drawFilledBox(mon, FUEL_TEMP_COLOR, fuelTempOffset, fuelTempSize) local fuelTempTextOffset = fuelTempOffset local fuelTempTextBackgroundColor = FUEL_TEMP_COLOR @@ -328,7 +327,7 @@ local function drawGraph(mon, dividerXCoord, name, graphOffset, graphSize) end local function drawGraphs(mon, monitorSize, graphSlots, dividerXCoord, offset, size) - DrawUtil.drawFilledBoxWithBorder(mon, colors.black, colors.lightBlue, offset, size) + DrawUtil.drawBox(mon, colors.lightBlue, offset, size) local label = " Reactor Graphs " DrawUtil.drawText( mon, @@ -352,7 +351,7 @@ local function drawControls(mon, offset, size, drawBufferVisualization) size = Vector2.new(30, 9) end - DrawUtil.drawFilledBoxWithBorder(mon, colors.black, colors.cyan, offset, size) + DrawUtil.drawBox(mon, colors.cyan, offset, size) DrawUtil.drawText(mon, " Reactor Controls ", offset + Vector2.new(4, 0), colors.black, colors.cyan) local reactorOnOffLabel = "Reactor "..(_G.btnOn and "Online" or "Offline") @@ -371,8 +370,8 @@ local function drawControls(mon, offset, size, drawBufferVisualization) local bufferVisualSize = Vector2.new(20, 3) DrawUtil.drawText(mon, "Buffer Target Range", bufferVisualOffset + Vector2.new(0, -1), colors.black, colors.orange) - DrawUtil.drawFilledRectangle(mon, colors.red, bufferVisualOffset, bufferVisualSize) - DrawUtil.drawFilledRectangle(mon, colors.green, bufferVisualOffset + Vector2.new(bufferMinInPixels, 0), Vector2.new(bufferRangePixelWidth, 3)) + DrawUtil.drawFilledBox(mon, colors.red, bufferVisualOffset, bufferVisualSize) + DrawUtil.drawFilledBox(mon, colors.green, bufferVisualOffset + Vector2.new(bufferMinInPixels, 0), Vector2.new(bufferRangePixelWidth, 3)) DrawUtil.drawText( mon, @@ -393,7 +392,7 @@ local function drawControls(mon, offset, size, drawBufferVisualization) end local function drawStatistics(mon, offset, size) - DrawUtil.drawFilledBoxWithBorder(mon, colors.black, colors.blue, offset, size) + DrawUtil.drawBox(mon, colors.blue, offset, size) DrawUtil.drawText(mon, " Reactor Statistics ", offset + Vector2.new(4, 0), colors.black, colors.blue ) @@ -533,7 +532,6 @@ local Monitor = { -- button.func() self.touch.buttonList[buttonName].func() - -- self.touch:drawButton(buttonName) print(buttonName, "clicked on", self.id) end, @@ -541,11 +539,10 @@ local Monitor = { self.monPeripheral.setTextScale(0.5) self.size = Vector2.new(self.monPeripheral.getSize()) self.mon = window.create(self.monPeripheral, 1, 1, self.size.x, self.size.y, false) + self.touch = _G.Touchpoint.new(self.id, self.mon) self.dividerXCoord = calculateDividerXCoord(self.size.x) self.dividerYCoord = calculateDividerYCoord(self.size.y) - self.touch = _G.Touchpoint.new(self.id, self.mon) - -- print(self.id) self.drawOptions = getDrawOptions(self.size) if self.drawOptions.drawGraphMenu then local offset = Vector2.new(self.dividerXCoord + 5, self.dividerYCoord - 14) @@ -567,6 +564,9 @@ local Monitor = { if touchpointEvent[1] == "button_click" then local buttonName = touchpointEvent[3] self:handleClick(buttonName) + + -- Immediately draw the clicked monitor so that users don't feel any input delay when using the monitors + self:draw() end if event[1] == "monitor_resize" then self:handleResize() diff --git a/src/scripts/controller.lua b/src/scripts/controller.lua index b35d708..7093eab 100644 --- a/src/scripts/controller.lua +++ b/src/scripts/controller.lua @@ -1,33 +1,3 @@ -local version = "0.51" -local tag = "reactorConfig" ---[[ -Program made by DrunkenKas - See github: https://github.com/Kasra-G/ReactorController/#readme - -The MIT License (MIT) - -Copyright (c) 2021 Kasra Ghaffari - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -]] - - ---@type table local monitors = {} @@ -47,7 +17,7 @@ _G.fuelTemp = 0 _G.caseTemp = 0 _G.fuelUsage = 0 _G.waste = 0 -_G.capacity = 1 +_G.capacity = 1000 _G.SECONDS_TO_AVERAGE = 0.5 @@ -194,6 +164,9 @@ local fuelTempValues = {} local caseTempValues = {} local rfLostValues = {} +local bat +local fuel + local function updateStats() if (_G.reactorVersion == "Big Reactors") then _G.storedThisTick = _G.reactor.getEnergyStored() @@ -206,8 +179,8 @@ local function updateStats() -- Big Reactors doesn't give us a way to directly query RF capacity through CC APIs _G.capacity = math.max(_G.capacity, _G.reactor.getEnergyStored) elseif (_G.reactorVersion == "Extreme Reactors") then - local bat = _G.reactor.getEnergyStats() - local fuel = _G.reactor.getFuelStats() + bat = _G.reactor.getEnergyStats() + fuel = _G.reactor.getFuelStats() _G.storedThisTick = bat.energyStored _G.lastRFT = bat.energyProducedLastTick @@ -248,34 +221,6 @@ local function loadFromConfig() _G.btnOn = deserialized.btnOn graphsToDraw = deserialized.graphsToDraw XOffs = deserialized.XOffs - elseif (legacyConfigExists) then - local file = fs.open(tag..".txt", "r") - local calibrated = file.readLine() == "true" - - --read calibration information - if (calibrated) then - _ = tonumber(file.readLine()) - _ = tonumber(file.readLine()) - end - _G.maxb = tonumber(file.readLine()) - _G.minb = tonumber(file.readLine()) - _G.rod = tonumber(file.readLine()) - _G.btnOn = file.readLine() == "true" - - --read Graph data - for i in pairs(XOffs) do - local graph = file.readLine() - local v1 = tonumber(file.readLine()) - local v2 = true - if (graph ~= "nil") then - v2 = false - graphsToDraw[graph] = v1 - end - - XOffs[i] = {v1, v2} - - end - file.close() else print("Config file not found, generating default settings!") @@ -299,15 +244,7 @@ local function getAllPeripheralIdsForType(targetType) return peripheralIds end -local function disconnectMonitor(monitorID) - if monitors[monitorID] == nil then - return - end - - print("Monitor "..monitorID.." disconnected!") - monitors[monitorID] = nil -end - +---@param monitorID string local function connectMonitor(monitorID) print("Monitor "..monitorID.." connected!") monitors[monitorID] = Monitor.new(monitorID) @@ -329,23 +266,41 @@ local function redrawMonitors() end end +---@param peripheralID string +local function handlePeripheralDetach(peripheralID) + if monitors[peripheralID] ~= nil then + print("Monitor "..peripheralID.." disconnected!") + monitors[peripheralID] = nil + end +end + +---@param peripheralID string +---@param peripheralType string +local function handlePeripheralAttach(peripheralID, peripheralType) + if peripheralType == "monitor" then + connectMonitor(peripheralID) + elseif peripheralType == "BiggerReactors_Reactor" then + print("Attached Reactor") + else + print("Unknown peripheral", peripheralID, "of type", peripheralType, "attached to network") + end +end + local function eventListener() while true do local event = { os.pullEvent() } - - if event[1] == "monitor_touch" or event[1] == "monitor_resize" then + + if event[1] == "bob" then + + elseif event[1] == "monitor_touch" or event[1] == "monitor_resize" then local monitor = monitors[event[2]] if monitor ~= nil then monitor:handleEvents(event) end - end - - if event[1] == "peripheral" and peripheral.getType(event[2]) == "monitor" then - connectMonitor(event[2]) - end - - if event[1] == "peripheral_detach" then - disconnectMonitor(event[2]) + elseif event[1] == "peripheral" then + handlePeripheralAttach(event[2], peripheral.getType(event[2])) + elseif event[1] == "peripheral_detach" then + handlePeripheralDetach(event[2]) end end end @@ -383,10 +338,21 @@ local function updateAverages() _G.averageRfLost = calculateAverage(rfLostValues) end +local iterations = 0 +_G.TICKS_TO_REDRAW = 4 +local function runLoop() + updateRods() + if iterations % TICKS_TO_REDRAW == 0 then + redrawMonitors() + end + iterations = iterations + 1 +end + -- Main loop, handles all the events local function loop() ::begin:: + -- os.sleep(0) local curTime = math.floor(os.clock() * 20) local lastTime = curTime @@ -394,37 +360,37 @@ local function loop() local tries = 0 local cur = {} local last = {} - sleep(0) + + os.queueEvent("bob") + os.pullEvent("bob") while true do tries = 0 curTime = math.floor(os.clock() * 20) - if curTime ~= lastTime + 1 then + if curTime > lastTime + 1 then print("lastTime "..lastTime..", curTime "..curTime) goto begin + elseif curTime < lastTime + 1 then + os.sleep(0) + goto continue end updateStats() - cur.rft = _G.reactor.getEnergyProducedLastTick() - cur.energy = _G.reactor.getEnergyStats().energyStored - if last.rft ~= nil and last.energy ~= nil then - while cur.rft == last.rft and cur.energy == last.energy or tries <= maxRetries do + if _G.lastRFTPrev ~= nil and _G.storedLastTick ~= nil then + while _G.lastRFT == _G.lastRFTPrev and _G.storedThisTick == _G.storedLastTick or tries <= maxRetries do updateStats() - cur.rft = _G.reactor.getEnergyProducedLastTick() - cur.energy = _G.reactor.getEnergyStats().energyStored tries = tries + 1 end updateAverages() - updateRods() - redrawMonitors() + runLoop() _G.averageStoredLastTick = _G.averageStoredThisTick end _G.storedLastTick = _G.storedThisTick _G.lastRFTPrev = _G.lastRFT - last.rft = cur.rft - last.energy = cur.energy lastTime = curTime - sleep(0) + os.queueEvent("bob") + os.pullEvent("bob") + ::continue:: end end diff --git a/src/scripts/main.lua b/src/scripts/main.lua index 8328b8a..b56e779 100644 --- a/src/scripts/main.lua +++ b/src/scripts/main.lua @@ -20,12 +20,12 @@ local function executeAllLuaFilesInSrcFolderExceptMain() end local function promptAndReadInputAndLoopUntilValid(prompt, validAnswersList) - local validAnswer + local validAnswer = nil local validAnswersTable = {} for _, answer in pairs(validAnswersList) do validAnswersTable[answer] = true end - while true do + while not validAnswer do print(prompt) local input = read() if validAnswersTable[input] then @@ -44,6 +44,22 @@ local function runFirstTimeSetup() end end +local function runUpdateLogic() + local updateAvailable = _G.UpdateScript.checkForUpdate() + if not updateAvailable then + return false + end + + print("Update available!") + -- Set some state variable somewhere to tell us an update is available + if not UPDATE_CONFIG.AUTOUPDATE then + print("Automatic update skipped because it's not enabled!") + return false + end + print("Automatic update is enabled! Updating...") + return _G.UpdateScript.performUpdate() +end + local function start() executeAllLuaFilesInSrcFolderExceptMain() -- Let reactors run for 1 second on world load. @@ -62,21 +78,12 @@ local function start() ConfigUtil.writeAllConfigs() end - local updateAvailable = _G.UpdateScript.checkForUpdate() - if updateAvailable then - print("Update available!") - -- Set some state variable somewhere to tell us an update is available - if UPDATE_CONFIG.AUTOUPDATE then - print("Automatic update is enabled! Updating...") - _G.UpdateScript.performUpdate() - print("Finished update! Rebooting...") - sleep(1) - os.reboot() - else - print("Automatic update skipped because it's not enabled!") - sleep(1) - end + local didUpdate = runUpdateLogic() + if didUpdate then + print("Successfully updated! Rebooting...") + os.reboot() end + -- For now, main() is in controller.lua main() end diff --git a/src/util/draw.lua b/src/util/draw.lua index 28028f4..645a2b9 100644 --- a/src/util/draw.lua +++ b/src/util/draw.lua @@ -4,7 +4,7 @@ ---@param color any ---@param offset Vector2 ---@param size Vector2 -local function drawFilledRectangle(mon, color, offset, size) +local function drawFilledBox(mon, color, offset, size) if size.x <= 0 or size.y <= 0 then return end @@ -14,6 +14,16 @@ local function drawFilledRectangle(mon, color, offset, size) term.redirect(old) end +local function drawBox(mon, color, offset, size) + if size.x <= 0 or size.y <= 0 then + return + end + local old = term.redirect(mon) + local endCoord = offset + size - 1 + paintutils.drawBox(offset.x, offset.y, endCoord.x, endCoord.y, color) + term.redirect(old) +end + ---comment Draws a rectangle with a fill color and border ---@param mon table ---@param innerColor any @@ -48,6 +58,7 @@ end _G.DrawUtil = { drawFilledBoxWithBorder = drawFilledBoxWithBorder, - drawFilledRectangle = drawFilledRectangle, + drawFilledBox = drawFilledBox, + drawBox = drawBox, drawText = drawText, } From 859dde520c95054ceb116ed6cc20858dc1ae7cc6 Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Sun, 5 Jan 2025 19:31:23 -0800 Subject: [PATCH 35/38] messing around with the main loop --- src/classes/monitor.lua | 11 +++-- src/scripts/controller.lua | 91 ++++++++++++++++++-------------------- 2 files changed, 50 insertions(+), 52 deletions(-) diff --git a/src/classes/monitor.lua b/src/classes/monitor.lua index f41ef23..91ea812 100644 --- a/src/classes/monitor.lua +++ b/src/classes/monitor.lua @@ -564,13 +564,18 @@ local Monitor = { if touchpointEvent[1] == "button_click" then local buttonName = touchpointEvent[3] self:handleClick(buttonName) - - -- Immediately draw the clicked monitor so that users don't feel any input delay when using the monitors - self:draw() end if event[1] == "monitor_resize" then + if self.monPeripheral.getTextScale() ~= 0.5 then + self.mon.setVisible(false) + self.monPeripheral.setTextScale(0.5) + return + end self:handleResize() end + + -- Immediately draw the clicked monitor so that users don't feel any input delay when using the monitors + self:draw() end, ---@param self Monitor diff --git a/src/scripts/controller.lua b/src/scripts/controller.lua index 7093eab..c0d41f9 100644 --- a/src/scripts/controller.lua +++ b/src/scripts/controller.lua @@ -286,25 +286,6 @@ local function handlePeripheralAttach(peripheralID, peripheralType) end end -local function eventListener() - while true do - local event = { os.pullEvent() } - - if event[1] == "bob" then - - elseif event[1] == "monitor_touch" or event[1] == "monitor_resize" then - local monitor = monitors[event[2]] - if monitor ~= nil then - monitor:handleEvents(event) - end - elseif event[1] == "peripheral" then - handlePeripheralAttach(event[2], peripheral.getType(event[2])) - elseif event[1] == "peripheral_detach" then - handlePeripheralDetach(event[2]) - end - end -end - local function updateAverages() table.insert(storedThisTickValues, _G.storedThisTick) table.insert(lastRFTValues, _G.lastRFT) @@ -349,48 +330,60 @@ local function runLoop() end --- Main loop, handles all the events -local function loop() - ::begin:: - -- os.sleep(0) +local function eventListener() + while true do + local event = { os.pullEvent() } + if event[1] == "monitor_touch" or event[1] == "monitor_resize" then + local monitor = monitors[event[2]] + if monitor ~= nil then + monitor:handleEvents(event) + end + elseif event[1] == "peripheral" then + handlePeripheralAttach(event[2], peripheral.getType(event[2])) + elseif event[1] == "peripheral_detach" then + handlePeripheralDetach(event[2]) + end + end +end + +local function loop() + local loopEventName = "yield" local curTime = math.floor(os.clock() * 20) local lastTime = curTime - local maxRetries = 10 + local maxRetries = 5 local tries = 0 - local cur = {} - local last = {} - os.queueEvent("bob") - os.pullEvent("bob") + os.sleep(0) while true do - tries = 0 curTime = math.floor(os.clock() * 20) - if curTime > lastTime + 1 then - print("lastTime "..lastTime..", curTime "..curTime) - goto begin - elseif curTime < lastTime + 1 then - os.sleep(0) - goto continue - end - - updateStats() - if _G.lastRFTPrev ~= nil and _G.storedLastTick ~= nil then - while _G.lastRFT == _G.lastRFTPrev and _G.storedThisTick == _G.storedLastTick or tries <= maxRetries do - updateStats() - tries = tries + 1 + if curTime < lastTime + 1 then + os.queueEvent(loopEventName) + os.pullEvent(loopEventName) + elseif curTime > lastTime + 1 then + print("Missed last", curTime - lastTime - 1, "ticks!", curTime) + updateStats() + else + -- We have missed the data from the last tick, so we must set them to nil + -- Guaranteed to run at the start of a new tick + updateStats() + if _G.lastRFTPrev ~= nil and _G.storedLastTick ~= nil then + + -- Since we are executing on a separate thread, we need to poll until the reactor has updated it's values in the new tick. + tries = 0 + while _G.lastRFT == _G.lastRFTPrev and _G.storedThisTick == _G.storedLastTick or tries < maxRetries do + updateStats() + tries = tries + 1 + end + updateAverages() + runLoop() end - updateAverages() - runLoop() - _G.averageStoredLastTick = _G.averageStoredThisTick + os.sleep(0) end - _G.storedLastTick = _G.storedThisTick + _G.averageStoredLastTick = _G.averageStoredThisTick _G.lastRFTPrev = _G.lastRFT lastTime = curTime - os.queueEvent("bob") - os.pullEvent("bob") - ::continue:: end end From 9c19a9fa178e87963f6458f2c7037432a2f3c483 Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Thu, 9 Jan 2025 00:26:35 -0800 Subject: [PATCH 36/38] Add some comments --- src/scripts/controller.lua | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/scripts/controller.lua b/src/scripts/controller.lua index c0d41f9..969ff81 100644 --- a/src/scripts/controller.lua +++ b/src/scripts/controller.lua @@ -1,6 +1,10 @@ ---@type table local monitors = {} +-- local reactors = {} +-- local turbines = {} +-- local buffers = {} +--TODO: Remove all these global variables and instead use tables _G.reactorVersion = nil _G.reactor = nil _G.btnOn = nil @@ -75,7 +79,12 @@ local function calculateAverage(array) return sum / #array end --- Define PID controller parameters +-- TODO: Move to 2 or 3 stage PID controller to eliminate integral windup (oscillations) +-- TODO: Provide multiple PID presets for different sizes of reactors + -- User can choose the one that works the best for each reactor. +-- TODO: Dynamic setting of PID constants based on measured change in RFT per % change in control rods +-- TODO: Try using % of max RFT generation as basis of PID controller +-- TODO: Try using gain scheduling to reduce integral windup local pid = { setpointRFT = 0, -- Target RFT setpointRF = 0, -- Target RF @@ -104,10 +113,9 @@ local function iteratePID(pid, error) pid.lastError = error return rodLevel end -local pretty = require("cc.pretty") local function updateRods() - if (not _G.btnOn) then + if not _G.btnOn then return end local currentRF = _G.averageStoredLastTick @@ -139,7 +147,7 @@ local function updateRods() setRods(rftRodLevel) end --- Saves the configuration of the reactor controller +--TODO: Update this to handle settings for multiple reactors and turbines local function saveToConfig() local file = fs.open(tag.."Serialized.txt", "w") local configs = { @@ -203,7 +211,7 @@ local function updateStats() _G.rfLost = math.floor(_G.lastRFT + _G.storedLastTick - _G.storedThisTick + 0.5) end ---Initialize variables from either a config file or the defaults +--TODO: Update this to handle settings for multiple reactors and turbines local function loadFromConfig() _G.invalidDim = false local legacyConfigExists = fs.exists(tag..".txt") @@ -259,10 +267,8 @@ end local function redrawMonitors() for _, monitor in pairs(monitors) do + -- Eventually pass in our list of reactors and turbines to draw stats for. monitor:draw() - -- ---@type ReactorStatistics - -- local reactorStats = {} - -- monitor:update(reactorStats) end end @@ -280,7 +286,7 @@ local function handlePeripheralAttach(peripheralID, peripheralType) if peripheralType == "monitor" then connectMonitor(peripheralID) elseif peripheralType == "BiggerReactors_Reactor" then - print("Attached Reactor") + print("DEBUG: Attached Reactor") else print("Unknown peripheral", peripheralID, "of type", peripheralType, "attached to network") end @@ -364,12 +370,13 @@ local function loop() print("Missed last", curTime - lastTime - 1, "ticks!", curTime) updateStats() else - -- We have missed the data from the last tick, so we must set them to nil -- Guaranteed to run at the start of a new tick updateStats() if _G.lastRFTPrev ~= nil and _G.storedLastTick ~= nil then -- Since we are executing on a separate thread, we need to poll until the reactor has updated it's values in the new tick. + -- Usually this is only 3-4 iterations as the reactor does not take long to update stats. + -- If it takes more than 5 iterations we give up and try again in the next tick tries = 0 while _G.lastRFT == _G.lastRFTPrev and _G.storedThisTick == _G.storedLastTick or tries < maxRetries do updateStats() From b69fbb23b624c3cbed1624a74dba5325967fc6f9 Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Sun, 5 Jan 2025 20:19:45 -0800 Subject: [PATCH 37/38] Changed Vector2 to be just based off of vector class --- src/classes/classes.lua | 99 ++--------------------------------------- src/classes/monitor.lua | 69 +++++++++++----------------- src/util/draw.lua | 6 +-- 3 files changed, 33 insertions(+), 141 deletions(-) diff --git a/src/classes/classes.lua b/src/classes/classes.lua index 66c1886..c4565ce 100644 --- a/src/classes/classes.lua +++ b/src/classes/classes.lua @@ -1,31 +1,3 @@ ----@class Peripheral -local Peripheral = { - ---@type string - id = nil, - ---@type string - type = nil, - ---@type table - wrap = nil, -} ----comment ----@param id string ----@param type string ----@return Peripheral -local function newPeripheral(id, type) - - local peripheralInstance = { - id = id, - type = type, - wrap = peripheral.wrap(id), - } - setmetatable(peripheralInstance, {__index = Peripheral}) - return peripheralInstance -end - -_G.Peripheral = { - new = newPeripheral -} - ---@class ReactorStatistics local ReactorStatistics = { @@ -46,40 +18,6 @@ _G.ReactorStatistics = { new = newReactorStatistics } --- local function newVector2(x, y) - --- local Vector2Instance = { --- x = x, --- y = y, --- } --- setmetatable(Vector2Instance, {__index = Vector2, __add = Vector2.__add, __sub = Vector2.__sub}) --- return Vector2Instance --- end - --- _G.Vector2 = { --- new = newVector2, --- zero = newVector2(0, 0), --- one = newVector2(1, 1), - --- __add = function (a, b) --- local t = type(b) --- if t == "table" then --- return Vector2.new(a.x + b.x, a.y + b.y) --- elseif t == "number" then --- return Vector2.new(a.x + b, a.y + b) --- end --- end, - --- __sub = function (a, b) --- local t = type(b) --- if t == "table" then --- return Vector2.new(a.x - b.x, a.y - b.y) --- elseif t == "number" then --- return Vector2.new(a.x - b, a.y - b) --- end --- end, --- } - ---@class Vector2 local Vector2 = { ---@type number @@ -88,39 +26,8 @@ local Vector2 = { y = nil, } -Vector2.mt = {} - -Vector2.mt.__index = Vector2 - ----comment ----@param x number ----@param y number ----@return Vector2 -function Vector2.new(x, y) - local instance = {x = x, y = y} - return setmetatable(instance, Vector2.mt) -end - -Vector2.mt.__add = function(self, other) - local t = type(other) - if t == "table" then - return Vector2.new(self.x + other.x, self.y + other.y) - elseif t == "number" then - return Vector2.new(self.x + other, self.y + other) - end -end - -Vector2.mt.__sub = function(self, other) - local t = type(other) - if t == "table" then - return Vector2.new(self.x - other.x, self.y - other.y) - elseif t == "number" then - return Vector2.new(self.x - other, self.y - other) - end -end - _G.Vector2 = { - new = Vector2.new, - zero = Vector2.new(0, 0), - one = Vector2.new(1, 1), + new = vector.new, + zero = vector.new(0, 0), + one = vector.new(1, 1), } diff --git a/src/classes/monitor.lua b/src/classes/monitor.lua index 91ea812..bc4e80d 100644 --- a/src/classes/monitor.lua +++ b/src/classes/monitor.lua @@ -254,7 +254,7 @@ end local function drawTemperatures(mon, offset, size) - DrawUtil.drawFilledBoxWithBorder(mon, colors.black, colors.gray, offset + Vector2.new(1, 1), size) + DrawUtil.drawBox(mon, colors.gray, offset + Vector2.one, size) local CASE_TEMP_COLOR = colors.lightBlue local FUEL_TEMP_COLOR = colors.magenta @@ -391,50 +391,35 @@ local function drawControls(mon, offset, size, drawBufferVisualization) DrawUtil.drawText(mon, "Max", offset + Vector2.new(19, 13), colors.black, colors.purple) end -local function drawStatistics(mon, offset, size) - DrawUtil.drawBox(mon, colors.blue, offset, size) - DrawUtil.drawText(mon, " Reactor Statistics ", offset + Vector2.new(4, 0), colors.black, colors.blue - ) - - DrawUtil.drawText( - mon, - "Generating : "..format(_G.averageLastRFT).."RF/t", - offset + Vector2.new(2, 2), - colors.black, - colors.green - ) - DrawUtil.drawText( - mon, - "RF Drain "..(_G.averageStoredThisTick <= _G.averageLastRFT and "> " or ": ")..format(_G.averageRfLost).."RF/t", - offset + Vector2.new(2, 4), - colors.black, - colors.red - ) - - DrawUtil.drawText( - mon, - "Efficiency : "..format(getEfficiency()).."RF/B", - offset + Vector2.new(2, 6), - colors.black, - colors.green - ) +local statsList = { + { message = "", color = colors.green }, + { message = "", color = colors.green }, + { message = "", color = colors.green }, + { message = "", color = colors.green }, + { message = "", color = colors.green }, +} - DrawUtil.drawText( - mon, - "Fuel Usage : "..format(_G.averageFuelUsage).."B/t", - offset + Vector2.new(2, 8), - colors.black, - colors.green - ) +local function drawStatistics(mon, offset, size) + DrawUtil.drawBox(mon, colors.blue, offset, size) + DrawUtil.drawText(mon, " Reactor Statistics ", offset + Vector2.new(4, 0), colors.black, colors.blue) + statsList[1].message = "Generating : "..format(_G.averageLastRFT).."RF/t" + statsList[1].color = colors.green + statsList[2].message = "RF Drain "..(_G.averageStoredThisTick <= _G.averageLastRFT and "> " or ": ")..format(_G.averageRfLost).."RF/t" + statsList[2].color = colors.red + statsList[3].message = "Efficiency : "..format(getEfficiency()).."RF/B" + statsList[3].color = colors.green + statsList[4].message = "Fuel Usage : "..format(_G.averageFuelUsage).."B/t" + statsList[4].color = colors.green + statsList[5].message = "Waste : "..string.format("%7d mB", _G.waste) + statsList[5].color = colors.green + + local elemOffset = offset + Vector2.new(2, 2) + for _, details in pairs(statsList) do + DrawUtil.drawText(mon, details.message, elemOffset, colors.black, details.color) + elemOffset = elemOffset + Vector2.new(0, 2) + end - DrawUtil.drawText( - mon, - "Waste : "..string.format("%7d mB", _G.waste), - offset + Vector2.new(2, 10), - colors.black, - colors.green - ) end local function updateReactorControlButtonStates(touch) diff --git a/src/util/draw.lua b/src/util/draw.lua index 645a2b9..efe032f 100644 --- a/src/util/draw.lua +++ b/src/util/draw.lua @@ -9,7 +9,7 @@ local function drawFilledBox(mon, color, offset, size) return end local old = term.redirect(mon) - local endCoord = offset + size - 1 + local endCoord = offset + size - Vector2.one paintutils.drawFilledBox(offset.x, offset.y, endCoord.x, endCoord.y, color) term.redirect(old) end @@ -19,7 +19,7 @@ local function drawBox(mon, color, offset, size) return end local old = term.redirect(mon) - local endCoord = offset + size - 1 + local endCoord = offset + size - Vector2.one paintutils.drawBox(offset.x, offset.y, endCoord.x, endCoord.y, color) term.redirect(old) end @@ -35,7 +35,7 @@ local function drawFilledBoxWithBorder(mon, innerColor, outerColor, offset, size return end local old = term.redirect(mon) - local endCoord = offset + size - 1 + local endCoord = offset + size - Vector2.one paintutils.drawBox(offset.x, offset.y, endCoord.x, endCoord.y, outerColor) if size.x > 2 and size.y > 2 then From 59ca8d84f0c4cb557ea54c42af917dfc73dffe60 Mon Sep 17 00:00:00 2001 From: Kasra Ghaffari <18647702+Kasra-G@users.noreply.github.com> Date: Mon, 13 Jan 2025 21:26:17 -0800 Subject: [PATCH 38/38] initial support for multiple reactors and external power buffers --- src/classes/classes.lua | 33 --- src/classes/deque.lua | 76 ++++++ src/classes/energybuffer.lua | 89 +++++++ src/classes/graph.lua | 10 +- src/classes/monitor.lua | 76 +++--- src/classes/pid.lua | 28 +++ src/classes/reactor.lua | 263 +++++++++++++++++++ src/classes/vector2.lua | 20 ++ src/scripts/controller.lua | 472 +++++++++++++++-------------------- 9 files changed, 717 insertions(+), 350 deletions(-) delete mode 100644 src/classes/classes.lua create mode 100644 src/classes/deque.lua create mode 100644 src/classes/energybuffer.lua create mode 100644 src/classes/pid.lua create mode 100644 src/classes/reactor.lua create mode 100644 src/classes/vector2.lua diff --git a/src/classes/classes.lua b/src/classes/classes.lua deleted file mode 100644 index c4565ce..0000000 --- a/src/classes/classes.lua +++ /dev/null @@ -1,33 +0,0 @@ ----@class ReactorStatistics -local ReactorStatistics = { - -} ----comment ----@param id string ----@param type string ----@return ReactorStatistics -local function newReactorStatistics(id, type) - - local reactorStatisticsInstance = { - } - setmetatable(reactorStatisticsInstance, {__index = ReactorStatistics}) - return reactorStatisticsInstance -end - -_G.ReactorStatistics = { - new = newReactorStatistics -} - ----@class Vector2 -local Vector2 = { - ---@type number - x = nil, - ---@type number - y = nil, -} - -_G.Vector2 = { - new = vector.new, - zero = vector.new(0, 0), - one = vector.new(1, 1), -} diff --git a/src/classes/deque.lua b/src/classes/deque.lua new file mode 100644 index 0000000..cad5fac --- /dev/null +++ b/src/classes/deque.lua @@ -0,0 +1,76 @@ +---@class Deque +---@field first number +---@field last number +---@field sum number +---@field size number +---@field list table +local Deque = { + sum = 0, + size = 0, + first = 0, + last = -1, + + ---@param self Deque + ---@param value number + pushleft = function(self, value) + local first = self.first - 1 + self.first = first + self.list[first] = value + self.size = self.size + 1 + self.sum = self.sum + value + end, + + ---@param self Deque + ---@param value number + pushright = function (self, value) + local last = self.last + 1 + self.last = last + self.list[last] = value + self.size = self.size + 1 + self.sum = self.sum + value + end, + + ---@param self Deque + ---@return number + popleft = function (self) + local first = self.first + if first > self.last then error("list is empty") end + + local value = self.list[first] + self.list[first] = nil -- to allow garbage collection + self.first = first + 1 + self.size = self.size - 1 + self.sum = self.sum - value + return value + end, + + ---@param self Deque + ---@return number + popright = function (self) + local last = self.last + if self.first > last then error("list is empty") end + local value = self.list[last] + self.list[last] = nil -- to allow garbage collection + self.last = last - 1 + self.size = self.size - 1 + self.sum = self.sum - value + return value + end, + + ---@param self Deque + ---@return number + average = function (self) + return self.sum / self.size + end +} + +---@return Deque +local function new() + local dequeInstance = {list = {}} + setmetatable(dequeInstance, {__index = Deque}) + return dequeInstance +end + +_G.Deque = { + new = new +} diff --git a/src/classes/energybuffer.lua b/src/classes/energybuffer.lua new file mode 100644 index 0000000..e047e21 --- /dev/null +++ b/src/classes/energybuffer.lua @@ -0,0 +1,89 @@ +---@class EnergyBuffer +---@field id string +---@field energyStoredLastTick number +---@field energyStoredThisTick number +---@field energyStoredLastTickValues Deque +---@field energyStoredThisTickValues Deque +---@field averageEnergyStoredLastTick number +---@field averageEnergyStoredThisTick number +---@field lastUpdatedTick number +---@field capacity number +---@field getEnergyStored function +---@field getEnergyCapacity function +local EnergyBuffer = { + updateAverages = function (self) + self.energyStoredThisTickValues:pushleft(self.energyStoredThisTick) + self.energyStoredLastTickValues:pushleft(self.energyStoredLastTick) + + local ticksToAverage = 20 * _G.SECONDS_TO_AVERAGE + while self.energyStoredThisTickValues.size > ticksToAverage do + self.energyStoredLastTickValues:popright() + self.energyStoredThisTickValues:popright() + end + + self.averageEnergyStoredLastTick = self.energyStoredLastTickValues:average() + self.averageEnergyStoredThisTick = self.energyStoredThisTickValues:average() + end, + + ---@param self EnergyBuffer + ---@param currentTickNumber number + update = function(self, currentTickNumber) + if self.lastUpdatedTick >= currentTickNumber then + return + elseif self.lastUpdatedTick < currentTickNumber - 1 then + -- We missed the last tick - we don't know what it is! just set it to 0 for now... + self.energyStoredLastTick = 0 + end + local newEnergyStored = self:getEnergyStored() + self.capacity = self:getEnergyCapacity() + self.energyStoredLastTick = self.energyStoredThisTick + self.energyStoredThisTick = newEnergyStored + + self:updateAverages() + + self.lastUpdatedTick = currentTickNumber + end, + +} + +---@return EnergyBuffer +local function new(id, energyStoredFunction, energyCapacityFunction) + local energyBufferInstance = { + id = id, + energyStoredLastTick = 0, + energyStoredThisTick = 0, + energyStoredLastTickValues = Deque.new(), + energyStoredThisTickValues = Deque.new(), + lastUpdatedTick = 0, + capacity = 0, + getEnergyStored = energyStoredFunction, + getEnergyCapacity = energyCapacityFunction, + } + setmetatable(energyBufferInstance, {__index = EnergyBuffer}) + local currentTickNumber = math.floor(os.clock() * 20) + energyBufferInstance:update(currentTickNumber) + return energyBufferInstance +end + +local function newReactorEnergyBuffer(id) + local energyPeripheral = peripheral.wrap(id) + return new( + id, + function() return energyPeripheral.getEnergyStats().energyStored end, + function() return energyPeripheral.getEnergyStats().energyCapacity end + ) +end + +local function newForgeEnergyBuffer(id) + local energyPeripheral = peripheral.wrap(id) + return new( + id, + energyPeripheral.getEnergy, + energyPeripheral.getEnergyCapacity + ) +end + +_G.EnergyBuffer = { + newForgeEnergyBuffer = newForgeEnergyBuffer, + newReactorEnergyBuffer = newReactorEnergyBuffer, +} \ No newline at end of file diff --git a/src/classes/graph.lua b/src/classes/graph.lua index 21035c9..da71268 100644 --- a/src/classes/graph.lua +++ b/src/classes/graph.lua @@ -89,12 +89,4 @@ local function drawControlGraph(mon, offset, size, averageRod) color, colors.black ) -end -local sizey = 1 -local controlGraph = _G.Graph.new( - "Control Level", - "Control Level", - Vector2.new(27, 4), - Vector2.new(15, sizey - 7), - drawControlGraph -) \ No newline at end of file +end \ No newline at end of file diff --git a/src/classes/monitor.lua b/src/classes/monitor.lua index bc4e80d..63164f3 100644 --- a/src/classes/monitor.lua +++ b/src/classes/monitor.lua @@ -36,11 +36,7 @@ local function format(num) end local function getPercPower() - return _G.averageStoredThisTick / _G.capacity * 100 -end - -local function getEfficiency() - return _G.averageLastRFT / _G.averageFuelUsage + return _G.overallStats.storedThisTick / _G.overallStats.capacity * 100 end --Helper method for adding buttons @@ -73,18 +69,16 @@ local function maxSub10() end local function turnOff() - if (_G.btnOn) then - _G.btnOff = true + if _G.btnOn then _G.btnOn = false - _G.reactor.setActive(false) + setReactors(false) end end local function turnOn() - if (_G.btnOff) then - _G.btnOff = false + if not _G.btnOn then _G.btnOn = true - _G.reactor.setActive(true) + setReactors(true) end end @@ -172,21 +166,22 @@ local function addGraphButtons(monitor, graphSlots, offset, size) end -local function drawGraphButtons(mon, offset, size) +local function drawGraphMenu(mon, offset, size) DrawUtil.drawBox(mon, colors.orange, offset, size) local textPos = offset + Vector2.new(4, 0) - DrawUtil.drawText(mon, " Graph Controls ", textPos, colors.black, colors.orange - ) + DrawUtil.drawText(mon, " Graph Controls ", textPos, colors.black, colors.orange) end local function drawEnergyBuffer(mon, offset, graphSize, drawPercentLabelOnRight) DrawUtil.drawText(mon, "Energy Buffer", offset, colors.black, colors.orange) DrawUtil.drawFilledBoxWithBorder(mon, colors.red, colors.gray, offset + Vector2.new(0, 1), graphSize) + local percentFull = getPercPower() + local exactBufferAmount = _G.overallStats.storedThisTick local energyBufferMaxHeight = graphSize.y - 2 - local unitEnergyLevel = getPercPower() / 100 + local unitEnergyLevel = percentFull / 100 local energyBufferHeight = math.floor(unitEnergyLevel * energyBufferMaxHeight + 0.5) - local rndpw = round(getPercPower(), 2) + local rndpw = round(percentFull, 2) local energyBufferColor if rndpw < _G.maxb and rndpw > _G.minb then @@ -224,7 +219,7 @@ local function drawEnergyBuffer(mon, offset, graphSize, drawPercentLabelOnRight) ) DrawUtil.drawText( mon, - format(_G.averageStoredThisTick).."RF", + format(exactBufferAmount).."RF", energyBufferTextOffset, rfLabelBackgroundColor, colors.black @@ -264,7 +259,9 @@ local function drawTemperatures(mon, offset, size) local assumedMaxFuelTemperature = 3000 local temperatureMaxHeight = size.y - 2 - local tempUnit = (_G.reactorVersion == "Bigger Reactors") and "K" or "C" + -- local tempUnit = (_G.reactorVersion == "Bigger Reactors") and "K" or "C" + local caseTemp = _G.selectedReactor.averageCaseTemp + local tempUnit = "C" local tempFormat = "%4s"..tempUnit DrawUtil.drawText(mon, "Temperatures", offset + Vector2.new(2, 0), BACKGROUND_COLOR, colors.orange) @@ -272,7 +269,7 @@ local function drawTemperatures(mon, offset, size) -- case temp DrawUtil.drawText(mon, "Case", offset + Vector2.new(3, 1), colors.gray, colors.lightBlue) - local caseUnit = math.min(_G.averageCaseTemp / assumedMaxCaseTemperature, 1) + local caseUnit = math.min(caseTemp / assumedMaxCaseTemperature, 1) local caseTempHeight = math.floor(caseUnit * temperatureMaxHeight + 0.5) local caseTempOffset = offset + Vector2.new(2, 2 + temperatureMaxHeight - caseTempHeight) @@ -289,12 +286,14 @@ local function drawTemperatures(mon, offset, size) caseTempTextColor, caseTempTextBackgroundColor = caseTempTextBackgroundColor, caseTempTextColor end - local caseRnd = math.floor(_G.averageCaseTemp + 0.5) + local caseRnd = math.floor(caseTemp + 0.5) DrawUtil.drawText(mon, string.format(tempFormat, caseRnd..""), caseTempTextOffset, caseTempTextBackgroundColor, caseTempTextColor) + local fuelTemp = _G.selectedReactor.averageFuelTemp + -- fuel temp DrawUtil.drawText(mon, "Fuel", offset + Vector2.new(10, 1), colors.gray, colors.lightBlue) - local fuelUnit = math.min(_G.averageFuelTemp / assumedMaxFuelTemperature, 1) + local fuelUnit = math.min(fuelTemp / assumedMaxFuelTemperature, 1) local fuelTempHeight = math.floor(fuelUnit * temperatureMaxHeight + 0.5) local fuelTempOffset = offset + Vector2.new(9, 2 + temperatureMaxHeight - fuelTempHeight) @@ -311,7 +310,7 @@ local function drawTemperatures(mon, offset, size) fuelTempTextColor, fuelTempTextBackgroundColor = fuelTempTextBackgroundColor, fuelTempTextColor end - local fuelRnd = math.floor(_G.averageFuelTemp + 0.5) + local fuelRnd = math.floor(fuelTemp + 0.5) DrawUtil.drawText(mon, string.format(tempFormat, fuelRnd..""), fuelTempTextOffset, fuelTempTextBackgroundColor, fuelTempTextColor) end @@ -320,7 +319,7 @@ local function drawGraph(mon, dividerXCoord, name, graphOffset, graphSize) local drawPercentLabelOnRight = graphOffset.x + 19 < dividerXCoord - 1 drawEnergyBuffer(mon, graphOffset, graphSize, drawPercentLabelOnRight) elseif (name == "Control Level") then - drawControlGraph(mon, graphOffset, graphSize, _G.averageRod) + drawControlGraph(mon, graphOffset, graphSize, _G.selectedReactor.averageRodLevel) elseif (name == "Temperatures") then drawTemperatures(mon, graphOffset, graphSize) end @@ -403,33 +402,26 @@ local statsList = { local function drawStatistics(mon, offset, size) DrawUtil.drawBox(mon, colors.blue, offset, size) DrawUtil.drawText(mon, " Reactor Statistics ", offset + Vector2.new(4, 0), colors.black, colors.blue) - statsList[1].message = "Generating : "..format(_G.averageLastRFT).."RF/t" + statsList[1].message = "Generating : "..format(_G.overallStats.lastRFT).."RF/t" statsList[1].color = colors.green - statsList[2].message = "RF Drain "..(_G.averageStoredThisTick <= _G.averageLastRFT and "> " or ": ")..format(_G.averageRfLost).."RF/t" + statsList[2].message = "RF Drain "..(_G.overallStats.storedThisTick <= _G.overallStats.lastRFT and "> " or ": ")..format(_G.overallStats.rfLost).."RF/t" statsList[2].color = colors.red - statsList[3].message = "Efficiency : "..format(getEfficiency()).."RF/B" + statsList[3].message = "Efficiency : "..format(_G.overallStats.efficiency()).."RF/B" statsList[3].color = colors.green - statsList[4].message = "Fuel Usage : "..format(_G.averageFuelUsage).."B/t" + statsList[4].message = "Fuel Usage : "..format(_G.overallStats.fuelUsage).."B/t" statsList[4].color = colors.green - statsList[5].message = "Waste : "..string.format("%7d mB", _G.waste) + statsList[5].message = "Waste : "..string.format("%7d mB", _G.overallStats.waste) statsList[5].color = colors.green local elemOffset = offset + Vector2.new(2, 2) - for _, details in pairs(statsList) do - DrawUtil.drawText(mon, details.message, elemOffset, colors.black, details.color) - elemOffset = elemOffset + Vector2.new(0, 2) + for i, details in pairs(statsList) do + DrawUtil.drawText(mon, details.message, elemOffset + Vector2.new(0, 2) * (i - 1), colors.black, details.color) end - end local function updateReactorControlButtonStates(touch) - if _G.btnOn then - touch:setButton("On", true) - touch:setButton("Off", false) - else - touch:setButton("On", false) - touch:setButton("Off", true) - end + touch:setButton("On", _G.btnOn) + touch:setButton("Off", not _G.btnOn) end local function updateGraphMenuButtonStates(touch, graphSlots) @@ -479,7 +471,7 @@ local function getDrawOptions(monitorSize) } return drawOptions end - +local pretty = require "cc.pretty" ---@class Monitor local Monitor = { navbar = nil, @@ -567,7 +559,7 @@ local Monitor = { draw = function(self) self.mon.setVisible(false) self:clear() - + if self.drawOptions.drawInvalidMonitorDimensions then self.mon.write("Invalid Monitor Dimensions") end @@ -575,7 +567,7 @@ local Monitor = { if self.drawOptions.drawGraphMenu then local offset = Vector2.new(self.dividerXCoord + 1, self.dividerYCoord - 14 + 1) local size = Vector2.new(30, 13) - drawGraphButtons(self.mon, offset, size) + drawGraphMenu(self.mon, offset, size) updateGraphMenuButtonStates(self.touch, self.graphSlots) end diff --git a/src/classes/pid.lua b/src/classes/pid.lua new file mode 100644 index 0000000..c567f68 --- /dev/null +++ b/src/classes/pid.lua @@ -0,0 +1,28 @@ +---@class PIDController +local PIDController = { + Kp = -.08, + Ki = -.0015, + Kd = -.01, + setpoint = 0, + integral = 0, + lastError = 0, + + iterate = function(self, error) + + end +} +---comment +---@return PIDController +local function new(Kp, Ki, Kd) + local pidControllerInstance = { + Kp = Kp, + Ki = Ki, + Kd = Kd, + } + setmetatable(pidControllerInstance, {__index = PIDController}) + return pidControllerInstance +end + +_G.PIDController = { + new = new +} \ No newline at end of file diff --git a/src/classes/reactor.lua b/src/classes/reactor.lua new file mode 100644 index 0000000..d6af5eb --- /dev/null +++ b/src/classes/reactor.lua @@ -0,0 +1,263 @@ +-- Function to calculate the average of an array of values +local function calculateAverage(array) + local sum = 0 + local count = 0 + for _, value in pairs(array) do + sum = sum + value + count = count + 1 + end + return sum / count +end + + +local function setRods(reactor, level) + level = math.max(level, 0) + level = math.min(level, 100) + local count = reactor.getNumberOfControlRods() + + local numberToAddOneLevelTo = math.floor((level - math.floor(level)) * count + 0.5) + + local levelsMap = {} + for idx0, _ in pairs(reactor.getControlRodsLevels()) do + local rodLevel = math.floor(level) + if numberToAddOneLevelTo > 0 then + rodLevel = rodLevel + 1 + numberToAddOneLevelTo = numberToAddOneLevelTo - 1 + end + levelsMap[idx0] = rodLevel + end + reactor.setControlRodsLevels(levelsMap) +end + + +local function lerp(start, finish, t) + t = math.max(0, math.min(1, t)) + + return (1 - t) * start + t * finish +end + +local function iteratePID(pid, error) + -- Proportional term + local P = pid.Kp * error + + -- Integral term + pid.integral = pid.integral + pid.Ki * error + pid.integral = math.max(math.min(100, pid.integral), -100) + + -- Derivative term + local derivative = pid.Kd * (error - pid.lastError) + + -- Calculate control rod level + local rodLevel = math.max(math.min(P + pid.integral + derivative, 100), 0) + + -- Update PID controller state + pid.lastError = error + return rodLevel +end + +---@class Reactor +---@field id string +---@field active boolean +---@field activelyCooled boolean +---@field lastUpdatedTick number +---@field lastRFT number +---@field rodLevel number +---@field fuelUsage number +---@field waste number +---@field fuelTemp number +---@field caseTemp number +---@field fuelEfficiency number +---@field steamProductionRate number +---@field storedSteam number +---@field steamCapacity number +---@field lastRFTValues Deque +---@field rodLevelValues Deque +---@field fuelUsageValues Deque +---@field wasteValues Deque +---@field fuelTempValues Deque +---@field caseTempValues Deque +---@field steamProductionRateValues Deque +---@field storedSteamValues Deque +---@field averageLastRFT number +---@field averageRodLevel number +---@field averageFuelUsage number +---@field averageWaste number +---@field averageFuelTemp number +---@field averageSteamProductionRate number +---@field averageStoredSteam number +---@field averageFuelEfficiency number +---@field getLastRFT function +---@field getRodLevel function +---@field getFuelUsage function +---@field getWaste function +---@field getFuelTemp function +---@field getCaseTemp function +---@field getSteamProductionRate function +---@field getStoredSteam function +---@field getSteamCapacity function +---@field isActivelyCooled function +---@field getActive function +---@field setActive function +---@field setRodLevels function +local Reactor = { + + lastUpdatedTick = 0, + + updateAverages = function (self) + self.fuelUsageValues:pushleft(self.fuelUsage) + self.lastRFTValues:pushleft(self.lastRFT) + self.fuelTempValues:pushleft(self.fuelTemp) + self.caseTempValues:pushleft(self.caseTemp) + self.rodLevelValues:pushleft(self.rodLevel) + self.wasteValues:pushleft(self.waste) + self.steamProductionRateValues:pushleft(self.steamProductionRate) + self.storedSteamValues:pushleft(self.storedSteam) + + local ticksToAverage = 20 * _G.SECONDS_TO_AVERAGE + while self.lastRFTValues.size > ticksToAverage do + self.fuelUsageValues:popright() + self.lastRFTValues:popright() + self.fuelTempValues:popright() + self.caseTempValues:popright() + self.rodLevelValues:popright() + self.wasteValues:popright() + self.steamProductionRateValues:popright() + self.storedSteamValues:popright() + end + + self.averageFuelUsage = self.fuelUsageValues:average() + self.averageLastRFT = self.lastRFTValues:average() + self.averageFuelTemp = self.fuelTempValues:average() + self.averageCaseTemp = self.caseTempValues:average() + self.averageRodLevel = self.rodLevelValues:average() + self.averageWaste = self.wasteValues:average() + self.averageSteamProductionRate = self.steamProductionRateValues:average() + self.averageStoredSteam = self.storedSteamValues:average() + + self.averageFuelEfficiency = self.averageLastRFT / self.averageFuelUsage + end, + + ---@param self Reactor + ---@param currentTickNumber number + update = function(self, currentTickNumber) + if self.lastUpdatedTick >= currentTickNumber then + return + elseif self.lastUpdatedTick < currentTickNumber - 1 then + -- We missed the last tick - Don't do anything different for now... + print("missed last tick!") + end + + self.activelyCooled = self.isActivelyCooled() + self.active = self.getActive() + self.lastRFT = self.getLastRFT() + self.rodLevel = self.getRodLevel() + self.fuelUsage = self.getFuelUsage() + self.waste = self.getWaste() + self.fuelTemp = self.getFuelTemp() + self.caseTemp = self.getCaseTemp() + self.steamProductionRate = self.getSteamProductionRate() + self.storedSteam = self.getStoredSteam() + self.steamCapacity = self.getSteamCapacity() + self.fuelEfficiency = self.lastRFT / self.fuelUsage + + self:updateAverages() + self.lastUpdatedTick = currentTickNumber + end, + + ---@param self Reactor + -- -@param targetRFT number + -- -@param targetRF number + updateRods = function (self) + if not self.active then + return + end + + local currentGenerationRate = self.averageLastRFT + local currentStoredAmount = _G.overallStats.storedThisTick + local capacity = _G.overallStats.capacity + local targetGenerationRate = _G.overallStats.rfLost + + if self.activelyCooled then + currentGenerationRate = self.averageSteamProductionRate + currentStoredAmount = _G.overallStats.storedSteam + capacity = _G.overallStats.steamCapacity + targetGenerationRate = _G.overallStats.steamConsumedLastTick + end + + local diffb = _G.maxb - _G.minb + local minRF = _G.minb / 100 * capacity + local diffRF = diffb / 100 * capacity + local diffr = diffb / 100 + -- local targetStoredAmount = diffRF / 2 + minRF + local targetStoredAmount = currentStoredAmount + + self.pid.setpointRFT = targetGenerationRate + self.pid.setpointRF = targetStoredAmount / capacity * 1000 + + local errorRFT = self.pid.setpointRFT - currentGenerationRate + local errorRF = self.pid.setpointRF - currentStoredAmount / capacity * 1000 + + local W_RFT = lerp(1, 0, (math.abs(targetStoredAmount - currentStoredAmount) / capacity / (diffr / 4))) + W_RFT = math.max(math.min(W_RFT, 1), 0) + + local W_RF = (1 - W_RFT) -- Adjust the weight for energy error + + -- Combine the errors with weights + local combinedError = W_RFT * errorRFT + W_RF * errorRF + local error = combinedError + local rftRodLevel = iteratePID(self.pid, error) + + -- Set control rod levels + self.setRodLevels(rftRodLevel) + end, +} + +---@param id string +---@return Reactor +local function newExtremeReactor(id) + local extremeReactor = peripheral.wrap(id) + local pid = { + setpointRFT = 0, -- Target RFT + setpointRF = 0, -- Target RF + Kp = -.008, -- Proportional gain + Ki = -.00015, -- Integral gain + Kd = -.01, -- Derivative gain + integral = 0, -- Integral term accumulator + lastError = 0, -- Last error for derivative term + } + local reactorInstance = { + id = id, + pid = pid, + fuelUsageValues = Deque.new(), + lastRFTValues = Deque.new(), + fuelTempValues = Deque.new(), + caseTempValues = Deque.new(), + rodLevelValues = Deque.new(), + wasteValues = Deque.new(), + steamProductionRateValues = Deque.new(), + storedSteamValues = Deque.new(), + + getFuelUsage = function () return extremeReactor.getFuelStats().fuelConsumedLastTick / 1000 end, + getLastRFT = function () return extremeReactor.getEnergyStats().energyProducedLastTick end, + getFuelTemp = extremeReactor.getFuelTemperature, + getCaseTemp = extremeReactor.getCasingTemperature, + getRodLevel = function () return calculateAverage(extremeReactor.getControlRodsLevels()) end, + getWaste = extremeReactor.getWasteAmount, + getSteamProductionRate = extremeReactor.getHotFluidProducedLastTick, + getSteamCapacity = extremeReactor.getHotFluidAmountMax, + getStoredSteam = extremeReactor.getHotFluidAmount, + getActive = extremeReactor.getActive, + isActivelyCooled = extremeReactor.isActivelyCooled, + setActive = extremeReactor.setActive, + setRodLevels = function (level) setRods(extremeReactor, level) end, + } + setmetatable(reactorInstance, {__index = Reactor}) + local currentTickNumber = math.floor(os.clock() * 20) + reactorInstance:update(currentTickNumber) + return reactorInstance +end + +_G.Reactor = { + newExtremeReactor = newExtremeReactor, + -- newBiggerReactor = newBiggerReactor, +} \ No newline at end of file diff --git a/src/classes/vector2.lua b/src/classes/vector2.lua new file mode 100644 index 0000000..2f155d2 --- /dev/null +++ b/src/classes/vector2.lua @@ -0,0 +1,20 @@ +---@class Vector2 +local Vector2 = { + ---@type number + x = nil, + ---@type number + y = nil, +} + +---@param x number +---@param y number +---@return Vector2 +local function new(x, y) + return vector.new(x, y) +end + +_G.Vector2 = { + new = new, + zero = new(0, 0), + one = new(1, 1), +} diff --git a/src/scripts/controller.lua b/src/scripts/controller.lua index 969ff81..4507051 100644 --- a/src/scripts/controller.lua +++ b/src/scripts/controller.lua @@ -1,82 +1,94 @@ ---@type table -local monitors = {} --- local reactors = {} --- local turbines = {} --- local buffers = {} - ---TODO: Remove all these global variables and instead use tables -_G.reactorVersion = nil -_G.reactor = nil -_G.btnOn = nil -_G.btnOff = nil -_G.minb = nil -_G.maxb = nil -_G.rod = nil -_G.rfLost = nil -_G.storedLastTick = 0 -_G.storedThisTick = 0 -_G.lastRFT = 0 - -_G.fuelTemp = 0 -_G.caseTemp = 0 -_G.fuelUsage = 0 -_G.waste = 0 -_G.capacity = 1000 - -_G.SECONDS_TO_AVERAGE = 0.5 - -_G.averageStoredThisTick = 0 -_G.averageStoredLastTick = 0 -_G.averageLastRFT = 0 -_G.averageRod = 0 -_G.averageFuelUsage = 0 -_G.averageWaste = 0 -_G.averageFuelTemp = 0 -_G.averageCaseTemp = 0 -_G.averageRfLost = 0 - ---returns the side that a given peripheral type is connected to -local function getPeripheral(targetType) - for _, name in pairs(peripheral.getNames()) do - if (peripheral.getType(name) == targetType) then - return name - end +_G.monitors = {} +---@type table +_G.reactors = {} +-- _G.turbines = {} + +_G.fluidBuffers = {} + +---@type table +_G.energyBuffers = {} + +_G.masterAutoRodControl = true + +-- These are averaged when possible +---@class OverallStats +_G.overallStats = { + storedLastTick = 0, + storedThisTick = 0, + lastRFT = 0, + rfLost = 0, + steamConsumedLastTick = 2000, + fuelUsage = 0, + waste = 0, + capacity = 1000, + efficiency = function () + return _G.overallStats.lastRFT / _G.overallStats.fuelUsage end - return "" -end - -local function setRods(level) - level = math.max(level, 0) - level = math.min(level, 100) - local count = reactor.getNumberOfControlRods() +} - local numberToAddOneLevelTo = math.floor((level - math.floor(level)) * count + 0.5) +_G.selectedReactor = nil + +-- -@class AveragedTurbineStatistics +-- -@field fuelUsage number +-- -@field fuelUsageValues Deque +-- -@field lastRFT number +-- -@field lastRFTValues Deque +-- -@field waste number +-- -@field wasteValues Deque +-- -@field fuelTemp number +-- -@field fuelTempValues Deque +-- -@field caseTemp number +-- -@field caseTempValues Deque + +local function updateOverallStats() + _G.overallStats.storedLastTick = 0 + _G.overallStats.storedThisTick = 0 + _G.overallStats.capacity = 0 + for id, energyBuffer in pairs(energyBuffers) do + _G.overallStats.storedLastTick = _G.overallStats.storedLastTick + energyBuffer.averageEnergyStoredLastTick + _G.overallStats.storedThisTick = _G.overallStats.storedThisTick + energyBuffer.averageEnergyStoredThisTick + _G.overallStats.capacity = _G.overallStats.capacity + energyBuffer.capacity + end - local levelsMap = {} - for idx0, _ in pairs(reactor.getControlRodsLevels()) do - local rodLevel = math.floor(level) - if numberToAddOneLevelTo > 0 then - rodLevel = rodLevel + 1 - numberToAddOneLevelTo = numberToAddOneLevelTo - 1 + _G.overallStats.fuelUsage = 0 + _G.overallStats.waste = 0 + _G.overallStats.lastRFT = 0 + _G.overallStats.steamProductionRate = 0 + _G.overallStats.storedSteam = 0 + _G.overallStats.steamCapacity = 0 + _G.overallStats.steamConsumedLastTick = 4000 + + for id, reactor in pairs(reactors) do + if reactor.isActivelyCooled then + _G.overallStats.steamProductionRate = _G.overallStats.steamProductionRate + reactor.averageSteamProductionRate + _G.overallStats.storedSteam = _G.overallStats.storedSteam + reactor.averageStoredSteam + _G.overallStats.steamCapacity = _G.overallStats.steamCapacity + reactor.steamCapacity end - levelsMap[idx0] = rodLevel + _G.overallStats.fuelUsage = _G.overallStats.fuelUsage + reactor.averageFuelUsage + _G.overallStats.lastRFT = _G.overallStats.lastRFT + reactor.averageLastRFT + _G.overallStats.waste = _G.overallStats.waste + reactor.waste end - _G.reactor.setControlRodsLevels(levelsMap) + + _G.overallStats.rfLost = math.floor(_G.overallStats.lastRFT + _G.overallStats.storedLastTick - _G.overallStats.storedThisTick + 0.5) end -local function lerp(start, finish, t) - t = math.max(0, math.min(1, t)) +_G.btnOn = nil +_G.minb = nil +_G.maxb = nil + +_G.SECONDS_TO_AVERAGE = 0.5 - return (1 - t) * start + t * finish -end -- Function to calculate the average of an array of values local function calculateAverage(array) local sum = 0 - for _, value in ipairs(array) do + local count = 0 + for _, value in pairs(array) do sum = sum + value + count = count + 1 end - return sum / #array + return sum / count end -- TODO: Move to 2 or 3 stage PID controller to eliminate integral windup (oscillations) @@ -85,67 +97,6 @@ end -- TODO: Dynamic setting of PID constants based on measured change in RFT per % change in control rods -- TODO: Try using % of max RFT generation as basis of PID controller -- TODO: Try using gain scheduling to reduce integral windup -local pid = { - setpointRFT = 0, -- Target RFT - setpointRF = 0, -- Target RF - Kp = -.08, -- Proportional gain - Ki = -.0015, -- Integral gain - Kd = -.01, -- Derivative gain - integral = 0, -- Integral term accumulator - lastError = 0, -- Last error for derivative term -} - -local function iteratePID(pid, error) - -- Proportional term - local P = pid.Kp * error - - -- Integral term - pid.integral = pid.integral + pid.Ki * error - pid.integral = math.max(math.min(100, pid.integral), -100) - - -- Derivative term - local derivative = pid.Kd * (error - pid.lastError) - - -- Calculate control rod level - local rodLevel = math.max(math.min(P + pid.integral + derivative, 100), 0) - - -- Update PID controller state - pid.lastError = error - return rodLevel -end - -local function updateRods() - if not _G.btnOn then - return - end - local currentRF = _G.averageStoredLastTick - local diffb = _G.maxb - _G.minb - local minRF = _G.minb / 100 * _G.capacity - local diffRF = diffb / 100 * _G.capacity - local diffr = diffb / 100 - local targetRFT = _G.averageRfLost - local currentRFT = _G.averageLastRFT - local targetRF = diffRF / 2 + minRF - - pid.setpointRFT = targetRFT - pid.setpointRF = targetRF / _G.capacity * 1000 - - local errorRFT = pid.setpointRFT - currentRFT - local errorRF = pid.setpointRF - currentRF / _G.capacity * 1000 - - local W_RFT = lerp(1, 0, (math.abs(targetRF - currentRF) / _G.capacity / (diffr / 4))) - W_RFT = math.max(math.min(W_RFT, 1), 0) - - local W_RF = (1 - W_RFT) -- Adjust the weight for energy error - - -- Combine the errors with weights - local combinedError = W_RFT * errorRFT + W_RF * errorRF - local error = combinedError - local rftRodLevel = iteratePID(pid, error) - - -- Set control rod levels - setRods(rftRodLevel) -end --TODO: Update this to handle settings for multiple reactors and turbines local function saveToConfig() @@ -163,53 +114,43 @@ local function saveToConfig() file.close() end -local storedThisTickValues = {} -local lastRFTValues = {} -local rodValues = {} -local fuelUsageValues = {} -local wasteValues = {} -local fuelTempValues = {} -local caseTempValues = {} -local rfLostValues = {} - -local bat -local fuel - -local function updateStats() - if (_G.reactorVersion == "Big Reactors") then - _G.storedThisTick = _G.reactor.getEnergyStored() - _G.lastRFT = _G.reactor.getEnergyProducedLastTick() - _G.rod = _G.reactor.getControlRodLevel(0) - _G.fuelUsage = _G.reactor.getFuelConsumedLastTick() / 1000 - _G.waste = _G.reactor.getWasteAmount() - _G.fuelTemp = _G.reactor.getFuelTemperature() - _G.caseTemp = _G.reactor.getCasingTemperature() - -- Big Reactors doesn't give us a way to directly query RF capacity through CC APIs - _G.capacity = math.max(_G.capacity, _G.reactor.getEnergyStored) - elseif (_G.reactorVersion == "Extreme Reactors") then - bat = _G.reactor.getEnergyStats() - fuel = _G.reactor.getFuelStats() - - _G.storedThisTick = bat.energyStored - _G.lastRFT = bat.energyProducedLastTick - _G.capacity = bat.energyCapacity - _G.rod = calculateAverage(_G.reactor.getControlRodsLevels()) - _G.fuelUsage = fuel.fuelConsumedLastTick / 1000 - _G.waste = _G.reactor.getWasteAmount() - _G.fuelTemp = _G.reactor.getFuelTemperature() - _G.caseTemp = _G.reactor.getCasingTemperature() - elseif (_G.reactorVersion == "Bigger Reactors") then - _G.storedThisTick = _G.reactor.battery().stored() - _G.lastRFT = _G.reactor.battery().producedLastTick() - _G.capacity = _G.reactor.battery().capacity() - _G.rod = _G.reactor.getControlRod(0).level() - _G.fuelUsage = _G.reactor.fuelTank().burnedLastTick() / 1000 - _G.waste = _G.reactor.fuelTank().waste() - _G.fuelTemp = _G.reactor.fuelTemperature() - _G.caseTemp = _G.reactor.casingTemperature() - end - _G.rfLost = math.floor(_G.lastRFT + _G.storedLastTick - _G.storedThisTick + 0.5) -end + + +-- local function updateStats() + -- if (_G.reactorVersion == "Big Reactors") then + -- _G.storedThisTick = _G.reactor.getEnergyStored() + -- _G.lastRFT = _G.reactor.getEnergyProducedLastTick() + -- _G.rod = _G.reactor.getControlRodLevel(0) + -- _G.fuelUsage = _G.reactor.getFuelConsumedLastTick() / 1000 + -- _G.waste = _G.reactor.getWasteAmount() + -- _G.fuelTemp = _G.reactor.getFuelTemperature() + -- _G.caseTemp = _G.reactor.getCasingTemperature() + -- -- Big Reactors doesn't give us a way to directly query RF capacity through CC APIs + -- _G.capacity = math.max(_G.capacity, _G.reactor.getEnergyStored) + -- elseif (_G.reactorVersion == "Extreme Reactors") then + -- local bat = _G.reactor.getEnergyStats() + -- local fuel = _G.reactor.getFuelStats() + + -- _G.storedThisTick = bat.energyStored + -- _G.lastRFT = bat.energyProducedLastTick + -- _G.capacity = bat.energyCapacity + -- _G.rod = calculateAverage(_G.reactor.getControlRodsLevels()) + -- _G.fuelUsage = fuel.fuelConsumedLastTick / 1000 + -- _G.waste = _G.reactor.getWasteAmount() + -- _G.fuelTemp = _G.reactor.getFuelTemperature() + -- _G.caseTemp = _G.reactor.getCasingTemperature() + -- elseif (_G.reactorVersion == "Bigger Reactors") then + -- _G.storedThisTick = _G.reactor.battery().stored() + -- _G.lastRFT = _G.reactor.battery().producedLastTick() + -- _G.capacity = _G.reactor.battery().capacity() + -- _G.rod = _G.reactor.getControlRod(0).level() + -- _G.fuelUsage = _G.reactor.fuelTank().burnedLastTick() / 1000 + -- _G.waste = _G.reactor.fuelTank().waste() + -- _G.fuelTemp = _G.reactor.fuelTemperature() + -- _G.caseTemp = _G.reactor.casingTemperature() + -- end + -- _G.rfLost = math.floor(_G.lastRFT + _G.storedLastTick - _G.storedThisTick + 0.5) +-- end --TODO: Update this to handle settings for multiple reactors and turbines local function loadFromConfig() @@ -241,27 +182,34 @@ local function loadFromConfig() _G.reactor.setActive(_G.btnOn) end -local function getAllPeripheralIdsForType(targetType) - ---@type table - local peripheralIds = {} - for _, id in pairs(peripheral.getNames()) do - if (peripheral.getType(id) == targetType) then - peripheralIds[id] = true - end - end - return peripheralIds -end - ---@param monitorID string local function connectMonitor(monitorID) print("Monitor "..monitorID.." connected!") monitors[monitorID] = Monitor.new(monitorID) end -local function discoverAndConnectMonitors() - local ids = getAllPeripheralIdsForType("monitor") - for id, _ in pairs(ids) do - connectMonitor(id) +---@param reactorID string +local function connectExtremeReactor(reactorID) + print("Extreme Reactor "..reactorID.." connected!") + reactors[reactorID] = Reactor.newExtremeReactor(reactorID) + _G.selectedReactor = reactors[reactorID] +end + +---@param energyBufferID string +local function connectForgeEnergyBuffer(energyBufferID) + print("Energy Buffer "..energyBufferID.." connected!") + energyBuffers[energyBufferID] = EnergyBuffer.newForgeEnergyBuffer(energyBufferID) +end + +---@param energyBufferID string +local function connectReactorEnergyBuffer(energyBufferID) + print("Reactor Energy Buffer "..energyBufferID.." connected!") + energyBuffers[energyBufferID] = EnergyBuffer.newReactorEnergyBuffer(energyBufferID) +end + +local function firePeriphalAttachEventForAllPeripherals() + for _, id in pairs(peripheral.getNames()) do + os.queueEvent("peripheral", id) end end @@ -272,12 +220,48 @@ local function redrawMonitors() end end +---@param currentTickNumber number +local function updateEnergyBuffers(currentTickNumber) + for _, energyBuffer in pairs(energyBuffers) do + energyBuffer:update(currentTickNumber) + end +end + +---@param currentTickNumber number +local function updateReactors(currentTickNumber) + for _, reactor in pairs(reactors) do + reactor:update(currentTickNumber) + end +end + +function _G.setReactors(active) + for _, reactor in pairs(reactors) do + reactor.setActive(active) + end +end + +local function updateReactorRods() + for _, reactor in pairs(reactors) do + reactor:updateRods() + end +end + ---@param peripheralID string local function handlePeripheralDetach(peripheralID) if monitors[peripheralID] ~= nil then print("Monitor "..peripheralID.." disconnected!") monitors[peripheralID] = nil end + + if energyBuffers[peripheralID] ~= nil then + print("Energy Buffer "..peripheralID.." disconnected!") + energyBuffers[peripheralID] = nil + end + + if reactors[peripheralID] ~= nil then + print("Reactor "..peripheralID.." disconnected!") + reactors[peripheralID] = nil + end end ---@param peripheralID string @@ -285,57 +269,27 @@ end local function handlePeripheralAttach(peripheralID, peripheralType) if peripheralType == "monitor" then connectMonitor(peripheralID) - elseif peripheralType == "BiggerReactors_Reactor" then - print("DEBUG: Attached Reactor") + elseif peripheralType == "BigReactors-Reactor" then + connectExtremeReactor(peripheralID) + connectReactorEnergyBuffer(peripheralID) + elseif peripheralType == "energy_storage" then + connectForgeEnergyBuffer(peripheralID) else print("Unknown peripheral", peripheralID, "of type", peripheralType, "attached to network") end end -local function updateAverages() - table.insert(storedThisTickValues, _G.storedThisTick) - table.insert(lastRFTValues, _G.lastRFT) - table.insert(rodValues, _G.rod) - table.insert(fuelUsageValues, _G.fuelUsage) - table.insert(wasteValues, _G.waste) - table.insert(fuelTempValues, _G.fuelTemp) - table.insert(caseTempValues, _G.caseTemp) - table.insert(rfLostValues, _G.rfLost) - - local maxIterations = 20 * _G.SECONDS_TO_AVERAGE - while #storedThisTickValues > maxIterations do - table.remove(storedThisTickValues, 1) - table.remove(lastRFTValues, 1) - table.remove(rodValues, 1) - table.remove(fuelUsageValues, 1) - table.remove(wasteValues, 1) - table.remove(fuelTempValues, 1) - table.remove(caseTempValues, 1) - table.remove(rfLostValues, 1) - end - -- Calculate running averages - _G.averageStoredThisTick = calculateAverage(storedThisTickValues) - _G.averageLastRFT = calculateAverage(lastRFTValues) - _G.averageRod = calculateAverage(rodValues) - _G.averageFuelUsage = calculateAverage(fuelUsageValues) - _G.averageWaste = calculateAverage(wasteValues) - _G.averageFuelTemp = calculateAverage(fuelTempValues) - _G.averageCaseTemp = calculateAverage(caseTempValues) - _G.averageRfLost = calculateAverage(rfLostValues) -end - -local iterations = 0 -_G.TICKS_TO_REDRAW = 4 -local function runLoop() - updateRods() - if iterations % TICKS_TO_REDRAW == 0 then +_G.TICKS_TO_REDRAW = 1 +local function runLoop(currentTickNumber) + updateEnergyBuffers(currentTickNumber) + updateReactors(currentTickNumber) + updateOverallStats() + if currentTickNumber % TICKS_TO_REDRAW == 0 then redrawMonitors() end - iterations = iterations + 1 end - local function eventListener() while true do local event = { os.pullEvent() } @@ -357,39 +311,36 @@ local function loop() local loopEventName = "yield" local curTime = math.floor(os.clock() * 20) local lastTime = curTime - local maxRetries = 5 - local tries = 0 os.sleep(0) while true do curTime = math.floor(os.clock() * 20) - if curTime < lastTime + 1 then + + local reactorCount = 0 + for _, reactor in pairs(_G.reactors) do + reactorCount = reactorCount + 1 + end + if reactorCount < 1 then + print("Reactor not detected! Please connect a reactor!") + sleep(1) + elseif curTime < lastTime + 1 then os.queueEvent(loopEventName) os.pullEvent(loopEventName) elseif curTime > lastTime + 1 then + -- We have missed the data from the last tick print("Missed last", curTime - lastTime - 1, "ticks!", curTime) - updateStats() + runLoop(curTime) else + local t = os.epoch("utc") -- Guaranteed to run at the start of a new tick - updateStats() - if _G.lastRFTPrev ~= nil and _G.storedLastTick ~= nil then - - -- Since we are executing on a separate thread, we need to poll until the reactor has updated it's values in the new tick. - -- Usually this is only 3-4 iterations as the reactor does not take long to update stats. - -- If it takes more than 5 iterations we give up and try again in the next tick - tries = 0 - while _G.lastRFT == _G.lastRFTPrev and _G.storedThisTick == _G.storedLastTick or tries < maxRetries do - updateStats() - tries = tries + 1 - end - updateAverages() - runLoop() + while os.epoch("utc") - t < 2 do + os.queueEvent(loopEventName) + os.pullEvent(loopEventName) end + runLoop(curTime) + updateReactorRods() os.sleep(0) end - _G.storedLastTick = _G.storedThisTick - _G.averageStoredLastTick = _G.averageStoredThisTick - _G.lastRFTPrev = _G.lastRFT lastTime = curTime end end @@ -427,29 +378,18 @@ function _G.main() term.clear() term.setCursorPos(1,1) - local reactorDetected = false - while not reactorDetected do - reactorDetected = detectReactor() - if not reactorDetected then - print("Reactor not detected! Trying again...") - sleep(1) - end - end - - print("Reactor detected!") + _G.monitors = {} + _G.reactors = {} + _G.turbines = {} + _G.energyBuffers = {} _G.maxb = 70 _G.minb = 30 _G.rod = 80 _G.btnOn = true - _G.btnOff = not _G.btnOn - _G.reactor.setActive(_G.btnOn) - discoverAndConnectMonitors() - -- initMon() - -- print("Writing config to disk...") - -- saveToConfig() - -- print("Reactor initialization done! Starting controller") + -- Manually fire the "peripheral" event to make sure all the connected peripherals are initialized correctly. + firePeriphalAttachEventForAllPeripherals() -- term.clear() -- term.setCursorPos(1,1) @@ -457,5 +397,5 @@ function _G.main() -- print("Reactor Mod: "..reactorVersion) --main loop - parallel.waitForAny(loop, eventListener) + parallel.waitForAll(loop, eventListener) end