AddCSLuaFile()

ENT.Type            = "anim"

ENT.PrintName = "Missile"
ENT.Author = "Luna"
ENT.Information = "LVS Missile"
ENT.Category = "[LVS]"

ENT.Spawnable		= true
ENT.AdminOnly		= true

ENT.ExplosionEffect = "lvs_explosion_small"

ENT.lvsProjectile = true
ENT.VJ_ID_Danger = true

function ENT:SetupDataTables()
	self:NetworkVar( "Bool", 0, "Active" )
	self:NetworkVar( "Entity", 0, "NWTarget" )
end

if SERVER then
	util.AddNetworkString( "lvs_missile_hud" )

	function ENT:GetAvailableTargets()
		local targets = {
			[1] = player.GetAll(),
			[2] = LVS:GetVehicles(),
			[3] = LVS:GetNPCs(),
		}

		return targets
	end

	function ENT:FindTarget( pos, forward, cone_ang, cone_len )
		local targets = self:GetAvailableTargets()

		local Attacker = self:GetAttacker()
		local Parent = self:GetParent()
		local Owner = self:GetOwner()
		local Target = NULL
		local DistToTarget = 0

		for _, tbl in ipairs( targets ) do
			for _, ent in pairs( tbl ) do
				if not IsValid( ent ) or ent == Parent or ent == Owner or Target == ent or Attacker == ent then continue end

				local pos_ent = ent:GetPos()
				local dir = (pos_ent - pos):GetNormalized()
				local ang = math.deg( math.acos( math.Clamp( forward:Dot( dir ) ,-1,1) ) )

				if ang > cone_ang then continue end

				local dist, _, _ = util.DistanceToLine( pos, pos + forward * cone_len, pos_ent )

				if not IsValid( Target ) then
					Target = ent
					DistToTarget = dist

					continue
				end

				if dist < DistToTarget then
					Target = ent
					DistToTarget = dist
				end
			end
		end

		self:SetTarget( Target )

		local ply = self:GetAttacker()

		if not IsValid( ply ) or not ply:IsPlayer() then return end

		net.Start( "lvs_missile_hud", true )
			net.WriteEntity( self )
		net.Send( ply )
	end

	function ENT:SetEntityFilter( filter )
		if not istable( filter ) then return end

		self._FilterEnts = {}

		for _, ent in pairs( filter ) do
			self._FilterEnts[ ent ] = true
		end
	end
	function ENT:SetTarget( ent ) self:SetNWTarget( ent ) end
	function ENT:SetDamage( num ) self._dmg = num end
	function ENT:SetForce( num ) self._force = num end
	function ENT:SetThrust( num ) self._thrust = num end
	function ENT:SetSpeed( num ) self._speed = num end
	function ENT:SetTurnSpeed( num ) self._turnspeed = num end
	function ENT:SetRadius( num ) self._radius = num end
	function ENT:SetAttacker( ent ) self._attacker = ent end

	function ENT:GetAttacker() return self._attacker or NULL end
	function ENT:GetDamage() return (self._dmg or 100) end
	function ENT:GetForce() return (self._force or 4000) end
	function ENT:GetRadius() return (self._radius or 250) end
	function ENT:GetSpeed() return (self._speed or 4000) end
	function ENT:GetTurnSpeed() return (self._turnspeed or 1) * 100 end
	function ENT:GetThrust() return (self._thrust or 500) end
	function ENT:GetTarget()
		if IsValid( self:GetNWTarget() ) then
			local Pos = self:GetPos()
			local tPos = self:GetTargetPos()

			local Sub = tPos - Pos
			local Len = Sub:Length()
			local Dir = Sub:GetNormalized()
			local Forward = self:GetForward()

			local AngToTarget = math.deg( math.acos( math.Clamp( Forward:Dot( Dir ) ,-1,1) ) )

			local LooseAng = math.min( Len / 100, 90 )

			if AngToTarget > LooseAng then
				self:SetNWTarget( NULL )
			end
		end

		return self:GetNWTarget()
	end
	function ENT:GetTargetPos()
		local Target = self:GetNWTarget()

		if not IsValid( Target ) then return Vector(0,0,0) end

		if isfunction( Target.GetMissileOffset ) then
			return Target:LocalToWorld( Target:GetMissileOffset() )
		end

		return Target:GetPos()
	end

	function ENT:SpawnFunction( ply, tr, ClassName )

		local ent = ents.Create( ClassName )
		ent:SetPos( ply:GetShootPos() )
		ent:SetAngles( ply:EyeAngles() )
		ent:Spawn()
		ent:Activate()
		ent:SetAttacker( ply )
		ent:Enable()

		return ent
	end

	function ENT:Initialize()	
		self:SetModel( "models/weapons/w_missile_launch.mdl" )
		self:SetMoveType( MOVETYPE_NONE )
		self:SetRenderMode( RENDERMODE_TRANSALPHA )
	end

	function ENT:Enable()
		if self.IsEnabled then return end

		local Parent = self:GetParent()

		if IsValid( Parent ) then
			self:SetOwner( Parent )
			self:SetParent( NULL )
		end

		self:PhysicsInit( SOLID_VPHYSICS )
		self:SetMoveType( MOVETYPE_VPHYSICS )
		self:SetSolid( SOLID_VPHYSICS )
		self:SetCollisionGroup( COLLISION_GROUP_NONE )
		self:PhysWake()

		self.IsEnabled = true

		local pObj = self:GetPhysicsObject()
		
		if not IsValid( pObj ) then
			self:Remove()

			print("LVS: missing model. Missile terminated.")

			return
		end

		pObj:SetMass( 1 ) 
		pObj:EnableGravity( false ) 
		pObj:EnableMotion( true )
		pObj:EnableDrag( false )

		self:SetTrigger( true )

		self:StartMotionController()

		self:PhysWake()

		self.SpawnTime = CurTime()

		self:SetActive( true )
	end

	function ENT:PhysicsSimulate( phys, deltatime )
		phys:Wake()

		local Thrust = self:GetThrust()
		local Speed = self:GetSpeed()
		local Pos = self:GetPos()
		local velL = self:WorldToLocal( Pos + self:GetVelocity() )

		local ForceLinear = (Vector( Speed * Thrust,0,0) - velL) * deltatime

		local Target = self:GetTarget()

		if not IsValid( Target ) then
			return (-phys:GetAngleVelocity() * 250 * deltatime), ForceLinear, SIM_LOCAL_ACCELERATION
		end

		local AngForce = -self:WorldToLocalAngles( (self:GetTargetPos() - Pos):Angle() )

		local ForceAngle = (Vector(AngForce.r,-AngForce.p,-AngForce.y) * self:GetTurnSpeed() - phys:GetAngleVelocity() * 5 ) * 250 * deltatime

		return ForceAngle, ForceLinear, SIM_LOCAL_ACCELERATION
	end

	function ENT:Think()	
		local T = CurTime()

		self:NextThink( T + 1 )

		if not self.SpawnTime then return true end

		if (self.SpawnTime + 12) < T then
			self:Detonate()
		end

		return true
	end

	ENT.IgnoreCollisionGroup = {
		[COLLISION_GROUP_NONE] = true,
		[COLLISION_GROUP_WORLD] =  true,
		[COLLISION_GROUP_INTERACTIVE_DEBRIS] = true,
	}

	function ENT:StartTouch( entity )
		if entity == self:GetAttacker() then return end

		if istable( self._FilterEnts ) and self._FilterEnts[ entity ] then return end

		if entity.GetCollisionGroup and self.IgnoreCollisionGroup[ entity:GetCollisionGroup() ] then return end

		if entity.lvsProjectile then return end

		self:Detonate( entity )
	end

	function ENT:EndTouch( entity )
	end

	function ENT:Touch( entity )
	end

	function ENT:PhysicsCollide( data )
		if istable( self._FilterEnts ) and self._FilterEnts[ data.HitEntity ] then return end

		self:Detonate( data.HitEntity )
	end

	function ENT:OnTakeDamage( dmginfo )	
	end

	function ENT:Detonate( target )
		if not self.IsEnabled or self.IsDetonated then return end

		self.IsDetonated = true

		local Pos =  self:GetPos() 

		local effectdata = EffectData()
			effectdata:SetOrigin( Pos )
		util.Effect( self.ExplosionEffect, effectdata, true, true )

		local attacker = self:GetAttacker()

		LVS:BlastDamage( Pos, self:GetForward(), IsValid( attacker ) and attacker or game.GetWorld(), self, self:GetDamage(), DMG_BLAST, self:GetRadius(), self:GetForce() )

		SafeRemoveEntityDelayed( self, FrameTime() )
	end
