zcnpci/fedhoria/modules/falling_legs.lua
2026-05-25 00:56:02 -04:00

331 lines
15 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.

--[[-------------------------------------------------------------------------
Скрипт для управления поведением падающих частей тела NPC (например, ног)
Включает эффекты дергания и анимацию переката при ударе.
Версия 2: Исправлены ошибки GetGameData и HitNormal.
---------------------------------------------------------------------------]]
MODULE = {}
MODULE.Model = "models/police.mdl" -- Модель, к которой применяется скрипт (вероятно, не используется напрямую в этом коде)
-- Список костей, которые МОГУТ быть затронуты физикой или анимацией.
-- Для дергания будут использоваться только кости ног из этого списка.
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"
}
-- Список костей, к которым БУДЕТ применяться эффект дергания.
-- Отфильтровано из MODULE.BoneList, чтобы исключить таз и другие нежелательные кости.
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 math_Clamp = math.Clamp
local math_Rand = math.Rand
local math_random = math.random
local table_Random = table.Random -- Используем table.Random для выбора случайного элемента
local IsValid = IsValid -- Локальная копия IsValid
local CurTime = CurTime -- Локальная копия CurTime
local timer_Create = timer.Create
local timer_Remove = timer.Remove
local VectorRand = VectorRand
--[[-------------------------------------------------------------------------
Конвары (Настройки)
---------------------------------------------------------------------------]]
-- Подергивание
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}, "Макс. угловая скорость для дергания")
-- Перекат
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}, "Скорость воспроизведения анимации")
-- Время жизни объекта после остановки
local die_time = CreateConVar("fedhoria_dietime", 5, {FCVAR_ARCHIVE, FCVAR_REPLICATED})
local die_time_variation = CreateConVar("fedhoria_dietime_variation", 3, {FCVAR_ARCHIVE, FCVAR_REPLICATED})
--[[-------------------------------------------------------------------------
Функция дергания
Применяет случайный импульс к одной из костей ног.
---------------------------------------------------------------------------]]
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
-- Получаем индекс кости по имени
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
-- Получаем физический объект кости по ее индексу
local phys_bone = self:GetPhysicsObjectNum(bone_index) -- Используем GetPhysicsObjectNum для получения физ. объекта конкретной кости
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
--[[-------------------------------------------------------------------------
Планирование следующего вызова DoTwitch
---------------------------------------------------------------------------]]
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
--[[-------------------------------------------------------------------------
Запуск анимации переката
---------------------------------------------------------------------------]]
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
-- Добавляем демпфирование для всех костей
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) -- Линейное и угловое демпфирование
end
end
end
-- запускаем систему дергания
self:ScheduleNextTwitch()
end
--[[-------------------------------------------------------------------------
Think-хук
---------------------------------------------------------------------------]]
function MODULE:Think()
-- Место для дополнительной логики
end
--[[-------------------------------------------------------------------------
Хук столкновения физики (ИСПРАВЛЕН)
---------------------------------------------------------------------------]]
function MODULE:PhysicsCollide(data, phys)
-- Проверяем валидность данных столкновения и наличие 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
-- Проверка на столкновение с землей (нормаль смотрит вверх)
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
--[[-------------------------------------------------------------------------
Хук симуляции физики (ИСПРАВЛЕН)
---------------------------------------------------------------------------]]
function MODULE:PhysicsSimulate(phys, dt)
local cur_time = CurTime()
local target = self:GetTarget()
if not IsValid(target) then self:Remove(); return false end
-- проверка на активную анимацию
if cur_time < self.AnimationRollEndTime then
--self.StartDie = nil -- сбрасываем таймер "смерти"
return true -- используем стандартную физику
end
-- логика для таймера исчезновения
local f = 1 -- множитель силы (по умолчанию 1)
if self.StartDie then
f = math_Clamp(1 - (cur_time - self.StartDie) / die_time:GetFloat(), 0, 1)
end
-- поддержка для 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
-- удаление, если истекло время жизни
if (f <= 0) then
self:Remove()
return false
end
local phys_bone_id = phys:GetID()
-- основная логика для корневой кости
if (phys_bone_id == 0) then
-- логика для воды
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
-- логика для анимации падения
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
-- фикс: безопасная работа с векторами
local pos = phys:GetPos()
if not pos then return true, f end -- если pos невалиден, ничего не делаем
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
-- коррекция физики после столкновения
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
-- логика для других костей
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
--[[-------------------------------------------------------------------------
Хук удаления сущности
---------------------------------------------------------------------------]]
function MODULE:OnRemove()
local timer_name = "Fedhoria_FallingLegs_Twitch_" .. self:EntIndex()
timer_Remove(timer_name)
end
--[[-------------------------------------------------------------------------
Регистрация модуля (если это часть системы модулей)
---------------------------------------------------------------------------]]
-- Пример регистрации:
-- falling_legs_manager:RegisterModule("FedhoriaFallingLegs", MODULE)