//////////////////////////////////////////////////////////////////////////////
//
// Asura_Lobby.cpp
//
// Copyright (c) 2008 Rebellion. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////////

// I N C L U D E S ///////////////////////////////////////////////////////////

#include "Core/Asura.h"

#ifdef ASURA_SUPPORT_LOBBY

// This file's header.
#include "Network/Online/Asura_Lobby.h"

// Asura includes
#include "Core/Asura_Timers.h"
#include "Core/Memory/Asura_Mem.h"
#include "Debugging/Asura_Assert.h"
#include "Debugging/Asura_DebugText.h"
#include "Debugging/Asura_Logfile.h"
#include "Interface/Asura_CommandConsole.h"
#include "Network/Asura_Message.h"
#include "Network/Asura_NetLinkManager.h"
#include "Network/Asura_Network.h"
#include "Network/Asura_Network_Session.h"
#include "Network/Asura_NetTransport_System.h"
#ifdef ASURA_ENABLE_QOS_SYSTEM
#include "Network/Asura_Qos_System.h"
#endif //#ifdef ASURA_ENABLE_QOS_SYSTEM
#include "Network/Asura_ReliableSocket.h"
#include "Network/Online/Asura_FriendsList.h"
#ifdef ASURA_SUPPORT_LEADERBOARDS
#include "Network/Online/Asura_Leaderboards.h"
#endif
#include "Network/Online/Asura_Merit.h"

// T Y P E S /////////////////////////////////////////////////////////////////

// helper classes to generate template code for team-specific functions
ASURA_STATIC_ASSERT(ASURA_LOBBY_MAX_TEAMS > 0 && ASURA_LOBBY_MAX_TEAMS <= 4);
class Asura_Lobby_Team0
{
public:
	static const u_char ucTEAM_NUM = 0;
};
#if ASURA_LOBBY_MAX_TEAMS > 1
class Asura_Lobby_Team1
{
public:
	static const u_char ucTEAM_NUM = 1;
};
#if ASURA_LOBBY_MAX_TEAMS > 2
class Asura_Lobby_Team2
{
public:
	static const u_char ucTEAM_NUM = 2;
};
#if ASURA_LOBBY_MAX_TEAMS > 3
class Asura_Lobby_Team3
{
public:
	static const u_char ucTEAM_NUM = 3;
};
#endif //3
#endif //2
#endif //1

// C O N S T A N T S /////////////////////////////////////////////////////////
//Min time between updates is used to stop players spamming the network by, for instance,
//repeatedly hitting "Ready"
const float fASURA_LOBBY_MIN_TIME_BETWEEN_FULL_UPDATES = 1.0f;
// Maximum time allowed to join lobby
const float fASURA_LOBBY_CONNECTION_TIMEOUT = 15.0f;
// Maximum time allowed to register before a game
const float fASURA_LOBBY_REGISTERING_TIMEOUT = 10.0f;
// Maximum time allowed to create lobby
const float fASURA_LOBBY_CREATE_TIMEOUT = 10.0f;
// Maximum time allowed to join game
const float fASURA_LOBBY_INITIALISING_GAME_TIMEOUT = 10.0f;
// "starting game" involves grabbing pre-game stats so it could take a little while
const float fASURA_LOBBY_STARTING_GAME_TIMEOUT = 30.0f;
// "ending game" is when stats get uploaded so could need a decent chunk of time.
const float fASURA_LOBBY_ENDING_GAME_TIMEOUT = 60.0f;
// Maximum time allowed to close sockets.
const float fASURA_LOBBY_TERMINATING_TIMEOUT = 10.0f;
// Maximum time allowed to linger in lobby when joining a game in progress, before being kicked
//Need to expose this to projects, also need to inform the player of it's progress - somehow.
const float fASURA_LOBBY_JOINING_GAME_IN_PROGRESS_TIMEOUT = 30.0f; 
// Maximum time allowed for entire process of migration, must be more than HOST_STARTING and HOST_STOPPING
const float fASURA_LOBBY_MIGRATION_TIMEOUT = 15.0f;
// Maximum time allowed for starting the new host, must be more than HOST_STOPPING (as the stopping host
// won't connect to the new host until its old host socket is closed)
const float fASURA_LOBBY_MIGRATION_HOST_STARTING_TIMEOUT = 10.0f;
// Maximum time allowed for stopping the old host
const float fASURA_LOBBY_MIGRATION_HOST_STOPPING_TIMEOUT = 5.0f;

DEBUGVAR bool dbg_bTestMigrationDisconnectFailure = false;
DEBUGVAR bool dbg_bTestMigrationConnectFailure = false;
DEBUGVAR bool dbg_bDisableMigrationTimeouts = false;

// masks for automatch data (contained in u_char)
#define ASURA_LOBBY_AUTOMATCHDATA_USINGAUTOMATCH_MASK		(0x80)
#define ASURA_LOBBY_AUTOMATCHDATA_NUMPLAYERSAWAITED_MASK	(0x7f)

#ifdef ASURA_USE_MERIT_SYSTEM
ASURA_STATIC_ASSERT(ASURA_LOBBY_MAX_SKILL == uASURA_MERIT_NUM_LEVELS);
#endif

// S T A T I C   C L A S S   D A T A /////////////////////////////////////////
// void (*Asura_Lobby_System::s_pfnServerMessageCallback)(const Asura_Message* pxMessage);
// void (*Asura_Lobby_System::s_pfnClientMessageCallback)(const Asura_Message* pxMessage);
Asura_Lobby* Asura_Lobby_System::s_pxLobby;
bool Asura_Lobby_System::s_bInitialised;
bool Asura_Lobby_System::s_bInvitationPending;
bool Asura_Lobby_System::s_bInvitedToGame;
bool Asura_Lobby_System::s_bUseRankedLobbies;
bool Asura_Lobby_System::s_bAllowJoinInProgress;
bool Asura_Lobby_System::s_bJoiningGameInProgress;
bool Asura_Lobby_System::s_bLocalPlayerReady;
bool Asura_Lobby_System::s_bLocalPlayerHasMicrophone = false;
bool Asura_Lobby_System::s_bLocalClientReceivedFirstUpdate;
bool Asura_Lobby_System::s_bPlayerJoinedSinceLastUpdate = false;
bool Asura_Lobby_System::s_bRegistered;
bool Asura_Lobby_System::s_bShowDebugOutput = false;
bool Asura_Lobby_System::s_bMigrateOnDeadHost = false;
bool Asura_Lobby_System::s_bMigrateOnGameStart = false;
bool Asura_Lobby_System::s_bPlatformCreateDone = false;

u_char Asura_Lobby_System::s_ucLocalPlayerRequestedTeam = ASURA_LOBBY_INVALID_TEAM;
u_char Asura_Lobby_System::s_aucProjectData[ASURA_LOBBY_PLAYER_PROJECT_DATA_SIZE]; 
u_char Asura_Lobby_System::s_ucVoiceChannels;
u_char Asura_Lobby_System::s_ucActiveVoiceChannel;
Asura_UnicodeChar Asura_Lobby_System::s_awcLocalPlayerName[ASURA_LOBBY_PLAYER_NAME_BUFFER_SIZE];
#ifdef ASURA_USE_MERIT_SYSTEM
Asura_Merit Asura_Lobby_System::s_xLocalPlayerMerit;
#else
int Asura_Lobby_System::s_iLocalPlayerSkill;
#endif
Asura_Lobby_HostingSettings Asura_Lobby_System::s_xHostingSettings;
ASURA_LOBBY_STATE Asura_Lobby_System::s_eLobbyState;
float Asura_Lobby_System::s_fStateTimer;
float Asura_Lobby_System::s_fUpdateMessageTimer;
int Asura_Lobby_System::s_iNetworkDisconnectReason;
float Asura_Lobby_System::s_fUpdateTimestamp;
bool Asura_Lobby_System::s_bLocalPlayerDataChanged;
// lobby search
ASURA_LOBBY_SEARCHSTATE Asura_Lobby_System::s_eSearchState;
Asura_Lobby_SearchCriterion* Asura_Lobby_System::s_pxSearchCriteria;
u_int Asura_Lobby_System::s_uNumSearchCriteria;
Asura_Lobby_ListEntry* Asura_Lobby_System::s_pxSearchResults;
u_int Asura_Lobby_System::s_uNumSearchResults;
u_int Asura_Lobby_System::s_uIndexOfLobbyToJoin;
// migration
ASURA_LOBBY_HOST_MIGRATIONSTATE Asura_Lobby_System::s_eHostMigrationState = ASURA_LOBBY_HOST_MIGRATIONSTATE_NONE;
ASURA_LOBBY_CLIENT_MIGRATIONSTATE Asura_Lobby_System::s_eClientMigrationState = ASURA_LOBBY_CLIENT_MIGRATIONSTATE_NONE;
float Asura_Lobby_System::s_fHostMigrationTimeout = 0.0f;

// Game completion records
Asura_GameCompletionHistory Asura_Lobby_System::s_xGameCompletionHistory;
ASURA_LOBBY_GAME_COMPLETION_STATUS Asura_Lobby_System::s_eGameCompletionStatus = ASURA_LOBBY_GAME_COMPLETION_UNSET;;
u_int Asura_Lobby::s_uNumTeams;

// Game information
char Asura_Lobby_System::s_acGameAddress[ASURA_LOBBY_ADDRESS_LENGTH];
bool Asura_Lobby_System::s_bPlatformInitialisingGameDone = false;

// Communications
Asura_ReliableSocket_Listener* Asura_Lobby_System::s_pxHostSocket = NULL;
Asura_ReliableSocket_Client* Asura_Lobby_System::s_pxClientSocket = NULL;
u_short Asura_Lobby_System::s_uListenTCPPort = ASURA_LOBBY_FIRST_PORT;


#ifdef ASURA_LOGFILES_ENABLED
#define ASURA_LOBBYSYSTEM_TRACE Asura_OutputToDebugger_Retail(("LOBBYSYSTEM: ")); Asura_OutputToDebugger_Retail
static const char* LobbyStateToString(ASURA_LOBBY_STATE eState)
{
	switch (eState)
	{
		case ASURA_LOBBY_STATE_INVALID:						return "Invalid";
		case ASURA_LOBBY_STATE_CREATING:					return "Creating";
		case ASURA_LOBBY_STATE_PRE_GAME:					return "Pre Game";
		case ASURA_LOBBY_STATE_JOINING_PLATFORM_EARLY_INIT:	return "Platform Early Init";
		case ASURA_LOBBY_STATE_JOINING_WAIT_FOR_CONNECTION:	return "Wait For Connection";
		case ASURA_LOBBY_STATE_JOINING_PLATFORM_LATE_INIT:	return "Platform Late Init";
		case ASURA_LOBBY_STATE_MIGRATING_DEADHOST:			return "Migrating Deadhost";
		case ASURA_LOBBY_STATE_MIGRATING_PREGAME:			return "Migrating Pregame";
		case ASURA_LOBBY_STATE_REGISTERING:					return "Registering";
		case ASURA_LOBBY_STATE_INITIALISING_GAME:			return "Initialising Game";
		case ASURA_LOBBY_STATE_STARTING_GAME:				return "Starting Game";
		case ASURA_LOBBY_STATE_PLAYING_GAME:				return "Playing Game";
		case ASURA_LOBBY_STATE_ENDING_GAME:					return "Ending Game";
		case ASURA_LOBBY_STATE_TERMINATING:					return "Terminating";
		default:											return "Unknown";
	}
}
#else
#define ASURA_LOBBYSYSTEM_TRACE(x)	//  ignore
#endif


// C L A S S   I M P L E M E N T A T I O N ///////////////////////////////////
void Asura_Lobby_System::Initialise()
{
	Asura_Assert(!s_bInitialised, "already initialised");

#ifdef _DEBUG
	Asura_ReliableSocket_UnitTest();
#endif
	
	s_eLobbyState = ASURA_LOBBY_STATE_INVALID;
	s_iNetworkDisconnectReason = ASURA_NDR_NOTDISCONNECTED;
	s_eSearchState = ASURA_LOBBY_SEARCHSTATE_NOT_SEARCHING;
	s_eHostMigrationState = ASURA_LOBBY_HOST_MIGRATIONSTATE_NONE;
	s_eClientMigrationState = ASURA_LOBBY_CLIENT_MIGRATIONSTATE_NONE;

	s_pxSearchCriteria = new Asura_Lobby_SearchCriterion[ASURA_LOBBY_MAX_SEARCH_CRITERIA];
	s_pxSearchResults = new Asura_Lobby_ListEntry[ASURA_LOBBY_MAX_SEARCH_RESULTS];

	Platform_Initialise();
	Asura_Assert(s_awcLocalPlayerName[0] != 0, "Platform_Initialise() didn't set local player name");

#ifdef ASURA_USE_MERIT_SYSTEM
	Platform_InitialiseMerit();
	Asura_OutputToDebugger_Retail(("Lobby system using player skill = %d\n", GetLocalPlayerSkill()));
#endif

	Asura_FriendsList::Initialise();

	s_acGameAddress[0] = 0;

	s_bInitialised = true;
	s_bLocalClientReceivedFirstUpdate = false;
	s_bPlayerJoinedSinceLastUpdate = false;
}

