2026-05-25 04:57:41 +00:00
--[[-------------------------------------------------------------------------
2026-05-25 05:07:46 +00:00
A script for controlling the behavior of falling NPC body parts ( e.g . , legs ) .
Includes flinching effects and roll animation upon impact .
Version 2 : Fixed GetGameData and HitNormal errors .
2026-05-25 04:57:41 +00:00
---------------------------------------------------------------------------]]
MODULE = { }
2026-05-25 05:07:46 +00:00
MODULE.Model = " models/police.mdl " -- The model to which the script is applied (likely not used directly in this code)
2026-05-25 04:57:41 +00:00
2026-05-25 05:07:46 +00:00
-- A list of bones that MAY be affected by physics or animation.
-- Only the leg bones from this list will be used for pulling.
2026-05-25 04:57:41 +00:00
MODULE.BoneList =
{
" ValveBiped.Bip01_Pelvis " ,
-- "ValveBiped.Bip01_Spine2",
-- "ValveBiped.Bip01_Head1",
-- "ValveBiped.Bip01_R_Upperarm",
-- "ValveBiped.Bip01_R_Forearm",
-- "ValveBiped.Bip01_R_Hand",
-- "ValveBiped.Bip01_L_Upperarm",
-- "ValveBiped.Bip01_L_Forearm",
-- "ValveBiped.Bip01_L_Hand",
" ValveBiped.Bip01_R_Thigh " ,
" ValveBiped.Bip01_R_Calf " ,
" ValveBiped.Bip01_R_Foot " ,
" ValveBiped.Bip01_L_Thigh " ,
" ValveBiped.Bip01_L_Calf " ,
" ValveBiped.Bip01_L_Foot "
}
2026-05-25 05:07:46 +00:00
-- A list of bones to which the jiggle effect WILL be applied.
-- Filtered from MODULE.BoneList to exclude the pelvis and other unwanted bones.
2026-05-25 04:57:41 +00:00
local twitchable_bone_names = {
" ValveBiped.Bip01_R_Thigh " ,
" ValveBiped.Bip01_R_Calf " ,
" ValveBiped.Bip01_R_Foot " ,
" ValveBiped.Bip01_L_Thigh " ,
" ValveBiped.Bip01_L_Calf " ,
" ValveBiped.Bip01_L_Foot "
}
2026-05-25 05:07:46 +00:00
-- Local Copies of Functions for Optimization
2026-05-25 04:57:41 +00:00
local math_Clamp = math.Clamp
local math_Rand = math.Rand
local math_random = math.random
2026-05-25 05:07:46 +00:00
local table_Random = table.Random -- We use `table.Random` to select a random element.
local IsValid = IsValid -- Local copy of IsValid
local CurTime = CurTime -- Local copy of CurTime
2026-05-25 04:57:41 +00:00
local timer_Create = timer.Create
local timer_Remove = timer.Remove
local VectorRand = VectorRand
--[[-------------------------------------------------------------------------
2026-05-25 05:07:46 +00:00
Convars ( Settings )
2026-05-25 04:57:41 +00:00
---------------------------------------------------------------------------]]
2026-05-25 05:07:46 +00:00
-- Twitching
2026-05-25 04:57:41 +00:00
local cv_twitch_enabled = CreateConVar ( " fedhoria_falling_twitch_enabled " , " 1 " , { FCVAR_ARCHIVE , FCVAR_REPLICATED } , " Включить дергание для падающих ног " )
local cv_twitch_interval_min = CreateConVar ( " fedhoria_falling_twitch_interval_min " , " 3 " , { FCVAR_ARCHIVE , FCVAR_REPLICATED } , " Минимальный интервал между дерганиями (сек) " )
local cv_twitch_interval_max = CreateConVar ( " fedhoria_falling_twitch_interval_max " , " 6 " , { FCVAR_ARCHIVE , FCVAR_REPLICATED } , " Максимальный интервал между дерганиями (сек) " )
local cv_twitch_force_min = CreateConVar ( " fedhoria_falling_twitch_min " , " 100 " , { FCVAR_ARCHIVE , FCVAR_REPLICATED } , " Мин. угловая скорость для дергания " )
local cv_twitch_force_max = CreateConVar ( " fedhoria_falling_twitch_max " , " 250 " , { FCVAR_ARCHIVE , FCVAR_REPLICATED } , " Макс. угловая скорость для дергания " )
2026-05-25 05:07:46 +00:00
-- Roll
2026-05-25 04:57:41 +00:00
local cv_anim_roll_enabled = CreateConVar ( " fedhoria_falling_anim_roll_enabled " , " 1 " , { FCVAR_ARCHIVE , FCVAR_REPLICATED } , " Включить анимацию при ударе о землю " )
local cv_anim_roll_min_delay = CreateConVar ( " fedhoria_falling_anim_roll_min_delay " , " 0.5 " , { FCVAR_ARCHIVE , FCVAR_REPLICATED } , " Мин. задержка перед возможной анимацией после удара (сек) " )
local cv_anim_roll_duration = CreateConVar ( " fedhoria_falling_anim_roll_duration " , " 3.5 " , { FCVAR_ARCHIVE , FCVAR_REPLICATED } , " Продолжительность принудительной анимации (сек) " )
local cv_anim_roll_impact_threshold = CreateConVar ( " fedhoria_falling_anim_roll_impact_threshold " , " 300 " , { FCVAR_ARCHIVE , FCVAR_REPLICATED } , " Мин. скорость удара для запуска анимации " )
local cv_anim_roll_playback_rate = CreateConVar ( " fedhoria_falling_anim_roll_playback_rate " , " 3.0 " , { FCVAR_ARCHIVE , FCVAR_REPLICATED } , " Скорость воспроизведения анимации " )
2026-05-25 05:07:46 +00:00
-- Death timer
2026-05-25 04:57:41 +00:00
local die_time = CreateConVar ( " fedhoria_dietime " , 5 , { FCVAR_ARCHIVE , FCVAR_REPLICATED } )
local die_time_variation = CreateConVar ( " fedhoria_dietime_variation " , 3 , { FCVAR_ARCHIVE , FCVAR_REPLICATED } )
--[[-------------------------------------------------------------------------
2026-05-25 05:07:46 +00:00
Tug function
Applies a random impulse to one of the leg bones .
2026-05-25 04:57:41 +00:00
---------------------------------------------------------------------------]]
function MODULE : DoTwitch ( )
if not IsValid ( self ) or not IsValid ( self : GetTarget ( ) ) then return end
if not cv_twitch_enabled : GetBool ( ) then return end
local phys = self : GetPhysicsObject ( )
if not IsValid ( phys ) then return end
if # twitchable_bone_names == 0 then return end
local random_bone_name = table_Random ( twitchable_bone_names )
if not random_bone_name then return end
2026-05-25 05:07:46 +00:00
-- Get bone index by name
2026-05-25 04:57:41 +00:00
local bone_index = self : LookupBone ( random_bone_name )
if not bone_index then
-- print("[Fedhoria Twitch] Bone index not found for name:", random_bone_name)
return
end
2026-05-25 05:07:46 +00:00
-- We retrieve the physical bone object by its index.
local phys_bone = self : GetPhysicsObjectNum ( bone_index ) -- We use GetPhysicsObjectNum to retrieve the physics object for a specific bone.
2026-05-25 04:57:41 +00:00
if IsValid ( phys_bone ) then
local force = math_Rand ( cv_twitch_force_min : GetFloat ( ) , cv_twitch_force_max : GetFloat ( ) )
local torque = VectorRand ( ) : GetNormal ( ) * force
phys_bone : AddAngleVelocity ( torque )
-- print("[Fedhoria Twitch] Applied torque", torque, "to bone", random_bone_name, "(index:", bone_index, ")")
-- else
-- print("[Fedhoria Twitch] Could not find a valid physics bone for index:", bone_index, "name:", random_bone_name)
end
self : ScheduleNextTwitch ( )
end
--[[-------------------------------------------------------------------------
2026-05-25 05:07:46 +00:00
Planning the next DoTwitch call
2026-05-25 04:57:41 +00:00
---------------------------------------------------------------------------]]
function MODULE : ScheduleNextTwitch ( )
if not IsValid ( self ) then return end
if not cv_twitch_enabled : GetBool ( ) then return end
local timer_name = " Fedhoria_FallingLegs_Twitch_ " .. self : EntIndex ( )
timer_Remove ( timer_name )
local delay = math_Rand ( cv_twitch_interval_min : GetFloat ( ) , cv_twitch_interval_max : GetFloat ( ) )
timer_Create ( timer_name , delay , 1 , function ( )
if not IsValid ( self ) then return end
self : DoTwitch ( )
end )
end
--[[-------------------------------------------------------------------------
2026-05-25 05:07:46 +00:00
Starting the roll animation
2026-05-25 04:57:41 +00:00
---------------------------------------------------------------------------]]
function MODULE : StartAnimationRoll ( )
if not cv_anim_roll_enabled : GetBool ( ) then return end
local target = self : GetTarget ( )
if not IsValid ( target ) then return end
local cur_time = CurTime ( )
self.AnimationRollEndTime = cur_time + cv_anim_roll_duration : GetFloat ( )
local seq = self : LookupSequence ( " Choked_Barnacle " )
if seq and seq > 0 then
self : ResetSequence ( seq )
self : SetPlaybackRate ( cv_anim_roll_playback_rate : GetFloat ( ) )
else
self.AnimationRollEndTime = 0
return
end
--self.StartDie = nil
end
function MODULE : Init ( )
local seq = self : LookupSequence ( " Choked_Barnacle " )
if seq then self : ResetSequence ( seq ) end
self : SetPlaybackRate ( 1 )
self.LastCollideTime = 0
self.LastGroundCollideTime = 0
self.StartDie = nil
self.AnimationRollEndTime = 0
2026-05-25 05:07:46 +00:00
-- Add damping to all bones.
2026-05-25 04:57:41 +00:00
local phys = self : GetPhysicsObject ( )
if IsValid ( phys ) then
local bone_count = phys : GetBoneCount ( )
for bone_index = 0 , bone_count - 1 do
local phys_bone = phys : GetBone ( bone_index )
if IsValid ( phys_bone ) then
2026-05-25 05:07:46 +00:00
phys_bone : SetDamping ( 0.8 , 0.9 ) -- Linear and Angular Damping
2026-05-25 04:57:41 +00:00
end
end
end
-- запускаем систему дергания
self : ScheduleNextTwitch ( )
end
--[[-------------------------------------------------------------------------
2026-05-25 05:07:46 +00:00
Think - hook
2026-05-25 04:57:41 +00:00
---------------------------------------------------------------------------]]
function MODULE : Think ( )
2026-05-25 05:07:46 +00:00
-- Space for additional logic
2026-05-25 04:57:41 +00:00
end
--[[-------------------------------------------------------------------------
2026-05-25 05:07:46 +00:00
Physics Collision Hook ( FIXED )
2026-05-25 04:57:41 +00:00
---------------------------------------------------------------------------]]
function MODULE : PhysicsCollide ( data , phys )
2026-05-25 05:07:46 +00:00
-- Checking the validity of collision data and the presence of HitNormal.
2026-05-25 04:57:41 +00:00
if not data or not data.HitNormal then
-- print("[Fedhoria Collide] Invalid collision data received.")
return
end
if data.HitEntity == self then return end
local cur_time = CurTime ( )
self.LastCollideTime = cur_time
2026-05-25 05:07:46 +00:00
-- Ground collision check (normal points upward)
2026-05-25 04:57:41 +00:00
if data.HitNormal . z > 0.7 then
local impact_speed = data.Speed
-- print("[Fedhoria Collide] Ground collision detected. Speed:", impact_speed)
if cv_anim_roll_enabled : GetBool ( ) and
impact_speed > cv_anim_roll_impact_threshold : GetFloat ( ) and
cur_time > self.AnimationRollEndTime and
cur_time > self.LastGroundCollideTime + cv_anim_roll_min_delay : GetFloat ( ) then
self : StartAnimationRoll ( )
end
self.LastGroundCollideTime = cur_time
end
end
--[[-------------------------------------------------------------------------
2026-05-25 05:07:46 +00:00
Physics Simulation Hook ( FIXED )
2026-05-25 04:57:41 +00:00
---------------------------------------------------------------------------]]
function MODULE : PhysicsSimulate ( phys , dt )
local cur_time = CurTime ( )
local target = self : GetTarget ( )
if not IsValid ( target ) then self : Remove ( ) ; return false end
2026-05-25 05:07:46 +00:00
-- Check for active animation
2026-05-25 04:57:41 +00:00
if cur_time < self.AnimationRollEndTime then
2026-05-25 05:07:46 +00:00
--self.StartDie = nil -- Resetting the "death" timer
return true -- We use standard physics.
2026-05-25 04:57:41 +00:00
end
2026-05-25 05:07:46 +00:00
-- Logic for the disappearance timer
local f = 1 -- Force multiplier (default: 1)
2026-05-25 04:57:41 +00:00
if self.StartDie then
f = math_Clamp ( 1 - ( cur_time - self.StartDie ) / die_time : GetFloat ( ) , 0 , 1 )
end
2026-05-25 05:07:46 +00:00
-- Support for RagMod
2026-05-25 04:57:41 +00:00
if ragmod and ragmod : IsRagmodRagdoll ( target ) then
local owner = target : GetOwningPlayer ( )
f = ( IsValid ( owner ) and owner : Alive ( ) ) and 1 or 0
--self.StartDie = nil
if f <= 0 then self.AnimationRollEndTime = 0 end
end
2026-05-25 05:07:46 +00:00
-- Deletion if the time-to-live has expired
2026-05-25 04:57:41 +00:00
if ( f <= 0 ) then
self : Remove ( )
return false
end
local phys_bone_id = phys : GetID ( )
2026-05-25 05:07:46 +00:00
-- Main logic for the root bone
2026-05-25 04:57:41 +00:00
if ( phys_bone_id == 0 ) then
2026-05-25 05:07:46 +00:00
-- Water Logic
2026-05-25 04:57:41 +00:00
if target : WaterLevel ( ) > 0 then
self.AnimationRollEndTime = 0
local seq_choked = self : LookupSequence ( " Choked_Barnacle " )
if seq_choked then self : ResetSequence ( seq_choked ) end
return false
end
2026-05-25 05:07:46 +00:00
-- Logic for fall animation
2026-05-25 04:57:41 +00:00
local vel = phys : GetVelocity ( )
local pbr = math_Clamp ( vel.z / - 600 , 0.5 , 1.5 )
if self : GetSequence ( ) ~= self : LookupSequence ( " Choked_Barnacle " ) or self : GetPlaybackRate ( ) ~= pbr then
local seq_idle = self : LookupSequence ( " Choked_Barnacle " )
if seq_idle then
self : ResetSequence ( seq_idle )
self : SetPlaybackRate ( pbr )
end
end
2026-05-25 05:07:46 +00:00
-- Fix: Safe vector handling
2026-05-25 04:57:41 +00:00
local pos = phys : GetPos ( )
2026-05-25 05:07:46 +00:00
if not pos then return true , f end -- If `pos` is invalid, do nothing.
2026-05-25 04:57:41 +00:00
self.last_pos = self.last_pos or pos
if not self.last_pos then self.last_pos = pos end
local offset_sqr = ( pos - self.last_pos ) : LengthSqr ( )
self.last_pos = pos
if ( offset_sqr < ( 10 * 10 * dt * dt ) and not ( ragmod and ragmod : IsRagmodRagdoll ( target ) ) ) then
self.StartDie = self.StartDie or cur_time
else
--self.StartDie = nil
end
2026-05-25 05:07:46 +00:00
-- Physics correction after collision
2026-05-25 04:57:41 +00:00
local delta_collide = cur_time - self.LastCollideTime
if ( delta_collide < 0.2 ) then
return true , 1 * f
elseif ( delta_collide < 1.2 ) then
return true , ( 1 - ( delta_collide - 0.2 ) ) * f
end
return true , f
end
2026-05-25 05:07:46 +00:00
-- Logic for other bones
2026-05-25 04:57:41 +00:00
local delta_collide = cur_time - self.LastCollideTime
if ( delta_collide < 0.2 ) then
return true , 1 * f
elseif ( delta_collide < 1.2 ) then
return true , ( 1 - ( delta_collide - 0.2 ) ) * f
end
return true , f
end
--[[-------------------------------------------------------------------------
2026-05-25 05:07:46 +00:00
Entity Deletion Hook
2026-05-25 04:57:41 +00:00
---------------------------------------------------------------------------]]
function MODULE : OnRemove ( )
local timer_name = " Fedhoria_FallingLegs_Twitch_ " .. self : EntIndex ( )
timer_Remove ( timer_name )
end
--[[-------------------------------------------------------------------------
2026-05-25 05:07:46 +00:00
Module registration ( if it is part of a module system )
2026-05-25 04:57:41 +00:00
---------------------------------------------------------------------------]]
2026-05-25 05:07:46 +00:00
-- Registration Example:
2026-05-25 04:57:41 +00:00
-- falling_legs_manager:RegisterModule("FedhoriaFallingLegs", MODULE)