Strict

Import "soundControl.bmx"			'play sounds and update music
Import "camera.bmx"					'defines the camera the game is viewed through
Import "gertrude.bmx"				'entity placeholder that contains its physics information
Import "fortifications.bmx"			'defines angles and amounts of defense a ship can have
Import "entityHosted.bmx"			'defines entity extension that allows synching between all machines
Import "luaScripts.bmx"				'gives the ability to access indexes of lua scripts
Import "cursor.bmx"					'handles the mouse/cursor
Import "stats.bmx"					'defines stats and stat bonuses
Import "chassis.bmx"				'defines ship archtypes
Import "components.bmx"				'defines components to be added to ships. can be a gun, armour, engines, etc.
Import "guns.bmx"					'defines shot entities that guns can fire
Import "emitters.bmx"				'defines sprite emitters to be created by the host then remade by the client
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 "entity_item.bmx"			'defines item entities


Include "shipWeapons.bmx"			'defines guns that components can add to ships
Include "shipAbilities.bmx"			'handles the activation and effect of special abilities

'for asteroids and suchlike
'Global inertSquad:Squadron = new_squad("inert",0)
'Const ECHARGE_DECAY# = .3				'what % of echarge decays per second
'Const ECHARGE_DAMAGE# = 5				'at max charge, this is the amount it does per second

Const POINT_JUICECHARGE# = .25			'how much juice a single point pickup gets
Const HEAL_SPEED# = .25					'amount to heal every loop once it's started

Const BARRIER_DELAY# = 150'500				'number of ms that it takes to start recharging after shooting
Const BARRIER_INTERRUPT# = 1500'500			'^-, only after taking damage
Const BARRIER_SPEED# = .15'.1				'speed that barrier recharges (IN PERCENT PER MS)
'Const BARRIER_POINTREGEN# = .75			'how much barrier recharges on a point pickup

Const AFTERBURN_COST# = .015

Const PLAYER_COLLISIONMOD# = .75			'how much less damage players take from collisions

