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

Purpose
=======
 Provides routines for drawing the HUD, the user's tactical view of the game.
 
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 os
import pygame
import random
import OpenGL.GL as GL
import OpenGL.GLU as GLU

import constants
import matrix
import path_utils
import session_constants
import tankchan

import rendering.rendering as rendering

_RADAR_RANGE = 2000.0 #: The range displayed by the radar.
_RADAR_PLANE_EQUALITY = 100.0 #: The Y-offset necessary for an elevation symbol to be drawn in the radar.
_THREAT_RANGE = 500.0 #: The range displayed by the threat-circle in the centre of the HUD.

_CENTRAL_REDRAW_HERTZ = 8 #: The number of times per second that the gauges in the centre of the HUD should be redrawn.
_CENTRAL_REDRAW_FRAME = None #: The frameskip value of the gauges in the centre of the HUD.
_TARGET_REDRAW_HERTZ = 8 #: The number of times per second that the target stats box should be redrawn.
_TARGET_REDRAW_FRAME = None #: The frameskip value of the target stats box.
_RADAR_REDRAW_HERTZ = 10 #: The number of times per second that the radar's contents should be redrawn.
_RADAR_REDRAW_FRAME = None #: The frameskip value of the radar.

_SUBSYSTEM_BOX_WIDTH = 106 #: The width of the subsystem stats box, pre-computed to prevent shivering.
_TARGET_SUBSYSTEM_BOX_WIDTH = 91 #: The width of the target's subsystem stats box, pre-computed to prevent shivering.
_WEAPONS_WIDTH = 64 #: The width available for the weapons status boxes.

_MESSAGE_LINE_CAP = None #: The maximum number of lines that may be displayed at a time.
_MESSAGE_TTL = 5.0 #: The number of seconds that comprise a message's lifespan.
_MESSAGE_BOX_WIDTH = 512 #: The number of pixels to allocate to the message box. (This should be 2 ** x)
_MESSAGE_ENTRY_WIDTH = int(_MESSAGE_BOX_WIDTH * .95) #: The width of the text-entry field.
_MESSAGE_FONT = None #: The font to use when rendering messages.
_MESSAGE_FONT_SPACE_SIZE = None #: The size of a space in the message font.

_STATS_FONT = None #: The font used to render statistical information.
_STATS_FONT_MEDIUM = None #: The font used to render statistical information in medium sizes.
_STATS_FONT_LARGE = None #: The font used to render statistical information in large sizes.

_DYNAMIC_BOX_BORDER = 2 #: The number of pixels to draw as a border for dynamic boxes.
_DYNAMIC_BOX_PADDING = 2 #: The number of pixels to leave as whitespace in dynamic boxes.

_RETICLE_OFFSCREEN_TEXTURE = None #: The ID of the offscreen reticle texture, plus its half-width and half-height values.
_POSITION_HOSTILE_TEXTURE = None #: The ID of the hostile position texture, plus its half-width and half-height values.
_POSITION_MISSILE_TEXTURE = None #: The ID of the missile position texture, plus its half-width and half-height values.
_TEXTURE_CENTRE = None #: The ID of the central HUD texture.
_SIZE_CENTRE_HALF = None #: Half the length of an edge around the central square.

_TEXTURE_PLAYER = None #: The ID of the player's HUD texture.

_TEXTURE_RADAR = None #: The ID of the radar HUD texture.
_TEXTURE_TARGET = None #: The ID of the target HUD texture.

_BLITMASK_ENERGY = None #: The PyGame Surface used to trim the energy gauge.
_BLITMASK_SPEED = None #: The PyGame Surface used to trim the speed gauge.
_BLITMASK_SHIELD = None #: The PyGame Surface used to trim the shielding oval.

_BASE_RADAR = None #: The PyGame Surface that serves as the base for radar elements.

_BASE_SCORE = None #: The PyGame Surface that serves as the base for scoring data.
_BASE_TIME = None #: The PyGame Surface that serves as the base for time data.
_BASE_ECM = None #: The PyGame Surface that serves as the base for ECM data.

_BASE_SUBSYSTEMS = None #: The PyGame Surface that serves as the base for the player's subsystem status data.

_BASE_TARGET = None #: The PyGame Surface that serves as the base for the target's data.
_BASE_TARGET_SUBSYSTEMS = None #: The PyGame Surface that serves as the base for the target's subsystem status data.

_HUD_COLOUR = None #: The byte-scaled (r, g, b) values of the player's chosen HUD colour.
_HUD_COLOUR_OGL = None #: The percent-scaled (r, g, b) values of the player's chosen HUD colour.

_DEATH_SCREENS = None #: A collection of all screens that may be rendered when the player dies.

