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

Purpose
=======
 Presents the user with an interface for interacting with a UTc! lobby, setting
 pre-game variables, and managing game processes.
 
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 ConfigParser
import os
import re
import time
import threading
import wx

import game

import src.constants as constants
import src.path_utils as path_utils
import src.weapons as weapons

import src.config.parser as parser

_TEAM_REGEXP = re.compile(r"TEAM (\w+) (\d+)") #: A protocol-regexp that catches updates to a player's team.
_PART_REGEXP = re.compile(r"PART (\w+)") #: A protocol-regexp that catches player quit events.
_TEXT_REGEXP = re.compile(r"TEXT (\w+) (.+)") #: A protocol-regexp that catches player messages.
_DISC_REGEXP = re.compile(r"DISC (.+)") #: A protocol-regexp that catches server disconnect events.
_JOIN_REGEXP = re.compile(r"JOIN (\w+) (\d+)") #: A protocol-regexp that catches player join events.
_GAME_REGEXP = re.compile(r"GAME (\d+)") #: A protocol-regexp that catches game invitation events.
_NOGM_REGEXP = re.compile(r"NOGM (.+)") #: A protocol-regexp that catches failed-to-join-game events.
_TSTT_REGEXP = re.compile(r"TSTT (\d+) (\d+) (-?\d+)") #: A protocol-regexp that catches team stats events.
_PSTT_REGEXP = re.compile(r"PSTT (\w+) (\d+) (\d+) (\d+) (\d+) (.+)") #: A protocol-regexp that catches player stats events.

EVT_ADDLINE_ID = wx.NewId() #: An identifier for the custom AddLine event.
def EVT_ADDLINE(window, function):
	"""
	Allows a custom AddLine event to be managed by the wx queue.
	
	@type window: wx.Window
	@param window: The object that will manage this event.
	@type function: callable
	@param function: The function that will handle this event.
	"""
	window.Connect(-1, -1, EVT_ADDLINE_ID, function)
class AddLineEvent(wx.PyEvent):
	"""
	A custom AddLine event object.
	"""
	_data = None #: The data to be displayed when this event is processed.
	
	def __init__(self, data):
		"""
		Initialises this event, setting the data it carries.
		
		@type data: basestring|None
		@param data: The message this event carries.
		"""
		wx.PyEvent.__init__(self)
		self.SetEventType(EVT_ADDLINE_ID)
		self._data = data
		
	def getData(self):
		"""
		Returns the data this event carries.
		
		@rtype: basestring|None
		@return: The data this event carries.
		"""
		return self._data
		
EVT_ADDPLAYERSTATS_ID = wx.NewId() #: An identifier for the custom AddPlayerStats event.
def EVT_ADDPLAYERSTATS(window, function):
	"""
	Allows a custom AddPlayerStats event to be managed by the wx queue.
	
	@type window: wx.Window
	@param window: The object that will manage this event.
	@type function: callable
	@param function: The function that will handle this event.
	"""
	window.Connect(-1, -1, EVT_ADDPLAYERSTATS_ID, function)
class AddPlayerStatsEvent(wx.PyEvent):
	"""
	A custom AddPlayerStats event object.
	"""
	_accuracy = None #: The player's accuracy.
	_deaths = None #: The deaths counted against the player.
	_kills = None #: The kills scored by the player.
	_name = None #: The name of the player to be added.
	_suicides = None #: The number of deaths that were suicides.
	_team = None #: The team to which the player belonged.
	
	def __init__(self, name, team, kills, deaths, suicides, accuracy):
		"""
		Initialises this event, setting the data it carries.
		
		@type name: basestring
		@param name: The name of the player being described.
		@type team: basestring
		@param team: The team ID of the player.
		@type kills: basestring
		@param kills: The kills of the player.
		@type deaths: basestring
		@param deaths: The deaths of the player.
		@type suicides: basestring
		@param suicides: The suicides of the player.
		@type accuracy: basestring
		@param accuracy: The accuracy of the player.
		"""
		wx.PyEvent.__init__(self)
		self.SetEventType(EVT_ADDPLAYERSTATS_ID)
		self._name = name
		self._team = team
		self._kills = kills
		self._deaths = deaths
		self._suicides = suicides
		self._accuracy = accuracy
		
	def getData(self):
		"""
		Returns the data this event carries.
		
		@rtype: tuple(6)
		@return: The name, team, kills, deaths, suicides, and accuracy of the
		    player this event describes.
		"""
		return (self._name, self._team, self._kills, self._deaths, self._suicides, self._accuracy)
		
EVT_ADDTEAMSTATS_ID = wx.NewId() #: An identifier for the custom AddTeamStats event.
def EVT_ADDTEAMSTATS(window, function):
	"""
	Allows a custom AddTeamStats event to be managed by the wx queue.
	
	@type window: wx.Window
	@param window: The object that will manage this event.
	@type function: callable
	@param function: The function that will handle this event.
	"""
	window.Connect(-1, -1, EVT_ADDTEAMSTATS_ID, function)
class AddTeamStatsEvent(wx.PyEvent):
	"""
	A custom AddTeamStats event object.
	"""
	_id = None #: The ID of the team to be added.
	_rank = None #: The rank of the team to be added.
	_score = None #: The score of the team to be added.
	
	def __init__(self, id, rank, score):
		"""
		Initialises this event, setting the data it carries.
		
		@type id: basestring
		@param id: The ID of the team being described.
		@type rank: basestring
		@param rank: The rank of the team.
		@type score: basestring
		@param score: The score of the team.
		"""
		wx.PyEvent.__init__(self)
		self.SetEventType(EVT_ADDTEAMSTATS_ID)
		self._id = id
		self._rank = rank
		self._score = score
		
	def getData(self):
		"""
		Returns the data this event carries.
		
		@rtype: tuple(3)
		@return: The ID, rank, and score of the team this event represents.
		"""
		return (self._id, self._rank, self._score)
		
EVT_CLEARSTATS_ID = wx.NewId() #: An identifier for the custom ClearStats event.
def EVT_CLEARSTATS(window, function):
	"""
	Allows a custom ClearStats event to be managed by the wx queue.
	
	@type window: wx.Window
	@param window: The object that will manage this event.
	@type function: callable
	@param function: The function that will handle this event.
	"""
	window.Connect(-1, -1, EVT_CLEARSTATS_ID, function)
class ClearStatsEvent(wx.PyEvent):
	"""
	A custom ClearStats event object.
	"""
	def __init__(self):
		"""
		Initialises this event.
		"""
		wx.PyEvent.__init__(self)
		self.SetEventType(EVT_CLEARSTATS_ID)
		
EVT_LISTPLAYERS_ID = wx.NewId() #: An identifier for the custom ListPlayers event.
def EVT_LISTPLAYERS(window, function):
	"""
	Allows a custom ListPlayers event to be managed by the wx queue.
	
	@type window: wx.Window
	@param window: The object that will manage this event.
	@type function: callable
	@param function: The function that will handle this event.
	"""
	window.Connect(-1, -1, EVT_LISTPLAYERS_ID, function)
class ListPlayersEvent(wx.PyEvent):
	"""
	A custom ListPlayers event object.
	"""
	def __init__(self):
		"""
		Initialises this event.
		"""
		wx.PyEvent.__init__(self)
		self.SetEventType(EVT_LISTPLAYERS_ID)
		
EVT_SETTEAM_ID = wx.NewId() #: An identifier for the custom SetTeam event.
def EVT_SETTEAM(window, function):
	"""
	Allows a custom SetTeam event to be managed by the wx queue.
	
	@type window: wx.Window
	@param window: The object that will manage this event.
	@type function: callable
	@param function: The function that will handle this event.
	"""
	window.Connect(-1, -1, EVT_SETTEAM_ID, function)
class SetTeamEvent(wx.PyEvent):
	"""
	A custom SetTeam event object.
	"""
	_team_id = None #: The ID of the team to be set.
	
	def __init__(self, team_id):
		"""
		Initialises this event, setting the data it carries.
		
		@type team_id: int
		@param team_id: The team ID the server has assigned to the client.
		"""
		wx.PyEvent.__init__(self)
		self.SetEventType(EVT_SETTEAM_ID)
		self._team_id = team_id
		
	def getData(self):
		"""
		Returns the data this event carries.
		
		@rtype: int
		@return: The team ID the server has assigned to the client.
		"""
		return self._team_id
		
