CCCPaste

local_agent

-- local_agent.lua
-- Run this on a computer adjacent to a reactor or brine tower.
-- Listens on a modem (wired/wireless/ender) for simple commands and performs local peripheral or redstone control.

local config = {
  auth_token = nil, -- optional token required as first word of incoming messages
  modem_side = nil, -- optional side/name to open rednet on; nil = auto-detect
  log_file = "agent_log.txt",
}

local function log(msg)
  local line = string.format("[%s] %s", os.date("%Y-%m-%d %H:%M:%S"), tostring(msg))
  print(line)
  local ok, f = pcall(fs.open, config.log_file, "a")
  if ok and f then f.writeLine(line); f.close() end
end

-- Return the peripheral name and type for the first modem-like peripheral found
local function find_modem_peripheral()
  for _,n in ipairs(peripheral.getNames()) do
    local ok, t = pcall(peripheral.getType, n)
    if ok and t then
      local tl = tostring(t):lower()
      if tl == "modem" or tl == "wireless_modem" or tl:find("ender") or tl:find("ender_modem") then
        return n, t
      end
    end
  end
  return nil, nil
end

local function scan_peripherals()
  local list = {}
  for _,n in ipairs(peripheral.getNames()) do table.insert(list, n) end
  return list
end

local function find_target(name)
  -- exact name first
  for _,n in ipairs(peripheral.getNames()) do if n == name then return n end end
  -- substring match
  for _,n in ipairs(peripheral.getNames()) do if n:lower():find(name:lower(),1,true) then return n end end
  return nil
end

local function try_call(side, method, ...)
  local ok, res = pcall(peripheral.call, side, method, ...)
  return ok, res
end

-- open rednet
local modem = config.modem_side
if not modem then
  local mside, mtype = find_modem_peripheral()
  if mside then
    modem = mside
    log("Detected modem peripheral: '" .. tostring(mside) .. "' (type: " .. tostring(mtype) .. ")")
    log("You can open rednet manually with: rednet.open(\"" .. tostring(mside) .. "\")")
  else
    log("No modem peripheral detected automatically.")
    log("If you have a wireless modem, attach it to this computer and set config.modem_side or run: rednet.open('')")
  end
else
  log("Using configured modem side/name: " .. tostring(modem))
  log("To open manually: rednet.open(\"" .. tostring(modem) .. "\")")
end

if modem then
  local ok, err = pcall(rednet.open, modem)
  if not ok then
    log("Failed to open rednet on " .. tostring(modem) .. " -> " .. tostring(err))
    log("If automatic open fails, try running: rednet.open('') in the REPL where  is the modem name shown by peripheral.getNames()")
  else
    log("Rednet opened on " .. tostring(modem))
  end
else
  log("Rednet not opened; agent will still run but won't receive network commands.")
end

log("Agent online")

local function handle_message(id, msg)
  if not id or not msg then return end
  local raw = tostring(msg)
  local parts = {}
  for w in raw:gmatch("%S+") do table.insert(parts, w) end
  local idx = 1
  if config.auth_token then
    if #parts == 0 or parts[1] ~= config.auth_token then
      if rednet then rednet.send(id, "AUTH_FAIL") end
      log("Rejected message from " .. tostring(id) .. " (bad token)")
      return
    end
    idx = 2
  end
  local cmd = parts[idx] and parts[idx]:upper() or ""
  local target = parts[idx+1]

  if cmd == "STATUS" then
    -- reply with list of peripherals
    local per = table.concat(scan_peripherals(), ",")
    if rednet then rednet.send(id, "OK PERIPHERALS " .. per) end
    log("STATUS -> replied to " .. tostring(id))
    return
  end

  if (cmd == "START" or cmd == "STOP" or cmd == "TOGGLE") and target then
    local tname = find_target(target)
    if not tname then
      if rednet then rednet.send(id, "NOTFOUND") end
      log("Target not found: " .. tostring(target))
      return
    end
    local desired
    if cmd == "START" then desired = true elseif cmd == "STOP" then desired = false end
    if cmd == "TOGGLE" then
      local active
      for _,m in ipairs({"isActive","getActive","isRunning","getPumpState","getValveState"}) do
        local ok, res = try_call(tname, m)
        if ok then active = res; break end
      end
      if active == nil then
        if rednet then rednet.send(id, "CANNOT_READ") end
        log("Cannot read state of " .. tname)
        return
      end
      desired = not active
    end

    local set_ok = false
    local methods = peripheral.getMethods(tname) or {}
    for _,m in ipairs({"setActive","setRunning","setPump","setValve","activate","deactivate","enable","disable"}) do
      for _,mm in ipairs(methods) do
        if mm == m then
          local ok, res = try_call(tname, m, desired)
          if ok then set_ok = true; break end
        end
      end
      if set_ok then break end
    end
    if not set_ok then
      for _,side in ipairs({"left","right","front","back","top","bottom"}) do
        pcall(redstone.setOutput, side, desired)
      end
      set_ok = true
    end
    if rednet then rednet.send(id, set_ok and "OK" or "FAIL") end
    log(string.format("%s %s -> %s", cmd, tostring(tname), tostring(set_ok)))
    return
  end

  if rednet then rednet.send(id, "UNKNOWN_CMD") end
  log("Unknown command: " .. tostring(raw))
end

while true do
  local id, msg = nil, nil
  if rednet then id, msg = rednet.receive(1) end
  if id and msg then
    handle_message(id, msg)
  end
  os.sleep(0.1)
end