# -*- coding: utf-8 -*-
"""
UTc! module: src.weapons

Purpose
=======
 Provides routines for creating, maintaining, and rendering in-game weapons.
 
Legal
=====
 All code, unless otherwise indicated, is original, and subject to the
 terms of the GPLv3, which is provided in COPYING.
 
 (C) Neil Tallim, 2009
"""
import math
import ode
import os
import OpenGL.GL as GL
import re

import camera
import constants
import gameobjects
import matrix
import path_utils
import session_constants

import config.parser

import rendering.rendering as rendering

BOLT_SPEED = 350.0 #: The speed at which bolts travel.
_BOLT_LENGTH = 2.0 #: The length of a bolt.
_BOLT_WIDTH = 0.2 #: The width of a bolt.
_BOLT_RADIUS = _BOLT_WIDTH / 2.0 #: The radius of a bolt, for cylindrical modeling.
_BOLT_SIZE = (_BOLT_WIDTH, _BOLT_WIDTH, _BOLT_LENGTH) #: The (x, y, z) size of a rendered bolt in box form.
_BOLT_ODE_SIZE = None #: The size of a bolt's ODE hitbox.

_ECM_ODE_RADIUS = 4.0 #: The side of an ECM's ODE hitbox.
_ECM_RANGE = 25.0 #: The number of metres an ECM may travel.
_ECM_SPEED = 5.0 #: The speed at which an ECM moves.

_MISSILE_ODE_RADIUS = 2.0 #: The size of a missile's ODE hitbox.

_POWER_SOURCE_EFFECTS = {
 constants.POWER_LOVE: (0.909, 1.0, 1.0),
 constants.POWER_STARLIGHT: (1.1, 1.1, 1.0),
 constants.POWER_MOE: (1.0, 1.1, 0.909),
 constants.POWER_RAINBOW: (1.0, 1.0, 0.909),
 constants.POWER_HAPPINESS: (1.0, 1.0, 1.0)
} #: Balance constants mapped to different power sources: damage multiplier, energy multiplier, cooldown multiplier.

MODIFIER_BALANCED = 1 #: The constant associated with the balanced bolt modifier.
MODIFIER_CAPACITY = 2 #: The constant associated with the capacity bolt modifier.
MODIFIER_DAMAGE = 3 #: The constant associated with the damage bolt modifier.
MODIFIER_COOLDOWN = 4 #: The constant associated with the cooldown bolt modifier.
_MODIFIER_EFFECTS = {
 MODIFIER_BALANCED: (1.0, 1.0, 1.0),
 MODIFIER_CAPACITY: (1.0, 0.909, 1.1),
 MODIFIER_DAMAGE: (1.1, 1.1, 1.0),
 MODIFIER_COOLDOWN: (0.909, 1.0, 0.909)
} #: Balance constants mapped to different bolt modifiers: damage multiplier, energy multiplier, cooldown multiplier.

BOLTS = {} #: A collection of all _BoltFactories, keyed by XML descriptor ID.
MISSILES = {} #: A collection of all _MissileFactories, keyed by XML descriptor ID.

_bolt_id = 0 #: The last-used bolt ID.
_ecm_id = 0 #: The last-used ECM ID.
_missile_id = 0 #: The last-used missile ID.

_INSCRIPTION_REGEXP = re.compile(r"MPOS \w+ \d+ (\d+) (-?\d+\.\d+),(-?\d+\.\d+),(-?\d+\.\d+) (-?\d+\.\d+),(-?\d+\.\d+),(-?\d+\.\d+) (\d+\.\d+)") #: The regular expression used to process missile position updates.

class _Weapon(gameobjects.MobileGameObject):
	"""
	An abstract representation of all in-game weapon-objects.
	"""
	_alive = True #: True while this weapon is in flight.
	_damage_hull = None #: The amount of damage this weapon can do to hulls.
	_damage_shield = None #: The amount of damage this weapon can do to shields.
	_damage_subsystem = None #: The amount of damage this weapon can do to subsystems.
	_id = None #: The ID of this weapon, used for multiplayer synchronization.
	_ode_geom = None #: The ODE geom used to detect collisions.
	_remaining_range = None #: The remaining distance this weapon may travel.
	_tank_chan = None #: The Tank-chan to which this weapon belongs.
	
	def __init__(self, range, damage_hull, damage_shield, damage_subsystem, tank_chan, ode_space, speed, position, rotation_matrix):
		"""
		Initialises this weapon object, setting its damage capacity, TTL
		variables, position, and orientation.
		
		@type range: float
		@param range: The distance this object can travel before becoming inert.
		@type damage_hull: float
		@param damage_hull: The damage this weapon does if it comes in contact
		    with a ship's hull.
		@type damage_shield: float
		@param damage_shield: The damage this weapon does if it comes in contact
		    with a ship's shields.
		@type damage_subsystem: float
		@param damage_subsystem: The damage this weapon does if it comes in
		    contact with a ship's subsystems.
		@type tank_chan: tankchan.TankChan
		@param tank_chan: The ship that created this weapon.
		@type ode_space: ode.SpaceBase|None
		@param ode_space: The ODE space in which this weapon will exist; None if
		    this client is not responsible for managing its collisions.
		@type speed: float
		@param speed: The speed at which this weapon is initially travelling.
		@type position: sequence(3)
		@param position: The (x, y, z) co-ordinates at which this weapon
		    originated.
		@type rotation_matrix: sequence(9)
		@param rotation_matrix: The 3x3 rotation matrix that specifies the
		    orientation of this weapon.
		"""
		gameobjects.MobileGameObject.__init__(self, rotation_matrix)
		
		self._tank_chan = tank_chan
		self._remaining_range = range
		self._damage_hull = damage_hull
		self._damage_shield = damage_shield
		self._damage_subsystem = damage_subsystem
		self._current_speed = speed
		if ode_space:
			self._setODE(ode_space)
			self._ode_geom.tank_chan = self._tank_chan
			self._ode_geom.parent = self
		self.setPosition(position)
		
	def destroy(self):
		"""
		Removes a weapon from game-space, deleting its ODE presence, if any, and
		setting its status to dead for clean-up routines.
		"""
		self._alive = False
		
		if self._ode_geom: #Kill the ODE object.
			self._ode_geom.disable()
			self._ode_geom.getSpace().remove(self._ode_geom)
			self._ode_geom = None
			
	def getHullDamage(self):
		"""
		Returns the damage this weapon is capable of doing to a ship's hull.
		
		@rtype: number
		@return: The damage this weapon is capable of doing to a ship's hull.
		"""
		return self._damage_hull
		
	def getID(self):
		"""
		Returns the ID associated with this weapon.
		
		@rtype: int
		@return: The ID associated with this weapon.
		"""
		return self._id
		
	def getShieldDamage(self):
		"""
		Returns the damage this weapon is capable of doing to a ship's shields.
		
		@rtype: number
		@return: The damage this weapon is capable of doing to a ship's shields.
		"""
		return self._damage_shield
		
	def getSubsystemDamage(self):
		"""
		Returns the damage this weapon is capable of doing to a ship's subsystems.
		
		@rtype: number
		@return: The damage this weapon is capable of doing to a ship's
		    subsystems.
		"""
		return self._damage_subsystem
		
	def getTankChan(self):
		"""
		Returns the TankChan associated with this weapon.
		
		@rtype: L{tankchan.TankChan}
		@return: The TankChan associated with this weapon.
		"""
		return self._tank_chan
		
	def isAlive(self):
		"""
		Indicates whether this weapon is still active or not.
		
		@rtype: bool
		@return: True if this weapon is still active.
		"""
		return self._alive
		
	def matchID(self, id, tank_chan):
		"""
		A convenience function for testing whether this weapon is a specific
		weapon associated with a TankChan and bearing a certain ID.
		
		@type id: int
		@param id: The ID to be tested.
		@type tank_chan: L{tankchan.TankChan}
		@param tank_chan: The TankChan to be tested.
		
		@rtype: bool
		@return: True if this weapon matches; False otherwise.
		"""
		return self._id == id and self._tank_chan == tank_chan
		
	def tick(self, seconds):
		"""
		Updates this weapon's position and counts down the time that remains until
		it is rendered inactive.
		
		@type seconds: float
		@param seconds: The number of seconds over which the movement should be
		    computed.
		
		@rtype: bool
		@return: False if this weapon is no longer active.
		"""
		if not self._alive:
			return False
			
		self._remaining_range -= self._move(seconds)
		if self._remaining_range <= 0.0:
			self.destroy()
			return False
		return True
		
	def _updateVectors(self, rotation_update):
		"""
		Updates this weapon's ODE geom placement, if necessary.
		
		Note: rotation_update is not relevant to any existing weapon types, so
		it is ignored.
		
		@type rotation_update: bool
		@param rotation_update: True if this object's rotation has changed.
		"""
		#rotation_update will never matter, so ignore it.
		if self._ode_geom:
			self._ode_geom.setPosition((self._pos_x, self._pos_y, self._pos_z))
			
class Bolt(_Weapon):
	"""
	A representation of Bolt-type weapons.
	"""
	_colour = None #: The colour of this bolt.
	_type = None #: The signature of the factory that produced this bolt.
	
	def __init__(self, colour, range, damage_hull, damage_shield, damage_subsystem, rotation_matrix, position, tank_chan, ode_space, type, id=None):
		"""
		Initialises this bolt, setting its colour and ID.
		
		@type colour: sequence(3)
		@param colour: The percentage-based (r, g, b) colour to apply to this
		    bolt.
		@type range: float
		@param range: The distance this bolt can travel before becoming inert.
		@type damage_hull: float
		@param damage_hull: The damage this bolt does if it comes in contact
		    with a ship's hull.
		@type damage_shield: float
		@param damage_shield: The damage this bolt does if it comes in contact
		    with a ship's shields.
		@type damage_subsystem: float
		@param damage_subsystem: The damage this bolt does if it comes in
		    contact with a ship's subsystems.
		@type rotation_matrix: sequence(9)
		@param rotation_matrix: The 3x3 rotation matrix that specifies the
		    orientation of this bolt.
		@type position: sequence(3)
		@param position: The (x, y, z) co-ordinates at which this bolt
		    originated.
		@type tank_chan: tankchan.TankChan
		@param tank_chan: The ship that created this bolt.
		@type ode_space: ode.SpaceBase|None
		@param ode_space: The ODE space in which this bolt will exist; None if
		    this client is not responsible for managing its collisions.
		@type type: string
		@param type: The signature of the factory that produced this bolt.
		@type id: int|None
		@param id: The ID of this bolt, or None if one should be dynamically
		    assigned.
		"""
		_Weapon.__init__(self, range, damage_hull, damage_shield, damage_subsystem, tank_chan, ode_space, BOLT_SPEED, position, rotation_matrix)
		
		self._colour = colour
		self._type = type
		
		if not id:
			global _bolt_id
			_bolt_id += 1
			self._id = _bolt_id
		else:
			self._id = id
			
	def getType(self):
		"""
		Returns this bolt's type-signature, which identifies the factory it came
		from.
		
		@rtype: str
		@return: This bolt's type-signature.
		"""
		return self._type
		
	def render(self):
		"""
		Displays this bolt in visual space.
		"""
		if not self._alive or not self._render:
			return
			
		GL.glPushMatrix()
		rendering.renderCylinder((self._pos_x, self._pos_y, self._pos_z), self._rotation_matrix, _BOLT_RADIUS, _BOLT_LENGTH, self._colour)
		#rendering.renderCube((self._pos_x, self._pos_y, self._pos_z), self._rotation_matrix, _BOLT_SIZE, self._colour)
		GL.glPopMatrix()
		
	def _setODE(self, ode_space):
		"""
		Assigns ODE geometry to this bolt.
		
		@type ode_space: ode.SpaceBase
		@param ode_space: The ODE space in which this bolt exists.
		"""
		self._ode_geom = ode.GeomBox(ode_space, _BOLT_ODE_SIZE)
		self._ode_geom.setRotation(self._rotation_matrix)
		
class ECM(_Weapon):
	"""
	A representation of ECMs.
	"""
	def __init__(self, tank_chan, ode_space, id=None):
		"""
		Initialises this ECM.
		
		@type tank_chan: tankchan.TankChan
		@param tank_chan: The ship that created this ECM.
		@type ode_space: ode.SpaceBase|None
		@param ode_space: The ODE space in which this ECM will exist; None if
		    this client is not responsible for managing its collisions.
		@type id: int|None
		@param id: The ID of this ECM, or None if one should be dynamically
		    assigned.
		"""
		_Weapon.__init__(self, _ECM_RANGE, 0, 0, 0, tank_chan, ode_space, _ECM_SPEED, tank_chan.getPosition(), tank_chan.getRotationMatrix())
		self.updateVectors(None, (0.0, 180.0, 0.0)) #Fly away from the ship.
		
		if not id:
			global _ecm_id
			_ecm_id += 1
			self._id = _ecm_id
		else:
			self._id = id
			
	def _setODE(self, ode_space):
		"""
		Assigns ODE geometry to this ECM.
		
		@type ode_space: ode.SpaceBase
		@param ode_space: The ODE space in which this ECM exists.
		"""
		self._ode_geom = ode.GeomSphere(ode_space, _ECM_ODE_RADIUS)
		self._ode_geom.tank_chan = self._tank_chan
		self._ode_geom.parent = self
		
class Missile(_Weapon):
	"""
	A representation of missile-type weapons.
	"""
	_acceleration = None #: The acceleration of this missile.
	_ecm = None #: The ECM that has distracted this missile, if any.
	_gl_list_id = None #: The ID of the GL List to call when rendering this missile.
	_speed_maximum = None #: The maximum speed of this missile.
	_speed_minimum = None #: The minimum speed of this missile.
	_tacking_x = None #: The maximum pitch of this missile per second.
	_tacking_y = None #: The maximum yaw of this missile per second.
	_target = None #: The target this missile is attempting to reach.
	_type = None #: The signature of the factory that produced this missile.
	
	def __init__(self, target, range, damage_hull, damage_shield, damage_subsystem, rotation_matrix, position, tank_chan, ode_space, gl_list_id, speed_maximum, speed_minimum, acceleration, tacking_x, tacking_y, type, id=None):
		"""
		Initialises this missile, setting its target, appearance, throttling, and
		tacking parameters.
		
		@type target: tankchan.TankChan
		@param target: The target that this missile will seek.
		@type range: float
		@param range: The distance this missile can travel before becoming inert.
		@type damage_hull: float
		@param damage_hull: The damage this missile does if it comes in contact
		    with a ship's hull.
		@type damage_shield: float
		@param damage_shield: The damage this missile does if it comes in contact
		    with a ship's shields.
		@type damage_subsystem: float
		@param damage_subsystem: The damage this missile does if it comes in
		    contact with a ship's subsystems.
		@type rotation_matrix: sequence(9)
		@param rotation_matrix: The 3x3 rotation matrix that specifies the
		    initial orientation of this missile.
		@type position: sequence(3)
		@param position: The (x, y, z) co-ordinates at which this missile
		    originated.
		@type tank_chan: tankchan.TankChan
		@param tank_chan: The ship that created this missile.
		@type ode_space: ode.SpaceBase|None
		@param ode_space: The ODE space in which this missile will exist; None if
		    this client is not responsible for managing its collisions.
		@type gl_list_id: int
		@param gl_list_id: The ID of the OpenGL List that describes what this
		    missile looks like.
		@type speed_maximum: float
		@param speed_maximum: The maximum velocity this missile can maintain.
		@type speed_minimum: float
		@param speed_minimum: The minimum velocity this missile can maintain.
		@type acceleration: float
		@param acceleration: The maximum speed-change this missile can undergo
		    over the course of a second.
		@type tacking_x: float
		@param tacking_x: The maximum angle, in degrees, that this missile can
		    rotate around the X-axis over the course of a second.
		@type tacking_y: float
		@param tacking_y: The maximum angle, in degrees, that this missile can
		    rotate around the Y-axis over the course of a second.
		@type type: string
		@param type: The signature of the factory that produced this missile.
		@type id: int|None
		@param id: The ID of this bolt, or None if one should be dynamically
		    assigned.
		"""
		_Weapon.__init__(self, range, damage_hull, damage_shield, damage_subsystem, tank_chan, ode_space, 0, position, rotation_matrix)
		
		self._target = target
		self._gl_list_id = gl_list_id
		self._speed_maximum = speed_maximum
		self._speed_minimum = speed_minimum
		self._acceleration = acceleration
		self._tacking_x = tacking_x
		self._tacking_y = tacking_y
		self._type = type
		
		if not id:
			global _missile_id
			_missile_id += 1
			self._id = _missile_id
		else:
			self._id = id
			
	def describe(self, current_time):
		"""
		Provides a serialized description of this Missile that can be used to
		synchronize its appearance across multiple clients.
		
		@type current_time: int
		@param current_time: The timestamp at which this Missile was serialized,
		    in milliseconds.
		
		@rtype: str
		@return: The string that describes the current state of this Missile.
		"""
		(x_rot, y_rot, z_rot) = self.getRotationEuler()
		return "MPOS %s %i %i %f,%f,%f %f,%f,%f %f" % (
		 self._tank_chan.getName(), self._id,
		 current_time,
		 self._pos_x, self._pos_y, self._pos_z,
		 x_rot, y_rot, z_rot,
		 max(0.0, self._current_speed)
		)
		
	def disable(self):
		"""
		Prevents this missile from seeking or colliding on its own, used to
		offload that responsibility on another client.
		"""
		self.destroy() #Kill the ODE elements.
		self._target = None #Disables targeting.
		self._alive = True #Undoes the non-ODE damage of destroy().
		
	def getType(self):
		"""
		Returns this missile's type-signature, which identifies the factory it
		came from.
		
		@rtype: str
		@return: This missile's type-signature.
		"""
		return self._type
		
	def inscribe(self, description, current_time):
		"""
		Sets this Missile's position and stats based on a description received
		from another client.
		
		Note: Movement occurs without ticking, making this function independent
		from remaining range.
		
		@type description: basestring
		@param description: The description of the Missile from which data is to
		    be parsed.
		@type current_time: float
		@param current_time: The current timestamp, in seconds.
		"""
		match = _INSCRIPTION_REGEXP.match(description)
		event_time = float(match.group(1)) / 1000.0
		self._pos_x = float(match.group(2))
		self._pos_y = float(match.group(3))
		self._pos_z = float(match.group(4))
		self.setRotation((float(match.group(5)), float(match.group(6)), float(match.group(7))))
		
		self._current_speed = float(match.group(8))
		
		self._move(current_time - event_time)
		
	def render(self):
		"""
		Displays this missile in visual-space.
		"""
		if not self._alive or not self._render:
			return
			
		GL.glPushMatrix()
		GL.glMultMatrixf(self._render_matrix)
		GL.glCallList(self._gl_list_id)
		GL.glPopMatrix()
		
	def tick(self, seconds, ecms):
		"""
		Updates this missile's angles and speed according to its orientation
		relative to its target, if any.
		
		Also, if a target exists, determines whether an ECM has distracted this
		missile.
		
		Finally, the missile's position is updated.
		
		@type seconds: float
		@param seconds: The number of seconds that passed since the last update.
		@type ecms: sequence
		@param ecms: A collection of ECMs relevant to this missile's target.
		
		@rtype: bool
		@return: False if this missile is no longer active.
		"""
		if self._target: #Don't manage this missile if not necessary.
			if not self._target.isAlive():
				return False
				
			if ecms: #Find out whether an ECM should divert this missile.
				target_distance = self.getDistance(self._target.getPosition())
				
				closest_ecm = None
				closest_ecm_distance = None
				for ecm in ecms:
					ecm_distance = self.getDistance(ecm.getPosition()) 
					if closest_ecm:
						if closest_ecm_distance > ecm_distance:
							closest_ecm = ecm
							closest_ecm_sitance = ecm_distance
					else:
						closest_ecm = ecm
						closest_ecm_sitance = ecm_distance
						
				if closest_ecm and closest_ecm_distance < target_distance:
					self._ecm = closest_ecm
				else:
					self._ecm = None
					
			#Determine the direction this missile must travel.
			(offset_x, offset_y) = self._computeAngleOffsets()
			
			tacking_x = min(self._tacking_x * seconds, abs(offset_x))
			tacking_y = min(self._tacking_y * seconds, abs(offset_y))
			
			rotation_x = cmp(offset_x, 0.0) * tacking_x
			rotation_y = cmp(offset_y, 0.0) * tacking_y
			
			relative_offset_x = abs(offset_x) - tacking_x
			relative_offset_y = abs(offset_y) - tacking_y
			
			#Apply rotation update.
			self.updateVectors(None, (rotation_x, rotation_y, 0.0))
			
			#Alter speed.
			speed_change = seconds * self._acceleration
			if relative_offset_x <= 45.0 and relative_offset_y <= 45.0: #Mostly facing the target.
				if self._current_speed < self._speed_maximum:
					self._current_speed = min(self._speed_maximum, self._current_speed + speed_change)
			elif self._current_speed > self._speed_minimum:
				self._current_speed = max(self._speed_minimum, self._current_speed - speed_change)
				
		return _Weapon.tick(self, seconds)
		
	def _computeAngleOffsets(self):
		"""
		Computes the angle between this missile's current orientation and the
		position of its target.
		
		This function logically places a camera in the missile and makes its
		calculations based on its "visual" perception of the world around it.
		
		@rtype: tuple(2)
		@return: The X and Y angle offsets, in degrees, between this missile and
		    its target's current position. Z is not necessary.
		"""
		target_render_matrix = None
		if self._ecm:
			target_render_matrix = matrix.buildIdentityRenderMatrix(self._ecm.getPosition())
		else:
			target_render_matrix = matrix.buildIdentityRenderMatrix(self._target.getPosition())
			
		(target_offset_x, target_offset_y, target_offset_z) = matrix.multiply4x4Matrices(
		 target_render_matrix,
		 camera.buildCameraTransformationMatrix((self._pos_x, self._pos_y, self._pos_z), matrix.decomposeRotationMatrix(self._rotation_matrix))
		)[12:15]
		target_offset_z *= -1
		
		degrees = math.degrees
		atan2 = math.atan2
		angle_x = degrees(atan2(target_offset_y, target_offset_z))
		if angle_x > 180.0:
			angle_x += -360.0
		angle_y = degrees(atan2(target_offset_x, target_offset_z))
		if angle_y > 180.0:
			angle_y += -360.0
			
		return (-angle_x, -angle_y)
		
	def _setODE(self, ode_space):
		"""
		Assigns ODE geometry to this missile.
		
		@type ode_space: ode.SpaceBase
		@param ode_space: The ODE space in which this missile exists.
		"""
		self._ode_geom = ode.GeomSphere(ode_space, _MISSILE_ODE_RADIUS)
		
		
