CCCPaste
-- rnc2.lua  (CC:T API version – no module/table tricks)
local BASE_PORT = 39000
local MODEMS = { peripheral.find("modem") }
if #MODEMS == 0 then
  error("rnc2: no modems found")
end

-- open base port
for _, m in ipairs(MODEMS) do
  m.open(BASE_PORT)
end

local function shortHash(s)
  local h = 0
  for i = 1, #s do
    h = (h + s:byte(i)) % 1000
  end
  return h
end

local function progPort(prog)
  return BASE_PORT + shortHash(prog)
end

local function openProg(prog)
  local p = progPort(prog)
  for _, m in ipairs(MODEMS) do
    if not m.isOpen(p) then m.open(p) end
  end
  return p
end

-- ✅ these are the PUBLIC ones CC will expose as rnc2.getId(...)
function getId(prog)
  return ("%s@%d"):format(prog, os.getComputerID())
end

function getPort(prog)
  return progPort(prog)
end

function send(prog, target, body)
  local port = openProg(prog)
  local pkt = {
    prog = prog,
    from = getId(prog),
    to   = target or "*",
    t    = os.epoch("utc"),
    body = body
  }
  local data = textutils.serialize(pkt)
  for _, m in ipairs(MODEMS) do
    m.transmit(port, port, data)
  end
end

function broadcast(prog, body)
  return send(prog, "*", body)
end

function recv(prog, timeout)
  local port = openProg(prog)
  local timerId
  if timeout and timeout > 0 then
    timerId = os.startTimer(timeout)
  end
  while true do
    local ev, p1, p2, p3, p4 = os.pullEvent()
    if ev == "modem_message" and p2 == port then
      local ok, pkt = pcall(textutils.unserialize, p4)
      if ok and type(pkt) == "table" then
        local me = getId(prog)
        if (not pkt.to) or pkt.to == "*" or pkt.to == me then
          return pkt
        end
      end
    elseif ev == "timer" and p1 == timerId then
      return nil
    end
  end
end

function autoPong(prog, pkt)
  if pkt.body and pkt.body.type == "ping" then
    send(prog, pkt.from, { type = "pong" })
    return true
  end
  return false
end