
dofile("Base.rte/Constants.lua")
require("Actors/AI/NativeCrabAI")

function Create(self)
	self.AI = NativeCrabAI:Create(self)
	self.SearchTimer = Timer()
	self.radarRange = 1024
	self.ValidTargets = {}
	self.Frame = 1
	
	-- The "AA-Drone Ammo Counter" object tracks ammo across battles and reduce gold cost of the carrier
	if self:HasObject("AA-Drone Ammo Counter") then
		if self.InventorySize == 1 then
			self.Frame = 2	-- One SAM left
		else
			self.Frame = 0	-- Out of ammo
		end
	end
	
	function self.UpdateInertSAM(self)
		if self.SearchTimer:IsPastSimMS(300) then
			local ArmedSAM = CreateAEmitter("Armed SAM", "Base.rte")
			if ArmedSAM then
				self.SAM.ToDelete = true
				self.SAM.HitsMOs = false
				self.SAM.GetsHitByMOs = false
				
				ArmedSAM.Pos = self.SAM.Pos
				ArmedSAM.Vel = self.SAM.Vel
				ArmedSAM.Team = self.SAM.Team
				ArmedSAM.IgnoresTeamHits = true
				ArmedSAM.RotAngle = self.SAM.RotAngle
				ArmedSAM.AngularVel = self.SAM.AngularVel
				
				self.proximityFuze = nil	-- Reset the proximity fuze
				
				-- Don't target the center of a craft
				if self.SAM_Target.ClassName == "ACDropShip" then
					if self.SAM_Target.Pos.X > self.SAM.Pos.X then
						self.SAM_Offset = Vector(-self.SAM_Target.Radius, 0) -- Left
					else
						self.SAM_Offset = Vector(self.SAM_Target.Radius, 0) -- Right
					end
				else
					self.SAM_Offset = Vector(0, self.SAM_Target.Radius*0.5) -- Below
				end
				
				-- Initialize missile velocity history and targeting data
				self.SAM_AimPos = self.SAM_Target.Pos + self.SAM_Target:RotateOffset(self.SAM_Offset)
				self.SAM_LastVel = Vector(self.SAM.Vel.X, self.SAM.Vel.Y)
				
				self.SAM = ArmedSAM
				MovableMan:AddMO(self.SAM)
				
				self.UpdateSAM = self.UpdateArmedSAM	-- Use the UpdateArmedSAM-function from now on
			else
				self.SAM = nil
			end
		else
			local angError = math.asin(Vector(0, -1):Cross(self.SAM:RotateOffset(Vector(1, 0))))	-- The angle between missile facing and straight up
			self.SAM.RotAngle = self.SAM.RotAngle + math.min(math.max(angError, -0.02), 0.02)
			self.SAM.AngularVel = self.SAM.AngularVel * 0.95
		end
	end
	
	function self.UpdateArmedSAM(self)
		-- Find the velocity vector that will take the missile to the target
		local FutureVel = self.SAM.Vel + (self.SAM.Vel-self.SAM_LastVel)*10
		local OptimalVel = SceneMan:ShortestDistance(self.SAM.Pos, self.SAM_AimPos, false)
		local angError = math.asin(OptimalVel.Normalized:Cross(FutureVel.Normalized))	-- The angle between FutureVel and OptimalVel
		
		-- Gradually turn towards the optimal velocity vector
		self.SAM.RotAngle = self.SAM.RotAngle + math.min(math.max(angError, -0.04), 0.04)
		
		-- Gradually return the thruster to the starting position if the missile is facing the target
		if math.abs(angError) < 0.15 then
			self.SAM.EmitAngle = self.SAM.EmitAngle * 0.8 + math.pi * 0.2
		else
			self.SAM.EmitAngle = math.max(math.min(self.SAM.EmitAngle+angError*0.1, 4.14), 2.14)	-- Vector thrust
		end
		
		-- Detonate the missile when appropriate
		local range = SceneMan:ShortestDistance(self.SAM.Pos, self.SAM_Target.Pos+self.SAM_Target:RotateOffset(self.SAM_Offset), false).Magnitude
		if self.proximityFuze then
			if range < 30 then
				self.SAM:GibThis()	-- The target is close enough; detonate
			elseif math.abs(angError) > 1.5 and range > self.proximityFuze then	-- The missile is moving away from the target: detonate
				self.SAM:GibThis()
			else
				self.proximityFuze = range
			end
		elseif range < 120 then	-- The target is close: arm the proximity fuze
			self.proximityFuze = range
		end
		
		self.SAM.AngularVel = self.SAM.AngularVel * 0.96 + (self.SAM.EmitAngle - math.pi) * 0.05	-- The vector thrust will cause the SAM to rotate
		self.SAM_LastVel = self.SAM_LastVel * 0.6 + self.SAM.Vel * 0.4	-- Used to calculate the acceleration of the missile
		self.SAM_AimPos = self.SAM_AimPos * 0.6 + (self.SAM_Target.Pos+self.SAM_Target:RotateOffset(self.SAM_Offset)+self.SAM_Target.Vel*math.min(range/50, 20)) * 0.4	-- Filter the AimPos to reduce noise
	end
