NPCs no longer spawn inside other entities, various bugfixes
This commit is contained in:
parent
d9415d880f
commit
259a3da4e1
4 changed files with 113 additions and 62 deletions
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,73 +296,95 @@ 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()
|
||||||
if (
|
|
||||||
(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
|
|
||||||
(target.organism.pain <= 80) and
|
|
||||||
(target.organism.consciousness > 0.65) and
|
|
||||||
!target:IsOnFire()
|
|
||||||
) then
|
|
||||||
local ent = ents.Create(target.class_in_previous_life)
|
|
||||||
ent:SetPos(target:GetPos())
|
|
||||||
ent:SetModel(target:GetModel())
|
|
||||||
ent:SetMaterial(target:GetMaterial())
|
|
||||||
|
|
||||||
ent:SetSkin(target:GetSkin())
|
-- 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 target.citizentype then
|
if (
|
||||||
ent:SetKeyValue("citizentype", target.citizentype)
|
(!tracehull.Hit) and
|
||||||
end
|
(minimum_down_timer >= 1.0) and
|
||||||
|
(target.class_in_previous_life != nil) and
|
||||||
for i = 0, target:GetNumBodyGroups() - 1 do
|
!(target.organism.llegamputated or target.organism.rlegamputated or target.organism.larmamputated or target.organism.rarmamputated) and
|
||||||
ent:SetBodygroup(i, target:GetBodygroup(i))
|
(target.organism.pain <= 80) and
|
||||||
end
|
(target.organism.consciousness > 0.65) and
|
||||||
|
!target:IsOnFire()
|
||||||
ent:Spawn()
|
) then
|
||||||
|
local ent = ents.Create(target.class_in_previous_life)
|
||||||
timer.Simple(0, function()
|
ent:SetPos(target:GetPos())
|
||||||
hg.organism.Add(ent)
|
ent:SetModel(target:GetModel())
|
||||||
table.Merge(ent.organism, target.organism)
|
ent:SetMaterial(target:GetMaterial())
|
||||||
|
|
||||||
ent.tourniquets = table.Copy(target.tourniquets)
|
|
||||||
ent.bandaged_limbs = table.Copy(target.bandaged_limbs)
|
|
||||||
|
|
||||||
ent.organism.alive = true
|
|
||||||
ent.organism.owner = ent
|
|
||||||
|
|
||||||
ent:CallOnRemove("organism", hg.organism.Remove, ent)
|
|
||||||
hg.send_bareinfo(ent.organism)
|
|
||||||
|
|
||||||
ent:SetSkin(target:GetSkin())
|
ent:SetSkin(target:GetSkin())
|
||||||
|
|
||||||
|
if target.citizentype then
|
||||||
|
ent:SetKeyValue("citizentype", target.citizentype)
|
||||||
|
end
|
||||||
|
|
||||||
for i = 0, target:GetNumBodyGroups() - 1 do
|
for i = 0, target:GetNumBodyGroups() - 1 do
|
||||||
ent:SetBodygroup(i, target:GetBodygroup(i))
|
ent:SetBodygroup(i, target:GetBodygroup(i))
|
||||||
end
|
end
|
||||||
|
|
||||||
target:Remove()
|
ent:Spawn()
|
||||||
end)
|
|
||||||
|
|
||||||
self.StopProcessing = true
|
timer.Simple(0, function()
|
||||||
|
hg.organism.Add(ent)
|
||||||
|
table.Merge(ent.organism, target.organism)
|
||||||
|
|
||||||
self:Remove()
|
ent.tourniquets = table.Copy(target.tourniquets)
|
||||||
return false
|
ent.bandaged_limbs = table.Copy(target.bandaged_limbs)
|
||||||
|
|
||||||
|
ent.organism.alive = true
|
||||||
|
ent.organism.owner = ent
|
||||||
|
|
||||||
|
ent:CallOnRemove("organism", hg.organism.Remove, ent)
|
||||||
|
hg.send_bareinfo(ent.organism)
|
||||||
|
|
||||||
|
ent:SetSkin(target:GetSkin())
|
||||||
|
|
||||||
|
for i = 0, target:GetNumBodyGroups() - 1 do
|
||||||
|
ent:SetBodygroup(i, target:GetBodygroup(i))
|
||||||
|
end
|
||||||
|
|
||||||
|
target:Remove()
|
||||||
|
end)
|
||||||
|
|
||||||
|
self.StopProcessing = true
|
||||||
|
|
||||||
|
self:Remove()
|
||||||
|
self.bullseye:Remove()
|
||||||
|
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()
|
||||||
|
|
||||||
-- Main logic for the root bone
|
-- Main logic for the root bone
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
--[[-------------------------------------------------------------------------
|
--[[-------------------------------------------------------------------------
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue