Strict

Import "globals.bmx"				'not ALL globals, but all the ones that matter (or are commonly used)
Import "entity_ship.bmx"			'defines ship entities
'Import "shipWeapons.bmx"			'defines guns that components can add to ships
Import "timers.bmx"					'defines egg timers
Import "vararray.bmx"				'an array of variables with names; useful when we want to track only a couple unique variables
Import "controls.bmx"				'sets the name of all keys, sets the controls to a default setup
Import "cursor.bmx"					'handles the mouse/cursor
Import "stageControl.bmx"			'The DM, sort of. Sets up & directs levels, win conditions	
Import "maths.bmx"					'math magic
Import "luaScripts.bmx"				'allows the loading of lua scripts


Global AI:AIfunctions = New AIfunctions

'called every loop with each ship
Function AIupdate(s:Ship)

	If s.controller <> Null
		Local target:Gertrude = s.controller.target
		Local vars:VarArray = s.controller.vars			'I don't know what I didn't do this one a long time ago...
		Local timerList:TList = s.controller.timerList
		
		s.controller.gox = 0
		s.controller.goy = 0
		
		'If Left(s.behavior, 5) <> "inert" Print "updating AI " + s.behavior
		
		Select Lower(s.behavior)
		Case "player"
			Include "AISource/player.bmx"
		
		Case "inert"
			Include "AISource/inert.bmx"
		Case "inert2"
			Include "AISource/inert2.bmx"
		Case "inert3"
			Include "AISource/inert3.bmx"
		Case "warp"
			Include "AISource/warp.bmx"
		Case "switch"
			Include "AISource/switch.bmx"
		Case "stationary"
			Include "AISource/stationary.bmx"
		Case "reflector"
			Include "AISource/reflector.bmx"
		Case "blackhole"
			vars.set("forcedir", -1)'in
			Include "AISource/blackhole.bmx"
		Case "blackpeak"
			vars.set("forcedir", 1)'out
			Include "AISource/blackhole.bmx"
			
		Case "spinner"
			Include "AISource/spinner.bmx"
		Case "missile"
			Include "AISource/missile.bmx"
		Case "seeker"
			vars.set("seeker", 1)
			Include "AISource/missile.bmx"
		Case "torpedo"
			Include "AISource/torpedo.bmx"
		Case "goop"
			vars.set("caution", 0)
			Include "AISource/torpedo.bmx"
		Case "mine"
			Include "AISource/mine.bmx"
		Case "bomb"
			Include "AISource/bomb.bmx"
		'Case "mineplaced"
		'	vars.set("shielded", 0)
		'	Include "AISource/mine.bmx"
		Case "ordinance"
			Include "AISource/ordinance.bmx"
		
		Case "circleandshoot"
			Include "AISource/circleandshoot.bmx"
		Case "capitolship"
			Include "AISource/capitolship.bmx"
		Case "turret"
			Include "AISource/turret.bmx"
		Case "strafeturret"
			Include "AISource/strafeturret.bmx"
		Case "strafeturret2"'longer shoot, strafe times
			timerInit(timerList, "firing", 4000)
			timerInit(timerList, "strafing", 2500)
			vars.init("aimspeed", 35)
			Include "AISource/strafeturret.bmx"
		Case "shieldturret"
			Include "AISource/shieldturret.bmx"
		Case "blinkturret"
			Include "AISource/blinkturret.bmx"
		Case "bubbledrone"
			Include "AISource/bubbledrone.bmx"
		Case "pulsardrone"
			Include "AISource/pulsardrone.bmx"
		Case "node"
			Include "AISource/node.bmx"
		Case "node2"
			vars.set("move", 1)
			Include "AISource/node.bmx"
		Case "minelayer"
			vars.set("circledist", 800)
			Include "AISource/circleandshoot.bmx"
		Case "pillbug"
			Include "AISource/pillbug.bmx"
		Case "flyby"
			Include "AISource/flyby.bmx"
		Case "claws"
			Include "AISource/claws.bmx"
		Case "claws_nice"
			vars.set("escortplayer", 1)
			Include "AISource/claws.bmx"
		Case "anemone"
			Include "AISource/anemone.bmx"
		Case "spiker"
			Include "AISource/spiker.bmx"
		Case "barnacle"
			Include "AISource/barnacle.bmx"
		Case "cryopod"
			Include "AISource/cryopod.bmx"
		Case "beacon"
			Include "AISource/beacon.bmx"
		Case "goto"
			Include "AISource/goto.bmx"
		Case "ufo"
			Include "AISource/ufo.bmx"
		
		Case "station"
			Include "AISource/station.bmx"
		Case "survivor"
			Include "AISource/survivor.bmx"
		Case "survivor_jumpstarted"
			vars.set("jumpstarted", 1)
			Include "AISource/survivor.bmx"
		Case "mob_bulwark"
			vars.set("boss", 0)
			Include "AISource/boss_bulwark.bmx"
		Case "boss_bulwark"
			vars.set("boss", 1)
			Include "AISource/boss_bulwark.bmx"
		Case "boss_wraith"
			Include "AISource/boss_wraith.bmx"
		Case "boss_reef"
			Include "AISource/boss_reef.bmx"
		Case "boss_reef_oyster"
			Include "AISource/boss_reef_oyster.bmx"
		Case "boss_dodongo"
			Include "AISource/boss_dodongo.bmx"
		Case "boss_dodongo_bubblenode"
			Include "AISource/boss_dodongo_bubblenode.bmx"
		Case "boss_axel"
			Include "AISource/boss_axel.bmx"
		Case "boss_aspoke"
			Include "AISource/boss_aspoke.bmx"
		Case "boss_archangel"
			Include "AISource/boss_archangel.bmx"
		Case "boss_angelspoke"
			Include "AISource/boss_angelspoke.bmx"
		Case "boss_angelturret"
			Include "AISource/boss_angelturret.bmx"
		Case "boss_angelshield"
			Include "AISource/boss_angelshield.bmx"
		Case "boss_angelpod"
			Include "AISource/inert3.bmx"'sneaky
			
		Case "cuttlefish"
			Include "AISource/cuttlefish.bmx"
		Case "cuttlefish_small"
			Include "AISource/cuttlefish.bmx"'sneaky
			
		Default
			'check to see if there's a lua file of this name
			If FileType(AI_PATH + s.behavior + ".lua") = 1
				'grab the behavior script for this ship
				If s.controller.scriptslot = -1
					s.controller.scriptslot = aiScript.produce(s.behavior, AI_PATH + s.behavior + ".lua")
					LuaRegisterObject AI, "AI"'so we're not constantly doing it each time
				EndIf
					
				'control the ship
				If s.controller.scriptslot > -1
					LuaRegisterObject s, "s"
					aiScript.call(s.controller.scriptslot, "update")
				EndIf
			Else
				Print "COULD NOT FIND BEHAVIOR " + s.behavior
			EndIf
		EndSelect
	EndIf
