CCCPaste

reactor_alert

-- reactor_alert_v2.lua
-- Alarma para reactor: detecta redstone por abajo y muestra alerta parpadeante en monitor top
-- y emite sonido por el speaker en back (o usa fallback de redstone si no hay método disponible).

local inputSide   = "bottom" -- señal de entrada
local monitorSide = "top"    -- monitor para la alerta
local speakerSide = "back"   -- speaker (periférico) o lado donde encender redstone como fallback

-- parámetros
local blinkInterval = 0.5    -- intervalo de parpadeo (s)
local soundInterval = 0.12   -- intervalo entre reproducciones de sonido (s)

local bgOn1 = colors.red
local bgOn2 = colors.orange or colors.red -- si orange no existe, red se usa (compatibilidad)
local bgOff = colors.black
local textColor = colors.white

-- Helpers de periféricos
local function hasPeripheral(side) return peripheral.isPresent(side) end
local function wrapPeripheral(side)
  if peripheral.isPresent(side) then
    return peripheral.wrap(side)
  end
  return nil
end

-- Dibuja el mensaje centrado en el monitor con fondo y texto dados
local function drawMessageOnMonitor(mon, w, h, text, bg)
  mon.setBackgroundColor(bg)
  mon.setTextColor(textColor)
  mon.clear()
  local x = math.max(1, math.floor((w - #text) / 2) + 1)
  local y = math.max(1, math.floor(h / 2))
  mon.setCursorPos(x, y)
  mon.write(text)
end

-- Limpia monitor (restaurar fondo negro y borrar)
local function clearMonitor(mon)
  mon.setBackgroundColor(bgOff)
  mon.clear()
  mon.setCursorPos(1,1)
end

-- Crea un "player" de sonido robusto:
-- intenta detectar métodos en el periférico, si no, devuelve nil y usaremos fallback redstone.
local function makeSoundPlayer()
  local p = wrapPeripheral(speakerSide)
  if p then
    -- averiguamos métodos disponibles
    local methods = {}
    for k,_ in pairs(p) do table.insert(methods, k) end
    -- métodos comunes que intentaremos usar (orden de preferencia)
    local tryList = {
      -- nombre => function(args...) que realiza una llamada segura
      {name="playSound", call=function(...) return p.playSound(...) end},
      {name="play", call=function(...) return p.play(...) end},
      {name="playNote", call=function(...) return p.playNote(...) end},
      {name="playTone", call=function(...) return p.playTone(...) end},
      {name="beep", call=function(...) return p.beep(...) end},
      {name="note", call=function(...) return p.note(...) end},
    }

    for _, t in ipairs(tryList) do
      if type(p[t.name]) == "function" then
        -- probamos una llamada segura pcall para confirmar que funciona
        local ok = pcall(p[t.name], p, "harp", 1) -- intento genérico; si falla no pasa nada
        if ok then
          -- devolvemos una función que reproducirá sonido periódicamente mientras shouldRun() sea true
          local name = t.name
          return function(shouldRun)
            while shouldRun() do
              pcall(p[name], p, "harp", 1) -- usamos parámetros genéricos; algunos métodos los ignorarán
              sleep(soundInterval)
            end
          end
        end
      end
    end

    -- si no encontramos un método estándar pero el peripheral tiene 'playNote', usarlo
    if type(p.playNote) == "function" then
      return function(shouldRun)
        while shouldRun() do
          pcall(p.playNote, p, "harp", 1)
          sleep(soundInterval)
        end
      end
    end
  end

  -- No hay periférico con método válido -> fallback con redstone toggle en speakerSide
  return nil
end

-- Funcion que activa/desactiva la señal redstone de fallback de sonido
local function setSpeakerRedstone(state)
  pcall(redstone.setOutput, speakerSide, state)
end

-- Rutina de parpadeo del monitor: retorna la función que detiene el parpadeo (stopFunc)
local function startMonitorBlink(text)
  if not hasPeripheral(monitorSide) then
    -- fallback: escribir en terminal local
    local function stop()
      term.setBackgroundColor(bgOff)
      term.clear()
      term.setCursorPos(1,1)
    end
    -- parpadeo en terminal: alternar fondo
    local running = true
    local co = coroutine.create(function()
      while running do
        term.setBackgroundColor(bgOn1)
        term.clear()
        term.setCursorPos(1,1)
        print(text)
        sleep(blinkInterval)
        term.setBackgroundColor(bgOff)
        term.clear()
        sleep(blinkInterval)
      end
    end)
    coroutine.resume(co)
    return function() running = false; coroutine.resume(co); stop() end
  end

  local mon = peripheral.wrap(monitorSide)
  local w,h = mon.getSize()
  local running = true
  local co = coroutine.create(function()
    local toggle = true
    while running do
      if toggle then
        drawMessageOnMonitor(mon, w, h, text, bgOn1)
      else
        drawMessageOnMonitor(mon, w, h, text, bgOn2)
      end
      toggle = not toggle
      -- dormir en trozos para permitir salida rápida si se para
      local slept = 0
      while slept < blinkInterval and running do
        sleep(0.05)
        slept = slept + 0.05
      end
    end
    clearMonitor(mon)
  end)
  coroutine.resume(co)
  return function() running = false; coroutine.resume(co) end
end

-- MAIN: monitorizamos la entrada y activamos/desactivamos alarma
local function main()
  -- Texto de alerta sin signos de apertura, solamente cierre de exclamación si quieres:
  local alertText = "ALERTA REACTOR !!!"  -- solo signos de cierre

  local soundPlayer = makeSoundPlayer()
  local alarmOn = false
  local stopMonitorFunc = nil
  local soundThread = nil
  local soundRunning = false

  print("Alarma lista. Esperando señal en " .. inputSide .. " ...")

  while true do
    -- chequeo simple del estado actual
    local sig = redstone.getInput(inputSide)
    if sig and not alarmOn then
      -- activar alarma
      alarmOn = true
      print("Señal detectada -> ACTIVANDO alarma.")
      -- lanzar parpadeo en monitor
      stopMonitorFunc = startMonitorBlink(alertText)

      -- iniciar sonido
      if soundPlayer then
        soundRunning = true
        soundThread = parallel.waitForAny(function()
          soundPlayer(function() return soundRunning end)
        end, function() while soundRunning do sleep(1) end end)
        -- note: the above construction keeps it non-blocking by running in parallel later controlled via soundRunning
        -- but we'll instead run sound in its own coroutine for better control:
        soundRunning = true
        local co = coroutine.create(function()
          soundPlayer(function() return soundRunning end)
        end)
        coroutine.resume(co)
        soundThread = co
      else
        -- fallback: toggle redstone rapidly to simulate continuous sound
        soundRunning = true
        local co2 = coroutine.create(function()
          while soundRunning do
            setSpeakerRedstone(true)
            sleep(soundInterval)
            setSpeakerRedstone(false)
            sleep(0.02)
          end
          setSpeakerRedstone(false)
        end)
        coroutine.resume(co2)
        soundThread = co2
      end

    elseif not sig and alarmOn then
      -- desactivar alarma
      alarmOn = false
      print("Señal perdida -> DESACTIVANDO alarma.")
      -- parar sonido
      soundRunning = false
      setSpeakerRedstone(false)
      -- parar monitor
      if stopMonitorFunc then
        stopMonitorFunc()
        stopMonitorFunc = nil
      end
    end

    -- dormir un poco y esperar siguiente cambio
    -- usamos pullEvent con timeout para reaccionar rápido pero seguir el lazo
    local ev = { os.pullEventTimeout("redstone", 1) } -- espera redstone o timeout 1s
    -- si evento es nil (timeout) seguimos el lazo y reevaluamos el estado
  end
end

-- Ejecutar
main()