Compare commits

...

5 commits

8 changed files with 384 additions and 93 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.
@ -12,9 +14,9 @@ This addon is based off of Kazarei's Euphoria, which in turn is a fork of Fedhor
## Known issues ## Known issues
- 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 right now, though if anyone experienced with Z-City's codebase would know how to I would appreciate knowing.
- NPCs currently instantly get up, with no animation. I plan on remedying this in the future. - The NPC getting up animation is quite ridiculous and a little ugly. Honestly, getting it to work that much was a struggle in itself and I'm terrified of trying to do anything more than that. If anyone else would like to do that, however, feel free to create a discussion and I can send you the source code link.
- Enemy NPCs will not target living downed NPCs like how they do players. This will be fixed. - Occasionally the game thinks that all damage is bullet damage? God knows why. I'll figure it out eventually.
## Compatibility ## Compatibility
@ -28,10 +30,27 @@ It is, however, not compatbile with:
- Most mods that modify the behavior of death ragdolls (Reagdoll, Artagdoll, especially Fedhoria.) - Most mods that modify the behavior of death ragdolls (Reagdoll, Artagdoll, especially Fedhoria.)
- Mods that automatically remove death ragdolls (it will delete all ragdolled NPCs, even if they're still alive! Try the built-in automatic corpse removal instead.) - Mods that automatically remove death ragdolls (it will delete all ragdolled NPCs, even if they're still alive! Try the built-in automatic corpse removal instead.)
## Future plans ## Todo list
No guarantees any of this will become real. Urgent shit:
- NPCs will back out of combat when injured. - Large amounts of damage (such as shotguns) fling NPC ragdolls to ridiculous lengths, sometimes severely injuring the player. While this is absolutely fucking hilarious, it also isn't the best experience.
- Friendly NPCs can heal each other.
- NPCs can heal themselves. Stuff to do:
- Facial expressions
- Actual specific reactions to being on fire / falling / lots of pain
- Wound holding
- NPCs should not stumble when dead
- Generally improve stumbling
Stuff to fix possibly:
- NPC targeting works, but works really weirdly (sometimes it just. keeps targeting corpses when it shouldn't.) Why? Who knows!!!!
- Look into improving the unfake animation? Really scared to try and touch it again but it would be worthwhile
Post release ideas:
- Localization support for other languages
- ReaSFX integration would probably be nice. I mean there's so many SFX packs to choose from, so I don't even have to pack it in
- To be entirely honest Fedhoria isn't the best addon out there for euphoria physics. Maybe rebase to another addon (or alternatively build a new one?)

View file

@ -38,6 +38,12 @@ local function PopulateRagdollSBXToolMenu(pnl)
pnl:NumSlider("Wound grab time", "zcnpci_woundgrab_time", 0, 10, 3) pnl:NumSlider("Wound grab time", "zcnpci_woundgrab_time", 0, 10, 3)
pnl:ControlHelp("How long the ragdoll should hold its wound.") pnl:ControlHelp("How long the ragdoll should hold its wound.")
pnl:CheckBox("Allow getting up", "zcnpci_unfake_enabled")
pnl:ControlHelp("If enabled, NPCs will get back up if able.")
pnl:NumSlider("Get up time", "zcnpci_unfake_time", 0, 5, 3)
pnl:ControlHelp("How long the getting up animation lasts. Setting to 0 will disable the getting up animation.")
pnl:NumSlider("Minimum down time", "zcnpci_down_time", 0, 20, 3) pnl:NumSlider("Minimum down time", "zcnpci_down_time", 0, 20, 3)
pnl:ControlHelp("The minimum amount of time the ragdoll should be down before trying to get back up.") pnl:ControlHelp("The minimum amount of time the ragdoll should be down before trying to get back up.")
end end

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

@ -0,0 +1,99 @@
AddCSLuaFile()
ENT.Type = "ai"
ENT.Base = "base_ai"
ENT.AutomaticFrameAdvance = true
local debug_show_ragdoll_targets = CreateConVar("zcnpci_debug_show_ragdoll_targets", 0, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
local function UpdateRelationship(ent, me)
if !IsValid(ent) or !ent:IsNPC() then return end
if ent:Disposition(me.dummy_entity) == D_HT then
ent:AddEntityRelationship(me, D_HT, -1)
end
end
local function RemoveRelationship(ent, me)
if !IsValid(ent) or !ent:IsNPC() then return end
if ent:Disposition(me.dummy_entity) == D_HT then
ent:AddEntityRelationship(me, D_NU, -1)
ent:ClearEnemyMemory(me)
if ent:GetEnemy() == me then
ent:SetEnemy(nil)
ent:ClearSchedule()
end
end
end
function ENT:Initialize()
if SERVER then
self:SetModel("models/maxofs2d/hover_basic.mdl")
self:SetCollisionGroup(COLLISION_GROUP_DEBRIS)
self:SetHullType(HULL_TINY_CENTERED)
self:SetNoDraw(!debug_show_ragdoll_targets:GetBool())
self.dummy_entity = ents.Create(self.ragdoll_to_follow.class_in_previous_life)
self.last_stop_targeting = nil
hook.Add("OnEntityCreated", "zcnpci-"..self:GetCreationID(), function(new_entity)
if !IsValid(new_entity) or !new_entity:IsNPC() then return end
if !self.last_stop_targeting then
UpdateRelationship(new_entity, self)
end
end)
end
end
function ENT:Think()
if SERVER then
local ragdoll = self.ragdoll_to_follow
if !IsValid(ragdoll) then return end
self:SetPos(ragdoll:GetPos())
self:SetNoDraw(!debug_show_ragdoll_targets:GetBool())
if !ragdoll.organism then return true end
local should_stop_targeting = (
(ragdoll.organism.consciousness <= 0.4) or
(ragdoll.organism.critical)
)
if should_stop_targeting != self.last_stop_targeting then
self.last_stop_targeting = should_stop_targeting
if should_stop_targeting then
self:SetColor(Color(255, 0, 0))
for i,ent in ipairs(ents.GetAll()) do
RemoveRelationship(ent, self)
end
else
self:SetColor(Color(0, 255, 0))
for i,ent in ipairs(ents.GetAll()) do
UpdateRelationship(ent, self)
end
end
end
self:NextThink(CurTime())
end
return true
end
function ENT:OnRemove()
if SERVER then
for i,ent in ipairs(ents.GetAll()) do
RemoveRelationship(ent, self)
end
self.dummy_entity:Remove()
hook.Remove("OnEntityCreated", "zcnpci-"..self:GetCreationID())
end
end

View file

@ -19,7 +19,9 @@ local treat_near_death_as_dead = CreateConVar("zcnpci_treat_near_death_as_dead"
local allow_extended_base_npcs = CreateConVar("zcnpci_allow_extended_base_npcs", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED)) local allow_extended_base_npcs = CreateConVar("zcnpci_allow_extended_base_npcs", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
local allow_modded_npcs = CreateConVar("zcnpci_allow_modded_npcs", 0, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED)) local allow_modded_npcs = CreateConVar("zcnpci_allow_modded_npcs", 0, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
local auto_enable_humanoid_npcs = CreateConVar("zcnpci_auto_enable_humanoid_npcs", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED)) local auto_enable_humanoid_npcs = CreateConVar("zcnpci_auto_enable_humanoid_npcs", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
local modded_npc_whitelist = CreateConVar("zcnpci_modded_npc_whitelist", "nb_example", bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED)) local modded_npc_whitelist = CreateConVar("zcnpci_modded_npc_whitelist", "npc_example_class", bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
local debug_ragdoll_all = CreateConVar("zcnpci_debug_ragdoll_all", 0, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
-- The way I have to set up the npc whitelist client-side makes it so the host can't configure it without console commands, so we have to do this shit -- The way I have to set up the npc whitelist client-side makes it so the host can't configure it without console commands, so we have to do this shit
local set_modded_npc_whitelist = concommand.Add("zcnpci_set_modded_npc_whitelist", function(ply, cmd, args) local set_modded_npc_whitelist = concommand.Add("zcnpci_set_modded_npc_whitelist", function(ply, cmd, args)
@ -30,8 +32,6 @@ local set_modded_npc_whitelist = concommand.Add("zcnpci_set_modded_npc_whitelis
modded_npc_whitelist:SetString(args[1]) modded_npc_whitelist:SetString(args[1])
end) end)
local debug_ragdoll_all = CreateConVar("zcnpci_debug_ragdoll_all", 0, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
local last_dmgpos = {} local last_dmgpos = {}
local last_hitgroup = {} local last_hitgroup = {}
local last_attacker = {} local last_attacker = {}
@ -121,6 +121,11 @@ hook.Add("CreateEntityRagdoll", "zcnpci", function(ent, ragdoll)
ragdoll:GetPhysicsObject():SetVelocity(ent.npcfakeknockback) ragdoll:GetPhysicsObject():SetVelocity(ent.npcfakeknockback)
end end
local velocity = ragdoll:GetPhysicsObject():GetVelocity()
print(velocity)
print(max_clamp)
-- For some reason, citizen types such as rebels, medics, refugees, etc. use a keyvalue. -- For some reason, citizen types such as rebels, medics, refugees, etc. use a keyvalue.
-- That keyvalue does not transfer over to the ragdoll, despite it looking like the NPC, -- That keyvalue does not transfer over to the ragdoll, despite it looking like the NPC,
-- and you have to manually save it to the ragdoll to be able to make it work. -- and you have to manually save it to the ragdoll to be able to make it work.
@ -128,9 +133,11 @@ hook.Add("CreateEntityRagdoll", "zcnpci", function(ent, ragdoll)
ragdoll.citizentype = ent:GetInternalVariable("citizentype") ragdoll.citizentype = ent:GetInternalVariable("citizentype")
timer.Simple(0, function() timer.Simple(0, function()
if !IsValid(ragdoll) then return end
ragdoll.organism.alive = true ragdoll.organism.alive = true
if !IsValid(ragdoll) then return end ragdoll:SetRenderMode(RENDERMODE_NORMAL)
zcnpci.StartModule(ragdoll, "stumble_legs", phys_bone, lpos) zcnpci.StartModule(ragdoll, "stumble_legs", phys_bone, lpos)
last_dmgpos[ent] = nil last_dmgpos[ent] = nil
@ -139,36 +146,44 @@ 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
if !ent.organism or !ent.StartDie then return end if !ent.organism or !ent.StartDie then return end
if dmginfo:IsDamageType(DMG_BULLET + DMG_BUCKSHOT + DMG_BLAST + DMG_CLUB + DMG_SLASH + DMG_GENERIC) then if dmginfo:IsDamageType(DMG_BULLET + DMG_BUCKSHOT + DMG_BLAST + DMG_CLUB + DMG_SLASH + DMG_GENERIC) then
-- Reset ragdoll get up timer -- don't want someone getting up mid-curbstomp -- Reset ragdoll get up timer -- don't want someone getting up mid-curbstomp
ent.StartDie = CurTime() ent.StartDie = CurTime()
print("I'M HIT FOR "..dmginfo:GetDamage())
end end
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
if dmginfo:GetDamage() > 3 then if dmginfo:GetDamage() > 3 then
table.insert(npcs_to_fake, ent) table.insert(npcs_to_fake, ent)
local attacker_angle = dmginfo:GetAttacker():EyeAngles()
local normal = attacker_angle:Forward(normal)
ent.npcfakeknockback = normal * dmginfo:GetDamage() * 0
end end
elseif dmginfo:IsDamageType(DMG_CLUB + DMG_SLASH) then elseif dmginfo:IsDamageType(DMG_CLUB + DMG_SLASH) then
local fists = dmginfo:GetAttacker():GetWeapon("weapon_hands_sh") local attacker = dmginfo:GetAttacker()
if !IsValid(fists) then fists = dmginfo:GetAttacker():GetWeapon("weapon_hg_coolhands") end if !IsValid(attacker) then return end
local fists = attacker:HasWeapon("weapon_hands_sh")
if !fists then fists = dmginfo:GetAttacker():HasWeapon("weapon_hg_coolhands") end
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 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 +303,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 +317,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

@ -40,6 +40,31 @@ local twitchable_bone_names = {
"ValveBiped.Bip01_L_Foot" "ValveBiped.Bip01_L_Foot"
} }
local fakeup_bone_names = {
"ValveBiped.Bip01_Pelvis",
--[[]"ValveBiped.Bip01_Spine2",]]
"ValveBiped.Bip01_Head1",
--[[]"ValveBiped.Bip01_R_Thigh",
"ValveBiped.Bip01_L_Thigh",
"ValveBiped.Bip01_L_Calf",
"ValveBiped.Bip01_R_Calf",
"ValveBiped.Bip01_R_Foot",
"ValveBiped.Bip01_L_Foot",
--[["ValveBiped.Bip01_R_Upperarm",
--[["ValveBiped.Bip01_L_Upperarm",
"ValveBiped.Bip01_R_Forearm",
"ValveBiped.Bip01_L_Forearm",
"ValveBiped.Bip01_R_Hand",
"ValveBiped.Bip01_L_Hand",]]
}
local fakeup_bone_down_names = {
"ValveBiped.Bip01_R_Forearm",
"ValveBiped.Bip01_L_Forearm",
"ValveBiped.Bip01_R_Thigh",
"ValveBiped.Bip01_L_Thigh",
}
-- Local Copies of Functions for Optimization -- Local Copies of Functions for Optimization
local math_Clamp = math.Clamp local math_Clamp = math.Clamp
local math_Rand = math.Rand local math_Rand = math.Rand
@ -69,7 +94,11 @@ 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!!!!!!!!!")
-- Unfaking
local can_unfake = CreateConVar("zcnpci_unfake_enabled", "1", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Whether an NPC can unfake")
local unfake_time = CreateConVar("zcnpci_unfake_time", "1.5", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Time it takes for an NPC to unfake")
--[[------------------------------------------------------------------------- --[[-------------------------------------------------------------------------
Tug function Tug function
@ -165,7 +194,13 @@ 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_ragdoll_target")
self.bullseye:SetPos(target:GetPos())
self.bullseye.ragdoll_to_follow = target
self.bullseye:Spawn()
self.bullseye:Activate()
-- Add damping to all bones. -- Add damping to all bones.
local phys = self:GetPhysicsObject() local phys = self:GetPhysicsObject()
@ -189,13 +224,22 @@ end
---------------------------------------------------------------------------]] ---------------------------------------------------------------------------]]
function MODULE:Think() function MODULE:Think()
if (CurTime() - self.LastThink) < 1 then return end --if (CurTime() - self.LastThink) < 1 then return end
local target = self:GetTarget()
if !IsValid(target) then return end if !IsValid(target) then return end
self.LastThink = CurTime() self.LastThink = CurTime()
local phys = target:GetPhysicsObject() local phys = target:GetPhysicsObject()
if target.FakeUp then
print("fucking it")
local parent = self.FakeParent
if !IsValid(parent) then return end
--parent:FrameAdvance(FrameTime())
end
if !IsValid(phys) or !phys:IsAsleep() then return end if !IsValid(phys) or !phys:IsAsleep() then return end
phys:Wake() phys:Wake()
@ -242,15 +286,84 @@ end
Physics Simulation Hook (FIXED) Physics Simulation Hook (FIXED)
---------------------------------------------------------------------------]] ---------------------------------------------------------------------------]]
function MODULE:PhysicsSimulate(phys, dt) 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
self.LastPhysProcess = CurTime()
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
if target.FakeUp then
local parent = self.FakeParent
if !IsValid(parent) then
self:Remove()
return false
end
if !self.ModelBoneList then
self.ModelBoneList = {}
local i = 0
while i < target:GetBoneCount() do
table.insert(self.ModelBoneList, target:GetBoneName(i))
i = i + 1
end
end
local animation_progress = (CurTime() - self.FakeUpStart) / (self.FakeUpEnd - self.FakeUpStart)
--[[util.TraceLine({
start = parent:GetPos() + Vector(0, 128, 0),
endpos = parent:GetPos(),
collisiongroup = COLLISION_GROUP_NPC
})]]
for i,v in pairs(fakeup_bone_down_names) do
local object = target:GetPhysicsObjectNum(target:TranslateBoneToPhysBone(target:LookupBone(v)))
object:SetMass(0.5)
end
for i,v in pairs(fakeup_bone_names) do
local object = target:GetPhysicsObjectNum(target:TranslateBoneToPhysBone(target:LookupBone(v)))
local parent_bone = parent:LookupBone(v)
if parent_bone == -1 then continue end
local parent_bone_matrix = parent:GetBoneMatrix(parent_bone)
if !parent_bone_matrix then continue end
local parent_bone_pos, parent_bone_angle = parent_bone_matrix:GetTranslation(), parent_bone_matrix:GetAngles()
parent_bone_angle.y = parent_bone_angle.y + 90
local shadow_data = {
secondstoarrive = 0.01,
pos = LerpVector(animation_progress, object:GetPos(), parent_bone_pos),
angle = LerpAngle(animation_progress, object:GetAngles(), parent_bone_angle),
maxspeed = 400,
maxangular = 2000,
maxspeeddamp = 60,
maxangularspeeddamp = 600,
}
-- Can't set position inside physics tick, crashes the game instantly.
-- Instead, send a shadow for it to follow (I think? I don't know GMod is kinda funky)
object:ComputeShadowControl(shadow_data)
object:Wake()
object:EnableGravity(false)
object:Wake()
i = i + 1
end
return false
end
--print((CurTime() - self.LastPhysProcess) < 0.05)
--if (CurTime() - self.LastPhysProcess) < (1 /) then return false end())
phys:Wake()
--self.bullseye:SetAngles(target:EyeAngles())
target.is_npc_corpse = true target.is_npc_corpse = true
@ -272,21 +385,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) and (can_unfake:GetBool()) 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
@ -311,6 +440,9 @@ function MODULE:PhysicsSimulate(phys, dt)
ent:Spawn() ent:Spawn()
ent:SetNotSolid(true)
ent:SetNPCState(NPC_STATE_NONE)
timer.Simple(0, function() timer.Simple(0, function()
hg.organism.Add(ent) hg.organism.Add(ent)
table.Merge(ent.organism, target.organism) table.Merge(ent.organism, target.organism)
@ -321,6 +453,8 @@ function MODULE:PhysicsSimulate(phys, dt)
ent.organism.alive = true ent.organism.alive = true
ent.organism.owner = ent ent.organism.owner = ent
target.organism = nil
ent:CallOnRemove("organism", hg.organism.Remove, ent) ent:CallOnRemove("organism", hg.organism.Remove, ent)
hg.send_bareinfo(ent.organism) hg.send_bareinfo(ent.organism)
@ -329,15 +463,40 @@ function MODULE:PhysicsSimulate(phys, dt)
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()
end) end)
self.StopProcessing = true target.FakeUp = true
self.FakeParent = ent
self.FakeUpTime = unfake_time:GetFloat()
self.FakeUpEnd = CurTime() + self.FakeUpTime
self.FakeUpStart = CurTime()
ent:SetAngles(Angle(0, math.random(-180, 180), 0))
local phys = target:GetPhysicsObject()
phys:EnableGravity(false)
target:SetNotSolid(true)
target:SetMoveType(MOVETYPE_NONE)
ent:SetRenderMode(RENDERMODE_NONE)
timer.Simple(self.FakeUpTime, function()
if !IsValid(ent) then return end
ent:SetNotSolid(false)
ent:SetNPCState(NPC_STATE_IDLE)
ent:SetRenderMode(RENDERMODE_NORMAL)
target:Remove()
self:Remove() self:Remove()
end)
self.bullseye:Remove()
return false return false
end end
end
local phys_bone_id = phys:GetID() local phys_bone_id = phys:GetID()
@ -404,6 +563,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 IsValid(self.bullseye) then self.bullseye:Remove() end
end end
--[[------------------------------------------------------------------------- --[[-------------------------------------------------------------------------

View file

@ -170,7 +170,10 @@ end
function MODULE:PhysicsSimulate(phys, dt) function MODULE:PhysicsSimulate(phys, dt)
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 !IsValid(target) then self:Remove(); return false end -- если цели нет, то идем нахуй
if !target.organism then self:Remove(); return false end
if target.FakeUp then self.AnimationRollEndTime = 0; return false end
-- проверка на физику -- проверка на физику
if cur_time < self.AnimationRollEndTime then if cur_time < self.AnimationRollEndTime then

View file

@ -279,6 +279,8 @@ local trace = {output={}}
local tr = trace.output local tr = trace.output
function MODULE:PhysicsSimulate(phys, dt) function MODULE:PhysicsSimulate(phys, dt)
if self.FakeUp then return end
local phys_bone = phys:GetID() local phys_bone = phys:GetID()
local target = self:GetTarget() local target = self:GetTarget()
@ -291,6 +293,11 @@ function MODULE:PhysicsSimulate(phys, dt)
local st = stumble_time:GetFloat() local st = stumble_time:GetFloat()
-- End stumbling if player is dead
if !target.organism or !target.organism.alive then
st = 0
end
if (st <= 0) then if (st <= 0) then
self:Remove() self:Remove()
return false return false
@ -298,16 +305,6 @@ function MODULE:PhysicsSimulate(phys, dt)
local f = 1 - (CurTime() - self.Created) / st local f = 1 - (CurTime() - self.Created) / st
--RagMod Reworked support
if ragmod and ragmod:IsRagmodRagdoll(target) then
local owner = target:GetOwningPlayer()
if !IsValid(owner) or !owner:Alive() then
f = 0
else
f = 1
end
end
if (f <= 0) then if (f <= 0) then
self:Remove() self:Remove()
return false return false