class HUDScreen(object):
	"""
	An intelligent container for all data and textures that comprise the player's
	HUD.
	"""
	_data_ecm_count = None #: The last-displayed number of ECMs available to the player.
	_data_energy = None #: The last-read energy data.
	_data_hull = None #: The last-read hull data.
	_data_reactor = None #: The last-displayed reactor distributions.
	_data_shield = None #: The last-read shield data.
	_data_speed = None #: The last-read speed data.
	_frame = 0 #: The frame being rendered. Used for FPS-sensitive operations.
	_player = None #: The player monitored by this HUDElements object.
	_readout_engines = None #: The last-displayed engines subsystems integrity value.
	_readout_sensors = None #: The last-displayed sensors subsystems integrity value.
	_readout_shields = None #: The last-displayed shields subsystems integrity value.
	_readout_weapons = None #: The last-displayed weapons subsystems integrity value.
	_reset = True #: True when all HUD elements need to be redrawn.
	_target = None #: The last-tracked target.
	_texture_bolts = None #: The texture to use to display the player's bolts.
	_texture_ecm = None #: The texture information used to indicate the player's ECM stock.
	_texture_energy = None #: Texture information used to indicate the player's energy reserve status.
	_texture_hull = None #: Texture information used to indicate the player's hull status.
	_texture_missiles = None #: The texture to use to display the player's missiles.
	_texture_offscreen = None #: Texture information used to indicate the position of an offscreen target.
	_texture_radar = None #: The texture information used to indicate the relative position of other players.
	_texture_reactor = None #: The texture information used to indicate the player's reactor allocations.
	_texture_shield = None #: Texture information used to indicate the player's shielding status.
	_texture_speed = None #: Texture information used to indicate the player's velocity status.
	_texture_subsystems = None #: Texture information used to indicate the player's subsystems status.
	_texture_target = None #: The texture information used to indicate key information about the target.
	_texture_target_subsystems = None #: The texture information used to indicate the target's subsystems' conditions.
	_textures_hostile = [] #: The textures used to indicate where to position hostile warning icons.
	_textures_missile = [] #: The textures used to indicate where to position missile warning icons.
	
	def __init__(self, player):
		"""
		Initialises with the player to be monitored during each tick.
		
		@type player: L{tankchan.TankChan}
		@param player: The player to be monitored.
		"""
		self._player = player
		
	def render(self, messages, scoring):
		"""
		Displays the HUD elements.
		
		@type messages: L{MessageBox}
		@param messages: The message box to display along with the HUD elements.
		@type scoring: L{Scoring}
		@param scoring: The scoring elements to display along with the HUD
		    elements.
		"""
		player_sensors = self._player.getSubsystemCondition(tankchan.SUBSYSTEM_SENSORS)
		
		textures = [
		 _TEXTURE_CENTRE,
		 _TEXTURE_PLAYER,
		 self._texture_energy,
		 self._texture_speed,
		 self._texture_shield,
		 self._texture_hull,
		 self._texture_subsystems,
		 self._texture_reactor,
		 self._texture_ecm,
		 self._texture_bolts,
		 self._texture_missiles,
		 scoring.getRankTexture(),
		 scoring.getScoreTexture(),
		 scoring.getKillTexture(),
		 scoring.getTimeTexture(),
		 messages.getTexture()
		]
		if player_sensors >= tankchan.SUBSYSTEM_CONDITION_CRIPPLED:
			textures.append(self._texture_offscreen)
			textures += self._textures_hostile
			textures += self._textures_missile
			textures.append(_TEXTURE_RADAR)
			textures.append(self._texture_radar)
			if self._target:
				textures.append(_TEXTURE_TARGET)
				if self._texture_target:
					self._renderTargetCone()
					textures.append(self._texture_target)
					if player_sensors >= tankchan.SUBSYSTEM_CONDITION_FUNCTIONAL:
						textures.append(self._texture_target_subsystems)
						
		_render(textures)
		
	def reset(self):
		"""
		Indicates that all HUD elements should be redrawn.
		"""
		self._reset = True
		self._frame = 0
		
	def update(self, other_players, missiles, hostiles, target):
		"""
		Rebuilds any HUD textures that need to be updated. This function should be
		invoked once every frame to keep the HUD's elements current.
		
		@type other_players: sequence
		@param other_players: A collection of L{tankchan.TankChan} objects, used
		    to position icons on the radar.
		@type missiles: sequence
		@param missiles: A collection of L{weapon.Missile} objects, used to
		    position inbound missile icons in the threat circle.
		@type hostiles: sequence
		@param hostiles: A collection of L{tankchan.TankChan} objects, used to
		    position hostile player icons in the threat circle.
		@type target: L{tankchan.TankChan}|None
		@param target: The ship targetted by the player, or None if the player
		    is not tracking a target.
		"""
		player_sensors = self._player.getSubsystemCondition(tankchan.SUBSYSTEM_SENSORS)
		target_changed = not target is self._target #Allows relevant elements to be instantly updated.
		self._target = target
		
		if target and not target.isOnscreen(): #Render offscreen indicator.
			self._redrawOffscreen(target)
		elif self._texture_offscreen: #Remove the indicator.
			self._texture_offscreen = None
			
		if player_sensors > tankchan.SUBSYSTEM_CONDITION_CRIPPLED:
			#Draw position markers for all attacking ships.
			self._redrawHostiles(hostiles)
			
			#Draw position markers for all hostile missiles.
			self._redrawMissiles(missiles)
			
			#Determine whether the radar should be redrawn.
			if self._frame == _RADAR_REDRAW_FRAME or (_RADAR_REDRAW_FRAME and not self._frame % _RADAR_REDRAW_FRAME):
				self._redrawRadar(other_players)
		else:
			self._textures_hostile = self._textures_missile = []
			self._texture_radar = None
			
		#Determine whether this is an update opportunity for the central elements.
		if self._reset or self._frame == _CENTRAL_REDRAW_FRAME or (_CENTRAL_REDRAW_FRAME and not self._frame % _CENTRAL_REDRAW_FRAME):
			#Determine whether the energy gauge needs to be redrawn.
			energy_data = self._player.getEnergy()
			if not energy_data == self._data_energy:
				self._redrawEnergyGauge(energy_data)
				
			#Determine whether the speed gauge needs to be redrawn.
			speed_data = self._player.getSpeed()
			if not speed_data == self._data_speed:
				self._redrawSpeedGauge(speed_data)
				
			#Determine whether the shield box needs to be redrawn.
			shield_data = self._player.getShielding()
			if not shield_data == self._data_shield:
				self._redrawShieldBox(shield_data)
				
			#Determine whether the hull box needs to be redrawn.
			hull_data = self._player.getHull()
			if not hull_data == self._data_hull:
				self._redrawHullBox(hull_data)
				
		#Determine whether this is an update opportunity for the target elements.
		if self._reset or target_changed or self._frame == _TARGET_REDRAW_FRAME or (_TARGET_REDRAW_FRAME and not self._frame % _TARGET_REDRAW_FRAME):
			if self._target:
				#Determine whether the target data needs to be redrawn.
				if player_sensors >= tankchan.SUBSYSTEM_CONDITION_CRIPPLED:
					(current_speed, max_speed, target_speed) = target.getSpeed()
					(camera_offset_x, camera_offset_y, camera_offset_z, camera_distance) = target.getCameraDistances()
					self._redrawTarget(current_speed, camera_distance, target.getName())
				else:
					if self._texture_target:
						self._texture_target = None
						
				#Determine whether the subsystems data needs to be redrawn.
				if player_sensors >= tankchan.SUBSYSTEM_CONDITION_GOOD:
					self._redrawTargetSubsystems(
					 self._target.getSubsystemCondition(tankchan.SUBSYSTEM_SENSORS),
					 self._target.getSubsystemCondition(tankchan.SUBSYSTEM_ENGINES),
					 self._target.getSubsystemCondition(tankchan.SUBSYSTEM_SHIELDS),
					 self._target.getSubsystemCondition(tankchan.SUBSYSTEM_WEAPONS)
					)
				else:
					if self._texture_target_subsystems: #Remove the information.
						self._texture_target_subsystems = None
			else:
				self._texture_target_subsystems = None
				self._texture_target = None
				
		#Determine whether the subsystems block needs to be redrawn.
		(current_sensors, max_sensors) = self._player.getSubsystem(tankchan.SUBSYSTEM_SENSORS)
		(current_engines, max_engines) = self._player.getSubsystem(tankchan.SUBSYSTEM_ENGINES)
		(current_shields, max_shields) = self._player.getSubsystem(tankchan.SUBSYSTEM_SHIELDS)
		(current_weapons, max_weapons) = self._player.getSubsystem(tankchan.SUBSYSTEM_WEAPONS)
		readout_sensors = int(100 * (float(current_sensors) / max_sensors))
		readout_engines = int(100 * (float(current_engines) / max_engines))
		readout_shields = int(100 * (float(current_shields) / max_shields))
		readout_weapons = int(100 * (float(current_weapons) / max_weapons))
		if not readout_sensors == self._readout_sensors or not readout_engines == self._readout_engines or not readout_shields == self._readout_shields or not readout_weapons == self._readout_weapons:
			self._redrawSubsystems(readout_sensors, readout_engines, readout_shields, readout_weapons)
			
		#Determine whether the reactor block needs to be redrawn.
		reactor_data = self._player.getReactorDistribution()
		if not reactor_data == self._data_reactor:
			self._redrawReactor(reactor_data)
			
		#Determine whether the ECM block needs to be redrawn.
		ecm_count = self._player.getECMStock()
		if not ecm_count == self._data_ecm_count:
			self._redrawECMCount(ecm_count)
			
		#Determine whether the weapon systems need to be redrawn.
		(bolt_system, missile_system) = self._player.getWeaponSystems()
		bolt_update = bolt_system.getHUDInformation()
		if bolt_update:
			self._redrawBoltsStatus(bolt_update)
		missile_update = missile_system.getHUDInformation()
		if missile_update:
			self._redrawMissilesStatus(missile_update)
			
		self._frame += 1
		if self._frame == session_constants.FPS:
			self._frame = 0
		self._reset = False
		
	def _redrawBoltsStatus(self, bolts_status):
		"""
		Updates the player's bolt subsystem status.
		
		@type bolts_status: sequence
		@param bolts_status: A collection of (selected:bool, text:str, ready:bool)
		    elements that describe the current status of each weapon belonging
		    to the player's bolt subsystem. 
		"""
		status_surface = self._redrawWeaponsStatus(bolts_status)
		
		_clearTexture(self._texture_bolts)
		self._texture_bolts = _buildTexture(session_constants.WIDTH - 164, session_constants.HEIGHT - 69 - status_surface.get_height(), status_surface)
		
	def _redrawCentralIndicators(self, threats, texture):
		"""
		Constructs a collection of drawable texture data for all given threats.
		
		@type threats: seqeunce
		@param threats: A collection of all threats for which drawable texture
		    data should be drawn.
		@type texture: sequence(3)
		@param texture: The OpenGL texture ID, width, and height of the texture to
		    use in constructing drawable data.
		
		@rtype: list
		@return: A list of all constructed drawable texture data.
		"""
		_threat_range = _THREAT_RANGE
		_width_half = session_constants.WIDTH_HALF
		_height_half = session_constants.HEIGHT_HALF
		_size_centre_half = _SIZE_CENTRE_HALF
		(_indicator_texture, _indicator_width, _indicator_height) = texture
		
		textures = []
		
		for threat in threats:
			if not threat.isAlive():
				continue
				
			(offset_x, offset_y, offset_z, distance) = threat.getCameraDistances()
			if distance <= _threat_range:
				x = _width_half + (offset_x / _threat_range) * _size_centre_half
				z = _height_half + (offset_z / _threat_range) * _size_centre_half
				
				scale = 0.25 + (1.0 - distance / _threat_range) * .75
				width_half = _indicator_width * scale
				height_half = _indicator_height * scale
				
				textures.append((
				 x - width_half,
				 z - height_half,
				 x + width_half,
				 z + height_half,
				 _indicator_texture
				))
		return textures
		
	def _redrawECMCount(self, ecm_count):
		"""
		Updates the player's ECM stock display.
		
		@type ecm_count: int
		@param ecm_count: The number of ECMs currently available for use by the
		    player. 
		"""
		self._data_ecm_count = ecm_count
		
		ecm_text = _STATS_FONT.render("ECM:%02i" % (ecm_count), True, _HUD_COLOUR)
		ecm_surface = _BASE_ECM.copy()
		ecm_surface.blit(ecm_text, (5, -2))
		
		_clearTexture(self._texture_ecm)
		self._texture_ecm = _buildTexture(session_constants.WIDTH - 59, session_constants.HEIGHT - 146, ecm_surface)
		
	def _redrawEnergyGauge(self, energy_data):
		"""
		Updates the player's energy reserve display.
		
		@type energy_data: sequence(2)
		@param energy_data: The player's current energy level and maximum energy
		    capacity. 
		"""
		(_r, _g, _b) = _hud_colour = _HUD_COLOUR
		
		self._data_energy = (current_energy, max_energy) = energy_data
		
		energy_text = _STATS_FONT.render(str(int(current_energy)), True, _hud_colour)
		(width, height_blit) = _BLITMASK_ENERGY.get_size()
		height_blit_offset = energy_text.get_height() + 3
		height = height_blit + height_blit_offset
		energy_surface = pygame.Surface((width, height), flags=(pygame.constants.SRCALPHA | pygame.constants.SWSURFACE))
		energy_surface.blit(energy_text, (width - energy_text.get_width(), 0))
		energy_surface.fill((_r, _g, _b, 63), (0, height_blit_offset, width, height))
		energy_surface.fill((_r, _g, _b, 159), (0, height_blit_offset + int(height_blit - height_blit * (current_energy / max_energy)), width, height))
		energy_surface.blit(_BLITMASK_ENERGY, (0, height_blit_offset, width, height), None, pygame.constants.BLEND_RGBA_SUB)
		
		_clearTexture(self._texture_energy)
		self._texture_energy = _buildTexture(session_constants.WIDTH_HALF + 35, session_constants.HEIGHT_HALF - 2 - height_blit_offset, energy_surface)
		
	def _redrawHostiles(self, hostiles):
		"""
		Updates the collection of drawable texture data that represents all
		currently attacking players.
		
		@type hostiles: sequence
		@param hostiles: A collection of L{tankchan.TankChan} objects that
		    includes all hostile players.
		"""
		self._textures_hostile = self._redrawCentralIndicators(hostiles, _POSITION_HOSTILE_TEXTURE)
		
	def _redrawHullBox(self, hull_data):
		"""
		Updates the player's hull integrity display.
		
		@type hull_data: sequence(2)
		@param hull_data: The player's current hull plating and maximum hull
		    plating. 
		"""
		(_r, _g, _b) = _hud_colour = _HUD_COLOUR
		
		self._data_hull = (current_hull, max_hull) = hull_data
		
		hull_text = _STATS_FONT_LARGE.render(str(int(100 * current_hull / max_hull)), True, _hud_colour)
		hull_surface = pygame.Surface((31, 21), flags=(pygame.constants.SRCALPHA | pygame.constants.SWSURFACE))
		hull_surface.fill((_r, _g, _b, int(159 * (float(current_hull) / max_hull))))
		hull_surface.blit(hull_text, (16 - hull_text.get_width() / 2, 11 - hull_text.get_height() / 2))
		
		_clearTexture(self._texture_hull)
		self._texture_hull = _buildTexture(session_constants.WIDTH_HALF - 16, session_constants.HEIGHT_HALF + 128, hull_surface)
		
	def _redrawMissiles(self, missiles):
		"""
		Updates the collection of drawable texture data that represents all
		currently inbound missiles.
		
		@type missiles: sequence
		@param missiles: A collection of L{weapons.Missile} objects that includes
		    all currently inbound missiles.
		"""
		if missiles:
			self._textures_missile = self._redrawCentralIndicators(missiles, _POSITION_MISSILE_TEXTURE)
		else:
			self._textures_missile = []
			
	def _redrawMissilesStatus(self, missiles_status):
		"""
		Updates the player's missile subsystem status.
		
		@type missiles_status: sequence
		@param missiles_status: A collection of (selected:bool, text:str,
		    ready:bool) elements that describe the current status of each weapon
		    belonging to the player's missile subsystem. 
		"""
		status_surface = self._redrawWeaponsStatus(missiles_status)
		
		_clearTexture(self._texture_missiles)
		self._texture_missiles = _buildTexture(session_constants.WIDTH - 164, session_constants.HEIGHT - 69, status_surface)
		
	def _redrawOffscreen(self, target):
		"""
		Updates the player's target's offscreen indicator position, used to help
		players find their target in a dogfight.
		
		@type target: L{tankchan.TankChan}
		@param target: The player's current target.
		"""
		_width = session_constants.WIDTH
		_height = session_constants.HEIGHT
		(_reticle_texture, _reticle_width, _reticle_height) = _RETICLE_OFFSCREEN_TEXTURE
		
		(camera_offset_x, camera_offset_y, camera_offset_z, distance) = target.getCameraDistances()
		(win_x, win_y) = target.getScreenPosition()
		position_x = _width - win_x
		if win_x < 0:
			position_x = _width
		elif win_x > _width:
			position_x = 0
		position_y = _height - win_y
		if win_y < 0:
			position_y = _height
		elif win_y > _height:
			position_y = 0
		if camera_offset_z > 0: #Behind the camera; flip co-ordinates.
			position_x = _width - position_x
			position_y = _height - position_y
			
		self._texture_offscreen = (
		 position_x - _reticle_width,
		 position_y - _reticle_height,
		 position_x + _reticle_width,
		 position_y + _reticle_height,
		 _reticle_texture
		)
		
	def _redrawRadar(self, other_players):
		"""
		Places icons on the radar to represent the location of other players in
		the player's vicinity.
		
		@type other_players: sequence
		@param other_players: A collection of L{tankchan.TankChan} objects
		    representing all other players in the game.
		"""
		_radar_range = _RADAR_RANGE
		_radar_plane_equality = _RADAR_PLANE_EQUALITY
		
		radar_surface = _BASE_RADAR.copy()
		(width, height) = radar_surface.get_size()
		half_width = width / 2
		half_height = height / 2
		
		for player in other_players:
			if player.isAlive():
				(offset_x, offset_y, offset_z, distance) = player.getCameraDistances()
				distance_xz = math.sqrt(offset_x ** 2 + offset_z ** 2)
				if distance_xz <= _radar_range:
					x = (offset_x / _radar_range) * half_width
					z = (offset_z / _radar_range) * half_height
					
					(icon_flat, icon_up, icon_down) = player.getRadarIcons()
					radar_icon = icon_flat
					if abs(offset_y) > _radar_plane_equality:
						if offset_y < 0.0:
							radar_icon = icon_down
						else:
							radar_icon = icon_up
					radar_surface.blit(radar_icon, (half_width + x - 2, half_height - z - 2))
					
		_clearTexture(self._texture_radar)
		self._texture_radar = _buildTexture(3, session_constants.HEIGHT - 129, radar_surface)
		
	def _redrawReactor(self, reactor_data):
		"""
		Updates the player's reactor allocation display.
		
		@type reactor_data: sequence(3)
		@param reactor_data: The player's engine, shield, and weapon power
		    allocations, expressed as a percentage.
		"""
		(_r, _g, _b) = _HUD_COLOUR
		
		self._data_reactor = (engine_power, shield_power, weapon_power) = reactor_data
		engine_power = int(engine_power * 100)
		shield_power = int(shield_power * 100)
		weapon_power = int(weapon_power * 100)
		
		reactor_surface = pygame.Surface((86, 100), flags=(pygame.constants.SRCALPHA | pygame.constants.SWSURFACE))
		reactor_surface.fill((_r, _g, _b, 63), (0, 0, 24, 100))
		reactor_surface.fill((_r, _g, _b, 63), (31, 0, 24, 100))
		reactor_surface.fill((_r, _g, _b, 63), (62, 0, 24, 100))
		reactor_surface.fill((_r, _g, _b, 159), (0, 100 - engine_power, 24, engine_power))
		reactor_surface.fill((_r, _g, _b, 159), (31, 100 - shield_power, 24, shield_power))
		reactor_surface.fill((_r, _g, _b, 159), (62, 100 - weapon_power, 24, weapon_power))
		
		_clearTexture(self._texture_reactor)
		self._texture_reactor = _buildTexture(session_constants.WIDTH - 91, session_constants.HEIGHT - 125, reactor_surface)
		
	def _redrawShieldBox(self, shield_data):
		"""
		Updates the player's shield status display.
		
		@type shield_data: sequence(4)
		@param shield_data: The player's current shielding, shielding capacity,
		    maximum shielding, and a boolean value that indicates whether the
		    player's shields are online or not.
		"""
		(_r, _g, _b) = _hud_colour = _HUD_COLOUR
		
		self._data_shield = (current_shielding, available_shielding, max_shielding, shields_up) = shield_data
		
		shield_text = _STATS_FONT_LARGE.render(str(int(100 * current_shielding / max_shielding)), True, _hud_colour)
		shield_surface = pygame.Surface(_BLITMASK_SHIELD.get_size(), flags=(pygame.constants.SRCALPHA | pygame.constants.SWSURFACE))
		if shields_up:
			shield_surface.fill((_r, _g, _b, int(159 * (float(current_shielding) / available_shielding))))
		else:
			shield_surface.fill((max(0, 127 - _r), max(0, 127 - _g), max(0, 127 - _b), 95))
		shield_surface.blit(shield_text, (shield_surface.get_width() / 2 - shield_text.get_width() / 2 + 1, shield_surface.get_height() / 2 - shield_text.get_height() / 2 + 1))
		shield_surface.blit(_BLITMASK_SHIELD, (0, 0), None, pygame.constants.BLEND_RGBA_SUB)
		
		_clearTexture(self._texture_shield)
		self._texture_shield = _buildTexture(session_constants.WIDTH_HALF - 23, session_constants.HEIGHT_HALF + 93, shield_surface)
		
	def _redrawSpeedGauge(self, speed_data):
		"""
		Updates the player's speed status display.
		
		@type speed_data: sequence(3)
		@param speed_data: The player's current speed, maximum speed, and target
		    speed.
		"""
		(_r, _g, _b) = _hud_colour = _HUD_COLOUR
		
		self._data_speed = (current_speed, max_speed, target_speed) = speed_data
		
		speed_text = _STATS_FONT.render("%.1fm/s" %(current_speed), True, _hud_colour)
		(width, height_blit) = _BLITMASK_SPEED.get_size()
		height_blit_offset = speed_text.get_height() + 3
		height = height_blit + height_blit_offset
		speed_surface = pygame.Surface((width, height), flags=(pygame.constants.SRCALPHA | pygame.constants.SWSURFACE))
		speed_surface.blit(speed_text, (0, 0))
		speed_surface.fill((_r, _g, _b, 63), (0, height_blit_offset, width, height))
		speed_surface.fill((_r, _g, _b, 159), (0, height_blit_offset + int(height_blit - height_blit * min(1.0, current_speed / max_speed)), width, height))
		speed_surface.fill((_r, _g, _b, 223), (0, height_blit_offset + int(height_blit - height_blit * (target_speed / max_speed)), width, 1))
		speed_surface.blit(_BLITMASK_SPEED, (0, height_blit_offset, width, height), None, pygame.constants.BLEND_RGBA_SUB)
		
		_clearTexture(self._texture_speed)
		self._texture_speed = _buildTexture(session_constants.WIDTH_HALF - 125, session_constants.HEIGHT_HALF - 2 - height_blit_offset, speed_surface)
		
	def _redrawSubsystems(self, readout_sensors, readout_engines, readout_shields, readout_weapons):
		"""
		Updates the player's subsystems status display.
		
		@type readout_sensors: int
		@param readout_sensors: The player's current sensors integrity, expressed
		    as a value between 0 and 100.
		@type readout_engines: int
		@param readout_engines: The player's current engines integrity, expressed
		    as a value between 0 and 100.
		@type readout_shields: int
		@param readout_shields: The player's current shields integrity, expressed
		    as a value between 0 and 100.
		@type readout_weapons: int
		@param readout_weapons: The player's current weapons integrity, expressed
		    as a value between 0 and 100.
		"""
		_stats_font_medium = _STATS_FONT_MEDIUM
		_hud_colour = _HUD_COLOUR
		_dynamic_box_padding = _DYNAMIC_BOX_PADDING
		_dynamic_box_border = _DYNAMIC_BOX_BORDER
		
		self._readout_sensors = readout_sensors
		self._readout_engines = readout_engines
		self._readout_shields = readout_shields
		self._readout_weapons = readout_weapons
		
		sensors_text = _stats_font_medium.render('sensors: %3i%%' % readout_sensors, True, _hud_colour)
		engines_text = _stats_font_medium.render('engines: %3i%%' % readout_engines, True, _hud_colour)
		shields_text = _stats_font_medium.render('shields: %3i%%' % readout_shields, True, _hud_colour)
		weapons_text = _stats_font_medium.render('weapons: %3i%%' % readout_weapons, True, _hud_colour)
		
		subsystem_surface = _BASE_SUBSYSTEMS.copy()
		(width, height) = subsystem_surface.get_size()
		
		offset_x = _dynamic_box_border + _dynamic_box_padding
		offset_y = _dynamic_box_padding
		subsystem_surface.blit(sensors_text, (offset_x, offset_y))
		offset_y += sensors_text.get_height()
		subsystem_surface.blit(engines_text, (offset_x, offset_y))
		offset_y += engines_text.get_height()
		subsystem_surface.blit(shields_text, (offset_x, offset_y))
		offset_y += shields_text.get_height()
		subsystem_surface.blit(weapons_text, (offset_x, offset_y))
		
		_clearTexture(self._texture_subsystems)
		self._texture_subsystems = _buildTexture(session_constants.WIDTH - width, 0, subsystem_surface)
		
	def _redrawTarget(self, target_velocity, target_distance, target_name):
		"""
		Updates the target's status display.
		
		@type target_velocity: float
		@param target_velocity: The target's current speed.
		@type target_distance: float
		@param target_distance: The target's distance from the player.
		@type target_name: basestring
		@param target_name: The player-name of the target.
		"""
		_stats_font = _STATS_FONT
		_hud_colour = _HUD_COLOUR
		
		name_text = _stats_font.render(target_name, True, _hud_colour)
		velocity_text = _stats_font.render('V:%0.1fm/s' % (target_velocity), True, _hud_colour)
		distance_text = _stats_font.render('D:%0.1fm' % (target_distance), True, _hud_colour)
		
		target_surface = _BASE_TARGET.copy()
		
		offset_y = 0
		target_surface.blit(name_text, (0, offset_y))
		offset_y += name_text.get_height()
		target_surface.blit(velocity_text, (0, offset_y))
		offset_y += velocity_text.get_height()
		target_surface.blit(distance_text, (0, offset_y))
		
		_clearTexture(self._texture_target)
		self._texture_target = _buildTexture(134, session_constants.HEIGHT - 128, target_surface)
		
	def _redrawTargetSubsystems(self, target_sensors, target_engines, target_shields, target_weapons):
		"""
		Updates the target's subsystems status display.
		
		@type target_sensors: int
		@param target_sensors: The target's current sensors integrity, expressed
		    as a subsystem status condition constant.
		@type target_engines: int
		@param target_engines: The target's current engines integrity, expressed
		    as a subsystem status condition constant.
		@type target_shields: int
		@param target_shields: The target's current shields integrity, expressed
		    as a subsystem status condition constant.
		@type target_weapons: int
		@param target_weapons: The target's current weapons integrity, expressed
		    as a subsystem status condition constant.
		"""
		_stats_font = _STATS_FONT
		_hud_colour = _HUD_COLOUR
		_dynamic_box_padding = _DYNAMIC_BOX_PADDING
		_dynamic_box_border = _DYNAMIC_BOX_BORDER
		
		sensors_text = _stats_font.render('sensors: %s' % tankchan.SUBSYSTEM_CONDITION_MAP[target_sensors], True, _hud_colour)
		engines_text = _stats_font.render('engines: %s' % tankchan.SUBSYSTEM_CONDITION_MAP[target_engines], True, _hud_colour)
		shields_text = _stats_font.render('shields: %s' % tankchan.SUBSYSTEM_CONDITION_MAP[target_shields], True, _hud_colour)
		weapons_text = _stats_font.render('weapons: %s' % tankchan.SUBSYSTEM_CONDITION_MAP[target_weapons], True, _hud_colour)
		
		subsystem_surface = _BASE_TARGET_SUBSYSTEMS.copy()
		(width, height) = subsystem_surface.get_size()
		
		offset_x = _dynamic_box_padding
		offset_y = _dynamic_box_padding + _dynamic_box_border
		subsystem_surface.blit(sensors_text, (offset_x, offset_y))
		offset_y += sensors_text.get_height()
		subsystem_surface.blit(engines_text, (offset_x, offset_y))
		offset_y += engines_text.get_height()
		subsystem_surface.blit(shields_text, (offset_x, offset_y))
		offset_y += shields_text.get_height()
		subsystem_surface.blit(weapons_text, (offset_x, offset_y))
		
		_clearTexture(self._texture_target_subsystems)
		self._texture_target_subsystems = _buildTexture(223, session_constants.HEIGHT - height - 3, subsystem_surface)
		
	def _redrawWeaponsStatus(self, status):
		"""
		Constructs a PyGame Surface that represents that status of a weapons
		subsystem.
		
		@type status: sequence
		@param status: A collection of (selected:bool, text:str, ready:bool)
		    elements that describe the current status of each weapon belonging
		    to the subsystem being rendered.
		
		@rtype: pygame.Surface
		@return: A PyGame Surface containing the supplied information.
		"""
		_hud_colour = (_r, _g, _b) = list(_HUD_COLOUR)
		_discharged_colour = (_r / 2, _g / 2, _b / 2)
		lines = []
		height = 0
		prefix = None
		for (selected, text, ready) in status:
			colour = None
			if ready:
				colour = _hud_colour
			else:
				colour = _discharged_colour
			if selected:
				prefix = '>'
			else:
				prefix = ' '
				
			line = _STATS_FONT.render(prefix + text, True, colour)
			height += line.get_height()
			lines.append(line)
			
		surface = pygame.Surface((_WEAPONS_WIDTH, height), flags=(pygame.constants.SRCALPHA | pygame.constants.SWSURFACE))
		surface.fill((_r, _g, _b, 63))
		
		offset = 0
		for line in lines:
			surface.blit(line, (0, offset))
			offset += line.get_height()
			
		return surface
		
	def _renderTargetCone(self):
		"""
		Renders a cone that points at the target's position relative to the centre
		of the player's ship, complementing the offscreen indicator.
		"""
		(camera_offset_x, camera_offset_y, camera_offset_z, camera_distance) = self._target.getCameraDistances()
		
		y_invert = 180.0
		if camera_offset_z < 0.0:
			y_invert = 0.0
		degrees = math.degrees
		atan2 = math.atan2
		rotation_matrix = matrix.buildRotationMatrix(
		 degrees(atan2(-camera_offset_y, camera_offset_z)),
		 y_invert + degrees(atan2(-camera_offset_x, camera_offset_z)),
		 0
		)
		
		GL.glMatrixMode(GL.GL_PROJECTION)
		GL.glPushMatrix()
		GL.glLoadIdentity()
		GL.glOrtho(0, session_constants.WIDTH, 0, session_constants.HEIGHT, 0, 80)
		
		GL.glMatrixMode(GL.GL_MODELVIEW)
		GL.glPushMatrix()
		GL.glLoadIdentity()
		
		rendering.renderCone((177, 44, -40), rotation_matrix, 10, 37, _HUD_COLOUR_OGL)
		
		GL.glPopMatrix()
		GL.glMatrixMode(GL.GL_PROJECTION)
		GL.glPopMatrix()
		GL.glMatrixMode(GL.GL_MODELVIEW)
		
		