'an actual ship
Global shipList:TList = New TList
Type Ship Extends hostedEntity
	Field name$
	Field base:Chassis					'the chassis this ship is based on
	Field placeholder:Gertrude			'a non-ship object that contains physics information
	
	Field config$						'the name of the configuration -> if Null then just use "default"
	Field config_owner$					'when we loaded this config, what was the owner?
	Field cshape[cshapedim,cshapedim]	'state of tile: 0=n/a | 1=empty | 2=utility | 3=turret | 4=forward | 5=angle | 6=reverse | (NEGATIVE = filled!)
	Field compList:TList = New TList	'list of components installed on this ship
	Field shapeBonusList:TList = New TList'a list of bonuses gained from the ship configuration, eg "symmetrical" "singlegun" "nogun" "noalt"
	
	Field behavior$						'how this ship behaves. "player":player-controlled|
	Field controller:Control			'an object to store all the controller commands and AI states
	
	Field gunGroup:TList[2]				'list of guns, 0=primary, 1=secondary
	
	'SHOOTY STATS, calculated on game initialization based on chassis stats + components
	Field armour#						'current amount of armour
	Field juice#						'currently stored seconds of afterburner
	Field points#						'current number of stored points
	Field barrier#						'current strength of kinetic barrier
	Field abilityList:TList = New TList	'the abilities that this ship has
	
	Field barrierhold#					'when shooting, hold the barrier here
	
	Field stat:StatBase					'the stats of this ship, with bonuses from components applied
	Field status:StatBonus				'sometimes get temporary bonus
	
	Field fortifyList:TList = New TList	'damage resistance applied to each incoming shot
			
	Field explodeSound:TSound
	Field explodeNum					'number of explosions
	Field debrisRGB[3]					'color of explosions, debris
	Field dropList:TList = New TList	'list of entities to drop when ship dies (in addition to any ones from the chassis)
	Field dropOnDeath = 1				'do we drop our items / ships on death?
	Field drop_points = True			'whether or not we drop base's points on death
	Field startingPoints = 0			'subtract this number from the amount we drop
	
	'SHOOTY tracking variables	
	Field afterburn, afterburn_old		'true/false, whether we're afterburning
	Field throttle#, throttle_old#		'what % the engines are pushin
	Field thrust_theta#					'the current direction of thrust
	Field tweenOnHit = True				'should we do a little white flash on hit of the ship?

	Field recent_damage#				'tracks the amount of damage the ship has taken recently
	Field recent_damage_timer#			'counts down how recent "recent" is. also acts at a damage text tween
	Field recent_damage_delay = 1800	'amount of time, in ms, how recent "recent" is
	Field recent_damage_theta			'angle of the most recent damage
	Field recent_damage_Sprite:Sprite	'the host-controlled sprite that displays damage taken
	
	Field damageMod# = 1.0				'some things can change all incoming damage by some amount (1.0 = 100%)
	Field damageBuffer#					'some things can give the ship invulnerability to damage up to a certain amount
	Field damageBuffer_old#				'what the damage buffer was last loop
	Field activeDamageBuffer = False	'is there a constant buffer being maintained?
	
	Field label:Sprite					'the host-controlled sprite that displays the name of the player
	
	Field recent_points#				'how many points the player has picked up recently
	Field recent_points_timer			'the countdown to fade the counter back out
	Field recent_points_delay = 7000
	
	Field backstab_delay = 750			'how long before something can be backstabbed again after the first one
	
	Field hideOverheat = False			'whether to hide the current overheat status
	'Field echarge#						'electrical charge built up on this ship. naturally decays. (if gets to 1 will start doing damage)
	Field emp_timer#					'the timer for how long we've been stunned
	Field emp_resistance#				'an accumulation of all the time we've spent stunned, builds up
	Field esparks						'the level of sparks currently being emitted (0:none 1:small 2:large 3:huge)
	
	Field collide_sfx_countdown			'can only make a collide noise every 1000 ms
	Field invulnerable					'should it be able to take damage?
		
	Field timerList:TList = New TList	'a list for timers
	
	Field weaponsLocked					'can we fire our weapons?
	Field abilityLocked					'can we use our abilities?
	
	Field dead = 0						'set to 1 when dead, 0 when alive

	Method setClass()
		class = "ship"
	EndMethod
	
	'call AFTER we've set the base chassis of the ship
	Method init(addToShipList = True)
		setClass()
		placeholder = New Gertrude
		
		'movement mechanics
		invulnerable = base.invulnerable
		If base.stationary
			ignorePhysics = True
			ignoreMovement = True
		EndIf
		
		'colored box on minimap / icons
		minimap = base.minimap
			
		'color
		RGB[0] = 255
		RGB[1] = 255
		RGB[2] = 255
			
		'set the color of the debris, explosions
		debrisRGB[0] = 255
		debrisRGB[1] = 255
		debrisRGB[2] = 255
						
		'death sfx
		'If base.explodeSound <> Null Then explodeSound = base.explodeSound Else explodeSound = getSound("explode_ship.wav")

		'-FIDDLY BITS-
		recalc_cshape()															'set the shape data, what's filled up	
		recalc_components()														'set what's powered, connected to what
		
		controller = New Control
		'controller.slot = createScript(behavior)	'behavior doubles as script name -> NOW HAPPENS IN HOSTLOOP
		
		'-ADDS TO SHIPLIST, MAKES CONTROLLER, INITIALIZES SPRITE-
		reset(addToShipList)														'that new-car-smell
		'Print "  initialized ship "+name + " '"+config+"'"
	EndMethod

	'general properties
	Method update()
	
		'update the gertrude placeholder
		If placeholder <> Null
			'get the amount of juice used up by the guns
			barrier:- placeholder.juiceUse
			'juice:- placeholder.juiceUse
			placeholder.juiceUse = 0
			
			'get how much we've overheated due to the guns
			If placeholder.overheat > 0 Then overheat(placeholder.overheat)
			placeholder.overheat = 0

			'set all other stats
			If placeholder.idnum = 0 Then placeholder.idnum = getIDNUM()
			placeholder.name = name
			placeholder.config = config
			placeholder.behavior = behavior
			placeholder.faction = faction
			placeholder.x = x
			placeholder.y = y
			placeholder.wid = wid
			placeholder.het = het
			placeholder.rot = rot
			placeholder.movrot = movrot
			placeholder.speed = speed
			placeholder.juice = juice
			placeholder.barrier = barrier
			placeholder.damageBuffer = damageBuffer
			placeholder.dead = dead
			placeholder.cloaked = abilityGetVar("", "cloaked")'abilityOn("Cloak") Or abilityOn("Blink")
			placeholder.ord = base.ord
			
			'picks up any forces FROM the placeholder and applies them to self
			If Not ListIsEmpty(placeholder.forceList)
				For Local f:Force = EachIn placeholder.forceList
					forceList.addLast(f)
				Next
				ClearList placeholder.forceList
			EndIf
		EndIf
		
		'default to not animated
		SetGNetInt(netSprite, NET_SPRITE_ANIMATED, 0)
		
		'bound armour & barrier amount
		armour = constrain(armour, 0, stat.armour)
		If invulnerable Then armour = stat.armour
		barrier = constrain(barrier, 0, stat.barrier)
		
		'did we gain/lose a damage buffer?
		If base.emitFX And alpha > 0
			If damageBuffer_old = 0 And damageBuffer > 0
				For Local i = 0 To 1
					'get a shield graphic
					Local e:Emitter = new_emitter("shield")
					e.onscreenOverride = 1
					If base.bio
						e.clearGFX()
						e.addGFX("spikershell.png")
						e.alpha = 1
						e.scale = 1
						e.trail = 4
						e.blend = "alpha"
					EndIf
					e.interval = frameTime/2
					e.num = -1
					e.attach(Self)
					e.upload()
				Next
			ElseIf damageBuffer_old > 0 And damageBuffer = 0
				'lose the shield graphic
				Local e:Emitter = new_emitter("shield")
				e.num = 0'no mas
				e.attach(Self)
				e.upload(0, 0, 1)'replace the old shield
			EndIf
		EndIf
		
		'is there an active damage buffer? (set to true if anything adds to the buffer)
		damageBuffer_old = damageBuffer
		If Not activeDamageBuffer Then damageBuffer = 0
		activeDamageBuffer = False
		
		'bound points amount
		'points = constrain(points, 0, stat.points)
		
		'if something locked our weapons/abilities, well, it'll just have to keep doing that to keep us down
		weaponsLocked = 0
		abilityLocked = 0
		
		'update the special ability
		For Local a:Ability = EachIn abilityList
			a.update(Self)
		Next
		
		'update all weapons
		For Local group = 0 To 1
			For Local g:Gun = EachIn gunGroup[group]
				'class/base update
				g.update()
				'weapon-specific update
				If Not g.skipUpdate
					LuaRegisterObject Self, "ship"
					LuaRegisterObject g, "gun"
					gunScript.call(g.scriptslot, "update")
				EndIf
			Next
		Next
										
		'regenerate juice
		'If Not afterburn Then juice = constrain(juice + (timePass/1000.0), 0, stat.juice)
		'turn overflow points into healing
		'If juice = stat.juice And armour < stat.armour And abilityHave("Overflow")
		'	juice:- POINT_JUICECHARGE
		'	repair(abilityGetVar("Overflow", "pointhealth"))
		'EndIf
		
		'lose dead and cloaked targets
		'If controller.target <> Null And (controller.target.dead Or controller.target.abilityOn("Cloak")) Then controller.target = Null
		If controller.target <> Null And controller.target.dead Then controller.target = Null
		
		'resolve the ship's thrust, add the actual force
		resolve_thrust()
		
		'current max speed; a larger throttle can push us over normal limit
		Local thrustSpeed# = stat.maxSpeed * Max(1.0, throttle)
		
		'did we just start/stop thrusting?
		Rem
		If base.emitFX And behavior = "player"
			If throttle_old = 0 And throttle <> 0
				Local enginecount
				For Local c:Component = EachIn compList
					If c.class = "engine" And Not c.addon
						'offset due to component placement
						Local ox = (c.x - (cshapedim-1)/2)*cshaperatio*scale
						Local oy = (c.y - (cshapedim-1)/2)*cshaperatio*scale
						
						'get a thrust graphic
						'Local e:Emitter = new_emitter("trail_exhaust")
						'e.x = ox
						'e.y = oy
						'e.attach(Self)
						'e.upload(0, 0, 1)'replace the old thrust...?
						'e.upload()
						
						'For Local i = 0 To 0'3
							Local e:Emitter = new_emitter("thrust")
							e.markerID = enginecount
							e.x = ox
							e.y = oy
							e.active = True
							e.attach(Self)
							'e.upload(0, 0, 1)'replace the old thrust...?
							e.upload()
					'	Next
						Print "ENGINE GFX " + enginecount + "   ox " + ox + " oy " + oy
						enginecount:+ 1
					EndIf
				Next
				
			ElseIf throttle_old <> 0 And throttle = 0			
				'lose the thrust graphic
				'Local e:Emitter = new_emitter("trail_exhaust")
				'e.num = 0'no mas
				'e.attach(Self)
				'e.upload(0, 0, 1)'replace the old thrust
				
				Local e:Emitter = new_emitter("thrust")
				e.markerID = -1'wild card
				e.active = False
				e.attach(Self)
				e.upload(0, 0, 1)'replace the old thrust
			EndIf
		EndIf
		EndRem
		
		'did we just start/stop afterburning?
		If base.emitFX
			If Not afterburn_old And afterburn
				'get a thrust graphic
				Local e:Emitter = new_emitter("trail_afterburn")
				e.attach(Self)
				e.upload()
				
				'e = new_emitter("thrust")
				'e.scale = 2
				'e.attach(Self)
				'e.upload(0, 0, 1)'replace the old thrust...?
				
			ElseIf afterburn_old And Not afterburn
				'lose the thrust graphic
				Local e:Emitter = new_emitter("trail_afterburn")
				e.num = 0'no mas
				e.attach(Self)
				e.upload(0, 0, 1)'replace the old thrust
			EndIf
		EndIf

		'default to not afterburning (or whatever the controller says to be)
		afterburn_old = afterburn
		afterburn = controller.afterburn
		
		'when not actively accelerating, decels to 50% of max speed
		Local decelSpeed# = (stat.maxSpeed * .5)
		
		'the closer we are to our current max speed, the harder the decel force
		Local decel# = Max(0, (speed - decelSpeed) / (thrustSpeed - decelSpeed)) * .4
		
		'decelerate
		If decel > 0 And (throttle >= 1 Or speed > thrustSpeed*.7) Then add_force(movrot+180, decel*mass)
		
		'reset throttle to 0, default direction of thrust to forward
		throttle_old = throttle
		throttle = 0
		thrust_theta = rot
		
		'OVERHEAT
		'count down the overheat timer (COOLANT ability accelerates this)
		timerTick(timerList, "overheat")
		If Not timerDinged(timerList, "overheat")	'if we're overheated
			barrier = 0
		
		ElseIf stat.barrier > 0
			'BARRIER RECHARGE
			'reset the timer if we fire weapons (now handled by gun objects, if they actually fire)
			'If controller.fire1 Or controller.fire2 Then timerSet(timerList, "barrierTimer")
			
			'if it's ticked down, start recharging (as long as we're not overheated)
			timerTick(timerList, "barrierTimer")
			If timerDinged(timerList, "barrierTimer")
				'do we need to recharge/regenerate?
				If barrier < stat.barrier
					'per ms, what percent recharges?
					Local barcharge# = stat.barrier * (BARRIER_SPEED / 100) * timePass
					repairBarrier(barcharge)
				EndIf
			EndIf
		EndIf
		
		'count down the backstab timer
		If base.backstabbable Then timerTick(timerList, "backstab_delay", backstab_delay)
		
		'POINT HEALING
		Rem
		'reset the timer if we press any buttons
		If controller.fire1 Or controller.fire2 Or controller.ability Then timerSet(timerList, "healTimer")
		'If controller.thrust Or controller.reverse Or controller.strafeleft Or controller.straferight Or controller.reverse
		'	timerSet(timerList, "healTimer")
		'EndIf
		
		'if it's ticked down, start healing
		If timerTick(timerList, "healTimer")
			'do we need to heal?
			If armour < stat.armour
				'can we heal?
				If points > 0
					If points > HEAL_SPEED
						addPoints(-HEAL_SPEED)
						repair(HEAL_SPEED/3)
					EndIf
				EndIf
			EndIf
		EndIf
		EndRem
	
		'labels, hit tween, sparks, etc.
		updateEffects()
		
		'keep rot b/t 0-359
		If rot < 0 Then rot = rot + 360
		If rot >= 360 Then rot = rot - 360
		If movrot < 0 Then movrot = movrot + 360
		If movrot >= 360 Then movrot = movrot - 360
		
		'physical laws of THIS universe have to do with sound, OK?             -->  ...what? 
		If collide_sfx_countdown > 0 Then collide_sfx_countdown:- timePass
				
		'damaged gfx
		Rem
		If base.material = MATTER_METAL Or base.material = MATTER_ROCK'explode or rock debris
			If onscreen
				Local damageTween# = armour / armourMax
				If damageTween < .5
					For Local e = 1 To explodeNum
						If Rand(1,500*damageTween+100) = 1
							Local rad = SHIPSCALE*(ImageWidth(gfx[0]) + ImageHeight(gfx[0]))/6
							Local ex:Foreground = add_explode(x+Rand(-rad,rad),y+Rand(-rad,rad))
							If base.material = MATTER_ROCK'rock
								ex.gfx = load_image(GFX_EFFECT_PATH+"dust.png")
								ex.alphaspeed:/ 4
								ex.lifetimer = 5000
								ex.animated = False
							EndIf
							ex.RGB[0] = debrisRGB[0]
							ex.RGB[1] = debrisRGB[1]
							ex.RGB[2] = debrisRGB[2]
						EndIf
					Next
				EndIf
			EndIf
		EndIf
		EndRem
		
		status = New StatBonus'clear the temporary stats
		
		'---DESTROYED
		If armour <= 0 Then death()
	EndMethod
	
	Method updateEffects()
		'decay hit tween back towards none
		If hitTween <> 0 Then hitTween:- (hitTween*8) * (timePass/1000.0)
		If hitTween < .05 Then hitTween = 0
		
		'WEAPON CHARGE: flash the ship if charging a weapon
		For Local group = 0 To 1
			For Local g:Gun = EachIn gunGroup[group]
				Local charge# = g.vars.get("charge")
				If charge > 0
					If charge >= .95 And hitTween <= .05		'full charge
						hitTween = .5
					ElseIf globalFrameSwitch
						If charge >= .6							'mid charge
							hitTween = .3
						ElseIf charge >= .05 And globalFrame	'low charge
							hitTween = .2
						EndIf
					EndIf
				EndIf
			Next
		Next

		'DRUNK: set hittween negative -- drawsprite will interpret this as drunk
		If emp_timer > 0 Then hitTween = -1
		Rem
		'ELECTRICAL CHARGE: sparks and damage
		Local eemit:Emitter
		Local echarge = constrain((emp_timer / 500), 0, 1)
		Local idnum = getIDNUM()
		If echarge > 0		'if we're charged at all
			'full charge: turn on the huge sparks
			If echarge > .5
				'Print "--- HUGE SPARKS ---"
				eemit = new_emitter("sparks")
				eemit.scale:* 3
				eemit.setRGB(255,200,200)
				eemit.upload(0, idnum, 1)
				esparks = 3
			'only dangerous sparks
			ElseIf echarge > .25
				'Print "--- med SPARKS ---"
				eemit = new_emitter("sparks")
				eemit.scale:* 2
				eemit.upload(0, idnum, 1)
				esparks = 2
			Else
				'Print "--- small SPARKS ---"
				eemit = new_emitter("sparks")
				eemit.upload(0, idnum, 1)
				esparks = 1
			EndIf
		EndIf
		EndRem
		
		'count down the emp timer
		If emp_timer > 0
			emp_timer:- timePass
			
			'big things recover faster
			If mass > 100 Then emp_timer:- (timePass*.5)
		
			Rem
			If emp_timer<= 0
				'turn off the electricity
				'Print "--- no SPARKS ---"
				eemit = new_emitter("sparks")
				eemit.active = 0
				eemit.upload(0, idnum, 1)
			EndIf
			EndRem
		EndIf
		
		'RECENT DAMAGE : count down recent damage timer
		If recent_damage_timer > 0 Then recent_damage_timer:- timePass
		If recent_damage_timer <= 0 Then recent_damage = 0
		'update the recent damage text sprite
		Local dam:Sprite = recent_damage_sprite
		Local show_damage = False
		'If recent_damage > 0 And (stat.armour >= 12 Or behavior = "player") Then show_damage = True
		If recent_damage > 0 And (stat.armour >= 10 Or (factionsAreAllies(faction, 1) And Not base.ord)) Then show_damage = True
		If show_damage And recent_damage_sprite <> Null' And Not base.bio'no damage showing for the wee beasties
			dam.skipDraw = 0
			dam.RGB[0] = 200
			dam.RGB[1] = 0
			dam.RGB[2] = 0
			'only show the damage for half of the time that we track it
			If recent_damage_timer > (recent_damage_delay/2) + 150
				dam.alpha = 1
			Else
				dam.alpha = (recent_damage_timer - (recent_damage_delay/2)) / 150
				If dam.alpha < 0 Then dam.alpha = 0
			EndIf
			'dam.text = "-" + Int(Ceil(recent_damage))'Int(ceil(armour))'
			dam.text = "-" + Int(Ceil(recent_damage))
			
			If label = Null
				label = New Sprite
				label.ignorePhysics = True
				label.rot = 0
				label.alpha = .5
				label.initNetSprite("", 0, False, 6)
				label.link = hostSpriteList.addLast(label)
			EndIf
			
			dam.scale = 1.2
			Local rad = Min(wid, het) * 1.3 * hitScale
			dam.x = x+Cos(recent_damage_theta-25)*rad - ((TextWidth(dam.text)/2)*dam.scale)
			dam.y = y+Sin(recent_damage_theta-25)*rad - ((TextHeight(dam.text)/2)*dam.scale)
		Else
			dam.skipDraw = 1
		EndIf
		
		'a warning effect
		If controller.vars.get("caution") = 1
			label = New Sprite
			label.ignorePhysics = True
			label.rot = -90
			label.alpha = .5
			label.initNetSprite("caution.png", NET_SPRITE_GFXTYPE_EFFECT, True, 6)
			label.link = hostSpriteList.addLast(label)
			controller.vars.set("caution", 2)
		EndIf
		
		'LABEL : position the ship's label
		If label <> Null And behavior <> "player"
			If controller.vars.get("caution") = 2
				label.x = x
				label.y = y
				
			ElseIf Not invulnerable'default; armour value
				'label.text = Int(Ceil(armour)) + " / " + Int(Ceil(stat.armour))
				Local blipnum = 4
				label.text = "["
				For Local blip = 1 To blipnum
					If blip <= Ceil((armour/stat.armour) * blipnum)
						label.text:+ "*"
					Else
						label.text:+ " "
					EndIf
				Next
				label.text:+ "]"
				label.x = x - TextWidth(label.text)/2
				label.y = y + Max(wid, het)*.8
				label.alpha = dam.alpha
				label.setRGB(180,180,180)
				'label.skipDraw = dam.skipDraw
			EndIf
		EndIf
		
		'count down recent point timer
		If recent_points_timer > 0 Then recent_points_timer:- timePass
		
		If recent_points_timer <= 0
			recent_points = 0
		ElseIf recent_points_timer <= 4000'count down the recent points
			recent_points = constrain(recent_points - 1, 0, recent_points)
		EndIf
		
		'if we faded, fade back
		'fadeAlpha = 1
	EndMethod
	
	'what happens once it's destroyed
	Method death()
		'Print "============================== I BE DEAD:"+dead
		
		'if have an ankh, burst of energy that cancels the death
		If abilityHave("Ankh")
		
			'make a divine shockwave
			For Local g:Gun = EachIn gunGroup[1]
				If Lower(g.name) = "ankh" Then g.fire(x, y, True)'force-fire the gun
			Next
			
			'heal the ship slightly
			repair(stat.armour)
		
			'give a temporary shield (player-only)
			timerSet(controller.timerList, "gibshield_active", 9000)
			
			'small message
			If behavior = "player" Then new_shakytext("Ankh broken!")', x, y+20)
		
			'remove the component from the ship
			For Local c:Component = EachIn compList
				If c.abilityName = "Ankh"
					remove_comp(c)
					Exit
				EndIf
			Next
			
			'remove the ability from the ship
			For Local a:Ability = EachIn abilityList
				If a.name = "Ankh"
					ListRemove(abilityList, a)
					Exit
				EndIf
			Next

			'cancel the death
			Return False
		EndIf
		
		If dropOnDeath
			Local debrisList:TList = New TList
			If abilityHave("Illusory")
				Local e:Emitter = new_emitter("warpblast")				'make an illusory explosion
				e.scale = 1
				e.lifetimer = 400
				debrisList.addLast(e)
			Else
				Select base.material
				Case MATTER_METAL
					debrisList.addLast(new_emitter("shipexplode"))		'make a ship explosion
					debrisList.addLast(new_emitter("shipexplode_wide"))	'make a ship explosion
					debrisList.addLast(new_emitter("shipexplode_slow"))	'make a ship explosion
					debrisList.addLast(new_emitter("shipexplode_smoke"))'make a ship explosion
					If Not base.ord Then debrisList.addLast(new_emitter("shipexplode_metal"))
					
					'players explode bigger
					If behavior = "player"
						For Local i = 0 To 6
							debrisList.addLast(new_emitter("shipexplode"))		'make a ship explosion
							debrisList.addLast(new_emitter("shipexplode_slow"))	'make a ship explosion
							debrisList.addLast(new_emitter("shipexplode_smoke"))'make a ship explosion
						Next
					EndIf
				Case MATTER_ROCK
					debrisList.addLast(new_emitter("rockexplode"))		'make a rock explosion
					debrisList.addLast(new_emitter("dustexplode"))		'make a dust poof
					debrisList.addLast(new_emitter("dustexplode_slow"))	'make a slow dust poof
				Case MATTER_BIO
					debrisList.addLast(new_emitter("gib"))		'make a gib explosion
					debrisList.addLast(new_emitter("gib_slow"))	'make a slow dust poof
				Case MATTER_VECTOR
					debrisList.addLast(new_emitter("shipexplode_vector"))		'make a vector explosion
				Case MATTER_ENERGY
					debrisList.addLast(new_emitter("warpblast_small"))			'make an energy explosion
				Default
					debrisList.addLast(new_emitter("shipexplode"))		'make a ship explosion
				EndSelect
			EndIf
			For Local e:Emitter = EachIn debrisList
				'e.RGB[0] = debrisRGB[0]
				'e.RGB[1] = debrisRGB[1]
				'e.RGB[2] = debrisRGB[2]
				e.y = y
				e.x = x
				e.num = e.num * Ceil( mass / 25 )
				'e.camshake = base.explodeShake
				e.upload()
			Next
			
			
			Local skipPointDrop = False
			If base.material = MATTER_VECTOR Then skipPointDrop = True
			'drop items (skip points?)
			dropItems(skipPointDrop)
				
			'drop any ships in our droplist
			For Local s:Ship = EachIn dropList
				If s.behavior = "player"
					s.movrot = 90
					If Not s.base.material = MATTER_VECTOR Then s.speed = s.stat.maxSpeed * (1 + (Rnd()/2))'(.75 + (Rnd()/2))
					'store the number of points they have
					Local pnts = s.points
					Local hp = s.armour
					'reset the ship and place it
					s.x = x
					s.y = y
					s.reset(True)
					'restore the status
					s.points = pnts
					s.armour = hp
				Else
					'use their current coordinates as offsets
					Local offx, offy
					If s.x = 0 Then offx = Rand(-wid/2,wid/2) Else offx = s.x
					If s.y = 0 Then offy = Rand(-het/2,het/2) Else offy = s.y
					'reset the ship and place it
					s.reset(True)
					s.x = x + offx
					s.y = y + offy
				
					'move direction according to the offset
					s.movrot = ATan2((s.y - y),(s.x - x))
					s.speed = Max(300, s.stat.maxSpeed * (2 + (Rnd()/2)))'(.75 + (Rnd()/2))
					s.spin = spin*(Rnd()-.5)*2.5
					'If s.controller.vars.get("inert") = 1 Then s.spin = Rnd() * s.mass * .15
					'Print s.name + " spin " + s.spin
				EndIf
				
				s.faction = faction
				
				'add all the other ships in the droplist to ignorelist so they don't all just explode
				'(lack of friendly fire fixes this) (but collision problems necessitate it)
				For Local o:Ship = EachIn dropList
					s.ignoreList.addLast(o)
				Next
			Next
			
			
		Else
			'kill all ships in droplist
			For Local s:Ship = EachIn dropList
				s.remove()
			Next
		EndIf

		'if there's recent damage (likely), transfer that to independent floating text
		recent_damage_sprite.lifetimer = 1100
		recent_damage_sprite.movrot = 270
		recent_damage_sprite.speed = 50
		'recent_damage_sprite.scale = 2
		'recent_damage_sprite.RGB[0] = 200
		'recent_damage_sprite.RGB[1] = 0
		'recent_damage_sprite.RGB[2] = 0
		
		'kill the label
		If label <> Null
			label.skipDraw = 1
			label.lifetimer = 1
			label.text = ""
		EndIf
		
		'SHIP EXPLOSIONS : bump everything nearby away
		If base.material = MATTER_METAL
			Local radius# = 350'constrain(mass * 10, 100, 350)
			For Local o:Ship = EachIn shipList
				If (o <> Self)
					Local dx# = x - o.x
					Local dy# = y - o.y
					Local dist# = approxDist(dx, dy)
					If dist <= radius
						
						Local force# = constrain(((1 - (dist / radius)) * mass) * 200, 1000, 6000)
						Local angle# = ATan2(dy,dx)
		
						o.bump(angle, force)
					EndIf
				EndIf
			Next
		EndIf

		If behavior <> "player"
			If base.explodeSound <> Null Then playLocationalSFX(base.explodeSound, splodeChannel, x, y)
		Else
			If base.material <> MATTER_VECTOR
				playLocationalSFX(getSound("explode_player.wav"), splodeChannel, x, y)
			Else
				playLocationalSFX(getSound("explode_vector1.wav"), splodeChannel, x, y)
			EndIf
		EndIf
		
		'ACHIEVEMENTS:
		'count it as a kill?
		If base.material = MATTER_METAL And factionsAreEnemies(1, faction)
			steamAchievement("kills", 1)
			'enemy killed by an asteroid?
			If placeholder.nemesis = "Asteroid10" Then steamAchievement("kill_asteroid", 1)
		EndIf
			
		'player killed by gate?
		If behavior = "player" And placeholder.nemesis = "GateEdge" Then steamAchievement("death_docking")
		
		'If engineChannel <> Null Then StopChannel(engineChannel)

		'become one with nothing
		remove()
	EndMethod
	
	'make the ship disappear quickly, quietly, and cleanly
	Method remove()
		'be dead, for anyone who still cares (placeholder, controller)
		dead = 1
		placeholder.dead = 1
		
		'clear ALL potential pointers that could result in infinite loops
		controller.target = Null
		ClearList(dropList)
		ClearList(abilityList)
				
		'clean up other bits
		If recent_damage_sprite <> Null
			recent_damage_sprite.lifetimer = 1000
			recent_damage_sprite = Null
		EndIf
		If label <> Null
			label.lifetimer = 1000
			label = Null
		EndIf
		For Local i = 0 To 1
			For Local g:Gun = EachIn gunGroup[i]
				g.remove()
			Next
		Next
		'base = Null
		'explodeSound = Null
		'ClearList compList
		'If engineChannel <> Null Then StopChannel(engineChannel)
		
		'the entity class handles the rest of the cleanup
		Super.remove()
		'Print "removed " + name
		'ListRemove(shipList,Self)
	EndMethod
	
	
	'turns the ship towards a target angle (if not absolute, turns towards that angle relative to it's own facing)
	Method turn(_angle#, _absolute = 1, _rate = 0)'_rate is an optional override for the turnspeed
		If emp_timer <= 0
			'SetColor 255,0,0
			'DrawText Int(_angle),x+game.camx,y+game.camy+20
	
			Local rate# = stat.turnRate + status.turnRate
			If _rate > 0 Then rate = _rate
	
			'turn slower when afterburning
			If afterburn And base.class = "fighter" Then rate = rate * .25
		
			'if we're turning towards an absolute angle, convert to a relative angle 
			If _absolute = 1 Then _angle = convert_to_relative(_angle, rot)
			
			Local turnAmt# = rate * (timePass/1000.0) * Sgn(_angle)
			Local threshold# = 1'30'degrees left to turn where we start slowing down
	
			'if we don't need to turn thaaaat much....
			If Abs(_angle) <= threshold
				'turn a little slower
				turnAmt:* constrain(Abs(_angle / threshold), .1, 1)
			EndIf
			
			'if we need to turn more than we will this loop
			If Abs(_angle) >= Abs(turnAmt)
				'turn at speed
				rot:+ turnAmt
			Else
				'turn just enough to finish it
				rot:+ _angle
			EndIf
		EndIf
	EndMethod
	
	'adds a direction of thrust to the ship
	Method add_thrust(_angle#,_throttle# = 1, _absolute = False)
		'_angle : what angle relative to the ship to thrust
		'_throttle : how much to modify the ship's maxiumum speed by
		'_absolute : whether given _angle is relative to ship (TRUE) or absolute coords (FALSE)
		
		'find the absolute angle of thrust
		If Not _absolute Then _angle:+ rot
		
		'a higher-than-1.0 throttle tells us to afterburn
		If _throttle > 1 Then afterburn = True
		
		'if we're going sideways, strafe limits take over
		'Local limit# = 1
		'limit = Max(Abs(Cos((rot - _angle))), stat.strafe)
		
		'constrain the throttle
		'_throttle = constrain(_throttle, 0, limit)

		'see what direction we're currently trying to move
		Local tx# = Cos(thrust_theta) * throttle
		Local ty# = Sin(thrust_theta) * throttle
				
		'see what direction we're adding now
		Local ax# = Cos(_angle) * _throttle
		Local ay# = Sin(_angle) * _throttle
		
		'add em together
		thrust_theta = ATan2(ty+ay,tx+ax)
		throttle = Max(throttle, _throttle)
	EndMethod
	
	'after all the add_thrusts have been added up, find what direction we finally decided on
	Method resolve_thrust()
		If stat.thrust > 0 And throttle > 0 And emp_timer <= 0
		
			'can't afterburn if overheated
			If Not timerDinged(timerList, "overheat") Then afterburn = False

			'- AFTERBURN -
			If afterburn
				'use up juice when afterburning
				'juice:- (timePass/1000.0)
				'If juice < 0 Then juice = 0
				
				'barrier:- AFTERBURN_COST'(timePass/1000.0)
				'If barrier < 0 Then barrier = 0
				
				'reset regen timer
				'timerSet(timerList, "barrierTimer")
				
				'can accel to near max speed quickly
				If speed <= (stat.maxSpeed*.3) Then throttle = 5.5 Else throttle = 1.85
				
				If behavior = "player"
					'shake the screen
					If cam.shake <= 0 Then cam.shake = Max(cam.shake, throttle*RndFloat()/20)
					'reduce camera offset due to mouse
					'game.camoffsetmod = 2.0
					'zoom in a little
					cam.zoomtarget = -.015
				EndIf
				'EndIf
				
				'kickstarter?
				activateAbility(404,-404,"Kickstarter")'kickstarter needs a special code to know it's us and not a player activation
				If abilityOn("Kickstarter")
					throttle = 6.0
				EndIf
				
				'can only afterburn straight forward
				thrust_theta = rot			
			EndIf
			
			'strafe limits take over for going sideways
			Local limit# = Abs(Cos(thrust_theta - rot))
			limit = Max(stat.strafe, limit)
			
			'find how fast we're going in the desired direction
			'Local currentspeed# = speed * Cos(thrust_theta)
			Local v_para# = Cos(movrot - thrust_theta) * speed
			'Local v_perp# = Sin(movrot - thrust_theta) * speed
			
			'limit:* constrain(1 - (v_para / stat.maxSpeed), 0, 1)

			'If speed < (stat.maxSpeed*throttle * Max(1.0, Sqr(throttle)))
			'If speed < (stat.maxSpeed * Max(1.0, Sqr(throttle)))
			If v_para < (stat.maxSpeed * limit * Max(1, throttle))
				'add the pushing force! (ignores mass of ship; larger ships have bigger thrusters, OK???)
				'add_force(thrust_theta, stat.thrust * throttle * mass)
				add_force(thrust_theta, stat.thrust * mass * Max(1, throttle))
				'add_force(thrust_theta, stat.thrust * mass * limit)
			EndIf
		EndIf
		
		'animate engines!
		Rem
		'If base.animated Then SetGNetInt(netSprite, NET_SPRITE_ANIMATED, 1)
		If throttle_old = 0 And throttle > 0
			'turn on engines
			Local thrust:Emitter = new_emitter("thrust")
			thrust.active = True
			thrust.attach(Self)
			thrust.upload(0, 0, 1)
		ElseIf throttle_old > 0 And throttle = 0
			'turn off engines
			Local thrust:Emitter = new_emitter("thrust")
			thrust.active = False
			thrust.attach(Self)
			thrust.upload(0, 0, 1)
		EndIf
		EndRem

	EndMethod	
	
	'this ship takes damage of a certain amount (from a certain direction)
	' if _collision = true, this is collision damage
	'returns TRUE if we did any armour damage (as opposed to just shield damage)
	Method damage(_amt#, _theta# = 0, _skipFortify = False, _skipShields = False, _collision = False, _tween = True, _crit# = 1.0)
		Local did_damage = False
		
		'change all incoming damage by the ship's current damage mod
		_amt:* damageMod
		
		'reduce collision damage for the player + survivors (75% of normal)
		If (behavior = "player" Or behavior = "survivor") And _collision Then _amt:* PLAYER_COLLISIONMOD
		
		Local shieldtween = False
		If Not invulnerable And _amt > 0
			If (_amt > damageBuffer Or _skipShields)
				
				'if have a cowcatcher and are afterburning, reduce damage
				If abilityHave("Cowcatcher") And afterburn Then _amt:* abilityGetVar("Cowcatcher", "DR")
				
				'BARRIER takes as much damage as it can
				If barrier > 0
					'Print name + " barriertimer : " + timerTime(timerList, "barrierTimer")
					timerBump(timerList, "barrierTimer", BARRIER_INTERRUPT)
				EndIf
				If Not _skipShields
					If barrier > _amt
						barrier:- _amt
						_amt = 0
						'make a shield effect 
						shieldtween = True
						'play a richochet sound
						playLocationalSFX(getSound("hit_reflect.wav"), reflectChannel, x, y, 1.0)
					Else
						If barrier > 0
							Print "barrier down!"
							'make a small blast effect
							Local e:Emitter = new_emitter("impact")
							e.alpha = .75
							e.scale = 1.5
							e.x = x
							e.y = y
							e.upload()
						EndIf
						If barrier > 0 Then shieldtween = True
						_amt:- barrier
						barrier = 0
					EndIf
				EndIf
				
				'if filled, take half collision damage
				If _collision And ListContains(shapeBonusList, "filled") Then _amt:*.5
				
				If Not _skipFortify'(if enough to reflect it, already happened in collisions.bmx)
					'is this ship backstabbable? (i rock at logic here, work it out!)       (wait this makes no sens--- ohhhh yeah ok I get it)
					If Not base.backstabbable Or timerDinged(timerList, "backstab_delay")
						'fortifications reduce all damage in their arc (or increase damage due to backstab)
						Local fortmod# = fortified(_theta, _collision)
						If fortmod <> 0
							'Print "fortmod : " + fortmod
							'if take MORE damage, say so & reset the backstab timer
							If fortmod < 0
								timerSet(timerList, "backstab_delay")
								new_text("Crit!", x, y-20, 1500)'hehe the accidental backstab from the front now is a feature instead of bug
								'make a small blast effect
								Local e:Emitter = new_emitter("impact")
								e.x = x
								e.y = y
								e.upload()
								
								'apply the given crit mod
								fortmod:* _crit
								
								playLocationalSFX(getSound("hit_crit.wav"), Null, x, y, 1.0)
							EndIf
							_amt:- fortmod
						EndIf
					EndIf
				EndIf
				
				'if there's anything left
				If _amt > 0
					If _amt > .15 Then playLocationalSFX(getSound("hit_misc.wav"), hitChannel, x, y, .5)
				
					'adds up the recent damage
					recent_damage:+ _amt
					'reset the recent damage time
					recent_damage_timer = recent_damage_delay
					'record where it came from
					recent_damage_theta = _theta
				
					'armour takes the damage
					armour = armour - _amt
					
					'tween the ship a bit
					If _tween Then hitTween = 1
					'player damage shakes camera
					If behavior = "player" Then cam.shake = Max(cam.shake, .5 + (RndFloat()/2))
					did_damage = True
				EndIf
				
			Else'if the damage buffer takes care of it
				shieldtween = True
			EndIf
			
			'make a blur effect
			If shieldtween
				If base.emitFX
					Local barrier:Emitter = new_emitter("shield")
					'If behavior <> "player"
						barrier.clearGFX()
						barrier.addGFX("shield2.png")
					'EndIf
					
					barrier.speed = speed
					barrier.movrot = movrot
					
					barrier.num = 7
					barrier.lifetimer:* 2
					barrier.rot = _theta + 180
					If _collision Then barrier.rot:- 180
					barrier.attach(Self)
					barrier.upload()
				EndIf
			EndIf
		EndIf
		Return did_damage
	End Method
	
	'returns the current amount fortification from an (absoulute) angle
	Method fortified#(_theta#, _collision = False)
		Local fort#
		For Local f:Fortification = EachIn fortifyList  
			'only block the correct type of damage
			'Print "fort? collision : " + _collision + " and fort does collide:"+f.collision+" normal:"+f.normal
			Local block = False
			If _collision And f.collision Then block = True
			If Not _collision And f.normal Then block = True
			
			'Local block = True
			'If Not f.normal And (Not _collision Or Not f.collision) Then block = False'<-
			'If f.normal And (_collision And Not f.collision) Then block = False'<- I had to graph this logic out. good luck re-figuring it.
			If block
				'Print "checking fort:  "+Abs(convert_to_relative(_theta, rot))+"  "+f.radius
				
				'only block damage that comes in this fortification's radius
				Local forttheta = 0
				If f.reverse Then forttheta = 180
				If Abs(convert_to_relative(_theta, rot+forttheta)) > (180 - f.radius)
					fort:+ f.DR
				EndIf
			EndIf
		Next
		Return fort
	EndMethod
	
	'restore some certain amount hull damage
	Method repair(_amt#)
		armour = constrain(armour + _amt, 0, stat.armour)
	End Method
	
	'restore/regenerate some amount of barrier
	Method repairBarrier(_amt#)
		barrier = constrain(barrier + _amt, 0, stat.barrier)
	End Method
	
	'overheats the ship for some amount of time
	Method overheat(time#)
		Local old_time = timerTime(timerList, "overheat")

		'reduce how much it can stack ( if overheat the same amount as current, only add half that much )
		Local reduction# = 1.0
		'If old_time > 0 Then reduction = constrain(1 - (time / (old_time*2)), .25, 1.0)
		If abilityHave("Coolant") > 0 Then reduction:/ (abilityHave("Coolant") + 1)
		time:* reduction
			
		If old_time + time <= 8000'cap overheat at 10s
			timerBump(timerList, "overheat", old_time + time)
		Else
			timerBump(timerList, "overheat", 8000)
		EndIf
	EndMethod
	
	'stuns the ship for some amount of time
	Method EMP(time# = 1000)
		emp_resistance:+ time
		'as resistance builds up, just overheats things instead of overheat + emping them
		
		emp_timer:*.5
		emp_timer:+ time
		'emp_timer:+ time' * (time / emp_resistance)
		overheat(time)
		
		If behavior = "player" Then Print "  PLAY EMP TIMER " + emp_timer
	EndMethod
	
	'electrically charges the ship (in % / sec)
	'Method charge(_speed#)
	'	If base.material <> MATTER_ROCK Then echarge:+ _speed * (timePass / 1000)
	'EndMethod
	
	'activate ALL abilities!!! (if no name is provided)
	Method activateAbility(tarx = 0, tary = 0, _abilityName$ = "", startingLayer = 0)
		Local success = False
		For Local l = startingLayer To ABILITY_MAXLAYER
			For Local a:Ability = EachIn abilityList
				If a.layer = l
					If _abilityName = "" Or a.name = _abilityName
						If Not abilityLocked
							If a.activate(Self, tarx, tary)		'activate the ability
								'If behavior = "player" Then Print "activating ability " + a.name + " tx " + tarx + "   ty " + tary
								success = True
							EndIf
						EndIf
					EndIf
				EndIf
			Next
		Next
		Return success
	EndMethod
	
	'returns TRUE if we have this ability // actually returns the number of times we have this ability
	Method abilityHave(_abilityName$)
		Local count = 0
		For Local a:Ability = EachIn abilityList
			If a.name = _abilityName Then count:+ 1
		Next
		Return count
	EndMethod

	'returns TRUE if this ship has this ability and it's on
	Method abilityOn(_abilityName$)
		For Local a:Ability = EachIn abilityList
			If a.name = _abilityName And (a.on Or a.passive) Then Return 1
		Next
		Return 0'we couldn't find it // it's not on
	EndMethod
	
	'returns an ability of the correct name
	Method abilityGet:Ability(_abilityName$)
		For Local a:Ability = EachIn abilityList
			If a.name = _abilityName Then Return a
		Next
		Return Null'we couldn't find it
	EndMethod
	
	'sets the timer of some ability to something
	Method abilitySetTimer(_abilityName$, _timer$, _value# = 0)
		For Local a:Ability = EachIn abilityList
			If _abilityName = "" Or a.name = _abilityName Then timerSet(a.timerList, _timer, _value)
		Next
	EndMethod
	
	'sets the var of some ability to something
	Method abilitySetVar(_abilityName$, _var$, _value#)
		For Local a:Ability = EachIn abilityList
			If _abilityName = "" Or a.name = _abilityName Then a.vars.set(_var, _value)
		Next
	EndMethod
	
	'gets the var of some ability
	'OR, if no ability name is given, returns the first non-zero value of that var name
	Method abilityGetVar#(_abilityName$, _var$)
		For Local a:Ability = EachIn abilityList
			If a.name = _abilityName Then Return a.vars.get(_var)
			If _abilityName = "" And a.vars.get(_var) <> 0 Then Return a.vars.get(_var)
		Next
		Return 0
	EndMethod

	'adds some amount to the current damage buffer
	Method addDamageBuffer(buffer#)
		activeDamageBuffer = True
		damageBuffer = Max(damageBuffer, buffer)
	EndMethod
	
	'kills the trail coming from this ship
	Method loseTrail()
		Local e:Emitter = new_emitter("trail")
		e.num = 0'no more
		e.attach(Self)
		e.upload(0, 0, 1)'replace the old trail
	EndMethod
	
	'adds a ship placeholder to the ignoreList
	Method ignore(_ship:Gertrude)
		ignoreList.AddLast(_ship)
	EndMethod
	
	'green flourescent protein, duh.
	Method GFP(flag$)
		Print flag$
	EndMethod
	
	'adds a thrust effect to all engines
	Rem
	Method addThrustEffect()
		For Local c:Component = EachIn compList
			If c.class = "engine"
				'offset due to component placement
				Local ox = (c.x - (cshapedim-1)/2)*cshaperatio*scale
				Local oy = (c.y - (cshapedim-1)/2)*cshaperatio*scale
				
				'For Local i = 0 To 2
					Local e:Emitter = new_emitter("thrust")
					e.x = ox
					e.y = oy
					e.attach(Self)
					e.upload()
				'Next
			EndIf
		Next
	EndMethod
	EndRem
	
	'picks up a small energy recharge
	Method addPoints(count# = 1)
		'regenerate self instead?
		If abilityHave("Vampiric")
			If armour < stat.armour
				repair(.1*count)
			'Else
				'repairBarrier(.2*count)
			EndIf
		
		Else'adds some points to our counter
			points = points + count
		
			'this is a good thing
			'new_text("+1", x, y, 1500)
		EndIf
		
		'regenerate some juice
		'juice = constrain(juice + .15*count, 0, stat.juice)
		'barrier = constrain(barrier + BARRIER_POINTREGEN*count, 0, stat.barrier)
	EndMethod
	
	'attracts all nearby items
	Method attractItem(itemName$, maxDist = 0)
		If Not dead
			'can't attract points after we've reached our max
			'If Lower(itemName) = "point" And points >= stat.points Then Return 0
		
			'default max distance
			If maxDist = 0 Then maxDist = 330
		
			'attract them items!
			For Local i:Item = EachIn itemList
				'needs to exist for a short time first
				If i.existtimer > 100
					If Lower(i.name) = Lower(itemName)
						Local dist# = approxDist((x-i.x),(y-i.y))
						'have we contacted it?
						If dist <= Min(wid, het)*1.5
							'LuaRegisterObject Self, "ship"
							'LuaRegisterObject i, "item"
							'call the script
							'itemScript.call(i.scriptslot, "shipContact")
							
							Select Lower(i.name)
							Case "point"
								addPoints(1)
								playLocationalSFX(getSound("game_pointpickup.wav"), Null, i.x, i.y, 1)
							Case "heal"
								
								repair(1)
							Default
								LuaRegisterObject Self, "ship"
								LuaRegisterObject i, "item"
								'call the script
								itemScript.call(i.scriptslot, "shipContact")
							EndSelect
							
							i.remove()
	
						Else'try to pull it
							i.pull(x, y, maxDist, dist)
						EndIf
					EndIf
				EndIf
			Next
		EndIf
		
		Return 0
	EndMethod
	
	'drops all the ship's items
	Method dropItems(skipPoints = 0)
		'mooks, enemies with NON-POINT drops
		For Local drop$ = EachIn base.dropItemList
			If Lower(drop) <> "point"
				Local point:Item = createItem(drop, x+Rand(-wid/2,wid/2), y+Rand(-het/2,het/2))
				point.movrot = ATan2(point.y-y, point.x-x)'set movement angle by angle from ship
			EndIf
		Next
	
		If drop_points And Not skipPoints
			'mooks, enemies with natural point drops
			For Local drop$ = EachIn base.dropItemList
				If Lower(drop) = "point"
					For Local i = 1 To (dropMultiplier + 1)'uses a global variable for current multiplier level
						Local point:Item = createItem(drop, x+Rand(-wid/2,wid/2), y+Rand(-het/2,het/2))
						point.movrot = ATan2(point.y-y, point.x-x)'set movement angle by angle from ship
					Next
				EndIf
			Next
		
			'things that collect points (aka players) also drop those points
			points:- startingPoints
			For Local p = 1 To Min(points, 100)
				Local point:Item = createItem("point", x+Rand(-wid/2,wid/2), y+Rand(-het/2,het/2))
				point.movrot = ATan2(point.y-y, point.x-x)'set movement angle by angle from ship
			Next
		EndIf
	EndMethod
	
	'clears the drop list
	Method noItemDrop()
		drop_points = False
	EndMethod
	
	'removes all abilities from the ship
	Method removeAbilities()
		ClearList(abilityList)
	EndMethod
	
	'sets all weapon damage to some amount
	Method setWeaponDamage(dmg#)
		For Local group = 0 To 1
			For Local g:Gun = EachIn gunGroup[group]
				g.shotdamage = dmg
			Next
		Next
	EndMethod
	
	'sets all weapon knockback to some amount
	Method setWeaponKnockback(knock#)
		For Local group = 0 To 1
			For Local g:Gun = EachIn gunGroup[group]
				g.shotknockback = knock
			Next
		Next
	EndMethod

	'sets all weapon range to some amount
	Method setWeaponRange(range#)
		For Local group = 0 To 1
			For Local g:Gun = EachIn gunGroup[group]
				g.shotrange = range
			Next
		Next
	EndMethod


	'removes a certain weapons from the ship
	Method removeWeapon(weaponName$)
		For Local group = 0 To 1
			For Local g:Gun = EachIn gunGroup[group]
				If g.name = weaponName Then ListRemove(gunGroup[group], g)
			Next
		Next
	EndMethod
	
	'targets the closest other enemy ship (closest to given point, that is; defaults to ship's position)
	'viableTargets is the relationship we're going for : [ -1 = enemies | 0 = neutral | 1 = allies ]
	Method target_closest(_detectRange = 1000, _viableTargets = -1, _x=0, _y=0, _skipCloaked = True)
		'Print "TARGETING CLOSEST"
		If controller <> Null
			'default to our own position
			If _x = 0 And _y = 0
				_x = x
				_y = y
			EndIf
			
			'don't target self
			Local selfList:TList = New TList
			selfList.addLast(Self)
			
			'target the closest NON-CLOAKED thingy
			controller.target = Null
			controller.target = closestShip(_x, _y, faction, _viableTargets, _detectRange, selfList, _skipCloaked)
		EndIf
	EndMethod
	
	'adds a component to the ship (_absolute = FALSE : the component will be added at the x,y absolute coordnates, otherwise rel. to center)
	Method add_comp(_compName$,_x=0,_y=0, _locked = False, _absolute = False)
		Print "adding comp " + _compName + " to " + name + "     locked :" + _locked
		'x,y input are offset from center
		Local cx = Floor(cshapedim / 2)
		Local cy = Floor(cshapedim / 2)
		
		'no offset of the provided coords are absolute
		If _absolute
			cx = 0
			cy = 0
		EndIf
		
		'make the component
		For Local c:Component = EachIn componentList
			If c.name = _compName
				Local ac:Component = new_comp(c.name)
				ac.x = cx + _x
				ac.y = cy + _y
				ac.locked = _locked
				compList.addLast(ac)
			EndIf
		Next
		
		recalc_cshape()
		recalc_components()
		recalc_stats()
	EndMethod
	
	Method remove_comp(_comp:Component)
		ListRemove(compList,_comp)
		recalc_cshape()
		recalc_components()
		recalc_stats()
	EndMethod
	
	'returns a string containing the information to make little gun readiness indicators
	Method gunBlips$()
		Local blips$
		For Local group = 0 To 1
			For Local g:Gun = EachIn gunGroup[group]
				blips:+ "[blip]" + timerDinged(g.timerList, "fireDelay")*(binsgn(group)*-1)'negative to say draw it differently
				blips:+ "[x]" + g.offsetx
				blips:+ "[y]" + g.offsety
			Next
		Next
		For Local a:Ability = EachIn abilityList
			If a.vars.get("doesnotcount") = 0 And Not a.passive And timerMaxTime(a.timerList, "cooldown") >= 500
				'find the component for this ability
				For Local c:Component = EachIn compList
					If c.abilityName = a.name
						blips:+ "[blip]" + (timerDinged(a.timerList, "cooldown"))*-1'negative to say draw it differently
						blips:+ "[x]" + c.getOffsetX()
						blips:+ "[y]" + c.getOffsety()
						Exit
					EndIf
				Next
			EndIf
		Next
		Return blips
	EndMethod
	
	'resets the ship back to its pristine state, and adds back to game if [addToShipList = true]
	Method reset( addToShipList = False )
	
		'we can't be added to the ship list twice
		If ListContains(shipList, Self) Then addToShipList = False
	
		'add self back to game
		If addToShipList
			If link = Null Then link = shipList.AddLast(Self)
			If netSprite = Null Then initNetSprite(base.gfxFile, NET_SPRITE_GFXTYPE_SHIP, base.animated, 4)
			
			'the HOST_CONTROLLED damage text sprite (so it's basically just a holder, never updated or drawn itself)
			If recent_damage_sprite = Null
				recent_damage_sprite = New Sprite
				recent_damage_sprite.ignorePhysics = True
				recent_damage_sprite.setRGB(255,0,0)
				recent_damage_sprite.scale = 1'2
				recent_damage_sprite.alpha = 0
				recent_damage_sprite.initNetSprite("", NET_SPRITE_GFXTYPE_EFFECT, False, 6)
				recent_damage_sprite.link = hostSpriteList.addLast(recent_damage_sprite)
			EndIf
			
			'make a name for ourselves
			If label = Null And behavior = "player"
				label = New Sprite
				label.ignorePhysics = True
				label.initNetSprite("", NET_SPRITE_GFXTYPE_EFFECT, False, 6)
				label.link = hostSpriteList.addLast(label)
			EndIf
		EndIf
		
		If controller <> Null Then controller.reset()
		
		'reSET that shit muthafuukah, I can't believe I wasn't doing this already
		abilityList = New TList
		
		recalc_cshape()
		recalc_components()
		recalc_stats()
		armour = stat.armour
		barrier = stat.barrier
		afterburn = False
		afterburn_old = False
		points = 0
		juice = stat.juice
		hitTween = 0
		alpha = 1
		shade = 0
		dist = 1
		spin = 0
		rot = 90
		scale = 1
		throttle = 0
		'echarge = 0
		emp_timer = 0
		emp_resistance = 0
		damageBuffer = 0
		damageBuffer_old = 0
		recent_damage = 0
		recent_damage_timer = 0
		recent_points = 0
		recent_points_timer = 0

		timerSet(timerList, "barrierTimer", BARRIER_DELAY)
		timerLoop(timerList, "barrierTimer", False)
		
		timerSet(timerList, "overheat", 0)
		timerLoop(timerList, "overheat", False)
		
		timerSet(timerList, "backstab_delay", backstab_delay)
		timerLoop(timerList, "backstab_delay", False)
		
		'controller
		If controller <> Null Then controller.reset()

		'If engineChannel <> Null Then SetChannelVolume engineChannel,0

		If addToShipList'(after we've recalc'd stats)
			If stat.thrust > 0
				If base.emitFX Or behavior = "player"
					'make a trail for the ship, longer for players
					Local e:Emitter = new_emitter("trail")
					If behavior = "player"
						e.onscreenoverride = 1
						e.lifetimer = 2000
						e.interval = 15
						e.alpha = 1
					EndIf
					If abilityHave("Sweet Rims") Then e.trail = 5'rainbow trail!
					e.attach(Self)
					e.upload()
				EndIf
				
				Rem
				'add a thrust effect for the engines
				'addThrustEffect()
				Local enginecount = 0
				For Local c:Component = EachIn compList
					If c.class = "engine"
						'offset due to component placement
						Local ox = (c.x - (cshapedim-1)/2)*cshaperatio*scale
						Local oy = (c.y - (cshapedim-1)/2)*cshaperatio*scale
													
						e = new_emitter("thrust")
						e.markerID = enginecount
						e.x = ox
						e.y = oy
						e.active = False
						e.attach(Self)
						'e.upload()
						enginecount:+ 1
					EndIf
				Next
				EndRem
			EndIf
		EndIf

		dead = 0
	EndMethod
	
	'get the base chassis from the chassis list (should PROBABLY be recalcing other things along with this if we ever have occasion to call it)
	Method recalc_base(_name$)
		name = _name
		For Local c:Chassis = EachIn chassisList
			If Lower(c.name) = Lower(_name) Or Lower(c.displayName) = Lower(_name) Then base = c
		Next
		If base = Null
			'Print "!!!!! Cannot find chassis for ship " + _name + " " + _config + " " + _owner
			Return Null
		EndIf
	EndMethod
	
	'recalculate max ability points, speed, turnspeed, armour, accel based on the chassis + configuration
	Method recalc_stats()
		'Print "recalcing stats for " + name
		status = New StatBonus'clear the temporary stats
		
		'start out as base
		mass = base.mass
		stat = copyStats(base.stat)
		
		'clear the fortification list
		ClearList(fortifyList)
		'is it backstabbable?
		If base.backstabbable
			Local f:Fortification = New Fortification
			f.DR = -8			'bonus damage
			f.radius = 90
			f.collision = False	'can't backstab with a collision
			f.normal = True		'can backstab with a knife
			f.reverse = True
			fortifyList.addLast(f)
		EndIf
	
		'get all bonuses...
		Local bonusList:TList = New TList
		'				...from chassis
		bonusList.addLast(base.bonus)
		'				...from components
		For Local c:Component = EachIn compList
			If c.plugged And Not c.addon'needs to be powered - AND - not an addon itself
				bonusList.addLast(c.bonus)
				'apply bonuses from attached addons (for them since they're skipped in this)
				For Local add:Component = EachIn c.addonList
					bonusList.addLast(add.bonus)
				Next
			EndIf
		Next
		
		'get non-gun bonuses from shape	
		If behavior = "player"	
			For Local sb$ = EachIn shapeBonusList
				Local shapebonus:StatBonus = New StatBonus	
				Select sb
				Case "symmetrical"
					'shapebonus.armour = .15
					'shapebonus.juice = .15
					'shapebonus.turnRate = .15
					'shapebonus.maxSpeed = .15
					'shapebonus.thrust = .15
				Case "nogun"
					For Local i = 0 To 1
						Local a:Ability = New Ability
						a.init("Coolant", Self)
						abilityList.addLast(a)
					Next
				EndSelect
				bonusList.addLast(sb)
			Next
		EndIf

	
		'apply bonuses
		Local bonus:StatBonus = stackBonuses(bonusList)	
		stat.armour = Ceil(stat.armour * (1+bonus.armour))
		stat.barrier:* (1+bonus.barrier)
		stat.juice:* (1+bonus.juice)
		stat.turnRate:* (1+bonus.turnRate)
		stat.maxSpeed:* (1+bonus.maxSpeed)
		stat.thrust:* (1+bonus.thrust)
		stat.strafe:* (1+bonus.strafe)
		'stat.points:* (1+bonus.points)
		
		'clear / make the gungroups
		For Local group = 0 To 1
			gunGroup[group] = New TList
		Next
				
		'components?
		For Local c:Component = EachIn compList
			'Print "recalcing " + c.name
		
			'offset due to component placement (scaled by size of ship)
			Local cshaperatio = Ceil(Max(wid,het) / cshapedim)
			Local ox = c.getOffsetX() * cshaperatio
			Local oy = c.getOffsetY() * cshaperatio
		
			'if this component is plugged in (or doesn't need to be)
			If c.plugged
				mass:+ c.mass
								
				'add any fortifications from this component
				If c.fortify <> Null Then fortifyList.addLast(c.fortify)
				
				'add any abilities from this component
				If c.abilityName <> ""
					Local a:Ability = New Ability
					a.init(c.abilityName, Self)
					abilityList.addLast(a)
				EndIf
				
				' v-------- GUNS --------v '
				'if this component has any base gun bonuses
				Local gunBonusList:TList = New TList
				gunBonusList.addLast(c.bonus)
				
				'get bonuses from addons
				For Local add:Component = EachIn c.addonList
					gunBonusList.addLast(add.bonus)
				Next
				
				'get gun bonuses from shape
				If behavior = "player"
					For Local sb$ = EachIn shapeBonusList
						Local shapebonus:StatBonus = New StatBonus	
						Select sb
						Case "singlegun"
							shapebonus.recycle = -.25
						Case "noalt"
							shapebonus.damage = .5
						EndSelect
						gunBonusList.addLast(sb)
					Next
				EndIf
				
				'stack all gun bonuses
				Local gunBonus:StatBonus = stackBonuses(gunBonusList)	'dang it now i've written the word "bonus" too much and it's not a word anymore
				
				'make the weapon (if this component has one)
				If c.weapon[0] <> "" And c.weapon[1] <> ""'lua name & gun class (e.g. "plasma" and "auto")
					'Print "      its a gun! " + c.weapon[0]
					'make the gun
					Local g:Gun = newGun(c.weapon[0], c.weapon[1], ox, oy)', c.freedom)
					g.owner = placeholder
		
					g.shotdamage:* (1+gunBonus.damage)
					g.shotspeed:* (1+gunBonus.shotspeed)
					g.shotrange:* (1+gunBonus.range)
					g.fireDelay:* (1+gunBonus.recycle)
					g.overheat:+ gunBonus.juicecost
					g.burstNum:+ gunBonus.burst
					g.shotcritbonus:+ gunBonus.crit
					If gunBonus.shotdurable
						g.shotdurable = True
						g.piercing = True
					EndIf
						
					If Not c.alt						'primary weapon
						gunGroup[0].addLast(g)
					Else								'alternate weapon
						gunGroup[1].addLast(g)
						'(make sure that all alternate weapons are the same kind)
						Rem
						If ListIsEmpty(gunGroup[1])
							gunGroup[1].addLast(g)
						Else
							For Local alt:Gun = EachIn gunGroup[1]
								If alt.name = g.name
									gunGroup[1].addLast(g)
								EndIf
							Next
						EndIf	
						EndRem	
						
						Rem
						For Local i = 1 To 5
							'if we've gotten to an empty list w/o finding an OK group
							If ListIsEmpty(gunGroup[i]) Or i = 5
								gunGroup[i].addLast(g)
								Exit
							Else
								'is THIS group OK?
								Local ok = False
								For Local altgun:Gun = EachIn gunGroup[i]
									'does this gun belong here?
									If altgun.name = g.name
										gunGroup[i].addLast(g)
										ok = True
									EndIf
								Next
								If ok Then Exit
							EndIf
						Next
						EndRem
					EndIf
				EndIf
				' ^-------- GUNS --------^ '
			EndIf
		Next
		
		'slow down the ship by how much mass was added
		stat.turnRate:* (base.mass / mass)
		stat.thrust:* (base.mass / mass)
		
		'Print "     armour:"+stat.armour
		'Print "     barrier:"+stat.barrier
		'Print "     speed:"+stat.maxSpeed
	EndMethod
	
	'fills cshape based on the current components in compList - ALSO - attaches addon tiles
	Method recalc_cshape()
		'clear the current shape data
		For Local x = 0 To cshapedim-1
		For Local y = 0 To cshapedim-1
			cshape[x,y] = base.cshape[x,y]
		Next
		Next
		
		'fill up the spaces this component tiles
		For Local c:Component = EachIn compList
			For Local cy = 0 To 4
			For Local cx = 0 To 4
				'if the component takes this tile up
				If c.shape[cx,cy] = 1
					'adjust for rotation
					Local cr[2]
					cr = rotAdjust(cx,cy,c.rot)
					'is it within bounds?
					If c.x+cr[0] < cshapedim And c.x+cr[0] >= 0 And c.y+cr[1] < cshapedim And c.y+cr[1] >= 0
						'fill this tile up
						Local tile = -1
						If c.needs_space Then tile = -2
						cshape[c.x+cr[0],c.y+cr[1]] = tile
					EndIf
				EndIf
			Next
			Next
		Next
		
		'fill up the spaces with component tiles
		For Local c:Component = EachIn compList
			'now fill in the area around all snowflake components with power tiles
			If c.needs_space'if this component is a snowflake
				For Local cy = 0 To 4
				For Local cx = 0 To 4	
					'if this space is solid
					If c.shape[cx,cy] = 1
						'adjust for rotation
						Local cr[2]
						cr = rotAdjust(cx,cy,c.rot)
						
						'fill all adjecent spaces with power tiles (as indicator that there can be no adj.)
						For Local ay = -1 To 1
						For Local ax = -1 To 1
							If (Abs(ax) > 0 Or Abs(ay) > 0) And Not (Abs(ax) = 1 And Abs(ay) = 1)
								Local crax = c.x+cr[0]+ax
								Local cray = c.y+cr[1]+ay
								'is adjecent within bounds of the ship?
								If crax < cshapedim And crax >= 0 And cray < cshapedim And cray >= 0
									'is it empty?
									If Abs(cshape[crax,cray]) = 1
										'put a peg there!
										cshape[crax,cray] = 3*Sgn(cshape[crax,cray])
									EndIf							
								EndIf
							EndIf
						Next
						Next
					EndIf
				Next
				Next
			EndIf
		Next
		
		'go through and see what shape bonuses we get (all ships track it but it's only applied to players)
		ClearList(shapeBonusList)
	
		'--SYMMETRY/filled/catawompus----
		Local filled = True
		Local symmetrical = True
		'Local catawompus = True, catty = 0
		Local maxsize = cshapedim-1
		For Local cy = 0 To maxsize
			For Local cx = 0 To maxsize
				'if this square is filled
				If cshape[cx,cy] < 0
					'check to see if the one on the opposite side isn't filled
					If cshape[maxsize-cx,cy] > 0
						symmetrical = False
						Exit
					EndIf
					'which side we are on
					Rem
					Local side = Sgn(x - (maxsize/2))
					Print "x y " + x + " " + y + "    side " + side
					'have we already found something on the other side?
					If catty <> 0 And catty <> side
						catawompus = False
					Else
						catty = side
					EndIf
					EndRem
				Else
					filled = False
				EndIf
			Next
			If Not symmetrical Then Exit
		Next
		If symmetrical Then shapeBonusList.addLast("symmetrical")
		If filled Then shapeBonusList.addLast("filled")
		'If catawompus And catty <> 0 Then shapeBonusList.addLast("catawompus")
		
		'--single gun--
		Local singlegun = False
		For Local c:Component = EachIn compList
			If c.weapon[0] <> "" And Not c.alt
				If Not singlegun
					singlegun = True
				Else
					singlegun = False
					Exit
				EndIf
			EndIf
		Next
		If singlegun Then shapeBonusList.addLast("singlegun")
		
		'--no gun--
		Local nogun = True
		For Local c:Component = EachIn compList
			If c.weapon[0] <> "" And Not c.alt
				nogun = False
				Exit
			EndIf
		Next
		If nogun Then shapeBonusList.addLast("nogun")
		
		'--no alt--
		Local noalt = True
		For Local c:Component = EachIn compList
			If c.needs_space
				If (c.alt) Or (c.abilityName <> "" And abilityGetVar(c.abilityName, "doesnotcount") = 0)
					noalt = False
					Exit
				EndIf
			EndIf
		Next
		If noalt Then shapeBonusList.addLast("noalt")
		

	EndMethod
	
	'recalculates the states of all components (addon tiles, powered)
	Method recalc_components()
		'--- POWERED? ---
		For Local c:Component = EachIn compList
			'first, set the freedom according to how the comp is rotated and positioned
			'Select (c.rot Mod 2)
			'Case 0'0, 180d
			'	c.freedom = GUN_FORWARD
			'Case 1'90, 270d
			'	c.freedom = GUN_ANGLE
			'EndSelect
			
				'clear any components that are added to this one, we'll re-add them in a second
			ClearList(c.addonList)
		
			'check to see if it's plugged in to a degree it wishes
			c.plugged = True'False
			
			Rem
			'engines are always plugged in
			If c.class = "engine"
				c.plugged = True
			
			'if we don't need no stinking power tiles
			ElseIf c.preq[0] = 0 And c.preq[1] = 0
				c.plugged = True
				
			'other things have to check for power tiles
			Else
				'c.freedom = GUN_REVERSE		'guns use the best type of freedom slot that they can
				Local ptile_count[2]		'number of each type of tile that this component needs
				ptile_count[0] = c.preq[0]
				ptile_count[1] = c.preq[1]
				
				'for every tile that makes up this component...
				For Local cy = 0 To 4
				For Local cx = 0 To 4
					If c.shape[cx,cy] = 1
						'adjust for rotation
						Local cr[2]
						cr = rotAdjust(cx,cy,c.rot)
						
						'are we within bounds?
						If isWithin(c.x+cr[0], 0, cshapedim-1) And isWithin(c.y+cr[1], 0, cshapedim-1)

							'is there a power tile here?
							Local ptile = base.cshape[c.x+cr[0], c.y+cr[1]]
							If ptile > CSHAPE_AVAILABLE
								
								If ptile = GUN_TURRET Then c.freedom = GUN_TURRET
								
								'Print name + " gun on " + ptile + "   (turret is " + GUN_TURRET+")"
								
								'do we need utility power?
								If ptile_count[0] > 0
									'is this tile a utility power tile?
									If ptile = CSHAPE_UTILITY
										ptile_count[0]:- 1
										'c.freedom = Min(c.freedom, ptile)
									EndIf
								EndIf
								
								'do we need weapon power?
								If ptile_count[1] > 0
									'is this tile a weapon power tile?
									If ptile > CSHAPE_UTILITY
										ptile_count[1]:- 1
										'store the best freedom that we can
										'c.freedom = Min(c.freedom, ptile)
										'If ptile = GUN_TURRET Then c.freedom = ptile
									EndIf
								EndIf
							EndIf
						EndIf
					EndIf
				Next
				Next

				'assume plugged unless we still have an unfulfilled requirement
				c.plugged = True
				For Local i = 0 To 1
					If ptile_count[i] > 0 Then c.plugged = False
				Next
			EndIf
			EndRem
			
		Next
				
		'-- ATTACHMENT TILES --
		'see if this component is adjecent to any others of the same name, attach components to their parents
		For Local c:Component = EachIn compList
			'if this component is an addon
			If c.addon
				Local addedon = False
				For Local cy = 0 To 4
				For Local cx = 0 To 4	
					'if it has an attactment tile at this space
					If c.shape[cx,cy] = 2
						'overlappers?
						For Local a:Component = EachIn compList
							'if it's the right class, accepts addons, and is not an addon itself (heaven forbid!)
							If c.class = a.class And a.accepts_addons And Not a.addon
								For Local ay = 0 To 4
								For Local ax = 0 To 4
									'if THAT comp is occupying this space
									If a.shape[ax,ay] = 1
										'adjust for rotation
										Local cr[2],ar[2]
										cr = rotAdjust(cx,cy,c.rot)
										ar = rotAdjust(ax,ay,a.rot)
										'see if that component's square is on top of the attachement tile
										If (c.x+cr[0]) = (a.x+ar[0]) And (c.y+cr[1]) = (a.y+ar[1])
											a.addonList.addLast(c)	'attach this component to this other component
											addedon = True			'this addon is added on
										EndIf
									EndIf
								Next
								Next
							EndIf
						Next
					EndIf
				Next
				Next
				If Not addedon Then c.plugged = False
			EndIf
		Next

	EndMethod
	
	'makes an interpretable string containing the stats of the ship
	Rem
	Method statsToString()		
		Local str$
		
		str:+ "[name]"+name
		str:+ "[config]"+config
		str:+ "[color]"+color

		str:+ "[ability]"+ability.name
		str:+ "[turnRate]"+turnRate
		str:+ "[speed]"+speedMax
		str:+ "[thrust]"+thrust
		str:+ "[strafe]"+strafe
		str:+ "[pointMax]"+pointMax
		str:+ "[armourMax]"+armourMax
		str:+ "[juiceMax]"+juiceMax
		
		For Local c:Component = EachIn compList
			str:+ "<"+c.makeString()+">"
		Next

	EndMethod
	EndRem
EndType


'tracks the current commands and AI states of a ship
Type Control
	Field aimx, aimy
	Field afterburn, thrust, ability, fire1, fire2
	'Field joythrust#, joythrust_theta#

	Field timerList:TList				'a list of timers that scripts are using
	Field vars:VarArray					'an array of variable spaces
	'Field gotVar#						'the last set/retrieved array variable
	
	Field target:Gertrude				'the ship that this ship is currently targeting
	Field targetList:TList = New TList	'sometimes the world just seems to be out to get you, y'know? i keep a list of all who have wronged me
	Field remoteInput:TGNetObject		'the object which is the conduit for REMOTE player input information (contrast with global localInput)
	
	Field ignoreProxList:TList = New TList'a list of things to ignore when figuring out what to avoid proximity-wise
	
	Field disabled = False
	
	Field scriptslot = -1				'the index of controlScriptIndex where our script is stored
	
	Field gox, goy
	Field goalx, goaly
	
	'Field stagewid, stagehet			'the width and height of the stage
	
	'that new-car smell, resets all soft variables
	Method reset()
		timerList = New TList
		targetList = New TList
		ignoreProxList = New TList
		vars = New VarArray
		target = Null
	EndMethod
	
	'returns whether or not we have a target
	Method hasTarget()
		If target = Null Then Return 0 Else Return 1
	EndMethod
	
	'sets the target to something
	Method setTarget(tar:Gertrude)
		target = tar
		Return 0
	EndMethod
	
	'ticks a specified timer (or creates it if it doesn't yet exist)
	Method hasTimerTicked(_timerName$, _interval = -1)
		Return timerTick(timerList, _timerName, _interval)
	EndMethod
	
	'resets // sets a timer
	Method resetTimer(_timerName$, _interval = -1, _loop = 0)
		timerSet(timerList, _timerName, _interval)
		timerLoop(timerList, _timerName, _loop)
	EndMethod
	
	Rem
	'sets a variable to a value if it doesn't already exist
	'(returns FALSE if it already existed and TRUE if we just set it)
	Method initVar#(_varName$, _varValue#)
		AIvar.init(_varName, _varValue#)
		gotVar = AIvar.got
		Return gotVar
	EndMethod
	
	'sets a variable to a value
	'(returns TRUE if we successfully set the value, FALSE if otherwise)
	Method setVar#(_varName$, _varValue#)
		AIvar.set(_varName, _varValue#)
		gotVar = AIvar.got
		Return gotVar
	EndMethod
	
	'returns a variable's current value
	Method getVar#(_varName$)
		AIvar.get(_varName)
		gotVar = AIvar.got
		Return gotVar
	EndMethod
	EndRem
		
	'gives control to current keyboard & mouse input
	Method downloadKeyboardInput()
		If remoteInput <> Null
			fire1 = 		GetGNetInt(remoteInput, NET_INPUT_FIRE1)
			fire2 = 		GetGNetInt(remoteInput, NET_INPUT_FIRE2)
			thrust = 		GetGNetInt(remoteInput, NET_INPUT_THRUST)
			afterburn = 	GetGNetInt(remoteInput, NET_INPUT_BURN)
			ability = 		GetGNetInt(remoteInput, NET_INPUT_ABILITY)
			aimx =			GetGNetFloat(remoteInput, NET_INPUT_X)
			aimy =			GetGNetFloat(remoteInput, NET_INPUT_Y)
		Else
			Print "input conduit not set!"
		EndIf
	
		'If Not joyDetected
		
			Rem
			If KeyDown(KEY_AFTERBURN) Then afterburn = 1 Else afterburn = 0
			If (KeyDown(KEY_THRUST) Or KeyDown(KEY_UP)) Then thrust = 1 Else thrust = 0
			If (KeyDown(KEY_REVERSE) Or KeyDown(KEY_DOWN)) Then reverse = 1 Else reverse = 0
			If (KeyDown(KEY_STRAFELEFT) Or KeyDown(KEY_LEFT)) Then strafeleft = 1 Else strafeleft = 0
			If (KeyDown(KEY_STRAFERIGHT) Or KeyDown(KEY_RIGHT)) Then straferight = 1 Else straferight = 0
			If KeyHit(KEY_SHIELD) Then ability = 1 Else ability = 0
			If KeyHit(KEY_CYCLEGUN) Then switch = 1 Else switch = 0
			If MouseDown(MOUSE_FIREPRIMARY) Then fire1 = 1 Else fire1 = 0
			If MouseDown(MOUSE_FIRESECONDARY) Then fire2 = 1 Else fire2 = 0
			If KeyDown(KEY_TAB) Then map = 1 Else map = 0
			EndRem
		
		Rem
		Else
			afterburn = JoyDown(JOY_AFTERBURN)	Or afterburn	'... I don't really remember what these are for, honestly
			thrust = JoyDown(JOY_THRUST)		Or thrust
			ability = JoyDown(JOY_SHIELD)		Or ability
			switch = JoyDown(JOY_CYCLEGUN)		Or switch
			fire1 = JoyDown(JOY_FIREPRIMARY)	Or fire1
			fire2 = JoyDown(JOY_FIRESECONDARY)	Or fire2
			map = JoyDown(JOY_MAP)				Or map
			
			'different method of joy movement
			Select JOY_MOVEAXIS
			Case JOY_HAT
				thrust =		( JoyHat(0) Mod 6 <= 1 )					Or thrust
				straferight = 	(( JoyHat(0) >= 1 ) And ( JoyHat(0) <= 3 ))	Or straferight
				reverse =		(( JoyHat(0) >= 3 ) And ( JoyHat(0) <= 5 )) Or reverse
				strafeleft =	(( JoyHat(0) >= 5 ) And ( JoyHat(0) <= 7 )) Or strafeleft
			Case JOY_LEFTSTICK
				joythrust = constrain(Sqr(JoyX(0)^2 + JoyY(0)^2), 0, 1)
				joythrust_theta = ATan2(-JoyX(0),JoyY(0)) + 90
			Case JOY_RIGHTSTICK
				joythrust = constrain(Sqr(JoyZ(0)^2 + JoyR(0)^2), 0, 1)
				joythrust_theta = ATan2(-JoyZ(0),JoyR(0)) + 90
			Case JOY_OTHERSTICK
				joythrust = constrain(Sqr(JoyU(0)^2 + JoyV(0)^2), 0, 1)
				joythrust_theta = ATan2(-JoyU(0),JoyV(0)) + 90
			EndSelect
			
			'KILL THE NOISE
			If joythrust < .01 Then joythrust = 0		
		EndIf
		EndRem
	EndMethod

EndType


'viableTargets is the relationship we're going for : [ -1 = enemies | 0 = neutral | 1 = allies | 2 = ANY]
'TODO: make more efficient
Function closestShip:Gertrude(_x, _y, _faction, _viableTargets = -1, _detectRange = 1000, _ignoreList:TList = Null, _skipCloaked = False)
	Local tar:Gertrude
	
	For Local o:Ship = EachIn shipList
		'can't target inerts (or cloaked things?)
		If o.controller.vars.get("inert") = 0 And (_skipCloaked Or Not o.placeholder.cloaked)
			If _ignoreList = Null Or (Not ListContains(_ignoreList, o) And Not ListContains(_ignoreList, o.placeholder))
				Local opinion = fTable[_faction,o.faction]'what does this ship think of this other ship? (-1 enemy, 0 neutral, 1 ally)
				
				'if this is the sort of relationship we're looking for
				If opinion = _viableTargets Or (_viableTargets = 2)'2 means we're desparate, any relationship will do. just _so_ lonely...
					Local tdist = approxDist(o.x-_x,o.y-_y)
					If tdist <= _detectRange
						'if lacking a target, take the first one we see
						If tar = Null
							tar = o.placeholder
						'if this new target is closer than the old one (OR the old one is cloaked)
						ElseIf tdist < approxDist(tar.x-_x, tar.y-_y) Or tar.cloaked
							tar = o.placeholder
						EndIf
					EndIf
				EndIf
			EndIf
		EndIf
	Next
	
	'LuaRegisterObject tar, "target"
	Return tar
EndFunction


'organizes a list of ships into an array [ CHASSIS TYPE, CONFIG ]
Function shipListToArray:Ship[,](_shipList:TList)
	Print "turning ship list to an array:"
	For Local s:Ship = EachIn _shipList
		Print "   " + s.name + " '"+s.config+"'"
	Next

	Local shipArray:Ship[MAXSHIP, MAXCONFIG]
	'place each ship from shipList into the first open appropriate slot
	For Local s:Ship = EachIn _shipList
		'chassis slots
		For Local chassis = 0 To (MAXSHIP-1)
			'fill the first empty chassis slot we see
			If shipArray[chassis,0] = Null
				Print "  put "+s.name+" '" + s.config + "' config in array"
				shipArray[chassis,0] = s
				Exit
			EndIf
			'if we find the same sort of chassis already, fill a config slot within that
			If shipArray[chassis,0].name = s.name
				For Local config = 0 To (MAXCONFIG-1)
					'fill the first empty config slot we see
					If shipArray[chassis,config] = Null
						Print "  put '" + s.config + "' config in array"
						shipArray[chassis,config] = s
						Exit
					EndIf
				Next
				Exit
			EndIf
		Next
	Next
	Return shipArray
EndFunction

'if powered = True, only returns powered components
'if skipCost = 0, sets full cost of the ship+components
'if skipCost = 1, sets cost to zero
'if skipCost = 2, only includes cost of ship itself
Function shipToEntryString$(s:Ship, skipCost = 0, powered = True)
	Local str$
	
	str:+ "[name]" + s.base.displayName's.name
	str:+ "[config]" + s.config
	str:+ "[gfxpath]" + s.base.gfxFile
	str:+ "[color]" + s.color
	str:+ "[desc]" + s.base.desc
	str:+ "[armourmax]" + s.stat.armour
	str:+ "[barriermax]" + s.stat.barrier
	str:+ "[speedmax]" + s.stat.maxSpeed
	str:+ "[juicemax]" + s.stat.juice
	str:+ "[turnrate]" + s.stat.turnRate
	For Local a:Ability = EachIn s.abilityList
		str:+ "[abilityname]" + a.name
		Exit
	Next
	For Local gun$ = EachIn listComponents(s.compList, "primary", powered)
		str:+ "[gun1]" + gun
	Next
	For Local gun$ = EachIn listComponents(s.compList, "secondary", powered)
		str:+ "[gun2]" + gun
	Next
	
	'add up the cost of the ship and all the contained components (this here is some terrible code)
	Local cost = s.base.cost
	If skipCost < 2 Then
		For Local c:Component = EachIn s.compList
			cost:+ c.cost
		Next
	EndIf
	If skipCost = 1 Then cost = 0		
	str:+ "[cost]" + cost
	
	
	Return str
EndFunction



Rem
'returns a list of ships within a radius of the provided ship
Function getNearbyShipList:TList(s:Ship, radius)
	Local returnList:TList = New TList
	For Local o:Ship = EachIn shipList
		If approxDist(s.x - o.x, s.y - o.y) <= radius And (o <> s) Then returnList.addLast(o)
	Next
	Return returnList
EndFunction
EndRem		

Rem
'a group of ships with common commands. if no ships/icons point to it, it stops existing. whoa. [sorta stopped using them, but it's messily everywhere]
Type Squadron
	Field x,y							'the current center position of this squadron
	Field behavior$						'behavior that overwrites the ship's natural behavior
	Field shipNum						'stores the last shipcount, AKA how many ships are in this squadron
	Field shipCount						'reset each loop, tells the ship how many ships have checked into this squad before it
	Field goal_x,goal_y,goal_ship:Ship	'the target of this squadron, be it a position or a ship.
	Field faction						'the faction all contained ships are part of
	Field setPos = False				'does this squadron set the position of its members at the start?
	
	Field avoidcollision_dist' = AI_AVOIDCOLLISION_DIST
	Field divebomb_dist' = AI_DIVEBOMB_DIST
	Field circle_dist' = AI_CIRCLE_DIST
	Field detect_dist' = AI_DETECT_DIST
EndType
Function new_squad:Squadron(_behav$,_faction,_setPos = False)'makes & returns a squadron of the specified specifications
	Local s:Squadron = New Squadron
	s.behavior = _behav
	s.faction = _faction
	s.setPos = _setPos
	Return s
EndFunction
'sets all ships in the provided list to Null
Function resetSquads(_sList:TList)
	For Local s:Ship = EachIn _sList
		s.squad = Null
	Next
EndFunction
EndRem