class _WeaponBank(object):
	"""
	An abstraction of any point from which weapons may originate.
	"""
	_tank_chan = None #: A reference to the Tank-chan with which this _WeaponBank is associated.
	_position = None #: The position at which this _WeaponBank appears, relative to a Tank-chan's origin.
	
	def __init__(self, tank_chan, position):
		"""
		Initialises this _WeaponBank, associating it with a ship and setting its
		position relative to ths ship's centre.
		
		@type tank_chan: L{tankchan.TankChan}
		@param tank_chan: The ship to which this _WeaponBank belongs.
		@type position: sequence(3)
		@param position: The (x, y, z) offsets of this _WeaponBank, relative to
		    the centre of the ship to which it is attached.
		"""
		self._tank_chan = tank_chan
		self._position = position
		
	def getOffset(self):
		"""
		Returns the offset of this _WeaponBank.
		
		@rtype: sequence(3)
		@return: The (x, y, z) offset of this _WeaponBank.
		"""
		return self._position
		
	def getPosition(self):
		"""
		Returns the absolute position of this _WeaponBank in game-space.
		
		@rtype: tuple(3)
		@return: The (x, y, z) position of this _WeaponBank in game-space.
		"""
		(tc_x, tc_y, tc_z) = self._tank_chan.getPosition()
		(x, y, z) = self._position
		return matrix.computePosition(tc_x, tc_y, tc_z, x, y, z, self._tank_chan.getRotationMatrix())
		
		
class _BoltBank(_WeaponBank):
	"""
	A point from which bolts may originate.
	"""
	_multiplier = None #: The dmage multiplier to apply to any bolts produced by this _BoltBank.
	
	def __init__(self, tank_chan, position, multiplier):
		"""
		Initialises this _BoltBank, settings its damage multiplier.
		
		@type tank_chan: L{tankchan.TankChan}
		@param tank_chan: The ship to which this _BoltBank belongs.
		@type position: sequence(3)
		@param position: The (x, y, z) offsets of this _BoltBank, relative to
		    the centre of the ship to which it is attached.
		@type multiplier: float
		@param multiplier: The damage multiplier of this _BoltBank.
		"""
		_WeaponBank.__init__(self, tank_chan, position)
		self._multiplier = multiplier
		
	def getMultiplier(self):
		"""
		Returns the damage multiplier of this _BoltBank.
		
		@rtype: float
		@return: The damage multiplier of this _BoltBank.
		"""
		return self._multiplier
		
class _WeaponFactory(object):
	"""
	An object that produces _Weapon objects.
	"""
	_cooldown = None #: The time it takes for this type of weapon to cool down.
	_damage_hull = None #: The amount of damage weapons of this type do to hulls.
	_damage_shield = None #: The amount of damage weapons of this type do to shields.
	_damage_subsystem = None #: The amount of damage weapons of this type do to subsystems.
	_name = None #: The common name of the weapons produced by this _WeaponFactory.
	_range = None #: The range that may be covered by weapons generated by this _WeaponFactory. 
	
	def __init__(self, name, damage_hull, damage_shield, damage_subsystem, range, cooldown):
		"""
		Initialises this _WeaponFactory, setting the displayed name of the weapons
		it produces, the damage they do, the range they can travel, and their
		inherent cooldown time.
		
		@type name: basestring
		@param name: The displayed name of weapons produced by this
		    _WeaponFactory.
		@type damage_hull: number
		@param damage_hull: The base hull damage that can be done by weapons
		    generated by this _WeaponFactory.
		@type damage_shield: number
		@param damage_shield: The base shield damage that can be done by weapons
		    generated by this _WeaponFactory.
		@type damage_subsystem: number
		@param damage_subsystem: The base subsystem damage that can be done by
		    weapons generated by this _WeaponFactory.
		@type range: number
		@param range: The distance that weapons generated by this _WeaponFactory
		    may travel.
		@type cooldown: float
		@param cooldown: The number of seconds required between successive
		    discharges of the type of weapon produced by this _WeaponFactory.
		"""
		self._name = name
		self._damage_hull = damage_hull
		self._damage_shield = damage_shield
		self._damage_subsystem = damage_subsystem
		self._range = range
		self._cooldown = cooldown
		
	def getCooldown(self):
		"""
		Returns the cooldown period of weapons generated by this _WeaponFactory.
		
		@rtype: float
		@return: The cooldown period of weapons generated by this _WeaponFactory.
		"""
		return self._cooldown
		
	def getName(self):
		"""
		Returns the displayed name of weapons generated by this _WeaponFactory.
		
		@rtype: str
		@return: The displayed name of weapons generated by this _WeaponFactory.
		"""
		return self._name
		
