CCCPaste

reactorInfoClient

-- 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