-----------------------------------------
-- FT BULLET CONTUSION (MILITARY RP)
-- Собственность Олега Закона и Scripty
-----------------------------------------
util.AddNetworkString("BulletCloseToPlayer")
util.AddNetworkString("FT_AddSuppression")

local function DistanceToSegment(p, a, b)
    local ap = p - a
    local ab = b - a

    local t = ap:Dot(ab) / ab:Dot(ab)
    t = math.Clamp(t, 0, 1)

    local closest = a + ab * t
    return p:Distance(closest)
end

local function WeaponHasAmmo(wep)
    if not IsValid(wep) then return false end

    if wep:Clip1() and wep:Clip1() > 0 then return true end
    if wep:Ammo1() and wep:Ammo1() > 0 then return true end

    return false
end

local function IsInsideVehicleOrPod(ply)
    local veh = ply:GetVehicle()
    if IsValid(veh) then
        local class = veh:GetClass()
        if string.StartWith(class, "lvs_") then return true end
        if string.StartWith(class, "sw_") then return true end
    end

    local parent = ply:GetParent()
    if IsValid(parent) then
        local class = parent:GetClass()
        if string.StartWith(class, "lvs_") then return true end
        if string.StartWith(class, "sw_") then return true end
    end

    local seat = ply:GetVehicle()
    if IsValid(seat) then
        local parent2 = seat:GetParent()
        if IsValid(parent2) then
            local class = parent2:GetClass()
            if string.StartWith(class, "lvs_") then return true end
            if string.StartWith(class, "sw_") then return true end
        end
    end

    return false
end

local BLACKLIST_WEAPONS = {
    ["gmod_tool"] = true,
    ["tacrp_knife"] = true,
    ["tacrp_knife2"] = true,
    ["weapon_physgun"] = true,
    ["weapon_physcannon"] = true,
    ["ix_hands"] = true,
    ["v92_bf2_medikit"] = true
}

local function ProcessBullet(entity, src, dir)
    if not src or not dir then return end
    if not IsValid(entity) then return end

    if entity:IsPlayer() then
        local wep = entity:GetActiveWeapon()
        if IsValid(wep) and BLACKLIST_WEAPONS[wep:GetClass()] then
            return
        end
    end

    local dirVec = Vector(dir.x, dir.y, dir.z)
    if dirVec:IsZero() then return end
    dirVec:Normalize()

    local bulletStart = src
    local bulletEnd   = src + dirVec * 10000

    for _, ply in ipairs(player.GetAll()) do
        if ply == entity then continue end
        if not IsValid(ply) or not ply:Alive() then continue end

        if IsInsideVehicleOrPod(ply) then
            continue
        end

        local headPos = ply:EyePos()
        local dist = DistanceToSegment(headPos, bulletStart, bulletEnd)
        if not dist then continue end

        if dist < 120 then
            net.Start("BulletCloseToPlayer")
            net.WriteString("bullet_contusion.mp3")
            net.Send(ply)
        end
    end
end

hook.Add("EntityEmitSound", "FT_PlayerShootDetect", function(data)
    local ent = data.Entity
    if not IsValid(ent) then return end
    if not ent:IsPlayer() then return end

    local wep = ent:GetActiveWeapon()
    if not IsValid(wep) then return end

    local snd = string.lower(data.SoundName)
    if snd:find("dry") or snd:find("empty") then return end

    if not string.find(string.lower(data.SoundName), "shoot") 
       and not string.find(string.lower(data.SoundName), "fire") 
       and not string.find(string.lower(data.SoundName), "shot") then
        return
    end

    local src = ent:GetShootPos()
    local dir = ent:GetAimVector()

    ProcessBullet(ent, src, dir)
end)

local RPG_CLASSES = {
    ["rpg26_rocket"] = true,
    ["rpg28_rocket"] = true,
    ["at4_rocket"] = true,
    ["panzerfaust3_rocket"] = true,
    ["tfa_panzerfaust3_rocket"] = true
}

hook.Add("OnEntityCreated", "FT_RPG_ShootDetect", function(ent)
    if not IsValid(ent) then return end
    if not RPG_CLASSES[ent:GetClass()] then return end

    timer.Simple(0, function()
        if not IsValid(ent) then return end
        local owner = ent.Owner or ent:GetOwner()
        if not IsValid(owner) or not owner:IsPlayer() then return end

        ProcessBullet(owner, owner:GetShootPos(), owner:GetAimVector())
    end)
end)

net.Receive("FT_AddSuppression", function(len, ply)
    local amount = net.ReadFloat()
    local cur = ply:GetNWFloat("FT_Suppression", 0)
    ply:SetNWFloat("FT_Suppression", math.Clamp(cur + amount, 0, 1))
end)

