# -*- coding: utf-8 -*-
"""
UTc! module: game

Purpose
=======
 An entry-point for UTc!'s official release, serving as a network gameplay
 client.
 
Legal
=====
 All code, unless otherwise indicated, is original, and subject to the
 terms of the GPLv3, which is provided in COPYING.
 
 (C) Neil Tallim, 2009
"""
import math
import ode
import OpenGL
import OpenGL.GL as GL
import OpenGL.GLU as GLU
import optparse
import os
import pygame
from pygame.constants import K_ESCAPE, K_RETURN
import random
import re
import socket
import sys
import threading
import time

import src.camera as camera
import src.collisions as collisions
import src.constants as constants
import src.effects as effects
import src.gameobjects as gameobjects
import src.hud as hud
import src.matrix as matrix
import src.obstacles as obstacles
import src.path_utils as path_utils
import src.session_constants as session_constants
import src.sounds as sounds
import src.tankchan as tankchan
import src.weapons as weapons

import src.networking.data_box
import src.networking.communication

import src.config.parser

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

import src.input.capture as capture

_player_id = 0 #: The number to assign to the next player who joins the game.

class GameVariables(object):
	"""
	A generic class used solely to share variable references between this thread
	and the server communication thread.
	"""
	current_time = None #: The current game-time, in milliseconds.
	hostile_missiles = None #: A list of all missiles managed by this client.
	lock = None #: A lock used to ensure that both threads do not simultaneously attempt to access these variables.
	player = None #: A reference to the TankChan controlled by this client.
	player_name = None #: The name of this client.
	
def _addPlayer(render_mode, client_path, ode_space, player_data, client_player_name):
	"""
	Adds a TankChan to this client's collections.
	
	@type render_mode: int
	@param render_mode: A constant identifying the type of rendering to use when
	    rasterizing this TankChan.
	@type client_path: basestring
	@param: UTc!'s client data path.
	@type ode_space: ode.SpaceBase
	@param ode_space: The ODE space in which this TankChan will exist.
	@type player_data: basestring
	@param player_data: The PLYR line that describes this player.
	@type client_player_name: basestring
	@param client_player_name: The name of this client.
	
	@rtype: tuple(2)
	@return: The name and TankChan object resulting from parsing the PLYR data.
	"""
	global _player_id
	player_id = _player_id
	_player_id += 1
	
	match = re.match(r"PLYR (\w+) (\d) (\d+),(\d+),(\d+) (\w{2}) (\w{2}),(\w{2}),(\w{2}),(\w{2}) (\d),(\d),(\d),(\d) (\w{2}),(\w{2}),(\w{2}) (\d) (-?\d+),(-?\d+),(-?\d+)", player_data)
	
	player_name = match.group(1)
	player_team = int(match.group(2))
	player_colour = (float(match.group(3)) / 1000.0, float(match.group(4)) / 1000.0, float(match.group(5)) / 1000.0)
	player_model = match.group(6)
	
	player_bolts = player_bolts_modifiers = player_missiles = None
	if player_name == client_player_name:
		player_bolts = [match.group(i) for i in (7, 8, 9, 10) if not match.group(i) == '__']
		player_bolts_modifiers = [int(match.group(i)) for i in (11, 12, 13, 14) if not match.group(i) == '0']
		player_missiles = [match.group(i) for i in (15, 16, 17) if not match.group(i) == '__']
		
	player_power = int(match.group(18))
	player_spawn = (float(match.group(19)), float(match.group(20)), float(match.group(21)))
	
	session_constants.initColours([player_colour], [], render_mode)
	
	player = tankchan.TankChan(
	 path_utils.getObjectsPath() + "ships" + os.sep + "tc%s.xml" % (player_model),
	 render_mode, player_name, player_id, player_team, player_power,
	 player_bolts, player_bolts_modifiers, player_missiles, ode_space
	)
	player.setPosition(player_spawn)
	player.explode()
	
	return (player_name, player)
	
def _determineRenderMode(options):
	"""
	Evaluates the user's chosen render mode against their OpenGL API and
	determines whether the mode should be kept or reverted to basic flat-shading.
	
	@param options: A collection of parsed options, as returned by
	    C{optparse.OptionParser.parse_args()}.
	
	@rtype: int
	@return: A render mode constant, specifying the type of rendering that should
	    be applied.
	"""
	render_mode = options.render_mode
	if render_mode in (shading.RENDER_MODE_BASIC, shading.RENDER_MODE_WIREFRAME):
		return render_mode
		
	try:
		shading.init()
		return render_mode
	except AttributeError:
		sys.stderr.write("No valid shading API found; switching to basic mode.\n")
		return shading.RENDER_MODE_BASIC
		
def _findClosestHostile(player, hostiles):
	"""
	Determines the closest hostile player, relative to another player's position.
	
	@type player: L{tankchan.TankChan}
	@param player: The player against whose position the closest hostile is to be
	    located.
	@type hostiles: sequence
	@param hostiles: A collection of all TankChans hostile towards the specified
	    player.
	
	@rtype: L{tankchan.TankChan}|None
	@return: The closest hostile TankChan or None if there are no hostile
	    players in the field.
	"""
	player_position = player.getPosition()
	hostiles_by_proximity = sorted([(hostile.getDistance(player_position), hostile) for hostile in hostiles if hostile.isAlive()])
	if hostiles_by_proximity:
		return hostiles_by_proximity[0][1]
	return None
	
def _initColours(render_mode):
	"""
	Initialises team colours and shading, if applicable. If shading cannot be
	initialised, the value returned will be that for plain rendering.
	
	@type render_mode: int
	@param render_mode: A render mode constant, specifying the type of rendering
	    that should be applied.
	
	@rtype: int
	@return: A render mode constant, specifying the type of rendering that should
	    be applied, depending on whether shaders could be initialised or not.
	"""
	team_colours = (
	 (1.0, 0.0, 0.0),
	 (0.0, 1.0, 0.0),
	 (0.0, 0.0, 1.0),
	 (1.0, 0.5, 0.5),
	 (0.5, 1.0, 0.5),
    (0.5, 0.5, 1.0),
	)
	switch_to_basic = False
	try:
		session_constants.initColours([], team_colours, render_mode)
	except ValueError, e:
		if render_mode in (shading.RENDER_MODE_CEL, shading.RENDER_MODE_GRADIENT, shading.RENDER_MODE_FLAT):
			switch_to_basic = True
		else:
			raise e
	except OpenGL.error.NullFunctionError:
		switch_to_basic = True
		
	if switch_to_basic: #Shaders unavailable, though they were requested.
		sys.stderr.write("Your system does not appear to support shaders; switching to basic mode.\n")
		render_mode = shading.RENDER_MODE_BASIC
		session_constants.initColours([], team_colours, render_mode)
		
	return render_mode
	