class MessageBox(object):
	"""
	An interactive container for creating and displaying messages for in-game
	communication and status updates.
	"""
	_messages = None #: A list of all messages (and the source) to be displayed, along with their TTL.
	_texture = None #: The texture to use to render these messages.
	_unprocessed_messages = None #: A list of all messages that were added during the last frame.
	_user_entry = None #: A list of characters entered by the user, to be sent to everyone.
	_user_entry_changed = False #: True if the user has entered text since the last update.
	
	def __init__(self):
		"""
		Initialises the message queues.
		"""
		self._messages = []
		self._unprocessed_messages = []
		
	def abortTextEntry(self):
		"""
		Discards any partially entered message.
		"""
		self._user_entry_changed = True
		self._user_entry = None
		
	def addCharacter(self, character):
		"""
		Adds a character to the in-progress message.
		
		If a backspace character is passed, the last character in the in-progress
		message is removed.
		
		@type character: basestring
		@param character: The character to be added.
		"""
		self._user_entry_changed = True
		if character == '\x08': #Backspace.
			self._user_entry = self._user_entry[:-1]
		else:
			self._user_entry.append(character)
			
	def addMessage(self, source, text, announcement):
		"""
		Adds a message to the queue of messages to be displayed following this
		object's next update.
		
		@type source: basestring
		@param source: The source or nature of this message.
		@type text: basestring
		@param text: The message to be displayed.
		@type announcement: bool
		@param announcement: True if this message's source should be highlighted
		    and its display duration doubled.
		"""
		text = text.strip()
		if text:
			self._unprocessed_messages.append((source, text, announcement))
			
	def beginTextEntry(self):
		"""
		Creates a new in-progress message.
		"""
		self._user_entry_changed = True
		self._user_entry = []
		
	def completeTextEntry(self):
		"""
		Ends and compiles an in-progress message.
		
		@rtype: str
		@return: The completed message.
		"""
		self._user_entry_changed = True
		message = ''.join(self._user_entry).strip()
		self._user_entry = None
		self.addMessage("me", message, False)
		return message
		
	def getTexture(self):
		"""
		Returns the drawable texture data used to display all visible messages.
		
		@rtype: tuple(5)|None
		@return: The left, top, right, and bottom pixel co-ordinates of the
		    texture to be drawn, plus the texture itself.
		"""
		return self._texture
		
	def isTextEntryInProgress(self):
		"""
		Indicates whether message entry is in-progress.
		
		@rtype: bool
		@return: True if there is an in-progress message being constructed.
		"""
		return not self._user_entry is None
		
	def tick(self, seconds):
		"""
		Updates the currently displayed messages, culling those that have expired
		or that have been pushed offscreen by newer events, which it also
		processes.
		
		@type seconds: float
		@param seconds: The number of seconds that elapsed since the last time
		    this function was invoked.
		"""
		changed = False
		
		#Decrement TTLs and cull dead messages.
		pre_update_count = len(self._messages)
		for message in self._messages:
			message[1] -= seconds
		self._messages = [message for message in self._messages if message[1] > 0]
		changed = len(self._messages) < pre_update_count
		
		#Process new messages.
		if self._unprocessed_messages:
			changed = changed or True
			for (source, text, annoucement) in self._unprocessed_messages:
				ttl = _MESSAGE_TTL
				if annoucement:
					ttl += ttl
				self._messages.append([self._rasterize(source, text, annoucement), ttl])
			self._unprocessed_messages = []
			
		if changed or self._user_entry_changed: #Rebuild texture.
			self._rebuildTexture()
			
	def _rebuildTexture(self):
		"""
		Handles the actual process of drawing a new texture to use for displaying
		current messages.
		"""
		_message_height = _MESSAGE_FONT_SPACE_SIZE[1]
		_message_box_width = _MESSAGE_BOX_WIDTH
		_message_entry_width = _MESSAGE_ENTRY_WIDTH
		_dynamic_box_padding = _DYNAMIC_BOX_PADDING
		_dynamic_box_border = _DYNAMIC_BOX_BORDER
		(_r, _g, _b) = _hud_colour = _HUD_COLOUR
		
		self._user_entry_changed = False
		
		#Pull renderable messages from the end of the list.
		available_lines = _MESSAGE_LINE_CAP
		retained_messages = []
		for message in reversed(self._messages):
			line_count = len(message[0]) - 1
			if line_count > available_lines: #No more space.
				break
			else:
				retained_messages.append(message)
				available_lines -= line_count
		retained_messages.reverse()
		self._messages = retained_messages
		
		if not self._messages and self._user_entry is None: #Nothing to render.
			self._texture = None
		else: #Build a texture.
			height = None
			if self._messages:
				height = _message_height * (_MESSAGE_LINE_CAP - available_lines) + _dynamic_box_padding * 2 + _dynamic_box_border
				height -= height % 2
			else:
				height = 0
				
			height_entry = None
			if not self._user_entry is None:
				height_entry = _message_height + _dynamic_box_padding * 2 + _dynamic_box_border
				height_entry -= height_entry % 2
			else:
				height_entry = 0
				
			_clearTexture(self._texture)
			if height + height_entry: #Create new texture.
				message_surface = pygame.Surface((_message_box_width, height + height_entry), flags=(pygame.constants.SRCALPHA | pygame.constants.SWSURFACE))
				
				#Blit all messages.
				if self._messages:
					message_surface.fill((_r, _g, _b, 191), (0, height - _dynamic_box_border, _message_box_width, _dynamic_box_border))
					message_surface.fill((_r, _g, _b, 191), (_message_box_width - _dynamic_box_border, 0, _dynamic_box_border, height - _dynamic_box_border))
					message_surface.fill((_r, _g, _b, 63), (0, 0, _message_box_width - _dynamic_box_border, height - _dynamic_box_border))
					
					y_offset = _dynamic_box_padding
					for message in self._messages:
						source = message[0][0]
						message_surface.blit(source, (_dynamic_box_padding, y_offset))
						x_offset = _dynamic_box_padding + source.get_width()
						for line in message[0][1:]:
							message_surface.blit(line, (x_offset, y_offset))
							y_offset += line.get_height()
							
				#Blit user input.
				if not self._user_entry is None:
					message_surface.fill((_r, _g, _b, 191), (0, height + height_entry - _dynamic_box_border, _message_entry_width, _dynamic_box_border))
					message_surface.fill((_r, _g, _b, 191), (_message_entry_width - _dynamic_box_border, height, _dynamic_box_border, height_entry - _dynamic_box_border))
					message_surface.fill((_r, _g, _b, 63), (0, height, _message_entry_width - _dynamic_box_border, height_entry - _dynamic_box_border))
					
					user_input = _MESSAGE_FONT.render(''.join(self._user_entry), True, _hud_colour)
					if user_input.get_width() < _message_entry_width - _dynamic_box_border - _dynamic_box_padding: #Left-align.
						message_surface.blit(user_input, (_dynamic_box_padding, height + _dynamic_box_padding))
					else: #Right-align.
						message_surface.blit(user_input, (_message_entry_width - _dynamic_box_padding - _dynamic_box_border - user_input.get_width(), height + _dynamic_box_padding))
						
				self._texture = _buildTexture(0, 0, message_surface)
			else: #Nothing to display.
				self._texture = None
				
	def _rasterize(self, source, text, announcement):
		"""
		Generates a PyGame Surface to represent a message.
		
		Long messages are split across several lines, based on whitespace.
		
		@type source: basestring
		@param source: The source or nature of the message.
		@type text: basestring
		@param text: The message to be rasterized.
		@type announcement: bool
		@param announcement: True if the message's source should be highlighted
		    and its display duration doubled.
		
		@rtype: sequence
		@return: The PyGame Surface(s) used to represent each line of the
		    rasterized message.
		"""
		_message_font = _MESSAGE_FONT
		space_width = _MESSAGE_FONT_SPACE_SIZE[0] 
		source_colour = _hud_colour = _HUD_COLOUR
		
		if announcement:
			source_colour = (255, 255, 255)
		source_surface = _message_font.render(source + ':', True, source_colour)
		available_width = 512 - source_surface.get_width()
		
		output = [source_surface]
		if _message_font.size(text)[0] + space_width <= available_width: #It can all fit on one line.
			output.append(_message_font.render(' ' + text, True, _hud_colour))
		else:
			line = []
			line_size = 0
			for word in text.split():
				word_size = space_width + _message_font.size(word)[0]
				if line_size + word_size <= available_width:
					line.append(word)
					line_size += word_size
				else:
					if line_size: #There is already text, so split here and add the new word to the next line.
						output.append(_message_font.render(' ' + ' '.join(line), True, _hud_colour))
						line = [word]
						line_size = word_size
					else: #It's a really huge word. Just render it.
						output.append(_message_font.render(' ' + word, True, _hud_colour))
						line_size = 0
			if line_size: #One last line to render.
				output.append(_message_font.render(' ' + ' '.join(line), True, _hud_colour))
		return output
		
		