else
	function ENT:Initialize()	
	end

	function ENT:Enable()
		if self.IsEnabled then return end

		self.IsEnabled = true

		self.snd = CreateSound(self, "weapons/rpg/rocket1.wav")
		self.snd:SetSoundLevel( 80 )
		self.snd:Play()

		local effectdata = EffectData()
			effectdata:SetOrigin( self:GetPos() )
			effectdata:SetEntity( self )
		util.Effect( "lvs_missiletrail", effectdata )
	end

	function ENT:CalcDoppler()
		local Ent = LocalPlayer()

		local ViewEnt = Ent:GetViewEntity()

		if Ent:lvsGetVehicle() == self then
			if ViewEnt == Ent then
				Ent = self
			else
				Ent = ViewEnt
			end
		else
			Ent = ViewEnt
		end

		local sVel = self:GetVelocity()
		local oVel = Ent:GetVelocity()

		local SubVel = oVel - sVel
		local SubPos = self:GetPos() - Ent:GetPos()

		local DirPos = SubPos:GetNormalized()
		local DirVel = SubVel:GetNormalized()

		local A = math.acos( math.Clamp( DirVel:Dot( DirPos ) ,-1,1) )

		return (1 + math.cos( A ) * SubVel:Length() / 13503.9)
	end

	function ENT:Draw()
		if not self:GetActive() then return end

		self:DrawModel()
	end

	function ENT:Think()
		if self.snd then
			self.snd:ChangePitch( 100 * self:CalcDoppler() )
		end

		if self.IsEnabled then return end

		if self:GetActive() then
			self:Enable()
		end
	end

	function ENT:SoundStop()
		if self.snd then
			self.snd:Stop()
		end
	end

	function ENT:OnRemove()
		self:SoundStop()
	end

	local function DrawDiamond( X, Y, radius, angoffset )
		angoffset = angoffset or 0

		local segmentdist = 90
		local radius2 = radius + 1

		for ang = 0, 360, segmentdist do
			local a = ang + angoffset
			surface.DrawLine( X + math.cos( math.rad( a ) ) * radius, Y - math.sin( math.rad( a ) ) * radius, X + math.cos( math.rad( a + segmentdist ) ) * radius, Y - math.sin( math.rad( a + segmentdist ) ) * radius )
			surface.DrawLine( X + math.cos( math.rad( a ) ) * radius2, Y - math.sin( math.rad( a ) ) * radius2, X + math.cos( math.rad( a + segmentdist ) ) * radius2, Y - math.sin( math.rad( a + segmentdist ) ) * radius2 )
		end
	end

	local color_red = Color(255,0,0,255)
	local HudTargets = {}
	hook.Add( "HUDPaint", "!!!!lvs_missile_hud", function()
		local T = CurTime()

		local Index = 0

		surface.SetDrawColor( 255, 0, 0, 255 )

		for ID, _ in pairs( HudTargets ) do
			local Missile = Entity( ID )

			if not IsValid( Missile ) then
				HudTargets[ ID ] = nil

				continue
			end

			local Target = Missile:GetNWTarget()

			if not IsValid( Target ) then
				HudTargets[ ID ] = nil

				continue
			end

			local MissilePos = Missile:GetPos():ToScreen()
			local TargetPos = Target:LocalToWorld( Target:OBBCenter() ):ToScreen()

			Index =  Index + 1

			if not TargetPos.visible then continue end

			DrawDiamond( TargetPos.x, TargetPos.y, 40, ID * 1337 - T * 100 )

			draw.DrawText("LOCK", "LVS_FONT", TargetPos.x + 20, TargetPos.y + 20, color_red, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP )

			if not MissilePos.visible then continue end

			DrawDiamond( MissilePos.x, MissilePos.y, 16, ID * 1337 - T * 100 )
			draw.DrawText( Index, "LVS_FONT", MissilePos.x + 10, MissilePos.y + 10, color_red, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP )
		
			surface.DrawLine( MissilePos.x, MissilePos.y, TargetPos.x, TargetPos.y )
		end
	end )

	net.Receive( "lvs_missile_hud", function( len )
		local ent = net.ReadEntity()

		if not IsValid( ent ) then return end

		HudTargets[ ent:EntIndex() ] = true
	end )
end
