
package.loaded.Constants = nil; require("Constants")
require("Actors/AI/HumanBehaviors")
require("Actors/AI/PID")

function Create(self)
	---------------- AI variables start ----------------
	self.lateralMoveState = Actor.LAT_STILL
	self.proneState = AHuman.NOTPRONE
	self.jumpState = AHuman.NOTJUMPING
	self.deviceState = AHuman.STILL
	self.lastAIMode = Actor.AIMODE_NONE
	self.teamBlockState = Actor.NOTBLOCKED
	self.SentryFacing = self.HFlipped
	self.fire = false
	self.minBurstTime = math.min(200, self.JetTimeTotal*0.99)
	
	self.Ctrl = self:GetController()
	
	self.AirTimer = Timer()
	self.PickUpTimer = Timer()
	self.ReloadTimer = Timer()
	self.BlockedTimer = Timer()
	
	self.AlarmTimer = Timer()
	self.AlarmTimer:SetSimTimeLimitMS(500)
	
	self.TargetLostTimer = Timer()
	self.TargetLostTimer:SetSimTimeLimitMS(1500)
	
	-- check if this team is controlled by a human
	local Activ = ActivityMan:GetActivity()
	for player = Activity.PLAYER_1, Activity.MAXPLAYERCOUNT - 1 do
		if Activ:PlayerActive(player) and Activ:PlayerHuman(player) then
			if self.Team == Activ:GetTeamOfPlayer(player) then
				self.isPlayerOwned = true
				self.PlayerInterferedTimer = Timer()
				self.PlayerInterferedTimer:SetSimTimeLimitMS(500)
				break
			end
		end
	end
	
	-- functions that create behaviors. the default behaviors are stored in the HumanBehaviors table. store your custom behaviors in a table to avoid name conflicts between mods.
	function self:CreateSentryBehavior()
		if not self.FirearmIsReady and not self.ThrowableIsReady then
			return
		end
		
		self.NextBehavior = coroutine.create(HumanBehaviors.Sentry)	-- replace "HumanBehaviors.Sentry" with the function name of your own sentry behavior
		self.NextCleanup = nil
		self.NextBehaviorName = "Sentry"
	end
	
	function self:CreatePatrolBehavior()
		self.NextBehavior = coroutine.create(HumanBehaviors.Patrol)
		self.NextCleanup = nil
		self.NextBehaviorName = "Patrol"
	end
	
	function self:CreateGoldDigBehavior()
		self.NextBehavior = coroutine.create(HumanBehaviors.GoldDig)
		self.NextCleanup = nil
		self.NextBehaviorName = "GoldDig"
	end

	function self:CreateBrainSearchBehavior()
		self.NextBehavior = coroutine.create(HumanBehaviors.BrainSearch)
		self.NextCleanup = nil
		self.NextBehaviorName = "BrainSearch"
	end
	
	function self:CreateGetToolBehavior()
		self.NextBehavior = coroutine.create(HumanBehaviors.ToolSearch)
		self.NextCleanup = nil
		self.NextBehaviorName = "ToolSearch"
	end
	
	function self:CreateGetWeaponBehavior()
		self.NextBehavior = coroutine.create(HumanBehaviors.WeaponSearch)
		self.NextCleanup = nil
		self.NextBehaviorName = "WeaponSearch"
	end
	
	function self:CreateGoToBehavior()
		self.NextGoTo = coroutine.create(HumanBehaviors.GoToWpt)
		self.NextGoToCleanup = function(self)
			self.lateralMoveState = Actor.LAT_STILL
			self.deviceState = AHuman.STILL
			self.crawling = false
			self.jump = false
			self.fire = false
			self:EquipFirearm(true)
			self:ClearMovePath()
		end
		self.NextGoToName = "GoToWpt"
	end
	
	function self:CreateAttackBehavior()
		if self:EquipFirearm(true) then
			self.NextBehavior = coroutine.create(HumanBehaviors.ShootTarget)
			self.NextBehaviorName = "ShootTarget"
		elseif self:EquipThrowable(true) then
			self.NextBehavior = coroutine.create(HumanBehaviors.ThrowTarget)
			self.NextBehaviorName = "ThrowTarget"
		elseif self:EquipDiggingTool(true) and SceneMan:ShortestDistance(self.Pos, self.Target.Pos, false).Magnitude < 150 then
			self.NextBehavior = coroutine.create(HumanBehaviors.AttackTarget)
			self.NextBehaviorName = "AttackTarget"
		else	-- unarmed or far away
			if self.PickUpTimer:IsPastSimMS(2500) then
				self.PickUpTimer:Reset()
				self.NextBehavior = coroutine.create(HumanBehaviors.WeaponSearch)
				self.NextBehaviorName = "WeaponSearch"
				self.NextCleanup = nil
				
				return
			else -- there are probably no weapons around here (in the vicinity of an area adjacent to a location)
				self.NextBehavior = coroutine.create(HumanBehaviors.AttackTarget)
				self.NextBehaviorName = "AttackTarget"
			end
		end
		
		self.NextCleanup = function(self)
			self.fire = false
			self.deviceState = AHuman.STILL
			self.proneState = AHuman.NOTPRONE
		end
	end
	
	-- force the use of a digger when attacking
	function self:CreateHtHBehavior()
		if self.Target and self:EquipDiggingTool(true) then
			self.NextBehavior = coroutine.create(HumanBehaviors.AttackTarget)
			self.NextBehaviorName = "AttackTarget"
			
			self.NextCleanup = function(self)
				self.fire = false
				self.Target = nil
				self.deviceState = AHuman.STILL
				self.proneState = AHuman.NOTPRONE
			end
		end
	end
	
	function self:CreateSuppressBehavior()
		if self:EquipFirearm(true) then
			self.NextBehavior = coroutine.create(HumanBehaviors.ShootArea)
			self.NextBehaviorName = "ShootArea"
		else
			if self.FirearmIsEmpty then
				self:ReloadFirearm()
			end
			
			return
		end
		
		self.NextCleanup = function(self)
			self.fire = false
			self.UnseenTarget = nil
			self.deviceState = AHuman.STILL
			self.proneState = AHuman.NOTPRONE
		end
	end
	
	function self:CreateMoveAroundBehavior()
		self.NextGoTo = coroutine.create(HumanBehaviors.MoveAroundActor)
		self.NextGoToCleanup = function(self)
			self.lateralMoveState = Actor.LAT_STILL
			self.jump = false
		end
		self.NextGoToName = "MoveAroundActor"
	end
	
	-- the controllers
	self.XposPID = RegulatorPID:New{p=0.04, i=0.00001, d=0.4, filter_leak=0.8, integral_max=100}
	self.YposPID = RegulatorPID:New{p=0.05, i=0.0001, d=0.2, filter_leak=0.8, integral_max=100}
	
	---------------- AI variables end ----------------