class Scoring(object):
	"""
	A container for statistical HUD data that doesn't have any involvement with
	game-space of visual-space.
	"""
	_current_time = None #: The number of seconds that have elapsed.
	_current_time_int = None #: The number of seconds that have elapsed, cached as an int.
	_kills = None #: The number of kills made by the player.
	_kills_changed = False #: True if the killcount has been updated.
	_kills_texture = None #: The texture information used to render the player's killcount.
	_rank = None #: The player's team's current rank.
	_rank_changed = False #: True if the rank has been updated.
	_rank_texture = None #: The texture information used to render the player's team's rank.
	_score = None #: The player's team's current score.
	_score_changed = False #: True if the score has been updated.
	_score_texture = None #: The texture information used to render the player's team's score.
	_time_texture = None #: The texture information used to render the game's time data.
	_total_time = None #: The total duration of the game, as a string.
	
	def __init__(self, minutes, current_time, rank, kills, score):
		"""
		Initialises the values and textures managed by this object.
		
		@type minutes: int
		@param minutes: The number of minutes this game is set to last.
		@type current_time: float
		@param current_time: The number of seconds that have elapsed in the game
		    prior to the creation of this object (used allows mid-game joining).
		@type rank: int
		@param rank: The rank of the player's team.
		@type kills: int
		@param kills: The number of kills the player has made.
		@type score: int
		@param score: The player's team's score.
		"""
		self._rank = rank
		self._kills = kills
		self._score = score
		self._current_time = current_time
		self._current_time_int = int(current_time)
		self._total_time = "/%02i:00" % (minutes)
		
		self._rebuildKillsTexture()
		self._rebuildScoreTexture()
		self._rebuildRankTexture()
		self._rebuildTimeTexture()
		
	def changeRank(self, rank):
		"""
		Sets the player's team's rank.
		
		@type rank: int
		@param rank: The rank of the player's team.
		"""
		self._rank = rank
		self._rank_changed = True
		
	def changeScore(self, score):
		"""
		Sets the player's team's score.
		
		@type score: int
		@param score: The player's team's new score.
		"""
		self._score = score
		self._score_changed = True
		
	def getKillTexture(self):
		"""
		Returns the drawable texture data used to display the player's kills.
		
		@rtype: tuple(5)
		@return: The left, top, right, and bottom pixel co-ordinates of the
		    texture to be drawn, plus the texture itself.
		"""
		return self._kills_texture
		
	def getRankTexture(self):
		"""
		Returns the drawable texture data used to display the player's team's
		rank.
		
		@rtype: tuple(5)
		@return: The left, top, right, and bottom pixel co-ordinates of the
		    texture to be drawn, plus the texture itself.
		"""
		return self._rank_texture
		
	def getScoreTexture(self):
		"""
		Returns the drawable texture data used to display the player's team's
		score.
		
		@rtype: tuple(5)
		@return: The left, top, right, and bottom pixel co-ordinates of the
		    texture to be drawn, plus the texture itself.
		"""
		return self._score_texture
		
	def getTimeTexture(self):
		"""
		Returns the drawable texture data used to display the game's time status.
		
		@rtype: tuple(5)
		@return: The left, top, right, and bottom pixel co-ordinates of the
		    texture to be drawn, plus the texture itself.
		"""
		return self._time_texture
		
	def incrementKills(self):
		"""
		Adds a kill to the player's total.
		"""
		self._kills += 1
		self._kills_changed = True
		
	def tick(self, seconds):
		"""
		Updates the textures used to represent the player's scoring-related
		statistics.
		
		@type seconds: float
		@param seconds: The number of seconds that have elapsed since the last
		    time this function was invoked.
		"""
		old_time_int = self._current_time_int
		self._current_time = self._current_time + seconds
		self._current_time_int = int(self._current_time)
		
		if not old_time_int == self._current_time_int:
			self._rebuildTimeTexture()
			
		if self._kills_changed:
			self._rebuildKillsTexture()
			
		if self._score_changed:
			self._rebuildScoreTexture()
			
		if self._rank_changed:
			self._rebuildRankTexture()
			
	def _rebuildKillsTexture(self):
		"""
		Reconstructs the player's kill-count texture.
		"""
		text = _STATS_FONT.render("K:%03i" % (self._kills), True, _HUD_COLOUR)
		surface = _BASE_SCORE.copy()
		surface.blit(text, (5, -2))
		
		_clearTexture(self._kills_texture)
		self._kills_texture = _buildTexture(session_constants.WIDTH - 145, session_constants.HEIGHT - 17, surface)
		self._kills_changed = False
		
	def _rebuildRankTexture(self):
		"""
		Reconstructs the player's team's rank texture.
		"""
		text = _STATS_FONT.render("R:%03i" % (self._rank), True, _HUD_COLOUR)
		surface = _BASE_SCORE.copy()
		surface.blit(text, (5, -2))
		
		_clearTexture(self._rank_texture)
		self._rank_texture = _buildTexture(session_constants.WIDTH - 247, session_constants.HEIGHT - 17, surface)
		self._rank_changed = False
		
	def _rebuildScoreTexture(self):
		"""
		Reconstructs the player's team's score texture.
		"""
		text = _STATS_FONT.render("S:%03i" % (self._score), True, _HUD_COLOUR)
		surface = _BASE_SCORE.copy()
		surface.blit(text, (5, -2))
		
		_clearTexture(self._score_texture)
		self._score_texture = _buildTexture(session_constants.WIDTH - 196, session_constants.HEIGHT - 17, surface)
		self._score_changed = False
		
	def _rebuildTimeTexture(self):
		"""
		Reconstructs the time-status texture.
		"""
		text = _STATS_FONT.render("%02i:%02i%s" % (self._current_time_int / 60, self._current_time_int % 60, self._total_time), True, _HUD_COLOUR)
		surface = _BASE_TIME.copy()
		surface.blit(text, (5, -2))
		
		_clearTexture(self._time_texture)
		self._time_texture = _buildTexture(session_constants.WIDTH - 94, session_constants.HEIGHT - 17, surface)
		
