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

Purpose
=======
 Provides routines for creating, maintaining, and rendering Tank-chans.
 
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 ode
import OpenGL.GL as GL
import os
import pygame
import random
import re

import constants
import gameobjects
import hud
import matrix
import path_utils
import session_constants
import weapons

import config.parser

import rendering.shading as shading
import rendering.rendering as rendering

SUBSYSTEM_CONDITION_BROKEN = 0 #: A constant that marks a subsystem's condition as broken.
SUBSYSTEM_CONDITION_CRIPPLED = 1 #: A constant that marks a subsystem's condition as crippled.
SUBSYSTEM_CONDITION_FUNCTIONAL = 2 #: A constant that marks a subsystem's condition as functional.
SUBSYSTEM_CONDITION_FAIR = 3 #: A constant that marks a subsystem's condition as fair.
SUBSYSTEM_CONDITION_GOOD = 4 #: A constant that marks a subsystem's condition as good.

SUBSYSTEM_CONDITION_MAP = {
 SUBSYSTEM_CONDITION_BROKEN: "brkn",
 SUBSYSTEM_CONDITION_CRIPPLED: "crpl",
 SUBSYSTEM_CONDITION_FUNCTIONAL: "func",
 SUBSYSTEM_CONDITION_FAIR: "fair",
 SUBSYSTEM_CONDITION_GOOD: "good"
} #: A mapping between subsystem condition constants and string representations.

SUBSYSTEM_SENSORS = 1 #: A constant that identifies a ship's sensors subsystem.
SUBSYSTEM_ENGINES = 2 #: A constant that identifies a ship's engines subsystem.
SUBSYSTEM_SHIELDS = 3 #: A constant that identifies a ship's shields subsystem.
SUBSYSTEM_WEAPONS = 4 #: A constant that identifies a ship's weapons subsystem.
SUBSYSTEM_PROJECTION = 5 #: A constant that identifies a ship's shielding projection.

_ECM_RESTORE_TIME = 7.5 #: The number of seconds that must elapse before an ECM will be replenished.

_SUBSYSTEM_MULTIPLIER_MINIMUM = 0.25 #: The effectiveness of a broken subsystem.
_SUBSYSTEM_MULTIPLIER_MAXIMUM = 1.0 - _SUBSYSTEM_MULTIPLIER_MINIMUM #: The range over which subsystem damage will affect performance.

_SURFACE_TARGET_HOSTILE = None #: A PyGame Surface used to indicate that a ship is hostile and the player's target.
_SURFACE_ONSCREEN_HOSTILE = None #: A PyGame Surface used to indicate that a ship is hostile and not the player's target.
_SURFACE_TARGET_FRIENDLY = None #: A PyGame Surface used to indicate that a ship is friendly and the player's target.
_SURFACE_ONSCREEN_FRIENDLY = None #: A PyGame Surface used to indicate that a ship is friendly and not the player's target.
_SURFACE_INDICATOR_BASE = None #: A PyGame Surface used as the base on which status, friendliness, and target-status information will be drawn.
_SURFACE_BLITMASK = None #: A PyGame Surface that blits non-circular regions out of the indicator's status.

_HIGH_POLY_DRAW_LIMIT = 1250.0 #: The maximum distance at which high-polygon versions of ships should be drawn.
_INDICATOR_DRAW_LIMIT = 500.0 #: The maximum distance at which the indicator overlay should be drawn.

_INDICATOR_REDRAW_HERTZ = 3 #: The frequency at which indicators should be redrawn.
_INDICATOR_REDRAW_FRAME = None #: The frame-count at which indicators should be redrawn, computed based on frequency and FPS.

_POWER_TEXTURES = {} #: A mapping between power source constants and OpenGL texture IDs.
_RETICLE_LEAD_TEXTURE = None #: The OpenGL texture ID to use when drawing the lead-reticle in front of a target.

_POWER_SOURCE_EFFECTS = {
 constants.POWER_LOVE: (1.0, 1.1, 1.0, 1.0, 1.0, 1.1),
 constants.POWER_STARLIGHT: (1.0, 1.0, 1.0, 1.0, 1.0, 1.0),
 constants.POWER_MOE: (1.0, 1.0, 1.0, 1.0, 1.1, 1.0),
 constants.POWER_RAINBOW: (1.0, 1.0, 1.0, 1.0, 0.909, 1.1),
 constants.POWER_HAPPINESS: (1.1, 1.0, 1.0, 1.0, 1.0, 1.0)
} #: Hull multiplier, shield multiplier, subsystem multiplier, energy multiplier, speed multiplier, tacking multiplier.

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