class _BoltFactory(_WeaponFactory):
	"""
	An object that produces _Bolt objects.
	"""
	_colour = None #: The colour of bolts produced by this _BoltFactory.
	_energy = None #: The amount of energy this type of bolt draws.
	_type = None #: The type signature to attach to every generated missile.
	
	def __init__(self, objects_path, descriptor_path, id):
		"""
		Initialises a _BoltFactory, setting its statistics and factory-signature.
		
		@type objects_path: basestring
		@param objects_path: The path to the objects directory.
		@type descriptor_path: basestring
		@param descriptor_path: The relative path to the XML file that describes
		     this _BoltFactory's bolts.
		@type id: basestring
		@param id: The factory-signature of this _BoltFactory.
		"""
		self._type = id
		parser = config.parser.Parser(objects_path + descriptor_path)
		
		_WeaponFactory.__init__(self,
		 parser.readData(None, 'bolt.list-name'),
		 int(parser.readData(None, 'bolt.specifications.damage.hull')),
		 int(parser.readData(None, 'bolt.specifications.damage.shield')),
		 int(parser.readData(None, 'bolt.specifications.damage.subsystem')),
		 int(parser.readData(None, 'bolt.specifications.range')),
		 float(parser.readData(None, 'bolt.specifications.rearm-time'))
		)
		self._colour = (
		 float(parser.readData(None, 'bolt.colour.r')),
		 float(parser.readData(None, 'bolt.colour.g')),
		 float(parser.readData(None, 'bolt.colour.b'))
		)
		self._energy = int(parser.readData(None, 'bolt.specifications.energy'))
		
	def createBolt(self, damage_multiplier, rotation_matrix, position, offset, target, tank_chan, ode_space, id=None):
		"""
		Generates a bolt object.
		
		@type damage_multiplier: float
		@param damage_multiplier: The modifier to apply to the damage potential of
		    the generated bolt.
		@type rotation_matrix: sequence(9)
		@param rotation_matrix: The orientation of the ship that generated this
		    bolt.
		@type position: sequence(3)
		@param position: The position from which this bolt originates.
		@type offset: sequence(3)|None
		@param offset: The offset of this bolt's origin, relative to the centre of
		    the ship that generated it, or None if there is no offset.
		@type target: L{gameobjects.GameObject}|None
		@param target: The target this bolt is intended to hit, used to determine
		    its focal distance. None if there is no target.
		@type tank_chan: L{tankchan.TankChan}
		@param tank_chan: The ship that generated this bolt.
		@type ode_space: ode.SpaceBase|None
		@param ode_space: The ODE space in which this bolt will reside, or None if
		    it should have no ODE presence.
		@type id: int|None
		@param id: The ID to assign to this bolt, or None if one should be
		    automatically assigned.
		
		@rtype: L{Bolt}
		@return: The generated bolt.
		"""
		distance = None
		if target:
			distance = target.getCameraDistances()[3]
		else:
			distance = BOLT_SPEED
			
		focused_rotation_matrix = rotation_matrix
		if offset:
			degrees = math.degrees
			atan2 = math.atan2
			focused_rotation_matrix = matrix.multiply3x3Matrices(
			 rotation_matrix, matrix.buildRotationMatrix(
			  90.0 - degrees(atan2(distance, offset[1])),
			  90.0 - degrees(atan2(distance, -offset[0])),
			  0.0
			 )
			)
		return Bolt(self._colour, self._range,
		 int(self._damage_hull * damage_multiplier),
		 int(self._damage_shield * damage_multiplier),
		 int(self._damage_subsystem * damage_multiplier),
		 focused_rotation_matrix,
		 position, tank_chan, ode_space,
		 self._type,
		 id
		)
		
	def getEnergy(self):
		"""
		Returns the energy that must be consumed to generate a bolt of this type.
		
		@rtype: int
		@return: The energy that must be consumed to generate a bolt of this
		    type.
		"""
		return self._energy
		