class StatusScreen(object):
	"""
	A container for elements to display while the player is not actively engaged
	in gameplay.
	"""
	_colour = None #: The colour to use to render status text.
	_image_texture = None #: The image to display in the centre of the screen.
	_offset_x = None #: The X-offset of status text, from the centre of the screen.
	_offset_y = None #: The Y-offset of status text, from the centre of the screen.
	_status_texture = None #: The status message to display.
	
	def __init__(self, image_texture_path, offset_xy, colour):
		"""
		Loads and compiles the texture data and text-rendering settings needed by
		this status screen.
		
		@type image_texture_path: basestring
		@param image_texture_path: The path to the image that will be displayed.
		@type offset_xy: sequence(2)
		@param offset_xy: The (x, y) co-ordinates, relative the the centre of the
		    screen, at which status text will be centred.
		@type colour: sequence(3)
		@param colour: The byte-valued (r, g, b) colour to use when rendering
		    status text.
		"""
		(self._offset_x, self._offset_y) = offset_xy
		self._colour = colour
		
		image_surface = pygame.image.load(image_texture_path)
		self._image_texture = _buildTexture(
		 session_constants.WIDTH_HALF - image_surface.get_width() / 2,
		 session_constants.HEIGHT_HALF - image_surface.get_height() / 2,
		 image_surface
		)
		
	def destroy(self):
		"""
		Releases OpenGL resources held by this status screen.
		"""
		_clearTexture(self._image_texture)
		_clearTexture(self._status_texture)
		
	def render(self, messages):
		"""
		Displays this status screen's contents.
		
		@type messages: L{MessageBox}
		@param messages: The message box to display along with this status screen.
		"""
		_render((messages.getTexture(), self._image_texture, self._status_texture))
		
	def setStatus(self, status):
		"""
		Updates the status text to be displayed as part of this status screen.
		
		@type status: basestring
		@param status: The text to be displayed.
		"""
		text = _MESSAGE_FONT.render(status, True, self._colour)
		_clearTexture(self._status_texture)
		self._status_texture = _buildTexture(
		 session_constants.WIDTH_HALF + self._offset_x - text.get_width() / 2,
		 session_constants.HEIGHT_HALF + self._offset_y - text.get_height() / 2,
		 text
		)
		