end

function UpdateAI(self)
	if self.isPlayerOwned then
		if self.PlayerInterferedTimer:IsPastSimTimeLimit() then
			self.Behavior = nil	-- remove the current behavior
			if self.BehaviorCleanup then
				self.BehaviorCleanup(self)	-- clean up after the current behavior
				self.BehaviorCleanup = nil
			end
			
			self.GoToBehavior = nil
			if self.GoToCleanup then
				self.GoToCleanup(self)
				self.GoToCleanup = nil
			end
			
			self.Target = nil
			self.UnseenTarget = nil
			self.OldTargetPos = nil
			self.PickupHD = nil
			self.BlockingActor = nil
			self.FollowingActor = nil
			
			self.fire = false
			self.jump = false
			self.crawling = false
			
			self.SentryFacing = self.HFlipped
			self.deviceState = AHuman.STILL
			self.lastAIMode = Actor.AIMODE_NONE
			self.teamBlockState = Actor.NOTBLOCKED
			
			if self.EquippedItem then
				self.PlayerPreferredHD = self.EquippedItem.PresetName
			else
				self.PlayerPreferredHD = nil
			end
		end
		
		self.PlayerInterferedTimer:Reset()
	end
	
	if self.Target and not MovableMan:IsActor(self.Target) then
		self.Target = nil
	end
	
	if self.UnseenTarget and not MovableMan:IsActor(self.UnseenTarget) then
		self.UnseenTarget = nil
	end
	
	-- switch to the next behavior, if avaliable
	if self.NextBehavior then
		if self.BehaviorCleanup then
			self.BehaviorCleanup(self)
		end
		
		self.Behavior = self.NextBehavior
		self.BehaviorCleanup = self.NextCleanup
		self.BehaviorName = self.NextBehaviorName
		self.NextBehavior = nil
	end
	
	-- switch to the next GoTo behavior, if avaliable
	if self.NextGoTo then
		if self.GoToCleanup then
			self.GoToCleanup(self)
		end
		
		self.GoToBehavior = self.NextGoTo
		self.GoToCleanup = self.NextGoToCleanup
		self.NextGoTo = nil
	end
	
	-- check if the AI mode has changed or if we need a new behavior
	if self.AIMode ~= self.lastAIMode or (not self.Behavior and not self.GoToBehavior) then
		self.Behavior = nil
		if self.BehaviorCleanup then
			self.BehaviorCleanup(self)	-- stop the current behavior
			self.BehaviorCleanup = nil
		end
		
		self.GoToBehavior = nil
		if self.GoToCleanup then
			self.GoToCleanup(self)
			self.GoToCleanup = nil
		end
		
		-- select a new behavior based on AI mode
		if self.AIMode == Actor.AIMODE_GOTO then
			self:CreateGoToBehavior()
		elseif self.AIMode == Actor.AIMODE_BRAINHUNT then
			self:CreateBrainSearchBehavior()
		elseif self.AIMode == Actor.AIMODE_GOLDDIG then
			self:CreateGoldDigBehavior()
		elseif self.AIMode == Actor.AIMODE_PATROL then
			self:CreatePatrolBehavior()
		else
			if self.AIMode ~= self.lastAIMode and self.AIMode == Actor.AIMODE_SENTRY then
				self.SentryFacing = self.HFlipped	-- store the direction in which we should be looking
				self.SentryPos = Vector(self.Pos.X, self.Pos.Y)	-- store the pos on which we should be standing
			end
			
			self:CreateSentryBehavior()
		end
		
		self.lastAIMode = self.AIMode
	end
	
	-- check if the legs reach the ground
	if self.AirTimer:IsPastSimMS(250) then
		self.AirTimer:Reset()
		
		if -1 < SceneMan:CastObstacleRay(self.Pos, Vector(0, self.Height/4), Vector(), Vector(), self.ID, self.IgnoresWhichTeam, rte.grassID, 3) then
			self.flying = false
		else
			self.flying = true
		end
		
		self:EquipShieldInBGArm()	-- try to equip a shield
	end
	
	-- look for targets
	local FoundMO, HitPoint = HumanBehaviors.LookForTargets(self)
	if FoundMO then
		if self.Target and MovableMan:IsActor(self.Target) and FoundMO.ID == self.Target.ID then	-- found the same target
			self.TargetOffset = SceneMan:ShortestDistance(self.Target.Pos, HitPoint, false)
			self.TargetLostTimer:Reset()
			self.ReloadTimer:Reset()
		elseif MovableMan:IsActor(FoundMO) then
			if FoundMO.Team == self.Team then	-- found an ally
				if self.Target then
					if SceneMan:ShortestDistance(self.Pos, FoundMO.Pos, false).Magnitude < SceneMan:ShortestDistance(self.Pos, self.Target.Pos, false).Magnitude then
						self.Target = nil	-- stop shooting
					end
				elseif FoundMO.ClassName ~= "ADoor" and
					SceneMan:ShortestDistance(self.Pos, FoundMO.Pos, false).Magnitude < self.Diameter + FoundMO.Diameter
				then
					self.BlockingActor = ToActor(FoundMO)	-- this actor is blocking our path
				end
			else
				if FoundMO.ClassName == "AHuman" then
					FoundMO = ToAHuman(FoundMO)
				elseif FoundMO.ClassName == "ACrab" then
					FoundMO = ToACrab(FoundMO)
				elseif FoundMO.ClassName == "ACRocket" then
					FoundMO = ToACRocket(FoundMO)
				elseif FoundMO.ClassName == "ACDropShip" then
					FoundMO = ToACDropShip(FoundMO)
				elseif FoundMO.ClassName == "ADoor" then
					FoundMO = ToADoor(FoundMO)
				elseif FoundMO.ClassName == "Actor" then
					FoundMO = ToActor(FoundMO)
				else
					FoundMO = nil
				end
				
				if FoundMO then
					self.ReloadTimer:Reset()
					self.TargetLostTimer:Reset()
					
					if self.Target then
						-- check if this MO sould be targeted instead
						if HumanBehaviors.CalculateThreatLevel(self, FoundMO) > HumanBehaviors.CalculateThreatLevel(self, self.Target) + 0.2 then
							self.OldTargetPos = self.Target.Pos
							self.Target = FoundMO
							self.TargetOffset = SceneMan:ShortestDistance(self.Target.Pos, HitPoint, false)	-- this is the distance vector from the target center to the point we hit with our ray
							self:CreateAttackBehavior()
						end
					else
						self.OldTargetPos = nil
						self.Target = FoundMO
						self.TargetOffset = SceneMan:ShortestDistance(self.Target.Pos, HitPoint, false)	-- this is the distance vector from the target center to the point we hit with our ray
						self:CreateAttackBehavior()
					end
				end
			end
		end
	else	-- no target found this frame
		if self.Target and self.TargetLostTimer:IsPastSimTimeLimit() then
			self.OldTargetPos = self.Target.Pos
			self.Target = nil	-- the target has been out of sight for too long, ignore it
		end
		
		if self.ReloadTimer:IsPastSimMS(8000) then	-- check if we need to reload
			self.ReloadTimer:Reset()
			if ToAHuman(self).FirearmNeedsReload then
				self:ReloadFirearm()
			end
		end
	end
	
	-- run the move behavior and delete it if it returns true
	if self.GoToBehavior then
		local msg, done = coroutine.resume(self.GoToBehavior, self)
		if not msg then
			ConsoleMan:PrintString(self.PresetName .. " " .. self.NextGoToName .. " error:\n" .. done)	-- print the error message
			done = true
		end
		
		if done then
			self.GoToBehavior = nil
			if self.GoToCleanup then
				self.GoToCleanup(self)
				self.GoToCleanup = nil
			end
		end
	elseif self.flying then	-- avoid falling damage
		if (not self.jump and self.Vel.Y > 9) or
			(self.jump and self.Vel.Y > 6)
		then
			self.jump = true
			
			-- try falling straight down
			if not self.Target then
				if self.Vel.X > 2 then
					self.lateralMoveState = Actor.LAT_LEFT
				elseif self.Vel.X < -2 then
					self.lateralMoveState = Actor.LAT_RIGHT
				else
					self.lateralMoveState = Actor.LAT_STILL
				end
			end
		else
			self.jump = false
			self.lateralMoveState = Actor.LAT_STILL
		end
	else
		self.jump = false
		self.lateralMoveState = Actor.LAT_STILL
		
		if not self.Target and not self.PickupHD and self.PickUpTimer:IsPastSimMS(4000) then	-- check if we need to look for weapons to pick up
			self.PickUpTimer:Reset()
			
			if not self:EquipFirearm(false) then
				self:CreateGetWeaponBehavior()
			elseif self.AIMode ~= Actor.AIMODE_SENTRY and not self:EquipDiggingTool(false) then
				self:CreateGetToolBehavior()
			end
		end
	end
	
	-- run the selected behavior and delete it if it returns true
	if self.Behavior then
		local msg, done = coroutine.resume(self.Behavior, self)
		if not msg then
			ConsoleMan:PrintString(self.PresetName .. " behavior " .. self.BehaviorName .. " error:\n" .. done)	-- print the error message
			done = true
		end
		
		if done then
			self.Behavior = nil
			if self.BehaviorCleanup then
				self.BehaviorCleanup(self)
				self.BehaviorCleanup = nil
			end
			
			if not self.NextBehavior and not self.PickupHD and self.PickUpTimer:IsPastSimMS(2000) then
				self.PickUpTimer:Reset()
				
				if not self:EquipFirearm(false) then
					self:CreateGetWeaponBehavior()
				elseif self.AIMode ~= Actor.AIMODE_SENTRY and not self:EquipDiggingTool(false) then
					self:CreateGetToolBehavior()
				end
			end
		end
	end
	
	if self.PickupHD then	-- there is a HeldDevice we want to pick up
		if not MovableMan:IsDevice(self.PickupHD) or self.PickupHD.ID ~= self.PickupHD.RootID then
			self.PickupHD = nil	-- the HeldDevice has been destroyed or picked up
			self:ClearAIWaypoints()
			
			if self.PrevAIWaypoint or self.FollowingActor then				
				if self.FollowingActor and MovableMan:IsActor(self.FollowingActor) then	-- what if the old destination was a moving actor?
					self:AddAIMOWaypoint(self.FollowingActor)
					self:CreateGoToBehavior()	-- continue towards our old destination
				else
					self.FollowingActor = nil
					if self.PrevAIWaypoint then
						self:AddAISceneWaypoint(self.PrevAIWaypoint)
						self:CreateGoToBehavior()	-- continue towards our old destination
					end
				end
			end
		else
			if SceneMan:ShortestDistance(self.Pos, self.PickupHD.Pos, false).Magnitude < self.Height then
				self.Ctrl:SetState(Controller.WEAPON_PICKUP, true)
			end
		end
	end
	
	-- react to relevant AlarmEvents
	if not self.Target and not self.UnseenTarget then
		if self.AlarmTimer:IsPastSimTimeLimit() and HumanBehaviors.ProcessAlarmEvent(self) then
			self.AlarmTimer:Reset()
		elseif self.AlarmPos then	-- if alarmed, look at the alarming point until AlarmTimer has expired
			self.jump = false
			self.deviceState = AHuman.AIMING
			self.lateralMoveState = Actor.LAT_STILL
			
			-- stop any behavior that can interfere with the aiming
			self.Behavior = nil
			if self.BehaviorCleanup then
				self.BehaviorCleanup(self)
				self.BehaviorCleanup = nil
			end
			
			-- look in the direction of alarm
			local aim = self:GetAimAngle(true)
			local angDiff = aim - SceneMan:ShortestDistance(self.EyePos, self.AlarmPos, false).AbsRadAngle
			if angDiff > math.pi then
				angDiff = angDiff - math.pi*2
			elseif angDiff < -math.pi then
				angDiff = angDiff + math.pi*2
			end
			
			self.Ctrl.AnalogAim = Vector(1,0):RadRotate(aim-angDiff*0.2)	-- smooth
		end
	end
	
	if self.teamBlockState == Actor.IGNORINGBLOCK then
		if self.BlockedTimer:IsPastSimMS(10000) then
			self.teamBlockState = Actor.NOTBLOCKED
		end
	elseif self.teamBlockState == Actor.BLOCKED then	-- we are blocked by a teammate, stop
		self.lateralMoveState = Actor.LAT_STILL
		self.jump = false
		if self.BlockedTimer:IsPastSimMS(20000) then
			self.BlockedTimer:Reset()
			self.teamBlockState = Actor.IGNORINGBLOCK
		end
	else
		self.BlockedTimer:Reset()
	end
	
	-- controller states
	if self.fire then
		self.Ctrl:SetState(Controller.WEAPON_FIRE, true)
	end
		
	if self.deviceState == AHuman.AIMING then
		self.Ctrl:SetState(Controller.AIM_SHARP, true)
	end
	
	if self.jump and self.JetTimeLeft > TimerMan.DeltaTimeMS then
		if self.jumpState == AHuman.PREJUMP then
			self.jumpState = AHuman.UPJUMP
		elseif self.jumpState ~= AHuman.UPJUMP then	-- the jetpack is off
			if self.JetTimeLeft >= self.minBurstTime then	-- don't start the jetpack unless there is enough fuel to fire a burst that is long enough to be useful
				self.jumpState = AHuman.PREJUMP
			else
				self.jumpState = AHuman.NOTJUMPING
			end
		end
	else
		self.jumpState = AHuman.NOTJUMPING
	end
	
	if self.jumpState == AHuman.PREJUMP then
		self.Ctrl:SetState(Controller.BODY_JUMPSTART, true)	-- trigger burst
	elseif self.jumpState == AHuman.UPJUMP then
		self.Ctrl:SetState(Controller.BODY_JUMP, true)	-- trigger normal jetpack emission
	end
	
	if self.proneState == AHuman.GOPRONE then
		self.proneState = AHuman.PRONE
	elseif self.proneState == AHuman.PRONE or self.crawling then
		self.Ctrl:SetState(Controller.BODY_CROUCH, true)
	end
	
	if self.lateralMoveState == Actor.LAT_LEFT then
		self.Ctrl:SetState(Controller.MOVE_LEFT, true)
	elseif self.lateralMoveState == Actor.LAT_RIGHT then
		self.Ctrl:SetState(Controller.MOVE_RIGHT, true)
	end
end