class TankChan(gameobjects.MobileGameObject):
	"""
	This class represents a Tank-chan that, in turn, represents a player.
	"""
	_acceleration = None #: The speed, in m/s, that this Tank-chan can shift through in a second.
	_alive = True #: False when this Tank-chan has kersploded.
	_bolt_system = None #: A bolt system interface object.
	_current_energy = None #: The current energy of this Tank-chan.
	_current_engines_integrity = None #: The current integrity of this Tank-chan's engines subsystem.
	_current_engines_multiplier = None #: The current integrity multiplier of this Tank-chan's engines subsystem.
	_current_hull = None #: The current hull integrity of this Tank-chan.
	_current_sensors_integrity = None #: The current integrity of this Tank-chan's sensors subsystem.
	_current_shileding = None #: The current shield integrity of this Tank-chan.
	_current_shields_integrity = None #: The current integrity of this Tank-chan's shields subsystem.
	_current_shields_multiplier = None #: The current integrity multiplier of this Tank-chan's shields subsystem.
	_current_weapons_integrity = None #: The current integrity of this Tank-chan's weapons subsystem.
	_current_weapons_multiplier = None #: The current integrity multiplier of this Tank-chan's weapons subsystem.
	_ecm_capacity = None #: The number of ECMs that this Tank-chan can hold.
	_ecm_countdown = None #: The number of seconds that must pass before this Tank-chan replenishes an ECM.
	_ecm_stock = None #: The number of ECMs that this Tank-chan holds.
	_energy = None #: The maximum energy reserve capacity of this Tank-chan.
	_engines_integrity = None #: The maximum integrity of this Tank-chan's engines subsystem.
	_engines_integrity_1 = None #: 1% of the maximum integrity of this Tank-chan's engines subsystem.
	_engines_integrity_25 = None #: 25% of the maximum integrity of this Tank-chan's engines subsystem.
	_engines_integrity_50 = None #: 50% of the maximum integrity of this Tank-chan's engines subsystem.
	_engines_integrity_75 = None #: 75% of the maximum integrity of this Tank-chan's engines subsystem.
	_frame = 0 #: The current frame, used to handle FPS-sensitive rendering operations.
	_gl_list_id = None #: An integer that identifies this object's OpenGL List binding.
	_gl_list_id_low_poly = None #: An integer that identifies this object's low-polygon OpenGL List binding.
	_hull = None #: The maximum hull integrity of this Tank-chan.
	_indicator_changed = True #: True when the indicator needs to be redrawn.
	_indicator_texture_id = None #: The ID of the indicator texture used to highlight this Tank-chan.
	_missile_system = None #: A missile system interface object.
	_ode_geoms = None #: A {geom : (subsystem, offset, rotation)} collection of ODE geometries associated with this Tank-chan.
	_ode_projection = None #: The ODE GeomSphere that serves as this Tank-chan's projection radius, plus its offset.
	_ode_space = None #: The ODE space in which this player exists.
	_player_id = None #: This Tank-chan's ID.
	_player_name = None #: The name of this Tank-chan's player.
	_power_source = None #: This Tank-chan's power source.
	_projection = None #: This Tank-chan's shield projection's radius and position.
	_radar_icons = None #: The icons used to position this Tank-chan on the radar.
	_rated_speed = None #: The speed that this Tank-chan was designed to maintain.
	_reactor_output = None #: The amount of energy units generated per second.
	_reactor_energy = 0.25 #: The current amount of reactor output allocated to energy reserves.
	_reactor_engines = 0.5 #: The current amount of reactor output allocated to engines.
	_reactor_shielding = 0.25 #: The current amount of reactor output allocated to shielding.
	_rotation_matrix = None #: The rotation matrix associated with this Tank-chan's current position.
	_sensors_integrity = None #: The maximum integrity of this Tank-chan's sensors subsystem.
	_sensors_integrity_1 = None #: 1% of the maximum integrity of this Tank-chan's sensors subsystem.
	_sensors_integrity_25 = None #: 25% of the maximum integrity of this Tank-chan's sensors subsystem.
	_sensors_integrity_50 = None #: 50% of the maximum integrity of this Tank-chan's sensors subsystem.
	_sensors_integrity_75 = None #: 75% of the maximum integrity of this Tank-chan's sensors subsystem.
	_shielding = None #: The maximum shield integrity of this Tank-chan.
	_shields_down = False #: True when shields have been knocked offline.
	_shields_integrity = None #: The maximum integrity of this Tank-chan's shields subsystem.
	_shields_integrity_1 = None #: 1% of the maximum integrity of this Tank-chan's shields subsystem.
	_shields_integrity_25 = None #: 25% of the maximum integrity of this Tank-chan's shields subsystem.
	_shields_integrity_50 = None #: 50% of the maximum integrity of this Tank-chan's shields subsystem.
	_shields_integrity_75 = None #: 75% of the maximum integrity of this Tank-chan's shields subsystem.
	_speed_multiplier = None #: The amount of energy required per m/s of applied top speed.
	_target_speed = 0 #: The speed that this Tank-chan is currently trying to achieve.
	_team_id = None #: The team to which this Tank-chan belongs.
	_thrusters = None #: A list of all thruster offsets and radiuseses.
	_weapons_integrity = None #: The maximum integrity of this Tank-chan's weapons subsystem.
	_weapons_integrity_1 = None #: 1% of the maximum integrity of this Tank-chan's weapons subsystem.
	_weapons_integrity_25 = None #: 25% of the maximum integrity of this Tank-chan's weapons subsystem.
	_weapons_integrity_50 = None #: 50% of the maximum integrity of this Tank-chan's weapons subsystem.
	_weapons_integrity_75 = None #: 75% of the maximum integrity of this Tank-chan's weapons subsystem.
	_x_rotation = None #: The efficiency, in %, of this Tank-chan in rotating around the X-axis.
	_y_rotation = None #: The efficiency, in %, of this Tank-chan in rotating around the Y-axis.
	_z_rotation = None #: The efficiency, in %, of this Tank-chan in rotating around the Z-axis.
	
	def __init__(self, filepath, render_mode, player_name, player_id, team_id, power_source, bolt_types, bolt_modifiers, missile_types, ode_space):
		"""
		This function is invoked when a new TankChan object is created.
		
		@type filepath: basestring
		@param filepath: The path of the XML file that describes this TankChan.
		@type render_mode: int
		@param render_mode: An integer that defines the manner in which the object
		    should be rendered.
		@type player_name: basestring
		@param player_name: The name of this Tank-chan's player.
		@type player_id: int
		@param player_id: The ID of the player being rendered.
		@type team_id: int
		@param team_id: The ID of the team being rendered.
		@type power_source: int
		@param power_source: The type of energy that powers this Tank-chan.
		@type bolt_types: sequence | None
		@param bolt_types: A collection, equal in length to the number of bolt-units on this Tank-chan, that indicates which type of weaponry is to be mounted.
		@type bolt_modifiers: sequence | None
		@param bolt_modifiers: A collection, equal in length to the number of bolt-units on this Tank-chan, that indicates the modifier effect to apply to each unit.
		@type missile_types: sequence | None
		@param missile_types: A collection, equal in length to the number of missile-units on this Tank-chan, that indicates which type of weaponry is to be mounted.
		@type ode_space: ode.SpaceBase
		@param ode_space: The ODE space in which this Tank-chan will exist.
		
		@return: Nothing.
		
		@raise AssertionError: If the supplied arguments are of invalid types.
		@raise Exception: If another error occurs, likely because of corrupt data.
		@raise IOError: If the specified XML file, or OBJ file/MTL file, if
		    applicable, cannot be read.
		@raise KeyError: If a named material is missing.
		"""
		self._player_name = player_name
		self._player_id = player_id
		self._team_id = team_id
		self._power_source = power_source
		self._ode_space = ode_space
		
		(hull_multiplier, shield_multiplier, subsystems_multiplier, energy_multiplier, speed_multiplier, tacking_multiplier) = _POWER_SOURCE_EFFECTS[power_source]
		
		parser = config.parser.Parser(filepath)
		obj_filepath = filepath[:-(len(filepath) - filepath.rfind(os.sep))] + os.sep + parser.readData(None, 'tank-chan.obj').replace('/', os.sep)
		
		tc_specifications = parser.getCollection(None, 'tank-chan', 'specifications')[0]
		#Set up Tank-chan properties.
		self._reactor_output = int(parser.readData(tc_specifications, 'reactor-output'))
		self._rated_speed = float(parser.readData(tc_specifications, 'speed'))
		self._speed_multiplier = (self._rated_speed / float(parser.readData(tc_specifications, 'engine-draw'))) * speed_multiplier
		self._acceleration = int(parser.readData(tc_specifications, 'acceleration'))
		self._x_rotation = (float(parser.readData(tc_specifications, 'tacking.pitch'))) * tacking_multiplier
		self._y_rotation = (float(parser.readData(tc_specifications, 'tacking.yaw'))) * tacking_multiplier
		self._z_rotation = (float(parser.readData(tc_specifications, 'tacking.roll'))) * tacking_multiplier
		self._current_hull = self._hull = int(float(parser.readData(tc_specifications, 'hull')) * hull_multiplier)
		self._current_energy = self._energy = int(float(parser.readData(tc_specifications, 'energy')) * energy_multiplier)
		self._current_shielding = self._shielding = int(float(parser.readData(tc_specifications, 'shielding')) * shield_multiplier)
		self._ecm_stock = self._ecm_capacity = int(parser.readData(tc_specifications, 'ecm-capacity'))
		
		self._current_engines_multiplier = self._current_shields_multiplier = self._current_weapons_multiplier = 1.0
		
		#Set up weapons. (But only if this is the player's ship)
		if bolt_types:
			self._setWeapons(parser, bolt_types, bolt_modifiers, missile_types)
			
		#Set up thrusters.
		self._setThrusters(parser)
		
		#Set up subsystems.
		self._setSubsystems(parser, tc_specifications, subsystems_multiplier)
		self._toggleShields(True)
		self._toggleHitboxes(False)
		
		#Set up visual elements.
		(_vertices, _normals, _faces, _materials) = gameobjects.loadOBJ(obj_filepath, (_mtlOverride, player_id, team_id))
		self._buildGLList(_vertices, _normals, _faces, _materials, player_id, team_id, render_mode)
		self._buildGLListLowPoly(player_id, team_id, render_mode)
		
		#Assign radar icons.
		self._radar_icons = session_constants.RADAR_ICONS[team_id]
		
		#Initialise physical properties.
		gameobjects.MobileGameObject.__init__(self)
		
	def applyRotation(self, x_intensity, y_intensity, z_intensity):
		"""
		Rotates this TankChan by a factor of its maneuverability values.
		
		@type x_intensity: float
		@param x_intensity: The multiplier to apply against this TankChan's pitch
		    maneuverability to determine its rotation along the X-axis.
		@type y_intensity: float
		@param y_intensity: The multiplier to apply against this TankChan's yaw
		    maneuverability to determine its rotation along the Y-axis.
		@type z_intensity: float
		@param z_intensity: The multiplier to apply against this TankChan's roll
		    maneuverability to determine its rotation along the Z-axis.
		
		@rtype: tuple(3)
		@return: The (x, y, z) rotation applied to this TankChan.
		"""
		rotation = (x_intensity * self._x_rotation, y_intensity * self._y_rotation, z_intensity * self._z_rotation)
		self.updateVectors(None, rotation)
		return rotation
		
	def consumeEnergy(self, energy):
		"""
		Indicates whether this TankChan has enough energy to satisfy the amount
		requested, reducing its reserves if it does.
		
		@type energy: number
		@param energy: The amount of energy being requested.
		
		@rtype: bool
		@return: True if the request was satisfied. 
		"""
		if self._current_energy >= energy:
			self._current_energy -= energy
			return True
		return False
		
	def damageHull(self, damage):
		"""
		Applies damage to this TankChan's hull.
		
		If this function returns False, L{TankChan.explode} should be invoked.
		
		@type damage: number
		@param damage: The amount of damage being applied.
		
		@rtype: bool
		@return: False if this TankChan's hull has been breached.
		"""
		self._indicator_changed = True
		self._current_hull = max(0, self._current_hull - damage)
		return not self._current_hull == 0
		
	def damageShielding(self, damage):
		"""
		Applies damage to this TankChan's shields.
		
		If this function returns False, L{TankChan.lowerShields} should be
		invoked.
		
		@type damage: number
		@param damage: The amount of damage being applied.
		
		@rtype: bool
		@return: False if this TankChan's shields have been depleted.
		"""
		self._indicator_changed = True
		self._current_shielding = max(0, self._current_shielding - damage)
		return not self._current_shielding == 0
		
	def damageSubsystem(self, damage, subsystem):
		"""
		Applies damage to one of this TankChan's subsystems.
		
		@type damage: number
		@param damage: The amount of damage being applied.
		
		@rtype: bool
		@return: False if this TankChan's subsystem has been broken.
		"""
		if subsystem == SUBSYSTEM_SENSORS:
			self._current_sensors_integrity = max(0, self._current_sensors_integrity - damage)
			return not self._current_sensors_integrity == 0
		elif subsystem == SUBSYSTEM_ENGINES:
			self._current_engines_integrity = max(0, self._current_engines_integrity - damage)
			self._current_engines_multiplier = (_SUBSYSTEM_MULTIPLIER_MINIMUM + _SUBSYSTEM_MULTIPLIER_MAXIMUM * (self._current_engines_integrity / self._engines_integrity))
			return not self._current_engines_integrity == 0
		elif subsystem == SUBSYSTEM_SHIELDS:
			self._current_shields_integrity = max(0, self._current_shields_integrity - damage)
			self._current_shields_multiplier = (_SUBSYSTEM_MULTIPLIER_MINIMUM + _SUBSYSTEM_MULTIPLIER_MAXIMUM * (self._current_shields_integrity / self._shields_integrity))
			self._current_shielding = min(self._current_shielding, self._shielding * self._current_shields_multiplier)
			return not self._current_shields_integrity == 0
		elif subsystem == SUBSYSTEM_WEAPONS:
			self._current_weapons_integrity = max(0, self._current_weapons_integrity - damage)
			self._current_weapons_multiplier = (_SUBSYSTEM_MULTIPLIER_MINIMUM + _SUBSYSTEM_MULTIPLIER_MAXIMUM * (self._current_weapons_integrity / self._weapons_integrity))
			self._current_energy = min(self._current_energy, self._energy * self._current_weapons_multiplier)
			return not self._current_weapons_integrity == 0
			
	def describe(self, current_time):
		"""
		Provides a serialized description of this TankChan that can be used to
		synchronize its appearance across multiple clients.
		
		@type current_time: int
		@param current_time: The timestamp at which this TankChan was serialized,
		    in milliseconds.
		
		@rtype: str
		@return: The string that describes the current state of this TankChan.
		"""
		(x_rot, y_rot, z_rot) = self.getRotationEuler()
		return "PPOS %s %i %f,%f,%f %f,%f,%f %f,%f %f %f %f %f %f %f %f %i %f,%f,%f" % (
		 self._player_name,
		 current_time,
		 self._pos_x, self._pos_y, self._pos_z,
		 x_rot, y_rot, z_rot,
		 max(0.0, self._current_speed), max(0.0, self._target_speed),
		 self._current_hull, self._current_energy, self._current_shielding,
		 self._current_sensors_integrity, self._current_engines_integrity,
		 self._current_shields_integrity, self._current_weapons_integrity,
		 self._shields_down,
		 self._reactor_engines, self._reactor_shielding, self._reactor_energy
		)
		
	def destroy(self):
		"""
		Removes all potentially memory-leak-causing elements associated with this
		TankChan, as well as marking it as dead.
		
		This function should be called whenever a TankChan is permanently removed
		from the game.
		"""
		self._alive = False
		
		#Kill the OpenGL lists.
		GL.glDeleteLists(self._gl_list_id, 1)
		GL.glDeleteLists(self._gl_list_id_low_poly, 1)
		
		#Kill the ODE objects.
		space = self._ode_projection[0].getSpace()
		
		self._ode_projection[0].disable()
		space.remove(self._ode_projection[0])
		for ode_geom in self._ode_geoms:
			ode_geom.disable()
			space.remove(ode_geom)
			
	def dischargeBolts(self, target):
		"""
		Causes this TankChan to fire its primary weapons.
		
		@type target: L{gameobject.GameObject}|None
		@param target: The target that any generated bolts are intended to hit.
		
		@rtype: list
		@return: A list of all newly created bolts.
		"""
		return self._bolt_system.discharge(target, self._ode_space)
		
	def dischargeECM(self):
		"""
		Causes this TankChan to launch an ECM, if any are available.
		
		@rtype: list
		@return: A list of all newly created ECMs.
		"""
		if self._ecm_stock:
			self._ecm_stock -= 1
			return [weapons.ECM(self, self._ode_space)]
		return []
		
	def dischargeMissile(self, target):
		"""
		Causes this TankChan to fire its secondary weapons.
		
		@type target: L{TankChan}|None
		@param target: The target that any generated missiles are intended to hit.
		    While this can be None, missiles will not be launched without a valid
		    target.
		
		@rtype: list
		@return: A list of all newly created missiles.
		"""
		if target and target.isAlive():
			return self._missile_system.discharge(target, self._ode_space)
		return []
		
	def explode(self):
		"""
		Marks this TankChan as dead and temporarily disables its ODE presence.
		"""
		self._alive = False
		
		self._toggleShields(False)
		self._toggleHitboxes(False)
		
	def getECMStock(self):
		"""
		Returns the number of ECMS currently available to the TankChan.
		
		@rtype: int
		@return: The number of ECMs available.
		"""
		return self._ecm_stock
		
	def getEnergy(self):
		"""
		Returns the TankChan's current amount of energy and current maximum energy
		capacity.
		
		@rtype: tuple(2)
		@return: The TankChan's current amount of energy and current maximum
		    reserve.
		"""
		return (self._current_energy, self._energy * self._current_weapons_multiplier)
		
	def getHull(self):
		"""
		Returns the TankChan's current and maximum hull integrity.
		
		@rtype: tuple(2)
		@return: The TankChan's current and maximum hull integrity.
		"""
		return (self._current_hull, self._hull)
		
	def getMaxSpeed(self):
		"""
		Returns the maximum speed the TankChan is currently capable of reaching.
		
		@rtype: float
		@return: The maximum speed the TankChan is currently capable of reaching.
		"""
		return (self._speed_multiplier * self._current_engines_multiplier) * (0.01 + 0.99 * self._reactor_output * self._reactor_engines)
		
	def getName(self):
		"""
		Returns the name of the player who controls this TankChan.
		
		For all practical purposes, this is the same as the TankChan's name.
		
		@rtype: basestring
		@return: The name of the player who controls this TankChan.
		"""
		return self._player_name
		
	def getPlayerID(self):
		"""
		Returns the ID of the player who controls this TankChan.
		
		For all practical purposes, this is the same as the TankChan's ID.
		
		@rtype: int
		@return: The ID of the player who controls this TankChan.
		"""
		return self._player_id
		
	def getRadarIcons(self):
		"""
		Returns the icons to be used when displaying this player on the radar.
		
		@rtype: tuple(3)
		@return: Three PyGame Surfaces that represent the
		    (same_level, above, below) positions of this player on the radar.
		"""
		return self._radar_icons
		
	def getReactorDistribution(self):
		"""
		Returns the current energy allocations of this TankChan's reactor.
		
		@rtype: tuple(3)
		@return: This TankChan's engines allocation, shields allocation, and
		    energy allocation, expressed as percentages.
		"""
		return (self._reactor_engines, self._reactor_shielding, self._reactor_energy)
		
	def getShielding(self):
		"""
		Returns this TankChan's current and current maximum shield integrity
		values.
		
		@rtype: tuple(2)
		@return: This TankChan's current and current maximum shield integrity
		    values.
		"""
		return (self._current_shielding, self._shielding * self._current_shields_multiplier, self._shielding, not self._shields_down)
		
	def getSpeed(self):
		"""
		Returns this TankChan's current, maximum, and effective target speeds.
		
		@rtype: tuple(3)
		@return: This TankChan's current, maximum, and effective target speeds.
		"""
		max_speed = self.getMaxSpeed()
		return (self._current_speed, max_speed, min(self._target_speed, max_speed))
		
	def getSubsystem(self, subsystem):
		"""
		Returns the current and maximum integrity of one of this TankChan's
		subsystems.
		
		@type subsystem: int
		@param subsystem: A constant that identifies the subsystem for which
		    data should be retrieved.
		
		@rtype: tuple(2)
		@return: The current and maximum integrity values of the specified
		    subsystem.
		"""
		if subsystem == SUBSYSTEM_SENSORS:
			return (self._current_sensors_integrity, self._sensors_integrity)
		elif subsystem == SUBSYSTEM_ENGINES:
			return (self._current_engines_integrity, self._engines_integrity)
		elif subsystem == SUBSYSTEM_SHIELDS:
			return (self._current_shields_integrity, self._shields_integrity)
		elif subsystem == SUBSYSTEM_WEAPONS:
			return (self._current_weapons_integrity, self._weapons_integrity)
			
	def getSubsystemCondition(self, subsystem):
		"""
		Returns the relative condition of one of this TankChan's subsystems.
		
		@type subsystem: int
		@param subsystem: A constant that identifies the subsystem for which
		    data should be retrieved.
		
		@rtype: int
		@return: The relative condition of the specified subsystem, expressed as
		    a symbolic condition constant.
		"""
		if subsystem == SUBSYSTEM_SENSORS:
			if self._current_sensors_integrity > self._sensors_integrity_75:
				return SUBSYSTEM_CONDITION_GOOD
			elif self._current_sensors_integrity > self._sensors_integrity_50:
				return SUBSYSTEM_CONDITION_FAIR
			elif self._current_sensors_integrity > self._sensors_integrity_25:
				return SUBSYSTEM_CONDITION_FUNCTIONAL
			elif self._current_sensors_integrity > 0.0:
				return SUBSYSTEM_CONDITION_CRIPPLED
			return SUBSYSTEM_CONDITION_BROKEN
		elif subsystem == SUBSYSTEM_ENGINES:
			if self._current_engines_integrity > self._engines_integrity_75:
				return SUBSYSTEM_CONDITION_GOOD
			elif self._current_engines_integrity > self._engines_integrity_50:
				return SUBSYSTEM_CONDITION_FAIR
			elif self._current_engines_integrity > self._engines_integrity_25:
				return SUBSYSTEM_CONDITION_FUNCTIONAL
			elif self._current_engines_integrity > 0.0:
				return SUBSYSTEM_CONDITION_CRIPPLED
			return SUBSYSTEM_CONDITION_BROKEN
		elif subsystem == SUBSYSTEM_SHIELDS:
			if self._current_shields_integrity > self._shields_integrity_75:
				return SUBSYSTEM_CONDITION_GOOD
			elif self._current_shields_integrity > self._shields_integrity_50:
				return SUBSYSTEM_CONDITION_FAIR
			elif self._current_shields_integrity > self._shields_integrity_25:
				return SUBSYSTEM_CONDITION_FUNCTIONAL
			elif self._current_shields_integrity > 0.0:
				return SUBSYSTEM_CONDITION_CRIPPLED
			return SUBSYSTEM_CONDITION_BROKEN
		elif subsystem == SUBSYSTEM_WEAPONS:
			if self._current_weapons_integrity > self._weapons_integrity_75:
				return SUBSYSTEM_CONDITION_GOOD
			elif self._current_weapons_integrity > self._weapons_integrity_50:
				return SUBSYSTEM_CONDITION_FAIR
			elif self._current_weapons_integrity > self._weapons_integrity_25:
				return SUBSYSTEM_CONDITION_FUNCTIONAL
			elif self._current_weapons_integrity > 0.0:
				return SUBSYSTEM_CONDITION_CRIPPLED
			return SUBSYSTEM_CONDITION_BROKEN
			
	def getTeamID(self):
		"""
		Returns the ID of the team to which this TankChan belongs.
		
		@rtype: int
		@return: The ID of the team to which this TankChan belongs.
		"""
		return self._team_id
		
	def getWeaponSystems(self):
		"""
		Returns the control interfaces for this TankChan's primary and secondary
		weapons systems.
		
		@rtype: tuple(2)
		@return: This TankChan's L{weapons.BoltSystem} and
		    L{weapons.MissileSystem}.
		"""
		return (self._bolt_system, self._missile_system)
		
	def inscribe(self, description, current_time):
		"""
		Sets this TankChan's position and stats based on a description received
		from another client.
		
		@type description: basestring
		@param description: The description of the TankChan 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._target_speed = float(match.group(9))
		
		self._current_hull = float(match.group(10))
		self._current_energy = float(match.group(11))
		self._current_shielding = float(match.group(12))
		self._current_sensors_integrity = float(match.group(13))
		self._current_engines_integrity = float(match.group(14))
		self._current_sheilds_integrity = float(match.group(15))
		self._current_weapons_integrity = float(match.group(16))
		if match.group(17) == '1':
			if not self._shields_down:
				self._toggleShields(False)
				self._toggleHitboxes(True)
		else:
			if self._shields_down:
				self._toggleShields(True)
				self._toggleHitboxes(False)
				
		self._reactor_engines = float(match.group(18))
		self._reactor_shielding = float(match.group(19))
		self._reactor_energy = float(match.group(20))
		
		self.move(current_time - event_time)
		
		self._indicator_changed = True
		
	def isAlive(self):
		"""
		Indicates whether this TankChan is alive.
		
		@rtype: bool
		@return: True if this TankChan is alive.
		"""
		return self._alive
		
	def isHostile(self, tank_chan):
		"""
		Indicates whether this TankChan is on a different team from the one
		specified.
		
		@type tank_chan: L{TankChan}
		@param tank_chan: The TankChan to be tested.
		
		@rtype: bool
		@return: True if these TankChans are on different teams.
		"""
		return not self._team_id == tank_chan.getTeamID()
		
	def isShielded(self):
		"""
		Indicates whether this TankChan's shields are up.
		
		@rtype: bool
		@return: True if this TankChan's shields are up.
		"""
		return not self._shields_down
		
	def lowerShields(self):
		"""
		Lowers this TankChan's shields, exposing its subsystem hitboxes.
		"""
		self._toggleShields(False)
		self._toggleHitboxes(True)
		
	def move(self, seconds):
		"""
		Updates this TankChan's position by first adjusting its speed relative to
		its target, and then moving at the new speed over the given time period.
		
		@type seconds: float
		@param seconds: The number of seconds over which acceleration and movement
		    have taken place.
		"""
		if self._alive:
			#Update speed.
			goal_speed = min(self._target_speed, self.getMaxSpeed())
			speed_change = seconds * self._acceleration * self._current_engines_multiplier
			if self._current_speed < goal_speed:
				self._current_speed = min(goal_speed, self._current_speed + speed_change)
			elif self._current_speed > goal_speed:
				self._current_speed = max(goal_speed, self._current_speed - speed_change)
				
			#Update position.
			if self._current_speed: #Don't waste cycles if this is nil.
				self._move(seconds)
				
	def reactorDown(self, subsystem, percent):
		"""
		Reduces the reactor output allocated to one subsystem, boosting the others
		evenly.
		
		@type subsystem: int
		@param subsystem: A constant identifying the subsystem having its
		    allocation reduced.
		@type percent: float
		@param percent: The amount to be removed from the specified subsystem's
		    allocation.
		"""
		if subsystem == SUBSYSTEM_ENGINES:
			amount = min(percent, self._reactor_engines)
			if amount:
				half_amount = amount / 2.0
				self._reactor_engines -= amount
				self._reactor_shielding += half_amount
				self._reactor_energy += half_amount
		elif subsystem == SUBSYSTEM_SHIELDS:
			amount = min(percent, self._reactor_shielding)
			if amount:
				half_amount = amount / 2.0
				self._reactor_engines += half_amount
				self._reactor_shielding -= amount
				self._reactor_energy += half_amount
		elif subsystem == SUBSYSTEM_WEAPONS:
			amount = min(percent, self._reactor_energy)
			if amount:
				half_amount = amount / 2.0
				self._reactor_engines += half_amount
				self._reactor_shielding += half_amount
				self._reactor_energy -= amount
				
	def reactorUp(self, subsystem, percent):
		"""
		Boosts the reactor output allocated to one subsystem, lowering the others
		evenly.
		
		In the event that one of the other subsystems would fall below 0%, its
		counterpart in the reduction carries the additional burden.
		
		@type subsystem: int
		@param subsystem: A constant identifying the subsystem having its
		    allocation increased.
		@type percent: float
		@param percent: The amount to be added to the specified subsystem's
		    allocation.
		"""
		if subsystem == SUBSYSTEM_ENGINES:
			amount = min(percent, 1.0 - self._reactor_engines)
			if amount:
				half_amount = amount / 2.0
				self._reactor_engines += amount
				if self._reactor_shielding < half_amount:
					self._reactor_energy -= half_amount * 2 - self._reactor_shielding
					self._reactor_shielding = 0.0
				elif self._reactor_energy < half_amount:
					self._reactor_shielding -= half_amount * 2 - self._reactor_energy
					self._reactor_energy = 0.0
				else:
					self._reactor_shielding -= half_amount
					self._reactor_energy -= half_amount
		elif subsystem == SUBSYSTEM_SHIELDS:
			amount = min(percent, 1.0 - self._reactor_shielding)
			if amount:
				half_amount = amount / 2.0
				self._reactor_shielding += amount
				if self._reactor_engines < half_amount:
					self._reactor_energy -= half_amount * 2 - self._reactor_engines
					self._reactor_engines = 0.0
				elif self._reactor_energy < half_amount:
					self._reactor_engines -= half_amount * 2 - self._reactor_energy
					self._reactor_energy = 0.0
				else:
					self._reactor_engines -= half_amount
					self._reactor_energy -= half_amount
		elif subsystem == SUBSYSTEM_WEAPONS:
			amount = min(percent, 1.0 - self._reactor_energy)
			if amount:
				half_amount = amount / 2.0
				self._reactor_energy += amount
				if self._reactor_engines < half_amount:
					self._reactor_shielding -= half_amount * 2 - self._reactor_engines
					self._reactor_engines = 0.0
				elif self._reactor_shielding < half_amount:
					self._reactor_engines -= half_amount * 2 - self._reactor_shielding
					self._reactor_shielding = 0.0
				else:
					self._reactor_engines -= half_amount
					self._reactor_shielding -= half_amount
					
	def render(self, friendly, indicator, indicator_condition, lead_reticle, target, force_redraw):
		"""
		Displays this TankChan in visual-space.
		
		@type friendly: bool
		@param friendly: If an indicator is to be drawn, this will render the
		    indicator using non-hostile colouring.
		@type indicator: bool
		@param indicator: True if an indicator should be overlaid on this
		    TankChan.
		@type indicator_condition: bool
		@param indicator_condition: True if the relative status of this TankChan's
		    shielding and hull should be displayed in the indicator.
		@type lead_reticle: bool
		@param lead_reticle: True if a lead reticle should be drawn in front of
		    this TankChan.
		@type target: bool
		@param target: True if this TankChan is the player's current target.
		@type force_redraw: bool
		@param force_redraw: True if this TankChan's indicator should be redrawn
		    immediately, rather than during its normal update frame.
		"""
		if not self._alive or not self._render:
			return
			
		GL.glPushMatrix()
		GL.glMultMatrixf(self._render_matrix)
		
		#Draw the ship itself.
		render_decorations = self._camera_offset_z < _HIGH_POLY_DRAW_LIMIT
		if render_decorations:
			GL.glCallList(self._gl_list_id)
		else:
			GL.glCallList(self._gl_list_id_low_poly)
			
		#Set up 2D rendering parameters.
		GL.glDisable(GL.GL_LIGHTING)
		GL.glEnable(GL.GL_BLEND)
		GL.glEnable(GL.GL_TEXTURE_2D)
		GL.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_MODULATE)
		GL.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA)
		GL.glColor(1.0, 1.0, 1.0)
		
		#Draw the power source behind each thruster.
		GL.glBindTexture(GL.GL_TEXTURE_2D, _POWER_TEXTURES[self._power_source])
		scale = 0.25 + ((self._current_speed / self._rated_speed) * 0.75)
		for ((x, y, z), radius) in self._thrusters:
			radius *= scale
			GL.glBegin(GL.GL_QUADS)
			GL.glTexCoord2f(0, 0)
			GL.glVertex3f(x + radius, y - radius, z)
			GL.glTexCoord2f(1, 0)
			GL.glVertex3f(x - radius, y - radius, z)
			GL.glTexCoord2f(1, 1)
			GL.glVertex3f(x - radius, y + radius, z)
			GL.glTexCoord2f(0, 1)
			GL.glVertex3f(x + radius, y + radius, z)
			GL.glEnd()
			
		if render_decorations and not self._shields_down: #Render shield projection.
			#Prepare to render shielding.
			GL.glDisable(GL.GL_TEXTURE_2D)
			GL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE)
			GL.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE)
			GL.glPushMatrix()
			
			(radius, position) = self._projection
			rendering.renderSphere(position, None, radius, 10, 10, (1.0, 1.0, 1.0, 0.05 * self._current_shielding / self._shielding))
			
			#Restore pre-shield settings.
			GL.glPopMatrix()
			GL.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA)
			GL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL)
			GL.glEnable(GL.GL_TEXTURE_2D)
			GL.glColor(1.0, 1.0, 1.0)
			
		#Prepare to render field-based HUD elements.
		GL.glPopMatrix()
		
		#Rebuild the indicator texture.
		if force_redraw or self._indicator_changed and (self._frame == _INDICATOR_REDRAW_FRAME or (_INDICATOR_REDRAW_FRAME and not self._frame % _INDICATOR_REDRAW_FRAME)):
			self._rebuildIndicatorTexture(target, friendly, indicator_condition)
			
		#Render indicator.
		if render_decorations and indicator:
			if target:
				GL.glDisable(GL.GL_DEPTH_TEST) #Show it through objects.
			else:
				GL.glEnable(GL.GL_DEPTH_TEST) #Don't show it through objects.
			rendering.renderOrtho(self._pos_x, self._pos_y, self._pos_z, 5, self._indicator_texture_id)
			
		#Render lead reticle.
		if lead_reticle:
			GL.glDisable(GL.GL_DEPTH_TEST) #Show it through objects.
			lead_distance = (self._camera_distance / weapons.BOLT_SPEED) * self._current_speed
			(x, y, z) = matrix.computePosition(self._pos_x, self._pos_y, self._pos_z, 0, 0, lead_distance, self._rotation_matrix)
			rendering.renderOrtho(x, y, z, 0.0625, _RETICLE_LEAD_TEXTURE, True)
			
		#Restore 3D rendering parameters.
		GL.glEnable(GL.GL_DEPTH_TEST)
		GL.glDisable(GL.GL_BLEND)
		GL.glDisable(GL.GL_TEXTURE_2D)
		GL.glEnable(GL.GL_LIGHTING)
		
		self._frame += 1
		self._frame %= session_constants.FPS
		
	def reset(self, position, rotation_euler):
		"""
		Restores all of this TankChan's systems and statistics to their initial,
		perfect-condition state, and marks it as alive.
		
		@type position: sequence(3)
		@param position: The (x, y, z) position at which this TankChan will
		    reappear.
		@type rotation_euler: sequence(3)
		@param rotation_euler: The Euler (x, y, z) rotation of this TankChan.
		"""
		self.setPosition(position)
		self.setRotation(rotation_euler)
		
		self._indicator_changed = True
		self._alive = True
		self._frame = 0
		
		self._current_speed = self._target_speed = 0
		self._current_energy = self._energy
		self._current_hull = self._hull
		
		#Reinitialise shields.
		self._current_shielding = self._shielding
		self._shields_down = False
		self._toggleShields(True)
		self._toggleHitboxes(False)
		
		#Reinitialise subsystems.
		self._current_sensors_integrity = self._sensors_integrity
		self._current_engines_integrity = self._engines_integrity
		self._current_shields_integrity = self._shields_integrity
		self._current_weapons_integrity = self._weapons_integrity
		self._current_engines_multiplier = self._current_shields_multiplier = self._current_weapons_multiplier = 1.0
		
		if self._bolt_system: #Player's ship.
			self._bolt_system.reset()
			self._missile_system.reset()
			
			self._ecm_stock = self._ecm_capacity
			self._ecm_countdown = None
			
	def speedDown(self, percent):
		"""
		Lowers this TankChan's target speed relative to its maximum speed.
		
		@type percent: float
		@param percent: The percentage of its maximum speed by which this
		    TankChan's target speed should be reduced.
		"""
		max_speed = self.getMaxSpeed()
		self._target_speed = max(0, self._target_speed - max_speed * percent)
		
	def speedUp(self, percent):
		"""
		Raises this TankChan's target speed relative to its maximum speed.
		
		@type percent: float
		@param percent: The percentage of its maximum speed by which this
		    TankChan's target speed should be increased.
		"""
		max_speed = self.getMaxSpeed()
		self._target_speed = min(max_speed, self._target_speed + max_speed * percent)
		
	def tick(self, seconds):
		"""
		Performs maintenance on the state of this TankChan, repairing its damaged
		subsystems, regenerating its shielding and energy, cooling down its
		weapons, and replenishing its ECMs. At the end of these updates,
		L{TankChan.move} is invoked.
		
		@type seconds: float
		@param seconds: The number of seconds over which recovery is to take
		    place.
		"""
		if not self._alive:
			return
			
		reactor_output = self._reactor_output * seconds
		
		#Restore energy.
		self._current_energy = min(self._energy * self._current_weapons_multiplier, self._current_energy + reactor_output * self._reactor_energy * self._current_weapons_multiplier)
		
		#Regenerate shields.
		shielding_cap = self._shielding * self._current_shields_multiplier
		if self._current_shielding < shielding_cap:
			self._indicator_changed = True
			self._current_shielding = min(shielding_cap, self._current_shielding + reactor_output * self._reactor_shielding * self._current_shields_multiplier)
			if self._shields_down and self._current_shielding / self._shielding > session_constants.SHIELD_RESTORATION:
				self._toggleShields(True)
				self._toggleHitboxes(False)
				
		#Repair subsystems.
		(self._current_sensors_integrity, updated) = self._repairSubsystem(
		 self._current_sensors_integrity, self._sensors_integrity,
		 self._sensors_integrity_1, seconds
		)
		
		(self._current_engines_integrity, updated) = self._repairSubsystem(
		 self._current_engines_integrity, self._engines_integrity,
		 self._engines_integrity_1, seconds
		)
		if updated:
			self._current_engines_multiplier = (_SUBSYSTEM_MULTIPLIER_MINIMUM + _SUBSYSTEM_MULTIPLIER_MAXIMUM * (self._current_engines_integrity / self._engines_integrity))
			
		(self._current_shields_integrity, updated) = self._repairSubsystem(
		 self._current_shields_integrity, self._shields_integrity,
		 self._shields_integrity_1, seconds
		)
		if updated:
			self._current_shields_multiplier = (_SUBSYSTEM_MULTIPLIER_MINIMUM + _SUBSYSTEM_MULTIPLIER_MAXIMUM * (self._current_shields_integrity / self._shields_integrity))
			
		(self._current_weapons_integrity, updated) = self._repairSubsystem(
		 self._current_weapons_integrity, self._weapons_integrity,
		 self._weapons_integrity_1, seconds
		)
		if updated:
			self._current_weapons_multiplier = (_SUBSYSTEM_MULTIPLIER_MINIMUM + _SUBSYSTEM_MULTIPLIER_MAXIMUM * (self._current_weapons_integrity / self._weapons_integrity))
			
		if self._bolt_system: #Player's ship.
			#Cool off weapons.
			self._bolt_system.tick(seconds)
			self._missile_system.tick(seconds)
			
			#Replenish ECMs.
			if self._ecm_stock < self._ecm_capacity:
				if self._ecm_countdown is None: #Start the countdown.
					self._ecm_countdown = _ECM_RESTORE_TIME
				else:
					self._ecm_countdown -= seconds
					if self._ecm_countdown <= 0.0:
						self._ecm_stock += 1
						if self._ecm_stock == self._ecm_capacity: #Kill the timer.
							self._ecm_countdown = None
						else: #Start counting down to the next one.
							self._ecm_countdown += _ECM_RESTORE_TIME
							
		self.move(seconds)
		
	def _buildGLList(self, _vertices, _normals, _faces, _materials, player_id, team_id, render_mode):
		"""
		This function is invoked to generate a list of GL instructions, needed to
		efficiently render this Tank-chan.
		
		@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.
		@type player_id: int
		@param player_id: The ID of the player being represented.
		@type team_id: int
		@param team_id: The ID of the team being represented.
		@type render_mode: int
		@param render_mode: A constant that defines the manner in which the object
		    should be rendered.
		
		@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.
		
		use_shading = render_mode in (shading.RENDER_MODE_CEL, shading.RENDER_MODE_GRADIENT, shading.RENDER_MODE_FLAT)
		if render_mode == shading.RENDER_MODE_WIREFRAME:
			GL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE)
		else:
			GL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL)
			
		for (vertices, normals, material) in _faces:
			rgb = None
			program_id = None
			if material:
				if use_shading: #Assign a shader program.
					program_id = session_constants.GL_PROGRAMS[material]
					shading.useProgram(program_id) #Has to be separate for some reason.
					
				if material == 'default':
					rgb = session_constants.PLAYER_COLOURS[player_id]
				elif material == 'team':
					rgb = session_constants.TEAM_COLOURS[team_id]
				else:
					rgb = _materials[material]['Kd']
			else:
				rgb = (1.0, 1.0, 1.0) #White.
				
			if use_shading and material in ('default', 'team'): #Apply shading uniforms.
				shading.setUniform(program_id, "r", rgb[0])
				shading.setUniform(program_id, "g", rgb[1])
				shading.setUniform(program_id, "b", rgb[2])
			GL.glColor(*rgb) #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.
		if use_shading: #Disable shading to avoid colouring other elements by mistake.
			shading.useProgram(None)
		GL.glEndList() #Indicate that the instruction list is complete.
		
	def _buildGLListLowPoly(self, player_id, team_id, render_mode):
		"""
		This function is invoked to generate a list of GL instructions, needed to
		efficiently render this Tank-chan for display in low-polygon mode.
		
		@type player_id: int
		@param player_id: The ID of the player being represented.
		@type team_id: int
		@param team_id: The ID of the team being represented.
		@type render_mode: int
		@param render_mode: A constant that defines the manner in which the object
		    should be rendered.
		"""
		self._gl_list_id_low_poly = GL.glGenLists(1) #Create a new GL List to optimize rendering, and cache the ID.
		GL.glNewList(self._gl_list_id_low_poly, GL.GL_COMPILE) #Indicate that all instructions from here belong to the GL List.
		GL.glFrontFace(GL.GL_CCW)
		
		if render_mode == shading.RENDER_MODE_WIREFRAME:
			GL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE)
		else:
			GL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL)
			
		#Define team box.
		GL.glColor(*session_constants.TEAM_COLOURS[team_id])
		GL.glBegin(GL.GL_QUADS)
		GL.glNormal(0, 1, 0)
		GL.glVertex3f(1, 1, 0)
		GL.glVertex3f(-1, 1, 0)
		GL.glVertex3f(-1, 1, 2)
		GL.glVertex3f(1, 1, 2)
		
		GL.glNormal(0, -1, 0)
		GL.glVertex3f(1, -1, 2)
		GL.glVertex3f(-1, -1, 2)
		GL.glVertex3f(-1, -1, 0)
		GL.glVertex3f(1, -1, 0)
		
		GL.glNormal(0, 0, 1)
		GL.glVertex3f(1, 1, 2)
		GL.glVertex3f(-1, 1, 2)
		GL.glVertex3f(-1, -1, 2)
		GL.glVertex3f(1, -1, 2)
		
		GL.glNormal(0, 0, -1)
		GL.glVertex3f(1, -1, 0)
		GL.glVertex3f(-1, -1, 0)
		GL.glVertex3f(-1, 1, 0)
		GL.glVertex3f(1, 1, 0)
		
		GL.glNormal(-1, 0, 0)
		GL.glVertex3f(-1, 1, 2)
		GL.glVertex3f(-1, 1, 0)
		GL.glVertex3f(-1, -1, 0)
		GL.glVertex3f(-1, -1, 2)
		
		GL.glNormal(1, 0, 0)
		GL.glVertex3f(1, 1, 0)
		GL.glVertex3f(1, 1, 2)
		GL.glVertex3f(1, -1, 2)
		GL.glVertex3f(1, -1, 0)
		GL.glEnd()
		
		#Define player box.
		GL.glColor(*session_constants.PLAYER_COLOURS[player_id])
		GL.glBegin(GL.GL_QUADS)
		GL.glNormal(0, 1, 0)
		GL.glVertex3f(1.5, 1.5, -3)
		GL.glVertex3f(-1.5, 1.5, -3)
		GL.glVertex3f(-1.5, 1.5, 0)
		GL.glVertex3f(1.5, 1.5, 0)
		
		GL.glNormal(0, -1, 0)
		GL.glVertex3f(1.5, -1.5, 0)
		GL.glVertex3f(-1.5, -1.5, 0)
		GL.glVertex3f(-1.5, -1.5, -3)
		GL.glVertex3f(1.5, -1.5, -3)
		
		GL.glNormal(0, 0, 1)
		GL.glVertex3f(1.5, 1.5, 0)
		GL.glVertex3f(-1.5, 1.5, 0)
		GL.glVertex3f(-1.5, -1.5, 0)
		GL.glVertex3f(1.5, -1.5, 0)
		
		GL.glNormal(0, 0, -1)
		GL.glVertex3f(1.5, -1.5, -3)
		GL.glVertex3f(-1.5, -1.5, -3)
		GL.glVertex3f(-1.5, 1.5, -3)
		GL.glVertex3f(1.5, 1.5, -3)
		
		GL.glNormal(-1, 0, 0)
		GL.glVertex3f(-1.5, 1.5, 0)
		GL.glVertex3f(-1.5, 1.5, -3)
		GL.glVertex3f(-1.5, -1.5, -3)
		GL.glVertex3f(-1.5, -1.5, 0)
		
		GL.glNormal(1, 0, 0)
		GL.glVertex3f(1.5, 1.5, -3)
		GL.glVertex3f(1.5, 1.5, 0)
		GL.glVertex3f(1.5, -1.5, 0)
		GL.glVertex3f(1.5, -1.5, -3)
		GL.glEnd()
		
		GL.glEndList() #Indicate that the instruction list is complete.
		
	def _rebuildIndicatorTexture(self, target, friendly, indicator_condition):
		"""
		Reconstructs this TankChan's indicator texture.
		
		@type target: bool
		@param target: True if this TankChan is the player's current target.
		@type friendly: bool
		@param friendly: True if this TankChan is on the same team as the player.
		@type indicator_condition: bool
		@param indicator_condition: True if this TankChan's relative hull and
		    shielding integrities should be represented as part of the indicator.
		"""
		overlay = _SURFACE_INDICATOR_BASE.copy()
		if indicator_condition:
			hull_strength = int(255 * self._current_hull / self._hull)
			shield_strength = int(255 * self._current_shielding / self._shielding)
			overlay.fill((hull_strength, hull_strength, hull_strength, 255), (315, 442, 64, 64))
			if not self._shields_down:
				overlay.fill((shield_strength, shield_strength, shield_strength, 255), (387, 442, 64, 64))
			else:
				overlay.fill((255, 0, 0, 255), (387, 442, 64, 64))
		else:
			if friendly:
				overlay.fill((64, 64, 128, 191), (315, 442, 136, 64))
			else:
				overlay.fill((128, 64, 64, 191), (315, 442, 136, 64))
		overlay.blit(_SURFACE_BLITMASK, (315, 442), None, pygame.constants.BLEND_RGBA_SUB)
		if target: #Render the full box.
			if friendly:
				overlay.blit(_SURFACE_TARGET_FRIENDLY, (53, 53))
			else:
				overlay.blit(_SURFACE_TARGET_HOSTILE, (53, 53))
		else: #Just render the bar.
			if friendly:
				overlay.blit(_SURFACE_ONSCREEN_FRIENDLY, (53, 436))
			else:
				overlay.blit(_SURFACE_ONSCREEN_HOSTILE, (53, 436))
				
		if not self._indicator_texture_id is None:
			GL.glDeleteTextures([self._indicator_texture_id])
		self._indicator_texture_id = rendering.buildTexture(overlay)
		
		self._indicator_changed = False
		
	def _repairSubsystem(self, current, max_100, max_1, seconds):
		"""
		Returns the integrity of a subsystem, post-repair.
		
		@type current: number
		@param current: The current integrity of the subsystem.
		@type max_100: number
		@param max_100: The maximum integrity of the subsystem.
		@type max_1: float
		@param max_1: 1% of the maximum integrity of the subsystem.
		@type seconds: float
		@param seconds: The number of seconds over which the repairs are to take
		    place.
		
		@rtype: tuple(2)
		@return: The post-repair integrity of the subsystem, and True if the value
		    changed.
		"""
		if current < max_100:
			if current <= 0.0:
				return (0, False)
			return (min(max_100, current + max_1 * seconds), True)
		return (max_100, False)
		
	def _setSubsystems(self, parser, tc_specifications, subsystems_multiplier):
		"""
		Assigns subsystem integrity values and ODE hitboxes to this TankChan,
		based on specifications provided in its XML descriptor file.
		
		@type parser: L{config.parser}
		@param parser: A parser that is currently reading this TankChan's XML
		    descriptor file.
		@type tc_specifications: XML node
		@param tc_specifications: The XML node that contains this TankChan's
		    specifications section.
		@type subsystems_multiplier: float
		@param subsystems_multiplier: The multiplier to apply to the maximum
		    integrity of this TankChan's subsystems.
		"""
		radius = float(parser.readData(tc_specifications, 'projection.sphere.radius'))
		position = (
		 float(parser.readData(tc_specifications, 'projection.sphere.position.x')),
		 float(parser.readData(tc_specifications, 'projection.sphere.position.y')),
		 float(parser.readData(tc_specifications, 'projection.sphere.position.z'))
		)
		self._projection = (radius, position)
		
		ode_geometry = ode.GeomSphere(self._ode_space, radius)
		ode_geometry.tank_chan = self #Mark this to avoid reporting collisions with self-launched weapons.
		ode_geometry.parent = self
		ode_geometry.subsystem = SUBSYSTEM_PROJECTION
		self._ode_projection = (ode_geometry, position)
		
		subsystem_boxes = []
		subsystem_spheres = []
		
		def _parseSubsystem(subsystem, tc_subsystem):
			for box in parser.getCollection(tc_subsystem, 'hit-box', 'box'):
				pos_x = float(parser.readData(box, 'position.x'))
				pos_y = float(parser.readData(box, 'position.y'))
				pos_z = float(parser.readData(box, 'position.z'))
				len_x = float(parser.readData(box, 'size.x'))
				len_y = float(parser.readData(box, 'size.y'))
				len_z = float(parser.readData(box, 'size.z'))
				rot_x = 0
				rot_y = 0
				rot_z = 0
				hb_rot = parser.getCollection(box, None, 'rotation')
				if hb_rot:
					rot_x = float(parser.readData(hb_rot[0], 'x'))
					rot_y = float(parser.readData(hb_rot[0], 'y'))
					rot_z = float(parser.readData(hb_rot[0], 'z'))
				subsystem_boxes.append((subsystem, pos_x, pos_y, pos_z, len_x, len_y, len_z, rot_x, rot_y, rot_z))
				
			for sphere in parser.getCollection(tc_subsystem, 'hit-box', 'sphere'):
				pos_x = float(parser.readData(box, 'position.x'))
				pos_y = float(parser.readData(box, 'position.y'))
				pos_z = float(parser.readData(box, 'position.z'))
				radius = float(parser.readData(box, 'radius'))
				subsystem_spheres.append((subsystem, pos_x, pos_y, pos_z, radius))
				
		tc_subsystem_sensors = parser.getCollection(None, 'tank-chan.subsystems', 'sensors')[0]
		self._current_sensors_integrity = self._sensors_integrity = int(float(parser.readData(tc_subsystem_sensors, 'hit-points')) * subsystems_multiplier)
		self._sensors_integrity_1 = self._sensors_integrity * 0.01
		self._sensors_integrity_25 = self._sensors_integrity_1 * 25
		self._sensors_integrity_50 = self._sensors_integrity_1 * 50
		self._sensors_integrity_75 = self._sensors_integrity_1 * 75
		_parseSubsystem(SUBSYSTEM_SENSORS, tc_subsystem_sensors)
		
		tc_subsystem_engines = parser.getCollection(None, 'tank-chan.subsystems', 'engines')[0]
		self._current_engines_integrity = self._engines_integrity = int(float(parser.readData(tc_subsystem_engines, 'hit-points')) * subsystems_multiplier)
		self._engines_integrity_1 = self._engines_integrity * 0.01
		self._engines_integrity_25 = self._engines_integrity_1 * 25
		self._engines_integrity_50 = self._engines_integrity_1 * 50
		self._engines_integrity_75 = self._engines_integrity_1 * 75
		_parseSubsystem(SUBSYSTEM_ENGINES, tc_subsystem_engines)
		
		tc_subsystem_shields = parser.getCollection(None, 'tank-chan.subsystems', 'shields')[0]
		self._current_shields_integrity = self._shields_integrity = int(float(parser.readData(tc_subsystem_shields, 'hit-points')) * subsystems_multiplier)
		self._shields_integrity_1 = self._shields_integrity * 0.01
		self._shields_integrity_25 = self._shields_integrity_1 * 25
		self._shields_integrity_50 = self._shields_integrity_1 * 50
		self._shields_integrity_75 = self._shields_integrity_1 * 75
		_parseSubsystem(SUBSYSTEM_SHIELDS, tc_subsystem_shields)
		
		tc_subsystem_weapons = parser.getCollection(None, 'tank-chan.subsystems', 'weapons')[0]
		self._current_weapons_integrity = self._weapons_integrity = int(float(parser.readData(tc_subsystem_weapons, 'hit-points')) * subsystems_multiplier)
		self._weapons_integrity_1 = self._weapons_integrity * 0.01
		self._weapons_integrity_25 = self._weapons_integrity_1 * 25
		self._weapons_integrity_50 = self._weapons_integrity_1 * 50
		self._weapons_integrity_75 = self._weapons_integrity_1 * 75
		_parseSubsystem(SUBSYSTEM_WEAPONS, tc_subsystem_weapons)
		
		#Initialise dictionaries.
		self._ode_geoms = {}
		for (subsystem, pos_x, pos_y, pos_z, len_x, len_y, len_z, rot_x, rot_y, rot_z) in subsystem_boxes:
			ode_geometry = ode.GeomBox(self._ode_space, (len_x, len_y, len_z))
			ode_geometry.tank_chan = self #Mark this to avoid reporting collisions with self-launched weapons.
			ode_geometry.parent = self
			ode_geometry.subsystem = subsystem
			
			rotation_matrix = None
			if rot_x or rot_y or rot_z:
				rotation_matrix = matrix.buildRotationMatrix(rot_x, rot_y, rot_z)
				
			self._ode_geoms[ode_geometry] = ((pos_x, pos_y, pos_z), rotation_matrix)
		for (subsystem, pos_x, pos_y, pos_z, radius) in subsystem_spheres:
			ode_geometry = ode.GeomSphere(self._ode_space, radius)
			ode_geometry.tank_chan = self #Mark this to avoid reporting collisions with self-launched weapons.
			ode_geometry.parent = self
			ode_geometry.subsystem = subsystem
			
			self._ode_geoms[ode_geometry] = ((pos_x, pos_y, pos_z), None)
			
	def _setThrusters(self, parser):
		"""
		Positions and sets the scale of the thrusters attached to this TankChan.
		
		@type parser: L{config.parser}
		@param parser: A parser that is currently reading this TankChan's XML
		    descriptor file.
		"""
		self._thrusters = []
		for thruster in parser.getCollection(None, 'tank-chan.thrusters', 'thruster'):
			x = float(parser.readData(thruster, 'position.x'))
			y = float(parser.readData(thruster, 'position.y'))
			z = float(parser.readData(thruster, 'position.z'))
			radius = float(parser.readData(thruster, 'radius'))
			self._thrusters.append(((x, y, z), radius))
			
	def _setWeapons(self, parser, bolt_types, bolt_modifiers, missile_types):
		"""
		Instantiates and positions the weapon banks that comprise this TankChan's
		weapons systems, and makes these objects accessible as part of a
		L{weapons.BoltSystem} and L{weapons.MissileSystem}.
		
		@type parser: L{config.parser}
		@param parser: A parser that is currently reading this TankChan's XML
		    descriptor file.
		"""
		tc_weapons = parser.getCollection(None, 'tank-chan', 'weapon-banks')[0]
		
		#Set up bolts.
		self._bolt_system = weapons.BoltSystem(self, parser, parser.getCollection(tc_weapons, None, 'bolt'), bolt_types, bolt_modifiers, self._power_source)
		#Set up missiles.
		self._missile_system = weapons.MissileSystem(self, parser, parser.getCollection(tc_weapons, None, 'missile'), missile_types)
		
	def _toggleShields(self, enabled):
		"""
		Raises or lowers this TankChan's shielding projection.
		
		@type enabled: bool
		@param enabled: True if this TankChan's shields are being brought online.
		"""
		self._shields_down = not enabled
		
		if enabled: #Raise ODE projection.
			self._ode_projection[0].enable()
			self._updateODEShielding()
		else: #Take down ODE projection.
			self._ode_projection[0].disable()
			
	def _toggleHitboxes(self, enabled):
		"""
		Exposes or hides this TankChan's subsystems hitboxes.
		
		@type enabled: bool
		@param enabled: True if this TankChan's subsystems are being exposed.
		"""
		if enabled: #Expose ODE hitboxes.
			for ode_geom in self._ode_geoms:
				ode_geom.enable()
			self._updateODEHitboxes(True)
		else: #Hide ODE hitboxes.
			for ode_geom in self._ode_geoms:
				ode_geom.disable()
				
	def _updateODEHitboxes(self, rotation_update):
		"""
		Updates this TankChan's ODE hitbox positions.
		
		@type rotation_update: bool
		@param rotation_update: True if this TankChan's rotation has changed,
		    necessitating changes in the orientations of its component hitboxes.
		"""
		if self._rotation_matrix: #A workaround for an instantiation order problem that provides substantial speedups in other ways.
			for (geom, ((pos_x, pos_y, pos_z), subrotation_matrix)) in self._ode_geoms.iteritems():
				geom.setPosition(matrix.computePosition(self._pos_x, self._pos_y, self._pos_z, pos_x, pos_y, pos_z, self._rotation_matrix))
				if rotation_update:
					applied_rotation_matrix = self._rotation_matrix
					if subrotation_matrix:
						applied_rotation_matrix = matrix.multiply3x3Matrices(self._rotation_matrix, subrotation_matrix)
					geom.setRotation(applied_rotation_matrix)
					
	def _updateODEShielding(self):
		"""
		Updates this TankChan's ODE projection position.
		"""
		if self._rotation_matrix: #A workaround for an instantiation order problem that provides substantial speedups in other ways.
			(ode_projection, (pos_x, pos_y, pos_z)) = self._ode_projection
			ode_projection.setPosition(matrix.computePosition(self._pos_x, self._pos_y, self._pos_z, pos_x, pos_y, pos_z, self._rotation_matrix))
			
	def _updateVectors(self, rotation_update):
		"""
		Updates this TankChan's ODE geometry.
		
		@type rotation_update: bool
		@param rotation_update: True if this TankChan's rotation has changed,
		    necessitating changes in the orientations of its component hitboxes.
		"""
		if not self._shields_down:
			self._updateODEShielding()
		else:
			self._updateODEHitboxes(rotation_update)
			
			