def init(hud_colour, message_font, stats_font, stats_font_medium, stats_font_large):
	"""
	Initialises constants required by this module.
	
	This function must be invoked before any other calls.
	
	@type hud_colour: sequence(3)
	@param hud_colour: The byte-valued (r, g, b) colour to use for HUD elements.
	@type message_font: pygame.Font
	@param message_font: The font to use for rendering messages.
	@type stats_font: pygame.Font
	@param stats_font: The font to use for rendering small statistical data.
	@type stats_font_medium: pygame.Font
	@param stats_font_medium: The font to use for rendering medium statistical data.
	@type stats_font_large: pygame.Font
	@param stats_font_large: The font to use for rendering large statistical data.
	"""
	_initVariables(hud_colour, message_font, stats_font, stats_font_medium, stats_font_large)
	_initSurfaces()
	
def getDeathScreen():
	"""
	Initialises and returns a random death screen, used to give the player
	something to look at while waiting to come back to life.
	
	@rtype: StatusScreen
	@return: A random death screen.
	"""
	return StatusScreen(*random.choice(_DEATH_SCREENS))
	
def _buildTexture(left, top, surface):
	"""
	Compiles a PyGame Surface and positioning information into an OpenGL
	co-ordinate set and texture ID, returning "drawable texture data".
	
	@type left: int
	@param left: The position at which the left edge of the surface will appear.
	@type top: int
	@param top: The position at which the top edge of the surface will appear.
	@type surface: pygame.Surface
	@param surface: The PyGame Surface to convert into an OpenGL texture.
	
	@rtype: tuple(5)
	@return: The left, top, right, and bottom pixel co-ordinates of the
	    texture to be drawn, plus the texture itself.
	"""
	(surface_width, surface_height) = surface.get_size()
	half_width = surface_width / 2
	half_height = surface_height / 2
	
	half_point = 32
	power_of_two = 64
	while power_of_two < max(surface_width, surface_height):
		half_point = power_of_two
		power_of_two += power_of_two
	power_of_two_sub_x = half_point - half_width
	power_of_two_sub_y = half_point - half_height
	
	texture_surface = pygame.Surface((power_of_two, power_of_two), flags=(pygame.constants.SRCALPHA | pygame.constants.SWSURFACE))
	texture_surface.blit(surface, (power_of_two_sub_x, power_of_two_sub_y))
	
	offset_left = left - power_of_two_sub_x
	offset_top = -top - power_of_two_sub_y + session_constants.HEIGHT - surface_height
	return (offset_left, offset_top, offset_left + power_of_two, offset_top + power_of_two, rendering.buildTexture(texture_surface))
	
