--[[------------------------------------------------------------------------- Скрипт для управления поведением падающих частей тела 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)