-- thrown knife

AddCSLuaFile()

if CLIENT then
   ENT.PrintName = "knife_thrown"
   ENT.Icon = "vgui/ttt/icon_knife"
end


ENT.Type = "anim"
ENT.Model = Model("models/weapons/w_knife_t.mdl")

-- When true, score code considers us a weapon
ENT.Projectile = true

ENT.Stuck = false
ENT.Weaponised = false
ENT.CanHavePrints = false
ENT.IsSilent = true
ENT.CanPickup = false

ENT.WeaponID = AMMO_KNIFE

ENT.Damage = 50

function ENT:Initialize()
   self:SetModel(self.Model)

   self:PhysicsInit(SOLID_VPHYSICS)

   if SERVER then
      self:SetGravity(0.4)
      self:SetFriction(1.0)
      self:SetElasticity(0.45)

      self.StartPos = self:GetPos()

      self:NextThink(CurTime())
   end

   self.Weaponised = false
   self.Stuck = false
end

function ENT:HitPlayer(other, tr)

   local range_dmg = math.max(self.Damage, self.StartPos:Distance(self:GetPos()) / 3)

   if other:Health() < range_dmg + 10 then
      self:KillPlayer(other, tr)
   elseif SERVER then
      local dmg = DamageInfo()
      dmg:SetDamage(range_dmg)
      dmg:SetAttacker(self:GetOwner())
      dmg:SetInflictor(self)
      dmg:SetDamageForce(self:EyeAngles():Forward())
      dmg:SetDamagePosition(self:GetPos())
      dmg:SetDamageType(DMG_SLASH)

      local ang = Angle(-28,0,0) + tr.Normal:Angle()
      ang:RotateAroundAxis(ang:Right(), -90)
      other:DispatchTraceAttack(dmg, self:GetPos() + ang:Forward() * 3, other:GetPos())

      if not self.Weaponised then
         self:BecomeWeaponDelayed()
      end
   end
   
   -- As a thrown knife, after we hit a target we can never hit one again.
   -- If we are picked up and re-thrown, a new knife_proj entity is created.
   -- To make sure we can never deal damage twice, make HitPlayer do nothing.
   self.HitPlayer = util.noop
end

function ENT:KillPlayer(other, tr)
   local dmg = DamageInfo()
   dmg:SetDamage(2000)
   dmg:SetAttacker(self:GetOwner())
   dmg:SetInflictor(self)
   dmg:SetDamageForce(self:EyeAngles():Forward())
   dmg:SetDamagePosition(self:GetPos())
   dmg:SetDamageType(DMG_SLASH)

   -- this bone is why we need the trace
   local bone = tr.PhysicsBone
   local pos = tr.HitPos
   local norm = tr.Normal
   local ang = Angle(-28,0,0) + norm:Angle()
   ang:RotateAroundAxis(ang:Right(), -90)
   pos = pos - (ang:Forward() * 8)

   local knife = self
   local prints = self.fingerprints

   other.effect_fn = function(rag)

                        if not IsValid(knife) or not IsValid(rag) then return end

                        knife:SetPos(pos)
                        knife:SetCollisionGroup(COLLISION_GROUP_DEBRIS)
                        knife:SetAngles(ang)

                        knife:SetMoveCollide(MOVECOLLIDE_DEFAULT)
                        knife:SetMoveType(MOVETYPE_VPHYSICS)

                        knife.fingerprints = prints
                        knife:SetNWBool("HasPrints", true)

                        --knife:SetSolid(SOLID_NONE)
                        -- knife needs to be trace-able to get prints
                        local phys = knife:GetPhysicsObject()
                        if IsValid(phys) then
                           phys:EnableCollisions(false)
                        end

                        constraint.Weld(rag, knife, bone, 0, 0, true)

                        rag:CallOnRemove("ttt_knife_cleanup", function() SafeRemoveEntity(knife) end)
                     end


   other:DispatchTraceAttack(dmg, self:GetPos() + ang:Forward() * 3, other:GetPos())

   self.Stuck = true
end

if SERVER then
  function ENT:Think()
     if self.Stuck then return end

     local vel = self:GetVelocity()
     if vel == vector_origin then return end

     local tr = util.TraceLine({start=self:GetPos(), endpos=self:GetPos() + vel:GetNormal() * 20, filter={self, self:GetOwner()}, mask=MASK_SHOT_HULL})

     if tr.Hit and tr.HitNonWorld and IsValid(tr.Entity) then
        local other = tr.Entity
        if other:IsPlayer() then
           self:HitPlayer(other, tr)
        end
     end

     self:NextThink(CurTime())
     return true
  end
end

-- When this entity touches anything that is not a player, it should turn into a
-- weapon ent again. If it touches a player it sticks in it.
if SERVER then
   function ENT:BecomeWeapon()
      self.Weaponised = true

      local wep = ents.Create("weapon_ttt_knife")
      wep:SetPos(self:GetPos())
      wep:SetAngles(self:GetAngles())
      wep.IsDropped = true

      local prints = self.fingerprints or {}

      self:Remove()

      wep:Spawn()
      wep.fingerprints = wep.fingerprints or {}
      table.Add(wep.fingerprints, prints)

      return wep
   end

   function ENT:BecomeWeaponDelayed()
      -- delay the weapon-replacement a tick because Source gets very angry
      -- if you do fancy stuff in a physics callback
      local knife = self
      timer.Simple(0,
                   function()
                      if IsValid(knife) and not knife.Weaponised then
                         knife:BecomeWeapon()
                      end
                   end)
   end

   function ENT:PhysicsCollide(data, phys)
      if self.Stuck then return false end

      local other = data.HitEntity
      if not IsValid(other) and not other:IsWorld() then return end

      if other:IsPlayer() then
         local tr = util.TraceLine({start=self:GetPos(), endpos=other:LocalToWorld(other:OBBCenter()), filter={self, self:GetOwner()}, mask=MASK_SHOT_HULL})
         if tr.Hit and tr.Entity == other then
            self:HitPlayer(other, tr)
         end

         return true
      end

      if not self.Weaponised then
         self:BecomeWeaponDelayed()
      end
   end
end