class _MissileFactory(_WeaponFactory):
	"""
	An object that produces _Missile objects.
	"""
	_acceleration = None #: The acceleration of this type of missile.
	_gl_list_id = None #: The ID of the GL List to use to render missiles of this type.
	_size = None #: The size of each missile.
	_speed_maximum = None #: The maximum speed of this type of missile.
	_speed_minimum = None #: The minimum speed of this type of missile.
	_tacking_x = None #: The maximum pitch of this type of missile per second.
	_tacking_y = None #: The maximum yaw of this type of missile per second.
	_type = None #: The type signature to attach to every generated missile.
	
	def __init__(self, objects_path, descriptor_path, id):
		"""
		Initialises a _MissileFactory, setting its statistics and
		factory-signature, as well as loading the OpenGL representation of its
		generated missiles.
		
		@type objects_path: basestring
		@param objects_path: The path to the objects directory.
		@type descriptor_path: basestring
		@param descriptor_path: The relative path to the XML file that describes
		     this _MissileFactory's missiles.
		@type id: basestring
		@param id: The factory-signature of this _MissileFactory.
		"""
		self._type = id
		filepath = objects_path + descriptor_path
		parser = config.parser.Parser(filepath)
		obj_filepath = filepath[:-(len(filepath) - filepath.rfind(os.sep))] + os.sep + parser.readData(None, 'missile.obj').replace('/', os.sep)
		
		_WeaponFactory.__init__(self,
		 parser.readData(None, 'missile.list-name'),
		 int(parser.readData(None, 'missile.specifications.damage.hull')),
		 int(parser.readData(None, 'missile.specifications.damage.shield')),
		 int(parser.readData(None, 'missile.specifications.damage.subsystem')),
		 int(parser.readData(None, 'missile.specifications.range')),
		 float(parser.readData(None, 'missile.specifications.rearm-time'))
		)
		self._speed_maximum = float(parser.readData(None, 'missile.specifications.speed.maximum'))
		self._speed_minimum = float(parser.readData(None, 'missile.specifications.speed.minimum'))
		self._acceleration = float(parser.readData(None, 'missile.specifications.speed.acceleration'))
		self._tacking_x = float(parser.readData(None, 'missile.specifications.tacking.x'))
		self._tacking_y = float(parser.readData(None, 'missile.specifications.tacking.y'))
		self._size = int(parser.readData(None, 'missile.specifications.size'))
		
		(_vertices, _normals, _faces, _materials) = gameobjects.loadOBJ(obj_filepath)
		self._buildGLList(_vertices, _normals, _faces, _materials)
		
	def createMissile(self, rotation_matrix, position, target, tank_chan, ode_space, id=None):
		"""
		Generates a missile object.
		
		@type rotation_matrix: sequence(9)
		@param rotation_matrix: The orientation of the ship that generated this
		    missile.
		@type position: sequence(3)
		@param position: The position from which this missile originates.
		@type target: L{gameobjects.GameObject}|None
		@param target: The target this missile is intended to hit. None if there
		    is no target.
		@type tank_chan: L{tankchan.TankChan}
		@param tank_chan: The ship that generated this missile.
		@type ode_space: ode.SpaceBase|None
		@param ode_space: The ODE space in which this missile will reside, or None
		    if it should have no ODE presence.
		@type id: int|None
		@param id: The ID to assign to this missile, or None if one should be
		    automatically assigned.
		
		@rtype: L{Missile}
		@return: The generated missile.
		"""
		return Missile(
		 target, self._range,
		 self._damage_hull, self._damage_shield, self._damage_subsystem,
		 rotation_matrix, position,
		 tank_chan, ode_space,
		 self._gl_list_id,
		 self._speed_maximum, self._speed_minimum, self._acceleration,
		 self._tacking_x, self._tacking_y,
		 self._type,
		 id
		)
		
	def getSize(self):
		"""
		Returns the number of capacity-units missiles of this type occupy.
		
		@rtype: int
		@return: The number of capacity-units missiles of this type occupy.
		"""
		return self._size
		
	def _buildGLList(self, _vertices, _normals, _faces, _materials):
		"""
		This function is invoked to generate a list of GL instructions, needed to
		efficiently render missiles of this type.
		
		@type _vertices: list
		@param _vertices: A list of all vertices that comprise this model.
		@type _normals: list
		@param _normals: A list of all normals used to render this model.
		@type _faces: list
		@param _faces: A list of all polygonal faces that make up this model.
		@type _materials: dict
		@param _materials: A dictionary that describes how to render each material
		    referenced by the OBJ.
		
		@raise Exception: If another error occurs, likely because of a GL issue.
		@raise KeyError: If a named material is missing.
		"""
		self._gl_list_id = GL.glGenLists(1) #Create a new GL List to optimize rendering, and cache the ID.
		GL.glNewList(self._gl_list_id, GL.GL_COMPILE) #Indicate that all instructions from here belong to the GL List.
		GL.glFrontFace(GL.GL_CCW) #Assume counter-clockwise polygons are front-facing.
		
		for (vertices, normals, material) in _faces:
			GL.glColor(*_materials[material]['Kd']) #Apply diffuse colour.
			
			GL.glBegin(GL.GL_POLYGON) #Describe how this face's polygon will be rendered.
			for i in range(0, len(vertices)):
				if normals[i] > 0:
					GL.glNormal3fv(_normals[normals[i] - 1])
				GL.glVertex3fv(_vertices[vertices[i] - 1])
			GL.glEnd() #Indicate the the polygon's description is complete.
		GL.glEndList() #Indicate that the instruction list is complete.
		
		
class _WeaponSystem(object):
	"""
	A control interface for generating weapon objects.
	"""
	_changed = True #: True when this system's HUD representation needs to be rebuilt.
	_selection_index = 0 #: The index of the currently selected bank, or None for special purposes.
	_units = None #: A list of all weapon-units available under this interface, as tuples of (unit, selected).
	
	def __init__(self, units):
		"""
		Initialises this _WeaponSystem, setting the list of all _WeaponUnits it
		has control over.
		
		@type units: sequence
		@param units: A collection of L{_WeaponUnit}s.
		"""
		self._units = [[unit, False] for unit in units]
		if self._units:
			self._units[self._selection_index][1] = True
			
	def cycleNext(self):
		"""
		Selects the next weapon-unit in the list.
		"""
		if self._units:
			self._selection_index = (self._selection_index + 1) % len(self._units)
			self._cycleUpdate()
			
	def cyclePrevious(self):
		"""
		Selects the previous weapon-unit in the list.
		"""
		if self._units:
			self._selection_index = (self._selection_index - 1) % len(self._units)
			self._cycleUpdate()
			
	def discharge(self, target, ode_space):
		"""
		Discharges all selected weapon-units, provided that they are able to be
		discharged.
		
		@type target: L{gameobjects.GameObject}
		@param target: The target the weapons are intended to hit.
		@type ode_space: ode.SpaceBase
		@param ode_space: The ODE space in which generated weapons will exist.
		
		@rtype: list
		@return: A list of all generated L{_Weapon}s.
		"""
		weapons = []
		for (unit, selected) in self._units:
			if selected:
				result = unit.discharge(target, ode_space)
				if result:
					weapons += result
					self._changed = True 
		return weapons
		
	def getHUDInformation(self):
		"""
		Returns information needed to display the status of the weapon-units under
		this _WeaponSystem's control to the user as part of the HUD.
		
		@rtype: list|None
		@return: A collection of (selected:bool, description:str, ready:bool)
		    tuples that describe the state of each weapon-unit, or None if nothing
		    has changed since the last time this _WeaponSystem's state was
		    displayed.
		"""
		if not self._changed:
			return None
			
		self._changed = False
		return [(selected, unit.describe(), unit.isReady()) for (unit, selected) in self._units]
		
	def reset(self):
		"""
		Rearms and reloads every weapon-unit under this _WeaponSystem.
		"""
		self._changed = True
		for (unit, selected) in self._units:
			unit.reset()
			
	def tick(self, seconds):
		"""
		Cools down each weapon-unit under this _WeaponSystem.
		
		@type seconds: float
		@param seconds: The number of seconds over which cooldown has occurred.
		"""
		for (unit, selected) in self._units:
			self._changed = unit.tick(seconds) or self._changed
			
	def _cycleUpdate(self):
		"""
		Updates each weapon-unit to set its selected state based on the selection
		index.
		"""
		self._changed = True
		for (i, unit) in enumerate(self._units):
			unit[1] = i == self._selection_index
			