def _initField(spaces, ode_space):
	space_none_regexp = re.compile(r"SPAC none (-?\d+),(-?\d+),(-?\d+)")
	space_sphere_regexp = re.compile(r"SPAC sphere (-?\d+),(-?\d+),(-?\d+) (-?\d+),(-?\d+),(-?\d+) (\d+) (\d+),(\d+),(\d+)")
	space_box_regexp = re.compile(r"SPAC box (-?\d+),(-?\d+),(-?\d+) (-?\d+),(-?\d+),(-?\d+) (\d+),(\d+),(\d+) (\d+),(\d+),(\d+)")
	
	spawn_points = []
	field_obstacles = []
	for space in spaces:
		none_match = space_none_regexp.match(space)
		if none_match:
			spawn_points.append(map(int, none_match.groups()))
		else:
			sphere_match = space_sphere_regexp.match(space)
			if sphere_match:
				field_obstacles.append(
				 obstacles.Sphere(
				  ode_space,
				  (int(sphere_match.group(1)), int(sphere_match.group(2)), int(sphere_match.group(3))),
				  (int(sphere_match.group(4)), int(sphere_match.group(5)), int(sphere_match.group(6))),
				  int(sphere_match.group(7)),
				  (float(sphere_match.group(8)) / 1000.0, float(sphere_match.group(9)) / 1000.0, float(sphere_match.group(10)) / 1000.0),
				 )
				)
			else:
				box_match = space_box_regexp.match(space)
				if box_match:
					field_obstacles.append(
					 obstacles.Box(
					  ode_space,
					  (int(box_match.group(1)), int(box_match.group(2)), int(box_match.group(3))),
					  (int(box_match.group(4)), int(box_match.group(5)), int(box_match.group(6))),
					  (int(box_match.group(7)), int(box_match.group(8)), int(box_match.group(9))),
					  (float(box_match.group(10)) / 1000.0, float(box_match.group(11)) / 1000.0, float(box_match.group(12)) / 1000.0),
					 )
					)
	return (tuple(spawn_points), tuple(field_obstacles))
	
def _initGamePlayElements():
	"""
	Initialises all modules related to gameplay elements.
	"""
	gameobjects.init()
	tankchan.init()
	effects.init()
	
	weapons.initBolts()
	weapons.initMissiles()
	
def _initHUDElements(client_path, r, g, b):
	"""
	Initialises the HUD module.
	
	@type client_path: basestring
	@param client_path: The path to UTc!'s client data path.
	@type r: int
	@param r: The red value to apply to the HUD.
	@type g: int
	@param g: The green value to apply to the HUD.
	@type b: int
	@param b: The blue value to apply to the HUD.
	"""
	message_font = client_path + 'assets' + os.sep + 'other' + os.sep + 'Vera.ttf'
	stats_font= client_path + 'assets' + os.sep + 'other' + os.sep + 'DejaVuSansMono.ttf'
	hud.init((r, g, b),
	 pygame.font.Font(message_font, 14),
	 pygame.font.Font(stats_font, 12),
	 pygame.font.Font(stats_font, 14),
	 pygame.font.Font(stats_font, 16)
	)
	
def _initOpenGL():
	"""
	Initialises OpenGL settings, such as lighting and perspective.
	"""
	rendering.init()
	GL.glLightModeli(GL.GL_LIGHT_MODEL_TWO_SIDE, 0)
	GL.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, (0, 0, 1, 0))
	GL.glLightfv(GL.GL_LIGHT0, GL.GL_AMBIENT, (0.5, 0.5, 0.5, 1))
	GL.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, (0.5, 0.5, 0.5, 0))
	GL.glLightfv(GL.GL_LIGHT0, GL.GL_SPECULAR, (0.5, 0.5, 0.5, 0))
	GL.glEnable(GL.GL_LIGHT0)
	GL.glEnable(GL.GL_LIGHTING)
	GL.glEnable(GL.GL_NORMALIZE)
	GL.glEnable(GL.GL_COLOR_MATERIAL)
	GL.glEnable(GL.GL_DEPTH_TEST)
	
	#Set up the GL canvas.
	GL.glMatrixMode(GL.GL_PROJECTION)
	GL.glLoadIdentity()
	GLU.gluPerspective(45.0, float(session_constants.WIDTH) / session_constants.HEIGHT, 1, 12500)
	GL.glMatrixMode(GL.GL_MODELVIEW)
	
def _initPlayers(players, player_name, render_mode, client_path, ode_space, player_map, tank_chans, other_players):
	"""
	Adds multiple TankChans to this client's collections.
	
	@type players: sequence
	@param players: A collection of all PLYR data to be processed.
	@type player_name: basestring
	@param player_name: The name of this client.
	@type render_mode: int
	@param render_mode: A constant identifying the type of rendering to use when
	    rasterizing these TankChans.
	@type client_path: basestring
	@param: UTc!'s client data path.
	@type ode_space: ode.SpaceBase
	@param ode_space: The ODE space in which these TankChans will exist.
	@type player_map: dict
	@param player_map: A mapping between player names and TankChan objects.
	@type tank_chans: list
	@param tank_chans: A list of all TankChans in the game.
	@type other_players: list
	@param other_players: A list of all non-client TankChans in the game.
	
	@rtype: L{tankchan.TankChan}|None
	@return: The TankChan associated with the player, provided it was among the
	    data processed.
	"""
	player = None
	for player_data in players:
		(name, tank_chan) = _addPlayer(render_mode, client_path, ode_space, player_data, player_name)
		player_map[name] = tank_chan
		tank_chans.append(tank_chan)
		if name == player_name:
			player = tank_chan
		else:
			other_players.append(tank_chan)
			
	return player
	
