-- Reactor Info Client
-- Receives reactor statistics via rednet and displays them on monitor
-- Configuration
local PROTOCOL = "reactor_stats"
local TIMEOUT = 5 -- Seconds to wait for data before showing error
-- Initialize peripherals
local m = peripheral.wrap("monitor_1")
if not m then
print("Error: No monitor found!")
return
end
-- Open rednet on any available modem
local modem = nil
for _, side in ipairs(peripheral.getNames()) do
if peripheral.getType(side) == "modem" then
modem = peripheral.wrap(side)
if modem.isWireless() then
rednet.open(side)
print("Rednet opened on wireless modem: " .. side)
break
end
end
end
if not modem then
print("Error: No wireless modem found!")
return
end
-- Rainbow colors for border animation
local rainbow_colors = {colors.red, colors.orange, colors.yellow, colors.lime, colors.cyan, colors.blue, colors.purple, colors.magenta}
-- Function to draw progress bar
local function drawProgressBar(x, y, value, min_val, max_val, target, unit, bar_width, force_color, min_indicator)
bar_width = bar_width or 20
local percentage = math.max(0, math.min(100, ((value - min_val) / (max_val - min_val)) * 100))
local filled_chars = math.floor((percentage / 100) * bar_width)
-- Color coding based on proximity to target
local color = colors.red
if force_color then
color = force_color
elseif target then
local target_percentage = ((target - min_val) / (max_val - min_val)) * 100
local diff = math.abs(percentage - target_percentage)
if diff < 10 then
color = colors.lime
elseif diff < 25 then
color = colors.yellow
end
else
-- Default color grading for values without targets
if percentage > 66 then
color = colors.lime
elseif percentage > 33 then
color = colors.yellow
end
end
m.setCursorPos(x, y)
m.setTextColor(colors.gray)
m.write("[")
-- Calculate minimum indicator position if provided
local min_indicator_pos = nil
if min_indicator then
min_indicator_pos = math.floor(((min_indicator - min_val) / (max_val - min_val)) * bar_width)
end
-- Draw filled portion
for i = 1, bar_width do
if min_indicator_pos and i == min_indicator_pos then
m.setTextColor(colors.cyan)
m.write("|")
elseif i <= filled_chars then
m.setTextColor(color)
m.write("=")
else
m.setTextColor(colors.gray)
m.write("-")
end
end
m.setTextColor(colors.gray)
m.write("] ")
m.setTextColor(colors.white)
m.write(string.format("%.2f %s", value, unit))
end
-- Function to show startup animation
local function showStartupAnimation()
local y_padding = 2
local x_padding = 3
local width, height = m.getSize()
m.setTextScale(1)
m.clear()
-- Phase 2: Moving rainbow border with centered text
local title_lines = {"Lag 4 President", "Draconic Reactor", "Quantum Monitor v1.0", " ~ By THCoder aka David"}
local corner_text = "Kennst du eigentlich Andi? :D"
for frame = 1, 25 do
m.clear()
-- Draw moving rainbow border
local color_offset = frame % #rainbow_colors
-- Top and bottom borders
for x = 1, width do
local color_index = ((x + color_offset - 1) % #rainbow_colors) + 1
m.setTextColor(rainbow_colors[color_index])
m.setCursorPos(x, 1)
m.write("=")
m.setCursorPos(x, height)
m.write("=")
end
-- Left and right borders
for y = 2, height - 1 do
local color_index = ((y + color_offset - 1) % #rainbow_colors) + 1
m.setTextColor(rainbow_colors[color_index])
m.setCursorPos(1, y)
m.write("|")
m.setCursorPos(width, y)
m.write("|")
end
-- Draw centered title text with rainbow colors
local start_y = math.floor((height - #title_lines) / 2)
for i, line in ipairs(title_lines) do
local x_pos = math.floor((width - #line) / 2) + 1
local line_color = rainbow_colors[((i - 1) % #rainbow_colors) + 1]
if frame > i * 3 then
for j = 1, math.min(#line, frame - i * 3) do
m.setTextColor(line_color)
m.setCursorPos(x_pos + j - 1, start_y + i)
m.write(line:sub(j, j))
end
end
end
-- Draw corner text (bottom right)
if frame > 15 then
m.setTextColor(colors.cyan)
m.setCursorPos(width - #corner_text - 2, height - 2)
m.write(corner_text)
end
sleep(0.1)
end
sleep(3)
end
-- Function to display reactor data
local function displayReactorData(info)
local y_padding = 2
local x_padding = 3
local value_align = 25
local width, height = m.getSize()
-- Clear and prepare monitor
m.clear()
m.setTextScale(1)
-- Header line
m.setCursorPos(x_padding + 8, 1)
m.setTextColor(colors.white)
m.write("* ")
m.setTextColor(colors.red)
m.write("Lag4President")
m.setTextColor(colors.white)
m.write(" - ")
m.setTextColor(colors.purple)
m.write("Draconic Reactor")
m.setTextColor(colors.white)
m.write(" *")
-- Extract data
local status = info["status"]
local temp = info["temperature"]
local energyOut = info["generationRate"] / 1000 -- Convert to kRF/t
local shieldIn = info["fieldDrainRate"] / 1000 -- Convert to kRF/t
local netEnergy = energyOut - shieldIn
local fieldStrength = (info["fieldStrength"] / info["maxFieldStrength"]) * 100
local saturation = (info["energySaturation"] / info["maxEnergySaturation"]) * 100
-- Check for fuel-related data
local fuelConversion = info["fuelConversion"] or 0
local maxFuelConversion = info["maxFuelConversion"] or 1
local fuelConversionRate = info["fuelConversionRate"] or 0
-- Calculate fuel percentage if available
local fuelPercentage = 0
if maxFuelConversion > 0 then
fuelPercentage = (fuelConversion / maxFuelConversion) * 100
end
local row = y_padding + 1 -- Start below header
-- Status (no progress bar)
m.setCursorPos(x_padding, row)
m.setTextColor(colors.white)
m.write("Reactor Status:")
m.setCursorPos(value_align, row)
if status == "running" or status == "online" then
m.setTextColor(colors.lime)
m.write("Running")
elseif status == "cooling" then
m.setTextColor(colors.yellow)
m.write("Shutting Down...")
elseif status == "cold" then
m.setTextColor(colors.red)
m.write("Offline / Powered off")
elseif status == "charging" then
m.setTextColor(colors.yellow)
m.write("Charging")
elseif status == "charged" then
m.setTextColor(colors.lime)
m.write("Charged - Ready for Activation")
else
m.setTextColor(colors.gray)
m.write("Unknown State")
end
row = row + 2
-- Temperature progress bar (0-8000, with minimum indicator at 2000)
m.setCursorPos(x_padding, row)
m.setTextColor(colors.white)
m.write("Temperature:")
drawProgressBar(value_align, row, temp, 0, 8000, 7500, "C", 15, nil, 2000)
row = row + 2
-- Energy Output progress bar with efficiency scaling
local base_output = 4000
local efficiency_multiplier = 1 + (fuelPercentage / 100) * 1.5
local expected_output = base_output * efficiency_multiplier
local max_output = base_output * 2.5
m.setCursorPos(x_padding, row)
m.setTextColor(colors.white)
m.write("Energy Output:")
drawProgressBar(value_align, row, energyOut, 0, max_output, expected_output, "kRF/t", 15)
row = row + 2
-- Field Input progress bar (0-2000, target 1400)
m.setCursorPos(x_padding, row)
m.setTextColor(colors.white)
m.write("Field Input:")
drawProgressBar(value_align, row, shieldIn, 0, 2000, 1400, "kRF/t", 15)
row = row + 2
-- Net Energy (just value with color grading)
m.setCursorPos(x_padding, row)
m.setTextColor(colors.white)
m.write("Net Energy:")
m.setCursorPos(value_align, row)
if netEnergy < 0 then
m.setTextColor(colors.red)
elseif netEnergy < 1000 then
m.setTextColor(colors.yellow)
else
m.setTextColor(colors.lime)
end
m.write(string.format("%.2f kRF/t", netEnergy))
row = row + 2
-- Field Strength progress bar (0-100, target 15)
m.setCursorPos(x_padding, row)
m.setTextColor(colors.white)
m.write("Field Strength:")
drawProgressBar(value_align, row, fieldStrength, 0, 100, 25, "%", 15)
row = row + 2
-- Energy Saturation progress bar (0-100, target 15)
m.setCursorPos(x_padding, row)
m.setTextColor(colors.white)
m.write("Energy Saturation:")
drawProgressBar(value_align, row, saturation, 0, 100, 15, "%", 15)
row = row + 2
-- Fuel Conversion progress bar (0-100) - white color
m.setCursorPos(x_padding, row)
m.setTextColor(colors.white)
m.write("Fuel Progress:")
drawProgressBar(value_align, row, fuelPercentage, 0, 100, nil, "%", 15, colors.white)
row = row + 2
-- Fuel Rate progress bar (0-5)
m.setCursorPos(x_padding, row)
m.setTextColor(colors.white)
m.write("Fuel Rate:")
drawProgressBar(value_align, row, fuelConversionRate / 1000, 0, 5, nil, "nb/t", 15)
-- Bottom warning notes
m.setCursorPos(x_padding + 5, height - 3)
m.setTextColor(colors.red)
m.write("NOTE:")
m.setTextColor(colors.orange)
m.write(" NEVER exceed 8000 C temperature!")
m.setCursorPos(x_padding + 11, height - 2)
m.write("NEVER let shield drop below 10%!")
m.setCursorPos(x_padding + 11, height - 1)
m.write("But making it close is quite rewarding :)")
end
-- Function to show connection error
local function showConnectionError()
m.clear()
m.setTextScale(1)
local width, height = m.getSize()
local y_center = math.floor(height / 2)
m.setCursorPos(1, y_center - 2)
m.setTextColor(colors.red)
m.write("CONNECTION ERROR")
m.setCursorPos(1, y_center)
m.setTextColor(colors.white)
m.write("No data received from server")
m.setCursorPos(1, y_center + 1)
m.write("Waiting for reactor server...")
m.setCursorPos(1, y_center + 3)
m.setTextColor(colors.gray)
m.write("Protocol: " .. PROTOCOL)
end
-- Main program
print("Reactor Info Client Started")
print("Listening for protocol: " .. PROTOCOL)
print("Press Ctrl+T to stop")
-- Show startup animation
showStartupAnimation()
while true do
-- Listen for reactor data
local sender, message, protocol = rednet.receive(PROTOCOL, TIMEOUT)
if sender and message and protocol == PROTOCOL then
-- Display the received reactor data
displayReactorData(message)
-- Debug output to terminal
term.clear()
term.setCursorPos(1, 1)
print("=== REACTOR CLIENT STATUS ===")
print("Connected to server ID: " .. sender)
print("Last update: " .. os.time())
print("Reactor status: " .. tostring(message.status))
print("")
print("Displaying data on monitor...")
else
-- No data received within timeout
showConnectionError()
-- Debug output to terminal
term.clear()
term.setCursorPos(1, 1)
print("=== REACTOR CLIENT ERROR ===")
print("No data received within " .. TIMEOUT .. " seconds")
print("Waiting for server...")
end
end