class BoltSystem(_WeaponSystem):
	"""
	A control interface for generating bolt objects.
	"""
	def __init__(self, tank_chan, parser, bolt_list, type_list, modifier_list, power_source):
		"""
		Initialises a BoltSystem, attaching it to a player's ship and loading all
		of the _BoltUnits it requires.
		
		@type tank_chan: L{tankchan.TankChan}
		@param tank_chan: The player to which this BoltSystem belongs.
		@type parser: L{config.parser.Parser}
		@param parser: A parser that has opened the player's ship's descriptor
		    file.
		@type bolt_list: sequence
		@param bolt_list: A collection of XML nodes that describe the player's
		    ship's bolt-units.
		@type type_list: sequence
		@param type_list: A collection of factory-signatures that identify which
		    types of bolts will be loaded.
		@type modifier_list: sequence
		@param modifier_list: A collection of symbolic constants that identify the
		    modifiers that should be applied to each bolt-unit.
		@type power_source: int
		@param power_source: A symbolc constant that identifies the power source
		    of the player's ship.
		"""
		units = []
		for (bolt, bolt_type, modifier) in zip(bolt_list, type_list, modifier_list):
			rearm_time = float(parser.readData(bolt, 'rearm-time'))
			unit = _BoltUnit(tank_chan, bolt_type, rearm_time, modifier, power_source)
			for bank in parser.getCollection(bolt, None, 'bank'):
				x = float(parser.readData(bank, 'position.x'))
				y = float(parser.readData(bank, 'position.y'))
				z = float(parser.readData(bank, 'position.z'))
				multiplier = 1.0
				mult = parser.getCollection(bank, None, 'multiplier')
				if mult:
					multiplier = float(parser.readData(bank, 'multiplier'))
				unit.addBank(tank_chan, (x, y, z), multiplier)
			units.append(unit)
			
		_WeaponSystem.__init__(self, units)
		
	def cycleNext(self):
		"""
		Selects the next weapon-unit in the list, or all if the end of the list
		has been reached.
		"""
		if self._units:
			if not self._selection_index is None and self._selection_index == len(self._units) - 1:
				self._selectAll()
				self._selection_index = None
			else:
				if self._selection_index is None:
					self._selection_index = len(self._units) - 1
				_WeaponSystem.cycleNext(self)
				
	def cyclePrevious(self):
		"""
		Selects the previous weapon-unit in the list, or all if the start of the
		list has been reached.
		"""
		if self._units:
			if not self._selection_index is None and self._selection_index == 0:
				self._selectAll()
				self._selection_index = None
			else:
				if self._selection_index is None:
					self._selection_index = 0
				_WeaponSystem.cyclePrevious(self)
				
	def _selectAll(self):
		"""
		Selects all weapon-units under this BoltUnit.
		"""
		self._changed = True
		for unit in self._units:
			unit[1] = True
			
class MissileSystem(_WeaponSystem):
	"""
	A control interface for generating missile objects.
	"""
	def __init__(self, tank_chan, parser, missile_list, type_list):
		"""
		Initialises a MissileSystem, attaching it to a player's ship and loading
		all of the _MissileUnits it requires.
		
		@type tank_chan: L{tankchan.TankChan}
		@param tank_chan: The player to which this MissileSystem belongs.
		@type parser: L{config.parser.Parser}
		@param parser: A parser that has opened the player's ship's descriptor
		    file.
		@type missile_list: sequence
		@param missile_list: A collection of XML nodes that describe the player's
		    ship's missile-units.
		@type type_list: sequence
		@param type_list: A collection of factory-signatures that identify which
		    types of missiles will be loaded.
		"""
		units = []
		for (missile, missile_type) in zip(missile_list, type_list):
			x = float(parser.readData(missile, 'position.x'))
			y = float(parser.readData(missile, 'position.y'))
			z = float(parser.readData(missile, 'position.z'))
			capacity = int(parser.readData(missile, 'capacity'))
			rearm_time = float(parser.readData(missile, 'rearm-time'))
			units.append(_MissileUnit(tank_chan, (x, y, z), capacity, rearm_time, missile_type))
			
		_WeaponSystem.__init__(self, units)
		
		
class _WeaponUnit(object):
	"""
	An abstraction of an object that serves as a descriptor for weapons that may
	be discharged.
	"""
	_cooldown = None #: The amount of time this weapon-unit takes to cool down between uses.
	_cooldown_remaining = None #: The amount of time remaining until this weapon-unit is ready to be used again.
	
	def discharge(self, target, ode_space):
		"""
		Generates weapon-objects.
		
		@type target: L{gameobjects.GameObject}|None
		@param target: The target this weapon is intended to hit, or None if no
		    target exists.
		@type ode_space: ode.SpaceBase|None
		@param ode_space: The ODE space in which this weapon will exist, or None
		    if no ODE presence is desired.
		
		@rtype: list|None
		@return: The L{_Weapon}s that were generated or None if no weapons could
		    be produced.
		"""
		if self._cooldown_remaining is None:
			result = self._discharge(target, ode_space)
			if result:
				self._cooldown_remaining = self._cooldown
				return result
		return None
		
	def isReady(self):
		"""
		Indicates whether this weapon-unit is ready to be discharged.
		
		@rtype: bool
		@return: True if this weapon-unit may be discharged.
		"""
		return self._cooldown_remaining is None
		
	def reset(self):
		"""
		Rearms this weapon-unit.
		"""
		self._cooldown_remaining = None
		
	def tick(self, seconds):
		"""
		Cools down this weapon-unit, eventually making it ready for use.
		
		@type seconds: float
		@param seconds: The number of seconds over which cooldown has occurred.
		
		@rtype: bool
		@return: True if the status of this weapon-unit has changed.
		"""
		if self._cooldown_remaining is None:
			return False
			
		self._cooldown_remaining -= seconds
		if self._cooldown_remaining <= 0.0:
			self._cooldown_remaining = None
			return True
			
