AddCSLuaFile()

ENT.Base                     = "tacrp_proj_base"
ENT.PrintName                = "Thrown Knife"
ENT.Spawnable                = false

ENT.Model                    = "models/weapons/tacint/w_knife.mdl"

ENT.IsRocket = false

ENT.InstantFuse = false
ENT.RemoteFuse = false
ENT.ImpactFuse = true

ENT.ExplodeOnDamage = false
ENT.ExplodeUnderwater = true

ENT.Delay = 0
ENT.ImpactDamage = 0

ENT.SmokeTrail = false
local path = "tacrp/weapons/knife/"
ENT.Sound_MeleeHit = {
    path .. "/scrape_metal-1.wav",
    path .. "/scrape_metal-2.wav",
    path .. "/scrape_metal-3.wav",
}
ENT.Sound_MeleeHitBody = {
    path .. "/flesh_hit-1.wav",
    path .. "/flesh_hit-2.wav",
    path .. "/flesh_hit-3.wav",
    path .. "/flesh_hit-4.wav",
    path .. "/flesh_hit-5.wav",
}

ENT.Damage = 35

DEFINE_BASECLASS(ENT.Base)

function ENT:Initialize()
    BaseClass.Initialize(self)
    if SERVER then
        self:GetPhysicsObject():SetDragCoefficient(2)
    end
end

