From 85eb62a495f8a7564bfd4ed0c430ac804b83ae73 Mon Sep 17 00:00:00 2001 From: toasterpanic Date: Tue, 26 May 2026 23:21:18 -0400 Subject: [PATCH] Clear crippled and near-death enemies, various optimizations --- lua/autorun/client/zcnpci_menu.lua | 6 ++ lua/zcnpci.lua | 159 +++++++++++++++++----------- lua/zcnpci/modules/falling_legs.lua | 22 ++-- 3 files changed, 115 insertions(+), 72 deletions(-) diff --git a/lua/autorun/client/zcnpci_menu.lua b/lua/autorun/client/zcnpci_menu.lua index abca5a2..97fb0ed 100644 --- a/lua/autorun/client/zcnpci_menu.lua +++ b/lua/autorun/client/zcnpci_menu.lua @@ -44,6 +44,12 @@ local function PopulatePerformanceSBXToolMenu(pnl) pnl:CheckBox("Never remove corpses near the player", "zcnpci_disable_corpse_removal_near_player") pnl:ControlHelp("If on, corpses under the variable above in distance will never be cleared.") + + pnl:CheckBox("Treat crippled NPCs as dead", "zcnpci_treat_crippled_as_dead") + pnl:ControlHelp("If on, corpses with amputations or both legs broken will be clearable.") + + pnl:CheckBox("Treat near-death NPCs as dead", "zcnpci_treat_near_death_as_dead") + pnl:ControlHelp("If on, corpses near death (severe blood loss, comas, etc.) will be clearable.") end if engine.ActiveGamemode() == "sandbox" then diff --git a/lua/zcnpci.lua b/lua/zcnpci.lua index 273c4a7..cdbc7fd 100644 --- a/lua/zcnpci.lua +++ b/lua/zcnpci.lua @@ -9,6 +9,10 @@ local max_corpses = CreateConVar("zcnpci_max_corpses", 8, bit.bor(FCVAR_ARC local max_corpse_time = CreateConVar("zcnpci_max_corpse_time", 120, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED)) local max_corpse_distance = CreateConVar("zcnpci_max_corpse_distance", 2500, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED)) local no_corpse_removal_near_player = CreateConVar("zcnpci_disable_corpse_removal_near_player", 0, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED)) +local treat_crippled_as_dead = CreateConVar("zcnpci_treat_crippled_as_dead", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED)) +local treat_near_death_as_dead = CreateConVar("zcnpci_treat_near_death_as_dead", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED)) + +local debug_ragdoll_all = CreateConVar("zcnpci_debug_ragdoll_all", 0, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_PROTECTED)) local last_dmgpos = {} local last_hitgroup = {} @@ -16,6 +20,9 @@ local last_attacker = {} local corpses = {} +local last_corpse_tick = CurTime() +local last_tick = CurTime() + hook.Add("CreateEntityRagdoll", "zcnpci", function(ent, ragdoll) if !ent.organism then return end @@ -83,79 +90,107 @@ hook.Add("ScaleNPCDamage", "zcnpci", function(npc, hitgroup, dmginfo) end) hook.Add("Think", "zcnpci", function() - for i, ent in pairs(corpses) do - if !IsValid(ent) then - table.RemoveByValue(corpses, ent) - end - end + local do_corpse_loop = (CurTime() - last_corpse_tick) > 2.0 + local do_general_loop = (CurTime() - last_tick) > 0.1 + + if do_general_loop then + if do_corpse_loop then + last_corpse_tick = CurTime() - if (max_corpses:GetFloat() != -1) and (#corpses > max_corpses:GetFloat()) then - if IsValid(corpses[1]) then - corpses[1]:Remove() - end - - table.remove(corpses, 1) - end - - for i, ent in pairs(ents.GetAll()) do - if !IsValid(ent) then continue end - if !ent.organism then continue end - - if ent:IsNPC() then - -- Knock them down if something is off - if ( - ((ent.organism.lleg >= 1) and (ent.organism.rleg >= 1)) or - ent.organism.llegamputated or - ent.organism.rlegamputated or - (ent.organism.consciousness <= 0.3) or - (ent.organism.pain > 90) or - ent:IsOnFire() - ) then - local damage_info = DamageInfo() - damage_info:SetDamage(ent:Health()) - damage_info:SetAttacker(ent) - damage_info:SetDamageType( DMG_DIRECT ) - damage_info:SetDamageForce(Vector(0, 0, 0)) - - ent:TakeDamageInfo(damage_info) - end - elseif !ent.organism.alive and remove_corpses and ent.is_npc_corpse then - -- Add to corpse list if not there already - if !table.HasValue(corpses, ent) then - table.insert(corpses, ent) - end - - -- Get nearest player for future checks - local nearby_player - local players = player.GetAll() - - for i, loop_player in pairs(players) do - local player_position = loop_player:GetPos() - - local distance = ent:GetPos():Distance(player_position) - - if (distance <= max_corpse_distance:GetFloat()) or (max_corpse_distance:GetFloat() == -1) then - nearby_player = loop_player - break + for i, ent in pairs(corpses) do + if !IsValid(ent) then + table.RemoveByValue(corpses, ent) end end - if nearby_player and IsValid(nearby_player) and no_corpse_removal_near_player:GetBool() then continue - elseif (max_corpse_distance:GetFloat() != -1) and !nearby_player then - ent:Remove() - continue - end + if (max_corpses:GetFloat() != -1) and (#corpses > max_corpses:GetFloat()) then + if IsValid(corpses[1]) then + corpses[1]:Remove() + end - if max_corpse_time:GetFloat() != -1 then - if !ent.corpse_timestamp then ent.corpse_timestamp = CurTime() - elseif (CurTime() - ent.corpse_timestamp) > max_corpse_time:GetFloat() then + table.remove(corpses, 1) + end + end + + for i, ent in pairs(ents.GetAll()) do + if !IsValid(ent) then continue end + if !ent.organism then continue end + + if ent:IsNPC() then + -- Knock them down if something is off + if ( + ((ent.organism.lleg >= 1) and (ent.organism.rleg >= 1)) or + ent.organism.llegamputated or + ent.organism.rlegamputated or + (ent.organism.consciousness <= 0.3) or + (ent.organism.pain > 90) or + ent:IsOnFire() or + debug_ragdoll_all:GetBool() + ) then + local damage_info = DamageInfo() + damage_info:SetDamage(ent:Health()) + damage_info:SetAttacker(ent) + damage_info:SetDamageType( DMG_DIRECT ) + damage_info:SetDamageForce(Vector(0, 0, 0)) + + ent:TakeDamageInfo(damage_info) + end + elseif do_corpse_loop and remove_corpses and ent.is_npc_corpse then + local clearable = false + + if !ent.organism.alive then clearable = true end + if treat_crippled_as_dead:GetBool() and ( + ent.organism.llegamputated or ent.organism.rlegamputated or ent.organism.larmamputated or ent.organism.rarmamputated or + ((ent.organism.lleg >= 1) and (ent.organism.rleg >= 1)) + ) then clearable = true end + if treat_near_death_as_dead:GetBool() and ( + ent.organism.heartstop or + (ent.organism.brain > 0.6) or + !ent.organism.lungsfunction or + (ent.organism.blood < 3000) or + (ent.organism.pulse < 10) + ) then clearable = true end + + if !clearable then return end + + -- Add to corpse list if not there already + if !table.HasValue(corpses, ent) then + table.insert(corpses, ent) + end + + -- Get nearest player for future checks + local nearby_player + local players = player.GetAll() + + for i, loop_player in pairs(players) do + local player_position = loop_player:GetPos() + + local distance = ent:GetPos():Distance(player_position) + + if (distance <= max_corpse_distance:GetFloat()) or (max_corpse_distance:GetFloat() == -1) then + nearby_player = loop_player + break + end + end + + if nearby_player and IsValid(nearby_player) and no_corpse_removal_near_player:GetBool() then continue + elseif (max_corpse_distance:GetFloat() != -1) and !nearby_player then ent:Remove() continue end - print((CurTime() - ent.corpse_timestamp) > max_corpse_time:GetFloat()) + + if max_corpse_time:GetFloat() != -1 then + if !ent.corpse_timestamp then ent.corpse_timestamp = CurTime() + elseif (CurTime() - ent.corpse_timestamp) > max_corpse_time:GetFloat() then + ent:Remove() + continue + end + print((CurTime() - ent.corpse_timestamp) > max_corpse_time:GetFloat()) + end end end end + end) --[[hook.Add("OnNPCKilled", "Fedhoria", function(ent, attacker, inflictor) diff --git a/lua/zcnpci/modules/falling_legs.lua b/lua/zcnpci/modules/falling_legs.lua index d09d0fe..35e9f99 100644 --- a/lua/zcnpci/modules/falling_legs.lua +++ b/lua/zcnpci/modules/falling_legs.lua @@ -161,6 +161,8 @@ function MODULE:Init() self.StartDie = nil self.AnimationRollEndTime = 0 self.StopProcessing = false + self.LastThink = CurTime() + self.LastPhysProcess = CurTime() -- Add damping to all bones. local phys = self:GetPhysicsObject() @@ -184,15 +186,15 @@ end ---------------------------------------------------------------------------]] function MODULE:Think() + if (CurTime() - self.LastThink) < 1 then return end if !IsValid(target) then return end - - print("target is valid") + + self.LastThink = CurTime() local phys = target:GetPhysicsObject() + if !IsValid(phys) or !phys:IsAsleep() then return end - print("phys is valid") - phys:Wake() end @@ -237,9 +239,11 @@ end Physics Simulation Hook (FIXED) ---------------------------------------------------------------------------]] function MODULE:PhysicsSimulate(phys, dt) - phys:Wake() - if self.StopProcessing then return false end + --print((CurTime() - self.LastPhysProcess) < 0.05) + --if (CurTime() - self.LastPhysProcess) < (1 /) then return false end + + self.LastPhysProcess = CurTime() local cur_time = CurTime() local target = self:GetTarget() @@ -357,10 +361,8 @@ function MODULE:PhysicsSimulate(phys, dt) local offset_sqr = (pos - self.last_pos):LengthSqr() self.last_pos = pos - if (offset_sqr < (10*10 * dt*dt) and not (ragmod and ragmod:IsRagmodRagdoll(target))) then + if offset_sqr < (10*10 * dt*dt) then self.StartDie = self.StartDie or cur_time - else - --self.StartDie = nil end -- Physics correction after collision @@ -373,7 +375,7 @@ function MODULE:PhysicsSimulate(phys, dt) return true, f end - + -- Logic for other bones local delta_collide = cur_time - self.LastCollideTime if (delta_collide < 0.2) then