void Asura_Lobby::AddConsoleCmdsAndVars()
{
	if(!Asura_CommandConsole::VarExists(L"Asura.Lobby.NumTeams"))
	{
		Asura_CommandConsole::AddVar( L"Asura.Lobby.NumTeams", &s_uNumTeams, 0, INT_MAX, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::MakeVarReadOnly(L"Asura.Lobby.NumTeams");
	}

}

void Asura_Lobby_System::AddConsoleCmdsAndVars()
{
	if(!Asura_CommandConsole::CmdExists(L"Asura.Lobby.IsEndingGame"))
	{
		Asura_Lobby::AddConsoleCmdsAndVars();

		Asura_CommandConsole::AddCmd( L"Asura.Lobby.IsStartingGame",			ConsoleCmd_IsStartingGame,			Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddCmd( L"Asura.Lobby.IsEndingGame",				ConsoleCmd_IsEndingGame,			Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddCmd( L"Asura.Lobby.IsCreating",				ConsoleCmd_IsCreating,				Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddCmd( L"Asura.Lobby.IsJoining",					ConsoleCmd_IsJoining,				Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddCmd( L"Asura.Lobby.IsPreGame",					ConsoleCmd_IsPreGame,				Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddCmd( L"Asura.Lobby.IsSearchComplete",			ConsoleCmd_IsSearchComplete,		Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddCmd( L"Asura.Lobby.IsMigrating",				ConsoleCmd_IsMigrating,				Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddCmd( L"Asura.Lobby.IsError",					ConsoleCmd_IsError,					Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddCmd( L"Asura.Lobby.IsTerminating",				ConsoleCmd_IsTerminating,			Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddCmd( L"Asura.Lobby.PrintErrorCode",			ConsoleCmd_PrintErrorCode,			Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddCmd( L"Asura.Lobby.IsHost",					ConsoleCmd_IsHost,					Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddCmd( L"Asura.Lobby.IsJoiningGameInProgress",	ConsoleCmd_IsJoiningGameInProgress,	Asura_CommandConsole::REMOVE_AT_SHUTDOWN );


		// player access functions
		Asura_CommandConsole::AddCmd( L"Asura.Lobby.GetPlayerName",			GetPlayerName,							Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddCmd( L"Asura.Lobby.IsPlayerReady",			GetPlayerReadyStatus,					Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddCmd( L"Asura.Lobby.IsPlayerNotReady",		GetPlayerNotReadyStatus,				Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddCmd( L"Asura.Lobby.IsPlayerAFriend",		GetPlayerIsFriend,						Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddCmd( L"Asura.Lobby.IsPlayerTalking",		GetPlayerIsTalking,						Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddCmd( L"Asura.Lobby.DoPlayerListAction",	DoPlayerListAction,						Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddCmd( L"Asura.Lobby.InviteFriends",			Platform_ConsoleCmd_LobbyInviteFriends,	Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddCmd( L"Asura.Lobby.IsPlayerMicAttached",	GetPlayerMicrophoneAttached,			Asura_CommandConsole::REMOVE_AT_SHUTDOWN );

		// team versions of player functions - templated to allow easy addition of XXX_Team0, XXX_Team1 etc.
		AddTeamConsoleCmdsAndVars<Asura_Lobby_Team0>();
#if ASURA_LOBBY_MAX_TEAMS > 1
		AddTeamConsoleCmdsAndVars<Asura_Lobby_Team1>();
#if ASURA_LOBBY_MAX_TEAMS > 2
		AddTeamConsoleCmdsAndVars<Asura_Lobby_Team2>();
#if ASURA_LOBBY_MAX_TEAMS > 3
		AddTeamConsoleCmdsAndVars<Asura_Lobby_Team3>();
#endif//3
#endif//2
#endif//1

		// actions
		Asura_CommandConsole::AddCmd( L"Asura.Lobby.ToggleReady",			ToggleReadyStatus,				Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddCmd( L"Asura.Lobby.LeaveLobby",			LeaveLobby,						Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddCmd( L"Asura.Lobby.OpenFriendsList",		OpenFriendsList,				Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddCmd( L"Asura.Lobby.CancelInvitation",		ConsoleCmd_CancelInvitation,	Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddCmd( L"Asura.Lobby.UseRankedMatch",		ConsoleCmd_UseRankedMatch,		Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddCmd( L"Asura.Lobby.AllowJoinInProgress",	ConsoleCmd_AllowJoinInProgress,	Asura_CommandConsole::REMOVE_AT_SHUTDOWN );

		// variables

		// JG - 19 March 2009 - Careful, these variables need their limits set before setting the remove parameter. The compiler doesn't help you here.
		Asura_CommandConsole::AddVar( L"Asura.Lobby.NumSearchResults",		&s_uNumSearchResults,			0,			0x7fffffff,			Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddVar( L"Asura.Lobby.ErrorCode",				&s_iNetworkDisconnectReason,	0,			0xffffffff,			Asura_CommandConsole::REMOVE_AT_SHUTDOWN );

		Asura_CommandConsole::AddVar( L"Asura.Lobby.InvitePending",			&s_bInvitationPending,			Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddVar( L"Asura.Lobby.InvitedToGame",			&s_bInvitedToGame,				Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddVar( L"Asura.Lobby.JoiningGameInProgress",	&s_bJoiningGameInProgress,		Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::MakeVarReadOnly( L"Asura.Lobby.NumSearchResults" );
		Asura_CommandConsole::MakeVarReadOnly( L"Asura.Lobby.ErrorCode" );
		Asura_CommandConsole::MakeVarReadOnly( L"Asura.Lobby.InvitePending" );
		Asura_CommandConsole::MakeVarReadOnly( L"Asura.Lobby.InvitedToGame" );
		Asura_CommandConsole::MakeVarReadOnly( L"Asura.Lobby.JoiningGameInProgress" );

#ifdef ASURA_SUPPORT_LEADERBOARDS
		Asura_Leaderboard_System::AddConsoleCmdsAndVars();
#endif

#if defined(ASURA_USE_DEBUG_TEXT)
		Asura_CommandConsole::AddVar( L"Asura.Lobby.ShowDebug",				&s_bShowDebugOutput,				Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
#endif
	}
}


// templated function to add console commands for team0, team1 etc.
template<typename T> void Asura_Lobby_System::AddTeamConsoleCmdsAndVars()
{
	Asura_UnicodeChar awcBuffer[256];

	Asura_PrintfToBfr( awcBuffer, L"%S%d", 256, true, "Asura.Lobby.GetPlayerNameTeam", T::ucTEAM_NUM );

	if(!Asura_CommandConsole::CmdExists(awcBuffer))
	{
		Asura_CommandConsole::AddCmd( awcBuffer, GetPlayerNameByTeam<T>, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );

		Asura_PrintfToBfr( awcBuffer, L"%S%d", 256, true, "Asura.Lobby.IsPlayerReadyTeam", T::ucTEAM_NUM );
		Asura_CommandConsole::AddCmd( awcBuffer, GetPlayerReadyStatusByTeam<T>, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );

		Asura_PrintfToBfr( awcBuffer, L"%S%d", 256, true, "Asura.Lobby.IsPlayerNotReadyTeam", T::ucTEAM_NUM );
		Asura_CommandConsole::AddCmd( awcBuffer, GetPlayerNotReadyStatusByTeam<T>, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );

		Asura_PrintfToBfr( awcBuffer, L"%S%d", 256, true, "Asura.Lobby.IsPlayerAFriendTeam", T::ucTEAM_NUM );
		Asura_CommandConsole::AddCmd( awcBuffer, GetPlayerIsFriendByTeam<T>, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );

		Asura_PrintfToBfr( awcBuffer, L"%S%d", 256, true, "Asura.Lobby.IsPlayerTalkingTeam", T::ucTEAM_NUM );
		Asura_CommandConsole::AddCmd( awcBuffer, GetPlayerIsTalkingByTeam<T>, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );

		Asura_PrintfToBfr( awcBuffer, L"%S%d", 256, true, "Asura.Lobby.DoPlayerListActionTeam", T::ucTEAM_NUM );
		Asura_CommandConsole::AddCmd( awcBuffer, DoPlayerListActionByTeam<T>, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );

		Asura_PrintfToBfr( awcBuffer, L"%S%d", 256, true, "Asura.Lobby.GetTeamName", T::ucTEAM_NUM );
		Asura_CommandConsole::AddCmd( awcBuffer, GetTeamName<T>, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
	}

}

void Asura_Lobby_System::UpdateDebugOutput()
{
#if defined(ASURA_USE_DEBUG_TEXT)
	if (s_bInitialised && s_bShowDebugOutput && s_pxLobby)
	{
		u_int uX( 5 );
		u_int uY( 25 );
		Asura_DebugText::AddTextBlock(uX, uY++, 0xFFFFFFFF, 0, "State: %d", s_eLobbyState );
		Asura_DebugText::AddTextBlock(uX, uY++, 0xFFFFFFFF, 0, "Game State: %d", s_pxLobby->GetGameState() );

		for (u_int u = 0; u < s_pxLobby->GetNumPlayers(); u++)
		{
			const Asura_Lobby_Player& xPlayer = s_pxLobby->GetPlayerByIndex( u );
			if (xPlayer.GetTimer() != 0.0f)
			{
				Asura_DebugText::AddTextBlock(uX, uY++, 0xFFFFFFFF, 0, "%d: %S - Team %d - GUID %d - Voice Channels 0x%02x - Host Score %d %s %s %s - %.2fs", 
					xPlayer.GetID().AsInt(), xPlayer.GetName(), xPlayer.GetTeamIndex(), xPlayer.GetClientGuid(), xPlayer.GetVoiceChannels(), xPlayer.DebugGetHostScore(), 
					xPlayer.IsLocalPlayer() ? "- Local" : "", (xPlayer.GetID() == s_pxLobby->GetHostID()) ? "- Host" : "",
					xPlayer.IsMicrophoneAttached() ? "(mic)" : "",
					xPlayer.GetTimer());
			}
			else
			{
				Asura_DebugText::AddTextBlock(uX, uY++, 0xFFFFFFFF, 0, "%d: %S - Team %d - GUID %d - Voice Channels 0x%02x - Host Score %d %s %s %s", 
					xPlayer.GetID().AsInt(), xPlayer.GetName(), xPlayer.GetTeamIndex(), xPlayer.GetClientGuid(), xPlayer.GetVoiceChannels(), xPlayer.DebugGetHostScore(), 
					xPlayer.IsLocalPlayer() ? "- Local" : "", (xPlayer.GetID() == s_pxLobby->GetHostID()) ? "- Host" : "",
					xPlayer.IsMicrophoneAttached() ? "(mic)" : "");
			}
		}

		if (IsMigrating())
		{
			uY++;
			Asura_DebugText::AddTextBlock(uX, uY++, 0xFFFFFFFF, 0, "Host Migration State: %d", s_eHostMigrationState );
			Asura_DebugText::AddTextBlock(uX, uY++, 0xFFFFFFFF, 0, "Client Migration State: %d", s_eClientMigrationState );
		}

		uY++;
		if (s_pxHostSocket && s_pxHostSocket->IsActive()) Asura_DebugText::AddTextBlock(uX, uY++, 0xFFFFFFFF, 0, "Host socket active : %d connections", s_pxHostSocket->GetNumConnections());
		if (s_pxClientSocket && s_pxClientSocket->IsActive()) Asura_DebugText::AddTextBlock(uX, uY++, 0xFFFFFFFF, 0, "Client socket %s", s_pxClientSocket->IsConnected() ? "connected" : "active" );

		uY++;
		Asura_DebugText::AddTextBlock( uX, uY++, 0xFFFFFFFF, 0, "Local Player Active Voice Channel: 0x%02x", 1 << s_ucActiveVoiceChannel );

		uY++;
		Platform_UpdateDebugOutput( uX, uY );
	}
#endif	// defined(ASURA_USE_DEBUG_TEXT)
}

void Asura_Lobby_System::Update()
{
	// if not initialised, still call Platform_Update().
	Platform_Update();

	// If we are ready to accept an invitation, join the relevant lobby.
	// This assumes that s_bInvitationPending only gets set to true after some form of user confirmation
	// i.e. accepting the invitation. The game code will still need to ensure there is an additional 
	// confirmation before leaving an existing game, but if the lobby system is initialised and the state
	// is invalid then there should be no reason not to go ahead and join.
	if (s_bInitialised && s_bInvitationPending)
	{
		if (s_eLobbyState == ASURA_LOBBY_STATE_INVALID)
		{
			s_bInvitationPending = false;
			s_bInvitedToGame = true;
			s_bUseRankedLobbies = false;	// no invitations to ranked games, so ensure we're in the "friendly game" area.
			s_bJoiningGameInProgress = false; // assume we are arriving to the lobby in time to meet with everyone
			Asura_Assert(s_uNumSearchResults == 1, "invitation pending should cause us to have exactly one search result");
			JoinLobby(0);
		}
	}

	if (s_bPlayerJoinedSinceLastUpdate)
	{
		s_bPlayerJoinedSinceLastUpdate = false;
	}

	UpdateNetworkConnection();

	switch (s_eLobbyState)
	{
		case ASURA_LOBBY_STATE_INVALID:							{ break; }
		case ASURA_LOBBY_STATE_CREATING:						{ ExecuteState_Creating();						break; }
		case ASURA_LOBBY_STATE_PRE_GAME:						{ ExecuteState_PreGame();						break; }
		case ASURA_LOBBY_STATE_JOINING_PLATFORM_EARLY_INIT:		{ ExecuteState_Joining_PlatformEarlyInit(); 	break; }
		case ASURA_LOBBY_STATE_JOINING_WAIT_FOR_CONNECTION:		{ ExecuteState_Joining_WaitForConnection(); 	break; }
		case ASURA_LOBBY_STATE_JOINING_PLATFORM_LATE_INIT:		{ ExecuteState_Joining_PlatformLateInit();		break; }
		case ASURA_LOBBY_STATE_MIGRATING_DEADHOST:				//Fall thru
		case ASURA_LOBBY_STATE_MIGRATING_PREGAME:				{ ExecuteState_Migrating();						break; }
		case ASURA_LOBBY_STATE_REGISTERING:						{ ExecuteState_Registering();					break; }
		case ASURA_LOBBY_STATE_STARTING_GAME:					{ ExecuteState_StartingGame();					break; }
		case ASURA_LOBBY_STATE_INITIALISING_GAME:				{ ExecuteState_InitialisingGame();				break; }
		case ASURA_LOBBY_STATE_PLAYING_GAME:					{ ExecuteState_PlayingGame();					break; }
		case ASURA_LOBBY_STATE_ENDING_GAME:						{ ExecuteState_EndingGame();					break; }
		case ASURA_LOBBY_STATE_TERMINATING:						{ ExecuteState_Terminating();					break; }

		default:
		{
			Asura_Assert(false, "invalid lobby state");
			break;
		}
	}

	switch (s_eSearchState)
	{
		case ASURA_LOBBY_SEARCHSTATE_NOT_SEARCHING:				{ break; }
		case ASURA_LOBBY_SEARCHSTATE_SEARCHING:					{ ExecuteSearchState_Searching();		break; }
		case ASURA_LOBBY_SEARCHSTATE_SEARCH_COMPLETE:			{ break; }
		default:
		{
			Asura_Assert(false, "invalid lobby state");
			break;
		}
	}

	UpdateDebugOutput();
}

void Asura_Lobby_System::Shutdown()
{
	if (s_pxLobby)
	{
		delete s_pxLobby;
		s_pxLobby = NULL;
	}

	/*Terminate()*/
	Platform_Terminate();

	Asura_FriendsList::Shutdown();

	delete[] s_pxSearchCriteria;
	s_pxSearchCriteria = NULL;
	s_uNumSearchCriteria = 0;
	delete[] s_pxSearchResults;
	s_pxSearchResults = NULL;
	s_uNumSearchResults = 0;

	s_bInvitationPending = false;
	s_bInvitedToGame = false;
	s_eLobbyState = ASURA_LOBBY_STATE_INVALID;
	s_eSearchState = ASURA_LOBBY_SEARCHSTATE_NOT_SEARCHING;
	s_bInitialised = false;

	//By this point the sockets should be closed and the lobby deleted, but check here to be sure.
	if (s_pxClientSocket)
	{
		if (s_pxClientSocket->IsConnected())
		{
			s_pxClientSocket->Disconnect( true, ASURA_RS_DISCONNECT_SHUTDOWN );
		}
		delete s_pxClientSocket;
		s_pxClientSocket = NULL;
	}
	if (s_pxHostSocket)
	{
		if (s_pxHostSocket->IsActive())
		{
			s_pxHostSocket->Close();
		}
		delete s_pxHostSocket;
		s_pxHostSocket = NULL;
	}

	Platform_Shutdown();

#ifdef ASURA_USE_MERIT_SYSTEM
	// in case we didn't go through the EndingGame state, shut down the merit system here.
	Asura_Merit_System::Shutdown();
#endif
}

bool Asura_Lobby_System::IsHost()
{
	return (s_pxHostSocket && s_pxHostSocket->IsActive());
}

bool Asura_Lobby_System::IsMigrating()
{
	return ((s_eLobbyState == ASURA_LOBBY_STATE_MIGRATING_DEADHOST) || (s_eLobbyState == ASURA_LOBBY_STATE_MIGRATING_PREGAME));
}

bool Asura_Lobby_System::ConsoleCmd_IsHost( bool bHelp )
{
	if (!bHelp)
	{
		Asura_CommandConsole::Printf( IsHost() ? "1" : "0" );
	}
	return true;
}

bool Asura_Lobby_System::ConsoleCmd_IsJoiningGameInProgress( bool bHelp )
{
	if (!bHelp)
	{
		Asura_CommandConsole::Printf( s_bJoiningGameInProgress ? "1" : "0" );
	}
	return true;
}

bool Asura_Lobby_System::ConsoleCmd_IsMigrating( bool bHelp )
{
	if (!bHelp)
	{
		// ASURA_LOBBY_STATE_MIGRATING_PREGAME is counted as starting game (so query ConsoleCmd_IsStartingGame())
		bool bStartingGame = (s_eLobbyState == ASURA_LOBBY_STATE_MIGRATING_DEADHOST);
		Asura_CommandConsole::Printf( bStartingGame ? "1" : "0" );
	}
	return true;
}

bool Asura_Lobby_System::ConsoleCmd_IsStartingGame( bool bHelp )
{
	if (!bHelp)
	{
		bool bStartingGame = ((s_eLobbyState == ASURA_LOBBY_STATE_STARTING_GAME) 
			|| (s_eLobbyState == ASURA_LOBBY_STATE_INITIALISING_GAME) 
			|| (s_eLobbyState == ASURA_LOBBY_STATE_PLAYING_GAME)
			|| (s_eLobbyState == ASURA_LOBBY_STATE_REGISTERING)
			|| (s_eLobbyState == ASURA_LOBBY_STATE_MIGRATING_PREGAME));
		Asura_CommandConsole::Printf( bStartingGame ? "1" : "0" );
	}
	return true;
}

bool Asura_Lobby_System::ConsoleCmd_IsEndingGame( bool bHelp )
{
	if (!bHelp)
	{
		Asura_CommandConsole::Printf( s_eLobbyState == ASURA_LOBBY_STATE_ENDING_GAME ? "1" : "0" );
	}
	return true;
}

bool Asura_Lobby_System::ConsoleCmd_IsCreating( bool bHelp )
{
	if (!bHelp)
	{
		Asura_CommandConsole::Printf( (s_eLobbyState == ASURA_LOBBY_STATE_CREATING) ? "1" : "0" );
	}
	return true;
}

bool Asura_Lobby_System::ConsoleCmd_IsJoining( bool bHelp )
{
	if (!bHelp)
	{
		Asura_CommandConsole::Printf( (s_eLobbyState >= ASURA_LOBBY_STATE_JOINING_PLATFORM_EARLY_INIT && 
			s_eLobbyState <= ASURA_LOBBY_STATE_JOINING_PLATFORM_LATE_INIT) ? "1" : "0" );
	}
	return true;
}

bool Asura_Lobby_System::ConsoleCmd_IsPreGame( bool bHelp )
{
	if (!bHelp)
	{
		Asura_CommandConsole::Printf( s_eLobbyState == ASURA_LOBBY_STATE_PRE_GAME ? "1" : "0" );
	}
	return true;
}

bool Asura_Lobby_System::ConsoleCmd_IsSearchComplete( bool bHelp )
{
	if (!bHelp)
	{
		Asura_CommandConsole::Printf( s_eSearchState == ASURA_LOBBY_SEARCHSTATE_SEARCH_COMPLETE ? "1" : "0" );
	}
	return true;
}

bool Asura_Lobby_System::ConsoleCmd_IsError( bool bHelp )
{
	if (!bHelp)
	{
		Asura_CommandConsole::Printf( Asura_Lobby_System::GetErrorCode() != ASURA_NDR_NOTDISCONNECTED ? "1" : "0" );
	}
	return true;
}

bool Asura_Lobby_System::ConsoleCmd_IsTerminating( bool bHelp )
{
	if (!bHelp)
	{
		Asura_CommandConsole::Printf( Asura_Lobby_System::GetState() == ASURA_LOBBY_STATE_TERMINATING ? "1" : "0" );
	}
	return true;
}

bool Asura_Lobby_System::ConsoleCmd_PrintErrorCode( bool bHelp )
{
	if (!bHelp)
	{
		Project_PrintErrorCodeToConsole();
	}
	return true;
}

bool Asura_Lobby_System::SetupHostPort()
{
	if (!s_pxHostSocket) 
	{
		s_pxHostSocket = Asura_ReliableSocket_Listener::Create();
		if (s_pxHostSocket)
		{
			s_pxHostSocket->SetCallback_Message(ServerMessageHandler);
			s_pxHostSocket->SetCallback_Connection(PlayerJoined);
			s_pxHostSocket->SetCallback_Disconnection(PlayerLeft);
		}
	}

	Asura_NetPort xPort( s_uListenTCPPort );
	if( s_pxHostSocket && s_pxHostSocket->Listen(xPort) )
	{
		return true;
	}

	return false;
}

void Asura_Lobby_System::CreateLobby( u_int uMaxPlayers, u_char ucNumTeams, const Asura_Lobby_Attributes& xAttributes )
{
	Asura_Assert(Asura_NetLinkManager::IsConnected(), "Asura_Lobby_System::CreateLobby() called before NetLink was established");

	Asura_Assert(s_eLobbyState == ASURA_LOBBY_STATE_INVALID, "invalid state");
	if (s_eLobbyState == ASURA_LOBBY_STATE_INVALID)
	{
		s_iNetworkDisconnectReason = ASURA_NDR_NOTDISCONNECTED;
		s_bLocalPlayerReady = false;
		s_bLocalPlayerHasMicrophone = false;
		s_bRegistered = false;
		// Initialise the players requested team to invalid. 
		// Players are assigned a team when they are added to the lobby.
		// s_ucLocalPlayerRequestedTeam only changes if the player requests a change of team.
		s_ucLocalPlayerRequestedTeam = ASURA_LOBBY_INVALID_TEAM;

		Asura_Assert(uMaxPlayers <= ASURA_LOBBY_MAX_PLAYERS, "too many players");

		if (!s_pxClientSocket)
		{
			s_pxClientSocket = Asura_ReliableSocket_Client::Create();
			Asura_Assert( s_pxClientSocket, "Platform did not create a socket for the lobby system");
			if( s_pxClientSocket )
			{
				s_pxClientSocket->SetCallback_Message(ClientMessageHandler);
				s_pxClientSocket->SetCallback_Disconnection(ClientDisconnected);
			}
		}
		bool bSocketError = !SetupHostPort();

		// local client is connected in AddLocalPlayerToLobby

		if (bSocketError)
		{
			Asura_Assert(false, "Error creating lobby sockets");

			s_iNetworkDisconnectReason = ASURA_NDR_RELIABLESOCKET_ERROR;
			ChangeState( ASURA_LOBBY_STATE_INVALID );
		}
		else
		{
			Asura_Assert(!s_pxLobby, "lobby data wasn't freed - memory leak");
			s_pxLobby = new Asura_Lobby(uMaxPlayers, xAttributes);
			s_pxLobby->SetNumTeams( ucNumTeams );
			s_pxLobby->ClearDataChangedFlag();

			Platform_CreateLobby(true);

			ChangeState(ASURA_LOBBY_STATE_CREATING);

#ifdef ASURA_ENABLE_QOS_SYSTEM
			Asura_Qos_System::CreateLobby(true);
#endif //#ifdef ASURA_ENABLE_QOS_SYSTEM
		}
	}
}

void Asura_Lobby_System::SearchForLobby( Asura_Lobby_SearchCriterion* pxSearchCriteria, u_int uNumSearchCriteria )
{
	Asura_Assert(Asura_NetLinkManager::IsConnected(), "Asura_Lobby_System::SearchForLobby() called before NetLink was established");

	Asura_Assert(s_eSearchState == ASURA_LOBBY_SEARCHSTATE_NOT_SEARCHING ||
		s_eSearchState == ASURA_LOBBY_SEARCHSTATE_SEARCH_COMPLETE, "invalid state");
	if (s_eSearchState == ASURA_LOBBY_SEARCHSTATE_NOT_SEARCHING || 
		s_eSearchState == ASURA_LOBBY_SEARCHSTATE_SEARCH_COMPLETE)
	{
		// make a copy of the search criteria
		Asura_Assert(uNumSearchCriteria <= ASURA_LOBBY_MAX_SEARCH_CRITERIA, "too many search criteria");
		uNumSearchCriteria = Asura_Maths::Min(uNumSearchCriteria, u_int(ASURA_LOBBY_MAX_SEARCH_CRITERIA));
		for (u_int u = 0; u < uNumSearchCriteria; ++u)
		{
			s_pxSearchCriteria[u] = pxSearchCriteria[u];
		}
		s_uNumSearchCriteria = uNumSearchCriteria;

		// and switch to the searching state
		ChangeSearchState(ASURA_LOBBY_SEARCHSTATE_SEARCHING);
	}
}

void Asura_Lobby_System::CancelSearch()
{
	if (s_eSearchState == ASURA_LOBBY_SEARCHSTATE_SEARCHING)
	{
		Platform_CancelSearch();
	}
	s_eSearchState = ASURA_LOBBY_SEARCHSTATE_NOT_SEARCHING;
}

void Asura_Lobby_System::JoinLobby( u_int uIndexOfSearchResult )
{
	Asura_Assert(Asura_NetLinkManager::IsConnected(), "Asura_Lobby_System::JoinLobby() called before NetLink was established");

	s_eSearchState = ASURA_LOBBY_SEARCHSTATE_NOT_SEARCHING;
	s_iNetworkDisconnectReason = ASURA_NDR_NOTDISCONNECTED;

	if (uIndexOfSearchResult < s_uNumSearchResults)
	{
		s_bLocalPlayerReady = false;
		s_bLocalPlayerHasMicrophone = false;
		s_bRegistered = false;
		// Initialise the players requested team to invalid. 
		// Players are assigned a team  when they are added to the lobby.
		// s_ucLocalPlayerRequestedTeam only changes if the player requests a change of team.
		s_ucLocalPlayerRequestedTeam = ASURA_LOBBY_INVALID_TEAM;

		s_uIndexOfLobbyToJoin = uIndexOfSearchResult;

		// create lobby here
		const Asura_Lobby_ListEntry& xLobbyInfo = s_pxSearchResults[uIndexOfSearchResult];
		s_pxLobby = new Asura_Lobby(xLobbyInfo.m_uMaxPlayers, xLobbyInfo.m_xAttributes);

		if (!s_pxClientSocket)
		{
			s_pxClientSocket = Asura_ReliableSocket_Client::Create();
			s_pxClientSocket->SetCallback_Message(ClientMessageHandler);
			s_pxClientSocket->SetCallback_Disconnection(ClientDisconnected);
		}

		// change the state
		ChangeState(ASURA_LOBBY_STATE_JOINING_PLATFORM_EARLY_INIT);
	}
	else
	{
		Asura_Assert(false, "join lobby failed");
		Terminate(ASURA_NDR_UNKNOWN);
	}
}

bool Asura_Lobby_System::LeaveLobby( bool bHelp )
{
	if (!bHelp)
	{
		Terminate(ASURA_NDR_NOTDISCONNECTED);
	}
	return true;
}

void Asura_Lobby_System::ServerMessageHandler(const Asura_ReliableConnectionID& xID, u_short usMsgID, u_short usMsgBodySize, const char* pcMsgBody)
{
	if (s_eLobbyState == ASURA_LOBBY_STATE_INVALID)
	{
		return;
	}

	//Ignore messages from clients while migrating (they must be old)
	if (IsMigrating())
	{
		return;
	}

	if (!IsHost() || !s_pxLobby)
	{
		Asura_Assert(false, "Server message handler receiving message in inappropriate state");
	}
	else
	{
		switch (usMsgID)
		{
			case ASURA_MSG_LOBBY_CLIENTSTATUS:
			{
				const Asura_Lobby_ClientStatusMsg* pxClientStatus = Asura_ReliableSocket_CheckAndExtractData<Asura_Lobby_ClientStatusMsg>( pcMsgBody, usMsgBodySize );
				ProcessClientStatus( xID, pxClientStatus);
				break;
			}

			default:
			{
				Asura_Assert(false, "unexpected message recived by lobby host");
				break;
			}
		}
	}
}

void Asura_Lobby_System::ClientMessageHandler(u_short usMsgID, u_short usMsgBodySize, const char* pcMsgBody)
{
	if (s_eLobbyState == ASURA_LOBBY_STATE_INVALID)
	{
		return;
	}

	if (!s_pxLobby)
	{
		//If we're in a state other than invalid, we may be receiving old messages (which we can safely ignore).
		Asura_Assert(s_eLobbyState != ASURA_LOBBY_STATE_INVALID, "Client message handler receiving message in inappropriate state");
	}
	else if (!IsHost())
	{
		switch (usMsgID)
		{
			case ASURA_MSG_LOBBY_UPDATE:
			{
				//The first update marks the point at which the client has a complete player list, and so is considered connected.
				if (!s_bLocalClientReceivedFirstUpdate) s_bLocalClientReceivedFirstUpdate = true;
				const Asura_Lobby_LobbyData* pxLobbyData = Asura_ReliableSocket_CheckAndExtractData<Asura_Lobby_LobbyData>( pcMsgBody, usMsgBodySize );
				if (pxLobbyData) ProcessStatusUpdate( pxLobbyData );
				break;
			}

			case ASURA_MSG_LOBBY_REMOVE_PLAYER:
			{
				if (IsMigrating()) return; //Ignore old messages while migrating
				const Asura_ReliableConnectionID* pxID = Asura_ReliableSocket_CheckAndExtractData<Asura_ReliableConnectionID>( pcMsgBody, usMsgBodySize );
				if (pxID) s_pxLobby->RemovePlayer( *pxID );
				break;
			}

			case ASURA_MSG_LOBBY_PLAYER_DATA:
			{
				const Asura_Lobby_PlayerData* pxPlayerData = Asura_ReliableSocket_CheckAndExtractData<Asura_Lobby_PlayerData>( pcMsgBody, usMsgBodySize );
				if (pxPlayerData)
				{
					if (IsMigrating())
					{
						Asura_Lobby_Player* pxPlayer = s_pxLobby->FindPlayer( *pxPlayerData );
						if (pxPlayer)
						{
							//New ID for existing player, but update all data in case the new servers differed
							pxPlayer->Initialise( pxPlayerData );
							break;
						}
					}

					//New player
					Asura_Assert(!Asura_Lobby_System::IsHost(),"Should only be called on client");
					s_pxLobby->AddPlayer( *pxPlayerData );
				}
				break;
			}

			case ASURA_MSG_LOBBY_PLAYER_STATUS:
			{
				if (IsMigrating()) return; //Ignore old messages while migrating
				const Asura_Lobby_PlayerStatusMsg* pxPlayerStatus = Asura_ReliableSocket_CheckAndExtractData<Asura_Lobby_PlayerStatusMsg>( pcMsgBody, usMsgBodySize );
				if (pxPlayerStatus)
				{
					s_pxLobby->UpdatePlayer( pxPlayerStatus );
				}
				break;
			}

			default:
			{
				if (!Platform_ClientMessageHandler(usMsgID, usMsgBodySize, pcMsgBody))
				{
					Asura_Assert(false, "unexpected message recived by lobby client");
				}
				break;
			}
		}
	}
}

bool Asura_Lobby_System::PlayerJoined(const Asura_ReliableConnectionID& xID, u_short usMsgSize, const char* pcMsgBody)
{
	if ((s_eLobbyState == ASURA_LOBBY_STATE_INVALID) || (!s_pxLobby))
	{
		Asura_Assert(false,"Can't act on playerjoined event");
		return false;
	}

	Asura_Assert(IsHost(), "only expected on host");
	if (!IsHost())
	{
		return false;
	}

	if (!s_bLocalClientReceivedFirstUpdate) s_bLocalClientReceivedFirstUpdate = true;

	Asura_Lobby_Player* pxPlayer = NULL;
	switch (usMsgSize)
	{
		case sizeof(Asura_Lobby_GameConnectData):
		{
			Asura_Assert(IsMigrating(),"Somone attempting to migrate outside of host's migration state (took too long?) - refusing connection");
			if (s_eHostMigrationState == ASURA_LOBBY_HOST_MIGRATIONSTATE_STARTING) //The only time when migrating players can connect
			{
				const Asura_Lobby_GameConnectData* pxConnectData = Asura_ReliableSocket_CheckAndExtractData<Asura_Lobby_GameConnectData>( pcMsgBody, usMsgSize );
				if (pxConnectData)
				{
					pxPlayer = s_pxLobby->FindPlayer( *pxConnectData );
					Asura_Assert(pxPlayer,"Could not find re-connecting player. Refusing connection");
					if (pxPlayer)
					{
						pxPlayer->SetConnectionID( xID );
					}
				}
			}
			break;
		}
		case sizeof(Asura_Lobby_ConnectData):
		{
			//Allow people to join even during migrations. The lobby player counts and 360/PS3/PC-NS should all be correct. Steamworks TBD.
			const Asura_Lobby_ConnectData* pxConnectData = Asura_ReliableSocket_CheckAndExtractData<Asura_Lobby_ConnectData>( pcMsgBody, usMsgSize );
			if (pxConnectData)
			{
				const bool bInvited = false;// TODO invitations
				static u_int ls_uNextTeam = 0;
				// allocate teams in round robin fashion
				const u_int uNumTeams = s_pxLobby->GetNumTeams();
				const u_char ucTeam = static_cast<u_char>(ls_uNextTeam);
				if (uNumTeams > 0)
				{
					ls_uNextTeam = (ls_uNextTeam + 1) % uNumTeams;
				}

				//Add the player locally
				const Asura_Lobby_PlayerData xData(xID, *pxConnectData, bInvited, ucTeam);
				pxPlayer = s_pxLobby->AddPlayer( xData );
				Asura_Assert(pxPlayer,"Couldn't add joining player");
				if (pxPlayer)
				{
					//Mark PS3 as needing to update the owner priority list
					s_bPlayerJoinedSinceLastUpdate = true;
				}
			}
			break;
		}
		default:
		{
			Asura_Assertf(false,("Unknown lobby connect data (size %d) - refusing connection", usMsgSize));
			return false;
		}
	}

	if (pxPlayer && pxPlayer->IsLocalPlayer())
	{
		s_pxLobby->SetHostID( pxPlayer->GetID() );
	}

	//If we have a player at this point, allow their connection.
	return (pxPlayer != NULL);
}

void Asura_Lobby_System::ChangeState( ASURA_LOBBY_STATE eNewState )
{
	ASURA_LOBBYSYSTEM_TRACE(("Changed state to '%s'\n", LobbyStateToString( eNewState ) ));

	if (eNewState != s_eLobbyState)
	{
		// Exit the current state 
		switch ( s_eLobbyState )
		{
			case ASURA_LOBBY_STATE_TERMINATING:						{ ExitState_Terminating();						break; } // Terminating state needs to make sure the host socket is closed
			default:												{												break; }
		}

		// Enter the new state
		switch (eNewState)
		{
			case ASURA_LOBBY_STATE_INVALID:							{ break; }
			case ASURA_LOBBY_STATE_CREATING:						{ EnterState_Creating();						break; }
			case ASURA_LOBBY_STATE_PRE_GAME:						{ EnterState_PreGame();							break; }
			case ASURA_LOBBY_STATE_JOINING_PLATFORM_EARLY_INIT:		{ EnterState_Joining_PlatformEarlyInit(); 		break; }
			case ASURA_LOBBY_STATE_JOINING_WAIT_FOR_CONNECTION:		{ EnterState_Joining_WaitForConnection(); 		break; }
			case ASURA_LOBBY_STATE_JOINING_PLATFORM_LATE_INIT:		{ EnterState_Joining_PlatformLateInit(); 		break; }
			case ASURA_LOBBY_STATE_MIGRATING_PREGAME:				//Fall thru
			case ASURA_LOBBY_STATE_MIGRATING_DEADHOST:				{ EnterState_Migrating();						break; }
			case ASURA_LOBBY_STATE_REGISTERING:						{ EnterState_Registering();						break; }
			case ASURA_LOBBY_STATE_INITIALISING_GAME:				{ EnterState_InitialisingGame();				break; }
			case ASURA_LOBBY_STATE_STARTING_GAME:					{ EnterState_StartingGame();					break; }
			case ASURA_LOBBY_STATE_PLAYING_GAME:					{ EnterState_PlayingGame();						break; }
			case ASURA_LOBBY_STATE_ENDING_GAME:						{ EnterState_EndingGame();						break; }
			case ASURA_LOBBY_STATE_TERMINATING:						{ EnterState_Terminating();						break; }
			default:
			{
				Asura_Assert(false, "invalid lobby state");
				break;
			}
		}

		s_eLobbyState = eNewState;
	}
}

void Asura_Lobby_System::Terminate( int iNetworkDisconnectionReason )
{
	ASURA_LOBBYSYSTEM_TRACE(( "Terminating, reason: %i\n", iNetworkDisconnectionReason ));
	// assert so the error is noticed - it's likely to be unexpected and we need 
	// to check that an appropriate error is displayed to the player.
	Asura_Assert(iNetworkDisconnectionReason == ASURA_NDR_NOTDISCONNECTED || 
		iNetworkDisconnectionReason == ASURA_NDR_SERVERQUIT, "Asura_Lobby_System::Terminate called");

	s_iNetworkDisconnectReason = iNetworkDisconnectionReason;

#ifdef ASURA_ENABLE_QOS_SYSTEM
	Asura_Qos_System::LobbyTerminated();
#endif //ASURA_ENABLE_QOS_SYSTEM

	//If we're playing the game, just set the disconnect reason so the project can detect it and exit correctly [by calling CompletedGame(), EndGame()]
	if (s_eLobbyState != ASURA_LOBBY_STATE_PLAYING_GAME)
	{
		if (s_pxLobby)
		{
			ChangeState(ASURA_LOBBY_STATE_TERMINATING);

			if (s_pxHostSocket && s_pxHostSocket->IsActive()) 
			{
				for (u_int u = 0; u < s_pxLobby->GetNumPlayers(); u++)
				{
					s_pxHostSocket->Disconnect( s_pxLobby->GetPlayerByIndex( u ).GetID(), ASURA_RS_DISCONNECT_SHUTDOWN );
				}
			}
			else if (s_pxClientSocket->IsActive())
			{
				s_pxClientSocket->Disconnect( false, ASURA_RS_DISCONNECT_SHUTDOWN );
			}

			delete s_pxLobby;
			s_pxLobby = NULL;
		}

		Platform_Terminate();

		s_bInvitedToGame = false;
	}
}

void Asura_Lobby_System::EnterState_Creating()
{
	s_bPlatformCreateDone = false;
	s_fStateTimer = fASURA_LOBBY_CREATE_TIMEOUT;
}

void Asura_Lobby_System::ExecuteState_Creating()
{
	s_fStateTimer -= Asura_Timers::GetFrameTime();
	if (s_fStateTimer < 0.0f)
	{
		Terminate( ASURA_NDR_UNKNOWN ); //TODO: Reason
	}

	//Currently we assume all lobbies have a local player
	if (!s_bPlatformCreateDone)
	{	
		if (Platform_UpdateLobbyCreation())
		{
			s_bPlatformCreateDone = true;
			AddLocalPlayerToLobby();
		}
	}
	else if (s_bLocalClientReceivedFirstUpdate)
	{
		ChangeState(ASURA_LOBBY_STATE_PRE_GAME);
	}
}

void Asura_Lobby_System::EnterState_PreGame()
{
	s_fUpdateMessageTimer = 0.0f;

	if (IsHost() && s_pxLobby)
	{
		//Required for after a deadhost migration
		s_pxLobby->SetGameState(ASURA_LOBBY_GAMESTATE_INVALID);
	}
	else
	{
		// if client, check for the server having started the game
		s_bJoiningGameInProgress = false; 
		if (s_bAllowJoinInProgress)
		{
			if (s_pxLobby)
			{
				if (s_pxLobby->GetGameState() == ASURA_LOBBY_GAMESTATE_INITIALISING ||
					s_pxLobby->GetGameState() == ASURA_LOBBY_GAMESTATE_STARTING ||
					s_pxLobby->GetGameState() == ASURA_LOBBY_GAMESTATE_PLAYING)
				{
					// the game is already in progress
					s_bJoiningGameInProgress = true; 
				}
			}
		}
	}
}

void Asura_Lobby_System::ExecuteState_PreGame()
{
	const bool bHost = IsHost();

	// if client, check for the server having started the game
	if (!bHost)
	{
		if (s_pxLobby)
		{
			switch (s_pxLobby->GetGameState())
			{
				case ASURA_LOBBY_GAMESTATE_REGISTERING:
				{
					Asura_Assert(s_bUseRankedLobbies, "client not expecting need to register");
					ChangeState(ASURA_LOBBY_STATE_REGISTERING);
					return;
				}
				
				case ASURA_LOBBY_GAMESTATE_MIGRATION:
				{
					Asura_Assert( s_bMigrateOnGameStart, "Host is causing a pre-game migration when client disallows it");
					if (s_bMigrateOnGameStart)
					{
						ChangeState( ASURA_LOBBY_STATE_MIGRATING_PREGAME );
					}
					else
					{
						Terminate( ASURA_NDR_SERVERKICK );
					}
					break;
				}

				case ASURA_LOBBY_GAMESTATE_INITIALISING:
				case ASURA_LOBBY_GAMESTATE_STARTING:
				case ASURA_LOBBY_GAMESTATE_PLAYING:
				{
					Asura_Assert(!s_bUseRankedLobbies, "game started but client did not register, treating as if kicked");
					if (s_bUseRankedLobbies)
					{
						Terminate(ASURA_NDR_SERVERKICK);
					}
					else
					{
						if (!s_bJoiningGameInProgress)
						{
							ChangeState(ASURA_LOBBY_STATE_INITIALISING_GAME);
						}
					}
					return;
				}

				default:
					break;
			}
		}

	}
}

void Asura_Lobby_System::EnterState_Joining_PlatformEarlyInit()
{
	s_fUpdateTimestamp = 0.0f;
	s_fStateTimer = fASURA_LOBBY_CONNECTION_TIMEOUT;
}

void Asura_Lobby_System::ExecuteState_Joining_PlatformEarlyInit()
{
	Asura_NetAddress xAddress;
	Asura_NetPort xPort;

	//Check socket first, don't prepare if we have none.
	if (s_pxClientSocket && Platform_PrepareToJoinLobby(s_uIndexOfLobbyToJoin, xAddress, xPort))
	{
		Asura_Lobby_ConnectData xConnectData;
		xConnectData.Fill();
		s_pxClientSocket->Connect( xAddress, xPort, &xConnectData, sizeof(xConnectData) );
		s_bLocalPlayerDataChanged = false; //We've just sent it.
		ChangeState(ASURA_LOBBY_STATE_JOINING_WAIT_FOR_CONNECTION);

#ifdef ASURA_ENABLE_QOS_SYSTEM
		Asura_Qos_System::SetServerAddress( xAddress );
#endif //ASURA_ENABLE_QOS_SYSTEM
	}
	else if (!s_pxClientSocket)
	{
		//Terminate straight away
		Asura_Assert(false,"Should have a client socket by this point");
		Terminate(ASURA_NDR_RELIABLESOCKET_ERROR);
	}
	else
	{
		s_fStateTimer -= Asura_Timers::GetFrameTime();
		if (s_fStateTimer <= 0.0f)
		{
			// connection failed.
			Terminate(ASURA_NDR_TIMEOUT);
		}
	}
}

void Asura_Lobby_System::EnterState_Joining_WaitForConnection()
{
	s_fUpdateTimestamp = 0.0f;
	s_fStateTimer = fASURA_LOBBY_CONNECTION_TIMEOUT;
}

void Asura_Lobby_System::ExecuteState_Joining_WaitForConnection()
{
	//We wait (1) for the client socket to connect (2) to receive the first 
	//update (so that any GUI has the chance to populate before the user sees it)
	if (s_pxClientSocket && s_pxClientSocket->IsConnected() && s_bLocalClientReceivedFirstUpdate)
	{
		ChangeState(ASURA_LOBBY_STATE_JOINING_PLATFORM_LATE_INIT);
	}
	else
	{
		s_fStateTimer -= Asura_Timers::GetFrameTime();
		if (s_fStateTimer <= 0.0f)
		{
			// connection failed.
			Terminate(ASURA_NDR_TIMEOUT);
		}
	}
}

void Asura_Lobby_System::EnterState_Joining_PlatformLateInit()
{
	s_fUpdateTimestamp = 0.0f;
	s_fStateTimer = fASURA_LOBBY_CONNECTION_TIMEOUT;

	Platform_CreateLobby(false);

#ifdef ASURA_ENABLE_QOS_SYSTEM
	Asura_Qos_System::CreateLobby(false);
#endif //#ifdef ASURA_ENABLE_QOS_SYSTEM
}

void Asura_Lobby_System::ExecuteState_Joining_PlatformLateInit()
{
	if (Platform_UpdateLobbyCreation())
	{
		ChangeState(ASURA_LOBBY_STATE_PRE_GAME);
	}
	else
	{
		s_fStateTimer -= Asura_Timers::GetFrameTime();
		if (s_fStateTimer <= 0.0f)
		{
			// connection failed.
			Terminate(ASURA_NDR_TIMEOUT);
		}
	}
}

void Asura_Lobby_System::EnterState_Migrating()
{
	s_fStateTimer = fASURA_LOBBY_MIGRATION_TIMEOUT;

	ASURA_LOBBY_CLIENT_MIGRATIONSTATE eClientMigrationState = ASURA_LOBBY_CLIENT_MIGRATIONSTATE_NONE;
	ASURA_LOBBY_HOST_MIGRATIONSTATE eHostMigrationState = ASURA_LOBBY_HOST_MIGRATIONSTATE_NONE;

	Asura_Assert(s_pxLobby,"");
	if (s_pxLobby)
	{
		eClientMigrationState = ASURA_LOBBY_CLIENT_MIGRATIONSTATE_DISCONNECTING;

		if (IsHost())
		{
			//Asura_Assert( eNewState == ASURA_LOBBY_STATE_MIGRATING_PREGAME, "Shouldn't have a host for a dead migration");
			Asura_Assert( s_pxLobby->GetHostID().IsValid(), "Dead migration started for a host?");

			Asura_Lobby_Player* pxLocalPlayer = s_pxLobby->FindLocalPlayer();
			Asura_Assert(pxLocalPlayer,"");
			if (pxLocalPlayer && pxLocalPlayer->IsABestHost())
			{
				//No need for migration
				return;
			}

			s_pxLobby->SetGameState( ASURA_LOBBY_GAMESTATE_MIGRATION );
			eHostMigrationState = ASURA_LOBBY_HOST_MIGRATIONSTATE_STOPPING;

			//We don't send data updates during migration, but we need to ensure the gamestate change is delivered.
			GenerateDataUpdates();
		}
		else
		{
			const Asura_Lobby_Player& xBestHost = s_pxLobby->FindUniqueBestHost();
			if (xBestHost.IsLocalPlayer())
			{
				eHostMigrationState = ASURA_LOBBY_HOST_MIGRATIONSTATE_STARTING;
			}
			else
			{
				eHostMigrationState = ASURA_LOBBY_HOST_MIGRATIONSTATE_NONE;
			}
		}

		ChangeHostMigrationState( eHostMigrationState );
		ChangeClientMigrationState( eClientMigrationState );
	}
}

void Asura_Lobby_System::ExecuteState_Migrating()
{
	switch (s_eHostMigrationState)
	{
		case ASURA_LOBBY_HOST_MIGRATIONSTATE_STARTING:			ExecuteHostMigrationState_Starting();			break;
		case ASURA_LOBBY_HOST_MIGRATIONSTATE_STOPPING:			ExecuteHostMigrationState_Stopping();			break;
		default: break;
	}

	//NOTE: Host state update must come first, in order for flow to be correct for the new host's client

	switch (s_eClientMigrationState)
	{
		case ASURA_LOBBY_CLIENT_MIGRATIONSTATE_DISCONNECTING:	ExecuteClientMigrationState_Disconnecting();	break;
		case ASURA_LOBBY_CLIENT_MIGRATIONSTATE_FINDHOST:		ExecuteClientMigrationState_FindHost();			break;
		case ASURA_LOBBY_CLIENT_MIGRATIONSTATE_CONNECTING:		ExecuteClientMigrationState_Connecting();		break;
		default: break;
	}

	if ((s_eClientMigrationState == ASURA_LOBBY_CLIENT_MIGRATIONSTATE_NONE)
		&& (s_eHostMigrationState == ASURA_LOBBY_HOST_MIGRATIONSTATE_NONE))
	{
		if (IsHost())
		{
			if (s_eLobbyState == ASURA_LOBBY_STATE_MIGRATING_PREGAME)
			{
				if (s_bUseRankedLobbies)
				{
					ChangeState(ASURA_LOBBY_STATE_REGISTERING);
				}
				else
				{
					ChangeState(ASURA_LOBBY_STATE_INITIALISING_GAME);
				}
			}
			else
			{
				ChangeState( ASURA_LOBBY_STATE_PRE_GAME );
			}
		}
		else
		{
			Asura_Assert(s_pxLobby,"");
			if (s_pxLobby)
			{
				switch (s_pxLobby->GetGameState())
				{
					case ASURA_LOBBY_GAMESTATE_MIGRATION:															break;
					case ASURA_LOBBY_GAMESTATE_INVALID:			ChangeState( ASURA_LOBBY_STATE_PRE_GAME );			break;
					case ASURA_LOBBY_GAMESTATE_REGISTERING:		ChangeState( ASURA_LOBBY_STATE_REGISTERING );		break;
					case ASURA_LOBBY_GAMESTATE_INITIALISING:	ChangeState( ASURA_LOBBY_STATE_INITIALISING_GAME );	break;
					default: 
					{
						Asura_Assertf(false,("Unhandled gamestate (%d) during client lobby migration", s_pxLobby->GetGameState())); 
						break;
					}
				}
			}
		}
	}
	else
	{
		s_fStateTimer -= Asura_Timers::GetFrameTime();
		if ((s_fStateTimer <= 0.0f) && (!dbg_bDisableMigrationTimeouts))
		{
			// connection failed.
			if (s_eLobbyState == ASURA_LOBBY_STATE_MIGRATING_DEADHOST)
			{
				//We'll say server quit, as that's what pre-empted it
				Terminate( ASURA_NDR_SERVERQUIT );
			}
			else
			{
				//Failed the pre-game migration
				Asura_Assertf( s_eLobbyState == ASURA_LOBBY_STATE_MIGRATING_PREGAME, ("Unexpected state for migration timeout (%d)", s_eLobbyState));
				Terminate( ASURA_NDR_TIMEOUT );
			}
		}
	}
}

void Asura_Lobby_System::EnterState_Registering()
{
	s_fStateTimer = fASURA_LOBBY_REGISTERING_TIMEOUT * 2.0f;	// host registers after all the clients

	if (IsHost() && s_pxLobby)
	{
		s_pxLobby->SetGameState(ASURA_LOBBY_GAMESTATE_REGISTERING);
	}

	// send update right away on host
	//s_fUpdateMessageTimer = IsHost() ? 0.0f : fASURA_LOBBY_TIME_BETWEEN_UPDATES_DURING_REGISTRATION;
}

void Asura_Lobby_System::ExecuteState_Registering()
{
	Asura_Assert(s_pxLobby, "no lobby");
	if (!s_pxLobby)
	{
		return;
	}

	if (IsHost())
	{
		// if all players have registered, or the time is up, register ourselves
		u_int uNumUnregistered = 0;
		Asura_Lobby_Player* apxUnregisteredPlayers[ASURA_LOBBY_MAX_PLAYERS];
		for (u_int u = 0; u < s_pxLobby->GetNumPlayers(); ++u)
		{
			Asura_Lobby_Player& xPlayer = s_pxLobby->GetPlayerByIndex(u);
			if (!xPlayer.IsLocalPlayer() && !xPlayer.HasRegistered())
			{
				Asura_Assert(uNumUnregistered < ASURA_LOBBY_MAX_PLAYERS, "lobby has more players than max");
				apxUnregisteredPlayers[uNumUnregistered++] = &xPlayer;
			}
		}

		if (!uNumUnregistered)
		{
			// register ourselves now
			Platform_UpdateSessionRegistration();
			if (s_bRegistered)
			{
				//SendFinalPlayerList(); //Probably don't need this anymore
				ChangeState(ASURA_LOBBY_STATE_INITIALISING_GAME);
			}
			else
			{
				s_fStateTimer -= Asura_Timers::GetFrameTime();
				if (s_fStateTimer <= 0.0f)
				{
					Terminate(ASURA_NDR_TIMEOUT);
					return;
				}
			}
		}
		else
		{
			s_fStateTimer -= Asura_Timers::GetFrameTime();
			if (s_fStateTimer <= fASURA_LOBBY_REGISTERING_TIMEOUT)
			{
				// kick any players who haven't registered yet.
				for (u_int u = 0; u < uNumUnregistered; ++u)
				{
					Asura_ReliableConnectionID xID = apxUnregisteredPlayers[u]->GetID();
					if (s_pxLobby->RemovePlayer( xID ))
					{
						//Tell the other players
						s_pxHostSocket->SendMessageToAllExcept( ASURA_MSG_LOBBY_REMOVE_PLAYER, sizeof(Asura_ReliableConnectionID), &xID, xID );

						//Begin disconnecting player
						s_pxHostSocket->Disconnect( xID, ASURA_RS_DISCONNECT_UNKNOWN ); //TODO: Reason
					}					
				}
				// next time through we will register ourselves.
			}
		}
	}
	else
	{
		// not host.
		if (Platform_UpdateSessionRegistration())
		{
			if (!s_bRegistered)
			{
				// TODO: proper NDR for this.
				Terminate(ASURA_NDR_UNKNOWN);
				return;
			}
			else
			{
				//We're registered, so inform the server
				SetLocalPlayerDataChanged();
			}
		}

		// give the host a certain length of time to send the final player list.
		// When we receive it we will change state.
		// Probably don't need the final player list anymore
		if (s_pxLobby->GetGameState() == ASURA_LOBBY_GAMESTATE_INITIALISING)
		{
			ChangeState( ASURA_LOBBY_STATE_INITIALISING_GAME );
		}

		s_fStateTimer -= Asura_Timers::GetFrameTime();
		if (s_fStateTimer <= 0.0f)
		{
			Terminate(ASURA_NDR_TIMEOUT);
		}
	}
}

void Asura_Lobby_System::EnterState_InitialisingGame()
{
	s_bPlatformInitialisingGameDone = false;

	s_fStateTimer = fASURA_LOBBY_INITIALISING_GAME_TIMEOUT;

	s_acGameAddress[0] = 0;

	if (IsHost())
	{
		s_pxLobby->SetGameState( ASURA_LOBBY_GAMESTATE_INITIALISING );
	}
}

void Asura_Lobby_System::ExecuteState_InitialisingGame()
{
	bool bTimeout = false;

	s_fStateTimer -= Asura_Timers::GetFrameTime();
	if (s_fStateTimer <= 0.0f)
	{
		bTimeout = true;
	}

	if (!s_bPlatformInitialisingGameDone)
	{
		s_bPlatformInitialisingGameDone = Platform_UpdateInitialisingGame();
	}

	if (IsHost())
	{
		Asura_Assert(s_acGameAddress[0] == 0,"Platform code has set the game address on host");

		//Tell the clients
		s_pxLobby->SetGameState( ASURA_LOBBY_GAMESTATE_INITIALISING );

		u_int uPlayersStillJoining = 0;
		for (u_int u = 0; u < s_pxLobby->GetNumPlayers(); u++)
		{
			const Asura_Lobby_Player& xPlayer = s_pxLobby->GetPlayerByIndex( u );
			if (xPlayer.GetClientGuid() == ASURA_GUID_UNREGISTERED) uPlayersStillJoining++;
		}

		if ((bTimeout || (uPlayersStillJoining == 0)))
		{
			if (s_bPlatformInitialisingGameDone)
			{
				if (uPlayersStillJoining > 0)
				{
					for (u_int u = 0; u < s_pxLobby->GetNumPlayers(); u++)
					{
						const Asura_Lobby_Player& xPlayer = s_pxLobby->GetPlayerByIndex( u );
						if (xPlayer.GetClientGuid() == ASURA_GUID_UNREGISTERED)
						{
							Asura_ReliableConnectionID xID = xPlayer.GetID();
							if (s_pxLobby->RemovePlayer( xID ))
							{
								//Tell the other players
								s_pxHostSocket->SendMessageToAllExcept( ASURA_MSG_LOBBY_REMOVE_PLAYER, sizeof(Asura_ReliableConnectionID), &xID, xID );

								//Begin disconnecting player
								s_pxHostSocket->Disconnect( xID, ASURA_RS_DISCONNECT_UNKNOWN ); //TODO: Reason
							}
						}
					}
				}

				ChangeState(ASURA_LOBBY_STATE_STARTING_GAME);
			}
			else if (bTimeout)
			{
				//We've timed out, but haven't completed our platform initialisation
				Terminate( ASURA_RS_DISCONNECT_UNKNOWN );
			}
		}
	}
	else
	{
		if (s_pxLobby && s_bPlatformInitialisingGameDone && ((s_pxLobby->GetGameState() == ASURA_LOBBY_GAMESTATE_STARTING) || (s_pxLobby->GetGameState() == ASURA_LOBBY_GAMESTATE_PLAYING)))
		{
			ChangeState(ASURA_LOBBY_STATE_STARTING_GAME);
		}
		else if (s_bJoiningGameInProgress && bTimeout)
		{
			//We've timed out, but haven't completed our platform initialisation
			Terminate( ASURA_NDR_TIMEOUT );
		}
	}
}

void Asura_Lobby_System::EnterState_StartingGame()
{
#ifdef ASURA_ENABLE_QOS_SYSTEM
	Asura_Qos_System::StartingGame();
#endif //ASURA_ENABLE_QOS_SYSTEM

	s_fStateTimer = fASURA_LOBBY_STARTING_GAME_TIMEOUT;

	s_eGameCompletionStatus = ASURA_LOBBY_GAME_COMPLETION_UNSET;

	if (IsHost() && s_pxLobby)
	{
		s_pxLobby->SetGameState(ASURA_LOBBY_GAMESTATE_STARTING);

		s_xGameCompletionHistory.IncrementNumGamesHosted();
	}

	s_xGameCompletionHistory.IncrementNumGamesStarted();

	// Start the transaction. It is finished in Platform_UpdateStartingSession called from ExecuteState_StartingGame.
	s_xGameCompletionHistory.Platform_WriteData_Start();
}

void Asura_Lobby_System::ExecuteState_StartingGame()
{
	if (!Platform_UpdateStartingSession())
	{
		s_fStateTimer -= Asura_Timers::GetFrameTime();
		if (s_fStateTimer <= 0.0f)
		{
			// connection failed.
			Terminate(ASURA_NDR_TIMEOUT);
		}
		return;
	}

#ifdef ASURA_USE_MERIT_SYSTEM
	// if a ranked game, init the merit system for this game
	Asura_Assert(s_pxLobby, "no lobby");
	if (s_bUseRankedLobbies && s_pxLobby)
	{
		// assume that join-in-progress is disabled for ranked games so we can set 
		// max players to be current num players
		const u_int uMaxPlayers = s_pxLobby->GetNumPlayers();
		u_int uNumTeamsForMerit = s_pxLobby->GetNumTeams();
		if (uNumTeamsForMerit <= 1 || uNumTeamsForMerit == ASURA_LOBBY_INVALID_TEAM)
		{
			uNumTeamsForMerit = uMaxPlayers;
		}
		// we get the merit system to store the platform-specific part of the player data, so platform
		// code can access e.g. sceNpId for players who disconnect partway through.
		Asura_Merit_System::Initialise(uMaxPlayers, uNumTeamsForMerit, sizeof(Asura_Platform_Lobby_PlayerData));
	}
#endif

	ChangeState(ASURA_LOBBY_STATE_PLAYING_GAME);
}

void Asura_Lobby_System::EnterState_PlayingGame()
{
	s_fUpdateMessageTimer = 0.0f;

	if (IsHost() && s_pxLobby)
	{
		s_pxLobby->SetGameState(ASURA_LOBBY_GAMESTATE_PLAYING);
	}
	else
	{

	}
}

void Asura_Lobby_System::ExecuteState_PlayingGame()
{
	//Check for connecting players
	//TODO: Tidy this up, remove need for constant searching for players
	if (IsHost())
	{
		for (u_int u = 0; s_pxLobby && (u < s_pxLobby->GetNumPlayers()); u++)
		{
			Asura_Lobby_Player& xPlayer = s_pxLobby->GetPlayerByIndex(u);
			if (xPlayer.GetClientGuid() == ASURA_GUID_UNREGISTERED)
			{
				xPlayer.UpdateTimer();
				if (xPlayer.GetTimer() < 0.0f)
				{
					if (IsHost())
					{
						Asura_ReliableConnectionID xID = xPlayer.GetID();
						if (s_pxLobby->RemovePlayer( xID ))
						{
							//Tell the other players
							s_pxHostSocket->SendMessageToAllExcept( ASURA_MSG_LOBBY_REMOVE_PLAYER, sizeof(Asura_ReliableConnectionID), &xID, xID );

							//Begin disconnecting player
							s_pxHostSocket->Disconnect( xID, ASURA_RS_DISCONNECT_REJECTED );
						}
					}
				}
			}
		}
	}
}

void Asura_Lobby_System::EnterState_Terminating()
{
	s_fStateTimer = fASURA_LOBBY_TERMINATING_TIMEOUT; 
}

void Asura_Lobby_System::ExecuteState_Terminating()
{
	s_fStateTimer -= Asura_Timers::GetFrameTime();

	if (s_fStateTimer > 0.0f)
	{
		if (s_pxHostSocket && (s_pxHostSocket->GetNumConnections() > 0))
		{
			return;
		}

		//We're waiting for the socket to shutdown, so we need to call IsActive as IsConnected will fail as soon
		//as the state becomes DISCONNECTION (Which will be *before* the disconnect callback is called).
		if (s_pxClientSocket && s_pxClientSocket->IsActive())
		{
			return;
		}
	}

	ChangeState( ASURA_LOBBY_STATE_INVALID );
}

void Asura_Lobby_System::ExitState_Terminating()
{
	if (s_pxHostSocket && s_pxHostSocket->IsActive())
	{
		Asura_Assert( s_pxHostSocket->GetNumConnections() == 0, "Host socket failed to disconnect all clients during Terminating state. Forcing the socket closed." );
		s_pxHostSocket->Close();
	}
}

void Asura_Lobby_System::EnterState_EndingGame()
{
	Asura_Assert(s_eGameCompletionStatus != ASURA_LOBBY_GAME_COMPLETION_UNSET, 
		"Project code needs to call Asura_Lobby_System::CompletedGame() before "
		"calling Asura_Lobby_System::EndGame()");

#ifdef ASURA_ENABLE_QOS_SYSTEM
	Asura_Qos_System::EndingGame();
#endif //ASURA_ENABLE_QOS_SYSTEM

#ifdef ASURA_USE_MERIT_SYSTEM
	// if a ranked match, need the merit stats if we finished the game,
	// or if we're the server, we do it for every game regardless (even 
	// if we quit, still do the merit update).
	// TODO: allow for a match to be abandoned e.g. if more than half the players leave
	// there shouldn't be a stats update.
	bool bShouldComputeMerit;
	if (IsHost())
	{
		// TODO: add check for (s_eGameCompletionStatus != ASURA_LOBBY_GAME_ABANDONED)
		bShouldComputeMerit = s_bUseRankedLobbies;
	}
	else
	{
		bShouldComputeMerit = s_bUseRankedLobbies && (s_eGameCompletionStatus == ASURA_LOBBY_GAME_COMPLETED);
	}
	if (bShouldComputeMerit)
	{
		for (u_int uPlayer = 0; uPlayer < s_pxLobby->GetNumPlayers(); ++uPlayer)
		{
			const Asura_Lobby_Player& xPlayer = s_pxLobby->GetPlayerByIndex(uPlayer);

			// the game was completed, so all remaining players count as completed, 
			// except for the server player if he quit the game.
			bool bLeftEarly = false;
			if (xPlayer.GetClientGuid() == ASURA_GUID_CLIENT_START) // guid 1000
			{
				// player is server - did he quit?
				bLeftEarly = (s_iNetworkDisconnectReason == ASURA_NDR_SERVERQUIT);
			}
			// merit system will store the base class in a void* so it can be accessed later to get e.g. sceNpId.
			Asura_Merit_System::StorePlayerStats(xPlayer.GetClientGuid(), xPlayer.GetMerit(), bLeftEarly, &xPlayer);
		}
		Asura_Merit_System::ComputeStats();
	}
#endif

	s_fStateTimer = fASURA_LOBBY_ENDING_GAME_TIMEOUT;

	Platform_EndGame();

	if (IsHost() && s_pxLobby)
	{
		s_pxLobby->SetGameState(ASURA_LOBBY_GAMESTATE_ENDING);
	}

	s_pxLobby->ResetAllClientGuids();

	s_bPlatformInitialisingGameDone = false;
}

void Asura_Lobby_System::ExecuteState_EndingGame()
{
	if (!Platform_UpdateEndingSession())
	{
		s_fStateTimer -= Asura_Timers::GetFrameTime();
		if (s_fStateTimer <= 0.0f)
		{
			// failed to end game
			s_iNetworkDisconnectReason = ASURA_NDR_TIMEOUT;
		}
		else
		{
			return;
		}
	}

#ifdef ASURA_USE_MERIT_SYSTEM
	Asura_Merit_System::Shutdown();
#endif

	// Terminate the connection with a clean exit (or the original disconnection reason).
	Terminate(s_iNetworkDisconnectReason);
}


// Search State functions
void Asura_Lobby_System::ChangeSearchState( ASURA_LOBBY_SEARCHSTATE eNewState )
{
	switch (eNewState)
	{
	case ASURA_LOBBY_SEARCHSTATE_NOT_SEARCHING:		{ break; }
	case ASURA_LOBBY_SEARCHSTATE_SEARCHING:			{ EnterSearchState_Searching();		break; }
	case ASURA_LOBBY_SEARCHSTATE_SEARCH_COMPLETE:	{ break; }
	default:
		{
			Asura_Assert(false, "invalid lobby state");
			break;
		}
	}

	s_eSearchState = eNewState;
}

void Asura_Lobby_System::EnterSearchState_Searching()
{
	s_uNumSearchResults = 0;

	Platform_BeginSearch();
}

void Asura_Lobby_System::ExecuteSearchState_Searching()
{
	if (!Platform_SearchHasFinished())
	{
		return;
	}

	ChangeSearchState(ASURA_LOBBY_SEARCHSTATE_SEARCH_COMPLETE);
}

// Migration State Functions
void Asura_Lobby_System::ChangeHostMigrationState( ASURA_LOBBY_HOST_MIGRATIONSTATE eNewState )
{
	if (eNewState == s_eHostMigrationState) return;

	switch (eNewState)
	{
		case ASURA_LOBBY_HOST_MIGRATIONSTATE_STARTING:		{ EnterHostMigrationState_Starting();	break; }
		case ASURA_LOBBY_HOST_MIGRATIONSTATE_STOPPING:		{ EnterHostMigrationState_Stopping();	break; }
		case ASURA_LOBBY_HOST_MIGRATIONSTATE_NONE:			{ break; }
		default:
		{
			Asura_Assert(false, "invalid lobby host migration state");
			break;
		}
	}

	s_eHostMigrationState = eNewState;
}

void Asura_Lobby_System::EnterHostMigrationState_Stopping()
{
	s_fHostMigrationTimeout = fASURA_LOBBY_MIGRATION_HOST_STOPPING_TIMEOUT;

	Platform_ServerStartMigration(false);
}

void Asura_Lobby_System::ExecuteHostMigrationState_Stopping()
{
	s_fHostMigrationTimeout -= Asura_Timers::GetFrameTime();
	if (((s_fHostMigrationTimeout <= 0.0f) && (!dbg_bDisableMigrationTimeouts)) || (s_pxHostSocket->GetNumConnections() == 0))
	{
		s_pxHostSocket->Close();
		ChangeHostMigrationState( ASURA_LOBBY_HOST_MIGRATIONSTATE_NONE );
	}
}

void Asura_Lobby_System::EnterHostMigrationState_Starting()
{
	s_fHostMigrationTimeout = fASURA_LOBBY_MIGRATION_HOST_STARTING_TIMEOUT;

	Platform_ServerStartMigration(true);

	if (!SetupHostPort())
	{
		Asura_Assert(false, "Error creating lobby sockets");

		s_iNetworkDisconnectReason = ASURA_NDR_RELIABLESOCKET_ERROR;
		ChangeState( ASURA_LOBBY_STATE_INVALID );
	}
	else
	{
		s_pxLobby->InvalidateAllPlayers();
	}
}

void Asura_Lobby_System::ExecuteHostMigrationState_Starting()
{
	s_fHostMigrationTimeout -= Asura_Timers::GetFrameTime();

	bool bInvalidPlayers = false;
	for (u_int u = 0; u < s_pxLobby->GetNumPlayers(); u++)
	{
		if (!s_pxLobby->GetPlayerByIndex(u).GetID().IsValid())
		{
			bInvalidPlayers = true;
			break;
		}
	}

	bool bTimeout = ((s_fHostMigrationTimeout <= 0.0f) && !dbg_bDisableMigrationTimeouts);
	if (bTimeout || !bInvalidPlayers)
	{
		if (bInvalidPlayers)
		{
			//Remove players that haven't reconnected
			s_pxLobby->RemoveInvalidPlayers();
		}

		if (Platform_ServerEndMigration())
		{
			//Send new player data and updated lobby data to clients
			//before sending state change (so re-population of GUI can be hidden)
			GenerateDataUpdates();

			ChangeHostMigrationState( ASURA_LOBBY_HOST_MIGRATIONSTATE_NONE );
		}
		else if (bTimeout)
		{
			//No more time to wait for plaform function
			Terminate( ASURA_NDR_UNKNOWN );
		}
	}
}

void Asura_Lobby_System::ChangeClientMigrationState( ASURA_LOBBY_CLIENT_MIGRATIONSTATE eNewState )
{
	if (eNewState == s_eClientMigrationState) return;

	switch (eNewState)
	{
		case ASURA_LOBBY_CLIENT_MIGRATIONSTATE_DISCONNECTING:	{ EnterClientMigrationState_Disconnecting();	break; }
		case ASURA_LOBBY_CLIENT_MIGRATIONSTATE_FINDHOST:		//Fallthru
		case ASURA_LOBBY_CLIENT_MIGRATIONSTATE_CONNECTING:		//Fallthru
		case ASURA_LOBBY_CLIENT_MIGRATIONSTATE_NONE:			{ break; }
		default:
		{
			Asura_Assert(false, "invalid lobby host migration state");
			break;
		}
	}

	s_eClientMigrationState = eNewState;
}

void Asura_Lobby_System::EnterClientMigrationState_Disconnecting()
{
	if (s_pxClientSocket->GetState() == ASURA_RS_CLIENT_STATE_CONNECTED)
	{
		if (!dbg_bTestMigrationDisconnectFailure)
		{
			s_pxClientSocket->Disconnect( false, ASURA_RS_DISCONNECT_SHUTDOWN );
		}
	}
}

void Asura_Lobby_System::ExecuteClientMigrationState_Disconnecting()
{
	if (s_pxClientSocket->GetState() == ASURA_RS_CLIENT_STATE_INACTIVE)
	{
		ChangeClientMigrationState( ASURA_LOBBY_CLIENT_MIGRATIONSTATE_FINDHOST );
	}
}

void Asura_Lobby_System::ExecuteClientMigrationState_FindHost()
{
	if ((IsHost() && (s_eHostMigrationState != ASURA_LOBBY_HOST_MIGRATIONSTATE_STOPPING))
		|| (s_eHostMigrationState == ASURA_LOBBY_HOST_MIGRATIONSTATE_STARTING))
	{
		AddLocalPlayerToLobby();

		ChangeClientMigrationState( ASURA_LOBBY_CLIENT_MIGRATIONSTATE_CONNECTING );
	}
	else
	{
		if (s_eHostMigrationState == ASURA_LOBBY_HOST_MIGRATIONSTATE_STOPPING)
		{
			//Don't connect our client socket if until the old server has closed.
			//This stops the new server starting the game before the old server is
			//ready (i.e., when a client has to be forcefully disconnected)
			return;
		}

		//Not-local client, if we were local we would already be active
		Asura_NetAddress xAddress;
		Asura_NetPort xPort;
		if (Platform_GetMigrationAddress( xAddress, xPort ))
		{
			//We'll get sent new player data, with new ID's
			s_pxLobby->InvalidateAllPlayers();

			//Only need to re-associate the player with a Connection ID, so no need to send
			//full data. This is needlessly padded (in this case) but the additional size is
			//insignificant.
			Asura_Lobby_GameConnectData xConnectData;
			xConnectData.Fill();

			if (!dbg_bTestMigrationConnectFailure)
			{
				s_pxClientSocket->Connect( xAddress, xPort, &xConnectData, sizeof(xConnectData) );
			}

			ChangeClientMigrationState( ASURA_LOBBY_CLIENT_MIGRATIONSTATE_CONNECTING );
		}
	}
}

void Asura_Lobby_System::ExecuteClientMigrationState_Connecting()
{
	if (s_pxClientSocket->GetState() == ASURA_RS_CLIENT_STATE_CONNECTED)
	{
		if (Platform_ClientEndMigration() && s_bLocalClientReceivedFirstUpdate)
		{
			Asura_Assert(s_pxLobby,"");
			if (s_pxLobby) 
			{
				s_pxLobby->RemoveInvalidPlayers();
			}

			ChangeClientMigrationState( ASURA_LOBBY_CLIENT_MIGRATIONSTATE_NONE );
		}
	}
	else if (s_pxClientSocket->GetState() == ASURA_RS_CLIENT_STATE_INACTIVE)
	{
		//Normal failiures (i.e., couldn't find host) should be handled by ClientDisconnected (with a reason)
		Terminate( ASURA_NDR_UNKNOWN ); //TODO: NDR (Failed to connect to migrated host
	}
}

void Asura_Lobby_System::UpdateNetworkConnection()
{
	if (s_eLobbyState == ASURA_LOBBY_STATE_INVALID)
	{
		return;
	}

	if ((s_eLobbyState != ASURA_LOBBY_STATE_TERMINATING) 
		&& (s_eLobbyState != ASURA_LOBBY_STATE_MIGRATING_DEADHOST)
		&& (s_eLobbyState != ASURA_LOBBY_STATE_MIGRATING_PREGAME))
	{
		GenerateDataUpdates();
	}

	if (s_pxHostSocket && s_pxHostSocket->IsActive()) 
	{
		s_pxHostSocket->Update();
	}

	if (s_pxClientSocket && s_pxClientSocket->IsActive()) 
	{
		s_pxClientSocket->Update();
	}
}

void Asura_Lobby_System::ProcessStatusUpdate( const Asura_Lobby_LobbyData* pxLobbyData )
{
	Asura_Assert(!IsHost(), "shouldn't happen on server");
	Asura_Assert(s_pxLobby, "should have created lobby already");
	Asura_Assert(pxLobbyData, "no lobby data to process");
	if (IsHost() || !s_pxLobby || !pxLobbyData)
	{
		return;
	}

	const u_int uMaxPlayers = pxLobbyData->m_ucMaxPlayers;

	// make sure the lobby is the right size. It's allowed to create the lobby with 0 for max
	// players in which case it gets set to ASURA_LOBBY_MAX_PLAYERS. It's OK to reduce it later.
	const u_int uCurrentMaxPlayers = s_pxLobby->GetMaxPlayers();
	if (uCurrentMaxPlayers != uMaxPlayers)
	{
		if (uCurrentMaxPlayers)
		{
			Asura_Assert (false, "max players can't change once lobby has started, ignoring bad data");
			return;
		}
		else if (uMaxPlayers > ASURA_LOBBY_MAX_PLAYERS)
		{
			Asura_Assert (false, "invalid max players, ignoring bad data");
			return;
		}
	}

	// overwrite the lobby data with the latest update
	s_pxLobby->SetNumTeams(pxLobbyData->m_ucNumTeams);
	s_pxLobby->SetData( pxLobbyData );
	s_pxLobby->ClearDataChangedFlag(); //Not interested on the client
}

void Asura_Lobby_System::GenerateDataUpdates()
{
	s_fUpdateMessageTimer += Asura_Timers::GetFrameTime();
	if (s_fUpdateMessageTimer >= fASURA_LOBBY_MIN_TIME_BETWEEN_FULL_UPDATES && s_bLocalClientReceivedFirstUpdate)
	{
		// Check whether the player microphone status has changed.
		if (s_bInitialised)
		{
			const bool bHasMicrophone = IsMicrophoneAttached();
			if (bHasMicrophone != s_bLocalPlayerHasMicrophone)
			{
				s_bLocalPlayerHasMicrophone = bHasMicrophone;
				SetLocalPlayerDataChanged();
			}
		}

		if (s_bLocalPlayerDataChanged)
		{
			SendClientUpdateToServer();
			s_fUpdateMessageTimer = 0.0f;
			s_bLocalPlayerDataChanged = false;
		}
	}

	if (IsHost() && s_pxLobby)
	{
		bool bNewPlayerDataExists = false;
		for (u_int u = 0; u < s_pxLobby->GetNumPlayers(); u++)
		{
			Asura_Lobby_Player& xPlayer = s_pxLobby->GetPlayerByIndex( u );
			if (xPlayer.HasDataChanged())
			{
				Asura_Lobby_PlayerStatusMsg xPlayerStatus( xPlayer );
				s_pxHostSocket->SendMessageToAll( ASURA_MSG_LOBBY_PLAYER_STATUS, sizeof(Asura_Lobby_PlayerStatusMsg), &xPlayerStatus );
				xPlayer.ClearDataChanged();
			}

			if (xPlayer.IsNewData())
			{
				bNewPlayerDataExists = true;

				Asura_ReliableConnectionID xID = xPlayer.GetID();

				// send message to all other remote client machines with this player data
				s_pxHostSocket->SendMessageToAllExcept( ASURA_MSG_LOBBY_PLAYER_DATA, sizeof(Asura_Lobby_PlayerData), xPlayer.GetDataPtr(), xID );

				// also send info all players to the client who just joined, if not local, including their details.
				if (!xPlayer.IsLocalPlayer())
				{
					for (u_int u = 0; u < s_pxLobby->GetNumPlayers(); ++u)
					{
						const Asura_Lobby_Player& xOtherPlayer = s_pxLobby->GetPlayerByIndex(u);
						//Don't send data about _other_ new players to a new player, as they will be receiving it from the above send anyway.
						if (!xOtherPlayer.IsNewData() || (xOtherPlayer.GetID() == xPlayer.GetID()))
						{
							s_pxHostSocket->SendMessage( xID, ASURA_MSG_LOBBY_PLAYER_DATA, sizeof(Asura_Lobby_PlayerData), xOtherPlayer.GetDataPtr() );
						}
					}

					//And send an initial lobby status message
					//(This also acts to inform the client that they have the complete player list)
					s_pxHostSocket->SendMessage( xID, ASURA_MSG_LOBBY_UPDATE, sizeof(Asura_Lobby_LobbyData), s_pxLobby->GetDataPtr() );
				}
			}
		}

		// Reset new data flags after the data send, as we cross reference them when sending other players data to a new player (to prevent duplication
		// on migration or when two players join within one update).
		if (bNewPlayerDataExists)
		{
			for (u_int u = 0; u < s_pxLobby->GetNumPlayers(); u++)
			{
				Asura_Lobby_Player& xPlayer = s_pxLobby->GetPlayerByIndex( u );
				if (xPlayer.IsNewData()) xPlayer.ClearNewData();
			}
		}

		if (s_pxLobby->HasDataChanged())
		{
			s_pxHostSocket->SendMessageToAll( ASURA_MSG_LOBBY_UPDATE, sizeof(Asura_Lobby_LobbyData), s_pxLobby->GetDataPtr() );
			s_pxLobby->ClearDataChangedFlag();
		}
	}
}

void Asura_Lobby_System::SendClientUpdateToServer()
{
	// create the status data on the stack
	Asura_Lobby_ClientStatusMsg xStatus(s_bLocalPlayerReady, s_ucLocalPlayerRequestedTeam, s_bRegistered, &s_aucProjectData[0], s_ucVoiceChannels, s_bLocalPlayerHasMicrophone);
	if( s_pxClientSocket->IsConnected() )
	{
		s_pxClientSocket->SendMessage( ASURA_MSG_LOBBY_CLIENTSTATUS, sizeof(xStatus), &xStatus );
	}
}


void Asura_Lobby_System::ProcessClientStatus( Asura_ReliableConnectionID xID, const Asura_Lobby_ClientStatusMsg* pxData )
{
	Asura_Assert(IsHost(), "only valid on host");
	Asura_Assert(s_pxLobby, "lobby not allocated");

	if (IsHost() && s_pxLobby)
	{
		Asura_Lobby_Player* pxPlayer = s_pxLobby->FindPlayer( xID );
		Asura_Assert(pxPlayer,"Could not find player to update lobby data");
		if (!pxPlayer || !pxData) return;

		//This will cause an update to be generated
		pxPlayer->UpdateData( pxData );
	}
}

void Asura_Lobby_System::AddLocalPlayerToLobby()
{
	Asura_Assert(s_pxLobby, "lobby not allocated");
	Asura_Assert(IsHost(), "should only be called on host");
	Asura_Assert(s_pxClientSocket, "should have client socket by now");
	// clients get a message to do this.
	if (IsHost() && s_pxLobby && s_pxClientSocket)
	{
		void* pData = NULL;
		u_short usDataSize = 0;
		if (IsMigrating())
		{
			Asura_Lobby_GameConnectData xConnectData;
			xConnectData.Fill();
			pData = &xConnectData;
			usDataSize = sizeof(xConnectData);
		}
		else
		{
			Asura_Lobby_ConnectData xConnectData;
			xConnectData.Fill();
			pData = &xConnectData;
			usDataSize = sizeof(xConnectData);
		}

		Asura_NetAddress xLoopback = Asura_ReliableSocket_Client::GetLoopbackAddress();
		Asura_NetPort xPort( GetBoundTCPPort() );
		s_pxClientSocket->Connect( xLoopback, xPort, pData, usDataSize );
	}
}

bool Asura_Lobby_System::GetPlayerName( u_int uPlayerIndex, bool bInterrogate, bool bHelp )
{
	if (bHelp || bInterrogate)
	{
		return false;
	}
	else
	{
		if (s_pxLobby && uPlayerIndex < s_pxLobby->GetNumPlayers())
		{
			Asura_CommandConsole::Printf( L"%s", s_pxLobby->GetPlayerNameByIndex(uPlayerIndex) );
			return true;
		}
	}
	return false;
}

bool Asura_Lobby_System::GetPlayerReadyStatus( u_int uPlayerIndex, bool bInterrogate, bool bHelp )
{
	if (bHelp || bInterrogate)
	{
		return false;
	}
	else
	{
		if (s_pxLobby && uPlayerIndex < s_pxLobby->GetNumPlayers())
		{
			Asura_CommandConsole::Printf( s_pxLobby->GetPlayerByIndex(uPlayerIndex).GetReadyStatus() ? "1" : "0" );
			return true;
		}
	}
	return false;
}

bool Asura_Lobby_System::GetPlayerNotReadyStatus( u_int uPlayerIndex, bool bInterrogate, bool bHelp )
{
	if (bHelp || bInterrogate)
	{
		return false;
	}
	else
	{
		if (s_pxLobby && uPlayerIndex < s_pxLobby->GetNumPlayers())
		{
			Asura_CommandConsole::Printf( s_pxLobby->GetPlayerByIndex(uPlayerIndex).GetReadyStatus() ? "0" : "1" );
			return true;
		}
	}
	return false;
}

bool Asura_Lobby_System::GetPlayerIsFriend( u_int uPlayerIndex, bool bInterrogate, bool bHelp )
{
	if (bHelp || bInterrogate)
	{
		return false;
	}
	else
	{
		if (s_pxLobby && uPlayerIndex < s_pxLobby->GetNumPlayers())
		{
			const bool bIsFriend = Asura_FriendsList::IsPlayerAFriend( s_pxLobby->GetPlayerByIndex(uPlayerIndex) );
			Asura_CommandConsole::Printf( bIsFriend ? "1" : "0" );
			return true;
		}
	}
	return false;
}

bool Asura_Lobby_System::GetPlayerIsTalking( u_int uPlayerIndex, bool bInterrogate, bool bHelp )
{
	if (bHelp || bInterrogate) { return false; }

	if (s_pxLobby && uPlayerIndex < s_pxLobby->GetNumPlayers())
	{
		Asura_CommandConsole::Printf(Platform_IsPlayerTalking(s_pxLobby->GetPlayerByIndex(uPlayerIndex)) ? "1" : "0");
		return true;
	}
	return false;
}

bool Asura_Lobby_System::GetPlayerMicrophoneAttached( u_int uPlayerIndex, bool bInterrogate, bool bHelp )
{
	if (bHelp || bInterrogate)
	{
		return false;
	}
	else
	{
		if (s_pxLobby && uPlayerIndex < s_pxLobby->GetNumPlayers())
		{
			Asura_CommandConsole::Printf( s_pxLobby->GetPlayerByIndex(uPlayerIndex).IsMicrophoneAttached() ? "1" : "0" );
			return true;
		}
	}
	return false;
}

bool Asura_Lobby_System::DoPlayerListAction( u_int uPlayerIndex, bool bInterrogate, bool bHelp )
{
	if (bHelp || bInterrogate)
	{
		return false;
	}
	else if (s_pxLobby && uPlayerIndex < s_pxLobby->GetNumPlayers())
	{
		Platform_DoPlayerListAction( uPlayerIndex );
	}
	return true;
}

template<typename T> bool Asura_Lobby_System::GetPlayerNameByTeam( u_int uPlayerIndex, bool bInterrogate, bool bHelp )
{
	if (bHelp || bInterrogate) { return false; }
	u_char ucTeam = T::ucTEAM_NUM;
	if (s_pxLobby && ucTeam < s_pxLobby->GetNumTeams() && uPlayerIndex < s_pxLobby->GetNumPlayersForTeam(ucTeam))
	{
		Asura_CommandConsole::Printf(L"%s", s_pxLobby->GetTeamPlayerByIndex(ucTeam, uPlayerIndex).GetName());
		return true;
	}
	return false;
}

template<typename T> bool Asura_Lobby_System::GetPlayerReadyStatusByTeam( u_int uPlayerIndex, bool bInterrogate, bool bHelp )
{
	if (bHelp || bInterrogate) { return false; }
	u_char ucTeam = T::ucTEAM_NUM;
	if (s_pxLobby && ucTeam < s_pxLobby->GetNumTeams() && uPlayerIndex < s_pxLobby->GetNumPlayersForTeam(ucTeam))
	{
		Asura_CommandConsole::Printf(s_pxLobby->GetTeamPlayerByIndex(ucTeam, uPlayerIndex).GetReadyStatus() ? "1" : "0");
		return true;
	}
	return false;
}

template<typename T> bool Asura_Lobby_System::GetPlayerNotReadyStatusByTeam( u_int uPlayerIndex, bool bInterrogate, bool bHelp )
{
	if (bHelp || bInterrogate) { return false; }
	u_char ucTeam = T::ucTEAM_NUM;
	if (s_pxLobby && ucTeam < s_pxLobby->GetNumTeams() && uPlayerIndex < s_pxLobby->GetNumPlayersForTeam(ucTeam))
	{
		Asura_CommandConsole::Printf(s_pxLobby->GetTeamPlayerByIndex(ucTeam, uPlayerIndex).GetReadyStatus() ? "0" : "1");
		return true;
	}
	return false;
}

template<typename T> bool Asura_Lobby_System::GetPlayerIsFriendByTeam( u_int uPlayerIndex, bool bInterrogate, bool bHelp )
{
	if (bHelp || bInterrogate) { return false; }
	u_char ucTeam = T::ucTEAM_NUM;
	if (s_pxLobby && ucTeam < s_pxLobby->GetNumTeams() && uPlayerIndex < s_pxLobby->GetNumPlayersForTeam(ucTeam))
	{
		const bool bIsFriend = Asura_FriendsList::IsPlayerAFriend( s_pxLobby->GetTeamPlayerByIndex(ucTeam, uPlayerIndex) );
		Asura_CommandConsole::Printf( bIsFriend ? "1" : "0" );
		return true;
	}
	return false;
}

template<typename T> bool Asura_Lobby_System::GetPlayerIsTalkingByTeam( u_int uPlayerIndex, bool bInterrogate, bool bHelp )
{
	if (bHelp || bInterrogate) { return false; }
	u_char ucTeam = T::ucTEAM_NUM;
	if (s_pxLobby && ucTeam < s_pxLobby->GetNumTeams() && uPlayerIndex < s_pxLobby->GetNumPlayersForTeam(ucTeam))
	{
		const bool bIsTalking = Platform_IsPlayerTalking( s_pxLobby->GetTeamPlayerByIndex(ucTeam, uPlayerIndex) );
		Asura_CommandConsole::Printf( bIsTalking ? "1" : "0" );
		return true;
	}
	return false;
}

template<typename T> bool Asura_Lobby_System::DoPlayerListActionByTeam( u_int uIndexInTeam, bool bInterrogate, bool bHelp )
{
	if (bHelp || bInterrogate) { return false; }
	u_char ucTeam = T::ucTEAM_NUM;
	if (s_pxLobby && ucTeam < s_pxLobby->GetNumTeams() && uIndexInTeam < s_pxLobby->GetNumPlayersForTeam(ucTeam))
	{
		u_int uPlayerIndex = s_pxLobby->GetTeamPlayerIndex(ucTeam, uIndexInTeam);
		Platform_DoPlayerListAction(uPlayerIndex);
		return true;
	}
	return false;
}

template<typename T> bool Asura_Lobby_System::GetTeamName( u_int uListboxEntry, bool bInterrogate, bool bHelp )
{
	if (bHelp || bInterrogate) { return false; }
	if (s_pxLobby && (uListboxEntry == 0))
	{
		Asura_CommandConsole::Printf(L"%s", s_pxLobby->GetTeamName(T::ucTEAM_NUM));
		return true;
	}
	return false;
}

bool Asura_Lobby_System::ToggleReadyStatus( bool bHelp )
{
	if (!bHelp)
	{
		Asura_Assert(s_eLobbyState == ASURA_LOBBY_STATE_PRE_GAME, "unexpected lobby state");
		if (s_eLobbyState == ASURA_LOBBY_STATE_PRE_GAME)
		{
			s_bLocalPlayerReady = !s_bLocalPlayerReady;

			SetLocalPlayerDataChanged();
		}
	}
	return true;
}

bool Asura_Lobby_System::OpenFriendsList( bool bHelp )
{
	if (!bHelp)
	{
		Asura_FriendsList::ShowFriendsGUI();
	}
	return true;
}

bool Asura_Lobby_System::ConsoleCmd_CancelInvitation( bool bHelp )
{
	if (!bHelp)
	{
		// allow this to be called even when invite isn't pending to simplify menu setup.
		s_bInvitationPending = false;
	}
	return true;
}

bool Asura_Lobby_System::ConsoleCmd_UseRankedMatch( bool bUseRankedMatch, bool bInterrogate, bool bHelp )
{
	if (bHelp)
	{
	}
	else if (bInterrogate)
	{
		Asura_CommandConsole::Printf("%d", s_bUseRankedLobbies);
	}
	else
	{
		SetUseRankedLobbies(bUseRankedMatch);
	}
	return true;
}

bool Asura_Lobby_System::ConsoleCmd_AllowJoinInProgress( bool bAllowJoinInProgress, bool bInterrogate, bool bHelp )
{
	if (bHelp)
	{
	}
	else if (bInterrogate)
	{
		Asura_CommandConsole::Printf("%d", s_bAllowJoinInProgress);
	}
	else
	{
		SetAllowJoinInProgress(bAllowJoinInProgress);
	}
	return true;
}

bool Asura_Lobby_System::IsReadyToJoinGame()
{
	switch (s_eLobbyState)
	{
		case ASURA_LOBBY_STATE_INITIALISING_GAME:
			return s_bPlatformInitialisingGameDone;

		case ASURA_LOBBY_STATE_STARTING_GAME:
		case ASURA_LOBBY_STATE_PLAYING_GAME:
			return true;

		default:
			break;
	}

	return false;
}

bool Asura_Lobby_System::IsReadyToLoadGame()
{
	switch (s_eLobbyState)
	{
		case ASURA_LOBBY_STATE_STARTING_GAME:
		case ASURA_LOBBY_STATE_PLAYING_GAME:
			return true;

		default:
			break;
	}

	return false;
}

void Asura_Lobby_System::StartGame()
{
	Asura_Assert((IsHost() || s_bJoiningGameInProgress) && s_eLobbyState == ASURA_LOBBY_STATE_PRE_GAME,
		"only valid on host or client joining game in progress from pre-game state");
	if (s_eLobbyState == ASURA_LOBBY_STATE_PRE_GAME)
	{	
		if (IsHost())
		{
			if (s_bMigrateOnGameStart)
			{
				ChangeState(ASURA_LOBBY_STATE_MIGRATING_PREGAME);
			}
			else
			{
				if (s_bUseRankedLobbies)
				{
					ChangeState(ASURA_LOBBY_STATE_REGISTERING);
				}
				else
				{
					ChangeState(ASURA_LOBBY_STATE_INITIALISING_GAME);
				}
			}
		}
		else if (s_bJoiningGameInProgress)
		{
			if (s_bUseRankedLobbies)
			{
				Terminate(ASURA_NDR_SERVERKICK);
			}
			else
			{
				ChangeState(ASURA_LOBBY_STATE_INITIALISING_GAME);
			}
		}
	}
}

void Asura_Lobby_System::EndGame()
{
	if (s_pxLobby)
	{
		Asura_Assert(s_eLobbyState == ASURA_LOBBY_STATE_PLAYING_GAME, "unexpected state");
		ChangeState(ASURA_LOBBY_STATE_ENDING_GAME);
	}
	else
	{
		Asura_Assert(s_eLobbyState == ASURA_LOBBY_STATE_INVALID, "unexpected state");
	}
}

void Asura_Lobby_System::CompletedGame(bool bCompleted)
{
	if (!Asura_Lobby_System::IsInitialised())
	{
		return;
	}

	if (bCompleted)
	{
		// Completed a game without the local user quitting.
		s_xGameCompletionHistory.IncrementNumGamesCompleted();

		// Do some sanity checking.
		Asura_Assert(s_xGameCompletionHistory.GetNumGamesCompleted() <= s_xGameCompletionHistory.GetNumGamesStarted(), "Invalid number of games completed/started");
		Asura_Assert(s_xGameCompletionHistory.GetNumGamesHosted()    <= s_xGameCompletionHistory.GetNumGamesStarted(), "Invalid number of games hosted/started");
	}

	s_eGameCompletionStatus = bCompleted ? ASURA_LOBBY_GAME_COMPLETED : ASURA_LOBBY_GAME_NOT_COMPLETED;
}

bool Asura_Lobby_System::IsAutomatching()
{
	if (s_pxLobby)
	{
		return s_pxLobby->IsAutomatching();
	}
	return false;
}

void Asura_Lobby_System::SetAutomatching(bool bAutomatching)
{
	Asura_Assert(s_pxLobby, "must have a lobby object");
	if (s_pxLobby)
	{
		s_pxLobby->SetAutomatching(bAutomatching);
	}
}

void Asura_Lobby_System::SetNumPlayersBeingWaitedFor( u_int uNumPlayers )
{
	Asura_Assert(IsHost() && s_pxLobby, "only valid on host when lobby exists");
	if (s_pxLobby)
	{
		s_pxLobby->SetAutomatchNumPlayersAwaited(uNumPlayers);
	}
}

u_int Asura_Lobby_System::GetNumPlayersBeingWaitedFor()
{
	if (s_pxLobby)
	{
		return s_pxLobby->GetAutomatchPlayersAwaited();
	}
	return 0;
}

const Asura_Lobby_ListEntry& Asura_Lobby_System::GetSearchResultByIndex(u_int uIndex)
{
	Asura_Assert(uIndex < s_uNumSearchResults, "invalid index");
	if (uIndex < s_uNumSearchResults)
	{
		return s_pxSearchResults[uIndex];
	}
	return s_pxSearchResults[0];
}

void Asura_Lobby_System::SetPlayerGuid( const Asura_Lobby_GameConnectData& xConnectData, Asura_Guid uGuid )
{
	Asura_Assert(IsHost() && s_pxLobby,"Shouldn't be setting player guids");
	if (IsHost() && s_pxLobby)
	{
		Asura_Lobby_Player* pxPlayer = s_pxLobby->FindPlayer( xConnectData );
		Asura_Assert(pxPlayer, "Couldn't find player from GameConnectData");
		if (pxPlayer)
		{
			pxPlayer->SetClientGuid( uGuid );
			pxPlayer->SetTimer( 0.0f );
		}
	}
}

void Asura_Lobby_System::SetLocalPlayerGuid( Asura_Guid uGuid )
{
	Asura_Assert(IsHost() && s_pxLobby, "Should be host with a lobby" );
	if (s_pxLobby)
	{
		Asura_Lobby_Player* pxPlayer = s_pxLobby->FindLocalPlayer();
		Asura_Assert(pxPlayer,"Could not find local player to set guid");
		if (pxPlayer)
		{
			pxPlayer->SetClientGuid( ASURA_GUID_CLIENT_START );
		}
	}
}

void Asura_Lobby_System::SetPlayerTeamByGuid( Asura_Guid uClientGuid, u_char ucTeam )
{
	Asura_Assert(IsHost() && s_pxLobby, "only valid on host when lobby exists");
	if (IsHost() && s_pxLobby)
	{
		Asura_Lobby_Player* pxPlayer = s_pxLobby->FindPlayer( uClientGuid );
		Asura_Assertf(false,("Could not find player %d in lobby", uClientGuid));
		if (pxPlayer)
		{
			pxPlayer->SetTeam( ucTeam );
		}
	}
}

void Asura_Lobby_System::SetPlayerVoiceChannelsByGuid( Asura_Guid uClientGuid, u_char ucVoiceChannels )
{
	Asura_Assert(IsHost() && s_pxLobby, "only valid on host when lobby exists");
	if (IsHost() && s_pxLobby)
	{
		Asura_Lobby_Player* pxPlayer = s_pxLobby->FindPlayer( uClientGuid );
		Asura_Assertf(false,("Could not find player %d in lobby", uClientGuid));
		if (pxPlayer)
		{
			pxPlayer->SetVoiceChannels( ucVoiceChannels );
		}
	}
}

void Asura_Lobby_System::SetLocalPlayersRequestedTeam( u_char ucTeam )
{ 
	if (s_eLobbyState == ASURA_LOBBY_STATE_PRE_GAME)
	{
		if (s_ucLocalPlayerRequestedTeam != ucTeam)
		{
			s_ucLocalPlayerRequestedTeam = ucTeam;
			// try to ensure the request is acted on ASAP
			SetLocalPlayerDataChanged();
		}
	}
} 

u_char Asura_Lobby_System::GetLocalPlayersRequestedTeam()
{
	return s_ucLocalPlayerRequestedTeam; 
} 

void Asura_Lobby_System::SetLocalPlayersProjectData( const u_char *pucData )
{
	if (s_eLobbyState == ASURA_LOBBY_STATE_PRE_GAME)
	{
		if (Asura_Mem::Compare(&s_aucProjectData[0], pucData, ASURA_LOBBY_PLAYER_PROJECT_DATA_SIZE))
		{
			Asura_Mem::Copy(&s_aucProjectData[0], pucData, ASURA_LOBBY_PLAYER_PROJECT_DATA_SIZE);
			// try to ensure the request is acted on ASAP
			SetLocalPlayerDataChanged();
		}
	}
}

void Asura_Lobby_System::ConnectLocalPlayerToVoiceChannel( const u_char ucVoiceChannelToConnect )
{
	Asura_Assert( ucVoiceChannelToConnect < ASURA_LOBBY_MAX_VOICE_CHANNELS, "Invalid voice channel" ); 

	// Connect player to channel
	u_char ucBitMask = 1 << ucVoiceChannelToConnect;
	u_char ucVoiceChannels = GetLocalPlayersVoiceChannels();
	ucVoiceChannels = ucVoiceChannels | ucBitMask; 

	if (s_ucVoiceChannels != ucVoiceChannels)
	{
		s_ucVoiceChannels = ucVoiceChannels;
		// try to ensure the request is acted on ASAP
		SetLocalPlayerDataChanged();
	}
}

void Asura_Lobby_System::DisconnectLocalPlayerFromVoiceChannel( const u_char ucVoiceChannelToDisconnect )
{
	Asura_Assert( ucVoiceChannelToDisconnect < ASURA_LOBBY_MAX_VOICE_CHANNELS, "Invalid voice channel" ); 

	// Disconnect player from channel
	u_char ucBitMask = 1 << ucVoiceChannelToDisconnect;
	ucBitMask = ~ucBitMask;
	u_char ucVoiceChannels = GetLocalPlayersVoiceChannels();
	ucVoiceChannels = ucVoiceChannels & ucBitMask; 

	if (s_ucVoiceChannels != ucVoiceChannels)
	{
		s_ucVoiceChannels = ucVoiceChannels;
		// try to ensure the request is acted on ASAP
		SetLocalPlayerDataChanged();
	}
}

void Asura_Lobby_System::SetLocalPlayersActiveVoiceChannel( const u_char ucVoiceChannel )
{
	Asura_Assert( ucVoiceChannel < ASURA_LOBBY_MAX_VOICE_CHANNELS, "Invalid voice channel" ); 
	s_ucActiveVoiceChannel = ucVoiceChannel; 
}

u_char Asura_Lobby_System::GetLocalPlayersVoiceChannels()
{
	return s_ucVoiceChannels; 
} 

u_char Asura_Lobby_System::GetLocalPlayersActiveVoiceChannel()
{
	return s_ucActiveVoiceChannel; 
} 

void Asura_Lobby_System::SetTeamName( u_char ucTeam, const Asura_UnicodeChar* wszName )
{
	if (s_pxLobby)
	{
		s_pxLobby->SetTeamName(ucTeam, wszName);
	}

}

void Asura_Lobby_System::SetMigrationEnabled( bool bOnDeadHost, bool bOnGameStart )
{
	s_bMigrateOnDeadHost = bOnDeadHost;
	s_bMigrateOnGameStart = bOnGameStart;
}

void Asura_Lobby_System::GetMigrationEnabled( bool& bOnDeadHost, bool& bOnGameStart )
{
	bOnDeadHost = s_bMigrateOnDeadHost;
	bOnGameStart = s_bMigrateOnGameStart;
}

// set this flag to determine whether we're using the ranked or unranked section of the game
void Asura_Lobby_System::SetUseRankedLobbies( bool bUseRankedLobbies )
{
	Asura_Assert(s_eLobbyState == ASURA_LOBBY_STATE_INVALID &&
		(s_eSearchState == ASURA_LOBBY_SEARCHSTATE_NOT_SEARCHING || 
		s_eSearchState == ASURA_LOBBY_SEARCHSTATE_SEARCH_COMPLETE),
		"must choose ranked/unranked before doing anything else");
	if (s_eLobbyState == ASURA_LOBBY_STATE_INVALID &&
		(s_eSearchState == ASURA_LOBBY_SEARCHSTATE_NOT_SEARCHING || 
		s_eSearchState == ASURA_LOBBY_SEARCHSTATE_SEARCH_COMPLETE))
	{
		s_bUseRankedLobbies = bUseRankedLobbies;
	}
}

void Asura_Lobby_System::SetAllowJoinInProgress( bool bAllowJoinInProgress )
{
	Asura_Assert(s_eLobbyState == ASURA_LOBBY_STATE_INVALID, "must do this before creating lobby");

	s_bAllowJoinInProgress = bAllowJoinInProgress;
}

void Asura_Lobby_System::SetLobbyAttributes( const Asura_Lobby_Attributes& xAttributes )
{
	Asura_Assert(IsHost() && s_pxLobby, "Should be host with a lobby" );
	if (s_pxLobby)
	{
		s_pxLobby->SetAttributes( xAttributes ); 
	}
}

u_short Asura_Lobby_System::GetBoundTCPPort()
{
	if (s_pxHostSocket)
	{
		return s_pxHostSocket->GetListenPort();
	}
	return 0;
}

//////////////////////////////////////////////////////////////////////////
// Asura_Lobby_Attributes
//////////////////////////////////////////////////////////////////////////
Asura_Lobby_Attributes::Asura_Lobby_Attributes()
{
	Asura_Mem::Set(this, 0, sizeof(*this));
}

void Asura_Lobby_Attributes::SetBinaryData( const void* pData )
{
	Asura_Mem::Copy(m_aucAttributeData, pData, sizeof(m_aucAttributeData));
}

///////////////////////////////////////////////////////////////////////////////////
// Asura_Lobby
///////////////////////////////////////////////////////////////////////////////////
Asura_Lobby::Asura_Lobby(u_int uMaxPlayers, const Asura_Lobby_Attributes& xAttributes)
: m_bDataChanged(false)
{
	if (!uMaxPlayers)
	{
		uMaxPlayers = ASURA_LOBBY_MAX_PLAYERS;
	}
	ASURA_STATIC_ASSERT(ASURA_LOBBY_MAX_PLAYERS <= UCHAR_MAX);
	Asura_Assert(uMaxPlayers <= ASURA_LOBBY_MAX_PLAYERS, "too many players");
	m_xData.m_ucGameState = ASURA_LOBBY_GAMESTATE_INVALID;
	m_xData.m_ucMaxPlayers = static_cast<u_char>(uMaxPlayers);
	m_uNumPlayers = 0;
	Asura_Mem::Copy(&m_xData.m_xAttributes, &xAttributes, sizeof(xAttributes));
	for (u_int u = 0; u < ASURA_LOBBY_MAX_PLAYERS; u++)
	{
		if (u < uMaxPlayers)
		{
			m_apxPlayers[u] = new Asura_Lobby_Player(*this);
		}
		else
		{
			m_apxPlayers[u] = NULL;
		}
	}

	for (u_int u = 0; u < ASURA_LOBBY_MAX_TEAMS; ++u)
	{
		m_apxTeams[u] = new Asura_Lobby_Team(uMaxPlayers);
	}
}

Asura_Lobby::~Asura_Lobby()
{
	// do platform shutdown on all players
	for (u_int u = 0; u < ASURA_LOBBY_MAX_PLAYERS; ++u)
	{
		if (u < m_uNumPlayers)
		{
			Platform_RemovePlayer(*m_apxPlayers[u]);
		}
		delete m_apxPlayers[u];
	}

	for (u_int u = 0; u < ASURA_LOBBY_MAX_TEAMS; ++u)
	{
		delete m_apxTeams[u];
	}

	s_uNumTeams = 0;
}

Asura_Lobby_Player* Asura_Lobby::AddPlayer( const Asura_Lobby_PlayerData& xPlayerData )
{
	Asura_Assert( m_uNumPlayers < m_xData.m_ucMaxPlayers, "not enough room");
	bool bPlayerExists = (FindPlayer( xPlayerData.m_xID ) != NULL);
	Asura_Assert( !bPlayerExists, "player already added");

	if (!bPlayerExists && (m_uNumPlayers < m_xData.m_ucMaxPlayers))
	{
		const u_int uPlayerIndex = m_uNumPlayers++;
		Asura_Lobby_Player& xPlayer = *m_apxPlayers[uPlayerIndex];
		xPlayer.Initialise( &xPlayerData );

		if (Asura_Lobby_System::IsReadyToLoadGame())
		{
			xPlayer.SetTimer( fASURA_LOBBY_JOINING_GAME_IN_PROGRESS_TIMEOUT );
		}
		else
		{
			xPlayer.SetTimer( 0.0f );
		}

		// add to the relevant team
		u_char ucTeam = xPlayerData.m_ucTeam;
		Asura_Assert(ucTeam < ASURA_LOBBY_MAX_TEAMS, "invalid team");
		AddPlayerToTeam(uPlayerIndex, ucTeam);

		Platform_AddPlayer(xPlayer);

		return &xPlayer;
	}

	return NULL;
}

void Asura_Lobby::UpdatePlayer( const Asura_Lobby_PlayerStatusMsg* pxPlayerStatus )
{
	Asura_Assert(pxPlayerStatus,"No player status to update with");
	Asura_Lobby_Player* pxPlayer = FindPlayer( pxPlayerStatus->GetID() );
	Asura_Assert(pxPlayer,"Could not find player to update");
	if (pxPlayer)
	{
		pxPlayer->UpdateData( pxPlayerStatus );
	}
}

bool Asura_Lobby::RemovePlayer( const Asura_ReliableConnectionID& xID )
{
	u_int uPlayer = 0;
	if (FindPlayerIndex( xID, uPlayer ))
	{
		return RemovePlayer( uPlayer );
	}

	Asura_Assert(Asura_Lobby_System::IsHost(),"Client couldn't find lobby player to remove");
	return false;
}

bool Asura_Lobby::RemovePlayer( u_int uPlayerIndex )
{
	const Asura_Lobby_Player& xLobbyPlayer = GetPlayerByIndex( uPlayerIndex );
	// for a ranked match, if the player left during gameplay, store stats for merit system
#ifdef ASURA_USE_MERIT_SYSTEM
	const bool bRankedMatch = Asura_Lobby_System::IsUsingRankedLobbies();
	if (bRankedMatch && Asura_Lobby_System::GetState() == ASURA_LOBBY_STATE_PLAYING_GAME)
	{
		const bool bLeftEarly = true;
		// merit system will store the base class in a void* so it can be accessed later to get e.g. sceNpId.
		Asura_Merit_System::StorePlayerStats(xLobbyPlayer.GetClientGuid(), xLobbyPlayer.GetMerit(), bLeftEarly, &xLobbyPlayer);
	}
#endif

	Platform_RemovePlayer( xLobbyPlayer );

	// remove from the team
	RemovePlayerFromTeam(uPlayerIndex, xLobbyPlayer.GetTeamIndex());
	// fix up player indices in mapping for players after this one in the list.
	UpdateTeamMappingsForPlayerRemoval(uPlayerIndex);

	// shuffle remaining player pointers down
	Asura_Lobby_Player* pxEmptySlot = m_apxPlayers[uPlayerIndex];
	u_int uLastSlot = GetNumPlayers() - 1;
	for (u_int uTemp = uPlayerIndex; uTemp < uLastSlot; ++uTemp)
	{
		//Copy player pointer
		m_apxPlayers[uTemp] = m_apxPlayers[uTemp+1];
	}

	// Move player pointer for removed player to back (unless it's already there)
	if (uPlayerIndex < uLastSlot)
	{
		m_apxPlayers[uLastSlot] = pxEmptySlot;
	}

	// decrease player count
	--m_uNumPlayers;

	return true;
}

void Asura_Lobby_System::ClientDisconnected(ASURA_RELIABLE_SOCKET_DISCONNECT_REASON eReason)
{
	ASURA_LOBBYSYSTEM_TRACE(( "Client Disconnected, reason: %i\n", eReason ));
	//Check s_bInitialised to see if we're shutting down
	if (s_bInitialised && (s_eLobbyState != ASURA_LOBBY_STATE_TERMINATING) 
		&& (!IsMigrating() || s_eClientMigrationState != ASURA_LOBBY_CLIENT_MIGRATIONSTATE_DISCONNECTING ))
	{
		Asura_Assert(!IsHost(),"Host shouldn't be disconnected in this manner");
		ASURA_NETWORK_DISCONNECT_REASON eNDR = ASURA_NDR_UNKNOWN;
		switch (eReason)
		{
			case ASURA_RS_DISCONNECT_SHUTDOWN:	eNDR = ASURA_NDR_SERVERQUIT;			break;
			case ASURA_RS_DISCONNECT_REJECTED:	eNDR = ASURA_NDR_SERVERKICK;			break;
			default:							eNDR = ASURA_NDR_RELIABLESOCKET_ERROR;	break;
		}

		if (!IsHost() && (s_eLobbyState == ASURA_LOBBY_STATE_PRE_GAME) && s_bMigrateOnDeadHost && !s_bJoiningGameInProgress)
		{
			//Remove host so they don't effect the besthost scoring process
			s_pxLobby->RemovePlayer( s_pxLobby->GetHostID() );
			Asura_ReliableConnectionID xInvalid;
			xInvalid.Invalidate();
			s_pxLobby->SetHostID( xInvalid );

			ChangeState( ASURA_LOBBY_STATE_MIGRATING_DEADHOST );
		}
		else
		{
			Terminate( eNDR );
		}
	}

	//TODO: Inform Asura_NetTransport_System? (if active)

	s_bLocalClientReceivedFirstUpdate = false;
}

void Asura_Lobby_System::PlayerLeft(const Asura_ReliableConnectionID& xID, ASURA_RELIABLE_SOCKET_DISCONNECT_REASON eReason)
{
	Asura_Assert( IsHost(), "should only be called by host");
	// We won't have a during shutdown
	if (IsHost() && s_pxLobby && !IsMigrating())
	{
		//We may have already removed the player if we caused it to leave
		Asura_Lobby_Player* pxPlayer = s_pxLobby->FindPlayer( xID );
		if (pxPlayer)
		{
			if (s_pxLobby->RemovePlayer( xID ))
			{
				//Tell the other players
				s_pxHostSocket->SendMessageToAllExcept( ASURA_MSG_LOBBY_REMOVE_PLAYER, sizeof(Asura_ReliableConnectionID), &xID, xID );
			}
		}
	}
}

void Asura_Lobby::AddPlayerToTeam( u_int uPlayerIndex, u_char ucTeam )
{
	if (ucTeam == ASURA_LOBBY_INVALID_TEAM)
	{
		return;
	}
	Asura_Assert(ucTeam < ASURA_LOBBY_MAX_TEAMS, "invalid team");
	if (ucTeam >= ASURA_LOBBY_MAX_TEAMS)
	{
		return;
	}
	m_apxTeams[ucTeam]->AddPlayer(uPlayerIndex);
}

void Asura_Lobby_Team::AddPlayer( u_int uPlayerIndex )
{
	Asura_Assert(m_uNumPlayersInTeam < m_uMaxPlayers, "too many players on team");
	if (m_uNumPlayersInTeam < m_uMaxPlayers)
	{
		const u_int uTeamMappingIndex = m_uNumPlayersInTeam++;
		m_puPlayerMapping[uTeamMappingIndex] = uPlayerIndex;
	}
}

void Asura_Lobby::RemovePlayerFromTeam( u_int uPlayerIndex, u_char ucTeam )
{
	if (ucTeam == ASURA_LOBBY_INVALID_TEAM)
	{
		return;
	}
	Asura_Assert(ucTeam < ASURA_LOBBY_MAX_TEAMS, "invalid team");
	if (ucTeam >= ASURA_LOBBY_MAX_TEAMS)
	{
		return;
	}
	m_apxTeams[ucTeam]->RemovePlayer(uPlayerIndex);
}

void Asura_Lobby_Team::RemovePlayer( u_int uPlayerIndex )
{
	Asura_Assert(m_uNumPlayersInTeam > 0, "invalid team count");
	// first find index into team mapping array
	u_int uTeamMappingIndex = 0;
	for ( ; uTeamMappingIndex < m_uNumPlayersInTeam; ++uTeamMappingIndex)
	{
		if (m_puPlayerMapping[uTeamMappingIndex] == uPlayerIndex)
		{
			break;
		}
	}
	Asura_Assert(uTeamMappingIndex < m_uNumPlayersInTeam, "didn't find team mapping for player");
	// shuffle remaining entries in team mapping array down.
	for (u_int uTemp = uTeamMappingIndex; uTemp < m_uNumPlayersInTeam - 1; ++uTemp)
	{
		m_puPlayerMapping[uTemp] = m_puPlayerMapping[uTemp+1];
	}
	// decrease team count
	--m_uNumPlayersInTeam;
}

void Asura_Lobby::UpdateTeamMappingsForPlayerRemoval( u_int uIndexOfPlayerBeingRemoved )
{
	const u_int uNumTeams = GetNumTeams();
	for (u_int uTeam = 0; uTeam < uNumTeams; ++uTeam)
	{
		m_apxTeams[uTeam]->UpdateMappingsForPlayerRemoval(uIndexOfPlayerBeingRemoved);
	}
}

// player indices are about to be shuffled down by 1 starting with the one after the player being removed.
void Asura_Lobby_Team::UpdateMappingsForPlayerRemoval( u_int uIndexOfPlayerBeingRemoved )
{
	const u_int uNumPlayersInTeam = m_uNumPlayersInTeam;
	for (u_int uTeamPlayer = 0; uTeamPlayer < uNumPlayersInTeam; ++uTeamPlayer)
	{
		if (m_puPlayerMapping[uTeamPlayer] > uIndexOfPlayerBeingRemoved)
		{
			// this player is affected so fix up the index
			--m_puPlayerMapping[uTeamPlayer];
		}
	}
}

u_int Asura_Lobby_Team::GetPlayerMapping( u_int uTeamPlayerIndex ) const
{
	Asura_Assert(uTeamPlayerIndex < m_uNumPlayersInTeam, "invalid player");
	return m_puPlayerMapping[uTeamPlayerIndex];
}

void Asura_Lobby::SetNumTeams( u_char ucNumTeams ) 
{ 
	Asura_Assert(ucNumTeams <= ASURA_LOBBY_MAX_TEAMS, "invalid num teams");
	if (m_xData.m_ucNumTeams != ucNumTeams)
	{
		m_xData.m_ucNumTeams = ucNumTeams; 
		m_bDataChanged = true;
	}

	s_uNumTeams = ucNumTeams;
}

void Asura_Lobby::SetGameState(ASURA_LOBBY_GAMESTATE eState)
{ 
	if (m_xData.m_ucGameState != static_cast<u_char>(eState) )
	{
		m_xData.m_ucGameState = static_cast<u_char>(eState); 
		m_bDataChanged = true;
	}
}

void Asura_Lobby::SetHostID( Asura_ReliableConnectionID xID )
{
	if (m_xData.m_xHostID != xID)
	{
		m_xData.m_xHostID = xID;
		m_bDataChanged = true;
	}
}

void Asura_Lobby::SetAttributes( const Asura_Lobby_Attributes& xAttributes )
{
	if ( m_xData.m_xAttributes != xAttributes )
	{
		m_xData.m_xAttributes = xAttributes; 
		m_bDataChanged = true; 
	}
}

void Asura_Lobby::ResetAllClientGuids()
{
	for (u_int u = 0; u < m_uNumPlayers; u++)
	{
		//No point in sending a message just for this,
		//as all clients can perform this task locally
		Asura_Lobby_Player* pxPlayer = m_apxPlayers[u];
		bool bChangedData = pxPlayer->HasDataChanged();
		pxPlayer->SetClientGuid( ASURA_GUID_UNREGISTERED );
		if (!bChangedData) pxPlayer->ClearDataChanged();
	}
}

void Asura_Lobby::SetTeamName( u_char ucTeam, const Asura_UnicodeChar* wszName )
{
	if (ucTeam < GetNumTeams())
	{
		m_apxTeams[ucTeam]->SetTeamName(wszName);
	}
}

// automatching data. Stored in u_char 7 bits for number of players awaited and 
// 1 bit for whether we're using automatch.
bool Asura_Lobby::IsAutomatching() const
{
	return (m_xData.m_ucAutomatchingData & ASURA_LOBBY_AUTOMATCHDATA_USINGAUTOMATCH_MASK) != 0;
}

u_int Asura_Lobby::GetAutomatchPlayersAwaited() const
{
	return m_xData.m_ucAutomatchingData & ASURA_LOBBY_AUTOMATCHDATA_NUMPLAYERSAWAITED_MASK;
}

void Asura_Lobby::SetAutomatching(bool bAutomatching)
{
	if (bAutomatching)
	{
		m_xData.m_ucAutomatchingData |= ASURA_LOBBY_AUTOMATCHDATA_USINGAUTOMATCH_MASK;
	}
	else
	{
		m_xData.m_ucAutomatchingData &= ~ASURA_LOBBY_AUTOMATCHDATA_USINGAUTOMATCH_MASK;
	}

	m_bDataChanged = true;
}

void Asura_Lobby::SetAutomatchNumPlayersAwaited(u_int uPlayers)
{
	m_xData.m_ucAutomatchingData &= ~ASURA_LOBBY_AUTOMATCHDATA_NUMPLAYERSAWAITED_MASK;
	m_xData.m_ucAutomatchingData |= (uPlayers & ASURA_LOBBY_AUTOMATCHDATA_NUMPLAYERSAWAITED_MASK);

	m_bDataChanged = true;
}

bool Asura_Lobby::FindPlayerIndex( Asura_ReliableConnectionID xID, u_int& uIndex ) const
{
	for (u_int u = 0; u < m_uNumPlayers; u++)
	{
		if (m_apxPlayers[u]->GetID() == xID) 
		{
			uIndex = u;
			return true;
		}
	}

	return false;
}

Asura_Lobby_Player* Asura_Lobby::FindPlayer( Asura_ReliableConnectionID xID ) const
{
	for (u_int u = 0; u < m_uNumPlayers; u++)
	{
		if (m_apxPlayers[u]->GetID() == xID) return m_apxPlayers[u];
	}

	return NULL;
}

Asura_Lobby_Player* Asura_Lobby::FindPlayer( Asura_Guid uClientGuid ) const
{
	for (u_int u = 0; u < m_uNumPlayers; ++u)
	{
		if (m_apxPlayers[u]->GetClientGuid() == uClientGuid)
		{
			return m_apxPlayers[u];
		}
	}
	return NULL;
}

Asura_Lobby_Player* Asura_Lobby::FindPlayer( const Asura_Platform_Lobby_GameConnectData& xConnectData ) const
{
	for (u_int u = 0; u < m_uNumPlayers; u++)
	{
		Asura_Lobby_Player& xPlayer = GetPlayerByIndex( u );
		if (xPlayer == xConnectData)
		{
			return &xPlayer;
		}
	}

	return NULL;
}

Asura_Lobby_Player* Asura_Lobby::FindPlayer( const Asura_Platform_Lobby_PlayerData& xPlayerData ) const
{
	for (u_int u = 0; u < m_uNumPlayers; u++)
	{
		Asura_Lobby_Player& xPlayer = GetPlayerByIndex( u );
		if (xPlayer == xPlayerData)
		{
			return &xPlayer;
		}
	}

	return NULL;
}

Asura_Lobby_Player* Asura_Lobby::FindLocalPlayer() const
{
	for (u_int u = 0; u < GetNumPlayers(); ++u)
	{
		if (m_apxPlayers[u]->IsLocalPlayer())
		{
			return m_apxPlayers[u];
		}
	}
	return NULL;
}

const Asura_Lobby_Player& Asura_Lobby::FindUniqueBestHost() const
{
	Asura_Lobby_Player* pxCurrentBestHost = m_apxPlayers[0];
	for (u_int u = 1; u < GetNumPlayers(); ++u)
	{
		if (!pxCurrentBestHost || m_apxPlayers[u]->IsBetterHost( pxCurrentBestHost ))
		{
			pxCurrentBestHost = m_apxPlayers[u];
		}
	}
	return *pxCurrentBestHost;
}

void Asura_Lobby::InvalidateAllPlayers()
{
	Asura_ReliableConnectionID xInvalidID;
	xInvalidID.Invalidate();

	for (u_int u = 0; u < m_uNumPlayers; u++)
	{
		Asura_Lobby_Player& xPlayer = GetPlayerByIndex( u );
		xPlayer.SetConnectionID( xInvalidID );
	}
}

void Asura_Lobby::RemoveInvalidPlayers()
{
	for (u_int u = 0; u < m_uNumPlayers; u++)
	{
		const Asura_Lobby_Player& xPlayer = GetPlayerByIndex( u );
		if (!xPlayer.GetID().IsValid())
		{
			RemovePlayer( u );
		}
	}
}

void Asura_Lobby::RemoveAllPlayers()
{
	Asura_Assert(!Asura_Lobby_System::IsHost(),"Invalid call to ClearPlayerData()");
	if (!Asura_Lobby_System::IsHost())
	{
		u_int uNumPlayersAtStart = m_uNumPlayers;
		for (u_int u = 0; u < uNumPlayersAtStart; u++)
		{
			RemovePlayer( 0 );
		}
		Asura_Assert(m_uNumPlayers == 0,"");
	}
}

Asura_Lobby_Player& Asura_Lobby::GetPlayerByIndex(u_int uIndex) const
{
	Asura_Assert(uIndex < GetNumPlayers(), "invalid player index");
	Asura_Assert(uIndex < GetMaxPlayers(), "invalid player index");
	return *m_apxPlayers[uIndex];
}

const Asura_UnicodeChar* Asura_Lobby::GetPlayerNameByIndex(u_int uIndex) const
{
	Asura_Assert(uIndex < GetNumPlayers(), "invalid player index");
	Asura_Assert(uIndex < GetMaxPlayers(), "invalid player index");
	return m_apxPlayers[uIndex]->GetName();
}

void Asura_Lobby::SetData( const void* pData )
{
	Asura_Mem::Copy( &m_xData, pData, sizeof(m_xData) );
}

u_int Asura_Lobby::GetNumPlayersForTeam( u_char ucTeam ) const
{
	Asura_Assert(ucTeam < GetNumTeams(), "invalid team index");
	return m_apxTeams[ucTeam]->GetNumPlayers();
}

u_int Asura_Lobby::GetTeamPlayerIndex( u_char ucTeam, u_int uIndexInTeam ) const
{
	Asura_Assert(ucTeam < GetNumTeams(), "invalid team index");
	Asura_Assert(uIndexInTeam < GetNumPlayersForTeam(ucTeam), "invalid team player index");
	return m_apxTeams[ucTeam]->GetPlayerMapping(uIndexInTeam);
}

const Asura_Lobby_Player& Asura_Lobby::GetTeamPlayerByIndex( u_char ucTeam, u_int uIndexInTeam ) const
{
	return GetPlayerByIndex(GetTeamPlayerIndex(ucTeam, uIndexInTeam));
}

const Asura_UnicodeChar* Asura_Lobby::GetTeamName(u_char ucTeam) const
{
	Asura_Assert(ucTeam < GetNumTeams(), "invalid team");
	if (ucTeam < GetNumTeams())
	{
		return m_apxTeams[ucTeam]->GetTeamName();
	}
	return NULL;
}

void Asura_Lobby_Player::SetConnectionID( Asura_ReliableConnectionID xID )
{
	// This sets the data as new, rather than changed, so that
	// the clients get a complete update
	m_xData.m_xID = xID; 
	m_bDataNew = (xID.IsValid() && Asura_Lobby_System::IsHost());
}

void Asura_Lobby_Player::Initialise( const Asura_Lobby_PlayerData* pxData )
{
	Asura_Assert(pxData,"");

	Asura_Mem::Copy( &m_xData, pxData, sizeof(m_xData) );
	m_xData.m_xID = pxData->m_xID;

	//We assume this will not change for the duration of the connection
	Asura_Lobby_GameConnectData xLocal;
	xLocal.Fill();

	m_bLocalPlayer = (*this == xLocal);
	m_bDataChanged = false;
	m_bDataNew = Asura_Lobby_System::IsHost();
}

void Asura_Lobby_Player::UpdateData( const Asura_Lobby_ClientStatusMsg* pxData )
{
	Asura_Assert(pxData,"");

	Asura_Assert(Asura_Lobby_System::IsHost(),"Should only be called on host");
	SetReadyStatus( pxData->IsReady() );
	SetRegistered( pxData->HasRegistered() );
	SetMicrophoneAttached( pxData->IsMicrophoneAttached() );
	SetVoiceChannels( pxData->GetVoiceChannels() ); 
	SetProjectData( pxData->GetProjectData() );

	//Set to ASURA_LOBBY_INVALID_TEAM on client, unless the client requests a new species
	if (pxData->GetRequestedTeam() != ASURA_LOBBY_INVALID_TEAM)
	{
		SetTeam( pxData->GetRequestedTeam() );
	}
}

void Asura_Lobby_Player::UpdateData( const Asura_Lobby_PlayerStatusMsg* pxData )
{
	Asura_Assert(pxData,"");

	Asura_Assert(!Asura_Lobby_System::IsHost(),"Should not be called on host");
	SetClientGuid( pxData->GetClientGuid() );
	SetReadyStatus( pxData->GetReadyStatus() );
	SetRegistered( pxData->HasRegistered() );
	SetMicrophoneAttached( pxData->IsMicrophoneAttached() );
	SetTeam( pxData->GetTeam() );
	SetVoiceChannels( pxData->GetVoiceChannels() ); 
	SetProjectData( pxData->GetProjectData() );

	//We're not interested on remote clients
	m_bDataChanged = false;
}

void Asura_Lobby_Player::UpdateTimer( void )
{
	m_fTimer -= Asura_Timers::GetFrameTime();
}

bool Asura_Lobby_Player::GetReadyStatus() const
{
	// on client machines, return local status for self to ensure it's bang up to date.
	if (!Asura_Lobby_System::IsHost() && IsLocalPlayer())
	{
		return Asura_Lobby_System::GetLocalPlayerReady();
	}

	return m_xData.m_bReady;
}

bool Asura_Lobby_Player::IsBetterHost( const Asura_Lobby_Player* pxOther ) const
{
	Asura_Assert(pxOther,"No player to compare host score with");
	if (!pxOther) return false;

	if (m_xData.m_cHostScore > pxOther->GetHostScore())
	{
		return true;
	}
	else if (m_xData.m_cHostScore < pxOther->GetHostScore())
	{
		return false;
	}
	else
	{
		return (m_xData.m_xID.AsInt() < pxOther->GetID().AsInt());
	}
}

bool Asura_Lobby_Player::IsABestHost() const
{
	char cBestScore = CHAR_MIN;
	for (u_int u = 0; u < m_xLobbyParent.GetNumPlayers(); ++u)
	{
		if (m_xLobbyParent.GetPlayerByIndex(u).GetHostScore() >= cBestScore)
		{
			cBestScore = m_xLobbyParent.GetPlayerByIndex(u).GetHostScore();
		}
	}

	return (GetHostScore() >= cBestScore);
}

void Asura_Lobby_Player::SetTeam( u_char ucTeam )
{
	// allow team to be == max teams since that represents "invalid team"
	Asura_Assert(ucTeam <= ASURA_LOBBY_MAX_TEAMS, "invalid team");
	const u_char ucOldTeam = m_xData.m_ucTeam;
	m_xData.m_ucTeam = ucTeam;

	if (ucOldTeam != ucTeam)
	{
		u_int uIndex = 0;
		if (m_xLobbyParent.FindPlayerIndex( GetID(), uIndex ))
		{
			m_xLobbyParent.RemovePlayerFromTeam(uIndex, ucOldTeam);
			m_xLobbyParent.AddPlayerToTeam(uIndex, ucTeam);
			return;
		}

		Asura_Assert(false,"Couldn't change lobby players teams");
		m_bDataChanged = true;
	}
}

void Asura_Lobby_Player::SetProjectData( const u_char* pucData )
{
	if (pucData) 
	{
		Asura_Mem::Copy( m_xData.m_aucProjectData, pucData, ASURA_LOBBY_PLAYER_PROJECT_DATA_SIZE ); 
		m_bDataChanged = true;
	}
}

void Asura_Lobby_ConnectData::Fill()
{
	// write all the relevant data to the connection data message
	Asura_PrintfToBfr( m_awcName, L"%s", ASURA_LOBBY_PLAYER_NAME_BUFFER_SIZE, true, Asura_Lobby_System::GetLocalPlayerName());

	//This will be based on completion data (which players provide themselves). Randomise for now.
	m_cHostScore = static_cast<char>(Asura_Random::GetInt( CHAR_MIN, CHAR_MAX ));

#ifdef ASURA_USE_MERIT_SYSTEM
	m_xMerit = Asura_Lobby_System::GetLocalPlayerMerit();
	// check that merit and padding are the same size
	ASURA_STATIC_ASSERT(sizeof(m_xMerit) == sizeof(u_int));
#endif

	Platform_Fill();
}

//////////////////////////////////////////////////////////////////////////////
// Asura_GameCompletionHistory
//////////////////////////////////////////////////////////////////////////////
// Do we want the ranked or friendly game completion record?
ASURA_GAME_COMPLETION_RECORD Asura_GameCompletionHistory::GetIndex() const
{
	return (Asura_Lobby_System::IsUsingRankedLobbies() ? ASURA_GAME_COMPLETION_RECORD_RANKED : ASURA_GAME_COMPLETION_RECORD_FRIENDLY);
}

// Get the completion ratio in the range (0,1) (the closer to one the better).
// Adjusted to reduce the impact of this ratio if only a small number of games have been played.
float Asura_GameCompletionHistory::GetAdjustedCompletionRatio() const
{
	if (GetNumGamesCompleted() > GetNumGamesStarted())
	{
		Asura_Assert(0, "Invalid game completion data");
		return 0.5f;
	}

	const float fRatio = (GetNumGamesCompleted() + 0.5f) / (GetNumGamesStarted() + 1.0f);
	return fRatio;
}

#undef CHECK_DATA_SIZE

#endif // #ifdef ASURA_SUPPORT_LOBBY
// eof
