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