function ENT:Impact(data, collider)
    if self.Impacted then return end
    self.Impacted = true

    local tgt = data.HitEntity
    local attacker = self.Attacker or self:GetOwner() or self
    if IsValid(tgt) then
        local d = data.OurOldVelocity:GetNormalized()

        local dmg = DamageInfo()
        dmg:SetAttacker(attacker)
        dmg:SetInflictor(IsValid(self.Inflictor) and self.Inflictor or self)
        dmg:SetDamage(self.Damage)
        dmg:SetDamageType(self.DamageType or DMG_SLASH)
        dmg:SetDamageForce(d * 10000)
        dmg:SetDamagePosition(data.HitPos)

        local tgtpos = data.HitPos
        if (tgt:IsPlayer() or tgt:IsNPC() or tgt:IsNextBot()) then
            if (tgt:GetNWFloat("TacRPLastBashed", 0) + 3 >= CurTime()
                    or (tgt:GetNWFloat("TacRPStunStart", 0) + tgt:GetNWFloat("TacRPStunDur", 0) >= CurTime())) then
                dmg:ScaleDamage(1.5)
                tgt:EmitSound("weapons/crossbow/bolt_skewer1.wav", 80, 110)
            end

            -- Check if the knife is a headshot
            -- Either the head is the closest bodygroup, or the direction is quite on point
            local headpos = nil
            local pos = data.HitPos + d * 8
            local hset = tgt:GetHitboxSet()
            local hdot, bhg, bdist, hdist = 0, 0, math.huge, math.huge
            for i = 0, tgt:GetHitBoxCount(hset) or 0 do

                local bone = tgt:GetHitBoxBone(i, hset)
                local mtx = bone and tgt:GetBoneMatrix(bone)
                if !mtx then continue end
                local hpos = mtx:GetTranslation()
                local dot = (hpos - data.HitPos):GetNormalized():Dot(d)
                local dist = (hpos - pos):LengthSqr()

                if tgt:GetHitBoxHitGroup(i, hset) == HITGROUP_HEAD then
                    hdot = dot
                    hdist = dist
                    headpos = hpos
                end
                if dist < bdist then
                    bdist = dist
                    bhg = tgt:GetHitBoxHitGroup(i, hset)
                    tgtpos = hpos
                end
            end

            if bhg == HITGROUP_HEAD or (hdot >= 0.85 and hdist < 2500) then
                dmg:ScaleDamage(2)
                tgt:EmitSound("player/headshot" .. math.random(1, 2) .. ".wav", 80, 105)
                tgtpos = headpos
            end

            self:EmitSound(istable(self.Sound_MeleeHitBody) and self.Sound_MeleeHitBody[math.random(1, #self.Sound_MeleeHitBody)] or self.Sound_MeleeHitBody, 80, 110, 1)
            -- self:EmitSound("tacrp/weapons/knife/flesh_hit-" .. math.random(1, 5) .. ".wav", 80, 110, 1)

            -- local ang = data.OurOldVelocity:Angle()
            -- local fx = EffectData()
            -- fx:SetStart(data.HitPos - d * 4)
            -- fx:SetOrigin(data.HitPos)
            -- fx:SetNormal(d)
            -- fx:SetAngles(-ang)
            -- fx:SetEntity(tgt)
            -- fx:SetDamageType(DMG_SLASH)
            -- fx:SetSurfaceProp(data.TheirSurfaceProps)
            -- util.Effect("Impact", fx)

        else
            dmg:SetDamageForce(d * 30000)
            local ang = data.OurOldVelocity:Angle()
            local fx = EffectData()
            fx:SetOrigin(data.HitPos)
            fx:SetNormal(-ang:Forward())
            fx:SetAngles(-ang)
            util.Effect("ManhackSparks", fx)
            if SERVER then
                self:EmitSound(istable(self.Sound_MeleeHit) and self.Sound_MeleeHit[math.random(1, #self.Sound_MeleeHit)] or self.Sound_MeleeHit, 80, 110, 1)
            end
        end

        -- tgt:TakeDamageInfo(dmg)

        local atktr = util.TraceLine({
            start = self:GetPos(),
            endpos = tgtpos,
            filter = self
        })

        TacRP.CancelBodyDamage(tgt, dmg, atktr.HitGroup)
        tgt:DispatchTraceAttack(dmg, atktr)
    else
        -- leave a bullet hole. Also may be able to hit things it can't collide with (like stuck C4)
        local ang = data.OurOldVelocity:Angle()
        self:FireBullets({
            Attacker = attacker,
            Damage = self.Damage,
            Force = 1,
            Distance = 4,
            HullSize = 4,
            Tracer = 0,
            Dir = ang:Forward(),
            Src = data.HitPos - ang:Forward(),
            IgnoreEntity = self,
            Callback = function(atk, tr, dmginfo)
                dmginfo:SetInflictor(IsValid(self.Inflictor) and self.Inflictor or self)
                if tr.HitSky then
                    SafeRemoveEntity(self)
                else
                    local fx = EffectData()
                    fx:SetOrigin(data.HitPos)
                    fx:SetNormal(-ang:Forward())
                    fx:SetAngles(-ang)
                    util.Effect("ManhackSparks", fx)
                    if SERVER then
                        self:EmitSound(istable(self.Sound_MeleeHit) and self.Sound_MeleeHit[math.random(1, #self.Sound_MeleeHit)] or self.Sound_MeleeHit, 80, 110, 1)
                    end
                end
            end
        })
    end

    -- self:SetCollisionGroup(COLLISION_GROUP_DEBRIS)

    if self.DamageType == DMG_SLASH and (tgt:IsWorld() or (IsValid(tgt) and tgt:GetPhysicsObject():IsValid())) then
        local angles = data.OurOldVelocity:Angle()
        angles:RotateAroundAxis(self:GetRight(), -90)
        self:GetPhysicsObject():Sleep()

        timer.Simple(0.01, function()
            if IsValid(self) and tgt:IsWorld() or (IsValid(tgt) and (!(tgt:IsNPC() or tgt:IsPlayer()) or tgt:Health() > 0)) then
                if !tgt:IsWorld() then
                    self:SetSolid(SOLID_NONE)
                end
                self:SetMoveType(MOVETYPE_NONE)

                local f = {self, self:GetOwner()}
                table.Add(f, tgt:GetChildren())
                local tr = util.TraceLine({
                    start = data.HitPos - data.OurOldVelocity,
                    endpos = data.HitPos + data.OurOldVelocity,
                    filter = f,
                    mask = MASK_SOLID,
                    ignoreworld = true,
                })

                local bone = (tr.Entity == tgt) and tr.PhysicsBone == 0
                        and tr.Entity:GetHitBoxBone(tr.HitBox, tr.Entity:GetHitboxSet())
                        or tr.PhysicsBone or -1
                local matrix = tgt:GetBoneMatrix(bone)
                if tr.Entity == tgt and matrix then
                    local bpos = matrix:GetTranslation()
                    local bang = matrix:GetAngles()
                    self:SetPos(data.HitPos)
                    self:FollowBone(tgt, bone)
                    local n_pos, n_ang = WorldToLocal(tr.HitPos + tr.HitNormal * self:GetModelRadius() * 0.5, angles, bpos, bang)
                    self:SetLocalPos(n_pos)
                    self:SetLocalAngles(n_ang)
                    debugoverlay.Cross(pos, 8, 5, Color(255, 0, 255), true)
                else
                    self:SetAngles(angles)
                    self:SetPos(data.HitPos - data.OurOldVelocity:GetNormalized() * self:GetModelRadius() * 0.5)
                    if !tgt:IsWorld() then
                        self:SetParent(tgt)
                    end
                end
            elseif IsValid(self) then
                self:GetPhysicsObject():SetVelocityInstantaneous(data.OurNewVelocity * 0.5)
                self:GetPhysicsObject():SetAngleVelocityInstantaneous(data.OurOldAngularVelocity * 0.5)
            end
        end)
    end

    timer.Simple(0.01, function()
        self:SetCollisionGroup(COLLISION_GROUP_DEBRIS)
        if self.HasAmmo then
            self:SetTrigger(true)
            self:SetOwner(NULL)
        end
    end)

    timer.Simple(5, function()
        if IsValid(self) then
            self:SetRenderMode(RENDERMODE_TRANSALPHA)
            self:SetRenderFX(kRenderFxFadeFast)
        end
    end)
    SafeRemoveEntityDelayed(self, 7)

    return true
end

function ENT:Touch(ent)
    if self.HasAmmo and ent:IsPlayer() and (IsValid(self.Inflictor) and ent == self.Inflictor:GetOwner()) then
        self.HasAmmo = false
        ent:GiveAmmo(1, "xbowbolt")
        self:Remove()
    end
end

hook.Add("PostEntityTakeDamage", "TacRP_KnifeProj", function(ent)
    if (ent:IsPlayer() or ent:IsNPC()) and ent:Health() < 0 then
        for _, proj in pairs(ent:GetChildren()) do
            if proj:GetClass() == "tacrp_proj_knife" then proj:Remove() end
        end
    end
end)
