Fix combine ignoring ai_ignoreplayers, general code refactoring

This commit is contained in:
toasterpanic 2026-06-04 22:01:27 -04:00
parent 303d596a31
commit 3b45d259b9
6 changed files with 62 additions and 12 deletions

View file

@ -1,14 +1,16 @@
# Z-City NPC Integration # 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! - 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. - 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 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. - 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. 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: MUST BE DONE BEFORE RELEASE:
- Spinal injuries
- NPCs should not unfake when moved around
- Fire / falling / pain reaction (more rapid flailing) - Fire / falling / pain reaction (more rapid flailing)
- NPCs should not unfake if being grabbed - 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: Stuff to do:
- Posturing (like when you get hit) more similar to Z-City
- Facial expressions - Facial expressions
- Generally improve stumbling (foot stepping) - 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: Post release ideas:

View file

@ -53,6 +53,12 @@ local function PopulateRagdollSBXToolMenu(pnl)
pnl:NumSlider("Wound grab time", "zcnpci_woundgrab_time", 0, 10, 3) pnl:NumSlider("Wound grab time", "zcnpci_woundgrab_time", 0, 10, 3)
pnl:ControlHelp("How long the ragdoll should hold its wound.") 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:CheckBox("Allow getting up", "zcnpci_unfake_enabled")
pnl:ControlHelp("If enabled, NPCs will get back up if able.") 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: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: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 end
local function PopulatePerformanceSBXToolMenu(pnl) local function PopulatePerformanceSBXToolMenu(pnl)

View file

@ -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 !IsValid(ent) or !ent:IsNPC() or (ent:GetClass() == "npc_ragdoll_target") then return end
if ent:Disposition(me.dummy_entity) == D_HT then 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) ent:AddEntityRelationship(me, D_HT, -1)
else else
ent:AddEntityRelationship(me, D_NU, -1) ent:AddEntityRelationship(me, D_NU, -1)
@ -84,7 +84,7 @@ function ENT:Think()
(ragdoll.organism.critical) (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 self.last_stop_targeting = should_stop_targeting
if should_stop_targeting then if should_stop_targeting then

View file

@ -39,6 +39,7 @@ function ENT:Initialize()
if CLIENT then if CLIENT then
print("I AM CLIENTSIDE") print("I AM CLIENTSIDE")
self:SetNoDraw(true)
end end
end end

View file

@ -57,16 +57,45 @@ local corpses = {}
local last_corpse_tick = CurTime() local last_corpse_tick = CurTime()
local last_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 local hook_table = hook.GetTable()
-- Don't particularly like doing this but you gotta do what you gotta do
local homigrad_damage_hook = hook.GetTable().EntityTakeDamage["homigrad-damage"]
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) hook.Add("EntityTakeDamage", "homigrad-damage", function(ent, dmginfo)
if ent:IsNPC() and ent.organism_no_damage then return false end if ent:IsNPC() and ent.organism_no_damage then return false end
homigrad_damage_hook(ent, dmginfo) homigrad_damage_hook(ent, dmginfo)
end) 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) hook.Add("CreateEntityRagdoll", "zcnpci", function(ent, ragdoll)
if !ent.organism then return end if !ent.organism then return end
@ -149,6 +178,8 @@ hook.Add("CreateEntityRagdoll", "zcnpci", function(ent, ragdoll)
spawn_flags = ent:GetSpawnFlags() spawn_flags = ent:GetSpawnFlags()
} }
ragdoll:AddCallback("PhysicsCollide", function(outEnt, data) hook.Run("Ragdoll Collide", ragdoll, data) end)
timer.Simple(0, function() timer.Simple(0, function()
if !IsValid(ragdoll) then return end if !IsValid(ragdoll) then return end

View file

@ -99,6 +99,8 @@ local minimum_down_time = CreateConVar("zcnpci_down_time", "5", {FCVAR_ARCHIVE,
-- Unfaking -- Unfaking
local can_unfake = CreateConVar("zcnpci_unfake_enabled", "1", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Whether an NPC can unfake") 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 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 -- Targeting
local npc_targeting_enabled = CreateConVar("zcnpci_npc_targeting_enabled", "1", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Should NPCs target downed NPCs") 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.LastThink = CurTime()
self.LastFakeUpCheck = CurTime() self.LastFakeUpCheck = CurTime()
self.LastPosCheck = target:GetPos() self.LastPosCheck = target:GetPos()
self.SpawnTimestamp = CurTime()
self.bullseye = ents.Create("npc_ragdoll_target") self.bullseye = ents.Create("npc_ragdoll_target")
self.bullseye:SetPos(target:GetPos()) self.bullseye:SetPos(target:GetPos())
@ -394,6 +397,10 @@ function MODULE:PhysicsSimulate(phys, dt)
return false -- Cut the bullshit return false -- Cut the bullshit
end 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 (!target.organism.alive) then
-- If the NPC is dead, they probably aren't coming back; don't bother bringing them back to life -- If the NPC is dead, they probably aren't coming back; don't bother bringing them back to life
self:Remove() self:Remove()
@ -409,7 +416,7 @@ function MODULE:PhysicsSimulate(phys, dt)
return false return false
end 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() self.LastPosCheck = target:GetPos()
target.StartDie = cur_time target.StartDie = cur_time
end end