class ClientGUI(wx.Frame):
	"""
	An interface that allows a user to interact with a UTc! lobby, set gameplay
	values, and join games.
	"""
	_bolts_map = {} #: A mapping between bolts display strings and IDs.
	_bolts_modifier_map = {} #: A mapping between bolt modifier names and constants.
	_config_data = None #: A collection of configuration data needed to spawn the game thread.
	_hostname = None #: The game's host's address.
	_lock = threading.Lock() #: A lock used to prevent collisions between server events and user events.
	_login_gui = None #: A reference to the login GUI frame.
	_missiles_map = {} #: A mapping between missiles display strings and IDs.
	_player_name = None #: The player's handle.
	_players = {} #: A collection of all players currently connected to the server.
	_power_source_map = {} #: A mapping between power source names and constants.
	_server = None #: A connection to the game server.
	_ships_map = {} #: A mapping between ship display strings and (ID, descriptor path)s.
	
	def __init__(self, *args, **kwds):
		"""
		wxPython control initialisation routine.
		
		Also sets up event bindings and variables required by this GUI.
		"""
		kwds["style"] = wx.SYSTEM_MENU|wx.MINIMIZE_BOX|wx.CLOSE_BOX|wx.CAPTION
		wx.Frame.__init__(self, *args, **kwds)
		
		self.ntb_main = wx.Notebook(self, -1, style=0)
		self.ntp_configuration = wx.Panel(self.ntb_main, -1, style=wx.TAB_TRAVERSAL)
		self.ntp_statistics = wx.Panel(self.ntb_main, -1, style=wx.TAB_TRAVERSAL)
		self.ntp_ship = wx.Panel(self.ntb_main, -1, style=wx.TAB_TRAVERSAL)
		self.ntp_lobby = wx.Panel(self.ntb_main, -1, style=wx.TAB_TRAVERSAL)
		self.szv_players_staticbox = wx.StaticBox(self.ntp_lobby, -1, "Other players")
		self.szv_ship_staticbox = wx.StaticBox(self.ntp_ship, -1, "Ship")
		self.szv_subsystems_staticbox = wx.StaticBox(self.ntp_ship, -1, "Subsystems")
		self.szv_integrity_staticbox = wx.StaticBox(self.ntp_ship, -1, "Integrity")
		self.szv_movement_staticbox = wx.StaticBox(self.ntp_ship, -1, "Movement")
		self.szv_stats_staticbox = wx.StaticBox(self.ntp_ship, -1, "Statistics")
		self.szv_vessel_staticbox = wx.StaticBox(self.ntp_ship, -1, "Vessel")
		self.szv_1_bank_1_staticbox = wx.StaticBox(self.ntp_ship, -1, "Bank 1")
		self.szv_1_bank_2_staticbox = wx.StaticBox(self.ntp_ship, -1, "Bank 2")
		self.szv_1_bank_3_staticbox = wx.StaticBox(self.ntp_ship, -1, "Bank 3")
		self.szv_1_bank_4_staticbox = wx.StaticBox(self.ntp_ship, -1, "Bank 4")
		self.szv_1_staticbox = wx.StaticBox(self.ntp_ship, -1, "Primary weapons")
		self.szv_2_bank_1_staticbox = wx.StaticBox(self.ntp_ship, -1, "Bank 1")
		self.szv_2_bank_2_staticbox = wx.StaticBox(self.ntp_ship, -1, "Bank 2")
		self.szv_2_bank_3_staticbox = wx.StaticBox(self.ntp_ship, -1, "Bank 3")
		self.szv_2_staticbox = wx.StaticBox(self.ntp_ship, -1, "Secondary weapons")
		self.szv_ship_power_staticbox = wx.StaticBox(self.ntp_ship, -1, "Power source")
		self.szh_loadout_staticbox = wx.StaticBox(self.ntp_ship, -1, "Loadout")
		self.szh_statistics_staticbox = wx.StaticBox(self.ntp_statistics, -1, "Results of the most recent game")
		self.szv_statistics_teams_staticbox = wx.StaticBox(self.ntp_statistics, -1, "Teams")
		self.szv_statistics_players_staticbox = wx.StaticBox(self.ntp_statistics, -1, "Players")
		self.szv_environment_settings_staticbox = wx.StaticBox(self.ntp_configuration, -1, "Environment settings")
		self.szv_res_staticbox = wx.StaticBox(self.ntp_configuration, -1, "Resolution")
		self.szh_performance_staticbox = wx.StaticBox(self.ntp_configuration, -1, "Performance")
		self.szv_appearance_staticbox = wx.StaticBox(self.ntp_configuration, -1, "Appearance")
		self.szv_hud_colour_staticbox = wx.StaticBox(self.ntp_configuration, -1, "HUD colour")
		self.szv_player_colour_staticbox = wx.StaticBox(self.ntp_configuration, -1, "Player colour")
		self.szv_gameplay_settings_staticbox = wx.StaticBox(self.ntp_configuration, -1, "Gameplay settings")
		self.szv_chat_staticbox = wx.StaticBox(self.ntp_lobby, -1, "Chat")
		self.txt_chat_display = wx.TextCtrl(self.ntp_lobby, -1, "", style=wx.TE_MULTILINE|wx.TE_READONLY)
		self.txt_chat_input = wx.TextCtrl(self.ntp_lobby, -1, "", style=wx.TE_PROCESS_ENTER)
		self.static_line_1 = wx.StaticLine(self.ntp_lobby, -1, style=wx.LI_VERTICAL)
		self.lst_players = wx.ListBox(self.ntp_lobby, -1, choices=[])
		self.lbl_team = wx.StaticText(self.ntp_lobby, -1, "Team: ")
		self.lst_team = wx.ComboBox(self.ntp_lobby, -1, choices=["Team 1 - Red", "Team 2 - Green", "Team 3 - Blue", "Team 4 - Light red", "Team 5 - Light green", "Team 6 - Light blue"], style=wx.CB_DROPDOWN|wx.CB_READONLY)
		self.btn_join = wx.Button(self.ntp_lobby, -1, "Join the game")
		self.lst_ship = wx.ComboBox(self.ntp_ship, -1, choices=[], style=wx.CB_DROPDOWN|wx.CB_READONLY)
		self.img_ship = wx.StaticBitmap(self.ntp_ship, -1, wx.NullBitmap)
		self.lbl_hull = wx.StaticText(self.ntp_ship, -1, "Hull: ")
		self.lbl_hull_value = wx.StaticText(self.ntp_ship, -1, "0")
		self.lbl_shielding = wx.StaticText(self.ntp_ship, -1, "Shields: ")
		self.lbl_shielding_value = wx.StaticText(self.ntp_ship, -1, "0")
		self.lbl_sensors = wx.StaticText(self.ntp_ship, -1, "Sensors: ")
		self.lbl_sensors_value = wx.StaticText(self.ntp_ship, -1, "0")
		self.lbl_engines = wx.StaticText(self.ntp_ship, -1, "Engines: ")
		self.lbl_engines_value = wx.StaticText(self.ntp_ship, -1, "0")
		self.lbl_shields = wx.StaticText(self.ntp_ship, -1, "Shields: ")
		self.lbl_shields_value = wx.StaticText(self.ntp_ship, -1, "0")
		self.lbl_weapons = wx.StaticText(self.ntp_ship, -1, "Weapons: ")
		self.lbl_weapons_value = wx.StaticText(self.ntp_ship, -1, "0")
		self.lbl_speed = wx.StaticText(self.ntp_ship, -1, "Top speed: ")
		self.lbl_speed_value = wx.StaticText(self.ntp_ship, -1, "0m/s")
		self.lbl_efficiency = wx.StaticText(self.ntp_ship, -1, "Efficiency: ")
		self.lbl_efficiency_value = wx.StaticText(self.ntp_ship, -1, "0m/s/u")
		self.lbl_acceleration = wx.StaticText(self.ntp_ship, -1, "Acceleration: ")
		self.lbl_acceleration_value = wx.StaticText(self.ntp_ship, -1, "0m/s²")
		self.lbl_pitch = wx.StaticText(self.ntp_ship, -1, "Pitch: ")
		self.lbl_pitch_value = wx.StaticText(self.ntp_ship, -1, "0°/s")
		self.lbl_yaw = wx.StaticText(self.ntp_ship, -1, "Yaw: ")
		self.lbl_yaw_value = wx.StaticText(self.ntp_ship, -1, "0°/s")
		self.lbl_roll = wx.StaticText(self.ntp_ship, -1, "Roll: ")
		self.lbl_roll_value = wx.StaticText(self.ntp_ship, -1, "0°/s")
		self.lbl_reactor = wx.StaticText(self.ntp_ship, -1, "Reactor: ")
		self.lbl_reactor_output = wx.StaticText(self.ntp_ship, -1, "0u/s")
		self.lbl_energy = wx.StaticText(self.ntp_ship, -1, "Energy reserve: ")
		self.lbl_energy_reserve = wx.StaticText(self.ntp_ship, -1, "0u")
		self.lbl_1_bank_1_cool = wx.StaticText(self.ntp_ship, -1, "Cooldown: ")
		self.lbl_1_bank_1_cooldown = wx.StaticText(self.ntp_ship, -1, "0s")
		self.lst_1_bank_1 = wx.ComboBox(self.ntp_ship, -1, choices=[], style=wx.CB_DROPDOWN|wx.CB_READONLY)
		self.lbl_1_modifier_1 = wx.StaticText(self.ntp_ship, -1, "Focus: ")
		self.lst_1_modifier_1 = wx.ComboBox(self.ntp_ship, -1, choices=[], style=wx.CB_DROPDOWN|wx.CB_READONLY)
		self.lbl_1_bank_2_cool = wx.StaticText(self.ntp_ship, -1, "Cooldown: ")
		self.lbl_1_bank_2_cooldown = wx.StaticText(self.ntp_ship, -1, "0s")
		self.lst_1_bank_2 = wx.ComboBox(self.ntp_ship, -1, choices=[], style=wx.CB_DROPDOWN|wx.CB_READONLY)
		self.lbl_1_modifier_2 = wx.StaticText(self.ntp_ship, -1, "Focus: ")
		self.lst_1_modifier_2 = wx.ComboBox(self.ntp_ship, -1, choices=[], style=wx.CB_DROPDOWN|wx.CB_READONLY)
		self.lbl_1_bank_3_cool = wx.StaticText(self.ntp_ship, -1, "Cooldown: ")
		self.lbl_1_bank_3_cooldown = wx.StaticText(self.ntp_ship, -1, "0s")
		self.lst_1_bank_3 = wx.ComboBox(self.ntp_ship, -1, choices=[], style=wx.CB_DROPDOWN|wx.CB_READONLY)
		self.lbl_1_modifier_3 = wx.StaticText(self.ntp_ship, -1, "Focus: ")
		self.lst_1_modifier_3 = wx.ComboBox(self.ntp_ship, -1, choices=[], style=wx.CB_DROPDOWN|wx.CB_READONLY)
		self.lbl_1_bank_4_cool = wx.StaticText(self.ntp_ship, -1, "Cooldown: ")
		self.lbl_1_bank_4_cooldown = wx.StaticText(self.ntp_ship, -1, "0s")
		self.lst_1_bank_4 = wx.ComboBox(self.ntp_ship, -1, choices=[], style=wx.CB_DROPDOWN|wx.CB_READONLY)
		self.lbl_1_modifier_4 = wx.StaticText(self.ntp_ship, -1, "Focus: ")
		self.lst_1_modifier_4 = wx.ComboBox(self.ntp_ship, -1, choices=[], style=wx.CB_DROPDOWN|wx.CB_READONLY)
		self.lbl_2_bank_1_sz = wx.StaticText(self.ntp_ship, -1, "Size: ")
		self.lbl_2_bank_1_size = wx.StaticText(self.ntp_ship, -1, "0")
		self.lbl_2_bank_1_cool = wx.StaticText(self.ntp_ship, -1, "Cooldown: ")
		self.lbl_2_bank_1_cooldown = wx.StaticText(self.ntp_ship, -1, "0s")
		self.lst_2_bank_1 = wx.ComboBox(self.ntp_ship, -1, choices=[], style=wx.CB_DROPDOWN|wx.CB_READONLY)
		self.lbl_2_bank_2_sz = wx.StaticText(self.ntp_ship, -1, "Size: ")
		self.lbl_2_bank_2_size = wx.StaticText(self.ntp_ship, -1, "0")
		self.lbl_2_bank_2_cool = wx.StaticText(self.ntp_ship, -1, "Cooldown: ")
		self.lbl_2_bank_2_cooldown = wx.StaticText(self.ntp_ship, -1, "0s")
		self.lst_2_bank_2 = wx.ComboBox(self.ntp_ship, -1, choices=[], style=wx.CB_DROPDOWN|wx.CB_READONLY)
		self.lbl_2_bank_3_sz = wx.StaticText(self.ntp_ship, -1, "Size: ")
		self.lbl_2_bank_3_size = wx.StaticText(self.ntp_ship, -1, "0")
		self.lbl_2_bank_3_cool = wx.StaticText(self.ntp_ship, -1, "Cooldown: ")
		self.lbl_2_bank_3_cooldown = wx.StaticText(self.ntp_ship, -1, "0s")
		self.lst_2_bank_3 = wx.ComboBox(self.ntp_ship, -1, choices=[], style=wx.CB_DROPDOWN|wx.CB_READONLY)
		self.static_line_2 = wx.StaticLine(self.ntp_ship, -1)
		self.lbl_ecms = wx.StaticText(self.ntp_ship, -1, "ECM capacity: ")
		self.lbl_ecms_capacity = wx.StaticText(self.ntp_ship, -1, "0")
		self.lst_ship_power = wx.ComboBox(self.ntp_ship, -1, choices=[], style=wx.CB_DROPDOWN|wx.CB_READONLY)
		self.rpt_stats_teams = wx.ListCtrl(self.ntp_statistics, -1, style=wx.LC_REPORT|wx.SUNKEN_BORDER)
		self.rpt_stats_players = wx.ListCtrl(self.ntp_statistics, -1, style=wx.LC_REPORT|wx.SUNKEN_BORDER)
		self.lbl_python = wx.StaticText(self.ntp_configuration, -1, "Python binary: ")
		self.txt_python = wx.TextCtrl(self.ntp_configuration, -1, "python")
		self.lbl_fps = wx.StaticText(self.ntp_configuration, -1, "FPS: ")
		self.txt_fps = wx.TextCtrl(self.ntp_configuration, -1, "30")
		self.lbl_res_width = wx.StaticText(self.ntp_configuration, -1, "Width: ")
		self.txt_res_width = wx.TextCtrl(self.ntp_configuration, -1, "1024")
		self.lbl_res_height = wx.StaticText(self.ntp_configuration, -1, "Height:")
		self.txt_res_height = wx.TextCtrl(self.ntp_configuration, -1, "768")
		self.chk_fsaa = wx.CheckBox(self.ntp_configuration, -1, "Enable FSAA")
		self.opt_rendering = wx.RadioBox(self.ntp_configuration, -1, "Rendering style", choices=["Basic", "Wireframe", "Cel-shaded", "Gradient-shaded", "Silhouette-shaded"], majorDimension=0, style=wx.RA_SPECIFY_ROWS)
		self.chk_sound = wx.CheckBox(self.ntp_configuration, -1, "Enable sounds")
		self.lbl_hud_red = wx.StaticText(self.ntp_configuration, -1, "Red:")
		self.sld_hud_red = wx.Slider(self.ntp_configuration, -1, 50, 25, 100, style=wx.SL_HORIZONTAL|wx.SL_LABELS)
		self.lbl_hud_red_unit = wx.StaticText(self.ntp_configuration, -1, "%")
		self.lbl_hud_green = wx.StaticText(self.ntp_configuration, -1, "Green:")
		self.sld_hud_green = wx.Slider(self.ntp_configuration, -1, 50, 25, 100, style=wx.SL_HORIZONTAL|wx.SL_LABELS)
		self.lbl_hud_green_unit = wx.StaticText(self.ntp_configuration, -1, "%")
		self.lbl_hud_blue = wx.StaticText(self.ntp_configuration, -1, "Blue:")
		self.sld_hud_blue = wx.Slider(self.ntp_configuration, -1, 100, 25, 100, style=wx.SL_HORIZONTAL|wx.SL_LABELS)
		self.lbl_hud_blue_unit = wx.StaticText(self.ntp_configuration, -1, "%")
		self.lbl_player_red = wx.StaticText(self.ntp_configuration, -1, "Red:")
		self.sld_player_red = wx.Slider(self.ntp_configuration, -1, 50, 5, 95, style=wx.SL_HORIZONTAL|wx.SL_LABELS)
		self.lbl_player_red_unit = wx.StaticText(self.ntp_configuration, -1, "%")
		self.lbl_player_green = wx.StaticText(self.ntp_configuration, -1, "Green:")
		self.sld_player_green = wx.Slider(self.ntp_configuration, -1, 50, 5, 95, style=wx.SL_HORIZONTAL|wx.SL_LABELS)
		self.lbl_player_green_unit = wx.StaticText(self.ntp_configuration, -1, "%")
		self.lbl_player_blue = wx.StaticText(self.ntp_configuration, -1, "Blue:")
		self.sld_player_blue = wx.Slider(self.ntp_configuration, -1, 50, 5, 95, style=wx.SL_HORIZONTAL|wx.SL_LABELS)
		self.lbl_player_blue_unit = wx.StaticText(self.ntp_configuration, -1, "%")
		
		self.__set_properties()
		self.__do_layout()
		self.__load_variables()
		
		#Set up event bindings.
		self.Bind(wx.EVT_CLOSE, self._quit)
		self.btn_join.Bind(wx.EVT_BUTTON, self._clickJoinButton)
		self.lst_ship.Bind(wx.EVT_COMBOBOX, self._changeShip)
		self.txt_chat_input.Bind(wx.EVT_TEXT_ENTER, self._processChat)
		self.lst_team.Bind(wx.EVT_COMBOBOX, self._changeTeam)
		self.txt_fps.Bind(wx.EVT_CHAR, self._limitToIntegers)
		self.txt_res_width.Bind(wx.EVT_CHAR, self._limitToIntegers)
		self.txt_res_height.Bind(wx.EVT_CHAR, self._limitToIntegers)
		
		#Set up custom event bindings.
		EVT_ADDLINE(self, self._addLine)
		EVT_ADDTEAMSTATS(self, self._addTeamStats)
		EVT_ADDPLAYERSTATS(self, self._addPlayerStats)
		EVT_CLEARSTATS(self, self._clearStats)
		EVT_LISTPLAYERS(self, self._listPlayers)
		EVT_SETTEAM(self, self._setTeam)
		
		#Set up thread-safing access for Windows.
		self.txt_python.Bind(wx.EVT_TEXT, self._bindConfig)
		self.txt_fps.Bind(wx.EVT_TEXT, self._bindConfig)
		self.txt_res_width.Bind(wx.EVT_TEXT, self._bindConfig)
		self.txt_res_height.Bind(wx.EVT_TEXT, self._bindConfig)
		self.chk_fsaa.Bind(wx.EVT_CHECKBOX, self._bindConfig)
		self.opt_rendering.Bind(wx.EVT_RADIOBOX, self._bindConfig)
		self.chk_sound.Bind(wx.EVT_CHECKBOX, self._bindConfig)
		self.sld_hud_red.Bind(wx.EVT_SCROLL, self._bindConfig)
		self.sld_hud_green.Bind(wx.EVT_SCROLL, self._bindConfig)
		self.sld_hud_blue.Bind(wx.EVT_SCROLL, self._bindConfig)
		
	def Destroy(self):
		"""
		Saves the currently selected options to ~/.UTc!/options.cfg, then ends
		this application.
		"""
		options = ConfigParser.RawConfigParser()
		
		options.add_section("meta")
		options.set("meta", "schema-version", "2")
		
		options.add_section("ship")
		options.set("ship", "ship", self.lst_ship.GetSelection())
		options.set("ship", "1-bank-1", self.lst_1_bank_1.GetSelection())
		options.set("ship", "1-bank-2", self.lst_1_bank_2.GetSelection())
		options.set("ship", "1-bank-3", self.lst_1_bank_3.GetSelection())
		options.set("ship", "1-bank-4", self.lst_1_bank_4.GetSelection())
		options.set("ship", "1-modifier-1", self.lst_1_modifier_1.GetSelection())
		options.set("ship", "1-modifier-2", self.lst_1_modifier_2.GetSelection())
		options.set("ship", "1-modifier-3", self.lst_1_modifier_3.GetSelection())
		options.set("ship", "1-modifier-4", self.lst_1_modifier_4.GetSelection())
		options.set("ship", "2-bank-1", self.lst_2_bank_1.GetSelection())
		options.set("ship", "2-bank-2", self.lst_2_bank_2.GetSelection())
		options.set("ship", "2-bank-3", self.lst_2_bank_3.GetSelection())
		options.set("ship", "power", self.lst_ship_power.GetSelection())
		
		options.add_section("environment")
		options.set("environment", "python", self.txt_python.GetValue())
		
		options.add_section("gameplay")
		options.set("gameplay", "fps", self.txt_fps.GetValue())
		options.set("gameplay", "res-width", self.txt_res_width.GetValue())
		options.set("gameplay", "res-height", self.txt_res_height.GetValue())
		options.set("gameplay", "fsaa", self.chk_fsaa.GetValue())
		options.set("gameplay", "rendering", self.opt_rendering.GetSelection())
		options.set("gameplay", "sound", self.chk_sound.GetValue())
		options.set("gameplay", "hud-red", self.sld_hud_red.GetValue())
		options.set("gameplay", "hud-green", self.sld_hud_green.GetValue())
		options.set("gameplay", "hud-blue", self.sld_hud_blue.GetValue())
		options.set("gameplay", "player-red", self.sld_player_red.GetValue())
		options.set("gameplay", "player-green", self.sld_player_green.GetValue())
		options.set("gameplay", "player-blue", self.sld_player_blue.GetValue())
		
		options.write(open(path_utils.getUserPath() + "options.cfg", "w"))
		
		wx.Frame.Destroy(self)
		
	def setConnection(self, login_gui, server, hostname, player_name, team_id, players):
		"""
		Sets details associated with the connection established to the server,
		needed to keep the Login GUI's fields encapsulated.
		
		@type login_gui: L{login_gui}
		@param login_gui: The login window, to be destroyed when this window dies.
		@type server: L{communication.Socket}
		@param server: The connection to the UTc! server.
		@type hostname: basestring
		@param hostname: The address of the server.
		@type player_name: basestring
		@param player_name: The name of the user.
		@type team_id: int
		@param team_id: The ID of the team assigned to the player by the server.
		@type players: sequence
		@param players: The names and team IDs of all players currently in the
		    server's lobby.
		"""
		self.lst_team.SetSelection(team_id - 1)
		for (name, team_id) in players:
			self._players[name] = _Player(name, team_id)
		wx.PostEvent(self, ListPlayersEvent())
		
		self._hostname = hostname
		self._player_name = player_name
		self._login_gui = login_gui
		self._server = server
		server.setCallback(self._processLine)
		server.start()
		
	def _addLine(self, event):
		"""
		Adds a line to the chat box.
		
		@type event: wx.PyEvent
		@param event: The event that triggered this function.
		"""
		data = event.getData()
		if data:
			self._lock.acquire()
			self._writeLine(data)
			self._lock.release()
			
	def _addPlayerStats(self, event):
		"""
		Adds an entry to the player statistics report.
		
		@type event: wx.PyEvent
		@param event: The event that triggered this function.
		"""
		index = self.rpt_stats_players.GetItemCount()
		(name, team, kills, deaths, suicides, accuracy) = event.getData()
		self.rpt_stats_players.InsertStringItem(index, name)
		self.rpt_stats_players.SetStringItem(index, 1, team)
		self.rpt_stats_players.SetStringItem(index, 2, kills)
		self.rpt_stats_players.SetStringItem(index, 3, deaths)
		self.rpt_stats_players.SetStringItem(index, 4, suicides)
		self.rpt_stats_players.SetStringItem(index, 5, accuracy)
		
	def _addTeamStats(self, event):
		"""
		Adds an entry to the team statistics report.
		
		@type event: wx.PyEvent
		@param event: The event that triggered this function.
		"""
		index = self.rpt_stats_teams.GetItemCount()
		(id, rank, score) = event.getData()
		self.rpt_stats_teams.InsertStringItem(index, id)
		self.rpt_stats_teams.SetStringItem(index, 1, rank)
		self.rpt_stats_teams.SetStringItem(index, 2, score)
		
	def _bindConfig(self, event):
		"""
		A wx threading workaround for Windows. Saves the states of all
		game-config variables to a thread-safe location.
		
		@type event: wx.PyEvent
		@param event: The event that triggered this function.
		"""
		self._lock.acquire()
		self._config_data = (
		 self.txt_python.GetValue(),
		 self.txt_fps.GetValue(), self.txt_res_width.GetValue(), self.txt_res_height.GetValue(),
		 self.chk_fsaa.GetValue(), self.opt_rendering.GetSelection(),
		 self.chk_sound.GetValue(),
		 self.sld_hud_red.GetValue(), self.sld_hud_green.GetValue(), self.sld_hud_blue.GetValue()
		)
		self._lock.release()
		
	def _changeShip(self, event):
		"""
		Repopulates the ship tab with the specifications of the ship the user has
		chosen.
		
		@type event: wx.PyEvent
		@param event: The event that triggered this function.
		"""
		filepath = path_utils.getObjectsPath() + self._ships_map[self.lst_ship.GetValue()][1]
		ship_parser = parser.Parser(filepath)
		
		tc_specifications = ship_parser.getCollection(None, 'tank-chan', 'specifications')[0]
		tc_subsystems = ship_parser.getCollection(None, 'tank-chan', 'subsystems')[0]
		
		self.lbl_hull_value.SetLabel(ship_parser.readData(tc_specifications, 'hull'))
		self.lbl_shielding_value.SetLabel(ship_parser.readData(tc_specifications, 'shielding'))
		self.lbl_sensors_value.SetLabel(ship_parser.readData(tc_subsystems, 'sensors.hit-points'))
		self.lbl_engines_value.SetLabel(ship_parser.readData(tc_subsystems, 'engines.hit-points'))
		self.lbl_shields_value.SetLabel(ship_parser.readData(tc_subsystems, 'shields.hit-points'))
		self.lbl_weapons_value.SetLabel(ship_parser.readData(tc_subsystems, 'weapons.hit-points'))
		
		reactor_output = int(ship_parser.readData(tc_specifications, 'reactor-output'))
		rated_speed = float(ship_parser.readData(tc_specifications, 'speed'))
		speed_multiplier = (rated_speed / float(ship_parser.readData(tc_specifications, 'engine-draw')))
		
		self.lbl_speed_value.SetLabel("%.1fm/s" % (reactor_output * speed_multiplier))
		self.lbl_efficiency_value.SetLabel("%.3fm/s/u" % (speed_multiplier)) 
		self.lbl_acceleration_value.SetLabel("%.1fm/s²" % (float(ship_parser.readData(tc_specifications, 'acceleration'))))
		self.lbl_pitch_value.SetLabel("%.1f°/s" % (float(ship_parser.readData(tc_specifications, 'tacking.pitch'))))
		self.lbl_yaw_value.SetLabel("%.1f°/s" % (float(ship_parser.readData(tc_specifications, 'tacking.yaw'))))
		self.lbl_roll_value.SetLabel("%.1f°/s" % (float(ship_parser.readData(tc_specifications, 'tacking.roll'))))
		
		self.lbl_reactor_output.SetLabel("%su/s" % (str(reactor_output)))
		self.lbl_energy_reserve.SetLabel("%su" % (ship_parser.readData(tc_specifications, 'energy')))
		
		self.lbl_ecms_capacity.SetLabel(ship_parser.readData(tc_specifications, 'ecm-capacity'))
		
		tc_weapons = ship_parser.getCollection(None, 'tank-chan', 'weapon-banks')[0]
		tc_bolts = ship_parser.getCollection(tc_weapons, None, 'bolt')
		tc_missiles = ship_parser.getCollection(tc_weapons, None, 'missile')
		if len(tc_bolts) >= 1:
			self.lbl_1_bank_1_cooldown.SetLabel("%.3fs" % (float(ship_parser.readData(tc_bolts[0], 'rearm-time'))))
			self.lst_1_bank_1.Enable(True)
			self.lst_1_modifier_1.Enable(True)
		else:
			self.lbl_1_bank_1_cooldown.SetLabel("N/A")
			self.lst_1_bank_1.Enable(False)
			self.lst_1_modifier_1.Enable(False)
		if len(tc_bolts) >= 2:
			self.lbl_1_bank_2_cooldown.SetLabel("%.3fs" % (float(ship_parser.readData(tc_bolts[1], 'rearm-time'))))
			self.lst_1_bank_2.Enable(True)
			self.lst_1_modifier_2.Enable(True)
		else:
			self.lbl_1_bank_2_cooldown.SetLabel("N/A")
			self.lst_1_bank_2.Enable(False)
			self.lst_1_modifier_2.Enable(False)
		if len(tc_bolts) >= 3:
			self.lbl_1_bank_3_cooldown.SetLabel("%.3fs" % (float(ship_parser.readData(tc_bolts[2], 'rearm-time'))))
			self.lst_1_bank_3.Enable(True)
			self.lst_1_modifier_3.Enable(True)
		else:
			self.lbl_1_bank_3_cooldown.SetLabel("N/A")
			self.lst_1_bank_3.Enable(False)
			self.lst_1_modifier_3.Enable(False)
		if len(tc_bolts) == 4:
			self.lbl_1_bank_4_cooldown.SetLabel("%.3fs" % (float(ship_parser.readData(tc_bolts[3], 'rearm-time'))))
			self.lst_1_bank_4.Enable(True)
			self.lst_1_modifier_4.Enable(True)
		else:
			self.lbl_1_bank_4_cooldown.SetLabel("N/A")
			self.lst_1_bank_4.Enable(False)
			self.lst_1_modifier_4.Enable(False)
			
		if len(tc_missiles) >= 1:
			self.lbl_2_bank_1_cooldown.SetLabel("%.3fs" % (float(ship_parser.readData(tc_missiles[0], 'rearm-time'))))
			self.lbl_2_bank_1_size.SetLabel(ship_parser.readData(tc_missiles[0], 'capacity'))
			self.lst_2_bank_1.Enable(True)
		else:
			self.lbl_2_bank_1_cooldown.SetLabel("N/A")
			self.lbl_2_bank_1_size.SetLabel("N/A")
			self.lst_2_bank_1.Enable(False)
		if len(tc_missiles) >= 2:
			self.lbl_2_bank_2_cooldown.SetLabel("%.3fs" % (float(ship_parser.readData(tc_missiles[1], 'rearm-time'))))
			self.lbl_2_bank_2_size.SetLabel(ship_parser.readData(tc_missiles[1], 'capacity'))
			self.lst_2_bank_2.Enable(True)
		else:
			self.lbl_2_bank_2_cooldown.SetLabel("N/A")
			self.lbl_2_bank_2_size.SetLabel("N/A")
			self.lst_2_bank_2.Enable(False)
		if len(tc_missiles) == 3:
			self.lbl_2_bank_3_cooldown.SetLabel("%.3fs" % (float(ship_parser.readData(tc_missiles[2], 'rearm-time'))))
			self.lbl_2_bank_3_size.SetLabel(ship_parser.readData(tc_missiles[2], 'capacity'))
			self.lst_2_bank_3.Enable(True)
		else:
			self.lbl_2_bank_3_cooldown.SetLabel("N/A")
			self.lbl_2_bank_3_size.SetLabel("N/A")
			self.lst_2_bank_3.Enable(False)
			
		img_filepath = filepath[:-(len(filepath) - filepath.rfind(os.sep))] + os.sep + ship_parser.readData(None, 'tank-chan.img').replace('/', os.sep)
		if os.path.isfile(img_filepath):
			self.img_ship.SetBitmap(wx.Bitmap(img_filepath, wx.BITMAP_TYPE_PNG))
			
	def _changeTeam(self, event):
		"""
		Updates the team selected in the team listbox.
		
		@type event: wx.PyEvent
		@param event: The event that triggered this function.
		"""
		if self._server.isAlive():
			self._server.sendData("TEAM %s %i" % (self._player_name, self.lst_team.GetSelection() + 1))
			
	def _clearStats(self, event):
		"""
		Clears the statistics report lists.
		
		@type event: wx.PyEvent
		@param event: The event that triggered this function.
		"""
		self.rpt_stats_teams.DeleteAllItems()
		self.rpt_stats_players.DeleteAllItems()
		self.__set_column_header_widths()
		
	def _clickJoinButton(self, event):
		"""
		Informs the server that the user would line to join an in-progress game.
		
		@type event: wx.PyEvent
		@param event: The event that triggered this function.
		"""
		self._server.sendData("PLAY %s %i,%i,%i %s %s,%s,%s,%s %i,%i,%i,%i %s,%s,%s %i" % (
		 self._player_name,
		 self.sld_player_red.GetValue() * 10, self.sld_player_green.GetValue() * 10, self.sld_player_blue.GetValue() * 10,
		 self._ships_map[self.lst_ship.GetValue()][0],
		 (self.lst_1_bank_1.Enabled and self._bolts_map[self.lst_1_bank_1.GetValue()]) or '__', (self.lst_1_bank_2.Enabled and self._bolts_map[self.lst_1_bank_2.GetValue()]) or '__',
		 (self.lst_1_bank_3.Enabled and self._bolts_map[self.lst_1_bank_3.GetValue()]) or '__', (self.lst_1_bank_4.Enabled and self._bolts_map[self.lst_1_bank_4.GetValue()]) or '__',
		 self._bolts_modifier_map[self.lst_1_modifier_1.GetValue()], self._bolts_modifier_map[self.lst_1_modifier_2.GetValue()],
		 self._bolts_modifier_map[self.lst_1_modifier_3.GetValue()], self._bolts_modifier_map[self.lst_1_modifier_4.GetValue()],
		 (self.lst_2_bank_1.Enabled and self._missiles_map[self.lst_2_bank_1.GetValue()]) or '__', (self.lst_2_bank_2.Enabled and self._missiles_map[self.lst_2_bank_2.GetValue()]) or '__',
		 (self.lst_2_bank_3.Enabled and self._missiles_map[self.lst_2_bank_3.GetValue()]) or '__',
		 self._power_source_map[self.lst_ship_power.GetValue()]
		))
		self.btn_join.Enable(False)
		
	def _gameEnd(self, output):
		"""
		A function that may be passed to a game process thread to serve as an
		on-termination callback.
		
		@type output: basestring|None
		@param output: The message to display in the event that the game
		    terminated unexpectedly, or None if everything went fine.
		"""
		if output:
			wx.PostEvent(self, AddLineEvent("Game ended unexpectedly: %s" % (output)))
			
	def _limitToIntegers(self, event):
		"""
		Restricts the characters that will be accepted to integers.
		
		@type event: wx.PyEvent
		@param event: The event that triggered this function.
		"""
		key = event.GetKeyCode()
		if key in (314, 316, 8, 127, 9): #Left, right, backspace, delete, tab.
			event.Skip()
		else:
			try:
				character = chr(key)
				if character.isdigit():
					event.Skip()
				else:
					return False
			except:
				return False
				
	def _listPlayers(self, event):
		"""
		Refreshes the list of players connected to the lobby.
		
		@type event: wx.PyEvent
		@param event: The event that triggered this function.
		"""
		self._lock.acquire()
		self.lst_players.Set(["Team %i | %s" % value for value in sorted([player.getValues() for player in self._players.values()])])
		self._lock.release()
		
	def _processChat(self, event):
		"""
		Adds a line to the chat box.
		
		@type event: wx.PyEvent
		@param event: The event that triggered this function.
		"""
		text = self.txt_chat_input.GetValue().strip()
		if text:
			self.txt_chat_input.SetValue('')
			self._writeLine("Me: %s" % (text))
			if self._server.isAlive():
				self._server.sendData("TEXT %s %s" % (self._player_name, text))
				
	def _processLine(self, line):
		"""
		Processes a line received from the server, generating approprate wxEvents
		or updating variables as necessary.
		
		@type line: basestring
		@param line: The protocol event to be processed.
		"""
		self._lock.acquire()
		try:
			match = _TEAM_REGEXP.match(line)
			if match:
				if match.group(1) == self._player_name:
					team_id = int(match.group(2))
					wx.PostEvent(self, SetTeamEvent(team_id))
					wx.PostEvent(self, AddLineEvent("Team set to %i." % (team_id)))
				else:
					player = self._players.get(match.group(1))
					if player:
						player.changeTeam(int(match.group(2)))
						wx.PostEvent(self, ListPlayersEvent())
				return
				
			match = _TEXT_REGEXP.match(line)
			if match:
				wx.PostEvent(self, AddLineEvent("%s: %s" % (match.group(1), match.group(2))))
				return
				
			match = _PSTT_REGEXP.match(line)
			if match:
				wx.PostEvent(self, AddPlayerStatsEvent(match.group(1), match.group(2), match.group(3), match.group(4), match.group(5), match.group(6)))
				return
				
			match = _TSTT_REGEXP.match(line)
			if match:
				wx.PostEvent(self, AddTeamStatsEvent(match.group(1), match.group(2), match.group(3)))
				return
				
			match = _PART_REGEXP.match(line)
			if match:
				player = self._players.get(match.group(1))
				if player:
					del self._players[match.group(1)]
					wx.PostEvent(self, AddLineEvent("%s has left." % (match.group(1))))
					wx.PostEvent(self, ListPlayersEvent())
				return
				
			match = _JOIN_REGEXP.match(line)
			if match:
				self._players[match.group(1)] = _Player(match.group(1), int(match.group(2)))
				wx.PostEvent(self, AddLineEvent("%s has joined." % (match.group(1))))
				wx.PostEvent(self, ListPlayersEvent())
				return
				
			match = _DISC_REGEXP.match(line)
			if match:
				self._server.kill()
				self.btn_join.Enable(False)
				wx.PostEvent(self, AddLineEvent("Disconnected: %s" % (match.group(1))))
				self._players = {}
				wx.PostEvent(self, ListPlayersEvent())
				return
				
			match = _GAME_REGEXP.match(line)
			if match:
				wx.PostEvent(self, AddLineEvent("Joining game..."))
				(txt_python, txt_fps, txt_res_width, txt_res_height,
				 chk_fsaa, opt_rendering,
				 chk_sound,
				 sld_hud_red, sld_hud_green, sld_hud_blue
				) = self._config_data
				game_process = game.Game(
				 self._gameEnd, txt_python,
				 self._player_name, self._hostname, match.group(1),
				 txt_fps, txt_res_width, txt_res_height,
				 chk_fsaa, str(opt_rendering + 1),
				 chk_sound,
				 str(int(127 * sld_hud_red / 100.0)), str(int(127 * sld_hud_green / 100.0)), str(int(127 * sld_hud_blue / 100.0))
				)
				game_process.start()
				return
				
			match = _NOGM_REGEXP.match(line)
			if match:
				wx.PostEvent(self, AddLineEvent("Unable to join game: %s" % (match.group(1))))
				return
				
			if line == "GSTR":
				self.btn_join.Enable(True)
				wx.PostEvent(self, AddLineEvent("A game has started."))
				return
			elif line == "GEND":
				self.btn_join.Enable(False)
				wx.PostEvent(self, AddLineEvent("The game has ended."))
				wx.PostEvent(self, ClearStatsEvent())
				return
			elif line == "SEND":
				self._server.kill()
				self.btn_join.Enable(False)
				wx.PostEvent(self, AddLineEvent("Server has shut down."))
				return
		finally:
			self._lock.release()
			
	def _quit(self, event):
		"""
		Informs the server that the user is leaving and terminates this
		application.
		
		@type event: wx.PyEvent
		@param event: The event that triggered this function.
		"""
		self._lock.acquire()
		try:
			try:
				self._server.forceData("PART %s" % (self._player_name))
			except:
				pass
				
			self._server.kill()
			
			self.Destroy()
			self._login_gui.Destroy()
		finally:
			self._lock.release()
			
	def _setTeam(self, event):
		"""
		Sets the selected team in the user's team listbox.
		
		@type event: wx.PyEvent
		@param event: The event that triggered this function.
		"""
		self.lst_team.SetSelection(event.getData() - 1)
		
	def _writeLine(self, line):
		"""
		Adds a line to the chat textbox.
		
		Note: This function must be invoked from a context that holds this
		window's lock.
		
		@type line: basestring
		@param line: The line to be added.
		"""
		self.txt_chat_display.SetInsertionPointEnd()
		self.txt_chat_display.WriteText("%s %s\n" % (time.strftime('[%H:%M.%S]'), line))
		
	def __set_column_header_widths(self):
		"""
		Sets the width of each column in the statistics lists.
		"""
		teams_width_chunk = self.rpt_stats_teams.GetClientSizeTuple()[0] / 3
		self.rpt_stats_teams.SetColumnWidth(0, teams_width_chunk)
		self.rpt_stats_teams.SetColumnWidth(1, teams_width_chunk)
		self.rpt_stats_teams.SetColumnWidth(2, teams_width_chunk)
		
		players_width = self.rpt_stats_players.GetClientSizeTuple()[0]
		players_width_chunk = players_width / 4
		self.rpt_stats_players.SetColumnWidth(0, players_width_chunk)
		players_width_chunk = (players_width - players_width_chunk) / 5
		self.rpt_stats_players.SetColumnWidth(1, players_width_chunk)
		self.rpt_stats_players.SetColumnWidth(2, players_width_chunk)
		self.rpt_stats_players.SetColumnWidth(3, players_width_chunk)
		self.rpt_stats_players.SetColumnWidth(4, players_width_chunk)
		self.rpt_stats_players.SetColumnWidth(5, players_width_chunk)
		
	def __set_properties(self):
		"""
		wxPython control configuration routine.
		"""
		self.SetTitle("Ultra Tank-chan! client")
		self.img_ship.SetMinSize((320, 240))
		self.btn_join.Enable(False)
		self.txt_chat_input.SetFocus()
		
		self.rpt_stats_teams.InsertColumn(0, "Number")
		self.rpt_stats_teams.InsertColumn(1, "Rank")
		self.rpt_stats_teams.InsertColumn(2, "Score")
		
		self.rpt_stats_players.InsertColumn(0, "Name")
		self.rpt_stats_players.InsertColumn(1, "Team")
		self.rpt_stats_players.InsertColumn(2, "Kills")
		self.rpt_stats_players.InsertColumn(3, "Deaths")
		self.rpt_stats_players.InsertColumn(4, "Suicides")
		self.rpt_stats_players.InsertColumn(5, "Accuracy")
		
	def __do_layout(self):
		"""
		wxPython control layout routine.
		"""
		szh_main = wx.BoxSizer(wx.HORIZONTAL)
		szh_configuration = wx.BoxSizer(wx.VERTICAL)
		szv_gameplay_settings = wx.StaticBoxSizer(self.szv_gameplay_settings_staticbox, wx.VERTICAL)
		szh_settings = wx.BoxSizer(wx.HORIZONTAL)
		szv_colours = wx.BoxSizer(wx.VERTICAL)
		szv_player_colour = wx.StaticBoxSizer(self.szv_player_colour_staticbox, wx.VERTICAL)
		szh_player_blue = wx.BoxSizer(wx.HORIZONTAL)
		szh_player_green = wx.BoxSizer(wx.HORIZONTAL)
		szh_player_red = wx.BoxSizer(wx.HORIZONTAL)
		szv_hud_colour = wx.StaticBoxSizer(self.szv_hud_colour_staticbox, wx.VERTICAL)
		szh_hud_blue = wx.BoxSizer(wx.HORIZONTAL)
		szh_hud_green = wx.BoxSizer(wx.HORIZONTAL)
		szh_hud_red = wx.BoxSizer(wx.HORIZONTAL)
		szv_presentation = wx.BoxSizer(wx.VERTICAL)
		szv_appearance = wx.StaticBoxSizer(self.szv_appearance_staticbox, wx.VERTICAL)
		szh_performance = wx.StaticBoxSizer(self.szh_performance_staticbox, wx.VERTICAL)
		szv_res = wx.StaticBoxSizer(self.szv_res_staticbox, wx.VERTICAL)
		szh_res_height = wx.BoxSizer(wx.HORIZONTAL)
		szh_res_width = wx.BoxSizer(wx.HORIZONTAL)
		szh_fps = wx.BoxSizer(wx.HORIZONTAL)
		szv_environment_settings = wx.StaticBoxSizer(self.szv_environment_settings_staticbox, wx.VERTICAL)
		szh_python = wx.BoxSizer(wx.HORIZONTAL)
		szh_statistics = wx.StaticBoxSizer(self.szh_statistics_staticbox, wx.HORIZONTAL)
		szv_statistics_teams = wx.StaticBoxSizer(self.szv_statistics_teams_staticbox, wx.VERTICAL)
		szv_statistics_players = wx.StaticBoxSizer(self.szv_statistics_players_staticbox, wx.VERTICAL)
		szh_ship = wx.BoxSizer(wx.HORIZONTAL)
		szh_loadout = wx.StaticBoxSizer(self.szh_loadout_staticbox, wx.HORIZONTAL)
		szv_loadout_2 = wx.BoxSizer(wx.VERTICAL)
		szv_ship_power = wx.StaticBoxSizer(self.szv_ship_power_staticbox, wx.VERTICAL)
		szv_2 = wx.StaticBoxSizer(self.szv_2_staticbox, wx.VERTICAL)
		szh_ecms = wx.BoxSizer(wx.HORIZONTAL)
		szv_2_bank_3 = wx.StaticBoxSizer(self.szv_2_bank_3_staticbox, wx.VERTICAL)
		szh_2_bank_3_cooldown = wx.BoxSizer(wx.HORIZONTAL)
		szh_2_bank_3_size = wx.BoxSizer(wx.HORIZONTAL)
		szv_2_bank_2 = wx.StaticBoxSizer(self.szv_2_bank_2_staticbox, wx.VERTICAL)
		szh_2_bank_2_cooldown = wx.BoxSizer(wx.HORIZONTAL)
		szh_2_bank_2_size = wx.BoxSizer(wx.HORIZONTAL)
		szv_2_bank_1 = wx.StaticBoxSizer(self.szv_2_bank_1_staticbox, wx.VERTICAL)
		szh_2_bank_1_cooldown = wx.BoxSizer(wx.HORIZONTAL)
		szh_2_bank_1_size = wx.BoxSizer(wx.HORIZONTAL)
		szv_loadout_1 = wx.BoxSizer(wx.VERTICAL)
		szv_1 = wx.StaticBoxSizer(self.szv_1_staticbox, wx.VERTICAL)
		szv_1_bank_4 = wx.StaticBoxSizer(self.szv_1_bank_4_staticbox, wx.VERTICAL)
		szh_1_bank_4_modifier = wx.BoxSizer(wx.HORIZONTAL)
		szh_1_bank_4_cool = wx.BoxSizer(wx.HORIZONTAL)
		szv_1_bank_3 = wx.StaticBoxSizer(self.szv_1_bank_3_staticbox, wx.VERTICAL)
		szh_1_bank_3_modifier = wx.BoxSizer(wx.HORIZONTAL)
		szh_1_bank_3_cool = wx.BoxSizer(wx.HORIZONTAL)
		szv_1_bank_2 = wx.StaticBoxSizer(self.szv_1_bank_2_staticbox, wx.VERTICAL)
		szh_1_bank_2_modifier = wx.BoxSizer(wx.HORIZONTAL)
		szh_1_bank_2_cool = wx.BoxSizer(wx.HORIZONTAL)
		szv_1_bank_1 = wx.StaticBoxSizer(self.szv_1_bank_1_staticbox, wx.VERTICAL)
		szh_1_bank_1_modifier = wx.BoxSizer(wx.HORIZONTAL)
		szh_1_bank_1_cool = wx.BoxSizer(wx.HORIZONTAL)
		szv_vessel = wx.StaticBoxSizer(self.szv_vessel_staticbox, wx.VERTICAL)
		szv_stats = wx.StaticBoxSizer(self.szv_stats_staticbox, wx.HORIZONTAL)
		szv_stats_2 = wx.BoxSizer(wx.VERTICAL)
		szh_energy = wx.BoxSizer(wx.HORIZONTAL)
		szh_reactor = wx.BoxSizer(wx.HORIZONTAL)
		szv_movement = wx.StaticBoxSizer(self.szv_movement_staticbox, wx.VERTICAL)
		szh_roll = wx.BoxSizer(wx.HORIZONTAL)
		szh_yaw = wx.BoxSizer(wx.HORIZONTAL)
		szh_pitch = wx.BoxSizer(wx.HORIZONTAL)
		szh_acceleration = wx.BoxSizer(wx.HORIZONTAL)
		szh_efficiency = wx.BoxSizer(wx.HORIZONTAL)
		szh_speed = wx.BoxSizer(wx.HORIZONTAL)
		szv_stats_1 = wx.BoxSizer(wx.VERTICAL)
		szv_integrity = wx.StaticBoxSizer(self.szv_integrity_staticbox, wx.VERTICAL)
		szv_subsystems = wx.StaticBoxSizer(self.szv_subsystems_staticbox, wx.VERTICAL)
		szh_weapons = wx.BoxSizer(wx.HORIZONTAL)
		szh_shields = wx.BoxSizer(wx.HORIZONTAL)
		szh_engines = wx.BoxSizer(wx.HORIZONTAL)
		szh_sensors = wx.BoxSizer(wx.HORIZONTAL)
		szh_shielding = wx.BoxSizer(wx.HORIZONTAL)
		szh_hull = wx.BoxSizer(wx.HORIZONTAL)
		szv_ship = wx.StaticBoxSizer(self.szv_ship_staticbox, wx.VERTICAL)
		szh_lobby = wx.BoxSizer(wx.HORIZONTAL)
		szv_game = wx.BoxSizer(wx.VERTICAL)
		szh_team = wx.BoxSizer(wx.HORIZONTAL)
		szv_players = wx.StaticBoxSizer(self.szv_players_staticbox, wx.VERTICAL)
		szv_chat = wx.StaticBoxSizer(self.szv_chat_staticbox, wx.VERTICAL)
		szv_chat.Add(self.txt_chat_display, 1, wx.EXPAND, 0)
		szv_chat.Add(self.txt_chat_input, 0, wx.EXPAND, 0)
		szh_lobby.Add(szv_chat, 2, wx.EXPAND, 0)
		szh_lobby.Add(self.static_line_1, 0, wx.EXPAND, 0)
		szv_players.Add(self.lst_players, 1, wx.EXPAND, 0)
		szv_game.Add(szv_players, 1, wx.EXPAND, 0)
		szh_team.Add(self.lbl_team, 0, wx.ALIGN_CENTER_VERTICAL, 0)
		szh_team.Add(self.lst_team, 1, wx.EXPAND, 0)
		szv_game.Add(szh_team, 0, wx.EXPAND, 0)
		szv_game.Add(self.btn_join, 0, wx.EXPAND, 0)
		szh_lobby.Add(szv_game, 1, wx.EXPAND, 0)
		self.ntp_lobby.SetSizer(szh_lobby)
		szv_ship.Add(self.lst_ship, 0, wx.EXPAND, 0)
		szv_ship.Add(self.img_ship, 0, 0, 0)
		szv_vessel.Add(szv_ship, 0, 0, 0)
		szh_hull.Add(self.lbl_hull, 0, 0, 0)
		szh_hull.Add(self.lbl_hull_value, 1, wx.EXPAND, 0)
		szv_integrity.Add(szh_hull, 0, wx.EXPAND, 0)
		szh_shielding.Add(self.lbl_shielding, 0, 0, 0)
		szh_shielding.Add(self.lbl_shielding_value, 1, wx.EXPAND, 0)
		szv_integrity.Add(szh_shielding, 0, wx.EXPAND, 0)
		szh_sensors.Add(self.lbl_sensors, 0, 0, 0)
		szh_sensors.Add(self.lbl_sensors_value, 1, wx.EXPAND, 0)
		szv_subsystems.Add(szh_sensors, 0, wx.EXPAND, 0)
		szh_engines.Add(self.lbl_engines, 0, 0, 0)
		szh_engines.Add(self.lbl_engines_value, 1, wx.EXPAND, 0)
		szv_subsystems.Add(szh_engines, 0, wx.EXPAND, 0)
		szh_shields.Add(self.lbl_shields, 0, 0, 0)
		szh_shields.Add(self.lbl_shields_value, 1, wx.EXPAND, 0)
		szv_subsystems.Add(szh_shields, 0, wx.EXPAND, 0)
		szh_weapons.Add(self.lbl_weapons, 0, 0, 0)
		szh_weapons.Add(self.lbl_weapons_value, 1, wx.EXPAND, 0)
		szv_subsystems.Add(szh_weapons, 0, wx.EXPAND, 0)
		szv_integrity.Add(szv_subsystems, 1, wx.EXPAND, 0)
		szv_stats_1.Add(szv_integrity, 0, wx.EXPAND, 0)
		szv_stats.Add(szv_stats_1, 0, 0, 0)
		szh_speed.Add(self.lbl_speed, 0, 0, 0)
		szh_speed.Add(self.lbl_speed_value, 1, wx.EXPAND, 0)
		szv_movement.Add(szh_speed, 0, wx.EXPAND, 0)
		szh_efficiency.Add(self.lbl_efficiency, 0, 0, 0)
		szh_efficiency.Add(self.lbl_efficiency_value, 0, 0, 0)
		szv_movement.Add(szh_efficiency, 0, wx.EXPAND, 0)
		szh_acceleration.Add(self.lbl_acceleration, 0, 0, 0)
		szh_acceleration.Add(self.lbl_acceleration_value, 1, wx.EXPAND, 0)
		szv_movement.Add(szh_acceleration, 0, wx.EXPAND, 0)
		szh_pitch.Add(self.lbl_pitch, 0, 0, 0)
		szh_pitch.Add(self.lbl_pitch_value, 1, wx.EXPAND, 0)
		szv_movement.Add(szh_pitch, 0, wx.EXPAND, 0)
		szh_yaw.Add(self.lbl_yaw, 0, 0, 0)
		szh_yaw.Add(self.lbl_yaw_value, 1, wx.EXPAND, 0)
		szv_movement.Add(szh_yaw, 0, wx.EXPAND, 0)
		szh_roll.Add(self.lbl_roll, 0, 0, 0)
		szh_roll.Add(self.lbl_roll_value, 1, wx.EXPAND, 0)
		szv_movement.Add(szh_roll, 0, wx.EXPAND, 0)
		szv_stats_2.Add(szv_movement, 0, wx.EXPAND, 0)
		szh_reactor.Add(self.lbl_reactor, 0, 0, 0)
		szh_reactor.Add(self.lbl_reactor_output, 1, wx.EXPAND, 0)
		szv_stats_2.Add(szh_reactor, 0, wx.LEFT|wx.RIGHT|wx.EXPAND, 2)
		szh_energy.Add(self.lbl_energy, 0, 0, 0)
		szh_energy.Add(self.lbl_energy_reserve, 1, wx.EXPAND, 0)
		szv_stats_2.Add(szh_energy, 0, wx.LEFT|wx.RIGHT|wx.EXPAND, 2)
		szv_stats.Add(szv_stats_2, 0, 0, 0)
		szv_vessel.Add(szv_stats, 0, 0, 0)
		szh_ship.Add(szv_vessel, 0, 0, 0)
		szh_1_bank_1_cool.Add(self.lbl_1_bank_1_cool, 0, 0, 0)
		szh_1_bank_1_cool.Add(self.lbl_1_bank_1_cooldown, 1, wx.EXPAND, 0)
		szv_1_bank_1.Add(szh_1_bank_1_cool, 1, wx.EXPAND, 0)
		szv_1_bank_1.Add(self.lst_1_bank_1, 0, wx.EXPAND, 0)
		szh_1_bank_1_modifier.Add(self.lbl_1_modifier_1, 0, wx.ALIGN_CENTER_VERTICAL, 0)
		szh_1_bank_1_modifier.Add(self.lst_1_modifier_1, 1, wx.EXPAND, 0)
		szv_1_bank_1.Add(szh_1_bank_1_modifier, 0, wx.EXPAND, 0)
		szv_1.Add(szv_1_bank_1, 0, wx.EXPAND, 0)
		szh_1_bank_2_cool.Add(self.lbl_1_bank_2_cool, 0, 0, 0)
		szh_1_bank_2_cool.Add(self.lbl_1_bank_2_cooldown, 1, wx.EXPAND, 0)
		szv_1_bank_2.Add(szh_1_bank_2_cool, 1, wx.EXPAND, 0)
		szv_1_bank_2.Add(self.lst_1_bank_2, 0, wx.EXPAND, 0)
		szh_1_bank_2_modifier.Add(self.lbl_1_modifier_2, 0, wx.ALIGN_CENTER_VERTICAL, 0)
		szh_1_bank_2_modifier.Add(self.lst_1_modifier_2, 1, wx.EXPAND, 0)
		szv_1_bank_2.Add(szh_1_bank_2_modifier, 0, wx.EXPAND, 0)
		szv_1.Add(szv_1_bank_2, 0, wx.EXPAND, 0)
		szh_1_bank_3_cool.Add(self.lbl_1_bank_3_cool, 0, 0, 0)
		szh_1_bank_3_cool.Add(self.lbl_1_bank_3_cooldown, 1, wx.EXPAND, 0)
		szv_1_bank_3.Add(szh_1_bank_3_cool, 1, wx.EXPAND, 0)
		szv_1_bank_3.Add(self.lst_1_bank_3, 0, wx.EXPAND, 0)
		szh_1_bank_3_modifier.Add(self.lbl_1_modifier_3, 0, wx.ALIGN_CENTER_VERTICAL, 0)
		szh_1_bank_3_modifier.Add(self.lst_1_modifier_3, 1, wx.EXPAND, 0)
		szv_1_bank_3.Add(szh_1_bank_3_modifier, 0, wx.EXPAND, 0)
		szv_1.Add(szv_1_bank_3, 0, wx.EXPAND, 0)
		szh_1_bank_4_cool.Add(self.lbl_1_bank_4_cool, 0, 0, 0)
		szh_1_bank_4_cool.Add(self.lbl_1_bank_4_cooldown, 1, wx.EXPAND, 0)
		szv_1_bank_4.Add(szh_1_bank_4_cool, 1, wx.EXPAND, 0)
		szv_1_bank_4.Add(self.lst_1_bank_4, 0, wx.EXPAND, 0)
		szh_1_bank_4_modifier.Add(self.lbl_1_modifier_4, 0, wx.ALIGN_CENTER_VERTICAL, 0)
		szh_1_bank_4_modifier.Add(self.lst_1_modifier_4, 1, wx.EXPAND, 0)
		szv_1_bank_4.Add(szh_1_bank_4_modifier, 0, wx.EXPAND, 0)
		szv_1.Add(szv_1_bank_4, 0, wx.EXPAND, 0)
		szv_loadout_1.Add(szv_1, 0, wx.EXPAND, 0)
		szv_loadout_1.Add((0, 0), 1, wx.EXPAND, 0)
		szh_loadout.Add(szv_loadout_1, 1, wx.EXPAND, 0)
		szh_2_bank_1_size.Add(self.lbl_2_bank_1_sz, 0, 0, 0)
		szh_2_bank_1_size.Add(self.lbl_2_bank_1_size, 1, wx.EXPAND, 0)
		szv_2_bank_1.Add(szh_2_bank_1_size, 1, wx.EXPAND, 0)
		szh_2_bank_1_cooldown.Add(self.lbl_2_bank_1_cool, 0, 0, 0)
		szh_2_bank_1_cooldown.Add(self.lbl_2_bank_1_cooldown, 1, wx.EXPAND, 0)
		szv_2_bank_1.Add(szh_2_bank_1_cooldown, 1, wx.EXPAND, 0)
		szv_2_bank_1.Add(self.lst_2_bank_1, 0, wx.EXPAND, 0)
		szv_2.Add(szv_2_bank_1, 0, wx.EXPAND, 0)
		szh_2_bank_2_size.Add(self.lbl_2_bank_2_sz, 0, 0, 0)
		szh_2_bank_2_size.Add(self.lbl_2_bank_2_size, 1, wx.EXPAND, 0)
		szv_2_bank_2.Add(szh_2_bank_2_size, 1, wx.EXPAND, 0)
		szh_2_bank_2_cooldown.Add(self.lbl_2_bank_2_cool, 0, 0, 0)
		szh_2_bank_2_cooldown.Add(self.lbl_2_bank_2_cooldown, 1, wx.EXPAND, 0)
		szv_2_bank_2.Add(szh_2_bank_2_cooldown, 1, wx.EXPAND, 0)
		szv_2_bank_2.Add(self.lst_2_bank_2, 0, wx.EXPAND, 0)
		szv_2.Add(szv_2_bank_2, 0, wx.EXPAND, 0)
		szh_2_bank_3_size.Add(self.lbl_2_bank_3_sz, 0, 0, 0)
		szh_2_bank_3_size.Add(self.lbl_2_bank_3_size, 1, wx.EXPAND, 0)
		szv_2_bank_3.Add(szh_2_bank_3_size, 1, wx.EXPAND, 0)
		szh_2_bank_3_cooldown.Add(self.lbl_2_bank_3_cool, 0, 0, 0)
		szh_2_bank_3_cooldown.Add(self.lbl_2_bank_3_cooldown, 1, wx.EXPAND, 0)
		szv_2_bank_3.Add(szh_2_bank_3_cooldown, 1, wx.EXPAND, 0)
		szv_2_bank_3.Add(self.lst_2_bank_3, 0, wx.EXPAND, 0)
		szv_2.Add(szv_2_bank_3, 0, wx.EXPAND, 0)
		szv_2.Add(self.static_line_2, 0, wx.TOP|wx.EXPAND, 3)
		szh_ecms.Add((0, 0), 1, wx.EXPAND, 0)
		szh_ecms.Add(self.lbl_ecms, 0, 0, 0)
		szh_ecms.Add(self.lbl_ecms_capacity, 0, 0, 0)
		szv_2.Add(szh_ecms, 0, wx.EXPAND, 0)
		szv_loadout_2.Add(szv_2, 0, wx.EXPAND, 0)
		szv_loadout_2.Add((0, 0), 1, wx.EXPAND, 0)
		szv_ship_power.Add(self.lst_ship_power, 0, wx.EXPAND, 0)
		szv_loadout_2.Add(szv_ship_power, 0, wx.EXPAND, 0)
		szh_loadout.Add(szv_loadout_2, 1, wx.EXPAND, 0)
		szh_ship.Add(szh_loadout, 0, 0, 0)
		self.ntp_ship.SetSizer(szh_ship)
		szv_statistics_teams.Add(self.rpt_stats_teams, 1, wx.EXPAND, 0)
		szv_statistics_players.Add(self.rpt_stats_players, 1, wx.EXPAND, 0)
		szh_statistics.Add(szv_statistics_teams, 1, wx.EXPAND, 0)
		szh_statistics.Add(szv_statistics_players, 2, wx.EXPAND, 0)
		self.ntp_statistics.SetSizer(szh_statistics)
		szh_python.Add(self.lbl_python, 0, wx.ALIGN_CENTER_VERTICAL, 0)
		szh_python.Add(self.txt_python, 1, 0, 0)
		szv_environment_settings.Add(szh_python, 1, wx.EXPAND, 0)
		szh_configuration.Add(szv_environment_settings, 0, wx.EXPAND, 0)
		szh_fps.Add(self.lbl_fps, 0, wx.ALIGN_CENTER_VERTICAL, 0)
		szh_fps.Add(self.txt_fps, 0, 0, 0)
		szh_performance.Add(szh_fps, 0, wx.EXPAND, 0)
		szh_res_width.Add(self.lbl_res_width, 0, wx.ALIGN_CENTER_VERTICAL, 0)
		szh_res_width.Add(self.txt_res_width, 0, 0, 0)
		szv_res.Add(szh_res_width, 1, wx.EXPAND, 0)
		szh_res_height.Add(self.lbl_res_height, 0, wx.ALIGN_CENTER_VERTICAL, 0)
		szh_res_height.Add(self.txt_res_height, 0, 0, 0)
		szv_res.Add(szh_res_height, 1, wx.EXPAND, 0)
		szh_performance.Add(szv_res, 1, wx.EXPAND, 0)
		szv_presentation.Add(szh_performance, 0, 0, 0)
		szv_appearance.Add(self.chk_fsaa, 0, wx.EXPAND, 0)
		szv_appearance.Add(self.opt_rendering, 0, 0, 0)
		szv_appearance.Add(self.chk_sound, 0, wx.EXPAND, 0)
		szv_presentation.Add(szv_appearance, 0, 0, 0)
		szh_settings.Add(szv_presentation, 0, 0, 0)
		szh_hud_red.Add(self.lbl_hud_red, 0, wx.ALIGN_CENTER_VERTICAL, 0)
		szh_hud_red.Add(self.sld_hud_red, 1, 0, 0)
		szh_hud_red.Add(self.lbl_hud_red_unit, 0, wx.ALIGN_CENTER_VERTICAL, 0)
		szv_hud_colour.Add(szh_hud_red, 0, wx.EXPAND, 0)
		szh_hud_green.Add(self.lbl_hud_green, 0, wx.ALIGN_CENTER_VERTICAL, 0)
		szh_hud_green.Add(self.sld_hud_green, 1, 0, 0)
		szh_hud_green.Add(self.lbl_hud_green_unit, 0, wx.ALIGN_CENTER_VERTICAL, 0)
		szv_hud_colour.Add(szh_hud_green, 0, wx.EXPAND, 0)
		szh_hud_blue.Add(self.lbl_hud_blue, 0, wx.ALIGN_CENTER_VERTICAL, 0)
		szh_hud_blue.Add(self.sld_hud_blue, 1, 0, 0)
		szh_hud_blue.Add(self.lbl_hud_blue_unit, 0, wx.ALIGN_CENTER_VERTICAL, 0)
		szv_hud_colour.Add(szh_hud_blue, 0, wx.EXPAND, 0)
		szv_colours.Add(szv_hud_colour, 1, wx.EXPAND, 0)
		szh_player_red.Add(self.lbl_player_red, 0, wx.ALIGN_CENTER_VERTICAL, 0)
		szh_player_red.Add(self.sld_player_red, 1, 0, 0)
		szh_player_red.Add(self.lbl_player_red_unit, 0, wx.ALIGN_CENTER_VERTICAL, 0)
		szv_player_colour.Add(szh_player_red, 0, wx.EXPAND, 0)
		szh_player_green.Add(self.lbl_player_green, 0, wx.ALIGN_CENTER_VERTICAL, 0)
		szh_player_green.Add(self.sld_player_green, 1, 0, 0)
		szh_player_green.Add(self.lbl_player_green_unit, 0, wx.ALIGN_CENTER_VERTICAL, 0)
		szv_player_colour.Add(szh_player_green, 0, wx.EXPAND, 0)
		szh_player_blue.Add(self.lbl_player_blue, 0, wx.ALIGN_CENTER_VERTICAL, 0)
		szh_player_blue.Add(self.sld_player_blue, 1, 0, 0)
		szh_player_blue.Add(self.lbl_player_blue_unit, 0, wx.ALIGN_CENTER_VERTICAL, 0)
		szv_player_colour.Add(szh_player_blue, 0, wx.EXPAND, 0)
		szv_colours.Add(szv_player_colour, 1, wx.EXPAND, 0)
		szh_settings.Add(szv_colours, 1, 0, 0)
		szv_gameplay_settings.Add(szh_settings, 0, wx.EXPAND, 0)
		szh_configuration.Add(szv_gameplay_settings, 0, wx.EXPAND, 0)
		self.ntp_configuration.SetSizer(szh_configuration)
		self.ntb_main.AddPage(self.ntp_lobby, "Lobby")
		self.ntb_main.AddPage(self.ntp_ship, "Ship")
		self.ntb_main.AddPage(self.ntp_statistics, "Statistics")
		self.ntb_main.AddPage(self.ntp_configuration, "Configuration")
		szh_main.Add(self.ntb_main, 1, wx.EXPAND, 0)
		self.SetSizer(szh_main)
		self.Layout()
		szh_main.Fit(self)
		
	def __load_variables(self):
		"""
		Initialises all menus using values from the UTc! API and the XML files
		on which the game depends.
		"""
		objects_path = path_utils.getObjectsPath()
		
		ships_parser = parser.Parser(objects_path + "ships.xml")
		ships_list = []
		for ship in ships_parser.getCollection(None, 'ships', 'ship'):
			display = '%s, "%s"' % (ships_parser.readData(ship, 'identification.model-number'), ships_parser.readData(ship, 'identification.name'))
			ships_list.append(display)
			self._ships_map[display] = (ships_parser.readData(ship, 'identification.id'), ships_parser.readData(ship, 'descriptor').replace('/', os.sep))
		self.lst_ship.SetItems(ships_list)
		
		bolts_parser = parser.Parser(objects_path + "bolts.xml")
		bolts_list = []
		for bolt in bolts_parser.getCollection(None, 'bolts', 'bolt'):
			display = bolts_parser.readData(bolt, 'identification.name')
			bolts_list.append(display)
			self._bolts_map[display] = bolts_parser.readData(bolt, 'identification.id')
		self.lst_1_bank_1.SetItems(bolts_list)
		self.lst_1_bank_2.SetItems(bolts_list)
		self.lst_1_bank_3.SetItems(bolts_list)
		self.lst_1_bank_4.SetItems(bolts_list)
		
		bolts_modifier_list = ("Balance", "Capacity", "Damage", "Cooldown")
		self._bolts_modifier_map["Balance"] = weapons.MODIFIER_BALANCED
		self._bolts_modifier_map["Capacity"] = weapons.MODIFIER_CAPACITY
		self._bolts_modifier_map["Damage"] = weapons.MODIFIER_DAMAGE
		self._bolts_modifier_map["Cooldown"] = weapons.MODIFIER_COOLDOWN
		self.lst_1_modifier_1.SetItems(bolts_modifier_list)
		self.lst_1_modifier_2.SetItems(bolts_modifier_list)
		self.lst_1_modifier_3.SetItems(bolts_modifier_list)
		self.lst_1_modifier_4.SetItems(bolts_modifier_list)
		
		missiles_parser = parser.Parser(objects_path + "missiles.xml")
		missiles_list = []
		for missile in missiles_parser.getCollection(None, 'missiles', 'missile'):
			display = missiles_parser.readData(missile, 'identification.name')
			missiles_list.append(display)
			self._missiles_map[display] = missiles_parser.readData(missile, 'identification.id')
		self.lst_2_bank_1.SetItems(missiles_list)
		self.lst_2_bank_2.SetItems(missiles_list)
		self.lst_2_bank_3.SetItems(missiles_list)
		
		power_source_list = ("Happiness", "Love", "Starlight", "Rainbows", "Moe")
		self._power_source_map["Happiness"] = constants.POWER_HAPPINESS
		self._power_source_map["Love"] = constants.POWER_LOVE
		self._power_source_map["Starlight"] = constants.POWER_STARLIGHT
		self._power_source_map["Rainbows"] = constants.POWER_RAINBOW
		self._power_source_map["Moe"] = constants.POWER_MOE
		self.lst_ship_power.SetItems(power_source_list)
		
		self.__set_options()
		
	def __set_options(self):
		"""
		Attempts to load settings from ~/.UTc!/options.cfg, falling back to
		defaults if that file does not exist.
		"""
		options = ConfigParser.RawConfigParser()
		if not (options.read(path_utils.getUserPath() + "options.cfg") and options.has_section("meta") and int(options.get("meta", "schema-version")) == 2):
			self.lst_ship.SetSelection(0)
			
			self.lst_1_bank_1.SetSelection(0)
			self.lst_1_bank_2.SetSelection(0)
			self.lst_1_bank_3.SetSelection(0)
			self.lst_1_bank_4.SetSelection(0)
			self.lst_1_modifier_1.SetSelection(0)
			self.lst_1_modifier_2.SetSelection(0)
			self.lst_1_modifier_3.SetSelection(0)
			self.lst_1_modifier_4.SetSelection(0)
			
			self.lst_2_bank_1.SetSelection(0)
			self.lst_2_bank_2.SetSelection(0)
			self.lst_2_bank_3.SetSelection(0)
			
			self.lst_ship_power.SetSelection(0)
			
			self.txt_python.SetValue("python")
			
			self.txt_fps.SetValue("30")
			self.txt_res_width.SetValue("1024")
			self.txt_res_height.SetValue("768")
			
			self.chk_fsaa.SetValue(1)
			self.opt_rendering.SetSelection(2)
			self.chk_sound.SetValue(1)
			
			self.sld_hud_red.SetValue(50)
			self.sld_hud_green.SetValue(50)
			self.sld_hud_blue.SetValue(100)
			
			self.sld_player_red.SetValue(50)
			self.sld_player_green.SetValue(50)
			self.sld_player_blue.SetValue(50)
		else:
			self.lst_ship.SetSelection(int(options.get("ship", "ship")))
			
			self.lst_1_bank_1.SetSelection(int(options.get("ship", "1-bank-1")))
			self.lst_1_bank_2.SetSelection(int(options.get("ship", "1-bank-2")))
			self.lst_1_bank_3.SetSelection(int(options.get("ship", "1-bank-3")))
			self.lst_1_bank_4.SetSelection(int(options.get("ship", "1-bank-4")))
			self.lst_1_modifier_1.SetSelection(int(options.get("ship", "1-modifier-1")))
			self.lst_1_modifier_2.SetSelection(int(options.get("ship", "1-modifier-2")))
			self.lst_1_modifier_3.SetSelection(int(options.get("ship", "1-modifier-3")))
			self.lst_1_modifier_4.SetSelection(int(options.get("ship", "1-modifier-4")))
			
			self.lst_2_bank_1.SetSelection(int(options.get("ship", "2-bank-1")))
			self.lst_2_bank_2.SetSelection(int(options.get("ship", "2-bank-2")))
			self.lst_2_bank_3.SetSelection(int(options.get("ship", "2-bank-3")))
			
			self.lst_ship_power.SetSelection(int(options.get("ship", "power")))
			
			self.txt_python.SetValue(options.get("environment", "python"))
			
			self.txt_fps.SetValue(options.get("gameplay", "fps"))
			self.txt_res_width.SetValue(options.get("gameplay", "res-width"))
			self.txt_res_height.SetValue(options.get("gameplay", "res-height"))
			
			self.chk_fsaa.SetValue(options.get("gameplay", "fsaa") == 'True')
			self.opt_rendering.SetSelection(int(options.get("gameplay", "rendering")))
			self.chk_sound.SetValue(options.get("gameplay", "sound") == 'True')
			
			self.sld_hud_red.SetValue(int(options.get("gameplay", "hud-red")))
			self.sld_hud_green.SetValue(int(options.get("gameplay", "hud-green")))
			self.sld_hud_blue.SetValue(int(options.get("gameplay", "hud-blue")))
			
			self.sld_player_red.SetValue(int(options.get("gameplay", "player-red")))
			self.sld_player_green.SetValue(int(options.get("gameplay", "player-green")))
			self.sld_player_blue.SetValue(int(options.get("gameplay", "player-blue")))
		self._changeShip(None)
		self._bindConfig(None)
		
class _Player(object):
	"""
	A representation of another player connected to the lobby server.
	"""
	_name = None #: The name of this player.
	_team_id = None #: The team to which the player belongs.
	
	def __init__(self, name, team_id):
		"""
		Initialises this player, setting their name and team ID.
		
		@type name: basestring
		@param name: The name of this player.
		@type team_id: int
		@param team_id: The ID of the team to which this player belongs.
		"""
		self._name = name
		self._team_id = team_id
		
	def changeTeam(self, team_id):
		"""
		Changes the team to which this player belongs.
		
		@type team_id: int
		@param team_id: The ID of the team to which this player belongs.
		"""
		self._team_id = team_id
		
	def getValues(self):
		"""
		Returns this player's name and current team.
		
		@rtype: tuple(2)
		@return: This player's team ID and name.
		"""
		return (self._team_id, self._name)
		