class _BoltUnit(_WeaponUnit):
	"""
	An object that serves as a descriptor for bolts that may be discharged.
	"""
	_banks = None #: A list of all banks under this bolt-unit.
	_bolt_factory = None #: The _BoltFactory responsible for generating the bolts produced by this unit's banks.
	_multiplier_damage = None #: The damage multiplier to apply to all bolts discharged by this _BoltUnit.
	_multiplier_energy = None #: The energy multiplier to apply to all bolts discharged by this _BoltUnit.
	_required_energy = None #: The amount of energy needed to discharge this BoltSystem.
	_tank_chan = None #: The Tank-chan that commands this BoltSystem, referenced to check for necessary energy.
	
	def __init__(self, tank_chan, bolt_type, rearm_time, modifier, power_source):
		"""
		Initialises this _BoltUnit, setting the ship to which it belongs, the type
		of bolts it discharges, its cooldown time, and its base/power modifiers.
		
		@type tank_chan: L{tankchan.TankChan}
		@param tank_chan: The ship to which this _MissileUnit belongs.
		@type bolt_type: str
		@param bolt_type: The factory-ID of the type of bolts that this
		    _BoltUnit will discharge.
		@type rearm_time: float
		@param rearm_time: The number of seconds it takes for this _BoltUnit to
		    cool down, independent of the cooldown time of the bolt type it
		    discharges.
		@type modifier: int
		@param modifier: A symbolic constant identifying the modifier applied to
		    this _BoltUnit.
		@type power_source: int
		@param power_source: A symbolic constant identifying the modifier applied
		    to the ship that owns this _BoltUnit.
		"""
		(power_damage, power_energy, power_cooldown) = _POWER_SOURCE_EFFECTS[power_source]
		(modifier_damage, modifier_energy, modifier_cooldown) = _MODIFIER_EFFECTS[modifier]
		
		self._tank_chan = tank_chan
		self._required_energy = 0
		self._bolt_factory = BOLTS[bolt_type]
		self._cooldown = (rearm_time + self._bolt_factory.getCooldown()) * power_cooldown * modifier_cooldown
		self._banks = []
		
		self._multiplier_damage = power_damage * modifier_damage
		self._multiplier_energy = power_energy * modifier_energy
		
	def addBank(self, tank_chan, position, multiplier):
		"""
		Adds a bolt-bank to this BoltUnit, increasing the number of bolts it
		discharges.
		
		@type tank_chan: L{tankchan.TankChan}
		@param tank_chan: The ship to which this BoltUnit belongs.
		@type position: sequence(3)
		@param position: The (x, y, z) offset of the new bank, relative to the
		    centre of the player's ship.
		@type multiplier: float
		@param multiplier: The damage multiplier to apply to the new bank.
		"""
		self._banks.append(_BoltBank(tank_chan, position, multiplier))
		self._required_energy += self._bolt_factory.getEnergy() * multiplier * self._multiplier_energy
		
	def describe(self):
		"""
		Returns the string description to use to identify this BoltUnit in the
		player's HUD.
		
		@rtype: str
		@return: The string description of this BoltUnit.
		"""
		return self._bolt_factory.getName()
		
	def _discharge(self, target, ode_space):
		"""
		Generates bolts.
		
		@type target: L{gameobjects.GameObject}|None
		@param target: The target these bolts are intended to hit, or None if no
		    target exists.
		@type ode_space: ode.SpaceBase|None
		@param ode_space: The ODE space in which these bolts will exist, or None
		    if no ODE presence is desired.
		
		@rtype: list|None
		@return: The L{Bolt}s that were generated or None if no bolts could
		    be produced.
		"""
		if not self._tank_chan.consumeEnergy(self._required_energy):
			return None
			
		bolts = []
		for bank in self._banks:
			bolts.append(
			 self._bolt_factory.createBolt(
			  self._multiplier_damage * bank.getMultiplier(),
			  self._tank_chan.getRotationMatrix(),
			  bank.getPosition(), bank.getOffset(),
			  target, self._tank_chan, ode_space
			 )
			)
		return bolts
		
class _MissileUnit(_WeaponUnit, _WeaponBank):
	"""
	An object that serves as a descriptor for missiles that may be discharged.
	"""
	_missile_factory = None #: The _MissileFactory responsible for generating the missiles produced by this unit.
	_quantity = None #: The number of missiles thia bank is able to hold.
	_quantity_remaining = None #: The number of missiles left in this bank.
	
	def __init__(self, tank_chan, position, capacity, rearm_time, missile_type):
		"""
		Initialises this _MissileUnit, setting its capacity, cooldown time, and
		payload type.
		
		@type tank_chan: L{tankchan.TankChan}
		@param tank_chan: The ship to which this _MissileUnit belongs.
		@type position: sequence(3)
		@param position: The (x, y, z) offsets of this _MissileUnit, relative to
		    the centre of the ship to which it is attached.
		@type capacity: int
		@param capacity: The size of this _MissileUnit.
		@type rearm_time: float
		@param rearm_time: The number of seconds it takes for this _MissileUnit to
		    cool down, independent of the cooldown time of the missile type it
		    contains.
		@type missile_type: str
		@param missile_type: The factory-ID of the type of missiles that this
		    _MissileUnit will contain.
		"""
		_WeaponBank.__init__(self, tank_chan, position)
		self._missile_factory = MISSILES[missile_type]
		self._quantity = self._quantity_remaining = capacity / self._missile_factory.getSize()
		self._cooldown = rearm_time + self._missile_factory.getCooldown()
		
	def describe(self):
		"""
		Returns the string description to use to identify this MissileUnit in the
		player's HUD. This includes the number of remaining missiles.
		
		@rtype: str
		@return: The string description of this MissileUnit.
		"""
		return "%02i%s" % (self._quantity_remaining, self._missile_factory.getName())
		
	def isReady(self):
		"""
		Indicates whether this MissileUnit is ready to be discharged.
		
		@rtype: bool
		@return: True if this MissileUnit may be discharged.
		"""
		return self._quantity_remaining and _WeaponUnit.isReady(self)
		
	def reset(self):
		"""
		Rearms and reloads this MissileUnit.
		"""
		_WeaponUnit.reset(self)
		self._quantity_remaining = self._quantity
		
	def _discharge(self, target, ode_space):
		"""
		Generates missiles.
		
		@type target: L{gameobjects.GameObject}|None
		@param target: The target these missiles are intended to hit, or None if
		    no target exists.
		@type ode_space: ode.SpaceBase|None
		@param ode_space: The ODE space in which these missile will exist, or None
		    if no ODE presence is desired.
		
		@rtype: list|None
		@return: The L{Missile}s that were generated or None if no missiles could
		    be produced.
		"""
		if not target or not self._quantity_remaining:
			return None
			
		self._quantity_remaining -= 1
		return [
		 self._missile_factory.createMissile(
		  self._tank_chan.getRotationMatrix(),
		  self.getPosition(),
		  target,
		  self._tank_chan,
		  ode_space
		 )
		]
		
def initBolts():
	"""
	Loads all bolt descriptors and variables, generating all bolt properties
	needed by this module.
	
	@raise IOError: If an XML file cannot be read.
	"""
	global _BOLT_ODE_SIZE
	_BOLT_ODE_SIZE = (
	 _BOLT_WIDTH, #x
	 _BOLT_WIDTH, #y
	 1.1 * BOLT_SPEED / session_constants.FPS #z - allow for 10% lag.
	)
	
	model_path = path_utils.getObjectsPath()
	parser = config.parser.Parser(model_path + "bolts.xml")
	
	for bolt in parser.getCollection(None, 'bolts', 'bolt'):
		id = parser.readData(bolt, 'identification.id')
		BOLTS[id] = _BoltFactory(
		 model_path,
		 parser.readData(bolt, 'descriptor').replace('/', os.sep),
		 id
		)
		
def initMissiles():
	"""
	Loads all missile descriptors and variables, generating all missile properties
	needed by this module.
	
	@raise IOError: If an XML file, or OBJ file/MTL file, if applicable, cannot
	    be read.
	"""
	model_path = path_utils.getObjectsPath()
	parser = config.parser.Parser(model_path + "missiles.xml")
	
	for missile in parser.getCollection(None, 'missiles', 'missile'):
		id = parser.readData(missile, 'identification.id')
		MISSILES[id] = _MissileFactory(
		 model_path,
		 parser.readData(missile, 'descriptor').replace('/', os.sep),
		 id
		)
		