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

This commit is contained in:
toasterpanic 2026-05-30 15:02:58 -04:00
parent 259a3da4e1
commit 278a0a9d7d
6 changed files with 218 additions and 16 deletions

View file

@ -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

View file

@ -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.")

View file

@ -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

View file

@ -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
--[[-------------------------------------------------------------------------

View file

@ -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

View file

@ -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()