From 3b45d259b96bca6a4f7ad109f9bd3396e4be432b Mon Sep 17 00:00:00 2001 From: toasterpanic Date: Thu, 4 Jun 2026 22:01:27 -0400 Subject: [PATCH] Fix combine ignoring ai_ignoreplayers, general code refactoring --- README.md | 14 ++++++----- lua/autorun/client/zcnpci_menu.lua | 9 +++++++ lua/entities/npc_ragdoll_target.lua | 4 +-- lua/entities/npc_ragdoll_unfaker.lua | 1 + lua/zcnpci.lua | 37 +++++++++++++++++++++++++--- lua/zcnpci/modules/falling_legs.lua | 9 ++++++- 6 files changed, 62 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 2704086..716d7b9 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,16 @@ # Z-City NPC Integration -Adds better NPC integration for the Garry's Mod addon Z-City. While Z-City has a convar that enables the custom health system on NPCs, it really isn't the best or most advanced thing in the world. This has irritated me since I first started using Z-City. Therefore, I've decided to take care of the problem myself with my own plugin. +Adds better NPC integration for the Garry's Mod addon Z-City. + +While Z-City has a convar that enables the custom health system on NPCs, it really isn't the best or most bug-free thing in the world. This has irritated me since I first started using Z-City. Therefore, I've decided to take care of the problem myself with my own plugin. - NPCs can be ragdolled like players can, and will wiggle around on the ground. They can get back up, too! - It takes all elements of Z-City's health system into account, such as unconsciousness and pain. - NPCs are responsive to damage from many sources -- bullets, fire, melee (especially kicks!) and more. - NPCs can also see and target downed NPCs, and will prioritize standing NPCs over them when needed. -- Various configurable settings are included, allowing you to tweak the mod to your liking. +- Everything is configurable, so you can modify and disable features as you like to create your preferred experience. -This mod is built to perform reasonably well, thus it includes optimizations, configurable performance settings, and an automatic corpse remover. +This mod is built to perform reasonably well, thus it includes optimizations, configurable performance settings, and an automatic corpse cleanup tool. This addon is based off of Kazarei's Euphoria, which in turn is a fork of Fedhoria by Rama. Credits to them for making the cool thing. @@ -34,16 +36,16 @@ It is, however, not compatbile with: MUST BE DONE BEFORE RELEASE: -- Spinal injuries -- NPCs should not unfake when moved around - Fire / falling / pain reaction (more rapid flailing) - NPCs should not unfake if being grabbed +- Fix combine targeting players when ai_ignoreplayers = 1. This is caused by an npc_bullseye that is always on the player. I don't know why it's always there but I don't like it. Will probably override it with my own npc_ragdoll_target entity. Stuff to do: +- Posturing (like when you get hit) more similar to Z-City - Facial expressions - Generally improve stumbling (foot stepping) -- Stop NPCs from dropping their guns (that's not a good thing to do) +- Stop NPCs from dropping their guns maybe? Post release ideas: diff --git a/lua/autorun/client/zcnpci_menu.lua b/lua/autorun/client/zcnpci_menu.lua index acdf8cd..848185f 100644 --- a/lua/autorun/client/zcnpci_menu.lua +++ b/lua/autorun/client/zcnpci_menu.lua @@ -53,6 +53,12 @@ local function PopulateRagdollSBXToolMenu(pnl) pnl:NumSlider("Wound grab time", "zcnpci_woundgrab_time", 0, 10, 3) pnl:ControlHelp("How long the ragdoll should hold its wound.") + pnl:NumSlider("Writhing strength", "zcnpci_writhing_strength", 0, 5, 3) + pnl:ControlHelp("How hard should the ragdoll wriggle/writhe. 1 is default.") + + pnl:NumSlider("Death timer", "zcnpci_death_timer", -1, 600, 3) + pnl:ControlHelp("After an NPC is ragdolled for longer than this timer, they will die instantly. Set to -1 to disable.") + pnl:CheckBox("Allow getting up", "zcnpci_unfake_enabled") pnl:ControlHelp("If enabled, NPCs will get back up if able.") @@ -61,6 +67,9 @@ local function PopulateRagdollSBXToolMenu(pnl) pnl:NumSlider("Minimum down time", "zcnpci_down_time", 0, 20, 3) pnl:ControlHelp("The minimum amount of time the ragdoll should be down before trying to get back up.") + + pnl:NumSlider("Movement sensitivity", "zcnpci_movement_sensitivity", -1, 256, 3) + pnl:ControlHelp("How far does the NPC have to move to reset the getting up timer. Set to -1 to disable.") end local function PopulatePerformanceSBXToolMenu(pnl) diff --git a/lua/entities/npc_ragdoll_target.lua b/lua/entities/npc_ragdoll_target.lua index b4bf22d..aeb33fe 100644 --- a/lua/entities/npc_ragdoll_target.lua +++ b/lua/entities/npc_ragdoll_target.lua @@ -15,7 +15,7 @@ local function UpdateRelationship(ent, me) if !IsValid(ent) or !ent:IsNPC() or (ent:GetClass() == "npc_ragdoll_target") then return end if ent:Disposition(me.dummy_entity) == D_HT then - print("Ragdoll target of class "..me.ragdoll_to_follow.class_in_previous_life.." is hated by a "..ent:GetClass()) + --print("Ragdoll target of class "..me.ragdoll_to_follow.class_in_previous_life.." is hated by a "..ent:GetClass()) ent:AddEntityRelationship(me, D_HT, -1) else ent:AddEntityRelationship(me, D_NU, -1) @@ -84,7 +84,7 @@ function ENT:Think() (ragdoll.organism.critical) ) - if true or (should_stop_targeting != self.last_stop_targeting) then + if (should_stop_targeting != self.last_stop_targeting) then self.last_stop_targeting = should_stop_targeting if should_stop_targeting then diff --git a/lua/entities/npc_ragdoll_unfaker.lua b/lua/entities/npc_ragdoll_unfaker.lua index 2778c71..0a18d7f 100644 --- a/lua/entities/npc_ragdoll_unfaker.lua +++ b/lua/entities/npc_ragdoll_unfaker.lua @@ -39,6 +39,7 @@ function ENT:Initialize() if CLIENT then print("I AM CLIENTSIDE") + self:SetNoDraw(true) end end diff --git a/lua/zcnpci.lua b/lua/zcnpci.lua index f603668..5883731 100644 --- a/lua/zcnpci.lua +++ b/lua/zcnpci.lua @@ -57,16 +57,45 @@ local corpses = {} local last_corpse_tick = CurTime() local last_tick = CurTime() --- We need to override the default homigrad damage hook so it doesn't cause damage to ragdolls we are tryign to fake --- Don't particularly like doing this but you gotta do what you gotta do -local homigrad_damage_hook = hook.GetTable().EntityTakeDamage["homigrad-damage"] +local hook_table = hook.GetTable() +local homigrad_damage_hook = hook_table.EntityTakeDamage["homigrad-damage"] +local homigrad_player_spawn_hook = hook_table.player_spawn["homigrad-spawn3"] + +-- We need to override the default homigrad damage hook so it doesn't cause damage to ragdolls we are tryign to fake hook.Add("EntityTakeDamage", "homigrad-damage", function(ent, dmginfo) if ent:IsNPC() and ent.organism_no_damage then return false end homigrad_damage_hook(ent, dmginfo) end) +-- This disables player bullseye spawning, which fixes issues with combine targeting with ai_ignoreplayers +hook.Add("player_spawn", "homigrad-spawn3", function(data) + local ply = Player(data.userid) + if not IsValid(ply) then return end + + ply.bull = { + IsValid = function() return true end, + Remove = function() return end + } + + homigrad_player_spawn_hook(data) +end) + +-- This is called right after the NPC bullseye is created for player ragdolls, which allows us to replace it with a dummy. +-- Fixes similar issues mentioned in previous comment +hook.Add("Ragdoll_Create", "zcnpci-override", function(ply, ragdoll) + if IsValid(ragdoll.bull) then + ragdoll.bull:Remove() + end + + ragdoll.bull = { + IsValid = function() return true end, + SetPos = function() return true end, + Remove = function() return end + } +end) + hook.Add("CreateEntityRagdoll", "zcnpci", function(ent, ragdoll) if !ent.organism then return end @@ -149,6 +178,8 @@ hook.Add("CreateEntityRagdoll", "zcnpci", function(ent, ragdoll) spawn_flags = ent:GetSpawnFlags() } + ragdoll:AddCallback("PhysicsCollide", function(outEnt, data) hook.Run("Ragdoll Collide", ragdoll, data) end) + timer.Simple(0, function() if !IsValid(ragdoll) then return end diff --git a/lua/zcnpci/modules/falling_legs.lua b/lua/zcnpci/modules/falling_legs.lua index 25ce3c2..64794b7 100644 --- a/lua/zcnpci/modules/falling_legs.lua +++ b/lua/zcnpci/modules/falling_legs.lua @@ -99,6 +99,8 @@ local minimum_down_time = CreateConVar("zcnpci_down_time", "5", {FCVAR_ARCHIVE, -- Unfaking local can_unfake = CreateConVar("zcnpci_unfake_enabled", "1", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Whether an NPC can unfake") local unfake_time = CreateConVar("zcnpci_unfake_time", "1.5", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Time it takes for an NPC to unfake") +local movement_sensitivity = CreateConVar("zcnpci_movement_sensitivity", "40", {FCVAR_ARCHIVE, FCVAR_REPLICATED}) +local death_timer = CreateConVar("zcnpci_death_timer", "-1", {FCVAR_ARCHIVE, FCVAR_REPLICATED}) -- Targeting local npc_targeting_enabled = CreateConVar("zcnpci_npc_targeting_enabled", "1", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Should NPCs target downed NPCs") @@ -199,6 +201,7 @@ function MODULE:Init() self.LastThink = CurTime() self.LastFakeUpCheck = CurTime() self.LastPosCheck = target:GetPos() + self.SpawnTimestamp = CurTime() self.bullseye = ents.Create("npc_ragdoll_target") self.bullseye:SetPos(target:GetPos()) @@ -394,6 +397,10 @@ function MODULE:PhysicsSimulate(phys, dt) return false -- Cut the bullshit end + if (death_timer:GetFloat() != -1) and ((CurTime() - self.SpawnTimestamp) > death_timer:GetFloat()) then + target.organism.alive = false + end + if (!target.organism.alive) then -- If the NPC is dead, they probably aren't coming back; don't bother bringing them back to life self:Remove() @@ -409,7 +416,7 @@ function MODULE:PhysicsSimulate(phys, dt) return false end - if (self.LastPosCheck:DistToSqr(target:GetPos()) > (32 ^ 2)) then + if (self.LastPosCheck:DistToSqr(target:GetPos()) > (movement_sensitivity:GetFloat() ^ 2)) then self.LastPosCheck = target:GetPos() target.StartDie = cur_time end