end

function Update(self)
	if self.SAM then	-- Check if any old missile is alive
		if MovableMan:ValidMO(self.SAM) then
			if self.SAM_Target and MovableMan:IsActor(self.SAM_Target) then
				self.UpdateSAM(self)
			else
				-- The target is not valid any more: replace the missile with an intert one
				if self.SAM.PresetName == "Armed SAM" then
					local InertSAM = CreateAEmitter("Inert SAM", "Base.rte")
					if InertSAM then
						self.SAM.ToDelete = true
						self.SAM.HitsMOs = false
						self.SAM.GetsHitByMOs = false
						
						InertSAM.Pos = self.SAM.Pos
						InertSAM.Vel = self.SAM.Vel
						InertSAM.RotAngle = self.SAM.RotAngle
						InertSAM.AngularVel = self.SAM.AngularVel
						InertSAM.Team = self.Team
						InertSAM.IgnoresTeamHits = true
						MovableMan:AddMO(InertSAM)
					end
				end
				
				self.SAM = nil
				self.SAM_Target = nil
			end
		else
			self.SAM = nil
		end
	elseif self.Frame > 0 and self.Vel.Largest < 12 then	-- we have SAMs left
		if #self.ValidTargets < 1 then	-- Find valid targets
			if self.SearchTimer:IsPastSimMS(100) then
				self.SearchTimer:Reset()	-- Only search a few times/sec to reduce calculations per update
				
				-- Only look for targets if there are no obstacles above us
				local Trace = self:RotateOffset(Vector(0, -200))
				if not SceneMan:CastStrengthRay(self.Pos, Trace, 5, Vector(), 9, -1, true) then	-- Terrain str 5
					local obstructed = false
					local ID = SceneMan:CastMORay(self.AboveHUDPos, Trace, self.ID, self.IgnoresWhichTeam, 0, true, 15)
					if ID < rte.NoMOID then
						local MO = MovableMan:GetMOFromID(ID)
						if ID ~= MO.RootID then
							MO = MovableMan:GetMOFromID(MO.RootID)
						end
						
						if MO.Team == self.Team then
							obstructed = true	-- The MO above us is on our team: don't shoot
						end
					end
					
					if not obstructed then
						local Dist, range, angle
						for Act in MovableMan.Actors do
							if Act.Team ~= self.Team and not Act:IsDead() and Act:HasObjectInGroup("Craft") then
								Dist = SceneMan:ShortestDistance(self.Pos, Act.Pos+Act.Vel*9, false)
								range = Dist.Magnitude - Act.Radius
								if range < self.radarRange then	-- Shoot at enemy craft within radarRange pixels
									angle = math.abs(math.asin(Dist.Normalized:Cross(self:RotateOffset(Vector(0, -1)))))
									if angle < 1.7 then -- Search in a ~200 degree arc above us
										table.insert(self.ValidTargets, {Actor=Act, priority=angle/3+range/300+(3-Act.Health/100)-math.abs(Act.AngularVel)})	-- prioritize close, damaged targets that are straight above us that does not spin
									end
								end
							end
						end
					end
				end
			end
			
			-- Sort the targets in ascending order
			if #self.ValidTargets > 1 then
				table.sort(self.ValidTargets, function(A, B) return A.priority > B.priority end)
			end
			
			-- Store brain location
			local GmActiv = ActivityMan:GetActivity()
			for player = Activity.PLAYER_1, Activity.MAXPLAYERCOUNT - 1 do
				if GmActiv:PlayerActive(player) and GmActiv:GetTeamOfPlayer(player) == self.Team then
					local Brain = GmActiv:GetPlayerBrain(player)
					if Brain and MovableMan:IsActor(Brain) then
						self.MyBrainPos = Vector(Brain.Pos.X, Brain.Pos.Y)
					end
					
					break
				end
			end
		else	-- Check if the missile have a clear line of sight to any of the selected targets
			local NewTarget = table.remove(self.ValidTargets).Actor	-- Only check one target to reduce calculations per update
			if NewTarget and MovableMan:IsActor(NewTarget) and not NewTarget:IsDead() then
				local Trace = SceneMan:ShortestDistance(self.AboveHUDPos, NewTarget.Pos, false)
				-- Don't shoot at targets that are out of reach
				if Trace.Magnitude < self.radarRange then
					-- Don't shoot at targets that are very close to the brain
					if SceneMan:ShortestDistance(self.MyBrainPos or self.Pos, NewTarget.Pos, false).Largest - NewTarget.Radius > 120 then
						-- First do a very inexact scan of half the distance to the target for friendly dropships and terrain
						if SceneMan:CastObstacleRay(self.AboveHUDPos, Trace*0.5, Vector(), Vector(), self.ID, self.IgnoresWhichTeam, 0, 25) < 0 then
							-- If nothing was found, do a more exact scan for terrain all the way to the target
							if not SceneMan:CastStrengthRay(self.AboveHUDPos, Trace, 5, Vector(), 9, -1, true) then	-- Terrain str 5
								self.SearchTimer:Reset()
								
								-- Spawn the SAM
								self.SAM = CreateAEmitter("Inert SAM", "Base.rte")
								if self.SAM then
									local SpawnOffset = Vector(0, -15)
									if self.Frame < 2 then
										self.Frame = 2 -- Remove the left SAM
										SpawnOffset.X = -10
									else
										self.Frame = 0 -- Remove the right SAM
										SpawnOffset.X = 10
									end
									
									self.SAM.Team = self.Team
									self.SAM.RotAngle = self.RotAngle + 1.571
									self.SAM.AngularVel = self.AngularVel
									self.SAM.Pos = self.Pos + self:RotateOffset(SpawnOffset)
									self.SAM.Vel = self.Vel + self:RotateOffset(Vector(0, -17))
									self.SAM.IgnoresTeamHits = true
									self.SAM:TriggerBurst()
									MovableMan:AddMO(self.SAM)
									
									self.armedSAM = false
									self.SAM_Target = NewTarget
									
									-- Call this function to update the missile
									self.UpdateSAM = self.UpdateInertSAM
								end
								
								-- Add an invisible object to the inventory to track ammo
								local AmmoCounter = CreateMOSRotating("AA-Drone Ammo Counter", "Base.rte")
								if AmmoCounter then
									self:AddInventoryItem(AmmoCounter)
								end
							end
						end
					end
				end
			end
		end
	end
end

function UpdateAI(self)
	self.AI:Update(self)
end
