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!
|
||||
- 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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
@ -287,8 +298,6 @@ hook.Add("Tick", "zcnpci", function()
|
|||
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
|
||||
for i,ent in pairs(npcs_to_fake) do
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,73 +296,95 @@ 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 (
|
||||
(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())
|
||||
if (CurTime() - self.LastFakeUpCheck) >= 1.0 then
|
||||
self.LastFakeUpCheck = CurTime()
|
||||
|
||||
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
|
||||
ent:SetKeyValue("citizentype", target.citizentype)
|
||||
end
|
||||
|
||||
for i = 0, target:GetNumBodyGroups() - 1 do
|
||||
ent:SetBodygroup(i, target:GetBodygroup(i))
|
||||
end
|
||||
|
||||
ent:Spawn()
|
||||
|
||||
timer.Simple(0, function()
|
||||
hg.organism.Add(ent)
|
||||
table.Merge(ent.organism, target.organism)
|
||||
|
||||
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)
|
||||
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
|
||||
(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())
|
||||
|
||||
if target.citizentype then
|
||||
ent:SetKeyValue("citizentype", target.citizentype)
|
||||
end
|
||||
|
||||
for i = 0, target:GetNumBodyGroups() - 1 do
|
||||
ent:SetBodygroup(i, target:GetBodygroup(i))
|
||||
end
|
||||
|
||||
target:Remove()
|
||||
end)
|
||||
ent:Spawn()
|
||||
|
||||
self.StopProcessing = true
|
||||
timer.Simple(0, function()
|
||||
hg.organism.Add(ent)
|
||||
table.Merge(ent.organism, target.organism)
|
||||
|
||||
self:Remove()
|
||||
return false
|
||||
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())
|
||||
|
||||
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
|
||||
|
||||
-- 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()
|
||||
|
||||
-- Main logic for the root bone
|
||||
|
|
@ -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
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Reference in a new issue