zcnpci/lua/fedhoria/modules/falling_legs.lua

331 lines
13 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

--[[-------------------------------------------------------------------------
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.
---------------------------------------------------------------------------]]
MODULE = {}
MODULE.Model = "models/police.mdl" -- The model to which the script is applied (likely not used directly in this code)
-- A list of bones that MAY be affected by physics or animation.
-- Only the leg bones from this list will be used for pulling.
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"
}
-- A list of bones to which the jiggle effect WILL be applied.
-- Filtered from MODULE.BoneList to exclude the pelvis and other unwanted bones.
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"
}
-- Local Copies of Functions for Optimization
local math_Clamp = math.Clamp
local math_Rand = math.Rand
local math_random = math.random
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
local timer_Create = timer.Create
local timer_Remove = timer.Remove
local VectorRand = VectorRand
--[[-------------------------------------------------------------------------
Convars (Settings)
---------------------------------------------------------------------------]]
-- Twitching
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}, "Макс. угловая скорость для дергания")
-- Roll
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}, "Скорость воспроизведения анимации")
-- Death timer
local die_time = CreateConVar("fedhoria_dietime", 5, {FCVAR_ARCHIVE, FCVAR_REPLICATED})
local die_time_variation = CreateConVar("fedhoria_dietime_variation", 3, {FCVAR_ARCHIVE, FCVAR_REPLICATED})
--[[-------------------------------------------------------------------------
Tug function
Applies a random impulse to one of the leg bones.
---------------------------------------------------------------------------]]
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
-- Get bone index by name
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
-- 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.
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
--[[-------------------------------------------------------------------------
Planning the next DoTwitch call
---------------------------------------------------------------------------]]
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
--[[-------------------------------------------------------------------------
Starting the roll animation
---------------------------------------------------------------------------]]
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
-- Add damping to all bones.
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
phys_bone:SetDamping(0.8, 0.9) -- Linear and Angular Damping
end
end
end
-- запускаем систему дергания
self:ScheduleNextTwitch()
end
--[[-------------------------------------------------------------------------
Think-hook
---------------------------------------------------------------------------]]
function MODULE:Think()
-- Space for additional logic
end
--[[-------------------------------------------------------------------------
Physics Collision Hook (FIXED)
---------------------------------------------------------------------------]]
function MODULE:PhysicsCollide(data, phys)
-- Checking the validity of collision data and the presence of HitNormal.
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
-- Ground collision check (normal points upward)
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
--[[-------------------------------------------------------------------------
Physics Simulation Hook (FIXED)
---------------------------------------------------------------------------]]
function MODULE:PhysicsSimulate(phys, dt)
local cur_time = CurTime()
local target = self:GetTarget()
if not IsValid(target) then self:Remove(); return false end
-- Check for active animation
if cur_time < self.AnimationRollEndTime then
--self.StartDie = nil -- Resetting the "death" timer
return true -- We use standard physics.
end
-- Logic for the disappearance timer
local f = 1 -- Force multiplier (default: 1)
if self.StartDie then
f = math_Clamp(1 - (cur_time - self.StartDie) / die_time:GetFloat(), 0, 1)
end
-- Support for RagMod
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
-- Deletion if the time-to-live has expired
if (f <= 0) then
self:Remove()
return false
end
local phys_bone_id = phys:GetID()
-- Main logic for the root bone
if (phys_bone_id == 0) then
-- Water Logic
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
-- Logic for fall animation
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
-- Fix: Safe vector handling
local pos = phys:GetPos()
if not pos then return true, f end -- If `pos` is invalid, do nothing.
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
-- Physics correction after collision
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
-- Logic for other bones
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
--[[-------------------------------------------------------------------------
Entity Deletion Hook
---------------------------------------------------------------------------]]
function MODULE:OnRemove()
local timer_name = "Fedhoria_FallingLegs_Twitch_" .. self:EntIndex()
timer_Remove(timer_name)
end
--[[-------------------------------------------------------------------------
Module registration (if it is part of a module system)
---------------------------------------------------------------------------]]
-- Registration Example:
-- falling_legs_manager:RegisterModule("FedhoriaFallingLegs", MODULE)