def _initPyGame(client_path, options):
	"""
	Initialises PyGame and all elements immediately required for other
	initialisation events.
	
	@type client_path: basestring
	@param: UTc!'s client data path.
	@param options: A collection of parsed options, as returned by
	    C{optparse.OptionParser.parse_args()}.
	
	@rtype: tuple(2)
	@return: The PyGame screen Surface, and a PyGame clock.
	"""
	pygame.mixer.pre_init(22050, -16, 2, 256) #22KHz, signed integers, stereo, 256ms buffer.
	pygame.init()
	
	#Initialise context.
	pygame.display.set_caption("Ultra Tank-chan!", "UTc!")
	pygame.display.set_icon(pygame.image.load(client_path + 'assets' + os.sep + 'other' + os.sep + 'utc-icon.png'))
	clock = pygame.time.Clock()
	if options.fsaa:
		try:
			pygame.display.gl_set_attribute(pygame.constants.GL_MULTISAMPLEBUFFERS, 1)
			return (pygame.display.set_mode((session_constants.WIDTH, session_constants.HEIGHT), pygame.constants.OPENGL | pygame.constants.DOUBLEBUF), clock)
		except:
			sys.stderr.write("Your system is incapable of FSAA.\n")
			pygame.display.gl_set_attribute(pygame.constants.GL_MULTISAMPLEBUFFERS, 0)
	return (pygame.display.set_mode((session_constants.WIDTH, session_constants.HEIGHT), pygame.constants.OPENGL | pygame.constants.DOUBLEBUF), clock)
	
def _spawnPlayer(player, position, respawn):
	"""
	Resets the client near a certain location, automatically computing its
	orientation and constructing a properly aligned camera.
	
	@type player: L{src.tankchan.TankChan}
	@param player: The player to be spawned.
	@type position: sequence(3)
	@param position: The (x, y, z) co-ordinates around which the player should
	    appear.
	@type respawn: bool
	@param respawn: False if this player is being spawned for the first time.
	
	@rtype: L{src.camera.Camera}
	@return: A camera aligned with the player's position and orientation.
	"""
	(x, y, z) = position
	rotation = None
	if x < 0.0:
		rotation = (0.0, 90.0, 0.0)
	elif x > 0.0:
		rotation = (0.0, 270.0, 0.0)
	elif y < 0.0:
		rotation = (270.0, 0.0, 0.0)
	elif y > 0.0:
		rotation = (90.0, 0.0, 0.0)
	elif z < 0.0:
		rotation = (0.0, 0.0, 0.0)
	elif z > 0.0:
		rotation = (0.0, 180.0, 0.0)
	else:
		rotation = (random.uniform(0.0, 360.0), random.uniform(0.0, 360.0), random.uniform(0.0, 360.0))
		
	if respawn:
		position = (x + random.choice((-15, 15)), y + random.choice((-15, 15)), z + random.choice((-15, 15)))
	player.reset(position, rotation)
	
	camera_obj = camera.Camera(position)
	camera_obj.rotate(*rotation)
	return camera_obj
	
def _updateBackgroundColour(current_colour, target_colour):
	"""
	Updates the current pastel to use as the background colour while moving
	slowly towards the target. When the target has been reached, a new target
	pastel is chosen.
	
	@type current_colour: sequence(3)
	@param current_colour: The current background colour's (r, g, b) values.
	@type target_colour: sequence(3)
	@param target_colour: The target background colour's (r, g, b) values.
	
	@rtype: tuple(2)
	@return: The current background colour's (r, g, b) and the target background
	    colour's (r, g, b) values.
	"""
	if not [None for (c, t) in zip(current_colour, target_colour) if abs(t - c) > 0.0005]: #Choose a new pastel target.
		target_colour = [random.uniform(0.5, 1.0) for i in range(3)]
	current_colour = [c + (cmp(t - c, 0) * 0.0005 * random.random()) for (c, t) in zip(current_colour, target_colour)] #Move slightly closer to the target.
	GL.glClearColor(current_colour[0], current_colour[1], current_colour[2], 0.0)
	return (current_colour, target_colour)
	
def _updateStatusScreen(status_screen, message, messages):
	"""
	A convenience function for redrawing a status screen.
	
	@type status_screen: L{hud.StatusScreen}
	@param status_screen: The status screen to be displayed.
	@type message: basestring
	@param message: The message to display along with the status screen.
	@type messages: L{hud.Messages}
	@param messages: A source for all message I/O to display along with the
	    status screen.
	"""
	GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)
	status_screen.setStatus(message)
	status_screen.render(messages)
	pygame.display.flip()
	