def _clearTexture(texture):
	"""
	Provides a convienient means of freeing OpenGL texture memory.
	
	@type texture: sequence(5)|None
	@param texture: The left, top, right, and bottom pixel co-ordinates of the
	    texture to be drawn, plus the texture itself.
	"""
	if not texture is None:
		GL.glDeleteTextures([texture[4]])
		
def _draw(texture):
	"""
	Draws the provided texture, if any.
	
	@type texture: sequence(5)|None
	@param texture: The left, top, right, and bottom pixel co-ordinates of the
	    texture to be drawn, plus the texture itself.
	"""
	if not texture: #Nothing to render.
		return
	(left, top, right, bottom, texture_id) = texture
	
	GL.glBindTexture(GL.GL_TEXTURE_2D, texture_id)
	GL.glFrontFace(GL.GL_CCW)
	GL.glPolygonMode(GL.GL_FRONT, GL.GL_FILL)
	GL.glBegin(GL.GL_QUADS)
	GL.glTexCoord2f(0, 0)
	GL.glVertex3f(left, top, 0)
	GL.glTexCoord2f(1, 0)
	GL.glVertex3f(right, top, 0)
	GL.glTexCoord2f(1, 1)
	GL.glVertex3f(right, bottom, 0)
	GL.glTexCoord2f(0, 1)
	GL.glVertex3f(left, bottom, 0)
	GL.glEnd()
	
def _initSurfaces():
	"""
	Initialises PyGame Surfaces and OpenGL textures required by this module.
	"""
	texture_path = path_utils.getTexturesPath()
	
	global _RETICLE_OFFSCREEN_TEXTURE
	global _POSITION_HOSTILE_TEXTURE
	global _POSITION_MISSILE_TEXTURE
	reticle_offscreen = pygame.image.load(texture_path + "reticle-offscreen.png")
	_RETICLE_OFFSCREEN_TEXTURE = (rendering.buildTexture(reticle_offscreen), reticle_offscreen.get_width() / 2, reticle_offscreen.get_height() / 2)
	position_hostile = pygame.image.load(texture_path + "position-hostile.png")
	_POSITION_HOSTILE_TEXTURE = (rendering.buildTexture(position_hostile), position_hostile.get_width() / 2, position_hostile.get_height() / 2)
	position_missile = pygame.image.load(texture_path + "position-missile.png")
	_POSITION_MISSILE_TEXTURE = (rendering.buildTexture(position_missile), position_missile.get_width() / 2, position_missile.get_height() / 2)
	
	global _TEXTURE_CENTRE, _SIZE_CENTRE_HALF
	hud_centre = pygame.image.load(texture_path + "hud-centre.png")
	hud_centre_base = pygame.Surface(hud_centre.get_size(), flags=(pygame.constants.SRCALPHA | pygame.constants.SWSURFACE))
	hud_centre_base.fill((_HUD_COLOUR[0], _HUD_COLOUR[1], _HUD_COLOUR[2], 191))
	hud_centre_base.blit(hud_centre, (0, 0), None, pygame.constants.BLEND_RGBA_SUB)
	_TEXTURE_CENTRE = _buildTexture(session_constants.WIDTH_HALF - hud_centre_base.get_width() / 2, session_constants.HEIGHT_HALF - hud_centre_base.get_height() / 2, hud_centre_base)
	_SIZE_CENTRE_HALF = hud_centre.get_width() / 2
	
	global _BLITMASK_ENERGY, _BLITMASK_SPEED, _BLITMASK_SHIELD
	_BLITMASK_ENERGY = pygame.image.load(texture_path + "hud-energy-blitmask.png")
	_BLITMASK_SPEED = pygame.image.load(texture_path + "hud-speed-blitmask.png")
	_BLITMASK_SHIELD = pygame.image.load(texture_path + "hud-shield-blitmask.png")
	
	global _TEXTURE_PLAYER
	hud_player = pygame.image.load(texture_path + "hud-player.png")
	hud_player_base = pygame.Surface(hud_player.get_size(), flags=(pygame.constants.SRCALPHA | pygame.constants.SWSURFACE))
	hud_player_base.fill((_HUD_COLOUR[0], _HUD_COLOUR[1], _HUD_COLOUR[2], 191))
	hud_player_base.blit(hud_player, (0, 0), None, pygame.constants.BLEND_RGBA_SUB)
	_TEXTURE_PLAYER = _buildTexture(session_constants.WIDTH - hud_player_base.get_width() - 3, session_constants.HEIGHT - hud_player_base.get_height() - 3, hud_player_base)
	
	global _BASE_SUBSYSTEMS
	space_sample = _STATS_FONT_MEDIUM.render(" ", False, (0, 0, 0))
	width = _SUBSYSTEM_BOX_WIDTH + _DYNAMIC_BOX_PADDING * 2 + _DYNAMIC_BOX_BORDER
	height = space_sample.get_height() * 4 + _DYNAMIC_BOX_PADDING * 2 + _DYNAMIC_BOX_BORDER
	_BASE_SUBSYSTEMS = pygame.Surface((width, height), flags=(pygame.constants.SRCALPHA | pygame.constants.SWSURFACE))
	_BASE_SUBSYSTEMS.fill((_HUD_COLOUR[0], _HUD_COLOUR[1], _HUD_COLOUR[2], 191), (0, height - _DYNAMIC_BOX_BORDER, width, _DYNAMIC_BOX_BORDER))
	_BASE_SUBSYSTEMS.fill((_HUD_COLOUR[0], _HUD_COLOUR[1], _HUD_COLOUR[2], 191), (0, 0, _DYNAMIC_BOX_BORDER, height - _DYNAMIC_BOX_BORDER))
	_BASE_SUBSYSTEMS.fill((_HUD_COLOUR[0], _HUD_COLOUR[1], _HUD_COLOUR[2], 63), (_DYNAMIC_BOX_BORDER, 0, width - _DYNAMIC_BOX_BORDER, height - _DYNAMIC_BOX_BORDER))
	
	global _BASE_SCORE, _BASE_TIME, _BASE_ECM
	blitmask_score = pygame.image.load(texture_path + "hud-score-blitmask.png")
	_BASE_SCORE = pygame.Surface(blitmask_score.get_size(), flags=(pygame.constants.SRCALPHA | pygame.constants.SWSURFACE))
	_BASE_SCORE.fill((_HUD_COLOUR[0], _HUD_COLOUR[1], _HUD_COLOUR[2], 63))
	_BASE_SCORE.blit(blitmask_score, (0, 0), None, pygame.constants.BLEND_RGBA_SUB)
	blitmask_time = pygame.image.load(texture_path + "hud-time-blitmask.png")
	_BASE_TIME = pygame.Surface(blitmask_time.get_size(), flags=(pygame.constants.SRCALPHA | pygame.constants.SWSURFACE))
	_BASE_TIME.fill((_HUD_COLOUR[0], _HUD_COLOUR[1], _HUD_COLOUR[2], 63))
	_BASE_TIME.blit(blitmask_time, (0, 0), None, pygame.constants.BLEND_RGBA_SUB)
	blitmask_ecm = pygame.image.load(texture_path + "hud-ecm-blitmask.png")
	_BASE_ECM = pygame.Surface(blitmask_ecm.get_size(), flags=(pygame.constants.SRCALPHA | pygame.constants.SWSURFACE))
	_BASE_ECM.fill((_HUD_COLOUR[0], _HUD_COLOUR[1], _HUD_COLOUR[2], 63))
	_BASE_ECM.blit(blitmask_ecm, (0, 0), None, pygame.constants.BLEND_RGBA_SUB)
	
	global _BASE_RADAR, _TEXTURE_RADAR
	hud_radar = pygame.image.load(texture_path + "hud-radar.png")
	_BASE_RADAR = pygame.Surface(hud_radar.get_size(), flags=(pygame.constants.SRCALPHA | pygame.constants.SWSURFACE))
	hud_radar_base = _BASE_RADAR.copy()
	hud_radar_fill = _BASE_RADAR.copy()
	hud_radar_base.fill((_HUD_COLOUR[0], _HUD_COLOUR[1], _HUD_COLOUR[2], 191))
	hud_radar_base.blit(hud_radar, (0, 0), None, pygame.constants.BLEND_RGBA_SUB)
	hud_radar_fill.fill((_HUD_COLOUR[0], _HUD_COLOUR[1], _HUD_COLOUR[2], 63))
	hud_radar_fill.blit(pygame.image.load(texture_path + "hud-radar-blitmask.png"), (0, 0), None, pygame.constants.BLEND_RGBA_SUB)
	hud_radar_base.blit(hud_radar_fill, (0, 0))
	_TEXTURE_RADAR = _buildTexture(3, session_constants.HEIGHT - hud_radar_base.get_height() - 3, hud_radar_base)
	
	global _TEXTURE_TARGET
	hud_target = pygame.image.load(texture_path + "hud-target.png")
	hud_target_base = pygame.Surface(hud_target.get_size(), flags=(pygame.constants.SRCALPHA | pygame.constants.SWSURFACE))
	hud_target_base.fill((_HUD_COLOUR[0], _HUD_COLOUR[1], _HUD_COLOUR[2], 191))
	hud_target_base.blit(hud_target, (0, 0), None, pygame.constants.BLEND_RGBA_SUB)
	hud_target_base.fill((_HUD_COLOUR[0], _HUD_COLOUR[1], _HUD_COLOUR[2], 63), (8, 48, 75, 75))
	_TEXTURE_TARGET = _buildTexture(6 + hud_radar_base.get_width(), session_constants.HEIGHT - hud_target_base.get_height() - 3, hud_target_base)
	
	global _BASE_TARGET
	_BASE_TARGET = target_surface = pygame.Surface((87, 45), flags=(pygame.constants.SRCALPHA | pygame.constants.SWSURFACE))
	_BASE_TARGET.fill((_HUD_COLOUR[0], _HUD_COLOUR[1], _HUD_COLOUR[2], 63))
	
	global _BASE_TARGET_SUBSYSTEMS
	space_sample = _STATS_FONT.render(" ", False, (0, 0, 0))
	width = _TARGET_SUBSYSTEM_BOX_WIDTH + _DYNAMIC_BOX_PADDING * 2 + _DYNAMIC_BOX_BORDER
	height = space_sample.get_height() * 4 + _DYNAMIC_BOX_PADDING * 2 + _DYNAMIC_BOX_BORDER * 2
	_BASE_TARGET_SUBSYSTEMS = pygame.Surface((width, height), flags=(pygame.constants.SRCALPHA | pygame.constants.SWSURFACE))
	_BASE_TARGET_SUBSYSTEMS.fill((_HUD_COLOUR[0], _HUD_COLOUR[1], _HUD_COLOUR[2], 191), (width - _DYNAMIC_BOX_BORDER, _DYNAMIC_BOX_BORDER, _DYNAMIC_BOX_BORDER, height - _DYNAMIC_BOX_BORDER * 2))
	_BASE_TARGET_SUBSYSTEMS.fill((_HUD_COLOUR[0], _HUD_COLOUR[1], _HUD_COLOUR[2], 191), (0, 0, width, _DYNAMIC_BOX_BORDER))
	_BASE_TARGET_SUBSYSTEMS.fill((_HUD_COLOUR[0], _HUD_COLOUR[1], _HUD_COLOUR[2], 191), (0, height - _DYNAMIC_BOX_BORDER, width, _DYNAMIC_BOX_BORDER))
	_BASE_TARGET_SUBSYSTEMS.fill((_HUD_COLOUR[0], _HUD_COLOUR[1], _HUD_COLOUR[2], 63), (0, _DYNAMIC_BOX_BORDER, width - _DYNAMIC_BOX_BORDER, height - _DYNAMIC_BOX_BORDER * 2))
	
	global _MESSAGE_FONT_SPACE_SIZE, _MESSAGE_LINE_CAP
	space_sample = _MESSAGE_FONT.render(" ", False, (0, 0, 0))
	_MESSAGE_FONT_SPACE_SIZE = space_sample.get_size()
	#Capping at _MESSAGE_BOX_WIDTH prevents the texture from exceeding power-of-two.
	_MESSAGE_LINE_CAP = int(min(_MESSAGE_BOX_WIDTH, (session_constants.HEIGHT_HALF - hud_centre_base.get_height() / 2)) / space_sample.get_height()) - 2
	
	global _DEATH_SCREENS
	death_screens = []
	death_screens.append((texture_path + "screen-death-1.png", (-99, 150), (127, 82, 82)))
	death_screens.append((texture_path + "screen-death-2.png", (0, 189), (31, 31, 31)))
	death_screens.append((texture_path + "screen-death-3.png", (-124, 215), (163, 0, 0)))
	_DEATH_SCREENS = tuple(death_screens)
	
