PLUGIN.name = "TFA Lean" PLUGIN.author = "TFA (Портирован для Helix)" PLUGIN.description = "Система наклонов влево/вправо с автоматическим наклоном при прицеливании" ix.util.Include("cl_plugin.lua") -- Конфигурация ix.config.Add("leanEnabled", true, "Включить систему наклонов", nil, { category = "Lean System" }) ix.config.Add("leanOffset", 16, "Расстояние наклона (в юнитах)", nil, { data = {min = 8, max = 32}, category = "Lean System" }) ix.config.Add("leanAutomatic", true, "Автоматический наклон при прицеливании за укрытием", nil, { category = "Lean System" }) ix.config.Add("leanAutoDist", 20, "Дистанция для определения укрытия (автонаклон)", nil, { data = {min = 10, max = 50}, category = "Lean System" }) ix.config.Add("leanRollAngle", 15, "Угол наклона камеры и модели", nil, { data = {min = 5, max = 30}, category = "Lean System" }) -- Локальные переменные local LeanOffset = ix.config.Get("leanOffset", 16) --[[Shared]] local function MetaInitLean() local PlayerMeta = FindMetaTable("Player") PlayerMeta.LeanGetShootPosOld = PlayerMeta.LeanGetShootPosOld or PlayerMeta.GetShootPos PlayerMeta.LeanEyePosOld = PlayerMeta.LeanEyePosOld or PlayerMeta.GetShootPos PlayerMeta.GetNW2Int = PlayerMeta.GetNW2Int or PlayerMeta.GetNWFloat PlayerMeta.SetNW2Int = PlayerMeta.SetNW2Int or PlayerMeta.SetNWFloat function PlayerMeta:GetShootPos() if not IsValid(self) or not self.LeanGetShootPosOld then return end local ply = self local status = ply.TFALean or ply:GetNW2Int("TFALean") local off = Vector(0, status * -LeanOffset, 0) off:Rotate(self:EyeAngles()) local gud, ret = pcall(self.LeanGetShootPosOld, self) if gud then return ret + off else return off end end function PlayerMeta:EyePos() if not IsValid(self) then return end local gud, pos, ang = pcall(self.GetShootPos, self) if gud then return pos, ang else return vector_origin, angle_zero end end hook.Add("ShutDown", "TFALeanPMetaCleanup", function() if PlayerMeta.LeanGetShootPosOld then PlayerMeta.GetShootPos = PlayerMeta.LeanGetShootPosOld PlayerMeta.LeanGetShootPosOld = nil end end) MetaOverLean = true end pcall(MetaInitLean) function PLUGIN:PlayerSpawn(ply) ply:SetNW2Int("TFALean", 0) ply.TFALean = 0 end function PLUGIN:OnConfigChanged(key, oldValue, newValue) if key == "leanOffset" then LeanOffset = newValue end end --[[Lean Calculations]] local targ local autoLeanDist = ix.config.Get("leanAutoDist", 20) local traceRes, traceResLeft, traceResRight local traceData = { ["mask"] = MASK_SOLID, ["collisiongroup"] = COLLISION_GROUP_DEBRIS, ["mins"] = Vector(-4, -4, -4), ["maxs"] = Vector(4, 4, 4) } local function AngleOffset(new, old) local _, ang = WorldToLocal(vector_origin, new, vector_origin, old) return ang end local function RollBone(ply, bone, roll) ply:ManipulateBoneAngles(bone, angle_zero) if CLIENT then ply:SetupBones() end local boneMat = ply:GetBoneMatrix(bone) local boneAngle = boneMat:GetAngles() local boneAngleOG = boneMat:GetAngles() boneAngle:RotateAroundAxis(ply:EyeAngles():Forward(), roll) ply:ManipulateBoneAngles(bone, AngleOffset(boneAngle, boneAngleOG)) if CLIENT then ply:SetupBones() end end function TFALeanModel() local rollAngle = ix.config.Get("leanRollAngle", 15) for k, ply in ipairs(player.GetAll()) do ply.TFALean = Lerp(FrameTime() * 10, ply.TFALean or 0, ply:GetNW2Int("TFALean")) local lean = ply.TFALean local bone = ply:LookupBone("ValveBiped.Bip01_Spine") if bone then RollBone(ply, bone, lean * rollAngle) end bone = ply:LookupBone("ValveBiped.Bip01_Spine1") if bone then RollBone(ply, bone, lean * rollAngle) end end end function PLUGIN:Move(ply) if not ix.config.Get("leanEnabled", true) then return end targ = (ply:KeyDown(IN_ALT1) and -1 or (ply:KeyDown(IN_ALT2) and 1 or 0)) autoLeanDist = ix.config.Get("leanAutoDist", 20) -- Автонаклон if targ == 0 and ix.config.Get("leanAutomatic", true) and ply:KeyDown(IN_ATTACK2) then local headpos = ply.LeanGetShootPosOld and ply:LeanGetShootPosOld() or ply:GetShootPos() traceData.start = headpos traceData.endpos = traceData.start + ply:EyeAngles():Forward() * autoLeanDist traceRes = util.TraceHull(traceData) if traceRes.Hit and traceRes.Fraction < 1 then traceData.start = headpos - ply:EyeAngles():Right() * LeanOffset traceData.endpos = traceData.start + ply:EyeAngles():Forward() * autoLeanDist * 2 traceResLeft = util.TraceLine(traceData) traceData.start = headpos + ply:EyeAngles():Right() * LeanOffset traceData.endpos = traceData.start + ply:EyeAngles():Forward() * autoLeanDist * 2 traceResRight = util.TraceLine(traceData) if traceResLeft.Fraction > 1 or not traceResLeft.Hit then targ = -1 elseif traceResRight.Fraction > 1 or not traceResRight.Hit then targ = 1 end end end ply:SetNW2Int("TFALean", targ) if SERVER then for _, v in ipairs(player.GetAll()) do v.TFALean = Lerp(FrameTime() * 10, v.TFALean or 0, v:GetNW2Int("TFALean")) end end end if SERVER and not game.SinglePlayer() then timer.Create("TFALeanSynch", 0.2, 0, function() if not ix.config.Get("leanEnabled", true) then return end local rollAngle = ix.config.Get("leanRollAngle", 15) for k, v in ipairs(player.GetAll()) do local lean = v:GetNW2Int("TFALean") v.OldLean = v.OldLean or lean if lean ~= v.OldLean then v.OldLean = lean local bone = v:LookupBone("ValveBiped.Bip01_Spine") if bone then RollBone(v, bone, lean * rollAngle) end bone = v:LookupBone("ValveBiped.Bip01_Spine1") if bone then RollBone(v, bone, lean * rollAngle) end end end end) end --[[Projectile Redirection]] local PlayerPosEntities = { ["rpg_missile"] = true, ["crossbow_bolt"] = true, ["npc_grenade_frag"] = true, ["apc_missile"] = true, ["viewmodel_predicted"] = true } function PLUGIN:OnEntityCreated(ent) local ply = ent.Owner or ent:GetOwner() if not IsValid(ply) or not ply:IsPlayer() then return end if ent:IsPlayer() then return end local headposold local gud, ret = pcall(ply.LeanGetShootPosOld, ply) if gud then headposold = ret else headposold = ply:EyePos() end local entpos = ent:GetPos() if PlayerPosEntities[ent:GetClass()] or (math.floor(entpos.x) == math.floor(headposold.x) and math.floor(entpos.y) == math.floor(headposold.y) and math.floor(entpos.z) == math.floor(headposold.z)) then ent:SetPos(ply:EyePos()) end end local ISPATCHING = false function PLUGIN:EntityFireBullets(ply, bul) if ISPATCHING then return end if not ix.config.Get("leanEnabled", true) then return end local bak = table.Copy(bul) ISPATCHING = true local call = hook.Run("EntityFireBullets", ply, bul) ISPATCHING = false if call == false then return false elseif call == nil then table.Empty(bul) table.CopyFromTo(bak, bul) end if ply:IsWeapon() and ply.Owner then ply = ply.Owner end if not ply:IsPlayer() then return end if ply.LeanGetShootPosOld and bul.Src == ply:LeanGetShootPosOld() then bul.Src = ply:GetShootPos() end return true end function PLUGIN:Initialize() print("[TFA Lean] Система наклонов загружена!") print("[TFA Lean] Управление: ALT1 (Q) - наклон влево, ALT2 (E) - наклон вправо") end