OverInteractive        = OverInteractive or {}
OverInteractive.Actions = OverInteractive.Actions or {}

-- ── Utilidades ───────────────────────────────────────────────────────────────

local function pickPhrase(phrases, viewer)
    local p = phrases[ZombRand(1, #phrases + 1)] or phrases[1]
    -- Los paréntesis truncan a UN valor: string.gsub devuelve (texto, nReemplazos)
    -- y sin ellos el contador viaja como segundo argumento hasta player:Say(),
    -- que no tiene sobrecarga (String, Double) y revienta.
    return (string.gsub(p, "{viewer}", viewer or "El Chat"))
end

-- Build 42 reemplazó los setters de Stats (setPain/setHunger/...) por un mapa
-- CharacterStat -> float. Los rangos varían por stat (0..1 para hambre/sed,
-- 0..100 para dolor/intoxicación), así que se trabaja en proporción del máximo.
-- Máscara de sincronización de stats. SyncPlayerStatsPacket.write recorre
-- CharacterStat.ORDERED_STATS acotado por su longitud y solo escribe las stats con
-- su bit encendido, así que pasar bits de más es inofensivo: evita mantener un mapa
-- stat→bit que se rompería con cada actualización de PZ.
local SYNC_ALL_STATS = 0x7FFFFFFF

local function setStat(player, stat, ratio)
    player:getStats():set(stat, stat:getMinimumValue()
        + (stat:getMaximumValue() - stat:getMinimumValue()) * ratio)
    -- Las stats son autoritativas del SERVIDOR: se cambian ahí y se empujan al
    -- cliente. Hacerlo al revés (escribir en el cliente) no dura: el servidor le
    -- devuelve su copia y el efecto se revierte al instante. Mismo patrón que
    -- server/Farming/SFarmingSystem.lua:changePlayer.
    if isServer() then
        syncPlayerStats(player, SYNC_ALL_STATS)
    end
end

-- ── Ámbito de cada comando ───────────────────────────────────────────────────

-- Qué lado ejecuta cada comando en una partida hosteada. PZ no tiene una regla
-- única: la autoridad depende del subsistema, y ambos casos están verificados en
-- vivo.
--
--   SERVIDOR (esta lista):
--     · mundo (zombies, clima, servicios) — obvio, el mundo es suyo.
--     · stats — son server-autoritativas: se cambian acá y se empujan al cliente
--       con syncPlayerStats. Escribirlas en el cliente no dura ni un tick.
--
--   CLIENTE del jugador afectado (todo lo demás):
--     · inventario — es client-autoritativo; lo que escriba el servidor se pierde.
--     · body damage — mismo patrón que ISHealthPanel.onCheatHealthCommand.
OverInteractive.ServerCodes = {
    -- mundo
    spawn_zombie   = true,
    spawn_horde    = true,
    spawn_sprinter = true,
    trigger_rain   = true,
    turn_power_off = true,
    turn_water_off = true,
    -- stats
    damage_player     = true,
    make_drunk        = true,
    make_hungry       = true,
    make_thirsty      = true,
    make_tired        = true,
    restore_endurance = true,
}

-- ── Objetivos ────────────────────────────────────────────────────────────────

-- Modos de objetivo, elegidos por el streamer desde la app. Solo tienen sentido
-- en partidas hosteadas: en single player siempre hay un único jugador.
--   "all"    → todos los conectados (default)
--   "host"   → solo quien disparó el comando (el streamer)
--   "random" → uno al azar entre los conectados
function OverInteractive.ResolveTargets(mode, sender)
    local list = {}
    local online = getOnlinePlayers()
    if online then
        for i = 0, online:size() - 1 do
            local p = online:get(i)
            if p then table.insert(list, p) end
        end
    end
    -- Sin lista de conectados (single player) el objetivo es siempre el emisor.
    if #list == 0 then
        return sender and { sender } or {}
    end
    if mode == "host" then
        return sender and { sender } or { list[1] }
    elseif mode == "random" then
        return { list[ZombRand(1, #list + 1)] }
    end
    return list
end

-- ── Listas de ítems ──────────────────────────────────────────────────────────

local FOOD_IDS = {
    "Base.Chocolate", "Base.Crisps", "Base.TinnedBeans", "Base.CannedChili",
    "Base.TinnedSoup", "Base.CannedTomato", "Base.CannedPeaches", "Base.Rice",
    "Base.Cheese", "Base.PeanutButter", "Base.Cereal", "Base.GranolaBar",
    "Base.ChickenFried", "Base.TortillaChips", "Base.TVDinner", "Base.Popcorn",
    "Base.Corndog", "Base.Smore", "Base.Crackers", "Base.DehydratedMeatStick",
}

local MELEE_IDS = {
    "Base.BaseballBat", "Base.Crowbar", "Base.Axe", "Base.HandAxe",
    "Base.Katana", "Base.Machete", "Base.KitchenKnife", "Base.HuntingKnife",
    "Base.SpearCrafted", "Base.Golfclub", "Base.Pan", "Base.Hammer",
    "Base.Poolcue", "Base.LeadPipe", "Base.Shovel", "Base.Sledgehammer",
}

local FIREARM_IDS = {
    "Base.Pistol", "Base.Pistol2", "Base.Pistol3",
    "Base.Revolver", "Base.Revolver_Long",
    "Base.Shotgun", "Base.ShotgunSawnoff",
    "Base.HuntingRifle", "Base.VarmintRifle",
    "Base.AssaultRifle", "Base.AssaultRifle2",
}

-- Calibres reales de B42 (campo AmmoType de media/scripts/generated/items/weapon.txt).
local AMMO_MAP = {
    ["Base.Pistol"]         = "Base.Bullets9mm",
    ["Base.Pistol2"]        = "Base.Bullets45",
    ["Base.Pistol3"]        = "Base.Bullets44",
    ["Base.Revolver"]       = "Base.Bullets357",
    ["Base.Revolver_Long"]  = "Base.Bullets44",
    ["Base.Shotgun"]        = "Base.ShotgunShells",
    ["Base.ShotgunSawnoff"] = "Base.ShotgunShells",
    ["Base.HuntingRifle"]   = "Base.308Bullets",
    ["Base.VarmintRifle"]   = "Base.556Bullets",
    ["Base.AssaultRifle"]   = "Base.556Bullets",
    ["Base.AssaultRifle2"]  = "Base.308Bullets",
}

-- En B42 las armas con cargador no se pueden cargar con balas sueltas: hay que
-- entregar también el cargador (campo MagazineType). Los revólveres, las
-- escopetas y los rifles de cerrojo no llevan.
local MAGAZINE_MAP = {
    ["Base.Pistol"]        = "Base.9mmClip",
    ["Base.Pistol2"]       = "Base.45Clip",
    ["Base.Pistol3"]       = "Base.44Clip",
    ["Base.AssaultRifle"]  = "Base.556Clip",
    ["Base.AssaultRifle2"] = "Base.M14Clip",
}

local TOOL_IDS = {
    "Base.Saw", "Base.Hammer", "Base.Screwdriver", "Base.Wrench",
    "Base.Crowbar", "Base.HandDrill", "Base.WoodAxe", "Base.TinOpener",
    "Base.FishingRod", "Base.PetrolCan", "Base.WaterPurificationTablets",
    "Base.MasonsChisel", "Base.WoodenMallet",
}

local function randItem(list)
    return list[ZombRand(1, #list + 1)]
end

-- ── Zombies ──────────────────────────────────────────────────────────────────

-- B42 agregó dos booleanos (crawler, isSitting) a addZombiesInOutfit. La firma
-- completa está en media/lua/client/DebugUIs/ISSpawnHordeUI.lua:
--   (x, y, z, count, outfit, femaleChance, crawler, isFallOnFront, isFakeDead,
--    knockedDown, isInvulnerable, isSitting, health, ...)
-- Ojo: el 6º parámetro es femaleChance (%), NO la vida — la vida es el 13º.
local function spawnZombies(x, y, z, count, sprinter)
    local list = addZombiesInOutfit(
        math.floor(x), math.floor(y), math.floor(z), count,
        nil,    -- outfit
        50,     -- femaleChance
        false,  -- crawler
        false,  -- isFallOnFront
        false,  -- isFakeDead
        false,  -- knockedDown
        false,  -- isInvulnerable
        false,  -- isSitting
        1.0)    -- health
    if not sprinter or not list then return list end
    -- No hay parámetro de velocidad: se marca el walkType y se recalcula
    -- speedType (setWalkType solo escribe el campo, no lo deriva).
    for i = 0, list:size() - 1 do
        local zed = list:get(i)
        if zed then
            zed:setWalkType("sprint")
            zed:setSpeedTypeFromWalkType()
        end
    end
    return list
end

local PHRASES_SPAWN_ZOMBIE = {
    "{viewer} me envio compania... no la queria!",
    "Que es ese ruido?! Gracias {viewer}!",
    "Huelen a muerto. Cortesia de {viewer}!",
    "Hay alguien ahi?... Espero que no.",
    "The Walking Dead temporada 1. Gracias {viewer}!",
}

function OverInteractive.Actions.spawn_zombie(player, args, viewer)
    local dist = math.max(1, tonumber(args and args[1]) or 5)
    local x = player:getX() + ZombRand(-dist, dist)
    local y = player:getY() + ZombRand(-dist, dist)
    spawnZombies(x, y, player:getZ(), 1, false)
    player:Say(pickPhrase(PHRASES_SPAWN_ZOMBIE, viewer))
end

local PHRASES_SPAWN_HORDE = {
    "CORRAN! {viewer} me mando la horda entera!",
    "No me gustan esos sonidos...",
    "Hay mas zombies que gente viva! Gracias {viewer}!",
    "Necesito una salida YA! Culpa de {viewer}!",
    "Esto es como TWD temporada 3 pero peor!",
}

function OverInteractive.Actions.spawn_horde(player, args, viewer)
    local count = math.max(1, math.min(30, tonumber(args and args[1]) or 15))
    -- Radio opcional, como en spawn_zombie/spawn_sprinter. Por defecto 20 tiles,
    -- que es el valor que tenía fijo.
    local radius = math.max(1, math.min(50, tonumber(args and args[2]) or 20))
    local cx, cy, z = player:getX(), player:getY(), player:getZ()
    for _ = 1, count do
        spawnZombies(cx + ZombRand(-radius, radius), cy + ZombRand(-radius, radius), z, 1, false)
    end
    player:Say(pickPhrase(PHRASES_SPAWN_HORDE, viewer))
end

local PHRASES_SPAWN_SPRINTER = {
    "Ese zombie corre mas que yo! Gracias {viewer}!",
    "28 dias despues en tiempo real. Cortesia de {viewer}!",
    "Que le dieron de comer?! Espinacas?!",
    "Hasta la vista baby! Oh espera, el corre rapido...",
    "Eso se mueve demasiado rapido! {viewer} por que?!",
}

function OverInteractive.Actions.spawn_sprinter(player, args, viewer)
    local dist = math.max(1, tonumber(args and args[1]) or 8)
    local x = player:getX() + ZombRand(-dist, dist)
    local y = player:getY() + ZombRand(-dist, dist)
    spawnZombies(x, y, player:getZ(), 1, true)
    player:Say(pickPhrase(PHRASES_SPAWN_SPRINTER, viewer))
end

-- ── Jugador ──────────────────────────────────────────────────────────────────

local PHRASES_HEAL = {
    "{viewer} me salvo la vida! Un angelito!",
    "Siento como si me hubieran bendecido...",
    "Mejor que cualquier doctor del apocalipsis. Gracias {viewer}!",
    "Me siento como nuevo. Mucho amor a {viewer}!",
    "El kit del doctor Strange. Cortesia de {viewer}!",
}

function OverInteractive.Actions.heal(player, args, viewer)
    player:getBodyDamage():RestoreToFullHealth()
    player:Say(pickPhrase(PHRASES_HEAL, viewer))
end

local PHRASES_DAMAGE = {
    "{viewer} me disparo por la espalda! TRAIDOR!",
    "Au! Eso dolio mas que ver Titanic!",
    "Friendly fire! {viewer} tiene mala punter a!",
    "Por que {viewer}? Por queeeee?!",
    "Au! Que fue eso?! {viewer}!",
}

function OverInteractive.Actions.damage_player(player, args, viewer)
    setStat(player, CharacterStat.PAIN, 0.9)
    setStat(player, CharacterStat.ENDURANCE, 0.0)
    player:Say(pickPhrase(PHRASES_DAMAGE, viewer))
end

local PHRASES_DRUNK = {
    "{viewer} me puso algo en el trago!",
    "Todo esta girando... Matrix pero borroso!",
    "Nadie me dijo que habia barra libre en el apocalipsis!",
    "Mis piernas ya no me obedecen. Gracias {viewer}!",
    "Shaken not stirred... pero demasiado. Culpa de {viewer}!",
}

function OverInteractive.Actions.make_drunk(player, args, viewer)
    -- B42 renombró "Drunkeness" a CharacterStat.INTOXICATION
    setStat(player, CharacterStat.INTOXICATION, 0.9)
    player:Say(pickPhrase(PHRASES_DRUNK, viewer))
end

local PHRASES_HUNGRY = {
    "{viewer} me quito el desayuno! Monstruo!",
    "Mi estomago suena mas fuerte que los zombies...",
    "Podria comerme un zombie en este momento...",
    "Como en Castaway pero sin pelota de voleibol. Tengo hambre!",
    "Esto no es Survivor pero si es sobrevivir. Gracias {viewer}!",
}

function OverInteractive.Actions.make_hungry(player, args, viewer)
    setStat(player, CharacterStat.HUNGER, 0.9)
    player:Say(pickPhrase(PHRASES_HUNGRY, viewer))
end

local PHRASES_THIRSTY = {
    "Mi garganta esta mas seca que el desierto de Mad Max! Gracias {viewer}!",
    "Agua agua agua! {viewer} por que me haces esto?!",
    "Dune no se ve tan ficticio en este momento...",
    "Necesito agua YA! Culpa de {viewer}!",
    "Fury Road era una profecia. {viewer} lo confirma!",
}

function OverInteractive.Actions.make_thirsty(player, args, viewer)
    setStat(player, CharacterStat.THIRST, 0.9)
    player:Say(pickPhrase(PHRASES_THIRSTY, viewer))
end

local PHRASES_TIRED = {
    "{viewer} me quito el redbull!",
    "No puedo dormir aqui hay zombies! Pero mis parpados dicen lo contrario...",
    "Mis ojos se cierran solos... gracias {viewer}!",
    "Como en TWD cuando nadie puede hacer guardia. Culpa de {viewer}!",
    "Funcionando en modo suenio. Gentileza de {viewer}!",
}

function OverInteractive.Actions.make_tired(player, args, viewer)
    setStat(player, CharacterStat.FATIGUE, 0.9)
    player:Say(pickPhrase(PHRASES_TIRED, viewer))
end

local PHRASES_RESTORE = {
    "{viewer} me dio un boost! A correr!",
    "GROOVY! Listo para pelear!",
    "Como Popeye con espinacas. Gracias {viewer}!",
    "Segunda vida desbloqueada por {viewer}!",
    "Me siento lleno de energia! Mucho amor a {viewer}!",
}

function OverInteractive.Actions.restore_endurance(player, args, viewer)
    setStat(player, CharacterStat.ENDURANCE, 1.0)
    setStat(player, CharacterStat.FATIGUE, 0.0)
    setStat(player, CharacterStat.PAIN, 0.0)
    player:Say(pickPhrase(PHRASES_RESTORE, viewer))
end

local PHRASES_CURE = {
    "{viewer} encontro la cura! Como en I Am Legend pero real!",
    "La infeccion desaparecio! El doctor {viewer} me salvo!",
    "Soy inmortal! Gracias {viewer}!",
    "Esto no es el final... todavia! Gracias {viewer}!",
    "Un milagro! Me he curado! Gracias {viewer}!",
}

function OverInteractive.Actions.cure_virus(player, args, viewer)
    local bd = player:getBodyDamage()
    bd:setInfected(false)
    bd:setIsFakeInfected(false)
    bd:setInfectionTime(-1)
    bd:setInfectionMortalityDuration(-1)
    local parts = bd:getBodyParts()
    if parts then
        for i = 0, parts:size() - 1 do
            local part = parts:get(i)
            if part then
                part:SetInfected(false)
                part:SetFakeInfected(false)
                part:SetBitten(false)
            end
        end
    end
    player:Say(pickPhrase(PHRASES_CURE, viewer))
end

-- ── Objetos (genérico + aleatorios) ──────────────────────────────────────────

local PHRASES_GIVE_ITEM = {
    "{viewer} me trae regalos del apocalipsis!",
    "Amazon Prime tiene entrega en el fin del mundo. Gracias {viewer}!",
    "Mi angel de la guarda se llama {viewer}!",
    "Un regalo caido del cielo! Cortesia de {viewer}!",
    "En el apocalipsis los regalos valen doble. Gracias {viewer}!",
}

function OverInteractive.Actions.give_item(player, args, viewer)
    local itemType = (args and args[1]) or "Base.Axe"
    player:getInventory():AddItem(itemType)
    player:Say(pickPhrase(PHRASES_GIVE_ITEM, viewer))
end

local PHRASES_RANDOM_FOOD = {
    "{viewer} me manda comida gourmet del apocalipsis!",
    "En Fallout esto valdria una fortuna. Gracias {viewer}!",
    "Justo lo que necesitaba para seguir! Gracias {viewer}!",
    "El supermercado del apocalipsis tiene stock! Cortesia de {viewer}!",
    "Gordon Ramsay del apocalipsis aprueba esto. Gracias {viewer}!",
}

function OverInteractive.Actions.give_random_food(player, args, viewer)
    player:getInventory():AddItem(randItem(FOOD_IDS))
    player:Say(pickPhrase(PHRASES_RANDOM_FOOD, viewer))
end

local PHRASES_RANDOM_WEAPON = {
    "{viewer} me manda el arma perfecta!",
    "Armado y peligroso gracias a {viewer}!",
    "Esto es mejor que ser Lara Croft! Gracias {viewer}!",
    "El arsenal del chat en accion. Cortesia de {viewer}!",
    "Con esto si que aguanto! Gracias {viewer}!",
}

function OverInteractive.Actions.give_random_weapon(player, args, viewer)
    player:getInventory():AddItem(randItem(MELEE_IDS))
    player:Say(pickPhrase(PHRASES_RANDOM_WEAPON, viewer))
end

local PHRASES_RANDOM_FIREARM = {
    "{viewer} me convierte en John Wick mode!",
    "Hasta la vista baby! Con esto si puedo. Gracias {viewer}!",
    "El Terminator estaria orgulloso. Gracias {viewer}!",
    "El chat me manda artilleria pesada! Cortesia de {viewer}!",
    "Con esta artilleria ni los zombies me asustan. Gracias {viewer}!",
}

function OverInteractive.Actions.give_random_firearm(player, args, viewer)
    local fid = randItem(FIREARM_IDS)
    local inv = player:getInventory()
    inv:AddItem(fid)
    local ammo = AMMO_MAP[fid]
    if ammo then
        for _ = 1, 3 do inv:AddItem(ammo) end
    end
    local mag = MAGAZINE_MAP[fid]
    if mag then inv:AddItem(mag) end
    player:Say(pickPhrase(PHRASES_RANDOM_FIREARM, viewer))
end

local PHRASES_RANDOM_TOOL = {
    "MacGyver modo ON gracias a {viewer}!",
    "Con esto puedo construir o destruir. Gracias {viewer}!",
    "El kit del hombre de campo. Gentileza de {viewer}!",
    "Una herramienta del cielo! Cortesia de {viewer}!",
    "{viewer} me manda lo necesario para sobrevivir!",
}

function OverInteractive.Actions.give_random_tool(player, args, viewer)
    player:getInventory():AddItem(randItem(TOOL_IDS))
    player:Say(pickPhrase(PHRASES_RANDOM_TOOL, viewer))
end

local PHRASES_MEDICAL_KIT = {
    "{viewer} me manda el kit del doctor del apocalipsis!",
    "La clinica de {viewer} esta abierta en el fin del mundo!",
    "No soy medico pero con esto finjo serlo. Gracias {viewer}!",
    "El doctor Strange podria aprender de {viewer}!",
    "Un poco de ayuda medica nunca viene mal. Gracias {viewer}!",
}

function OverInteractive.Actions.give_medical_kit(player, args, viewer)
    local inv = player:getInventory()
    inv:AddItem("Base.FirstAidKit")
    inv:AddItem("Base.Bandage")
    inv:AddItem("Base.Bandage")
    inv:AddItem("Base.AlcoholWipes")
    inv:AddItem("Base.Antibiotics")
    inv:AddItem("Base.Pills")
    player:Say(pickPhrase(PHRASES_MEDICAL_KIT, viewer))
end

local PHRASES_MOLOTOVS = {
    "{viewer} me convierte en un piromano controlado!",
    "Fuego! El arma mas antigua y efectiva. Gracias {viewer}!",
    "A purificar el mundo con fuego! Gracias {viewer}!",
    "Como Firestarter pero mas util. Gentileza de {viewer}!",
    "El fuego purifica todo! Cortesia de {viewer}!",
}

function OverInteractive.Actions.give_molotovs(player, args, viewer)
    local inv = player:getInventory()
    inv:AddItem("Base.Molotov")
    inv:AddItem("Base.Molotov")
    inv:AddItem("Base.Molotov")
    inv:AddItem("Base.Lighter")
    player:Say(pickPhrase(PHRASES_MOLOTOVS, viewer))
end

-- ── Objetos individuales ─────────────────────────────────────────────────────

local function makeGiveItem(itemId, phrases)
    return function(player, args, viewer)
        player:getInventory():AddItem(itemId)
        player:Say(pickPhrase(phrases, viewer))
    end
end

local function makeGiveFirearm(itemId, phrases)
    return function(player, args, viewer)
        local inv = player:getInventory()
        inv:AddItem(itemId)
        local ammoId = AMMO_MAP[itemId]
        if ammoId then
            inv:AddItem(ammoId)
            inv:AddItem(ammoId)
        end
        local magId = MAGAZINE_MAP[itemId]
        if magId then inv:AddItem(magId) end
        player:Say(pickPhrase(phrases, viewer))
    end
end

OverInteractive.Actions.give_axe = makeGiveItem("Base.Axe", {
    "{viewer} me manda el hacha del lumberjack apocaliptico!",
    "Shining mode activado! Gracias {viewer}!",
    "Jack Torrance la hubiera usado bien... yo tambien. Gracias {viewer}!",
    "Gracias! A picar zombies! Cortesia de {viewer}!",
    "Aqui hay un hacha! Cortesia de {viewer}!",
})

OverInteractive.Actions.give_baseball_bat = makeGiveItem("Base.BaseballBat", {
    "{viewer} me convierte en Negan del apocalipsis!",
    "Lucille dice hola! Gracias {viewer}!",
    "Batter up! El chat manda el bat. Cortesia de {viewer}!",
    "Play ball! Con zombies de target. Gentileza de {viewer}!",
    "Home run zombie! Cortesia de {viewer}!",
})

OverInteractive.Actions.give_katana = makeGiveItem("Base.Katana", {
    "{viewer} me convierte en Michonne del apocalipsis!",
    "Kill Bill mode ON! Gracias {viewer}!",
    "El Samurai sobreviviente ha llegado. Cortesia de {viewer}!",
    "Katana del chat! Como en Kill Bill pero con zombies. Gracias {viewer}!",
    "La katana de {viewer}! Silenciosa y letal!",
})

OverInteractive.Actions.give_crowbar = makeGiveItem("Base.Crowbar", {
    "{viewer} me manda el arma de Gordon Freeman!",
    "La palanca: el arma mas versatil del apocalipsis. Gracias {viewer}!",
    "Gordon Freeman estaria orgulloso. Gracias {viewer}!",
    "Half-Life vibes con esta palanca de {viewer}!",
    "Clasico. Nunca falla! Cortesia de {viewer}!",
})

OverInteractive.Actions.give_machete = makeGiveItem("Base.Machete", {
    "{viewer} me convierte en el machete del apocalipsis!",
    "Machete! Como la pelicula pero sin Danny Trejo. Gracias {viewer}!",
    "Danny Trejo estaria orgulloso. Gentileza de {viewer}!",
    "En la selva y en el apocalipsis el machete es el rey. Gracias {viewer}!",
    "A machetear se dijo! Cortesia de {viewer}!",
})

OverInteractive.Actions.give_hunting_knife = makeGiveItem("Base.HuntingKnife", {
    "{viewer} me manda el cuchillo de Rambo!",
    "John Rambo usaria esto. Gracias {viewer}!",
    "Con esto soy Rick de TWD en modo ninja. Gracias {viewer}!",
    "Silencioso y efectivo! Cortesia de {viewer}!",
    "El cazador silencioso entra en escena. Gracias {viewer}!",
})

OverInteractive.Actions.give_water_bottle = makeGiveItem("Base.WaterBottle", {
    "{viewer} recuerda que la hidratacion es vital en el apocalipsis!",
    "Agua! El recurso mas valioso de Mad Max. Gracias {viewer}!",
    "En Fury Road mueren por agua. Tu me la mandas. Gracias {viewer}!",
    "Hidratado y listo para sobrevivir. Cortesia de {viewer}!",
    "Agua pura del chat! Gracias {viewer}!",
})

OverInteractive.Actions.give_canned_food = makeGiveItem("Base.TinnedBeans", {
    "{viewer} me manda la comida gourmet del apocalipsis!",
    "En Fallout esto valdria una fortuna. Gracias {viewer}!",
    "La lata: la comida perfecta del fin del mundo. Gracias {viewer}!",
    "El supermercado del apocalipsis tiene stock! Cortesia de {viewer}!",
    "Frijolitos enlatados del chat! Gracias {viewer}!",
})

OverInteractive.Actions.give_pistol = makeGiveFirearm("Base.Pistol", {
    "{viewer} me arma como John Wick en la primera pelicula!",
    "Pew pew! Con municion incluida. Gracias {viewer}!",
    "El canon de {viewer} dice pum pum!",
    "Armed and dangerous! Gracias a {viewer}!",
    "Pistola con municion! Cortesia de {viewer}!",
})

OverInteractive.Actions.give_shotgun = makeGiveFirearm("Base.Shotgun", {
    "{viewer} me manda la escopeta del Evil Dead! Groovy!",
    "GROOVY! Ash Williams estaria orgulloso. Gracias {viewer}!",
    "Esta es mi escopeta, mi nombre es Ash. Gracias {viewer}!",
    "Con esto si que hay partido. Cortesia de {viewer}!",
    "Escopeta lista! Gentileza de {viewer}!",
})

OverInteractive.Actions.give_rifle = makeGiveFirearm("Base.AssaultRifle", {
    "{viewer} me pone en modo Terminator!",
    "Hasta la vista baby! Con este rifle de {viewer}!",
    "Mission Impossible acepta este equipo. Gracias {viewer}!",
    "Artilleria pesada del chat! A {viewer} le gusta el caos!",
    "Rifle de asalto del chat! Cortesia de {viewer}!",
})

-- ── Entorno ──────────────────────────────────────────────────────────────────

-- Índice del float de precipitación en ClimateManager. El estático de Java no está
-- expuesto a Lua: el propio panel de admin del juego lo hardcodea
-- (ISUI/AdminPanel/ISAdmPanelClimate.lua, FLOAT_PRECIPITATION_INTENSITY = 3).
local FLOAT_PRECIPITATION_INTENSITY = 3

-- Días transcurridos en la escala que usan ElecShutModifier / WaterShutModifier.
-- Fórmula tomada de ISUI/ISButtonPrompt.lua: hay luz mientras
--   worldAgeHours/24 + (timeSinceApo-1)*30 < ElecShutModifier
-- así que poner el modificador en el valor actual corta el suministro ya mismo.
local function apocalypseDays()
    return getGameTime():getWorldAgeHours() / 24
         + (getSandboxOptions():getTimeSinceApo() - 1) * 30
end

local PHRASES_RAIN = {
    "{viewer} llamo a las nubes del apocalipsis!",
    "La tormenta viene! Como en Twister pero con zombies. Gracias {viewer}!",
    "Blade Runner 2049 tenia mejor lluvia pero esta sirve. Gracias {viewer}!",
    "La lluvia del fin del mundo. Cortesia meteorologica de {viewer}!",
    "El cielo llora conmigo... culpa de {viewer}!",
}

function OverInteractive.Actions.trigger_rain(player, args, viewer)
    local cm = getClimateManager()
    if cm then
        cm:setPrecipitationIsSnow(false)
        local cf = cm:getClimateFloat(FLOAT_PRECIPITATION_INTENSITY)
        if cf then
            cf:setEnableOverride(true)
            cf:setOverride(1.0, 1.0)
        end
    end
    player:Say(pickPhrase(PHRASES_RAIN, viewer))
end

local PHRASES_POWER_OFF = {
    "{viewer} pago la factura de la luz... pero al reves!",
    "Blackout total! Como en las pelis de terror. Gracias {viewer}!",
    "Sin electricidad somos primitivos. Cortesia de {viewer}!",
    "John Hammond diria que esto no debia pasar. Gracias {viewer}!",
    "Se fue la luz! Culpa de {viewer}!",
}

function OverInteractive.Actions.turn_power_off(player, args, viewer)
    getWorld():setHydroPowerOn(false)
    getSandboxOptions():set("ElecShutModifier", apocalypseDays())
    getSandboxOptions():toLua()
    player:Say(pickPhrase(PHRASES_POWER_OFF, viewer))
end

local PHRASES_WATER_OFF = {
    "{viewer} corto el suministro de agua! Cruel!",
    "Mad Max Fury Road era una profecia y {viewer} la cumple!",
    "Sin agua en el apocalipsis... nivel dificil activado por {viewer}!",
    "Thanos chasquea y {viewer} corta el agua. Same energy.",
    "El grifo ya no da agua... gracias {viewer}!",
}

function OverInteractive.Actions.turn_water_off(player, args, viewer)
    getSandboxOptions():set("WaterShutModifier", apocalypseDays())
    getSandboxOptions():toLua()
    player:Say(pickPhrase(PHRASES_WATER_OFF, viewer))
end