def _run(options, _player_name):
	"""
	The client's main routine: establishes a connection to the game server, then
	begins the game's mainloop, processing until the game comes to an end or the
	user chooses to generate a QUIT event.
	
	Note: I am fully aware that good OO practice would have this routine broken
	up into various small functions. However, because pretty much every
	instruction is needed in pretty much every frame, the performance gains by
	not splitting it up are considered worth the cost in maintenance (it's
	internally sub-divided into logical components anyway).
	
	Note: If you want to make changes to this routine, do *not* assign anything
	to one of the existing variables prefixed with an underscore. These are
	necessary for correct operation between cycles, and because this is all
	just one big function, debugging problems that may arise as a result of
	carelessness, even if it's just using one as a temporary variable in a loop,
	may be a very time-consuming endeavour.
	
	@param options: A collection of parsed options, as returned by
	    C{optparse.OptionParser.parse_args()}.
	@type _player_name: basestring
	@param _player_name: The name of the client.
	
	@raise Exception if the mainloop is terminated by means other than a clean
	    server shutdown.
	"""
	_client_path = path_utils.getClientPath()
	
	#Process complex options.
	_render_mode = _determineRenderMode(options)
	
	#Initialise environment constants.
	session_constants.init(options.fps, options.width, options.height)
	
	#Initialise surface elements.
	(_surface, _clock) = _initPyGame(_client_path, options)
	_initOpenGL()
	
	#Initialise sounds and randomly play startup sound.
	if options.sound:
		sounds.init()
		if random.random() < 0.025:
			sounds.SND_UTC_STARTUP.play()
			
	#Initialise common elements.
	_initHUDElements(_client_path, options.hud_r, options.hud_g, options.hud_b)
	_messages = hud.MessageBox()
	_ode_space = ode.Space()
	
	#Initialise background to a random pastel.
	_background_rgb_current = [random.uniform(0.5, 0.95) for i in range(3)]
	(_background_rgb_current, _background_rgb_target) = _updateBackgroundColour(_background_rgb_current, _background_rgb_current)
	
	#Render loading screen.
	status_screen = hud.StatusScreen(_client_path + 'assets' + os.sep + 'textures' + os.sep + 'screen-loading.png', (128, 164), (0, 0, 0))
	_updateStatusScreen(status_screen, "initialising environment", _messages)
	_initGamePlayElements()
	
	#Bring often-referenced classes into the local-variable namespace.
	Missile = weapons.Missile
	Bolt = weapons.Bolt
	TankChan = tankchan.TankChan
	
	#Initialise protocol regexps.
	_join_regexp = re.compile(r"JOIN (\w+)")
	_part_regexp = re.compile(r"PART (\w+)")
	_actv_regexp = re.compile(r"ACTV (\w+)")
	_aliv_regexp = re.compile(r"ALIV (\w+) \d+ (-?\d+\.\d+),(-?\d+\.\d+),(-?\d+\.\d+) (-?\d+\.\d+),(-?\d+\.\d+),(-?\d+\.\d+)")
	_kill_regexp = re.compile(r"KILL (\w+) (\w+)")
	_suci_regexp = re.compile(r"SUCI (\w+)")
	_rank_regexp = re.compile(r"RANK (\d+) (\d+)")
	_ppos_regexp = re.compile(r"PPOS (\w+) \d+ -?\d+\.\d+,-?\d+\.\d+,-?\d+\.\d+ -?\d+\.\d+,-?\d+\.\d+,-?\d+\.\d+ -?\d+\.\d+,-?\d+\.\d+ -?\d+\.\d+ -?\d+\.\d+ -?\d+\.\d+ -?\d+\.\d+ -?\d+\.\d+ -?\d+\.\d+ -?\d+\.\d+ \d -?\d+\.\d+,-?\d+\.\d+,-?\d+\.\d+")
	_mpos_regexp = re.compile(r"MPOS (\w+) (\d+) \d+ -?\d+\.\d+,-?\d+\.\d+,-?\d+\.\d+ -?\d+\.\d+,-?\d+\.\d+,-?\d+\.\d+ \d+\.\d+")
	_bnew_regexp = re.compile(r"BNEW (\w+) (\d+) (\w{2}) (\d+) (-?\d+\.\d+),(-?\d+\.\d+),(-?\d+\.\d+) (-?\d+\.\d+),(-?\d+\.\d+),(-?\d+\.\d+)")
	_mnew_regexp = re.compile(r"MNEW (\w+) (\d+) (\w+) (\w{2}) (\d+) (-?\d+\.\d+),(-?\d+\.\d+),(-?\d+\.\d+) (-?\d+\.\d+),(-?\d+\.\d+),(-?\d+\.\d+)")
	_damg_regexp = re.compile(r"DAMG \w+ (\w+) \d+ (\d) (\d+\.\d+),(\d+\.\d+),(\d+\.\d+)")
	_bdel_regexp = re.compile(r"BDEL (\w+) (\d+)")
	_mdel_regexp = re.compile(r"MDEL (\w+) (\d+)")
	_mxpl_regexp = re.compile(r"MXPL (-?\d+\.\d+),(-?\d+\.\d+),(-?\d+\.\d+)")
	_plyr_regexp = re.compile(r"PLYR (\w+) \d \d+,\d+,\d+ \w{2} \w{2},\w{2},\w{2},\w{2} \d,\d,\d,\d \w{2},\w{2},\w{2} \d -?\d+,-?\d+,-?\d+")
	_text_regexp = re.compile(r"TEXT (\w+) (.+)")
	
	#Connect to server.
	_updateStatusScreen(status_screen, "connecting to server", _messages)
	
	_game_variables = GameVariables()
	_data_box = src.networking.data_box.DataBox()
	_socket = src.networking.communication.Socket(options.host, options.port, _data_box, _game_variables)
	_readData = _socket.readData #Make it a local variable since it's used often.
	_sendData = _socket.sendData #Make it a local variable since it's used often.
	try:
		_socket.forceData(_player_name) #Send name to prove that this client belongs in the game.
		_readData(True)
		(code, payload) = _data_box.getLine().split(None, 1)
		if not code == "PASS":
			raise ValueError("Authentication failed: %s" % payload)
		del code
		del payload
		_game_variables.player_name = _player_name
		
		#Get field data and rules.
		_updateStatusScreen(status_screen, "setting up game-space", _messages)
		
		#Initialise gameplay constants.
		_readData(True)
		session_constants.initVariables(*map(float, _data_box.getLine().split()))
		
		#Initialise field.
		space_data = []
		space_data_complete = False
		while True:
			_readData(True)
			while _data_box.hasLines():
				line = _data_box.getLine()
				if line == "LEND": #End of list.
					space_data_complete = True
					break
				space_data.append(line)
			if space_data_complete:
				break
			else:
				time.sleep(0.1)
		(spawn_points, field_obstacles) = _initField(space_data[:-1], _ode_space)
		del space_data
		del space_data_complete
		
		#Get player data.
		_updateStatusScreen(status_screen, "setting up players", _messages)
		
		#Initialise colours system.
		_render_mode = _initColours(_render_mode)
		
		#Establish gameplay element lists.
		_tank_chans = []
		_other_players = []
		_visual_effects = []
		_bolts = []
		_bolts_map = {}
		_ecms = []
		_missiles = []
		_hostile_missiles = []
		_missiles_map = {_player_name: {}}
		_player_map = {} #: A mapping of player names to TankChan objects.
		
		#Initialise players.
		player_data = []
		player_data_complete = False
		while True:
			_readData(True)
			while _data_box.hasLines():
				line = _data_box.getLine()
				if line == "LEND": #End of list.
					player_data_complete = True
					break
				player_data.append(line)
			if player_data_complete:
				break
			else:
				time.sleep(0.1)
		_player = _initPlayers(player_data, _player_name, _render_mode, _client_path, _ode_space, _player_map, _tank_chans, _other_players)
		_hostiles = [other_player for other_player in _other_players if other_player.isHostile(_player)]
		del player_data
		del player_data_complete
		_game_variables.player = _player
		for other_player in _other_players:
			name = other_player.getName()
			_bolts_map[name] = {}
			_missiles_map[name] = {}
			
		#Initialise interface.
		_updateStatusScreen(status_screen, "setting up interface", _messages)
		
		#Create camera.
		_camera_obj = _spawnPlayer(_player, _player.getPosition(), False)
		
		#Create the player's target variable.
		_target = None
		
		#Create informative variables.
		_hud_screen = hud.HUDScreen(_player)
		_30_second_warning_shown = False
		_death_screen = None
		_death_time = 0.0
		
		#Set up input handling.
		_bindings_handler = capture.BindingsHandler(_player, os.path.expanduser("~/.UTc!/keybindings.cfg"))
		_event_reader = capture.EventReader()
		
		#Synchronize time with server.
		_updateStatusScreen(status_screen, "synchronizing", _messages)
		#Kill the loading screen.
		status_screen.destroy()
		del status_screen
		del options
		
		#Set up reporting variables.
		_game_variables.lock = game_lock = threading.Lock()
		_game_variables.hostile_missiles = _hostile_missiles
		
		#Finalize the connection.
		_socket.forceData("SYNC") #Tell the server that setup is done and synchronize.
		_readData(True) #Wait for PING.
		_data_box.getLine() #Ingore PING.
		_socket.forceData("PONG") #Respond to PING.
		_readData(True) #Wait for PING.
		_data_box.getLine() #Ingore PING.
		_socket.forceData("PONG") #Respond to PING.
		_readData(True) #Wait for PING.
		_data_box.getLine() #Ingore PING.
		_socket.forceData("PONG") #Respond to PING.
		_readData(True) #Wait for stats.
		(_current_time, duration, rank, score, ping_delay) = map(int, _data_box.getLine().split())
		_current_time = _current_time / 1000.0
		_remaining_time = (duration * 60.0) - _current_time
		
		_game_variables.current_time = _current_time_ms = int(_current_time * 1000.0)
		_game_variables.throttle = max(0.0675, ping_delay / 1000.0)
		
		_scoring = hud.Scoring(duration, _current_time, rank, score, 0)
		del duration
		del rank
		del score
		del ping_delay
		
		#Inform all players that this client is now ready to play.
		_sendData("ALIV %s %i %f,%f,%f %f,%f,%f" % ((_player_name, _current_time_ms) + _player.getPosition() + _player.getRotationEuler()))
		_socket.start() #Don't start sending updates before this point.
		
		_clock.tick(session_constants.FPS) #Ensure the FPS isn't skewed at the start, which would mess up movement.
		
		while True:
			#Update the player's target, if appropriate.
			target_changed = False #True when the player has changed targets, necessitating some forced redraws.
			bolt_impact = False #True if the player was hit by a bolt during this cycle, preventing multiple sound playback.
			player_sensors = _player.getSubsystemCondition(tankchan.SUBSYSTEM_SENSORS)
			if _target and _target.isAlive():
				if not _player.isAlive() or player_sensors < tankchan.SUBSYSTEM_CONDITION_CRIPPLED:
					_target = None
					target_changed = True
			elif _player.isAlive() and player_sensors >= tankchan.SUBSYSTEM_CONDITION_CRIPPLED:
				_target = _findClosestHostile(_player, _hostiles)
				target_changed = True
				
			#Read and process data sent from the server.
			_readData()
			if _data_box.hasLines():
				for line in _data_box.getLines():
					match = _ppos_regexp.match(line)
					if match:
						origin_player = _player_map.get(match.group(1))
						if origin_player:
							origin_player.inscribe(line, _current_time)
						continue
						
					match = _mpos_regexp.match(line)
					if match:
						m_map = _missiles_map.get(match.group(1))
						if m_map:
							missile = m_map.get(int(match.group(2)))
							if missile:
								missile.inscribe(line, _current_time)
						continue
						
					match = _bnew_regexp.match(line)
					if match:
						factory = weapons.BOLTS.get(match.group(3))
						if factory:
							name = match.group(1)
							origin_player = _player_map.get(name)
							if origin_player:
								id = int(match.group(2))
								bolt = factory.createBolt(
								 0,
								 matrix.buildRotationMatrix(float(match.group(8)), float(match.group(9)), float(match.group(10))),
								 (float(match.group(5)), float(match.group(6)), float(match.group(7))),
								 None, None, origin_player, None, id
								)
								if bolt.tick(_current_time - float(match.group(4)) / 1000.0):
									_bolts_map[name][id] = bolt
									_bolts.append(bolt)
						continue
						
					match = _mnew_regexp.match(line)
					if match:
						factory = weapons.MISSILES.get(match.group(4))
						if factory:
							name = match.group(1)
							origin_player = _player_map.get(name)
							target_player = _player_map.get(match.group(3))
							if origin_player and target_player:
								missile_ode_space = _ode_space
								if not target_player == _player:
									target_player = missile_ode_space = None
								id = int(match.group(2))
								missile = factory.createMissile(
								 matrix.buildRotationMatrix(float(match.group(9)), float(match.group(10)), float(match.group(11))),
								 (float(match.group(6)), float(match.group(7)), float(match.group(8))),
								 target_player, origin_player, missile_ode_space, id
								)
								if missile.tick(_current_time - float(match.group(5)) / 1000.0, None):
									if target_player: #Missile targets the client.
										_hostile_missiles.append(missile)
									else:
										_missiles_map[name][id] = missile
										_missiles.append(missile)
						continue
						
					match = _damg_regexp.match(line)
					if match:
						if _player.isAlive():
							attacker_name = match.group(1)
							subsystem = int(match.group(2))
							shield_damage = float(match.group(3))
							hull_damage = float(match.group(4))
							subsystem_damage = float(match.group(5))
							
							if not bolt_impact:
								bolt_impact = True
								if sounds.USE_SOUNDS:
									if _player.isShielded():
										sounds.SND_IMPACT_BOLT_SHIELD.play()
									else:
										sounds.SND_IMPACT_BOLT_HULL.play()
										
							if subsystem == tankchan.SUBSYSTEM_PROJECTION:
								if _player.isShielded():
									if not _player.damageShielding(shield_damage):
										_player.lowerShields()
								else:
									if not _player.damageHull(hull_damage):
										_sendData("DEAD %s %s %i" % (_player_name, attacker_name, int(_current_time * 1000.0)))
										_player.explode()
										_death_screen = hud.getDeathScreen()
										_death_time = session_constants.RESURRECTION_TIME
										_death_screen.setStatus("killed by %s" % (attacker_name))
									else:
										subsystem_damage = subsystem_damage / 4.0
										_player.damageSubsystem(subsystem_damage, tankchan.SUBSYSTEM_SENSORS)
										_player.damageSubsystem(subsystem_damage, tankchan.SUBSYSTEM_ENGINES)
										_player.damageSubsystem(subsystem_damage, tankchan.SUBSYSTEM_SHIELDS)
										_player.damageSubsystem(subsystem_damage, tankchan.SUBSYSTEM_WEAPONS)
							elif not _player.damageHull(hull_damage):
								_sendData("DEAD %s %s %i" % (_player_name, attacker_name, int(_current_time * 1000.0)))
								_player.explode()
								_death_screen = hud.getDeathScreen()
								_death_time = session_constants.RESURRECTION_TIME
								_death_screen.setStatus("killed by %s" % (attacker_name))
							else:
								_player.damageSubsystem(subsystem_damage, subsystem)
						continue
						
					match = _bdel_regexp.match(line)
					if match:
						name = match.group(1)
						id = int(match.group(2))
						b_map = _bolts_map.get(name)
						if b_map and id in b_map:
							del b_map[id]
							origin_player = _player_map.get(name)
							if origin_player:
								for bolt in _bolts:
									if bolt.matchID(id, origin_player):
										_bolts.remove(bolt)
										break
						continue
						
					match = _mdel_regexp.match(line)
					if match:
						name = match.group(1)
						id = int(match.group(2))
						m_map = _missiles_map.get(name)
						if m_map and id in m_map:
							del m_map[id]
							origin_player = _player_map.get(name)
							if origin_player:
								for missile in _missiles:
									if missile.matchID(id, origin_player):
										_missiles.remove(missile)
										break
						continue
						
					match = _mxpl_regexp.match(line)
					if match:
						_visual_effects.append(effects.MissileExplosion((float(match.group(1)), float(match.group(2)), float(match.group(3)))))
						continue
						
					match = _aliv_regexp.match(line)
					if match:
						origin_player = _player_map.get(match.group(1))
						if origin_player:
							origin_player.reset(
							 (float(match.group(2)), float(match.group(3)), float(match.group(4))),
							 (float(match.group(5)), float(match.group(6)), float(match.group(7)))
							)
							if not _target and origin_player.isHostile(_player):
								_target = origin_player
								target_changed = True
						continue
						
					match = _rank_regexp.match(line)
					if match:
						_scoring.changeRank(int(match.group(1)))
						_scoring.changeScore(int(match.group(2)))
						continue
						
					match = _kill_regexp.match(line)
					if match:
						victim_name = match.group(1)
						killer_name = match.group(2)
						victim_alive = False
						if victim_name == _player_name: #Client killed.
							if _player.isAlive():
								victim_alive = True
								_player.explode()
								_death_screen = hud.getDeathScreen()
								_death_time = session_constants.RESURRECTION_TIME
								_death_screen.setStatus("killed by %s" % (killer_name))
								_target = None
						else:
							victim = _player_map.get(victim_name)
							if victim and victim.isAlive():
								victim_alive = True
								victim.explode()
								_visual_effects.append(effects.ShipExplosion(victim.getPosition()))
								if victim == _target:
									_target = None
									target_changed = True
									
							if killer_name == _player_name:
								_scoring.incrementKills()
								
						if victim_alive:
							_messages.addMessage("Kill", "%s killed by %s" % (victim_name, killer_name), True)
							if sounds.USE_SOUNDS:
								sounds.SND_NEW_EVENT.play()
						continue
						
					match = _suci_regexp.match(line)
					if match:
						name = match.group(1)
						origin_player = _player_map.get(name)
						if origin_player and origin_player.isAlive():
							origin_player.explode()
							_visual_effects.append(effects.ShipExplosion(origin_player.getPosition()))
							_messages.addMessage("Kill", "%s crashed into an obstacle" % (name), True)
							if sounds.USE_SOUNDS:
								sounds.SND_NEW_EVENT.play()
							if origin_player == _target:
								_target = None
								target_changed = True
						continue
						
					match = _text_regexp.match(line)
					if match:
						_messages.addMessage(match.group(1), match.group(2), False)
						if sounds.USE_SOUNDS:
							sounds.SND_NEW_MESSAGE.play()
						continue
						
					match = _join_regexp.match(line)
					if match:
						_messages.addMessage("Join", "%s has joined the game" % (match.group(1)), True)
						if sounds.USE_SOUNDS:
							sounds.SND_NEW_EVENT.play()
						_sendData("ACTV %s" % _player_name)
						continue
						
					match = _actv_regexp.match(line)
					if match:
						origin_player = _player_map.get(match.group(1))
						if origin_player and not origin_player.isAlive():
							origin_player.reset((0.0, 0.0, 0.0), (0.0, 0.0, 0.0))
							if not _target and origin_player.isHostile(_player):
								_target = origin_player
								target_changed = True
						continue
						
					match = _plyr_regexp.match(line)
					if match:
						name = match.group(1)
						if not name in _player_map:
							(name, tank_chan) = _addPlayer(_render_mode, _client_path, _ode_space, line, _player_name)
							_player_map[name] = tank_chan
							_other_players.append(tank_chan)
							if tank_chan.isHostile(_player):
								_hostiles.append(tank_chan)
							_bolts_map[name] = {}
							_missiles_map[name] = {}
						continue
						
					match = _part_regexp.match(line)
					if match:
						name = match.group(1)
						origin_player = _player_map.get(name)
						if origin_player:
							for bolt in _bolts_map[name].values():
								if bolt in _bolts:
									_bolts.remove(bolt)
							del _bolts_map[name]
							
							for missile in _missiles_map[name].values():
								if missile in _missiles:
									_missiles.remove(missile)
								elif missile in _hostile_missiles:
									missile.destroy()
									_hostile_missiles.remove(missile)
							del _missiles_map[name]
							
							origin_player.destroy()
							if origin_player in _tank_chans:
								_tank_chans.remove(origin_player)
							if origin_player in _other_players:
								_other_players.remove(origin_player)
							if origin_player.isHostile(_player) and origin_player in _hostiles:
								_hostiles.remove(origin_player)
							del _player_map[name]
							_messages.addMessage("Quit", "%s has left the game" % (name), True)
							if sounds.USE_SOUNDS:
								sounds.SND_NEW_EVENT.play()
						continue
						
					if line == "GEND":
						return
						
			#Perform local operations.
			game_lock.acquire()
			#Update frame variables.
			player_alive = _player.isAlive()
			_clock.tick(session_constants.FPS)
			seconds = _clock.get_time() / 1000.0
			
			#Process input events.
			(axes, buttons, keys, new_keys) = _event_reader.getEvents(seconds)
			if _messages.isTextEntryInProgress():
				keys = [key for key in keys if not (key[1] or key[0] in (K_RETURN, K_ESCAPE))] #Strip all input keys.
			if new_keys:
				for (i, (key, ucode)) in enumerate(new_keys):
					if _messages.isTextEntryInProgress():
						if key == K_RETURN:
							_sendData("TEXT %s %s" % (_player_name, _messages.completeTextEntry()))
						elif key == K_ESCAPE:
							_messages.abortTextEntry()
						elif ucode:
							_messages.addCharacter(ucode)
					elif key == K_RETURN:
						_messages.beginTextEntry()
			if player_alive:
				(new_bolts, new_missiles, new_ecms, new_target) = _bindings_handler.processEvents(axes, buttons, keys, seconds, _target, _other_players, _camera_obj)
				_current_time_ms = int(_current_time * 1000.0)
				for bolt in new_bolts:
					(x, y, z) = bolt.getPosition()
					(x_rot, y_rot, z_rot) = bolt.getRotationEuler()
					_sendData("BNEW %s %i %s %i %f,%f,%f %f,%f,%f" % (_player_name, bolt.getID(), bolt.getType(), _current_time_ms, x, y, z, x_rot, y_rot, z_rot))
				_bolts += new_bolts
				for missile in new_missiles:
					missile.disable()
					missile_id = missile.getID()
					_missiles_map[_player_name][missile_id] = missile
					(x, y, z) = missile.getPosition()
					(x_rot, y_rot, z_rot) = missile.getRotationEuler()
					_sendData("MNEW %s %i %s %s %i %f,%f,%f %f,%f,%f" % (_player_name, missile_id, _target.getName(), missile.getType(), _current_time_ms, x, y, z, x_rot, y_rot, z_rot))
				_missiles += new_missiles
				_ecms += new_ecms
				target_changed = not _target == new_target
				_target = new_target
				
				if sounds.USE_SOUNDS:
					if new_ecms:
						sounds.SND_ECM.play()
					elif new_missiles:
						sounds.SND_MISSILE.play()
					elif new_bolts:
						sounds.SND_BOLT.play()
						
			#Update elements.
			_current_time += seconds
			_remaining_time -= seconds
			if not _30_second_warning_shown and _remaining_time <= 30.0:
				_messages.addMessage("Time", "30 seconds left.", True)
				if sounds.USE_SOUNDS:
					sounds.SND_NEW_EVENT.play()
				_30_second_warning_shown = True
				
			_game_variables.current_time = _current_time_ms = int(_current_time * 1000.0)
			if not player_alive:
				old_death_seconds = math.ceil(_death_time)
				_death_time -= seconds
				if _death_time <= 0:
					spawn_point = random.choice(spawn_points) #Pick a random spawn point.
					for attempt in range(5): #Try to find an empty location, falling back to the random one if five tries are unsuccessful.
						potential_point = random.choice(spawn_points)
						if not [None for other_player in _other_players if _player.getDistance(potential_point) < 50.0]:
							spawn_point = potential_point
							break
					_camera_obj = _spawnPlayer(_player, spawn_point, True)
					player_alive = True
					if _death_screen:
						_death_screen.destroy()
						_death_screen = None
					_hud_screen.reset()
					_sendData("ALIV %s %i %f,%f,%f %f,%f,%f" % ((_player_name, _current_time_ms) + _player.getPosition() + _player.getRotationEuler()))
				elif math.ceil(_death_time) < old_death_seconds:
					_death_screen.setStatus("resurrection in %i..." % math.ceil(_death_time))
					
			#Tick elements.
			_player.tick(seconds)
			for tank_chan in _other_players:
				tank_chan.move(seconds)
			_camera_obj.setPosition(_player.getPosition())
			_messages.tick(seconds)
			_scoring.tick(seconds)
			_visual_effects = [effect for effect in _visual_effects if effect.tick(seconds)]
			for bolt in _bolts[:]:
				if not bolt.tick(seconds):
					_bolts.remove(bolt)
					b_map = _bolts_map.get(bolt.getTankChan().getName())
					id = bolt.getID()
					if b_map and id in b_map:
						del b_map[id]
			_ecms = [ecm for ecm in _ecms if ecm.tick(seconds)]
			for missile in _missiles[:]:
				if not missile.tick(seconds, None):
					_missiles.remove(missile)
					m_map = _missiles_map.get(missile.getTankChan().getName())
					id = missile.getID()
					if m_map and id in m_map:
						del m_map[id]
			_missiles = [missile for missile in _missiles if missile.tick(seconds, None)]
			for missile in _hostile_missiles[:]:
				missile_alive = missile.tick(seconds, _ecms)
				if not missile_alive or not player_alive:
					_sendData("MDEL %s %i" % (missile.getTankChan().getName(), missile.getID()))
					if not player_alive:
						missile.destroy()
					_hostile_missiles.remove(missile)
					
			#Handle collisions.
			dead_elements = []
			kills = []
			fallen_shields = []
			_ode_space.collide((_visual_effects, [], dead_elements, kills, fallen_shields, _player), collisions.collisionHandler)
			for (element, struck_element, struck_subsystem) in dead_elements:
				element.destroy()
				if type(element) == Missile:
					_sendData("MDEL %s %i" % (element.getTankChan().getName(), element.getID()))
					_sendData("MXPL %f,%f,%f" % element.getPosition())
					if struck_element == _player and sounds.USE_SOUNDS:
						sounds.SND_IMPACT_MISSILE.play()
				elif type(element) == Bolt:
					_sendData("BDEL %s %i" % (_player_name, element.getID()))
					
				if type(struck_element) == TankChan and not struck_element == _player: #Only broadcast damage being done to others by this player.
					_sendData("DAMG %s %s %i %i %f,%f,%f" % (struck_element.getName(), element.getTankChan().getName(), _current_time_ms, struck_subsystem, element.getShieldDamage(), element.getHullDamage(), element.getSubsystemDamage()))
			for tank_chan in fallen_shields:
				tank_chan.lowerShields()
			for (killer, victim) in kills:
				victim.explode()
				if victim == _player:
					_death_screen = hud.getDeathScreen()
					_death_time = session_constants.RESURRECTION_TIME
					_target = None
					if not killer:
						_sendData("SUCI %s %i" % (_player_name, _current_time_ms))
						_death_screen.setStatus("crashed into an obstacle")
						_messages.addMessage("Kill", "%s crashed into an obstacle" % (_player_name), True)
						if sounds.USE_SOUNDS:
							sounds.SND_NEW_EVENT.play()
					else:
						killer_name = killer.getName()
						_sendData("DEAD %s %s %i" % (_player_name, killer_name, _current_time_ms))
						_death_screen.setStatus("killed by %s" % (killer_name))
						_messages.addMessage("Kill", "%s killed by %s" % (_player_name, killer_name), True)
						if sounds.USE_SOUNDS:
							sounds.SND_NEW_EVENT.play()
				elif killer == _player:
					_sendData("DEAD %s %s %i" % (victim.getName(), _player_name, _current_time_ms))
					if victim == _target:
						_target = None
						target_changed = True
						
			#Render the new frame.
			GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)
			if player_alive:
				#Set the camera.
				camera_transformation_matrix = _camera_obj.getTransformationMatrix()
				_camera_obj.setView()
				
				#Render weapons.
				if _render_mode == shading.RENDER_MODE_WIREFRAME:
					GL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE)
				else:
					GL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL)
					
				#Draw bolts.
				GL.glDisable(GL.GL_LIGHTING)
				for bolt in _bolts:
					bolt.updateCameraVectors(camera_transformation_matrix, False)
					bolt.render()
				GL.glEnable(GL.GL_LIGHTING)
				
				#Draw missiles.
				for missile in _missiles + _hostile_missiles:
					missile.updateCameraVectors(camera_transformation_matrix, True)
					missile.render()
					
				#Render the obstacles.
				GL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL)
				for obstacle in field_obstacles:
					obstacle.updateCameraVectors(camera_transformation_matrix, False)
					obstacle.render()
				GL.glShadeModel(GL.GL_FLAT)
				
				#Render the other players.
				render_indicator = player_sensors >= tankchan.SUBSYSTEM_CONDITION_CRIPPLED
				render_condition = player_sensors >= tankchan.SUBSYSTEM_CONDITION_FUNCTIONAL
				render_reticle = player_sensors >= tankchan.SUBSYSTEM_CONDITION_FAIR
				for tank_chan in _other_players:
					tank_chan.updateCameraVectors(camera_transformation_matrix, True)
					is_target = tank_chan == _target
					tank_chan.render(
					 not tank_chan.isHostile(_player),
					 render_indicator,
					 render_condition,
					 is_target and render_reticle,
					 is_target, target_changed
					)
					
				#Draw visual effects.		
				#Set up 2D rendering parameters.
				GL.glEnable(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_AND_BACK, GL.GL_FILL)
				GL.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR)
				GL.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR)
				GL.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_MODULATE)
				
				for effect in reversed(_visual_effects):
					effect.updateCameraVectors(camera_transformation_matrix, False)
					effect.render()
					
				#Restore 3D rendering parameters.
				GL.glDisable(GL.GL_BLEND)
				GL.glDisable(GL.GL_TEXTURE_2D)
				
				#Render HUD.
				if _target and _target.isAlive():
					_hud_screen.update(_other_players, _hostile_missiles, _hostiles, _target)
				else:
					_hud_screen.update(_other_players, _hostile_missiles, _hostiles, None)
				_hud_screen.render(_messages, _scoring)
			else:
				_death_screen.render(_messages)
				
			#Display the new frame.
			pygame.display.flip()
			
			#Change the background colour slightly.
			(_background_rgb_current, _background_rgb_target) = _updateBackgroundColour(_background_rgb_current, _background_rgb_target)
			
			game_lock.release()
	finally:
		_socket.kill()
		pygame.quit()
		
if __name__ == '__main__':
	#Parse commandline arguments.
	parser = optparse.OptionParser(version="%s v%s" % ("Ultra Tank-chan!", "beta"))
	parser.add_option("--host", type="string", dest="host", default="localhost")
	parser.add_option("--port", type="int", dest="port", default=64753)
	parser.add_option("--hud_red", type="int", dest="hud_r", default=63)
	parser.add_option("--hud_green", type="int", dest="hud_g", default=63)
	parser.add_option("--hud_blue", type="int", dest="hud_b", default=127)
	parser.add_option("--render_mode", type="int", dest="render_mode", default=shading.RENDER_MODE_BASIC)
	parser.add_option("--fsaa", action="store_true", dest="fsaa", default=False)
	parser.add_option("--fps", type="int", dest="fps", default=30)
	parser.add_option("--width", type="int", dest="width", default=800)
	parser.add_option("--height", type="int", dest="height", default=600)
	parser.add_option("--sound", action="store_true", dest="sound", default=False)
	(options, args) = parser.parse_args()
	if not args:
		print "No username provided."
		sys.exit(1)
	del parser
	
	#Run the game.
	try:
		_run(options, args[0])
		print "OKAY"
	except Exception, e:
		print e
		