Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --[[
- Draconic Energy Core Dashboard
- For CC:Tweaked / ATM10
- Features:
- - Automatic monitor and Draconic Energy Core detection
- - Vertical energy buffer
- - Current Flow: Input, Output, Net and charging state
- - Statistics panel
- - Compact scrolling flow graph
- - Automatic graph scaling
- - Automatic reconnect
- - Touch the monitor to cycle graph modes
- All display text is in English.
- ]]
- -- ============================================================
- -- CONFIGURATION
- -- ============================================================
- local UPDATE_DELAY = 0.25
- local GRAPH_POINTS = 600
- local GRAPH_MODE = "BAR" -- BAR, LINE, AREA
- local TEXT_SCALE = 1
- -- ============================================================
- -- THEME
- -- ============================================================
- local C = {
- bg = colors.black,
- panel = colors.black,
- border = colors.gray,
- text = colors.white,
- secondary = colors.lightGray,
- title = colors.cyan,
- charge = colors.lime,
- chargeHigh = colors.green,
- warning = colors.yellow,
- danger = colors.red,
- idle = colors.lightGray,
- graphPos = colors.green,
- graphNeg = colors.red,
- graphZero = colors.gray,
- bufferEmpty = colors.gray,
- bufferLow = colors.red,
- bufferMid = colors.yellow,
- bufferGood = colors.lime,
- bufferHigh = colors.cyan
- }
- -- ============================================================
- -- UTILITY FUNCTIONS
- -- ============================================================
- local function clamp(v, min, max)
- if v < min then return min end
- if v > max then return max end
- return v
- end
- local function safeCall(obj, method, ...)
- if not obj or not obj[method] then
- return false, nil
- end
- return pcall(obj[method], obj, ...)
- end
- local function formatNumber(num)
- num = tonumber(num) or 0
- local abs = math.abs(num)
- if abs >= 1e15 then
- return string.format("%.2fP", num / 1e15)
- elseif abs >= 1e12 then
- return string.format("%.2fT", num / 1e12)
- elseif abs >= 1e9 then
- return string.format("%.2fG", num / 1e9)
- elseif abs >= 1e6 then
- return string.format("%.2fM", num / 1e6)
- elseif abs >= 1e3 then
- return string.format("%.1fk", num / 1e3)
- else
- return string.format("%.0f", num)
- end
- end
- local function formatRF(num)
- return formatNumber(num) .. " RF/t"
- end
- local function formatEnergy(num)
- return formatNumber(num) .. " RF"
- end
- local function formatDuration(seconds)
- seconds = math.max(0, math.floor(seconds or 0))
- local days = math.floor(seconds / 86400)
- seconds = seconds % 86400
- local hours = math.floor(seconds / 3600)
- seconds = seconds % 3600
- local minutes = math.floor(seconds / 60)
- seconds = seconds % 60
- if days > 0 then
- return string.format("%dd %02dh %02dm", days, hours, minutes)
- elseif hours > 0 then
- return string.format("%02dh %02dm %02ds", hours, minutes, seconds)
- else
- return string.format("%02dm %02ds", minutes, seconds)
- end
- end
- local function centerText(mon, y, text, fg, bg)
- local w = select(1, mon.getSize())
- local x = math.max(1, math.floor((w - #text) / 2) + 1)
- mon.setCursorPos(x, y)
- mon.setTextColor(fg or C.text)
- mon.setBackgroundColor(bg or C.bg)
- mon.write(text)
- end
- local function writeAt(mon, x, y, text, fg, bg)
- if y < 1 then return end
- local w, h = mon.getSize()
- if y > h or x > w then return end
- text = tostring(text)
- if x < 1 then
- text = text:sub(2 - x)
- x = 1
- end
- text = text:sub(1, w - x + 1)
- mon.setCursorPos(x, y)
- mon.setTextColor(fg or C.text)
- mon.setBackgroundColor(bg or C.bg)
- mon.write(text)
- end
- local function horizontalLine(mon, x1, x2, y, char, fg)
- if x2 < x1 then return end
- mon.setTextColor(fg or C.border)
- mon.setBackgroundColor(C.bg)
- mon.setCursorPos(x1, y)
- for _ = x1, x2 do
- mon.write(char or "-")
- end
- end
- local function drawBox(mon, x1, y1, x2, y2, title)
- if x2 <= x1 or y2 <= y1 then return end
- mon.setTextColor(C.border)
- mon.setBackgroundColor(C.bg)
- mon.setCursorPos(x1, y1)
- mon.write("+")
- mon.write(string.rep("-", math.max(0, x2 - x1 - 1)))
- mon.write("+")
- for y = y1 + 1, y2 - 1 do
- mon.setCursorPos(x1, y)
- mon.write("|")
- mon.setCursorPos(x2, y)
- mon.write("|")
- end
- mon.setCursorPos(x1, y2)
- mon.write("+")
- mon.write(string.rep("-", math.max(0, x2 - x1 - 1)))
- mon.write("+")
- if title then
- local label = " " .. title .. " "
- local tx = x1 + 2
- if tx + #label <= x2 then
- mon.setCursorPos(tx, y1)
- mon.setTextColor(C.title)
- mon.setBackgroundColor(C.bg)
- mon.write(label)
- end
- end
- end
- -- ============================================================
- -- PERIPHERALS
- -- ============================================================
- local monitor
- local monitorName
- local core
- local coreName
- local function findMonitor()
- for _, name in ipairs(peripheral.getNames()) do
- if peripheral.getType(name) == "monitor" then
- local p = peripheral.wrap(name)
- if p then
- return p, name
- end
- end
- end
- return nil, nil
- end
- local function findEnergyCore()
- for _, name in ipairs(peripheral.getNames()) do
- if peripheral.getType(name) == "draconic_rf_storage" then
- local p = peripheral.wrap(name)
- if p then
- return p, name
- end
- end
- end
- return nil, nil
- end
- local function getCoreTier(maxEnergy)
- if maxEnergy >= 9223372036854775807 then
- return 8
- elseif maxEnergy >= 2140000000000 then
- return 7
- elseif maxEnergy >= 356000000000 then
- return 6
- elseif maxEnergy >= 59300000000 then
- return 5
- elseif maxEnergy >= 9880000000 then
- return 4
- elseif maxEnergy >= 1640000000 then
- return 3
- elseif maxEnergy >= 273000000 then
- return 2
- else
- return 1
- end
- end
- local function initialize()
- monitor, monitorName = findMonitor()
- if not monitor then
- print("Error: Monitor not found.")
- return false
- end
- core, coreName = findEnergyCore()
- if not core then
- print("Error: Draconic Energy Core not found.")
- return false
- end
- if monitor.setTextScale then
- monitor.setTextScale(TEXT_SCALE)
- end
- monitor.setBackgroundColor(C.bg)
- monitor.setTextColor(C.text)
- monitor.clear()
- return true
- end
- -- ============================================================
- -- ENERGY DATA
- -- ============================================================
- local history = {}
- local peakInput = 0
- local peakOutput = 0
- local totalInput = 0
- local totalOutput = 0
- local lastEnergy = 0
- local lastTime = os.epoch("utc") / 1000
- local startTime = os.epoch("utc") / 1000
- local function getFlow()
- local inputRate = 0
- local outputRate = 0
- local hasSeparateRates =
- core.getInputPerTick and
- core.getOutputPerTick
- if hasSeparateRates then
- local okIn, inValue = safeCall(core, "getInputPerTick")
- local okOut, outValue = safeCall(core, "getOutputPerTick")
- if okIn and type(inValue) == "number" then
- inputRate = math.max(0, inValue)
- end
- if okOut and type(outValue) == "number" then
- outputRate = math.max(0, outValue)
- end
- else
- local now = os.epoch("utc") / 1000
- local dt = now - lastTime
- if dt > 0 then
- local delta = lastEnergy
- local okEnergy, currentEnergy = safeCall(core, "getEnergyStored")
- if okEnergy and type(currentEnergy) == "number" then
- delta = currentEnergy - lastEnergy
- local rate = delta / dt / 20
- if rate >= 0 then
- inputRate = rate
- else
- outputRate = -rate
- end
- end
- end
- end
- return inputRate, outputRate
- end
- -- ============================================================
- -- BUFFER COLOR
- -- ============================================================
- local function getBufferColor(percent)
- if percent < 10 then
- return C.bufferLow
- elseif percent < 30 then
- return C.warning
- elseif percent < 70 then
- return C.bufferGood
- elseif percent < 95 then
- return C.chargeHigh
- else
- return C.bufferHigh
- end
- end
- -- ============================================================
- -- DRAW ENERGY BUFFER
- -- ============================================================
- local function drawEnergyBuffer(mon, x1, y1, x2, y2, stored, maxEnergy, percent)
- drawBox(mon, x1, y1, x2, y2, "ENERGY BUFFER")
- local innerX1 = x1 + 2
- local innerX2 = x2 - 2
- local innerY1 = y1 + 2
- local innerY2 = y2 - 2
- local barWidth = math.min(5, math.max(3, innerX2 - innerX1 + 1))
- local barX1 = math.floor((innerX1 + innerX2 - barWidth) / 2)
- local barX2 = barX1 + barWidth - 1
- local barTop = innerY1
- local barBottom = math.min(innerY2, barTop + 13)
- local barHeight = barBottom - barTop + 1
- local filled = math.floor(barHeight * clamp(percent / 100, 0, 1) + 0.5)
- local fillColor = getBufferColor(percent)
- for y = barTop, barBottom do
- local filledRow = y > barBottom - filled
- for x = barX1, barX2 do
- mon.setCursorPos(x, y)
- mon.setTextColor(C.text)
- mon.setBackgroundColor(filledRow and fillColor or C.bufferEmpty)
- mon.write(" ")
- end
- end
- local percentText = string.format("%.1f%%", percent)
- local py = barBottom + 1
- if py <= innerY2 then
- centerText(mon, py, percentText, fillColor, C.bg)
- end
- local labelY = math.min(innerY2 - 5, py + 2)
- if labelY >= innerY1 and labelY <= innerY2 then
- writeAt(mon, innerX1, labelY, "Stored", C.secondary)
- writeAt(mon, innerX1, labelY + 1, formatEnergy(stored), C.text)
- writeAt(mon, innerX1, labelY + 3, "Max Capacity", C.secondary)
- writeAt(mon, innerX1, labelY + 4, formatEnergy(maxEnergy), C.text)
- if labelY + 6 <= innerY2 then
- writeAt(mon, innerX1, labelY + 6, "Free Space", C.secondary)
- writeAt(mon, innerX1, labelY + 7, formatEnergy(maxEnergy - stored), C.text)
- end
- end
- end
- -- ============================================================
- -- CURRENT FLOW
- -- ============================================================
- local function drawCurrentFlow(mon, x1, y1, x2, y2, inputRate, outputRate)
- drawBox(mon, x1, y1, x2, y2, "CURRENT FLOW")
- local net = inputRate - outputRate
- local status
- local statusColor
- if net > 1 then
- status = "CHARGING"
- statusColor = C.charge
- elseif net < -1 then
- status = "DISCHARGING"
- statusColor = C.danger
- else
- status = "IDLE"
- statusColor = C.idle
- end
- local left = x1 + 2
- local right = x2 - 2
- writeAt(mon, left, y1 + 2, "Status", C.secondary)
- writeAt(mon, left, y1 + 3, status, statusColor)
- writeAt(mon, left, y1 + 5, "Input", C.secondary)
- writeAt(mon, left, y1 + 6, formatRF(inputRate), C.text)
- writeAt(mon, right - math.max(0, #formatRF(outputRate) - 1), y1 + 5,
- "Output", C.secondary)
- writeAt(mon, right - #formatRF(outputRate) + 1, y1 + 6,
- formatRF(outputRate), C.text)
- writeAt(mon, left, y1 + 8, "Net Flow", C.secondary)
- local netText = (net >= 0 and "+" or "") .. formatRF(net)
- writeAt(mon, left, y1 + 9, netText, net >= 0 and C.charge or C.danger)
- end
- -- ============================================================
- -- STATISTICS
- -- ============================================================
- local function drawStatistics(mon, x1, y1, x2, y2, tier, peakIn, peakOut,
- runtime, etaFull, etaEmpty)
- drawBox(mon, x1, y1, x2, y2, "STATISTICS")
- local left = x1 + 2
- local mid = math.floor((x1 + x2) / 2) + 1
- writeAt(mon, left, y1 + 2, "Tier", C.secondary)
- writeAt(mon, left, y1 + 3, "Tier " .. tostring(tier), C.text)
- writeAt(mon, mid, y1 + 2, "Runtime", C.secondary)
- writeAt(mon, mid, y1 + 3, runtime, C.text)
- writeAt(mon, left, y1 + 5, "Peak Input", C.secondary)
- writeAt(mon, left, y1 + 6, formatRF(peakIn), C.text)
- writeAt(mon, mid, y1 + 5, "Peak Output", C.secondary)
- writeAt(mon, mid, y1 + 6, formatRF(peakOut), C.text)
- writeAt(mon, left, y1 + 8, "ETA Full", C.secondary)
- writeAt(mon, left, y1 + 9, etaFull, C.text)
- writeAt(mon, mid, y1 + 8, "ETA Empty", C.secondary)
- writeAt(mon, mid, y1 + 9, etaEmpty, C.text)
- end
- -- ============================================================
- -- GRAPH
- -- ============================================================
- local function getGraphRange()
- local maxAbs = 1
- for _, value in ipairs(history) do
- maxAbs = math.max(maxAbs, math.abs(value))
- end
- -- Add headroom so the graph does not touch the border.
- maxAbs = maxAbs * 1.15
- return maxAbs
- end
- local function drawGraph(mon, x1, y1, x2, y2)
- drawBox(mon, x1, y1, x2, y2, "FLOW HISTORY")
- local gx1 = x1 + 1
- local gx2 = x2 - 1
- local gy1 = y1 + 2
- local gy2 = y2 - 1
- local width = gx2 - gx1 + 1
- local height = gy2 - gy1 + 1
- if width <= 0 or height <= 2 then
- return
- end
- local maxAbs = getGraphRange()
- local centerY = math.floor((gy1 + gy2) / 2)
- -- Zero line
- for x = gx1, gx2 do
- writeAt(mon, x, centerY, "-", C.graphZero)
- end
- local count = math.min(#history, width)
- for screenX = 0, count - 1 do
- local historyIndex = #history - count + screenX + 1
- local value = history[historyIndex] or 0
- local normalized = clamp(value / maxAbs, -1, 1)
- local length = math.floor(math.abs(normalized) * math.floor(height / 2))
- local x = gx1 + screenX
- local color = value >= 0 and C.graphPos or C.graphNeg
- if GRAPH_MODE == "BAR" then
- if value >= 0 then
- for offset = 1, length do
- local y = centerY - offset
- if y >= gy1 then
- writeAt(mon, x, y, "█", C.text, color)
- end
- end
- else
- for offset = 1, length do
- local y = centerY + offset
- if y <= gy2 then
- writeAt(mon, x, y, "█", C.text, color)
- end
- end
- end
- elseif GRAPH_MODE == "AREA" then
- if value >= 0 then
- for y = centerY - length, centerY - 1 do
- if y >= gy1 then
- writeAt(mon, x, y, " ", C.text, color)
- end
- end
- else
- for y = centerY + 1, centerY + length do
- if y <= gy2 then
- writeAt(mon, x, y, " ", C.text, color)
- end
- end
- end
- if length > 0 then
- writeAt(mon, x, centerY - (value >= 0 and length or -length),
- "●", C.text, color)
- end
- else
- local y = centerY - math.floor(normalized * math.floor(height / 2))
- y = clamp(y, gy1, gy2)
- writeAt(mon, x, y, "●", C.text, color)
- end
- end
- -- Graph scale
- local scaleText = "±" .. formatRF(maxAbs)
- local scaleX = math.max(gx1, gx2 - #scaleText + 1)
- writeAt(mon, scaleX, gy1, scaleText, C.secondary)
- end
- -- ============================================================
- -- MAIN RENDER
- -- ============================================================
- local function render(stored, maxEnergy, percent, tier,
- inputRate, outputRate,
- runtime, etaFull, etaEmpty)
- local w, h = monitor.getSize()
- monitor.setBackgroundColor(C.bg)
- monitor.setTextColor(C.text)
- monitor.clear()
- -- Header
- local title = "DRACONIC ENERGY CORE"
- local tierText = "TIER " .. tostring(tier)
- centerText(monitor, 1, title, C.title)
- writeAt(monitor, math.max(1, w - #tierText), 1, tierText, C.secondary)
- horizontalLine(monitor, 1, w, 2, "-", C.border)
- -- Layout dimensions
- local bufferWidth = math.max(20, math.floor(w * 0.27))
- bufferWidth = math.min(bufferWidth, w - 24)
- local leftX1 = 1
- local leftX2 = bufferWidth
- local rightX1 = bufferWidth + 1
- local rightX2 = w
- local topY = 3
- local flowHeight = math.min(12, math.max(9, math.floor(h * 0.24)))
- local flowY1 = topY
- local flowY2 = math.min(h - 10, flowY1 + flowHeight)
- local statsY1 = flowY2 + 1
- local statsY2 = math.min(h - 5, statsY1 + 11)
- local graphY1 = statsY2 + 1
- local graphY2 = h
- -- Energy buffer
- drawEnergyBuffer(
- monitor,
- leftX1,
- topY,
- leftX2,
- h,
- stored,
- maxEnergy,
- percent
- )
- -- Right panels
- if rightX2 - rightX1 >= 18 then
- drawCurrentFlow(
- monitor,
- rightX1,
- flowY1,
- rightX2,
- flowY2,
- inputRate,
- outputRate
- )
- if statsY2 > statsY1 then
- drawStatistics(
- monitor,
- rightX1,
- statsY1,
- rightX2,
- statsY2,
- tier,
- peakInput,
- peakOutput,
- runtime,
- etaFull,
- etaEmpty
- )
- end
- end
- -- Graph across bottom
- if graphY2 > graphY1 + 3 then
- drawGraph(
- monitor,
- 1,
- graphY1,
- w,
- graphY2
- )
- end
- end
- -- ============================================================
- -- INITIALIZATION
- -- ============================================================
- while not initialize() do
- sleep(1)
- end
- lastEnergy = core.getEnergyStored()
- lastTime = os.epoch("utc") / 1000
- startTime = os.epoch("utc") / 1000
- -- ============================================================
- -- MAIN LOOP
- -- ============================================================
- while true do
- if not peripheral.isPresent(monitorName) or
- not peripheral.isPresent(coreName) then
- sleep(0.5)
- if initialize() then
- history = {}
- peakInput = 0
- peakOutput = 0
- totalInput = 0
- totalOutput = 0
- lastEnergy = core.getEnergyStored()
- lastTime = os.epoch("utc") / 1000
- startTime = os.epoch("utc") / 1000
- end
- goto continue
- end
- local okEnergy, stored = safeCall(core, "getEnergyStored")
- local okMax, maxEnergy = safeCall(core, "getMaxEnergyStored")
- if not okEnergy or not okMax or
- type(stored) ~= "number" or
- type(maxEnergy) ~= "number" or
- maxEnergy <= 0 then
- sleep(0.5)
- goto continue
- end
- local percent = clamp((stored / maxEnergy) * 100, 0, 100)
- local tier
- if core.getTier then
- local okTier, result = safeCall(core, "getTier")
- if okTier and type(result) == "number" then
- tier = result
- else
- tier = getCoreTier(maxEnergy)
- end
- else
- tier = getCoreTier(maxEnergy)
- end
- local now = os.epoch("utc") / 1000
- local dt = now - lastTime
- if dt <= 0 then
- dt = UPDATE_DELAY
- end
- local inputRate, outputRate = getFlow()
- -- If separate input/output methods exist, use them.
- -- Otherwise calculate from energy change.
- if not (core.getInputPerTick and core.getOutputPerTick) then
- local delta = stored - lastEnergy
- local rate = delta / dt / 20
- inputRate = 0
- outputRate = 0
- if rate >= 0 then
- inputRate = rate
- else
- outputRate = -rate
- end
- end
- lastEnergy = stored
- lastTime = now
- inputRate = math.max(0, inputRate or 0)
- outputRate = math.max(0, outputRate or 0)
- if inputRate > peakInput then
- peakInput = inputRate
- end
- if outputRate > peakOutput then
- peakOutput = outputRate
- end
- totalInput = totalInput + inputRate * 20 * dt
- totalOutput = totalOutput + outputRate * 20 * dt
- local net = inputRate - outputRate
- table.insert(history, net)
- while #history > GRAPH_POINTS do
- table.remove(history, 1)
- end
- -- ETA calculations
- local etaFull = "--:--:--"
- local etaEmpty = "--:--:--"
- if inputRate > 0 and stored < maxEnergy then
- local seconds = (maxEnergy - stored) / (inputRate * 20)
- etaFull = formatDuration(seconds)
- elseif stored >= maxEnergy then
- etaFull = "FULL"
- end
- if outputRate > 0 and stored > 0 then
- local seconds = stored / (outputRate * 20)
- etaEmpty = formatDuration(seconds)
- elseif stored <= 0 then
- etaEmpty = "EMPTY"
- end
- local runtime = formatDuration(
- os.epoch("utc") / 1000 - startTime
- )
- render(
- stored,
- maxEnergy,
- percent,
- tier,
- inputRate,
- outputRate,
- runtime,
- etaFull,
- etaEmpty
- )
- -- Touch monitor to cycle graph mode.
- local timer = os.startTimer(UPDATE_DELAY)
- while true do
- local event, side, p1, p2 = os.pullEvent()
- if event == "timer" and p1 == timer then
- break
- end
- if event == "monitor_touch" and side == monitorName then
- if GRAPH_MODE == "BAR" then
- GRAPH_MODE = "LINE"
- elseif GRAPH_MODE == "LINE" then
- GRAPH_MODE = "AREA"
- else
- GRAPH_MODE = "BAR"
- end
- break
- end
- end
- ::continue::
- end
Advertisement