def init():
	"""
	Initialises this module, loading assets and computing values needed for the
	proper behaviour of L{TankChan} objects.
	"""
	global _INDICATOR_REDRAW_FRAME
	_INDICATOR_REDRAW_FRAME = (session_constants.FPS / _INDICATOR_REDRAW_HERTZ) % session_constants.FPS
	
	_initSurfaces()
	
def _initSurfaces():
	"""
	Loads textures for use in rendering L{TankChan} objects.
	"""
	texture_path = path_utils.getTexturesPath()
	
	global _RETICLE_LEAD_TEXTURE
	_RETICLE_LEAD_TEXTURE = rendering.buildTexture(pygame.image.load(texture_path + "reticle-lead.png"))
	
	global _SURFACE_TARGET_HOSTILE
	global _SURFACE_ONSCREEN_HOSTILE
	global _SURFACE_TARGET_FRIENDLY
	global _SURFACE_ONSCREEN_FRIENDLY
	global _SURFACE_INDICATOR_BASE
	global _SURFACE_BLITMASK
	_SURFACE_TARGET_HOSTILE = pygame.image.load(texture_path + "target-hostile.png")
	_SURFACE_ONSCREEN_HOSTILE = pygame.image.load(texture_path + "onscreen-hostile.png")
	_SURFACE_TARGET_FRIENDLY = pygame.image.load(texture_path + "target-friendly.png")
	_SURFACE_ONSCREEN_FRIENDLY = pygame.image.load(texture_path + "onscreen-friendly.png")
	_SURFACE_INDICATOR_BASE = pygame.Surface((512, 512), flags=(pygame.constants.SRCALPHA | pygame.constants.SWSURFACE))
	_SURFACE_BLITMASK = pygame.image.load(texture_path + "status-blitmask.png")
	
	_POWER_TEXTURES[constants.POWER_LOVE] = rendering.buildTexture(pygame.image.load(texture_path + "power-heart.png"))
	_POWER_TEXTURES[constants.POWER_STARLIGHT] = rendering.buildTexture(pygame.image.load(texture_path + "power-star.png"))
	_POWER_TEXTURES[constants.POWER_MOE] = rendering.buildTexture(pygame.image.load(texture_path + "power-wikipe-tan.png"))
	_POWER_TEXTURES[constants.POWER_RAINBOW] = rendering.buildTexture(pygame.image.load(texture_path + "power-rainbow.png"))
	_POWER_TEXTURES[constants.POWER_HAPPINESS] = rendering.buildTexture(pygame.image.load(texture_path + "power-happy.png"))
	
def _mtlOverride(values, contents, player_id, team_id):
	"""
	A function that applies overrides to the mapping of materials in the
	construction of an OpenGL representation of a L{TankChan}.
	
	@type values: sequence
	@param values: A collection of tokens that describe a material in an MTL
	    file.
	@type contents: dict
	@param contents: A dictionary mapping material names to properties.
	@type player_id: int
	@param player_id: The ID of the player whose materials are being mapped.
	@type team_id: int
	@param team_id: The ID of the team of the player whose materials are being
	    mapped.
	
	@rtype: tuple(2)
	@return: True if the material has been overridden, and the material's
	    properties dictionary in either case.
	"""
	if values[1] == 'default': #Apply the player's colour and ignore other lines.
		contents['default'] = {'Kd': session_constants.PLAYER_COLOURS[player_id]}
		return (True, contents['default'])
	elif values[1] == 'team': #Apply the team's colour and ignore other lines.
		contents['team'] = {'Kd': session_constants.TEAM_COLOURS[team_id]}
		return (True, contents['team'])
	else: #Start populating a new materials entry.
		contents[values[1]] = {}
		return (False, contents[values[1]])
		