diff --git a/gamemode/chargersystem.lua b/gamemode/chargersystem.lua new file mode 100644 index 0000000..a824772 --- /dev/null +++ b/gamemode/chargersystem.lua @@ -0,0 +1,103 @@ +local healthRechargeTime = CreateConVar( + "gc_healthchargerrechargetime", + "60", + FCVAR_ARCHIVE, + "Seconds before an emptied wall health charger refills; 0 disables automatic refills" +) + +local suitRechargeTime = CreateConVar( + "gc_suitchargerrechargetime", + "30", + FCVAR_ARCHIVE, + "Seconds before an emptied wall suit charger refills; 0 disables automatic refills" +) + +local chargerClasses = { + item_healthcharger = healthRechargeTime, + item_suitcharger = suitRechargeTime +} + +local chargerStates = {} + +local function GetChargerJuice(charger) + local juice = charger:GetInternalVariable("m_iJuice") + return tonumber(juice) +end + +local function GetChargerCapacity(charger, juice) + local maxJuice = charger:GetInternalVariable("m_iMaxJuice") + local displayedJuice = charger:GetInternalVariable("m_flJuice") + + return math.max( + juice or 0, + tonumber(maxJuice) or 0, + tonumber(displayedJuice) or 0 + ) +end + +local function RefillCharger(charger, state) + charger:Fire("Recharge") + + -- GMod's suit charger implements Recharge, but item_healthcharger does not + -- on all engine branches. Give the native input a frame to run, then use + -- its internal Source charge fields as a compatibility fallback. + timer.Simple(0.1, function() + if not IsValid(charger) or (GetChargerJuice(charger) or 0) > 0 then return end + if not state.capacity or state.capacity <= 0 then return end + + charger:SetSaveValue("m_iJuice", state.capacity) + if charger:GetInternalVariable("m_flJuice") ~= nil then + charger:SetSaveValue("m_flJuice", state.capacity) + end + charger:SetCycle(0) + charger:EmitSound("items/medshot4.wav", 60, 100, 0.5) + end) +end + +timer.Create("CustomCoopRechargeWallChargers", 1, 0, function() + local now = CurTime() + local seen = {} + + for className, rechargeTime in pairs(chargerClasses) do + local delay = math.max(rechargeTime:GetFloat(), 0) + + for _, charger in ipairs(ents.FindByClass(className)) do + seen[charger] = true + + local juice = GetChargerJuice(charger) + if juice == nil then continue end + + local state = chargerStates[charger] + if not state then + -- A mapper may deliberately place a charger with zero charge. + -- Only chargers observed being drained during play are refilled. + state = { + lastJuice = juice, + capacity = GetChargerCapacity(charger, juice) + } + chargerStates[charger] = state + elseif juice > 0 or delay == 0 then + state.refillAt = nil + elseif state.lastJuice > 0 and juice <= 0 then + state.refillAt = now + delay + elseif state.refillAt and now >= state.refillAt then + -- Use the native Source input rather than replacing the entity, + -- preserving its name, Hammer I/O, animation and sounds. + RefillCharger(charger, state) + state.refillAt = nil + end + + state.lastJuice = juice + end + end + + for charger in pairs(chargerStates) do + if not seen[charger] or not IsValid(charger) then + chargerStates[charger] = nil + end + end +end) + +hook.Add("PostCleanupMap", "CustomCoopResetWallChargerTracking", function() + chargerStates = {} +end) diff --git a/gamemode/init.lua b/gamemode/init.lua index ce53c3d..ad0257c 100644 --- a/gamemode/init.lua +++ b/gamemode/init.lua @@ -2,8 +2,9 @@ AddCSLuaFile( "cl_init.lua" ) AddCSLuaFile( "shared.lua" ) AddCSLuaFile( "levels.lua" ) include( "spawninit.lua" ) -include( "weaponsystem.lua" ) -include( "allplayersdied.lua" ) +include( "weaponsystem.lua" ) +include( "chargersystem.lua" ) +include( "allplayersdied.lua" ) include( "deathspectate.lua" ) include( "headcrabs.lua" ) include( "zombies.lua" ) diff --git a/gamemode/weaponsystem.lua b/gamemode/weaponsystem.lua index de508c7..157d0f1 100644 --- a/gamemode/weaponsystem.lua +++ b/gamemode/weaponsystem.lua @@ -15,10 +15,23 @@ local respawnDelay = CreateConVar( local trackedItems = {} local suppressRespawns = false +-- These use the item_ prefix but are map machinery or containers rather than +-- touch pickups. Ammo crates are already infinite; the others may be removed +-- deliberately by Hammer I/O and must not be recreated as loose supplies. +local persistentItemClasses = { + item_ammo_crate = true, + item_dynamic_resupply = true, + item_healthcharger = true, + item_item_crate = true, + item_suitcharger = true +} + local function IsRespawnable(ent) if not IsValid(ent) then return false end local className = ent:GetClass() + if persistentItemClasses[className] then return false end + return string.StartWith(className, "item_") or string.StartWith(className, "weapon_") end