From 278a0a9d7d299dbc0b2a4c24d0513842cf732a37 Mon Sep 17 00:00:00 2001 From: toasterpanic Date: Sat, 30 May 2026 15:02:58 -0400 Subject: [PATCH] NPCs now have a getting up animation (if I have to try and work on it again I'm going to kill someone), various new configurations and optimizations --- README.md | 5 +- lua/autorun/client/zcnpci_menu.lua | 6 + lua/zcnpci.lua | 4 +- lua/zcnpci/modules/falling_legs.lua | 210 +++++++++++++++++++++++++-- lua/zcnpci/modules/falling_torso.lua | 7 +- lua/zcnpci/modules/stumble_legs.lua | 2 + 6 files changed, 218 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 1438dde..1de6321 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,9 @@ This addon is based off of Kazarei's Euphoria, which in turn is a fork of Fedhor ## 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. -- NPCs currently instantly get up, with no animation. I plan on remedying this in the future. +- 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. +- 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. +- Occasionally the game thinks that all damage is bullet damage? God knows why. I'll figure it out eventually. ## Compatibility diff --git a/lua/autorun/client/zcnpci_menu.lua b/lua/autorun/client/zcnpci_menu.lua index d608c99..bea33e1 100644 --- a/lua/autorun/client/zcnpci_menu.lua +++ b/lua/autorun/client/zcnpci_menu.lua @@ -37,6 +37,12 @@ local function PopulateRagdollSBXToolMenu(pnl) pnl:NumSlider("Wound grab time", "zcnpci_woundgrab_time", 0, 10, 3) 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:ControlHelp("The minimum amount of time the ragdoll should be down before trying to get back up.") diff --git a/lua/zcnpci.lua b/lua/zcnpci.lua index e7198d9..3213fb8 100644 --- a/lua/zcnpci.lua +++ b/lua/zcnpci.lua @@ -128,9 +128,11 @@ hook.Add("CreateEntityRagdoll", "zcnpci", function(ent, ragdoll) ragdoll.citizentype = ent:GetInternalVariable("citizentype") timer.Simple(0, function() + if !IsValid(ragdoll) then return end + ragdoll.organism.alive = true - if !IsValid(ragdoll) then return end + ragdoll:SetRenderMode(RENDERMODE_NORMAL) zcnpci.StartModule(ragdoll, "stumble_legs", phys_bone, lpos) last_dmgpos[ent] = nil diff --git a/lua/zcnpci/modules/falling_legs.lua b/lua/zcnpci/modules/falling_legs.lua index 3bebf44..6a097ad 100644 --- a/lua/zcnpci/modules/falling_legs.lua +++ b/lua/zcnpci/modules/falling_legs.lua @@ -40,6 +40,31 @@ local twitchable_bone_names = { "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 math_Clamp = math.Clamp local math_Rand = math.Rand @@ -71,6 +96,10 @@ local cv_anim_roll_playback_rate = CreateConVar("zcnpci_falling_anim_roll_playba -- 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 Applies a random impulse to one of the leg bones. @@ -184,6 +213,7 @@ function MODULE:Init() if ent:Disposition(self.dummy_entity) == D_HT then ent:AddEntityRelationship(self.bullseye, D_HT, -1) + print("HATE. HATE. HATE") end end @@ -210,12 +240,23 @@ end ---------------------------------------------------------------------------]] function MODULE:Think() - if (CurTime() - self.LastThink) < 1 then return end + --if (CurTime() - self.LastThink) < 1 then return end + print("thinking") + + local target = self:GetTarget() if !IsValid(target) then return end self.LastThink = CurTime() 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 @@ -263,15 +304,82 @@ end Physics Simulation Hook (FIXED) ---------------------------------------------------------------------------]] function MODULE:PhysicsSimulate(phys, dt) - if self.StopProcessing then return false end + local cur_time = CurTime() + local target = self:GetTarget() + if not IsValid(target) then self:Remove(); self.bullseye:Remove(); return false end + + if target.FakeUp then + print("fucking it") + 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) + 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() - local cur_time = CurTime() - local target = self:GetTarget() - if not IsValid(target) then self:Remove(); self.bullseye:Remove(); return false end self.bullseye:SetPos(target:GetPos()) --self.bullseye:SetAngles(target:EyeAngles()) @@ -310,7 +418,7 @@ function MODULE:PhysicsSimulate(phys, dt) return false end - if (CurTime() - self.LastFakeUpCheck) >= 1.0 then + if ((CurTime() - self.LastFakeUpCheck) >= 1.0) and (can_unfake:GetBool()) then self.LastFakeUpCheck = CurTime() -- This basically checks if the NPC will be in collision with anything when it spawns. @@ -351,6 +459,9 @@ function MODULE:PhysicsSimulate(phys, dt) ent:Spawn() + ent:SetNotSolid(true) + ent:SetNPCState(NPC_STATE_NONE) + timer.Simple(0, function() hg.organism.Add(ent) table.Merge(ent.organism, target.organism) @@ -361,6 +472,8 @@ function MODULE:PhysicsSimulate(phys, dt) ent.organism.alive = true ent.organism.owner = ent + target.organism = nil + ent:CallOnRemove("organism", hg.organism.Remove, ent) hg.send_bareinfo(ent.organism) @@ -369,13 +482,88 @@ function MODULE:PhysicsSimulate(phys, dt) for i = 0, target:GetNumBodyGroups() - 1 do ent:SetBodygroup(i, target:GetBodygroup(i)) end - - target:Remove() 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() + ent:SetNotSolid(false) + ent:SetNPCState(NPC_STATE_IDLE) + + ent:SetRenderMode(RENDERMODE_NORMAL) + + target:Remove() + + self:Remove() + end) + --[[] + local parent = self.FakeParent + + 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 + + for i,v in pairs(self.ModelBoneList) do + print(v) + + print("I'm here") + + local bone = target:TranslateBoneToPhysBone(target:LookupBone(v)) + local object = target:GetPhysicsObjectNum(bone) + if !IsValid(object) then i = i + 1; continue end + + print("I'm getting to object") + + local parent_bone = parent:TranslateBoneToPhysBone(parent:LookupBone(v)) + if parent_bone == nil then i = i + 1; continue end + + print("I'm getting to parent object") + + object:Wake() + + local shadow_data = { + secondstoarrive = 0.01, + pos = LerpVector(0.1, object:GetPos(), parent:GetBonePosition(parent_bone)), + angle = LerpAngle(0.1, object:GetAngles(), parent:GetBoneMatrix(parent_bone):GetAngles()), + maxspeed = 5000, + maxangular = 5000, + maxspeeddamp = 2000, + maxangularspeeddamp = 2000, + } + + object:ComputeShadowControl(shadow_data) + + --object:SetPos(LerpVector(0.2, object:GetPos(), parent:GetBonePosition(parent_bone))) + --object:SetAngles(LerpAngle(0.2, object:GetAngles(), parent:GetBoneMatrix(parent_bone):GetAngles())) + + --object:EnableMotion(false) + + --object:SetVelocity(Vector()) + object:EnableGravity(false) + + i = i + 1 + end]] - self:Remove() self.bullseye:Remove() return false end @@ -451,7 +639,7 @@ function MODULE:OnRemove() local timer_name = "Fedhoria_FallingLegs_Twitch_" .. self:EntIndex() timer_Remove(timer_name) - if self.bullseye then self.bullseye:Remove() end + if IsValid(self.bullseye) then self.bullseye:Remove() end end --[[------------------------------------------------------------------------- diff --git a/lua/zcnpci/modules/falling_torso.lua b/lua/zcnpci/modules/falling_torso.lua index ef7a937..60a4e7a 100644 --- a/lua/zcnpci/modules/falling_torso.lua +++ b/lua/zcnpci/modules/falling_torso.lua @@ -123,7 +123,7 @@ function MODULE:Init() if seq then self:ResetSequence(seq) end local target = self:GetTarget() - + self:SetPlaybackRate(1) self.LastCollideTime = 0 self.LastGroundCollideTime = 0 @@ -170,7 +170,10 @@ end function MODULE:PhysicsSimulate(phys, dt) local cur_time = CurTime() 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 diff --git a/lua/zcnpci/modules/stumble_legs.lua b/lua/zcnpci/modules/stumble_legs.lua index ef2ae5c..917f36c 100644 --- a/lua/zcnpci/modules/stumble_legs.lua +++ b/lua/zcnpci/modules/stumble_legs.lua @@ -279,6 +279,8 @@ local trace = {output={}} local tr = trace.output function MODULE:PhysicsSimulate(phys, dt) + if self.FakeUp then return end + local phys_bone = phys:GetID() local target = self:GetTarget()