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! - 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 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. - 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. 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. - 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. - 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 ## Compatibility

View file

@ -97,7 +97,7 @@ end
function ENT:Initialize() function ENT:Initialize()
if !self.Module then return end 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 for key, value in pairs(self.Module) do
if !self[key] then if !self[key] then

View file

@ -137,6 +137,20 @@ hook.Add("CreateEntityRagdoll", "zcnpci", function(ent, ragdoll)
end) end)
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) hook.Add("HomigradDamage", "zcnpci", function(ent, dmginfo)
if !enabled:GetBool() or !IsValid(dmginfo) then return end if !enabled:GetBool() or !IsValid(dmginfo) then return end
if !ent:IsNPC() then if !ent:IsNPC() then
@ -150,7 +164,7 @@ hook.Add("HomigradDamage", "zcnpci", function(ent, dmginfo)
return return
end end
last_dmgpos[ent] = dmginfo:GetDamage() last_dmgpos[ent] = dmginfo:GetDamagePosition()
last_attacker[ent] = dmginfo:GetAttacker() last_attacker[ent] = dmginfo:GetAttacker()
if dmginfo:IsDamageType(DMG_BULLET + DMG_BUCKSHOT + DMG_BLAST) then 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() local attacker = dmginfo:GetAttacker()
print(fists.InLegKick)
-- Kicks should knock NPCs down -- Kicks should knock NPCs down
if IsValid(dmginfo:GetInflictor()) and IsValid(fists) and attacker.InLegKick and ((attacker.InLegKick + 0.1) > CurTime()) then if IsValid(dmginfo:GetInflictor()) and IsValid(fists) and attacker.InLegKick and ((attacker.InLegKick + 0.1) > CurTime()) then
table.insert(npcs_to_fake, ent) table.insert(npcs_to_fake, ent)
print("ADD TO LIST!!!")
local attacker_angle = dmginfo:GetAttacker():EyeAngles() local attacker_angle = dmginfo:GetAttacker():EyeAngles()
@ -288,8 +299,6 @@ hook.Add("Tick", "zcnpci", function()
end end
end end
PrintTable(npcs_to_fake)
-- NPC faking is in a seperate area because we want NPCs to ragdoll as soon as they can -- 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 for i,ent in pairs(npcs_to_fake) do
local damage_info = DamageInfo() local damage_info = DamageInfo()
@ -304,13 +313,6 @@ hook.Add("Tick", "zcnpci", function()
end end
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) hook.Add("OnEntityCreated", "zcnpci", function(ent)
if !IsValid(ent) then return end 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}, "Скорость воспроизведения анимации") local cv_anim_roll_playback_rate = CreateConVar("zcnpci_falling_anim_roll_playback_rate", "3.0", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Скорость воспроизведения анимации")
-- Down time -- 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 Tug function
@ -165,7 +165,28 @@ function MODULE:Init()
self.AnimationRollEndTime = 0 self.AnimationRollEndTime = 0
self.StopProcessing = false self.StopProcessing = false
self.LastThink = CurTime() 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. -- Add damping to all bones.
local phys = self:GetPhysicsObject() local phys = self:GetPhysicsObject()
@ -244,13 +265,16 @@ end
function MODULE:PhysicsSimulate(phys, dt) function MODULE:PhysicsSimulate(phys, dt)
if self.StopProcessing then return false end if self.StopProcessing then return false end
--print((CurTime() - self.LastPhysProcess) < 0.05) --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 cur_time = CurTime()
local target = self:GetTarget() 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 target.is_npc_corpse = true
@ -272,21 +296,37 @@ function MODULE:PhysicsSimulate(phys, dt)
if !target.organism then if !target.organism then
self:Remove() self:Remove()
self.bullseye:Remove()
return false -- Cut the bullshit return false -- Cut the bullshit
end 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()
self.bullseye:Remove()
return false -- Cut the bullshit return false -- Cut the bullshit
elseif (target.organism.consciousness <= 0.5) or ((target.organism.lleg >= 0.85) and (target.organism.rleg >= 0.85)) then elseif (target.organism.consciousness <= 0.5) or ((target.organism.lleg >= 0.85) and (target.organism.rleg >= 0.85)) then
target.StartDie = cur_time target.StartDie = cur_time
return false return false
end end
-- Getting up if (CurTime() - self.LastFakeUpCheck) >= 1.0 then
-- Don't need to check for consciousness and the like because we've done it already above 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 ( if (
(!tracehull.Hit) and
(minimum_down_timer >= 1.0) and (minimum_down_timer >= 1.0) and
(target.class_in_previous_life != nil) and (target.class_in_previous_life != nil) and
!(target.organism.llegamputated or target.organism.rlegamputated or target.organism.larmamputated or target.organism.rarmamputated) 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.StopProcessing = true
self:Remove() self:Remove()
self.bullseye:Remove()
return false return false
end 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() local phys_bone_id = phys:GetID()
@ -404,6 +450,8 @@ end
function MODULE:OnRemove() function MODULE:OnRemove()
local timer_name = "Fedhoria_FallingLegs_Twitch_" .. self:EntIndex() local timer_name = "Fedhoria_FallingLegs_Twitch_" .. self:EntIndex()
timer_Remove(timer_name) timer_Remove(timer_name)
if self.bullseye then self.bullseye:Remove() end
end end
--[[------------------------------------------------------------------------- --[[-------------------------------------------------------------------------