EndFunction



Type AIfunctions

	Method GFP(msg$)
		Print msg
	EndMethod

	'sets dir_x & y to point away from all the ships in the proximity list
	Method setProximityAvoid(s:Ship, avoidradius = TSIZE)', ignoreList:TList = Null)
		'If ignoreList = Null Then ignoreList = New TList
		Local ignoreList:TList = s.controller.ignoreProxList
		Local centercoord = MAXTILE / 2
	
		'look at each corner of our avoidance radius, look at all the cboxes we intersect
		For Local y_box = -1 To 1
			Local cby = Int(s.y / TSIZE) + centercoord + y_box
			If cby < stage.tileminy Or cby > stage.tilemaxy Then Continue
	
			For Local x_box = -1 To 1
				Local cbx = Int(s.x / TSIZE) + centercoord + x_box
				If cbx < stage.tileminx Or cbx > stage.tilemaxx Then Continue
				
				'Print "avoiding box :" + cbx+","+cby+ "        > " + CountList(stage.cboxList[cbx,cby])
				'add all the ships in this cbox to the proxList
				For Local o:Ship = EachIn stage.cboxList[cbx,cby]
					If o <> s And (ListIsEmpty(ignoreList) Or (Not ListContains(ignoreList, o.placeholder) And Not ListContains(ignoreList, o)))
						'Print "checking ...-> "+o.name
						If Not o.ignoreCollisions													'if we're not ignoring collisions
						If Not ListContains(s.ignoreList, o) And Not ListContains(o.ignoreList, s)	'if we're not ignoring eachother
						If Not o.base.ord Or fTable[s.faction, o.faction] <> 1						'no friendly collisions; don't have to worry about their ordenance
						If Not o.placeholder.cloaked												'if they are not cloaked! haha they'll accidentally run you over	
							Local dx = s.x-o.x
							Local dy = s.y-o.y
							Local odist = approxDist(dx,dy)
							
							'weight the desire to go away from something by how close we are to it
							If odist < avoidradius
								Local tarWt# = ((avoidradius - odist)/15)^2
								Local angle# = ATan2(dy,dx)
								
								'Print "  AVOIDING " + o.name + "  wt:"+tarWt
								
								s.controller.gox:+ Cos(angle)*tarWt
								s.controller.goy:+ Sin(angle)*tarWt
							EndIf
						EndIf
						EndIf
						EndIf
						EndIf
					EndIf
				Next
			Next
		Next
		
		'also, avoid edges of the map
		If Abs(s.x) > (stage.width - 300) Then s.controller.gox:- (Abs(s.x) - (stage.width - 300))*Sgn(s.x)*.5
		If Abs(s.y) > (stage.height - 300) Then s.controller.goy:- (Abs(s.y) - (stage.height - 300))*Sgn(s.y)*.5
	
	EndMethod
	
	
	Method holdingPattern(s:Ship)
		'how far To circle the goal (If Not already set)
		s.controller.vars.init("holdrad", Rand(100, 200))
		s.controller.vars.init("holddir", binsgn(Rand(0,1)))
		
		Local goalx = s.controller.goalx
		Local goaly = s.controller.goaly
	
		'find distance And angle To the goal (center of the circle)
		Local dist = Floor(approxDist(goalx-s.x, goaly-s.y))
		Local theta = ATan2((goaly-s.y), (goalx-s.x))
		
		'get If we're within or outside the holding circle
		Local ratio# = (dist / s.controller.vars.get("holdrad"))
		If ratio > 2 Then ratio = 2
	
		'get the angle To go To maintain a constant distance
		theta = 180 + theta + (90*ratio*s.controller.vars.get("holddir"))
	
		'weight desire To go towards that direction
		Local dx = Cos(theta)*80
		Local dy = Sin(theta)*80
		
		'store the New direction
		s.controller.gox = s.controller.gox + dx
		s.controller.goy = s.controller.goy + dy	
	EndMethod
	
	'turns the ship to face either the go direction or the goal direction
	Method face(s:Ship, dir$, rate# = 0)
		Select dir
		Case "go"
			s.turn(ATan2(s.controller.goy, s.controller.gox), 1, rate)
		Case "goal"
			s.turn(ATan2(s.controller.goaly - s.y, s.controller.goalx - s.x), 1, rate)
			'Print "facing goal " + ATan2(s.controller.goaly, s.controller.goalx)
		Case "target"
			If s.controller.target <> Null
				s.turn(ATan2(s.controller.target.y - s.y, s.controller.target.x - s.x), 1, rate)
			EndIf
		EndSelect	
	EndMethod
	
	'sets the goal to something
	Method setGoalTo(s:Ship, dir$)
		Select dir
		Case "target"
			If s.controller.target <> Null And Not s.controller.target.cloaked Then
				s.controller.goalx = s.controller.target.x
				s.controller.goaly = s.controller.target.y
			EndIf
		Case "center"
			s.controller.goalx = TSIZE/2
			s.controller.goaly = TSIZE/2
		EndSelect
	EndMethod
	
	'makes the ship go in the go direction
	Method go(s:Ship, throttle# = 1)
		s.add_thrust(ATan2(s.controller.goy, s.controller.gox), throttle, 1)
	EndMethod
	
	'gets the dist from the ship to a point
	Method getDist#(dx,dy)
		Return approxDist(dx,dy)
	EndMethod
	
	'searches for a target within detectdist every interval ms
	'diplomacy : [ -1 = enemies | 0 = neutral | 1 = allies ]
	'returns TRUE if we JUST found one
	Method getTarget(s:Ship, detectdist, interval = 1500, diplomacy = -1, _skipCloaked = True)
		interval:+ Rand(-200,200)
		If interval < 10 Then interval = 10
	
		'count down the check timer // (Create it If it doesn't exist)
		If s.controller.hasTimerTicked("targetCheck", interval) = 1 Or s.controller.vars.get("firstgettarget") = 0
			'target the closest target
			s.target_closest(detectdist, diplomacy, s.x, s.y, _skipCloaked)
			'the first time we call this, do it instantly
			s.controller.vars.set("firstgettarget", 1)
			
			If s.controller.target = Null Then Return False Else Return True
		EndIf
		
		Return False
	EndMethod
	
	'fires the ship's weapons, no questions asked
	Method fireWeapons(s:Ship, group, tarx = 0, tary = 0)
		Return fireShipWeapons(s, group, tarx, tary)
	EndMethod
	
	'fires a ship's weapons according to the type of weapon
	Method fireAIWeapons(s:Ship, group, tarx = 0, tary = 0)
		'checks the state of the guns to see if we should be firing right now
		For Local g:Gun = EachIn s.gunGroup[group]				
			'skip some fire attempts by AI
			If g.skipAIFire Then Return False
		Next
		'if we made it this far, fire ship weapons
		Return fireShipWeapons(s, group, tarx, tary)
	EndMethod
	
	'fires all weapons and abilities
	Method fireEverything(s:Ship)
		'like inert only fires all weapons and activates all abilities
		fireAIWeapons(s, 0)
		fireAIWeapons(s, 1)
		
		'overwrite default ability values
		If s.controller.vars.get("setabilities") = 0
			s.abilitySetTimer("", "cooldown", 50)
			s.abilitySetTimer("", "duration", 20000)
			s.abilitySetVar("", "cost", 0)
			s.abilitySetVar("", "skipgfx", 1)
			
			s.controller.vars.set("setabilities", 1)
		EndIf
		
		s.activateAbility()
	EndMethod
	
	
EndType
