diff --git a/README.md b/README.md index 4c62fb5..1438dde 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/lua/entities/active_ragdoll_controller.lua b/lua/entities/active_ragdoll_controller.lua index 67cb16e..d2ae8ba 100644 --- a/lua/entities/active_ragdoll_controller.lua +++ b/lua/entities/active_ragdoll_controller.lua @@ -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 diff --git a/lua/zcnpci.lua b/lua/zcnpci.lua index 7843ecc..e7198d9 100644 --- a/lua/zcnpci.lua +++ b/lua/zcnpci.lua @@ -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 diff --git a/lua/zcnpci/modules/falling_legs.lua b/lua/zcnpci/modules/falling_legs.lua index 1a508dc..3bebf44 100644 --- a/lua/zcnpci/modules/falling_legs.lua +++ b/lua/zcnpci/modules/falling_legs.lua @@ -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 --[[-------------------------------------------------------------------------