hook.Add("PreRegisterSWEP", "FT_HookTFA_Shoot", function(class, tbl)
    if not tbl or not tbl.IsTFAWeapon then return end

    local oldShoot = tbl.ShootBulletInformation or tbl.ShootBullet or tbl.PrimaryAttack
    if not oldShoot then return end
    if tbl.FT_Hooked_TFA then return end
    tbl.FT_Hooked_TFA = true

    tbl.ShootBulletInformation = function(self, ...)
        local owner = self:GetOwner()
        if IsValid(owner) and owner:IsPlayer() then
            if WeaponHasAmmo(self) then
                ProcessBullet(owner, owner:GetShootPos(), owner:GetAimVector())
            end
        end

        return oldShoot(self, ...)
    end
end)

hook.Add("PreRegisterSWEP", "FT_HookTacRP_Shoot", function(class, tbl)
    if not tbl or not tbl.Base or tbl.Base ~= "tacrp_base" then return end

    local oldShoot = tbl.ShootBullet or tbl.PrimaryAttack
    if not oldShoot then return end
    if tbl.FT_Hooked_TacRP then return end
    tbl.FT_Hooked_TacRP = true

    tbl.ShootBullet = function(self, ...)
        local owner = self:GetOwner()
        if IsValid(owner) and owner:IsPlayer() then
            if WeaponHasAmmo(self) then
                ProcessBullet(owner, owner:GetShootPos(), owner:GetAimVector())
            end
        end

        return oldShoot(self, ...)
    end
end)

hook.Add("LVS.DoFireBullet", "FT_LVS_TankBulletTrack", function(weapon, bullet)
    if not bullet or not bullet.Attacker then return end
    local attacker = bullet.Attacker
    if not IsValid(attacker) then return end

    local src = bullet.Src
    local dir = bullet.Dir

    if not src or not dir then return end

    ProcessBullet(attacker, src, dir)
end)

hook.Add("lvs_bullet_hit", "FT_LVS_BulletHitTrack", function(data)
    local shooter = data.Attacker
    if not IsValid(shooter) then return end

    local src = data.StartPos
    local dir = data.Dir

    ProcessBullet(shooter, src, dir)
end)

hook.Add("EntityFireBullets", "FT_BulletTrack", function(ent, data)
    ProcessBullet(ent, data.Src, data.Dir)
end)

hook.Add("TFA_Bullet", "FT_TFA_BulletTrack", function(ent, data)
    if not IsValid(ent) then return end
    ProcessBullet(ent, data.src, data.dir)
end)

hook.Add("TacRP_FireBullet", "FT_TACRP_BulletTrack", function(wep, data)
    local owner = wep:GetOwner()
    if not IsValid(owner) then return end
    ProcessBullet(owner, data.Src, data.Dir)
end)

hook.Add("ARC9_FireBullets", "FT_ARC9_BulletTrack", function(wep, data)
    local owner = wep:GetOwner()
    if not IsValid(owner) then return end
    ProcessBullet(owner, data.Src, data.Dir)
end)

hook.Add("TFA_Bullet", "FT_TFA_BASE_Suppression", function(ent, data)
    local owner = IsValid(ent) and ent:GetOwner()
    if not IsValid(owner) or not owner:IsPlayer() then return end

    local src = data.src or owner:GetShootPos()
    local dir = data.dir or owner:GetAimVector()

    ProcessBullet(owner, src, dir)
end)

hook.Add("TFA_Throwing", "FT_TFA_NADE_Suppression", function(wep, nade)
    local owner = IsValid(wep) and wep:GetOwner()
    if not IsValid(owner) or not owner:IsPlayer() then return end

    local src = owner:GetShootPos()
    local dir = owner:GetAimVector()

    ProcessBullet(owner, src, dir)
end)

hook.Add("TFA_PostPrimaryAttack", "FT_TFA_AllWeapons_Suppression", function(wep)
    if not IsValid(wep) then return end
    if not wep.IsTFAWeapon then return end

    local owner = wep:GetOwner()
    if not IsValid(owner) or not owner:IsPlayer() then return end

    local src = owner:GetShootPos()
    local dir = owner:GetAimVector()

    ProcessBullet(owner, src, dir)
end)

timer.Create("FT_SuppressionDecay", 0.1, 0, function()
    for _, ply in ipairs(player.GetAll()) do
        if not IsValid(ply) then continue end

        local v = ply:GetNWFloat("FT_Suppression", 0)
        if v > 0 then
            ply:SetNWFloat("FT_Suppression", math.max(v - 0.03, 0))
        end
    end
end)

hook.Add("EntityFireBulletsCallback", "FT_BulletTrackCallback", function(ent, data)
    ProcessBullet(ent, data.Src, data.Dir)
end)
-----------------------------------------
-- FT BULLET CONTUSION (MILITARY RP)
-- Собственность Олега Закона и Scripty
-----------------------------------------
