-- nanomachine_client.lua
local component = require("component")
local event = require("event")
local term = require("term")
local gpu = component.gpu
local keyboard = require("keyboard")
-- Config
local SERVER_ADDR = nil
local PORT = 2000
local modem = component.modem
-- UI State
local currentScreen = "main"
local selectedProfile = nil
local scrollOffset = 0
local statusData = nil
local profiles = {}
local manualInputs = {}
-- Colors
local COLOR_BG = 0x000000
local COLOR_FG = 0xFFFFFF
local COLOR_HEADER = 0x00FFFF
local COLOR_SUCCESS = 0x00FF00
local COLOR_WARNING = 0xFFFF00
local COLOR_ERROR = 0xFF0000
local COLOR_ACTIVE = 0x00FF00
local COLOR_INACTIVE = 0x888888
local COLOR_BUTTON = 0x0066CC
local COLOR_SELECTED = 0xFF6600
-- Setup
modem.open(PORT)
gpu.setResolution(80, 25)
-- Server Setup
local function setupServer()
term.clear()
gpu.setForeground(COLOR_HEADER)
print("═══ NANOMACHINE CLIENT SETUP ═══")
print("")
gpu.setForeground(COLOR_FG)
print("Enter Server Address:")
print("(or 'broadcast' for auto-discovery)")
print("")
SERVER_ADDR = io.read()
if SERVER_ADDR == "broadcast" then
SERVER_ADDR = nil
print("")
gpu.setForeground(COLOR_SUCCESS)
print("✓ Using broadcast mode")
else
print("")
gpu.setForeground(COLOR_SUCCESS)
print("✓ Server set to: " .. SERVER_ADDR:sub(1, 8) .. "...")
end
gpu.setForeground(COLOR_FG)
print("")
print("Press any key to continue...")
event.pull("key_down")
end
-- Communication
local function sendCommand(command, ...)
if SERVER_ADDR then
modem.send(SERVER_ADDR, PORT, command, ...)
else
modem.broadcast(PORT, command, ...)
end
end
local function requestData(command)
sendCommand(command)
local timeout = os.time() + 2
while os.time() < timeout do
local _, _, sender, _, _, cmd, data = event.pull(0.1, "modem_message")
if cmd and cmd == command .. "_RESPONSE" then
if not SERVER_ADDR then
SERVER_ADDR = sender
end
return data
end
end
return nil
end
-- Draw Functions
local function drawHeader(title)
gpu.setBackground(COLOR_BG)
gpu.setForeground(COLOR_HEADER)
gpu.fill(1, 1, 80, 3, " ")
local titleText = "╔═══ " .. title .. " ═══╗"
gpu.set(math.floor((80 - #titleText) / 2), 2, titleText)
end
local function drawButton(x, y, text, width, active)
local bg = active and COLOR_SELECTED or COLOR_BUTTON
gpu.setBackground(bg)
gpu.setForeground(COLOR_FG)
local padding = math.floor((width - #text) / 2)
local buttonText = string.rep(" ", padding) .. text .. string.rep(" ", width - padding - #text)
gpu.set(x, y, buttonText)
gpu.setBackground(COLOR_BG)
end
local function drawStatusBar(y, label, value, maxValue, color)
gpu.setForeground(COLOR_FG)
gpu.set(2, y, label .. ":")
local barWidth = 50
local barX = 20
local percentage = maxValue > 0 and (value / maxValue) or 0
local filled = math.floor(barWidth * percentage)
gpu.setForeground(color)
gpu.set(barX, y, "[" .. string.rep("█", filled) .. string.rep("░", barWidth - filled) .. "]")
gpu.setForeground(COLOR_FG)
gpu.set(barX + barWidth + 3, y, string.format("%d/%d (%.1f%%)", value, maxValue, percentage * 100))
end
-- Main Dashboard Screen
local function drawMainScreen()
term.clear()
drawHeader("NANOMACHINE CONTROL CENTER")
gpu.setForeground(COLOR_FG)
if not statusData then
gpu.setForeground(COLOR_ERROR)
gpu.set(2, 5, "⚠ No connection to server")
gpu.setForeground(COLOR_WARNING)
gpu.set(2, 7, "Attempting to reconnect...")
else
-- Status Section
gpu.setForeground(COLOR_HEADER)
gpu.set(2, 5, "═══ SYSTEM STATUS ═══")
gpu.setForeground(COLOR_FG)
drawStatusBar(7, "Power", statusData.power or 0, statusData.maxPower or 100, COLOR_SUCCESS)
drawStatusBar(8, "Health", statusData.health or 0, statusData.maxHealth or 20, COLOR_ERROR)
drawStatusBar(9, "Hunger", statusData.hunger or 0, 20, COLOR_WARNING)
gpu.set(2, 11, "Active Inputs: " .. (statusData.activeInputs or 0) .. "/" .. (statusData.totalInputs or 0))
gpu.set(2, 12, "Active Effects: " .. (statusData.activeEffects or 0))
-- Current Profile
gpu.setForeground(COLOR_HEADER)
gpu.set(2, 14, "═══ CURRENT PROFILE ═══")
gpu.setForeground(COLOR_FG)
if statusData.currentProfile then
gpu.setForeground(COLOR_SUCCESS)
gpu.set(2, 15, "● " .. statusData.currentProfile)
else
gpu.setForeground(COLOR_INACTIVE)
gpu.set(2, 15, "○ No profile active")
end
-- Auto-Protection Status
gpu.setForeground(COLOR_HEADER)
gpu.set(45, 14, "═══ AUTO-PROTECT ═══")
gpu.setForeground(COLOR_FG)
local autoProtect = statusData.autoProtect or false
if autoProtect then
gpu.setForeground(COLOR_SUCCESS)
gpu.set(45, 15, "● ENABLED")
else
gpu.setForeground(COLOR_INACTIVE)
gpu.set(45, 15, "○ DISABLED")
end
end
-- Navigation Buttons
gpu.setForeground(COLOR_HEADER)
gpu.set(2, 18, "═══ QUICK ACTIONS ═══")
drawButton(2, 20, "1: Profiles", 15, false)
drawButton(19, 20, "2: Manual", 15, false)
drawButton(36, 20, "3: Discovery", 15, false)
drawButton(53, 20, "4: Settings", 15, false)
drawButton(2, 22, "E: Emergency Stop", 20, false)
drawButton(24, 22, "A: Toggle Auto", 20, false)
drawButton(46, 22, "R: Refresh", 20, false)
-- Footer
gpu.setForeground(COLOR_INACTIVE)
gpu.set(2, 24, "Q: Quit")
gpu.set(70, 24, "v1.0")
end
-- Profile Selection Screen
local function drawProfileScreen()
term.clear()
drawHeader("PROFILE MANAGER")
gpu.setForeground(COLOR_FG)
if not profiles or #profiles == 0 then
gpu.setForeground(COLOR_WARNING)
gpu.set(2, 5, "No profiles available")
gpu.setForeground(COLOR_FG)
gpu.set(2, 7, "Press B to go back")
return
end
gpu.setForeground(COLOR_HEADER)
gpu.set(2, 5, "═══ AVAILABLE PROFILES ═══")
gpu.setForeground(COLOR_FG)
for i, profile in ipairs(profiles) do
if i > scrollOffset and i <= scrollOffset + 10 then
local y = 7 + (i - scrollOffset - 1)
local isActive = statusData and statusData.currentProfile == profile.name
if isActive then
gpu.setForeground(COLOR_SUCCESS)
gpu.set(2, y, "▶ ")
else
gpu.setForeground(COLOR_INACTIVE)
gpu.set(2, y, " ")
end
gpu.setForeground(COLOR_FG)
gpu.set(5, y, string.format("[%d] %s", i, profile.name))
if profile.description then
gpu.setForeground(COLOR_INACTIVE)
gpu.set(30, y, "- " .. profile.description)
end
end
end
-- Instructions
gpu.setForeground(COLOR_HEADER)
gpu.set(2, 20, "═══ CONTROLS ═══")
gpu.setForeground(COLOR_FG)
gpu.set(2, 21, "Number: Activate profile")
gpu.set(2, 22, "C: Create new profile")
gpu.set(2, 23, "B: Back to main menu")
end
-- Manual Control Screen
local function drawManualScreen()
term.clear()
drawHeader("MANUAL INPUT CONTROL")
gpu.setForeground(COLOR_FG)
if not manualInputs or #manualInputs == 0 then
gpu.setForeground(COLOR_WARNING)
gpu.set(2, 5, "No input data available")
gpu.setForeground(COLOR_FG)
gpu.set(2, 7, "Press B to go back")
return
end
gpu.setForeground(COLOR_HEADER)
gpu.set(2, 5, "═══ INPUT CHANNELS ═══")
gpu.setForeground(COLOR_FG)
local cols = 4
local row = 0
for i = 1, math.min(#manualInputs, 32) do
local input = manualInputs[i]
local col = (i - 1) % cols
local x = 2 + (col * 19)
local y = 7 + row
if col == 0 and i > 1 then
row = row + 1
end
local status = input.active and "ON " or "OFF"
local color = input.active and COLOR_SUCCESS or COLOR_INACTIVE
gpu.setForeground(color)
gpu.set(x, y, string.format("[%2d] %s", i, status))
end
-- Instructions
gpu.setForeground(COLOR_HEADER)
gpu.set(2, 20, "═══ CONTROLS ═══")
gpu.setForeground(COLOR_FG)
gpu.set(2, 21, "Number: Toggle input")
gpu.set(2, 22, "X: Disable all")
gpu.set(2, 23, "B: Back to main menu")
end
-- Discovery Screen
local function drawDiscoveryScreen()
term.clear()
drawHeader("EFFECT DISCOVERY")
gpu.setForeground(COLOR_FG)
gpu.setForeground(COLOR_HEADER)
gpu.set(2, 5, "═══ DISCOVERY MODE ═══")
gpu.setForeground(COLOR_FG)
if statusData and statusData.discoveryActive then
gpu.setForeground(COLOR_SUCCESS)
gpu.set(2, 7, "● Discovery in progress...")
gpu.setForeground(COLOR_FG)
gpu.set(2, 9, "Progress: " .. (statusData.discoveryProgress or 0) .. "%")
gpu.set(2, 10, "Tested: " .. (statusData.discoveryTested or 0) .. " combinations")
gpu.set(2, 11, "Found: " .. (statusData.discoveryFound or 0) .. " effects")
else
gpu.setForeground(COLOR_INACTIVE)
gpu.set(2, 7, "○ Discovery not running")
end
gpu.setForeground(COLOR_HEADER)
gpu.set(2, 14, "═══ CONTROLS ═══")
gpu.setForeground(COLOR_FG)
gpu.set(2, 15, "S: Start discovery")
gpu.set(2, 16, "P: Pause discovery")
gpu.set(2, 17, "R: Reset and restart")
gpu.set(2, 18, "B: Back to main menu")
gpu.setForeground(COLOR_WARNING)
gpu.set(2, 20, "⚠ Warning: Discovery will systematically test all")
gpu.set(2, 21, " input combinations. This may take several minutes.")
end
-- Settings Screen
local function drawSettingsScreen()
term.clear()
drawHeader("SETTINGS")
gpu.setForeground(COLOR_HEADER)
gpu.set(2, 5, "═══ AUTO-PROTECTION ═══")
gpu.setForeground(COLOR_FG)
local autoProtect = statusData and statusData.autoProtect or false
gpu.set(2, 7, "Status: " .. (autoProtect and "ENABLED" or "DISABLED"))
gpu.set(2, 8, "Health Threshold: " .. (statusData and statusData.healthThreshold or 10))
gpu.set(2, 9, "Hunger Threshold: " .. (statusData and statusData.hungerThreshold or 6))
gpu.setForeground(COLOR_HEADER)
gpu.set(2, 12, "═══ CONTROLS ═══")
gpu.setForeground(COLOR_FG)
gpu.set(2, 13, "A: Toggle auto-protection")
gpu.set(2, 14, "H: Set health threshold")
gpu.set(2, 15, "F: Set hunger threshold")
gpu.set(2, 16, "B: Back to main menu")
end
-- Input Handling
local function handleMainInput(char)
if char == string.byte("1") then
currentScreen = "profiles"
profiles = requestData("GET_PROFILES") or {}
elseif char == string.byte("2") then
currentScreen = "manual"
manualInputs = requestData("GET_INPUTS") or {}
elseif char == string.byte("3") then
currentScreen = "discovery"
elseif char == string.byte("4") then
currentScreen = "settings"
elseif char == string.byte("e") or char == string.byte("E") then
sendCommand("EMERGENCY_STOP")
statusData = requestData("GET_STATUS")
elseif char == string.byte("a") or char == string.byte("A") then
sendCommand("TOGGLE_AUTO")
statusData = requestData("GET_STATUS")
elseif char == string.byte("r") or char == string.byte("R") then
statusData = requestData("GET_STATUS")
elseif char == string.byte("q") or char == string.byte("Q") then
return false
end
return true
end
local function handleProfileInput(char)
if char == string.byte("b") or char == string.byte("B") then
currentScreen = "main"
elseif char >= string.byte("0") and char <= string.byte("9") then
local num = char - string.byte("0")
if profiles[num] then
sendCommand("ACTIVATE_PROFILE", profiles[num].name)
statusData = requestData("GET_STATUS")
end
elseif char == string.byte("c") or char == string.byte("C") then
term.clear()
print("Enter profile name:")
local name = io.read()
if name and name ~= "" then
sendCommand("CREATE_PROFILE", name)
profiles = requestData("GET_PROFILES") or {}
end
end
end
local function handleManualInput(char)
if char == string.byte("b") or char == string.byte("B") then
currentScreen = "main"
elseif char == string.byte("x") or char == string.byte("X") then
sendCommand("DISABLE_ALL")
manualInputs = requestData("GET_INPUTS") or {}
elseif char >= string.byte("0") and char <= string.byte("9") then
local num = char - string.byte("0")
sendCommand("TOGGLE_INPUT", num)
manualInputs = requestData("GET_INPUTS") or {}
end
end
local function handleDiscoveryInput(char)
if char == string.byte("b") or char == string.byte("B") then
currentScreen = "main"
elseif char == string.byte("s") or char == string.byte("S") then
sendCommand("START_DISCOVERY")
statusData = requestData("GET_STATUS")
elseif char == string.byte("p") or char == string.byte("P") then
sendCommand("PAUSE_DISCOVERY")
statusData = requestData("GET_STATUS")
elseif char == string.byte("r") or char == string.byte("R") then
sendCommand("RESET_DISCOVERY")
statusData = requestData("GET_STATUS")
end
end
local function handleSettingsInput(char)
if char == string.byte("b") or char == string.byte("B") then
currentScreen = "main"
elseif char == string.byte("a") or char == string.byte("A") then
sendCommand("TOGGLE_AUTO")
statusData = requestData("GET_STATUS")
elseif char == string.byte("h") or char == string.byte("H") then
term.clear()
print("Enter health threshold (1-20):")
local threshold = tonumber(io.read())
if threshold then
sendCommand("SET_HEALTH_THRESHOLD", threshold)
statusData = requestData("GET_STATUS")
end
elseif char == string.byte("f") or char == string.byte("F") then
term.clear()
print("Enter hunger threshold (1-20):")
local threshold = tonumber(io.read())
if threshold then
sendCommand("SET_HUNGER_THRESHOLD", threshold)
statusData = requestData("GET_STATUS")
end
end
end
-- Main Loop
local function main()
setupServer()
local running = true
local lastUpdate = 0
while running do
local currentTime = os.time()
-- Auto-refresh every 2 seconds
if currentTime - lastUpdate >= 2 then
statusData = requestData("GET_STATUS")
lastUpdate = currentTime
end
-- Draw current screen
if currentScreen == "main" then
drawMainScreen()
elseif currentScreen == "profiles" then
drawProfileScreen()
elseif currentScreen == "manual" then
drawManualScreen()
elseif currentScreen == "discovery" then
drawDiscoveryScreen()
elseif currentScreen == "settings" then
drawSettingsScreen()
end
-- Handle input
local _, _, char, code = event.pull(0.5, "key_down")
if char then
if currentScreen == "main" then
running = handleMainInput(char)
elseif currentScreen == "profiles" then
handleProfileInput(char)
elseif currentScreen == "manual" then
handleManualInput(char)
elseif currentScreen == "discovery" then
handleDiscoveryInput(char)
elseif currentScreen == "settings" then
handleSettingsInput(char)
end
end
end
term.clear()
print("Nanomachine Client terminated.")
end
-- Error handling
local success, err = xpcall(main, debug.traceback)
if not success then
term.clear()
gpu.setForeground(COLOR_ERROR)
print("ERROR: " .. tostring(err))
gpu.setForeground(COLOR_FG)
end