Files
2026-03-31 10:27:04 +03:00

276 lines
7.4 KiB
Lua
Raw Permalink 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.
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