Strict

Import "globals.bmx"			'not ALL globals, but all the ones that matter (or are commonly used)
Import "camera.bmx"				'defines the camera the game is viewed through
Import "vararray.bmx"			'an array of variables with names; useful when we want to track only a couple unique variables
Import "entity_ship.bmx"		'defines ship entities
Import "entity_item.bmx"		'defines item entities (points, primarily)
Import "shipCreate.bmx"			'allows the creation of ingame ships
Import "collisions.bmx"			'handles ship, shot, and item collisions
Import "configControl.bmx"		'allows the loading and saving of ship configurations
Import "messageText.bmx"		'message boxes (a la starfox) and notification screens
Import "terrain.bmx"			'defines terrain scripts
Import "waypoints.bmx"			'defines waypoints
Import "profiles.bmx"			'defines the player profile object & the creation/loading thereof

Global stage:Level					'the current stage

'a single map, sceneario
Type Level
	Field name$								'the name of THIS stage/map
	Field missionName$						'the name of the parent mission

	Field stageSource$
	Field stageClass:TLuaClass
	Field stageScript:TLuaObject

	Field width, height						'the width and height PIXEL RADIUSES of the playing field (0,0 is center)
	Field tilewid, tilehet					'the width and height TILE RADIUSES of the playing field (0,0 is top-left)
	Field tileminx, tilemaxx				'the min and max x coordinates of the map
	Field tileminy, tilemaxy				'the min and max y coordinates of the map
	
	Field backdrop_name$[2]					'the filename of the background image (sent over the network, clients actually load it)
	Field map:Terrain[100,100]				'the terrain object controlling each "tile"
	Field terrainShipCheckList:TList = New TList'a list of terrain that needs to know when ships are inside it
	Field cboxList:TList[100,100]			'array of collision box lists
	
	Field waypoints:Waypoint[MAXWAYPOINT]	'an array of waypoints
	Field waypointStart						'the index of the waypoint to start out players
	Field beacons$							'a string with all the visible beacons, to send to clients
	
	Field multiplier#						'every time a tracked thing dies, multiplier timer resets
	Field multiplyTime = 1500				'how much time you have to kill another thing
	Field multiplierMax# = 2				'the max above +1 that the multiplier can go
	
	Field trackList:TList = New TList		'a list of ships to track for whatever reason
	Field trackBeacons = 0					'should we put beacons on the tracked ships? [1 = arrows | 3 = BIG beacons]
	Field trackFaction = -1					'track all created ships of this faction
	
	Field factionCensus[8]					'current count of all NON-ORDINANCE ships in each faction
	'Field tookCensus = False				'have we taken a census yet this loop?
	Field warpedOutList:TList = New TList	'a list of ships that were warped out of the stage
		
	Field spawnCountMod# = 1.0				'multiplies all ships spawned from spawn point terrain
	
	Field bossFriendly = -1					'-1 is red bar, 0 is blue bar, 1 is green bar
	Field bossNameList:TList = New TList	'list of ship names to add to the boss list
	Field bossList:TList = New TList		'a list of ships whose health to add up and keep track of on a boss healthbar
	Field bossHP#, bossHPmax#				'total boss health and amount left; used to calculate health bar
	
	Field messageGroup:TList[MAXMESSAGEGROUPS]'groups of messages To be played at specfic times.
	
	Field destination = -1					'if a specific destination has been set on stage completion
	Field finished							'whether this stage has been completed
	Field failure = False					'whether all players have died
	
	Field fadeCurrent# = 1					'the current opacity of the black fade rectangle
	Field fadeSpeed# = .05					'the correct %/ms to change the fade
	Field fadeTarget# = 0					'the target opacity of the black fade rectangle
	
	Field timerList:TList = New TList		'where the timers that the script, update functions go
	Field vars:VarArray = New VarArray		'an array of variable spaces
	Field playerShipList:TList = New TList	'a list of all player ships
	
	Field deflectedBeamList:TList = New TList'a list containing beam-tracking objects that track the corner of a beam deflection
	
	Field minimalHUD = False				'skip most of the HUD for players
	Field newClient							'the player # of the most recently-joined client

	'loads a new lua script and runs it
	Method callLuaFile(scriptName$, functionName$)
		Local scriptPath$ = MISSION_PATH + missionName + "/" + scriptName + ".lua"
		If FileType(scriptPath) > 0
			Local scriptSource$ = getSource(scriptPath)
			Local scriptClass:TLuaClass = TLuaClass.Create(scriptSource)
			Local script:TLuaObject = TLuaObject.Create(scriptClass, Null)

			'call the script
			script.Invoke(functionName, Null)
		EndIf
	EndMethod
	
	Method setBackdropName(layer, filename$)
		backdrop_name[layer] = filename
	EndMethod

	'looks in the current game folder for a stage of this name, and runs that script
	Method init(_missionName$,_name$)
		Print "initing stage"
		name = _name
		missionName = _missionName
		
		setBackdropName(0, "space_tile1.png")'back-background
		setBackdropName(1, "space_tile2.png")'paralax background
		
		For Local i = 0 To MAXMESSAGEGROUPS-1
			messageGroup[i] = New TList
		Next
		
		timerSet(timerList, "multiply", multiplyTime)
		
		'--- RELOADED EACH TIME --- because galaxy_battle.lua script is going to be dynamically generated
		stageSource$ = getSource(MISSION_PATH + missionName + "/" + name + ".lua")
		stageClass:TLuaClass = TLuaClass.Create(stageSource)
		stageScript:TLuaObject = TLuaObject.Create(stageClass, Null)
		
		'allow the lua object to use this stage                  'can I just... do this? and have it work everywhere? IS IT POSSIBLE???
		LuaRegisterObject Self, "stage"
		'call the script
		stageScript.Invoke("init", Null)
		
		'initialize the collision boxes
		setup_cboxes()
	EndMethod

	Method update()
		Local beaconstr$'compile a string containing all beacon locations
		
		'lose dead targets
		Local checkList:TList = trackList.Copy()
		For Local s:Ship = EachIn checkList
			If s.dead
				ListRemove(trackList, s)
				'if the player killed it, bump up the multiplier
				If trackFaction = s.faction And s.placeholder.nemesis_behavior = "player"
					'Local oldmult# = multiplier
					multiplier = Min(multiplier + .5, multiplierMax)'increase the multiplier
					timerSet(timerList, "multiply")		'reset the timer
					
					'give a message when get multipliers
					If multiplier >= 1 Then  new_text("x" + Int(Floor(multiplier+1)) + "!", s.x, s.y-40)
				EndIf
			EndIf
		Next

		
		'put beacons on the correct things // track the bosses
		For Local s:Ship = EachIn trackList
			Local arrowtype = 1
			If checkDiplomacy(1, s.faction) = 1 Then arrowtype = 2
			If trackBeacons = 3 Then arrowtype = 3
			If trackBeacons > 0 Then beaconstr:+ "[wp]"+arrowtype+"[x]"+s.x+"[y]"+s.y
			
			'Print "tracking ship " + s.name + " with arrowtype " + arrowtype
			
			If isBossShip(s)
				If Not ListContains(bossList, s) Then bossList.addLast(s)
				If Not ListContains(trackList, s) Then trackList.addLast(s)'also all track all bosses <==== UM WHAT look at this carefully
			EndIf
		Next
		
		'countdown the multiplier timer
		If multiplier > 0.05
			'if it finished counting down, reset to 0
			'If timerTick(timerList, "multiply") Then multiplier = 0'Max(multiplier - 1, 0)
			
			'if it finished counting down, start reducing the multiplier
			
			If Not timerDinged(timerList, "multiply")
				timerTick(timerList, "multiply")
			Else
				multiplier:- (multiplier * .004 * timeMod)
			EndIf
			
		Else
			multiplier = 0
		EndIf
		dropMultiplier = multiplier'update the global variable
		
		Local old_bossHPMax = bossHPMax
		bossHPMax = 0
		bossHP = 0
		For Local s:Ship = EachIn bossList
			'Print s.name + " is a boss!"
			'If s.dead Or Not ListContains(bossNameList, Lower(s.name)) Then ListRemove(bossList, s)
			If s.dead Then ListRemove(bossList, s)
			bossHP:+ s.armour
			bossHPMax:+ s.stat.armour
		Next
		
		'Print "bossnamelist:"
		'For Local bs$ = EachIn stage.bossNameList
		'	Print "   " + bs
		'Next
		
		'boss HP max stores the high-water mark for boss HP
		If old_bossHPMax > bossHPMax Then bossHPMax = old_bossHPMax
	
		'clear faction census
		For Local i = 0 To 7
			factionCensus[i] = 0
		Next
		
		For Local s:Ship = EachIn shipList
			If Not s.base.ord
				factionCensus[s.faction]:+ 1'count non-ordinance ships
				'Print " plos onw " + s.faction + "    " + factionCensus[s.faction]
			EndIf
		Next

		'allow the lua object to use this stage
		LuaRegisterObject Self, "stage"
		'call the script
		stageScript.Invoke("update", Null)
		
		
		'update the terrain
		For Local x = tileminx To tilemaxx
			For Local y = tileminy To tilemaxy
				Local t:Terrain = map[x,y]
				If t <> Null
					'check tgat all the ships that the terrain is tracking are still alive
					t.trackCount = 0
					'lose dead targets
					Local checkList:TList = t.trackList.Copy()
					For Local s:Ship = EachIn checkList
						If Not s.dead Then t.trackCount:+ 1 Else ListRemove(t.trackList, s)
					Next
									
					If t.callUpdate' And 1=2
						'allow the lua object to use the terrain
						LuaRegisterObject t, "terrain"
						'call the script
						terrainScript.call(t.scriptslot, "updateHost")
					EndIf
					
					'nobody here but us chickens
					'If t.playerNear = 2 Then t.playerNear = 1 Else t.playerNear = 0
				EndIf
			Next
		Next
		
		'move the screen fade to the target at the correct speed
		If fadeCurrent <> fadeTarget
			If Abs(fadeCurrent-fadeTarget) <= fadeSpeed Then fadeCurrent = fadeTarget Else fadeCurrent:+ Abs(fadeSpeed)*Sgn(fadeTarget-fadeCurrent)
			
			'tell all the players about the new fade
			For Local o:TGNetObject = EachIn playerObjectList
				If GetGNetInt(o, NET_ID) = ID_PLAYER Then SetGNetFloat(o, NET_PLAYER_SCREENFADE, fadeCurrent)
			Next
		EndIf
		
		'update the waypoints
		For Local i = 0 To (MAXWAYPOINT-1)
			Local wp:Waypoint = waypoints[i]
			If wp <> Null
				'do we care about player proximity?
				If wp.playerProx > 0
					'default off
					wp.on = 0
					'look at all players
					For Local pship:Ship = EachIn playerShipList
						'are they within the indicated range?
						Local range = ((wp.playerProx-1)*TSIZE) + (TSIZE/2)
						If Abs(pship.x - wp.x) <= range And Abs(pship.y - wp.y) <= range
							wp.on = 1	'turn on
						EndIf
					Next
				EndIf
				
				'tell the clients about visible waypoints
				If wp.beacon > 0 Then beaconstr:+ "[wp]"+wp.beacon+"[x]"+wp.x+"[y]"+wp.y
			EndIf
		Next
		
		'tell the clients about visible waypoints
		If beaconstr <> beacons'(if it has changed)
			'Print "================ SETTING BEACONSTR ============="
			For Local o:TGNetObject = EachIn playerObjectList
				If GetGNetInt(o, NET_ID) = ID_PLAYER Then SetGNetString(o, NET_PLAYER_BEACONLIST, beaconstr)
			Next
			beacons = beaconstr
		EndIf
		
		'clean up any unused beam corner trackers
		For Local tracker:DeflectedBeam = EachIn deflectedBeamList
			If Not tracker.used
				'Print "closing corner tracker"
				For Local deflected:TGNetObject = EachIn tracker.deflectList
					CloseGNetObject(deflected)
				Next
				deflectedBeamList.Remove(tracker)
			EndIf
			tracker.used = False'must be continually set to True to not be cleaned up
		Next
				
		'if we're about to finish, count down the timer
		If timerExists(timerList, "finishTimer")
			If timerTick(timerList, "finishTimer") Then finished = True'ends the stage
		EndIf
	EndMethod
	
	'ends a stage, fade to black
	Method finish()
		If Not failure
			timerSet(timerList, "finishTimer", 800)
			fade(1)
		EndIf
	EndMethod
	
	'calculates the number of rows and columns of cboxes on this map, initializes the lists
	Method setup_cboxes()
		Print "mapsize: " + width+","+height + "   ["+tilehet+","+tilewid+"]"
		
		For Local x = tileminx To tilemaxx
			For Local y = tileminy To tilemaxy
				cboxList[x,y] = New TList
			Next
		Next
	EndMethod
	
	'checks the map boxes for collisions AND updates terrain (like lightning) (does it still do that? I don't think so...)
	Method updateMapCollisions()	
		'Print "checking for collissions:"	
		'check for collisions
		For Local cbx = tileminx To tilemaxx
		For Local cby = tileminy To tilemaxy
			Local cbList:TList = cboxList[cbx,cby]
			Local cbList2:TList = cbList.Copy()'so we can modify it on the fly
			If Not ListIsEmpty(cbList)
				'Print "  box:"+cbx+","+cby+ "   with count:"+CountList(cbList)
				
				'go through all entities in this box
				For Local e:Entity = EachIn cbList
					If e.link = Null Then Continue
					'Print "   checking entity with wid: "+e.wid' + "    {"+CountList(e.proximity_shipList)+"}"
									
					'then go through all the entities also in this box
					For Local o:Entity = EachIn cbList2
						If o.link = Null Then Continue
						
						'skip if both are the same
						If o = e Then Continue
						
						'what sorts of things are these two rascals?
						Local eclass$ = e.class
						Local oclass$ = o.class
						
						'skip if either has already collided (unless one is a gravity well)
						If Not eclass = "gravitywell" And Not oclass = "gravitywell"
							If o.collided Or e.collided Then Continue
						EndIf
						
						'Print "      against ship with wid: "+o.wid' + "    {"+CountList(e.proximity_shipList)+"}"
									
						'did we already check these two? skip if we've already compared 'em.
						Rem
						Local rechecked = False
						For Local checked:Ship = EachIn o.proximity_shipList
							If e = checked
								rechecked = True
								Print "recheck??"
							EndIf
						Next
						EndRem
						'If rechecked Then Continue
						'If ListContains(o.proximity_shipList, e) Or ListContains(e.proximity_shipList, o)
						'	Print "recheck abort"
						'	Continue
						'EndIf
						'Print "COLISIG CHECK:"+e.class+" vs "+o.class
						
						
						Rem
						'if one of them are a gravity well (but not both), skip the "at least one ship" requirement
						If (eclass = "gravitywell" And oclass <> "gravitywell") Or (oclass = "gravitywell" And eclass <> "gravitywell")
							'lalala
							
						'otherwise, one of the players has to be a ship; no shot-shot collisions
						ElseIf Not (eclass = "ship" Or oclass = "ship")
							Continue
						EndIf
						EndRem
						
						'the only class of things that can interact with themselves are ships
						If eclass = "ship"' or oclass = "ship"
							'Life is a dream for the wise, a game for the fool, a comedy for the rich, a tragedy for the poor.
						ElseIf eclass = oclass
							Continue
						EndIf
						
						'should we check friend/foe? (things being pulled around by gravity wells can hurt their friends)
						Local checkFF = True
						If o.beingAffectedByGravityWell Or e.beingAffectedByGravityWell Then checkFF = False
						
						If oclass = "ship"
							'only ships have placeholders in ignorlist
							If ListContains(e.ignoreList, Ship(o).placeholder) Then Continue
							'ship-ship collisions
							If eclass = "ship"
								If Ship(o).base.ignoreShipCollisions Then Continue
								If Ship(o).base.ord And checkFF And Not factionsCanHurtEachother(Ship(e).faction, Ship(o).faction) Then Continue'no friendly ordinance
							EndIf
							'ship-shot collisions
							If eclass = "shot"
								If Ship(o).base.ignoreShotCollisions Then Continue
								If Shot(e).owner <> Null
									If checkFF And Not factionsCanHurtEachother(Shot(e).owner.faction, Ship(o).faction) Then Continue'no friendly fire
								EndIf
							EndIf
						EndIf
						If eclass = "ship"
							If ListContains(o.ignoreList, Ship(e).placeholder) Then Continue
							'ship-ship collisions
							If oclass = "ship"
								If Ship(e).base.ignoreShipCollisions Then Continue
								If Ship(o).base.ord And checkFF And Not factionsCanHurtEachother(Ship(o).faction, Ship(e).faction) Then Continue'no friendly ordinance	
							EndIf
							'ship-shot collisions
							If oclass = "shot"
								If Ship(e).base.ignoreShotCollisions Then Continue
								If Shot(o).owner <> Null
									If checkFF And Not factionsCanHurtEachother(Shot(o).owner.faction, Ship(e).faction) Then Continue'no friendly fire
								EndIf
							EndIf
						EndIf
						
						'if the gravitywells are ignoring this ship (or faction)
						If oclass = "gravitywell" And eclass = "ship"
							If Not ListIsEmpty(o.ignoreList) And ListContains(o.ignoreList, Ship(e).placeholder) Then Continue
							If o.faction <> 0 And o.faction = e.faction Then Continue
						EndIf
						If eclass = "gravitywell" And oclass = "ship"
							If Not ListIsEmpty(e.ignoreList) And ListContains(e.ignoreList, Ship(o).placeholder) Then Continue
							If e.faction <> 0 And e.faction = o.faction Then Continue
						EndIf


						'Print "    COLLISION?:"
						
						'--- POTENTIAL COLLISION ---
						'get the radii of the entity gfx
						Local wid = e.scale * (e.wid/2) * e.hitScale
						Local het = e.scale * (e.het/2) * e.hitScale
						'get the radii of the ship gfx
						Local owid = o.scale * (o.wid/2) * o.hitScale
						Local ohet = o.scale * (o.het/2) * o.hitScale
						
						'get how close ovals are to colliding
						'If e.class = "gravitywell" Then Print "well: " + e.x + " " + e.y + " " + e.wid + " " + e.het + " | " + o.class + " " + o.x + " " + o.y
						'If o.class = "gravitywell" Then Print "well: " + o.x + " " + o.y + " " + o.wid + " " + o.het + " | " + e.class + " " + e.x + " " + e.y
						Local colliding = OvalsCollide(e.x,e.y,wid,het,e.rot,o.x,o.y,owid,ohet,o.rot)
						
						
						'collide?
						If colliding
							o.collided = True
							e.collided = True

							'ship - ship
							If eclass = "ship" And oclass = "ship"
								shipCollide(Ship(e), Ship(o))

							'ship - shot
							ElseIf eclass = "ship" And oclass = "shot"
								shotCollide(Shot(o), Ship(e))
							ElseIf eclass = "shot" And oclass = "ship"
								shotCollide(Shot(e), Ship(o)) 
						
							'ship - item
							ElseIf eclass = "ship" And oclass = "item"
								itemCollide(Item(o), Ship(e))
							ElseIf eclass = "item" And oclass = "ship"
								itemCollide(Item(e), Ship(o)) 
								
							'gravitywell - anything
							ElseIf eclass = "gravitywell"
								gravitywellCollide(GravityWell(e), o)
								e.collided = False'so it can affect multiple things
							ElseIf oclass = "gravitywell"
								gravitywellCollide(GravityWell(o), e)
								o.collided = False'so it can affect multiple things
							EndIf
							
							'new_text("boink!", (e.x + o.x)/2, (e.y + o.y)/2)
						EndIf
					Next
					
					'If e.class = "gravitywell" Then Print "gravitywell ================= done"
					
					'we've checked all possible collisions with the first guy, remove from the list to check on subsequent runs
					ListRemove(cbList2, e)
					
					'if this entity REALLY cares about proximity, find ships in adjecent cboxes, too (for AI purposes)
					Rem
					If e.proximity_track
						'each adjecent cbox
						For Local py = -1 To 1
						For Local px = -1 To 1
							'skip the center tile
							If (py <> 0 Or px <> 0)
								'make sure we're within bounds
								If (cbx+px >= 0) And (cbx+px <= cboxColNum-1) And (cby+py >= 0) And (cby+py <= cboxRowNum-1)
									For Local o:Ship = EachIn cboxList[cbx+px,cby+py]
										If o <> e'if not self
											If Not ListContains(e.proximity_shipList, o)
												e.proximity_shipList.addLast(o)
											EndIf
										EndIf
									Next
								EndIf
							EndIf
						Next
						Next
					EndIf
					EndRem
				Next
			EndIf
		
		Next
		Next
	EndMethod
	
	'adds an entity to collision boxes
	'[small groups of entities based on location, so we perform (a^2 + b^2 + c^2... comparisons instead of just n^2)]
	Method addToCollisionBoxes(e:Entity)', priority = False)
		If Not e.ignoreCollisions' And (priority Or Rand(1,2) = 1)
			Local cbx, cby, cbx_old = -1, cby_old = -1' x, y tile of the current and previous cbox
			Local srad# = Max(e.wid, e.het)/2'the maximum radius of the entity (it could be pointing in any direction)
			Local cb_rad = Ceil(Ceil(srad*2/TSIZE)/2)'how many collision boxes-wide the entity is
			
			Local centercoord = MAXTILE / 2
			
			'If e.mass = 6 Then Print "adding well to boxes:         (rad "+srad+"   cb_rad " + cb_rad+")"
							
			
			'look at each corner of the entity, add self to each cbox group that it overlaps
			For Local y_box# = -cb_rad To cb_rad
				cby = Int((e.y + (y_box*srad)) / TSIZE) + centercoord
				If cby < tileminy Or cby > tilemaxy Then Continue
	
				If cby <> cby_old'if we've moved onto a new row of cboxes for this corner
					For Local x_box# = -cb_rad To cb_rad
						cbx = Int((e.x + (x_box*srad)) / TSIZE) + centercoord
						If cbx < tileminx Or cbx > tilemaxx Then Continue

						If cbx <> cbx_old'if we've moved into a new column of cboxes for this corner
							cboxList[cbx,cby].addLast(e)
							
							'If e.mass = 6'gravitywell
							'	Print "        cbx " + cbx + "    cby " + cby
							'EndIf
								 
							'SetColor 255,255,255
							'SetAlpha .07
							'DrawRect (cbx*cboxSize)+camx-width, (cby*cboxSize)+camy-height, cboxSize, cboxSize
						EndIf
						cbx_old = cbx
					Next
				EndIf
				cby_old = cby
				cbx_old = -255'we're on a new row, reset the column check
			Next
		EndIf
	EndMethod

	'removes prexisting entities
	Method wipeMap()
		For Local e:Entity = EachIn shipList
			e.remove()
		Next
		For Local e:Entity = EachIn shotList
			e.remove()
		Next
	EndMethod
	
	Method loadMap(fileName$)
		'open the map
		Local mFile:TStream = ReadFile(MISSION_PATH + missionName + "/" + fileName + ".map")

		Local centercoord = MAXTILE / 2
		Print "(centercoord : " + centercoord+")"

		If mFile
			'load each data point
			Repeat
				'get the next datum type
				Local dat$ = ReadString(mFile,1)
				Select dat
				Case "x"'map width
					tilewid = ReadByte(mfile)
					tileminx = (centercoord-tilewid)
					tilemaxx = (centercoord+tilewid)
					width = tilewid * TSIZE'oh god just decide on a single coordinate system already you fucker
					Print "loaded map width " + tilewid + " : " + width
				Case "y"'map height
					tilehet = ReadByte(mfile)
					tileminy = (centercoord-tilehet)
					tilemaxy = (centercoord+tilehet)
					height = tilehet * TSIZE
					Print "loaded map height " + tilehet + " : " + height
				Case "t"'terrain icon
					Local tx = ReadByte(mfile) - tilewid + centercoord
					Local ty = ReadByte(mfile) - tilehet + centercoord
					Local t$ = ReadLine(mfile)
					'Print "creating terrain " + t + " at " + tx + "," + ty
					createTerrain(t, tx, ty)
				Case "e"'entity icon
					Local state = ReadByte(mfile)		'[-1: won't reactivate | 0:inactive | 1:active ]
					Local spawnmode = ReadByte(mfile)	'[0:once | 1:continual respawn up to num | 2:don't warp, just appear]
					Local spawndelay = ReadInt(mfile)
					Local spawnnum = ReadByte(mfile)
					Local faction = ReadByte(mfile)
					Local group = ReadByte(mfile)
					Local trigger = ReadByte(mfile)
					
					Local slen = ReadByte(mfile)
					Local behavior$ = ReadString(mfile, slen)
					Local clen = ReadByte(mfile)
					Local config$ = ReadString(mfile, clen)
					Local ix = ReadByte(mfile) - tilewid + centercoord
					Local iy = ReadByte(mfile) - tilehet + centercoord
					Local e$ = ReadLine(mfile)
					
					'if this is an enemy spawnpoint, modify the spawn counts
					If faction = 3 Then spawnnum:* spawnCountMod
					
					If spawnmode = 2'if they just appear (overrides state)
						For Local n = 1 To spawnnum
							'Print "creating entity " + e + " at " + ix + "," + iy + " with behavior " + behavior
							Local sx# = (ix - centercoord) * TSIZE + Rand(50,TSIZE-50)
							Local sy# = (iy - centercoord) * TSIZE + Rand(50,TSIZE-50)
							createShip(e, config, sx, sy, faction, behavior)
						Next
					
					Else'they spawn
						'really, we're just doing this to store the creation parameters for the spawner. Too much to make a friggin string array?
						'^^^-- did eventually do that. update this? --vvv
						Local s:Ship = new_ship(e, config, missionName, False)
						s.faction = faction
						s.behavior = behavior
						
						'make some terrain to handle the spawning/respawning
						Local respawn:Terrain = createTerrain("respawn", ix, iy, True)
						respawn.trackShip(s)
						respawn.vars.set("state", state)'[ -1 : inactivated | 0 : inactive | 1 : active ]
						respawn.vars.set("spawnmode", spawnmode)
						respawn.vars.set("spawndelay", spawndelay)
						respawn.vars.set("spawnnum", spawnnum)
						respawn.vars.set("trigger", trigger)
						respawn.vars.set("group", group)
						
						Print "creating spawner " + e + " at " + ix + "," + iy + " with behavior " + behavior
					EndIf			
					
				Case "b"'backdrop
					Local b$ = ReadLine(mfile)
					
					'load the .lua script
					Local bg_scriptslot = terrainScript.produce(b, TERRAIN_PATH + "backdrop_" + b + ".lua")
					terrainScript.call(bg_scriptslot, "initHost")
					
					'Print "creating backdrop " + b
					
				Case "w"'waypoint v- WHOA SO FANCY -v
					Local w:Waypoint = New Waypoint
					waypoints[ReadByte(mfile)] = w
					w.x = (ReadByte(mfile) - tilewid) * TSIZE + (TSIZE/2)
					w.y = (ReadByte(mfile) - tilehet) * TSIZE + (TSIZE/2)
					w.beacon = ReadByte(mfile)
					w.playerProx = ReadByte(mfile)
					Print "set waypoint to " + w.x + ","+w.y
					'If w.beacon Then createEmitter("waypoint", w.x, w.y).upload()
						
				EndSelect
			
			Until Eof(mfile)
			
			'initialize all terrain
			initTerrain()
									
			Return True
			
		Else
			RuntimeError "failed to load map: "+missionName+"/"+fileName
			Return False
		EndIf
	EndMethod
	
	'generates a string based off of the map terrain
	Method mapToString$()
		Local str$
		For Local x = tileminx To tilemaxx
			For Local y = tileminy To tilemaxy
				If map[x,y] <> Null
					str$ = str$ + "|" + map[x,y].name + ":" + x + "," + y
				EndIf
			Next
		Next
		
		Return str
	EndMethod
	
	'if _cut = true, then we cut straight to that music
	Method setMusic(_music$, _cut = False)
		cutmusic = _cut
		new_music = getSound(_music)
	EndMethod
	
	'if _cut = true, then we cut straight to that music
	Method haltMusic()
		stopmusic = True
	EndMethod
	
	Method GFP(flag$ = "")
		Print flag
		'Print "-==============-============ IMA AGREEN IN========================"
	EndMethod
	
		
	'initializes all terrain tiles
	Method initTerrain()
		For Local y = tileminy To tilemaxy
			For Local x = tileminx To tilemaxx
				Local t:Terrain = map[x,y]
				If t <> Null
					'allow the lua object to use the terrain
					LuaRegisterObject t, "terrain"
					'call the script
					terrainScript.call(t.scriptslot, "initHost")
					'if the terrain cares about far-away ships, add it to the check list
					'If t.callWithin And t.callWithinRange > 0 Then terrainShipCheckList.addLast(t)
				EndIf
			Next
		Next
	EndMethod
	
	Rem
	'gives the lua script a terrain tile
	Field gotTerrain'whether the last call to this was successful
	Method getTerrain(x, y)
		gotTerrain = 0
		If x >= tileminx And x <= tilemaxx And y >= tileminy And y <= tilemaxy
			Local t:Terrain = map[x,y]
			If t <> Null
				'allow the lua object to use the terrain
				LuaRegisterObject t, "gotterrain"
				gotTerrain = 1
			EndIf
		EndIf
		Return gotTerrain
	EndMethod
	EndRem

	'creates some terrain at x,y coords (_override will overwrite any existing terrain)
	Method createTerrain:Terrain(_name$, _coordx, _coordy, _override = False)
		'Print "creating terrain " + _name + " at " + _coordx + " ," + _coordy
		Local t:Terrain
		
		'_coordx = constrain(_coordx, 0, 99)
		'_coordy = constrain(_coordy, 0, 99)

		
		'if there's nothing there yet, create the terrain as normal
		If map[_coordx, _coordy] = Null
			t = new_terrain(_name, _coordx, _coordy)
			map[_coordx, _coordy]  = t
			
		'otherwise, try shunting it
		Else
			If Not _override
				t = new_terrain(_name, _coordx, _coordy)
			'(or the preexisting one, if we're overriding)
			Else
				t = map[_coordx, _coordy]
				map[_coordx, _coordy] = new_terrain(_name, _coordx, _coordy)
			EndIf
			
			Local centercoord = MAXTILE / 2
			'shunt the terrain
			For Local i = 0 To 15'Min(tilewid, tilehet)
				_coordx:+ Rand(-1,1)
				_coordy:+ Rand(-1,1)
				
				_coordx = constrain(_coordx, centercoord-tilewid, centercoord+tilewid)
				_coordy = constrain(_coordy, centercoord-tilewid, centercoord+tilewid)
				If map[_coordx, _coordy] = Null
					t.calculateDimensions(_coordx, _coordy)
					map[t.coordx, t.coordy] = t
					Exit
				EndIf
				
				If i = Min(tilewid, tilehet) Then Print "DID NOT FIND PLACE TO SHUNT TERRAIN " + t.name
			Next
		EndIf
		
		
		Rem
		'allow the lua object to use the stage
		LuaRegisterObject Self, "stage"
		'allow the lua object to use the terrain
		LuaRegisterObject t, "terrain"
		'call the script
		 t.terrainScript.Invoke("initHost", Null)
		EndRem
		
		Return t
	EndMethod
	
	Method create_item(scriptName$, _x, _y)
		LuaRegisterObject createItem(scriptName$, _x, _y), "item"
	EndMethod
	
	'creates a ship pulled from either the local mission configs folder (FOR THE HOST)
	Method createShip:Ship(_name$, _config$, _x, _y, _faction, _behavior$ = "")'NO MORE PARAMETERS. luas crash for some reason w/ more than this.
		Local s:Ship
				
		'load the ship from the current/base mission folder
		s = new_ship(_name, _config, missionName, False, _behavior)
		's.controller.stagewid = width
		's.controller.stagehet = height
		
		'set the faction of the created ship
		s.faction = _faction
		If s.color = 0 Then s.color = _faction
		
		'track certain factions so we can draw arrows to them
		If s.faction = trackFaction And Not s.base.ord Then trackList.addLast(s)
		
		'track certain ships so we can make the boss HP bar
		'Print "placing ship : " + Lower(s.name) + "    boss?:"+ ListContains(bossNameList, Lower(s.name))
		If isBossShip(s)
			If Not ListContains(bossList, s) Then bossList.addLast(s)
			If Not ListContains(trackList, s) Then trackList.addLast(s)'also all track all bosses
		EndIf
		
		'set position of the new ship
		placeShip(s, _x, _y)
		s.reset(True)
					
		'allow any lua objects to use this ship
		LuaRegisterObject s, "ship"
		Return s
	EndMethod
	
	'places a ship and makes sure it doesn't start overlapping with something
	Method placeShip(s:Ship, _x, _y)
		'set the position of the ship
		s.x = _x
		s.y = _y
		
		'keep it on the stage
		If Abs(s.x) > stage.width Then s.x = stage.width*Sgn(s.x)
		If Abs(s.y) > stage.height Then s.y = stage.height*Sgn(s.y)
		
		'random-walk it away from any potential collisions
		If Not s.base.ord'not for missiles, etc.
			For Local o:Ship = EachIn shipList
				If Not o.base.ignoreShipCollisions And Not o.ignoreCollisions
					Local steps = 0
					If s <> o
						Local wid = (s.wid/2)
						Local het = (s.het/2)
					
						Local owid = (o.wid/2)
						Local ohet = (o.het/2)
						
						'move it while it's colliding
						While OvalsCollide(s.x,s.y,wid,het,s.rot,o.x,o.y,owid,ohet,o.rot)
							s.x:+ Rand(-100,100)
							s.y:+ Rand(-100,100)
						Wend
						
						steps:+1
						If steps > 20 Then Exit
					EndIf
				EndIf
			Next
		EndIf
	EndMethod
	
	
	
	'creates a ship and warps it in
	Method warpShip:Ship(_name$, _config$, _x, _y, _faction, _behavior$ = "", _warptime = 0)'NO MORE PARAMETERS. luas crash for some reason w/ any more
		Print "warping ship " + _name + " " + _config + " on faction " + _faction + " with behavior: " + _behavior
		
		'create the ship to be warped in
		Local s:Ship = new_ship(_name, _config, missionName, False, _behavior)
		
		'set the faction of the created ship
		s.faction = _faction
		s.color = _faction
		If s.faction = trackFaction Then trackList.addLast(s)
		If isBossShip(s)
			If Not ListContains(bossList, s) Then bossList.addLast(s)
			If Not ListContains(trackList, s) Then trackList.addLast(s)'also all track all bosses
		EndIf

		
		s.x = _x
		s.y = _y
		
		'WARP it?
		Local warp:Ship
		If _warptime > 0
			warp = warpThisShip(s, _x, _y, _warptime)
		
		'PLACE it?
		Else
			placeShip(s, _x, _y)
		EndIf
		
		'allow the lua object to use the TO-BE-WARPED ship // the warp itself
		LuaRegisterObject s, "ship"
		If warp <> Null Then LuaRegisterObject warp, "warp"
		Return s
	EndMethod

	'warps in a given ship (RETURNS THE WARP, NOT THE SHIP ITSELF)
	Method warpThisShip:Ship(s:Ship, _x, _y, _warptime)
		'create a warp "ship"
		Local warp:Ship = createShip("Warp", "", _x, _y, s.faction)
		warp.controller.vars.set("warptime", _warptime)
		warp.dropList.addLast(s)
		warp.alpha = 0
		
		s.x = 0
		s.y = 0

		Select s.base.material
		Case MATTER_VECTOR
			'ship effects
			Local e:Emitter = createEmitter("ship_warp", warp.x, warp.y)
			e.addGFX(s.base.gfxFile)
			e.lifetimer = _warptime + 300
			e.scalespeed = -frameTime / _warptime' how much to increase per frame to reach 1 in _warptime ms
			e.alphaspeed = frameTime / _warptime
			e.upload()

		Default
			'swirly effects
			Local e:Emitter = createEmitter("ship_warp_swirlies", warp.x, warp.y)
			e.lifetimer = _warptime + 300
			e.scalespeed = (frameTime / _warptime) * 2' how much to increase per frame to reach 1 in _warptime ms
			e.alphaspeed = -(frameTime / _warptime) * .5
			e.upload()
			
			'ship effects
			e = createEmitter("ship_warp", warp.x, warp.y)
			e.addGFX(s.base.gfxFile)
			e.lifetimer = _warptime + 300
			e.scalespeed = -frameTime / _warptime' how much to increase per frame to reach 1 in _warptime ms
			e.alphaspeed = frameTime / _warptime
			e.upload()
		
			'particle effects
			e = createEmitter("warp_sparklies", warp.x, warp.y)
			e.movrotX = e.x
			e.movrotY = e.y
			e.upload()
	
			If s.behavior = "player"
				playSFX(getSound("game_warpcharge.wav"), warpChannel)
			Else
				playLocationalSFX(getSound("game_warpcharge.wav"), warpChannel, warp.x, warp.y, 2)
			EndIf
		EndSelect
	
		Return warp
	EndMethod
	
	Method makeGravityWell(x, y, range = 350, strength = 3.75, dir = -1, lifetime = -1)
		'make the gravity well
		Local well:GravityWell = New GravityWell
		well.init(x, y, range, strength, dir, lifetime)
		
		LuaRegisterObject well, "well"
	EndMethod

	'fires a beam from point A to B. returns 1 if it hits something, 0 otherwise
	Method makeBeam(beam:TGNetObject, owner:Gertrude, absx, absy, tarx, tary)
		Local hit = 0
		Local hitList:TList = New TList'if piercing, keep track of who we've hit so far (as to not hit them again)
		
		If beam <> Null
			'Print "     making beam " + GetGNetInt(beam, NET_BEAM_STYLE) + ":  " + absx+","+absy + "  To  " + tarx+","+tary
		
			'update the beam net object to be firing
			SetGNetInt(beam, NET_BEAM_ACTIVE, 1)
			
			'find the angle, distance towards the target
			Local dx = tarx-absx, dy = tary-absy
			Local dist# = approxDist(dx,dy)
			Local theta# = ATan2(dy,dx)
			
			'get the range, damage, knockback
			Local beamrange# = GetGNetFloat(beam, NET_BEAM_RANGE)
			Local beamdamage# = GetGNetFloat(beam, NET_BEAM_DAMAGE)
			Local beamknockback# = GetGNetFloat(beam, NET_BEAM_KNOCKBACK)
			
			Local beamstyle = GetGNetFloat(beam, NET_BEAM_STYLE)
			
			'fire less than maximum range?
			Local absolute = GetGNetInt(beam, NET_BEAM_ABSOLUTE)
			If absolute Then beamrange = Min(beamrange, dist)
			
			'piercing?
			Local piercing = GetGNetInt(beam, NET_BEAM_PIERCE)
			
			'starting at the gun
			Local stepx, stepy
			
			Local centercoord = MAXTILE / 2
			'step towards the target
			For Local i = 1 To beamrange Step BEAMSTEP
			
				'if we didn't hit anything on that last step
				If hit = 0
					'step forward
					stepx = absx + Cos(theta)*i
					stepy = absy + Sin(theta)*i
				Else
					Exit
				EndIf
			
				'find the cbox that we're in
				Local cbx = Int(stepx / TSIZE) + centercoord
				Local cby = Int(stepy / TSIZE) + centercoord
				If cby < tileminy Or cby > tilemaxy Then Continue
				If cbx < tileminx Or cbx > tilemaxx Then Continue
				
				'see if we hit anything in that cbox
				If Not ListIsEmpty(cboxList[cbx,cby])
					For Local s:Ship = EachIn cboxList[cbx,cby]
						'Print "beam checking " + s.name + " in box " + cbx+","+cby
						'skip owner and uncollidable ships
						If s.base.ignoreShotCollisions Or owner = s.placeholder Or s.dead Then Continue
						
						'skip anything already hit
						If ListContains(hitList, s) Then Continue
						
						'if this cheap collision detection says they've collided (using the average radii of the ship)
						If CirclesCollide(stepx,stepy,1, s.x,s.y,s.hitScale*s.scale*((s.wid+s.het)/4))
							'stop the beam // start ignoring this ship
							If Not piercing
								hit = 1
							Else
								hitList.addLast(s)
								
								'make some sparks
								Local sparks:Emitter = new_emitter("sparks")
								sparks.x = stepx
								sparks.y = stepy
								sparks.speed = 230
								sparks.speedVar = 70
								sparks.num = 3
								sparks.interval = 0
								sparks.intervalVar = 0
								sparks.lifetimer = 300
								sparks.upload()
							EndIf
							
							'Print "beam hitting " + s.name + " for damage " + beamdamage
							
							'skip friendly ships (unless we're doing healing "damage")
							If Not factionsCanHurtEachother(s.faction, owner.faction) And beamdamage > 0 Then Continue
							
							'Print "     can do damage!" + beamdamage
						
							Local dmg# = beamdamage * (timePass/1000)
							Local knock# = beamknockback * (timePass/1000)
						
							'some things can "reflect" the beam
							Local reflect = False
							If s.abilityOn("Prism") Or (dmg < s.damageBuffer And dmg > 0) Then reflect = True
						
							'some beams can't be reflected (apply AGAIN post-damage incase barrier reflect)
							If beamrange >= 800 Then reflect = False
							If piercing Then reflect = False
						
							'Print "BEAM CONTACT " + s.name
							'ARMOUR COLLIDE
							If Not reflect Or beamdamage < 0
								'Print "BEAM HITTING " + s.name
								
								'do damage to the ship / give this ship a damage buffer
								If beamdamage > 0
									'Print "    - damage " + beamdamage
									If Not s.damage(dmg, theta) Then reflect = True'reflect deflected beams
									's.damage(dmg, theta)
								Else
									'Print "     - buffer " + Abs(beamdamage)
									s.addDamageBuffer(Abs(beamdamage))
									reflect = False
								EndIf
								
								'knockback the ship / slow down the ship
								'Print "       knocking back " + s.name + "   with knockback " + beamknockback
								If beamknockback >= 0
									s.bump(theta+180, knock)
								ElseIf s.speed > Abs(beamknockback)
									s.speed = Abs(beamknockback)':- 20*timeMod
								EndIf
															
								'store the fact that this beam hit this ship
								s.placeholder.nemesis_IDNUM = owner.idnum
								s.placeholder.nemesis_behavior = owner.behavior
							EndIf
							
							'some beams can't be reflected (was applied BEFORE damage as well)
							If beamrange >= 800 Then reflect = False
							If piercing Then reflect = False
							
							'SHIELD/PRISIM COLLIDE : make a new beam or two
							If reflect								
								'go through the deflected beam list to see if there's already an object handling this corner
								Local corner:DeflectedBeam
								For Local tracker:DeflectedBeam = EachIn deflectedBeamList
									If tracker.originalBeam = beam'if this tracker is tracking this beam
										'Print "alreadyhandled"
										tracker.used = True'don't clean up this corner
										corner = tracker
									EndIf
								Next
								
								'if there is no tracker yet, make one
								Local newcorner = False
								If corner = Null And CountList(deflectedBeamList) < MAXDEFLECTEDBEAM
									corner = New DeflectedBeam
									corner.originalBeam = beam
									
									deflectedBeamList.addLast(corner)
									newcorner = True
									'Print "new corner"
								EndIf
								
								'make the deflected beam(s)
								If corner <> Null
									Local deflectcount = 1
									If s.abilityOn("Prism") Then deflectcount = 2
									For Local j = 1 To deflectcount
										Local deflected:TGNetObject
										
										'if it's a new corner, then make a new child beam
										If newcorner
											deflected = newBeamNetObject(beamdamage, beamknockback, beamrange, beamstyle, 1)
											SetGNetInt(deflected, NET_BEAM_ACTIVE, 1)
											SetGNetInt(deflected, NET_BEAM_PIERCE, 0)
											corner.deflectList.addLast(deflected)
											
										'otherwise, search deflectList for the appropriate net object
										Else
											Local count = j
											For Local b:TGNetObject = EachIn corner.deflectList
												count:- j
												If count <= 0
													deflected = b
													Exit
												EndIf
											Next
										EndIf
										
										'sets the target coords of the new beam
										'Local hittheta# = ATan2((s.y - stepy),(s.x - stepx))	'the angle between the beam and the ship
										'Local drot# = Abs(theta Mod 90) - hittheta				'the difference between the beam's angle and hittheta
								
										'find the angle from the start to the hit ship
										Local tartheta# = ATan2((s.y - absy),(s.x - absx))
										'get the difference between that and the actual angle of the beam
										Local dtheta# = tartheta - theta
								
										Local deflectangle#' = theta
										If s.abilityOn("Prism")
											deflectangle = theta + (binsgn(j-1)*s.abilityGetVar("Prism","deflection"))
										Else
											'deflectangle:+ drot
											deflectangle = theta + 180 + dtheta*10
										EndIf
										
										Local deflectrange = (beamrange - i)*1.5'add a little bit of range to the deflected
										
										Local deflectx = stepx + Cos(deflectangle)*deflectrange
										Local deflecty = stepy + Sin(deflectangle)*deflectrange
										
										'make the deflected beam (with the deflectee as the new owner)
										makeBeam(deflected, s.placeholder, stepx, stepy, deflectx, deflecty)
									Next
								EndIf
								
								'make a shield effect
								If Not s.abilityOn("Prism") And s.base.emitFX
									Local shield:Emitter = new_emitter("shield")
									shield.num = 3
									shield.lifetimer = 100
									shield.rot = theta
									shield.attach(s)
									shield.upload()
								EndIf
							EndIf
							
							Exit
						EndIf
					Next
				EndIf
			Next
						
			'set the start and end points of this beam
			SetGNetInt(beam, NET_BEAM_X1, absx)
			SetGNetInt(beam, NET_BEAM_Y1, absy)
			SetGNetInt(beam, NET_BEAM_X2, stepx)
			SetGNetInt(beam, NET_BEAM_Y2, stepy)
		EndIf
		
		Return hit
	EndMethod
	
	'make a shot
	Method createShot:Shot(gfxName$, absx#, absy#, theta#, speed#, range#, damage#)
		Local s:Shot = New Shot
		s.name = name
		s.owner = Null
		s.scriptslot = Null
		
		s.x = absx
		s.y = absy
		s.rot = theta
		s.movrot = theta
		s.speed = speed
		s.setRange(range)
		s.damage = damage
		
		s.init(gfxName)
		
		'give any lua scripts access to this shot
		LuaRegisterObject s, "shot"
		Return s
	EndMethod

	
	'just a holder function for the new_emitter function, so we can access it from inside map load scripts
	Method createEmitter:Emitter(luaFile$ = "", ex = 0, ey = 0)
		Local e:Emitter = new_emitter(luaFile, False)
		e.x = ex
		e.y = ey
		'Print "  stage created emitter"+ luaFile + " at " + e.x+","+e.y
		LuaRegisterObject e, "emitter"'so lua scripts can use this emitter now
		Return e
	EndMethod
	
	Method createText:Sprite(txt$, tx, ty, time = 500)
		Return new_text(txt, tx, ty, time)
	EndMethod
	
	Method createSprite:Sprite(gfxName$, tx, ty, layer = 2, anim = False, gfxType = NET_SPRITE_GFXTYPE_EFFECT)
		'Print flag
		'Print "-==============-============ IMA AGREEN IN======================== " + gfxName
		Print "creating sprite: " + gfxName
		
		Local s:Sprite = New Sprite
		s.initNetSprite(gfxName, gfxType, anim, layer)
		s.link = hostSpriteList.addLast(s)
	
		s.x = tx
		s.y = ty
		
		LuaRegisterObject s, "sprite"
		Return s

	EndMethod
	
	'pulls all points(crystals) towards a single point
	Method pullPointsTowards( _x, _y)
		'attract them items!
		For Local i:Item = EachIn itemList
			If Lower(i.name) = "point"
				Local dist# = approxDist((_x-i.x),(_y-i.y))
				If dist > 600 Then i.pull(_x, _y, -1, dist)
			EndIf
		Next
	EndMethod

	
	'gets the closest ship
	Method getClosestShip:Gertrude(_x, _y, _faction, _viableTargets = -1, _detectRange = 1000)
		Return closestShip(_x, _y, _faction, _viableTargets, _detectRange)
	EndMethod
	
	'sets a black sheet to a given opacity over a given amount of time
	Method fade(alpha#)
		fadeTarget = alpha
	EndMethod

	'sets the rate of time passage
	Method setTimeRate(rate# = 1.0)
		timeRate = rate
	EndMethod

	'keeps ships from going off the map by softly springing them back
	Method borders()
	
		'bouncy bouncy!
		For Local s:Ship = EachIn shipList
			If s.x > width Then s.add_force(180, 3*(s.x - width))			'right edge
			If s.x < -width Then s.add_force(0, 3*(Abs(s.x) - Abs(width)))	'left edge
			If s.y > height Then s.add_force(270, 3*(s.y - height))		'bottom edge
			If s.y < -height Then s.add_force(90,  3*(Abs(s.y)-Abs(height)))'top edge
			
			'sorta inappropriate but this is the most efficient place to do stage looping of ships
			'If ListContains(bossNameList, Lower(s.name)) Then bossList.addLast(s)'THIS WAS TERRIBLE
			
			'sorta inappropriate but this is the most efficient place to do stage looping of ships i have found this to be true twice
			'If Not s.base.ord Then factionCensus[s.faction]:+ 1'count non-ordinance ships
			'BUT maps without borders never count then, have this be the backup system
			'tookCensus = True	
		Next
	EndMethod
	
	'keeps ships from going off the map by wrapping them around
	Method wrap()
		'cheese rap wrap!!
		For Local i = 0 To 1
			Local eList:TList
			If i = 0 Then eList = shipList
			If i = 1 Then eList = shotList
			For Local s:Entity = EachIn eList
				If s.x > width+30 Then s.x = -width-25
				If s.x < -width-30 Then s.x = width+25
				If s.y > height+30 Then s.y = -height-25
				If s.y < -height-30 Then s.y = height+25
			Next
		Next
	EndMethod

	Method setSizeToScreen()
		width = SWIDTH/2
		height = SHEIGHT/2
		If cam <> Null
			cam.stagewid = width
			cam.stagehet = height
		EndIf
	EndMethod
	
	'clear all queued messages
	Method clearMessages()
		ClearList(mboxList)
		ClearList(shakytextList)
	EndMethod
	
	'so lua scripts can make messages
	Method newMessage(msg$, portrait$)
		new_message(msg, portrait)
	EndMethod
	
	Method newShakyText(msg$, lifetimer = 0)
		new_shakytext(msg, lifetimer)
	EndMethod
	
	'returns 1 if a message group is empty, 0 otherwise
	Method isMessageGroupEmpty(group)
		If group < MAXMESSAGEGROUPS
			If ListIsEmpty(messageGroup[group]) Then Return 1
		EndIf
		Return 0
	EndMethod
	
	'triggers a group of messages
	Method activateMessageGroup(group)
		If group < MAXMESSAGEGROUPS
			For Local m:Messagebox = EachIn messageGroup[group]
				new_message(m.message, m.text[0])'message and placeholder portrait name
			Next
		EndIf
	EndMethod
	
	'makes a placeholder message and adds it to a group list
	Method addMessageToGroup(group, text$, portrait$ = "")
		Print "adding to message group " + group + "   : " + text + "  | " + portrait
		Local m:Messagebox = New Messagebox'not a REAL messagebox but a placeholder with the message and portrait name
		m.message = text
		m.text[0] = portrait
		messageGroup[group].addLast(m)
	EndMethod
	
	'gives a series of death messages
	Method playDeathMessages(tutorial = False)
		If ListIsEmpty(mboxList)
			Local message$
			If Not tutorial
				Local roll = Rand(0, 16)
				Select roll
				Case 0
					message = "I LOVE THAT DEATH EFFECT"
				Case 1
					message = "DAISY DAISY"
				Case 2
					message = "ANOTHER DAY ANOTHER DEAD PILOT"
				Case 3
					message = "TIME TO GO FIND A NEW LAST HOPE"
				Case 4
					message = "NICE REAL CONSIDERATE DID YOU THINK OF HOW THIS WILL AFFECT ME"
				Case 5
					message = "YEAH CAN'T SAY I DIDN'T SEE THAT COMING"
				Case 6
					message = "I DON'T WANT TO MAKE THIS A THING BUT YOU SEEM TO DO THAT A LOT"
				Case 7
					message = "BAM! RIGHT IN THE N.IX-X!"
				Case 8
					message = "WHOOPS, MY BAD. NOTE TO SELF: DO NOT ACTIVATE THAT FUNCTION"
				Case 9
					message = "ONLY YOU CAN SAVE - - - NEVERMIND"
				Case 10
					message = "AT LEAST YOU HAVE CHICKEN"
				Case 11
					message = "INSERT QUARTER TO CONTINUE"
				Case 12
					message = "ALAS PILOT I KNEW YE WELL"
				Case 13
					message = "I REALLY THOUGHT YOU HAD IT THAT TIME"
				Case 14
					message = "WELL YOU TRIED VERY HARD AND THAT'S WHAT IS IMPORTANT"
				Case 15
					message = "BWAHAHA - - - AHEM, SORRY. GOOD JOB. YOUR SACRIFICE WILL BE REMEMBERED."
				Case 16
					message = "NEXT TIME I'LL TRY TO FIND A PILOT WHO WON'T DO THAT"
				EndSelect
			Else'tutorial
				Local roll = Rand(-3,9)
				Select roll
				Case -3
					message = "PERFORM RAPID KILLS FOR POINT MULTIPLIER"
				Case -2
					message = "CRITICAL HITS BY REAR ARMOUR VULNERABLE PARTS"
				Case -1
					message = "TRICKED OUT SHIPS MORE EXPENSIVE TO REPAIR"
				Case 0
					message = "THRUST : [" + controlName("thrust") + "] PRESS AND HOLD TO CONTROL MOVEMENT"
				Case 1
					message = "FIRE WEAPONS : [" + controlName("fire") + "] WEAPONS CONTAIN UNLIMITED AMMO BUT LOWER SHIELDS"
				Case 2
					message = "AFTERBURNER : [" + controlName("afterburn") + "] AFTERBURN IS UNLIMITED BUT REDUCES TURN ABILITY AND HALTS SHIELD RECHARGE"
				Case 3
					message = "SPECIAL ABILITY : [" + controlName("ability") + "] ABILITIES MAY OVERHEAT SHIP --- USE WITH DISCRETION"
				Default'play a normal death line instead
					message = ""
					playDeathMessages(False)
				EndSelect
			EndIf
					
			If message <> "" Then newMessage(message, "stolenrobot")
		EndIf
	EndMethod
		
	'sets players to the specified waypoint
	Method putPlayersAt(wp = 0)
		If waypoints[wp] <> Null
			For Local player:TGNetObject = EachIn playerObjectList
				Local pship:Ship = Ship(GetGNetTarget(player))
				If pship <> Null
					If Not pship.dead
						new_message("ENERGIZE")
						pship.x = waypoints[wp].x
						pship.y = waypoints[wp].y
					EndIf
				EndIf
			Next
		EndIf
	EndMethod

	'revives dead players at the specified waypoint (or at the center of the map, if no such waypoint exists)
	Method reviveDeadPlayers(wp = 0)
		Local revivex = 0, revivey = 0
		
		If waypoints[wp] <> Null
			revivex = waypoints[wp].x + TSIZE/2
			revivey = waypoints[wp].y + TSIZE/2
		EndIf
		
		Local revived = 0
		
		'For Local player:TGNetObject = EachIn playerObjectList
		'	Local pship:Ship = Ship(GetGNetTarget(player))
		For Local pship:Ship = EachIn playerShipList
			'Print "checking if need to revive:"
			'Print "    player ship  " + pship.dead
			If pship.dead = 1
				Print "     -> revive do! (at waypoint " + wp + ")"
				warpThisShip(pship, revivex, revivey, 2500)
				'new_message("LIVE LONG AND PROSPER")
				'pship.x = revivex
				'pship.y = revivey
				pship.reset(False)
				
				'give them a gibshield
				'pship.controller.vars.set("armour_old", pship.armour)
				'timerSet(pship.controller.timerList, "gibshield_active", 2500)
				pship.abilitySetTimer("Initshield", "init", 2000)
				
				'warpShip("Lamjet", "bushwacker", revivex + 100, revivey, 3)
				revived = 1
			EndIf
		Next
		
		Return revived
	EndMethod
	
	'on player death, players assume control of the ship that killed them
	Method playersBecomeTheirKiller()
		For Local player:TGNetObject = EachIn playerObjectList
			Local pship:Ship = Ship(GetGNetTarget(player))
			If pship <> Null
				If pship.dead = 1
					'get the net ID of the killing ship
					Local nemesis:Ship
					For Local n:Ship = EachIn shipList
						If n.placeholder.idnum = pship.placeholder.nemesis_idnum'this is the one that kill us!
							nemesis = n
							Exit
						EndIf
					Next
					
					'become that which we opposed
					If nemesis <> Null
						nemesis.faction = 1
						nemesis.controller.remoteInput = pship.controller.remoteInput
						stage.playerShipList.addLast(nemesis)
						stage.playerShipList.Remove(pship)
						SetGNetInt(player, NET_PLAYER_SHIPIDNUM, nemesis.placeholder.idnum)
						SetGNetTarget(player, nemesis)
	
					'if we can't find the ship that killed them, just revive them at the start
					Else
						reviveDeadPlayers(0)
					EndIf
				EndIf
			EndIf
		Next
	EndMethod
	
	'sets all player ships to the given ship
	Method setPlayerShips(_shipname$, _shipconfig$)
		For Local player:TGNetObject = EachIn playerObjectList
			Local pship:Ship = loadShip(_shipname, _shipconfig, missionName)
			Local pshipString$ = configToString(pship)
		
			If pship <> Null
				'SetGNetTarget(player, pship)
				SetGNetString(player, NET_PLAYER_SHIP, pshipString)
				'pship.controller.remoteInput = TGNetObject(GetGNetTarget(player))
			EndIf
		Next
	EndMethod
	
	'tells all terrain tiles that a client has joined
	Rem
	Method terrain_clientJoined(_newClient = 0)
	
		'store the player # of the new client
		newClient = _newClient
	
		'allow the lua object to use the stage & terrain
		'LuaRegisterObject Self, "stage"
		
		'Print "TELLING TERRAIN ABOUT JOINED CLIENT " + _newClient
		For Local x = tileminx To tilemaxx
			For Local y = tileminy To tilemaxy
				Local t:Terrain = map[x,y]
				If t <> Null
					'allow the lua object to use the terrain
					LuaRegisterObject t, "terrain"
					Print "  calling terrain "+t.name+" client join script for player :" + newClient
					'call the script
					terrainScript.call(t.scriptslot, "initClient")
				EndIf
			Next
		Next

	EndMethod
	EndRem
	
	'called by the host while a ship is inside this terrain
	Method terrain_shipWithin(s:Ship, tx, ty)
		If terrain_exists(tx,ty)
			'does this terrain tile care about ships?
			If map[tx,ty].callWithin = 1
				'allow the lua object to use the terrain & ship
				LuaRegisterObject map[tx,ty], "terrain"
				LuaRegisterObject s, "ship"
				
				'call the script
				terrainScript.call(map[tx,ty].scriptslot, "shipWithin")
			EndIf
		EndIf
		Rem					
		'do nearby terrain tiles care about ships?
		For Local near:Terrain = EachIn terrainShipCheckList
			Local tdist = (Abs(near.coordx - tx) + Abs(near.coordy - ty))
			If tdist <= near.callWithinRange And tdist > 0'if it's nearby (but not the one we just checked, above)
				LuaRegisterObject near, "terrain"
				LuaRegisterObject s, "ship"
				terrainScript.call(near.scriptslot, "shipWithin")
			EndIf
		Next
		EndRem
	EndMethod

	'returns whether some terrain exists at some coords
	Method terrain_exists(tx,ty)
		If tx >= tileminx And tx <= tilemaxx And ty >= tileminy And ty <= tilemaxy
			If map[tx,ty] <> Null Then Return True Else Return False
		EndIf
	EndMethod
	
	Rem
	'returns the name of the terrain at a tile (if any)
	Method terrain_nameAt$(s:Ship, tx = 0, ty = 0)
		'use ship's position, if provided
		If s <> Null
			Local centercoord = MAXTILE / 2
			tx = Floor(s.x / TSIZE) + centercoord
			ty = Floor(s.y / TSIZE) + centercoord
		EndIf
	
		If terrain_exists(tx,ty) Then Return map[tx,ty].name Else Return ""
	EndMethod
	EndRem
	
	'returns the terrain at a tile (if any)
	Method terrain_at:Terrain(s:Ship, tx = 0, ty = 0, register = 0)
		'use ship's position, if provided
		If s <> Null
			Local centercoord = MAXTILE / 2
			tx = Floor(s.x / TSIZE) + centercoord
			ty = Floor(s.y / TSIZE) + centercoord
		EndIf
	
		'if terrain exists here, return it
		If terrain_exists(tx,ty)
			'allow the lua object to use the terrain?
			'If register = 1 Then LuaRegisterObject map[tx,ty], "terrain"
			Return map[tx,ty]
		Else
			Return Null
		EndIf
	EndMethod
	
	'returns the terrain at a waypoint (if any)
	Method terrain_at_waypoint:Terrain(wp)
		If waypoints[wp] <> Null
			Local centercoord = MAXTILE / 2
			Local tx = Floor((waypoints[wp].x-(TSIZE/2)) / TSIZE) + centercoord
			Local ty = Floor((waypoints[wp].y-(TSIZE/2)) / TSIZE) + centercoord
		
			'if terrain exists here, return it
			If terrain_exists(tx,ty)
				LuaRegisterObject map[tx,ty], "terrain"
				Return map[tx,ty]
			Else
				Return Null
			EndIf
		EndIf
	EndMethod
	
	'searches for and returns the first instance of a certain terrain type
	Method terrain_get:Terrain(tname$)
		For Local ty = tileminy To tilemaxy
		For Local tx = tileminx To tilemaxx
			Local t:Terrain = terrain_at(Null, tx, ty)
			If t <> Null And Lower(t.name) = Lower(tname)
				'allow the lua object to use the terrain
				LuaRegisterObject t, "terrain"
				Return t
			EndIf
		Next
		Next
	EndMethod
	
	'returns a waypoint at map coord x, y
	Method waypoint_at(coordx, coordy)
		Local centercoord = MAXTILE / 2
		For Local wp = 0 To MAXWAYPOINT-1
			If waypoints[wp] <> Null
				Local wpx = Floor((waypoints[wp].x-(TSIZE/2)) / TSIZE) + centercoord
				Local wpy = Floor((waypoints[wp].y-(TSIZE/2)) / TSIZE) + centercoord
				If coordx = wpx And coordy = wpy
					'allow the lua object to use the waypoint
					LuaRegisterObject waypoints[wp], "waypoint"
					Return wp
				EndIf
			EndIf
		Next
		
		Return 0
	EndMethod
	
	'returns a waypoint
	Method waypoint_get:Waypoint(wp)
		'allow the lua object to use the terrain
		LuaRegisterObject waypoints[wp], "waypoint"
		Return waypoints[wp]
	EndMethod

	'sets the visibility of a waypoint [ 0:invisible | 1:minimap | 2:minimap+beacon ]
	Method waypoint_setbeacon(wp, visible)
		waypoints[wp].beacon = visible
	EndMethod
	
	'counts the waypoints
	Method waypoint_count()
		Local count
		For Local wp = 0 To MAXWAYPOINT-1
			If waypoints[wp] <> Null Then count:+ 1
		Next
		Return count
	EndMethod

	'adds a certain enemy type to the boss HP bar
	Method addBoss(bossName$)
		bossNameList.addLast(Lower(bossName))
	EndMethod
	
	Method isBossShip(s:Ship)
		If ListContains(stage.bossNameList, Lower(s.name)) Then Return True
		If ListContains(stage.bossNameList, Lower(s.config)) Then Return True
		If s.config[(Len(s.config)-1)..] = "2"'for upgraded survivors
			If ListContains(stage.bossNameList, Lower(s.config[..(Len(s.config)-1)])) Then Return True
		EndIf
		Return False
	EndMethod

	'returns that diplomatic status between two factions
	Method checkDiplomacy(f1,f2)
		Return fTable[f1,f2]
	EndMethod
	
	'returns the number of enemies still alive
	Method enemyCount(faction)
		Local count
		Rem
		For Local s:Ship = EachIn shipList
			If Not s.base.ord
				If checkDiplomacy(faction, s.faction) = -1 Then count:+ 1
			EndIf
		Next
		EndRem
		'check non-ordinance census
		For Local i = 0 To 7
			If checkDiplomacy(faction, i) = -1 Then count:+ factionCensus[i]
		Next
		Return count
	EndMethod
	
	'returns 1 if all of a faction's enemies are defeated and 0 if any remain
	Method enemiesAreDefeated(faction)
		Rem
		For Local s:Ship = EachIn shipList
			If Not s.base.ord
				If checkDiplomacy(faction, s.faction) = -1 Then Return 0
			EndIf
		Next
		EndRem
		For Local i = 0 To 7
			If checkDiplomacy(faction, i) = -1 And factionCensus[i] > 0 Then Return 0
		Next

		'Print "enemies are dead!"
		Return 1
	EndMethod
	
	'returns the number of ships in a faction still alive
	Method factionCount(faction)
		Rem
		Local count
		For Local s:Ship = EachIn shipList
			If Not s.base.ord
				If s.faction = faction Then count:+ 1
			EndIf
		Next
		EndRem
		'Print "census for faction " + faction + " : " + factionCensus[faction]
		Return factionCensus[faction]
	EndMethod
	
	'returns 1 if there are no ships of the provided faction and 0 if any remain
	Method factionIsDefeated(faction)
		For Local s:Ship = EachIn shipList
			If s.faction = faction Then Return 0
		Next
		'Print "enemies are dead!"
		Return 1
	EndMethod
	
	'checks if a spawn group is already active
	Method isSpawnGroupActivated(group)
		For Local x = tileminx To tilemaxx
			For Local y = tileminy To tilemaxy
				Local t:Terrain = map[x,y]
				'is it a respawn tile?
				If t <> Null And t.name = "respawn"
					'is it the correct group?
					If Int(t.vars.get("group")) = group
						'is it active?
						If Int(t.vars.get("state")) = 1 Then Return 1
					EndIf
				EndIf
			Next
		Next
		Return 0
	EndMethod


	'activates a spawn group (can force it to be reactivated if it's already been active)
	Method activateSpawnGroup(group, _force = False)
		For Local x = tileminx To tilemaxx
			For Local y = tileminy To tilemaxy
				Local t:Terrain = map[x,y]
				'is it a respawn tile?
				If t <> Null And t.name = "respawn"
					'is it the correct group?
					If Int(t.vars.get("group")) = group
						'is it waiting to be activated OR if we can force it
						If Int(t.vars.get("state")) = 0 Or _force
							'turn it on
							t.vars.set("state", 1)
							'spawn the next thing immediately
							t.vars.set("spawntimer", 0)
						EndIf
					EndIf
				EndIf
			Next
		Next
	EndMethod
	
	'reduces the countdown timer of all untriggered respawn groups
	Method advanceSpawnCountdowns(amt)
		For Local x = tileminx To tilemaxx
			For Local y = tileminy To tilemaxy
				Local t:Terrain = map[x,y]
				'is it a respawn tile?
				If t <> Null And t.name = "respawn"
					Print "advance found respawn"
					'is it a countdown spawner
					If Int(t.vars.get("trigger")) = 2
						Print "   it's a counter"
						'is it waiting to be activated
						If Int(t.vars.get("state")) = 0
							Print "    it's an activate " + t.vars.get("spawntimer")
							'advance the countdown
							t.vars.set("spawntimer", t.vars.get("spawntimer") - amt)
						EndIf
					EndIf
				EndIf
			Next
		Next
	EndMethod
	
	'returns 1 if there are any respawn terrains still active
	Method respawnsStillActive()
		For Local x = tileminx To tilemaxx
			For Local y = tileminy To tilemaxy
				Local t:Terrain = map[x,y]
				'is it a respawn tile?
				If t <> Null And t.name = "respawn"
					If Int(t.vars.get("state")) >= 0 Then Return 1
				EndIf
			Next
		Next
		Return 0
	EndMethod
	
	'returns the number of spawn groups
	Method countSpawnGroups()
		Print "counting spawn groups : "
		Local groupList:TList = New TList
		For Local x = tileminx To tilemaxx
		For Local y = tileminy To tilemaxy
			Local t:Terrain = map[x,y]
			'is it a respawn tile?
			If t <> Null And t.name = "respawn"
				If Not ListContains(groupList, String(Int(t.vars.get("group")))) Then groupList.addLast(String(Int(t.vars.get("group"))))
			EndIf
		Next
		Next
		Print "     found " + CountList(groupList)
		Return CountList(groupList)
	EndMethod
	
	'sets the controller var of all ships to this value
	Method broadcast(varname$, value#)
		Print "broadcasting " + varname + " to " + value
		For Local s:Ship = EachIn shipList
			s.controller.vars.set(varname, value)
		Next
	EndMethod

	'keeps track of a ship for as long as it's alive
	Method trackShip(s:Ship)
		If Not ListContains(trackList, s) Then trackList.addLast(s)
	EndMethod
	
	'loses track of a ship
	Method loseShip(s:Ship)
		ListRemove(trackList, s)
	EndMethod
	
	'starts tracking all ships at a waypoint (can specify only a specific type of ship)
	Method trackShipsAt(wp, shipName$ = "")
		If waypoints[wp] <> Null
			For Local s:Ship = EachIn shipList
				If Abs(s.x - waypoints[wp].x) <= (TSIZE/2) And Abs(s.y - waypoints[wp].y) <= (TSIZE/2)
					If shipName = "" Or Lower(s.name) = Lower(shipName)
						trackList.addLast(s)
						LuaRegisterObject s, "ship"
					EndIf
				EndIf
			Next
		EndIf
	EndMethod
	
	'checks for a specific kind of ship to track and tracks it
	Method trackShipName(shipName$ = "")
		For Local s:Ship = EachIn shipList
			If Not ListContains(trackList, s)
				If shipName = "" Or Lower(s.name) = Lower(shipName)
					trackList.addLast(s)
					LuaRegisterObject s, "ship"
				EndIf
			EndIf
		Next
	EndMethod
	
	'starts tracking all ships of a behavior
	Method trackShipBehavior(behav$ = "")
		For Local s:Ship = EachIn shipList
			If Not ListContains(trackList, s)
				If Lower(behav) = Lower(s.behavior)
					trackList.addLast(s)
					LuaRegisterObject s, "ship"
				EndIf
			EndIf
		Next
	EndMethod
	
	'gets the xth tracked ship from the list
	Method getTrackedShip(num)
		Local i = 0
		For Local tracked:Ship = EachIn trackList
			If i = num
				LuaRegisterObject tracked, "ship"
				Return 1
			EndIf
			i:+ 1
		Next
		Return 0
	EndMethod
	
	'returns whether or not we're tracking a ship
	Method isTrackingShip(s:Ship)
		If ListContains(trackList, s) Then Return 1 Else Return 0
	EndMethod
	
	'returns the number of ships of the given name that we are tracking
	Method countTrackedShipName(shipName$ = "")
		Local count
		For Local s:Ship = EachIn trackList'shipList
			If Lower(s.name) = Lower(shipName) Then count:+ 1
		Next
		Return count
	EndMethod
	
	'counts the number of tracked ships
	Method countTrackedShips()
		Return CountList(trackList)
	EndMethod
	
	'clears the tracked ships list
	Method clearTracking()
		ClearList(trackList)
	EndMethod
	
	'kills all untracked ships (of a certain faction)
	Method killUntrackedShips(_faction = 0)
		For Local s:Ship = EachIn shipList
			If s.faction = _faction Or _faction = 0
				If Not ListContains(trackList, s) Then s.armour = 0
			EndIf
		Next
	EndMethod
	
	'adds a ship to the list of warped-out ships
	Method addToWarpedList(s:Ship)
		If s <> Null Then warpedOutList.addLast(s)
	EndMethod
	
	'returns the name of a current control
	Method controlName$(_name$)
		Return getControlName(_name)
	EndMethod
	
	'returns a random VIP name
	Method getRandomVIP$()
		Return getVIP(0)
	EndMethod
	
	'allows scripts to tick timers
	Method scriptTimerTick(_timerName$, _interval = -1)
		If timerTick(timerList, _timerName$, _interval) Then Return 1 Else Return 0
	EndMethod
	
	'relays a sound effect command from the lua file
	Method sfx(path$, x, y, vol# = 1, skipRateModulation = 0)
		If vol = 0 Then vol = 1
		playLocationalSFX(getSound(path), Null, x, y, vol, skipRateModulation)
		'playLocationalSFX(getSound(path), stageChannel, x, y, vol)
		'Print "stage sfx " + path + " vol " + vol
		'playLocationalSFX(getSound(path), Null, x, y)', vol)
	EndMethod

EndType


Const MAXDEFLECTEDBEAM = 32					'maximum number of deflected beams

Type DeflectedBeam
	Field used = True						'set to false every update. if still false by the next one, cleaned up.
	Field originalBeam:TGNetObject
	Field deflectList:TList = New TList
EndType