def _initVariables(hud_colour, message_font, stats_font, stats_font_medium, stats_font_large):
	"""
	Initialises constants required by this module.
	
	@type hud_colour: sequence(3)
	@param hud_colour: The byte-valued (r, g, b) colour to use for HUD elements.
	@type message_font: pygame.Font
	@param message_font: The font to use for rendering messages.
	@type stats_font: pygame.Font
	@param stats_font: The font to use for rendering small statistical data.
	@type stats_font_medium: pygame.Font
	@param stats_font_medium: The font to use for rendering medium statistical data.
	@type stats_font_large: pygame.Font
	@param stats_font_large: The font to use for rendering large statistical data.
	"""
	global _CENTRAL_REDRAW_FRAME, _TARGET_REDRAW_FRAME, _RADAR_REDRAW_FRAME
	_CENTRAL_REDRAW_FRAME = (session_constants.FPS / _CENTRAL_REDRAW_HERTZ) % session_constants.FPS
	_TARGET_REDRAW_FRAME = (session_constants.FPS / _TARGET_REDRAW_HERTZ) % session_constants.FPS
	_RADAR_REDRAW_FRAME = (session_constants.FPS / _RADAR_REDRAW_HERTZ) % session_constants.FPS
	
	global _HUD_COLOUR, _HUD_COLOUR_OGL
	_HUD_COLOUR = hud_colour
	_HUD_COLOUR_OGL = float(hud_colour[0]) / 255, float(hud_colour[1]) / 255, float(hud_colour[2]) / 255
	
	global _MESSAGE_FONT
	global _STATS_FONT
	global _STATS_FONT_MEDIUM
	global _STATS_FONT_LARGE
	_MESSAGE_FONT = message_font
	_STATS_FONT = stats_font
	_STATS_FONT_MEDIUM = stats_font_medium
	_STATS_FONT_LARGE = stats_font_large
	
def _render(textures):
	"""
	Provides a convienient and efficient means of rendering any number of
	textures.
	
	@type textures: sequence
	@param textures: A list of all left, top, right, and bottom pixel
	    co-ordinates of the textures to be drawn, plus the textures themselves.
	"""
	GL.glDisable(GL.GL_DEPTH_TEST)
	GL.glDisable(GL.GL_LIGHTING)
	GL.glEnable(GL.GL_TEXTURE_2D)
	GL.glEnable(GL.GL_BLEND)
	GL.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA)
	GL.glPolygonMode(GL.GL_FRONT, GL.GL_FILL)
	GL.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_MODULATE)
	GL.glColor(1.0, 1.0, 1.0)
	
	GL.glMatrixMode(GL.GL_PROJECTION)
	GL.glPushMatrix()
	GL.glLoadIdentity()
	GLU.gluOrtho2D(0, session_constants.WIDTH, 0, session_constants.HEIGHT)
	GL.glMatrixMode(GL.GL_MODELVIEW)
	GL.glPushMatrix()
	GL.glLoadIdentity()
	
	draw = _draw
	for texture in textures:
		if texture:
			draw(texture)
			
	GL.glPopMatrix()
	GL.glMatrixMode(GL.GL_PROJECTION)
	GL.glPopMatrix()
	GL.glMatrixMode(GL.GL_MODELVIEW)
	
	GL.glDisable(GL.GL_BLEND)
	GL.glDisable(GL.GL_TEXTURE_2D)
	GL.glEnable(GL.GL_LIGHTING)
	GL.glEnable(GL.GL_DEPTH_TEST)
	