NPCs no longer spawn inside other entities, various bugfixes

This commit is contained in:
toasterpanic 2026-05-29 19:51:17 -04:00
parent d9415d880f
commit 259a3da4e1
4 changed files with 113 additions and 62 deletions

View file

@ -4,6 +4,8 @@ Adds better NPC integration for the Garry's Mod addon Z-City. While Z-City has a
- 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.
This mod is built to perform reasonably well, thus it includes optimizations, configurable performance settings, and an automatic corpse remover.
@ -14,7 +16,6 @@ This addon is based off of Kazarei's Euphoria, which in turn is a fork of Fedhor
- Bandages, tourniquets, etc. that are on the ragdoll or NPC body will not be there when they get back up. Don't know how to fix this unfortunately.
- NPCs currently instantly get up, with no animation. I plan on remedying this in the future.
- Enemy NPCs will not target living downed NPCs like how they do players. This will be fixed.
## Compatibility

View file

@ -97,7 +97,7 @@ end
function ENT:Initialize()
if !self.Module then return end
self:SetModel(self.Module.Model)
if self.Module.Model then self:SetModel(self.Module.Model) end
for key, value in pairs(self.Module) do
if !self[key] then

View file

@ -137,6 +137,20 @@ hook.Add("CreateEntityRagdoll", "zcnpci", function(ent, ragdoll)
end)
end)
hook.Add("EntityTakeDamage", "zcnpci", function(ent, dmginfo)
if !IsValid(ent) or !IsValid(dmginfo) then return end
if ent:GetClass() == "npc_bullseye" then
local npc_rag = IsValid(ent.npc_rag) and ent.npc_rag
if IsValid(npc_rag) then
npc_rag:TakeDamageInfo(dmgInfo)
end
return
end
end)
hook.Add("HomigradDamage", "zcnpci", function(ent, dmginfo)
if !enabled:GetBool() or !IsValid(dmginfo) then return end
if !ent:IsNPC() then
@ -150,7 +164,7 @@ hook.Add("HomigradDamage", "zcnpci", function(ent, dmginfo)
return
end
last_dmgpos[ent] = dmginfo:GetDamage()
last_dmgpos[ent] = dmginfo:GetDamagePosition()
last_attacker[ent] = dmginfo:GetAttacker()
if dmginfo:IsDamageType(DMG_BULLET + DMG_BUCKSHOT + DMG_BLAST) then
@ -163,12 +177,9 @@ hook.Add("HomigradDamage", "zcnpci", function(ent, dmginfo)
local attacker = dmginfo:GetAttacker()
print(fists.InLegKick)
-- Kicks should knock NPCs down
if IsValid(dmginfo:GetInflictor()) and IsValid(fists) and attacker.InLegKick and ((attacker.InLegKick + 0.1) > CurTime()) then
table.insert(npcs_to_fake, ent)
print("ADD TO LIST!!!")
local attacker_angle = dmginfo:GetAttacker():EyeAngles()
@ -288,8 +299,6 @@ hook.Add("Tick", "zcnpci", function()
end
end
PrintTable(npcs_to_fake)
-- NPC faking is in a seperate area because we want NPCs to ragdoll as soon as they can
for i,ent in pairs(npcs_to_fake) do
local damage_info = DamageInfo()
@ -304,13 +313,6 @@ hook.Add("Tick", "zcnpci", function()
end
end)
--[[hook.Add("OnNPCKilled", "Fedhoria", function(ent, attacker, inflictor)
if (!enabled:GetBool() or !npcs:GetBool()) then return end
if (!ent:IsNPC() or dmginfo:GetDamage() < ent:Health()) then return end
last_dmgpos[ent] = dmginfo:GetDamagePosition()
end)]]
hook.Add("OnEntityCreated", "zcnpci", function(ent)
if !IsValid(ent) then return end

View file

@ -69,7 +69,7 @@ local cv_anim_roll_impact_threshold = CreateConVar("zcnpci_falling_anim_roll_imp
local cv_anim_roll_playback_rate = CreateConVar("zcnpci_falling_anim_roll_playback_rate", "3.0", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Скорость воспроизведения анимации")
-- Down time
local minimum_down_time = CreateConVar("zcnpci_down_time", "3", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Down time!!!!!!!!!")
local minimum_down_time = CreateConVar("zcnpci_down_time", "5", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Down time!!!!!!!!!")
--[[-------------------------------------------------------------------------
Tug function
@ -165,7 +165,28 @@ function MODULE:Init()
self.AnimationRollEndTime = 0
self.StopProcessing = false
self.LastThink = CurTime()
self.LastPhysProcess = CurTime()
self.LastFakeUpCheck = CurTime()
self.bullseye = ents.Create("npc_bullseye")
self.bullseye:SetPos(target:GetPos())
self.bullseye:SetMoveType(MOVETYPE_OBSERVER)
self.bullseye:SetModel("models/editor/bullseye.mdl")
self.bullseye:SetHealth(99999)
self.bullseye:Spawn()
self.bullseye:Activate()
self.bullseye:SetNotSolid(true)
self.bullseye.npc_rag = target
self.dummy_entity = ents.Create(target.class_in_previous_life)
for i,ent in ipairs(ents.GetAll()) do
if !IsValid(ent) or !ent.IsNPC() then continue end
if ent:Disposition(self.dummy_entity) == D_HT then
ent:AddEntityRelationship(self.bullseye, D_HT, -1)
print("HATE. HATE. HATE")
end
end
-- Add damping to all bones.
local phys = self:GetPhysicsObject()
@ -244,13 +265,16 @@ end
function MODULE:PhysicsSimulate(phys, dt)
if self.StopProcessing then return false end
--print((CurTime() - self.LastPhysProcess) < 0.05)
--if (CurTime() - self.LastPhysProcess) < (1 /) then return false end
--if (CurTime() - self.LastPhysProcess) < (1 /) then return false end())
self.LastPhysProcess = CurTime()
phys:Wake()
local cur_time = CurTime()
local target = self:GetTarget()
if not IsValid(target) then self:Remove(); return false end
if not IsValid(target) then self:Remove(); self.bullseye:Remove(); return false end
self.bullseye:SetPos(target:GetPos())
--self.bullseye:SetAngles(target:EyeAngles())
target.is_npc_corpse = true
@ -272,21 +296,37 @@ function MODULE:PhysicsSimulate(phys, dt)
if !target.organism then
self:Remove()
self.bullseye:Remove()
return false -- Cut the bullshit
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()
self.bullseye:Remove()
return false -- Cut the bullshit
elseif (target.organism.consciousness <= 0.5) or ((target.organism.lleg >= 0.85) and (target.organism.rleg >= 0.85)) then
target.StartDie = cur_time
return false
end
-- Getting up
-- Don't need to check for consciousness and the like because we've done it already above
if (CurTime() - self.LastFakeUpCheck) >= 1.0 then
self.LastFakeUpCheck = CurTime()
-- This basically checks if the NPC will be in collision with anything when it spawns.
-- Side note: the mins and maxs values are slightly larger than the default npc_citizen
-- hitboxes, mostly because I assume that most humanoid NPCs will be around that size.
local tracehull = util.TraceHull({
start = target:GetPos(),
endpos = target:GetPos(),
mins = Vector(-20, -20, 0),
maxs = Vector(20, 20, 84),
mask = MASK_NPCSOLID,
filter = { target }
})
if (
(!tracehull.Hit) and
(minimum_down_timer >= 1.0) and
(target.class_in_previous_life != nil) and
!(target.organism.llegamputated or target.organism.rlegamputated or target.organism.larmamputated or target.organism.rarmamputated) and
@ -336,8 +376,14 @@ function MODULE:PhysicsSimulate(phys, dt)
self.StopProcessing = true
self:Remove()
self.bullseye:Remove()
return false
end
end
-- Getting up
-- Don't need to check for consciousness and the like because we've done it already above
local phys_bone_id = phys:GetID()
@ -404,6 +450,8 @@ end
function MODULE:OnRemove()
local timer_name = "Fedhoria_FallingLegs_Twitch_" .. self:EntIndex()
timer_Remove(timer_name)
if self.bullseye then self.bullseye:Remove() end
end
--[[-------------------------------------------------------------------------