#include "LobbyServer.h"
#include "MessageIdentifiers.h"
#include "BitStream.h"
#include "StringCompressor.h"
#include "LobbyDBSpec.h"
#include "RankingServerDBSpec.h"
#include "DS_Table.h"
#include "TableSerializer.h"
#include "RakPeerInterface.h"
#include "GetTime.h"
#include <RakAssert.h>

using namespace RakNet;

static const int MAX_BINARY_DATA_LENGTH=10000000;
static const char *DEFAULT_FRIEND_INVITE_BODY="I would like to be your friend.";
static const char *DEFAULT_FRIEND_ACCEPT_BODY="I have accepted your friends invitation.";
static const char *DEFAULT_FRIEND_DECLINE_BODY="I do not wish to be friends with you.";
static const int MAX_ALLOWED_EMAIL_RECIPIENTS=128;
static const int LOBBY_SERVER_MAX_PUBLIC_SLOTS=128;
static const int LOBBY_SERVER_MAX_PRIVATE_SLOTS=128;

#ifdef _MSC_VER
#pragma warning( push )
#endif

int UserCompBySysAddr( SystemAddress const &key, LobbyServerUser * const &data )
{
	if (key < data->systemAddress)
		return -1;
	if (key == data->systemAddress)
		return 0;
	return 1;
}
int UserCompById( LobbyDBSpec::DatabaseKey const &key, LobbyServerUser * const &data )
{
	if (key < data->GetID())
		return -1;
	if (key == data->GetID())
		return 0;
	return 1;
}
LobbyServerUser::LobbyServerUser()
{
	downloadedFriends=false;
	data=0;
	status=LOBBY_USER_STATUS_OFFLINE;
	title=0;
	currentRoom=0;
}
LobbyServerUser::~LobbyServerUser()
{
	if (data)
		data->Deref();

	unsigned i;
	for (i=0; i < friends.Size(); i++)
		friends[i]->Deref();

	for (i=0; i < ignoredUsers.Size(); i++)
		ignoredUsers[i]->Deref();
}
bool LobbyServerUser::AddFriend(LobbyDBSpec::AddFriend_Data *_friend)
{
	if (IsFriend(_friend->friendId.databaseRowId)==false)
	{
		_friend->AddRef();
		friends.Insert(_friend);
		return true;
	}
	return false;
}
void LobbyServerUser::RemoveFriend(LobbyDBSpec::DatabaseKey friendId)
{
	unsigned i;
	for (i=0; i < friends.Size(); i++)
	{
		if (friends[i]->friendId.databaseRowId==friendId)
		{
			friends[i]->Deref();
			friends.RemoveAtIndex(i);
			break;
		}
	}
}
bool LobbyServerUser::IsFriend(LobbyDBSpec::DatabaseKey friendId)
{
	unsigned i;
	for (i=0; i < friends.Size(); i++)
	{
		if (friends[i]->friendId.databaseRowId==friendId)
			return true;
	}
	return false;
}
bool LobbyServerUser::AddIgnoredUser(LobbyDBSpec::AddToIgnoreList_Data *ignoredUser)
{
	if (IsIgnoredUser(ignoredUser->ignoredUser.databaseRowId)==false)
	{
		ignoredUser->AddRef();
		ignoredUsers.Insert(ignoredUser);
		return true;
	}
	return false;
}
void LobbyServerUser::RemoveIgnoredUser(LobbyDBSpec::DatabaseKey userId)
{
	unsigned i;
	for (i=0; i < ignoredUsers.Size(); i++)
	{
		if (ignoredUsers[i]->ignoredUser.databaseRowId==userId)
		{
			ignoredUsers[i]->Deref();
			ignoredUsers.RemoveAtIndex(i);
			break;
		}
	}
}
bool LobbyServerUser::IsIgnoredUser(LobbyDBSpec::DatabaseKey userId)
{
	unsigned i;
	for (i=0; i < ignoredUsers.Size(); i++)
	{
		if (ignoredUsers[i]->ignoredUser.databaseRowId==userId)
			return true;
	}
	return false;
}
LobbyServerClan::LobbyServerClan()
{
}
LobbyServerClan::~LobbyServerClan()
{

}
LobbyDBSpec::DatabaseKey LobbyServerUser::GetID(void) const
{
	return data->id.databaseRowId;
}
LobbyServer::LobbyServer()
{
	orderingChannel=0;
	rakPeer=0;
	packetPriority=MEDIUM_PRIORITY;
}
LobbyServer::~LobbyServer()
{
	Clear();
}
void LobbyServer::Clear(void)
{
	ClearUsers();
	ClearTitles();
	ClearLobbyGames();
}
void LobbyServer::OnAttach(RakPeerInterface *peer)
{
	rakPeer=peer;
}
PluginReceiveResult LobbyServer::OnReceive(RakPeerInterface *peer, Packet *packet)
{
	if (packet->data[0]==ID_LOBBY_GENERAL && packet->length>=2)
	{
		switch (packet->data[1])
		{
		case LOBBY_MSGID_LOGIN:
			Login_NetMsg(packet);
			break;
		case LOBBY_MSGID_SET_TITLE_LOGIN_ID:
			SetTitleLoginId_NetMsg(packet);
			break;
		case LOBBY_MSGID_LOGOFF:
			Logoff_NetMsg(packet);
			break;
		case LOBBY_MSGID_REGISTER_ACCOUNT:
			RegisterAccount_NetMsg(packet);
			break;
		case LOBBY_MSGID_UPDATE_ACCOUNT:
			UpdateAccount_NetMsg(packet);
			break;
		case LOBBY_MSGID_CHANGE_HANDLE:
			ChangeHandle_NetMsg(packet);
			break;
		case LOBBY_MSGID_RETRIEVE_PASSWORD_RECOVERY_QUESTION:
			RetrievePasswordRecoveryQuestion_NetMsg(packet);
			break;
		case LOBBY_MSGID_RETRIEVE_PASSWORD:
			RetrievePassword_NetMsg(packet);
			break;
		case LOBBY_MSGID_DOWNLOAD_FRIENDS:
			DownloadFriends_NetMsg(packet);
			break;
		case LOBBY_MSGID_SEND_ADD_FRIEND_INVITE:
			SendAddFriendInvite_NetMsg(packet);
			break;
		case LOBBY_MSGID_ACCEPT_ADD_FRIEND_INVITE:
			AcceptAddFriendInvite_NetMsg(packet);
			break;
		case LOBBY_MSGID_DECLINE_ADD_FRIEND_INVITE:
			DeclineAddFriendInvite_NetMsg(packet);
			break;
		case LOBBY_MSGID_DOWNLOAD_IGNORE_LIST:
			DownloadIgnoreList_NetMsg(packet);
			break;
		case LOBBY_MSGID_UPDATE_IGNORE_LIST:
			UpdateIgnoreList_NetMsg(packet);
			break;
		case LOBBY_MSGID_REMOVE_FROM_IGNORE_LIST:
			RemoveFromIgnoreList_NetMsg(packet);
			break;
		case LOBBY_MSGID_DOWNLOAD_EMAILS:
			DownloadEmails_NetMsg(packet);
			break;
		case LOBBY_MSGID_SEND_EMAIL:
			SendEmail_NetMsg(packet);
			break;
		case LOBBY_MSGID_DELETE_EMAIL:
			DeleteEmail_NetMsg(packet);
			break;
		case LOBBY_MSGID_UPDATE_EMAIL_STATUS:
			UpdateEmailStatus_NetMsg(packet);
			break;
		case LOBBY_MSGID_SEND_IM:
			SendIM_NetMsg(packet);
			break;
		case LOBBY_MSGID_CREATE_ROOM:
			CreateRoom_NetMsg(packet);
			break;
		case LOBBY_MSGID_DOWNLOAD_ROOMS:
			DownloadRooms_NetMsg(packet);
			break;
		case LOBBY_MSGID_LEAVE_ROOM:
			LeaveRoom_NetMsg(packet);
			break;
		case LOBBY_MSGID_JOIN_ROOM:
			JoinRoom_NetMsg(packet);
			break;
		case LOBBY_MSGID_JOIN_ROOM_BY_FILTER:
			JoinRoomByFilter_NetMsg(packet);
			break;
		case LOBBY_MSGID_ROOM_CHAT:
			RoomChat_NetMsg(packet);
			break;
		case LOBBY_MSGID_INVITE_TO_ROOM:
			InviteToRoom_NetMsg(packet);
			break;
		case LOBBY_MSGID_SET_READY_TO_PLAY_STATUS:
			SetReadyToPlayStatus_NetMsg(packet);
			break;
		case LOBBY_MSGID_KICK_ROOM_MEMBER:
			KickRoomMember_NetMsg(packet);
			break;
		case LOBBY_MSGID_SET_INVITE_ONLY:
			SetRoomIsHidden_NetMsg(packet);
			break;
		case LOBBY_MSGID_SET_ROOM_ALLOW_SPECTATORS:
			SetRoomAllowSpectators_NetMsg(packet);
			break;
		case LOBBY_MSGID_CHANGE_NUM_SLOTS:
			ChangeNumSlots_NetMsg(packet);
			break;
		case LOBBY_MSGID_GRANT_MODERATOR:
			GrantModerator_NetMsg(packet);
			break;
		case LOBBY_MSGID_START_GAME:
			StartGame_NetMsg(packet);
			break;
		case LOBBY_MSGID_SUBMIT_MATCH:
			SubmitMatch_NetMsg(packet);
			break;
		case LOBBY_MSGID_DOWNLOAD_RATING:
			DownloadRating_NetMsg(packet);
			break;
		case LOBBY_MSGID_QUICK_MATCH:
			QuickMatch_NetMsg(packet);
			break;
		case LOBBY_MSGID_CANCEL_QUICK_MATCH:
			CancelQuickMatch_NetMsg(packet);
			break;
		case LOBBY_MSGID_DOWNLOAD_ACTION_HISTORY:
			DownloadActionHistory_NetMsg(packet);
			break;
		case LOBBY_MSGID_ADD_TO_ACTION_HISTORY:
			AddToActionHistory_NetMsg(packet);
			break;
		case LOBBY_MSGID_CREATE_CLAN:
			CreateClan_NetMsg(packet);
			break;
		case LOBBY_MSGID_LEAVE_CLAN:
			LeaveClan_NetMsg(packet);
			break;
		case LOBBY_MSGID_DOWNLOAD_CLANS:
			DownloadClans_NetMsg(packet);
			break;
		case LOBBY_MSGID_SEND_CLAN_JOIN_INVITATION:
			SendClanJoinInvitation_NetMsg(packet);
			break;
		case LOBBY_MSGID_WITHDRAW_CLAN_JOIN_INVITATION:
			WithdrawClanJoinInvitation_NetMsg(packet);
			break;
		case LOBBY_MSGID_ACCEPT_CLAN_JOIN_INVITATION:
			AcceptClanJoinInvitation_NetMsg(packet);
			break;
		case LOBBY_MSGID_REJECT_CLAN_JOIN_INVITATION:
			RejectClanJoinInvitation_NetMsg(packet);
			break;
		case LOBBY_MSGID_DOWNLOAD_MY_CLAN_JOIN_INVITATIONS:
			DownloadMyClanJoinInvitations_NetMsg(packet);
			break;
		case LOBBY_MSGID_SEND_CLAN_MEMBER_JOIN_REQUEST:
			SendClanMemberJoinRequest_NetMsg(packet);
			break;
		case LOBBY_MSGID_WITHDRAW_CLAN_MEMBER_JOIN_REQUEST:
			WithdrawClanMemberJoinRequest_NetMsg(packet);
			break;
		case LOBBY_MSGID_ACCEPT_CLAN_MEMBER_JOIN_REQUEST:
			AcceptClanMemberJoinRequest_NetMsg(packet);
			break;
		case LOBBY_MSGID_REJECT_CLAN_MEMBER_JOIN_REQUEST:
			RejectClanMemberJoinRequest_NetMsg(packet);
			break;
		case LOBBY_MSGID_DOWNLOAD_MY_CLAN_MEMBER_JOIN_REQUESTS:
			DownloadMyClanMemberJoinRequests_NetMsg(packet);
			break;
		case LOBBY_MSGID_SET_CLAN_MEMBER_RANK:
			SetClanMemberRank_NetMsg(packet);
			break;
		case LOBBY_MSGID_CLAN_KICK_AND_BLACKLIST_USER:
			ClanKickAndBlacklistUser_NetMsg(packet);
			break;
		case LOBBY_MSGID_CLAN_UNBLACKLIST_USER:
			ClanUnblacklistUser_NetMsg(packet);
			break;
		case LOBBY_MSGID_DOWNLOAD_MY_BLACKLISTS_TO_CLANS:
			DownloadMyBlacklistsToClans_NetMsg(packet);
			break;
		case LOBBY_MSGID_ADD_POST_TO_CLAN_BOARD:
			AddPostToClanBoard_NetMsg(packet);
			break;
		case LOBBY_MSGID_REMOVE_POST_FROM_CLAN_BOARD:
			RemovePostFromClanBoard_NetMsg(packet);
			break;
		case LOBBY_MSGID_DOWNLOAD_MY_CLAN_BOARDS:
			DownloadMyClanBoards_NetMsg(packet);
			break;
		case LOBBY_MSGID_DOWNLOAD_CLAN_BOARD:
			DownloadClanBoard_NetMsg(packet);
			break;
		case LOBBY_MSGID_DOWNLOAD_CLAN_MEMBER:
			DownloadClanMember_NetMsg(packet);
			break;
		}
		return RR_STOP_PROCESSING_AND_DEALLOCATE;
	}
	else if (packet->data[0]==ID_DISCONNECTION_NOTIFICATION || packet->data[0]==ID_CONNECTION_LOST)
	{
		RemoveUser(packet->systemAddress);
	}

	return RR_CONTINUE_PROCESSING;
}
void LobbyServer::OnCloseConnection(RakPeerInterface *peer, SystemAddress systemAddress)
{
	RemoveUser(systemAddress);
}
void LobbyServer::OnShutdown(RakPeerInterface *peer)
{
	Clear();
}
void LobbyServer::Update(RakPeerInterface *peer)
{
	RakNetTime time = RakNet::GetTime();
	unsigned i;
	LobbyServerUser *user;
	TitleValidationDBSpec::DatabaseKey titleIdToMatch;

	// Try to match users. I will make this more sophisticated and correct later
	if (quickMatchUsers.Size()>=2)
	{
		if (quickMatchUsers[0]->quickMatchUserRequirement >= (int) quickMatchUsers.Size())
		{
			titleIdToMatch=quickMatchUsers[0]->title->titleId;
			DataStructures::List<LobbyServerUser *> matchedUsers;
			for (i=0; i < quickMatchUsers.Size(); i++)
			{
				if (titleIdToMatch==quickMatchUsers[i]->title->titleId)
					matchedUsers.Insert(quickMatchUsers[i]);
			}

			if ((int) matchedUsers.Size()>=quickMatchUsers[0]->quickMatchUserRequirement)
			{
				// Remove matched users
				i=0;
				while (i<quickMatchUsers.Size())
				{
					if (titleIdToMatch==quickMatchUsers[i]->title->titleId)
					{
						quickMatchUsers.RemoveAtIndex(i);
					}
					else
					{
						i++;
					}
				}

				// Start game with matched users
				RakNet::BitStream bs;
				bs.Write((unsigned char)ID_LOBBY_GENERAL);
				bs.Write((unsigned char)LOBBY_MSGID_NOTIFY_START_GAME);
				bs.Write(true); // Quick match
				bs.Write(matchedUsers[0]->systemAddress); // Arbitrary moderator
				bs.Write(titleIdToMatch);
				bs.WriteCompressed((unsigned short) matchedUsers.Size());
				// No spectators
				bs.WriteCompressed((unsigned short) 0);
				for (i=0; i < matchedUsers.Size(); i++)
					SerializeClientSafeCreateUserData(matchedUsers[i], &bs);
				for (i=0; i < matchedUsers.Size(); i++)
				{
					Send(&bs, matchedUsers[i]->systemAddress);
					matchedUsers[i]->status=LOBBY_USER_STATUS_IN_LOBBY_IDLE;
				}
			}
		}
	}
	
	i=0;
	while (i<quickMatchUsers.Size())
	{
		if (quickMatchUsers[i]->quickMatchStopWaiting<time)
		{
			user = quickMatchUsers[i];
			RakNet::BitStream bs;
			bs.Write((unsigned char)ID_LOBBY_GENERAL);
			bs.Write((unsigned char)LOBBY_MSGID_NOTIFY_QUICK_MATCH_TIMEOUT);

			QuickMatchTimeout_PC quickMatchTimeoutContext;
			quickMatchTimeoutContext.Serialize(&bs);
			Send(&bs, user->systemAddress);

			RakAssert(user->status==LOBBY_USER_STATUS_WAITING_ON_QUICK_MATCH);
			user->status=LOBBY_USER_STATUS_IN_LOBBY_IDLE;

			quickMatchUsers.RemoveAtIndex(i);

		}
		else
			i++;
	}
}
void LobbyServer::Login_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	char userIdStr[512];
	char userPw[512];
	
	stringCompressor->DecodeString(userPw, sizeof(userPw), &bs, 0);
	stringCompressor->DecodeString(userIdStr, sizeof(userIdStr), &bs, 0);

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_LOGIN);
	if (GetUserByHandle(userIdStr))
	{
		reply.Write((unsigned char)LC_RC_INVALID_INPUT);
		stringCompressor->EncodeString("This username is already connected.", 512, &reply, 0);
		Send(&reply, packet->systemAddress);
		return;
	}

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu)
	{
		if (lsu->status==LOBBY_USER_STATUS_LOGGING_ON)
		{
			// Already logging on
			reply.Write((unsigned char)LC_RC_IN_PROGRESS);
		}
		else
		{
			// Already logged on
			reply.Write((unsigned char)LC_RC_SUCCESS);
		}
		Send(&reply, packet->systemAddress);
	}
	else
	{
		Login_DBQuery(packet->systemAddress, userIdStr, userPw);

		// TODO - Process download clan query. Call function common to DownloadClans_NetMsg
	}
}
void LobbyServer::Login_DBResult(bool dbSuccess, const char *queryErrorMessage, SystemAddress systemAddress, LobbyDBSpec::GetUser_Data *res, const char *userPw_in)
{
	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_LOGIN);
	if (dbSuccess==false)
	{
		reply.Write((unsigned char)LC_RC_DATABASE_FAILURE);
		stringCompressor->EncodeString(queryErrorMessage,512,&reply,0);
	}
	else if (res->userFound==false)
	{
		reply.Write((unsigned char)LC_RC_UNKNOWN_USER_ID);		
	}
	else if (res->isBanned)
	{
		reply.Write((unsigned char)LC_RC_BANNED_USER_ID);
		stringCompressor->EncodeString(res->banOrSuspensionReason.C_String(),512,&reply,0);
	}
	else if (res->isSuspended)
	{
		reply.Write((unsigned char)LC_RC_SUSPENDED_USER_ID);
		reply.Write(res->suspensionExpiration);
		stringCompressor->EncodeString(res->banOrSuspensionReason.C_String(),512,&reply,0);
	}
	else if (stricmp(res->output.password.C_String(), userPw_in)!=0)
	{
		reply.Write((unsigned char)LC_RC_INVALID_INPUT);
		stringCompressor->EncodeString("Bad password.",512,&reply,0);
	}
	else
	{
		reply.Write((unsigned char)LC_RC_SUCCESS);
		reply.Write(res->id.databaseRowId);
		res->output.Serialize(&reply);

		LobbyServerUser *user = new LobbyServerUser;
		user->data=res;
		user->readyToPlay=false;
		res->AddRef();
		user->systemAddress=systemAddress;
		user->connectionTime=RakNet::GetTime();
		user->status=LOBBY_USER_STATUS_IN_LOBBY_IDLE;
		user->title=0;

		// Inefficient, do it instead when the list is downloaded
		/*
		// Notify friends who had you on their lists, but offline
		unsigned i,j;
		for (i=0; i < users.Size(); i++)
		{
			for (j=0; j < users[i]->friends.Size(); j++)
			{
				if (users[i]->friends[j]->friendId.userId==user->GetID())
				{
					NotifyFriendStatusChange(users[i]->systemAddress, user->GetID(), user->data->id.userHandle.C_String(), FSC_FRIEND_ONLINE);
				}
			}
		}
		*/

		usersBySysAddr.Insert(systemAddress, user, true);
		usersByID.Insert(user->GetID(), user, true);
	}
	Send(&reply,systemAddress);
}
void LobbyServer::AddFriend_DBResult(bool dbSuccess, const char *queryErrorMessage, LobbyDBSpec::AddFriend_Data *res, const char *messageBody, unsigned char language)
{
	LobbyServerUser *u1 = GetUserByID(res->id.databaseRowId);
	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_ACCEPT_ADD_FRIEND_INVITE);
	if (dbSuccess==false)
	{
		reply.Write((unsigned char)LC_RC_DATABASE_FAILURE);
		stringCompressor->EncodeString(queryErrorMessage,512,&reply,0);
	}
	else if (res->userFound==false || res->friendFound==false)
	{
		reply.Write((unsigned char)LC_RC_UNKNOWN_USER_ID);
		stringCompressor->EncodeString(res->queryResult.C_String(),512,&reply,0);
	}
	else if (res->success==false)
	{
		reply.Write((unsigned char)LC_RC_PERMISSION_DENIED);
		stringCompressor->EncodeString(res->queryResult.C_String(),512,&reply,0);
	}
	else
	{
		reply.Write((unsigned char)LC_RC_SUCCESS);

		LobbyServerUser *u2 = GetUserByID(res->friendId.databaseRowId);
		LobbyDBSpec::AddFriend_Data *afd;

		u1->AddFriend(res);
		
		afd = new LobbyDBSpec::AddFriend_Data;
		afd->id.hasDatabaseRowId=true;
		afd->id.databaseRowId=u2->GetID();
		afd->id.handle=u2->data->id.handle;
		afd->friendId.hasDatabaseRowId=true;
		afd->friendId.databaseRowId=u1->GetID();
		afd->friendId.handle=u1->data->id.handle;
		afd->creationTime=res->creationTime;
		if (u2->AddFriend(afd)==false)
			afd->Deref();

		// Send friend notification acceptance to u2
	//	SendIM(u1, u2, messageBody, language);


		// Invite accepted
		FriendInvitation_PC noticeStruct;
		RakNet::BitStream invitationBS;
		invitationBS.Write((unsigned char)ID_LOBBY_GENERAL);
		invitationBS.Write((unsigned char)LOBBY_MSGID_NOTIFY_ADD_FRIEND_RESPONSE);
		noticeStruct._invitee=u1->GetID(); // Replying switches lsu and potentialFriend
		noticeStruct._invitor=u2->GetID();// Replying switches lsu and potentialFriend
		noticeStruct.messageBodyEncodingLanguage=language;
		strncpy(noticeStruct.invitorHandle, u2->data->id.handle.C_String(), sizeof(noticeStruct.invitorHandle)-1);
		noticeStruct.invitorHandle[sizeof(noticeStruct.invitorHandle)-1]=0;
		strncpy(noticeStruct.inviteeHandle, u1->data->id.handle.C_String(), sizeof(noticeStruct.inviteeHandle)-1);
		noticeStruct.inviteeHandle[sizeof(noticeStruct.inviteeHandle)-1]=0;
		noticeStruct.request=false;
		noticeStruct.accepted=true;
		noticeStruct.Serialize(&invitationBS);
		Send(&invitationBS, u2->systemAddress);


		NotifyFriendStatusChange(u1->systemAddress, u2->GetID(), u2->data->id.handle.C_String(),  0);
		NotifyFriendStatusChange(u2->systemAddress, u1->GetID(), u1->data->id.handle.C_String(), 0);
	}

	Send(&reply,u1->systemAddress);
}
void LobbyServer::NotifyFriendStatusChange(SystemAddress target, LobbyDBSpec::DatabaseKey friendId, const char *friendHandle, unsigned char status)
{
	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_NOTIFY_FRIEND_STATUS);
	reply.Write((unsigned char) status);
	reply.Write(friendId);
	reply.Write(true);
	stringCompressor->EncodeString(friendHandle,512,&reply,0);
	Send(&reply,target);
}
void LobbyServer::RetrievePasswordRecoveryQuestion_DBResult(bool dbSuccess, const char *queryErrorMessage, SystemAddress systemAddress, LobbyDBSpec::GetUser_Data *res)
{
	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_RETRIEVE_PASSWORD_RECOVERY_QUESTION);
	if (dbSuccess==false)
	{
		reply.Write((unsigned char)LC_RC_DATABASE_FAILURE);
		stringCompressor->EncodeString(queryErrorMessage,512,&reply,0);
	}
	else if (res->userFound==false)
	{
		reply.Write((unsigned char)LC_RC_UNKNOWN_USER_ID);		
	}
	else
	{
		reply.Write((unsigned char)LC_RC_SUCCESS);
		stringCompressor->EncodeString(res->output.passwordRecoveryQuestion.C_String(),512,&reply,0);
	}
	Send(&reply,systemAddress);
}
void LobbyServer::RetrievePassword_DBResult(bool dbSuccess, const char *queryErrorMessage, SystemAddress systemAddress, LobbyDBSpec::GetUser_Data *res, const char *passwordRecoveryAnswer)
{
	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_RETRIEVE_PASSWORD);

	if (dbSuccess==false)
	{
		reply.Write((unsigned char)LC_RC_DATABASE_FAILURE);
		stringCompressor->EncodeString(queryErrorMessage,512,&reply,0);
	}
	else if (res->userFound==false)
	{
		reply.Write((unsigned char)LC_RC_UNKNOWN_USER_ID);		
	}
	else if (stricmp(passwordRecoveryAnswer, res->output.passwordRecoveryAnswer.C_String())!=0)
	{
		reply.Write((unsigned char)LC_RC_PERMISSION_DENIED);
	}
	else
	{
		reply.Write((unsigned char)LC_RC_SUCCESS);
		stringCompressor->EncodeString(res->output.password.C_String(),512,&reply,0);
	}
	Send(&reply,systemAddress);
}
void LobbyServer::GetFriends_DBResult(bool dbSuccess, const char *queryErrorMessage, LobbyDBSpec::GetFriends_Data *res, SystemAddress systemAddress)
{
	LobbyServerUser *lsu = GetUserByAddress(systemAddress);
	if (lsu)
	{
		lsu->downloadedFriends=true;

		RakNet::BitStream reply;
		reply.Write((unsigned char)ID_LOBBY_GENERAL);
		reply.Write((unsigned char)LOBBY_MSGID_DOWNLOAD_FRIENDS);
		if (dbSuccess==false)
		{
			reply.Write((unsigned char)LC_RC_DATABASE_FAILURE);
			stringCompressor->EncodeString(queryErrorMessage,512,&reply,0);
		}
		else
		{
			reply.Write((unsigned char)LC_RC_SUCCESS);
			reply.WriteCompressed(res->friends.Size());
			unsigned i;
			LobbyServerUser *_friend;
			for (i=0; i < res->friends.Size(); i++)
			{
				_friend = GetUserByID(res->friends[i]->friendId.databaseRowId);
				bool friendIsOnline = _friend!=0;
				reply.Write(friendIsOnline);
				reply.Write(res->friends[i]->friendId.databaseRowId);
				stringCompressor->EncodeString(res->friends[i]->friendId.handle.C_String(), 512, &reply, 0);
				lsu->AddFriend(res->friends[i]);

				// Tell our friends that we are online if they have also already downloaded us into their friends list
				if (_friend)
				{
					if (_friend->IsFriend(lsu->GetID()))
					{
						NotifyFriendStatusChange(_friend->systemAddress, lsu->GetID(), lsu->data->id.handle.C_String(), FSC_FRIEND_ONLINE);
						NotifyFriendStatusChange(lsu->systemAddress, res->friends[i]->friendId.databaseRowId, res->friends[i]->friendId.handle.C_String(), friendIsOnline ? FSC_FRIEND_ONLINE : FSC_FRIEND_OFFLINE);
					}
				}

			}
		}
		Send(&reply,systemAddress);
	}
}
void LobbyServer::CreateUser_DBResult(bool dbSuccess, bool querySuccess, const char *queryErrorMessage, SystemAddress systemAddress, LobbyDBSpec::CreateUser_Data *res)
{
	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_REGISTER_ACCOUNT);
	if (dbSuccess==false)
	{
		reply.Write((unsigned char)LC_RC_DATABASE_FAILURE);
		stringCompressor->EncodeString(queryErrorMessage,512,&reply,0);
	}
	else if (querySuccess==false)
	{
		reply.Write((unsigned char)LC_RC_INVALID_INPUT);
		stringCompressor->EncodeString(queryErrorMessage,512,&reply,0);
	}
	else
	{
		reply.Write((unsigned char)LC_RC_SUCCESS);
		res->Serialize(&reply);
	}
	Send(&reply,systemAddress);
}
void LobbyServer::UpdateHandle_DBResult(LobbyDBSpec::ChangeUserHandle_Data *res)
{
	LobbyServerUser *lsu = GetUserByID(res->id.databaseRowId);
	if (lsu)
	{
		RakNet::BitStream reply;
		reply.Write((unsigned char)ID_LOBBY_GENERAL);
		reply.Write((unsigned char)LOBBY_MSGID_CHANGE_HANDLE);
		
		if (res->success)
		{
			// TODO - Update viewers of this user, so they get the handle updated accordingly
			lsu->data->id.handle=res->newHandle;
			reply.Write((unsigned char)LC_RC_SUCCESS);
		}
		else
		{
			reply.Write((unsigned char)LC_RC_PERMISSION_DENIED);
			stringCompressor->EncodeString(res->queryResult.C_String(),512,&reply,0);
		}

		Send(&reply,lsu->systemAddress);
		
	}
}
void LobbyServer::GetIgnoreList_DBResult(bool dbSuccess, const char *queryErrorMessage, SystemAddress systemAddress, LobbyDBSpec::GetIgnoreList_Data *res)
{
	LobbyServerUser *lsu = GetUserByAddress(systemAddress);
	if (lsu)
	{
		RakNet::BitStream reply;
		reply.Write((unsigned char)ID_LOBBY_GENERAL);
		reply.Write((unsigned char)LOBBY_MSGID_DOWNLOAD_IGNORE_LIST);

		if (dbSuccess)
		{
			reply.Write((unsigned char)LC_RC_SUCCESS);

			reply.WriteCompressed(res->ignoredUsers.Size());
			unsigned i;
			for (i=0; i < res->ignoredUsers.Size(); i++)
			{
				reply.Write(res->ignoredUsers[i]->ignoredUser.databaseRowId);
				stringCompressor->EncodeString(res->ignoredUsers[i]->actions.C_String(), 4096, &reply, 0);
				reply.Write(res->ignoredUsers[i]->creationTime);

				// Store the ignore list in memory too
				lsu->AddIgnoredUser(res->ignoredUsers[i]);
			}
		}
		else
		{
			reply.Write((unsigned char)LC_RC_DATABASE_FAILURE);
			stringCompressor->EncodeString(queryErrorMessage,512,&reply,0);
		}

		Send(&reply,lsu->systemAddress);
	}
}
void LobbyServer::AddToIgnoreList_DBResult(bool dbSuccess, bool querySuccess, const char *queryErrorMessage, LobbyDBSpec::DatabaseKey ignoredUserId,
										   LobbyDBSpec::DatabaseKey myId, LobbyDBSpec::AddToIgnoreList_Data *res)
{
	LobbyServerUser *lsu = GetUserByID(myId);
	if (lsu)
	{
		RakNet::BitStream reply;
		reply.Write((unsigned char)ID_LOBBY_GENERAL);
		reply.Write((unsigned char)LOBBY_MSGID_UPDATE_IGNORE_LIST);

		if (dbSuccess==false)
		{
			reply.Write((unsigned char)LC_RC_DATABASE_FAILURE);
			stringCompressor->EncodeString(queryErrorMessage,512,&reply,0);
		}
		else if (querySuccess==false)
		{
			reply.Write((unsigned char)LC_RC_PERMISSION_DENIED);
			stringCompressor->EncodeString(queryErrorMessage,512,&reply,0);
		}
		else
		{
			reply.Write((unsigned char)LC_RC_SUCCESS);
			reply.Write(res->ignoredUser.databaseRowId);
			stringCompressor->EncodeString(res->actions.C_String(), 4096, &reply, 0);
			reply.Write(res->creationTime);


			lsu->AddIgnoredUser(res);
		}

		Send(&reply,lsu->systemAddress);
	}
}
void LobbyServer::RemoveFromIgnoreList_DBResult(bool dbSuccess, const char *queryErrorMessage, LobbyDBSpec::DatabaseKey unignoredUserId, LobbyDBSpec::DatabaseKey myId)
{
	LobbyServerUser *lsu = GetUserByID(myId);
	if (lsu==0)
		return;

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_REMOVE_FROM_IGNORE_LIST);

	if (dbSuccess==false)
	{
		reply.Write((unsigned char)LC_RC_DATABASE_FAILURE);
		stringCompressor->EncodeString(queryErrorMessage,512,&reply,0);
	}
	else
	{
		reply.Write((unsigned char)LC_RC_SUCCESS);
		reply.Write(unignoredUserId);
		lsu->RemoveIgnoredUser(unignoredUserId);
	}

	Send(&reply,lsu->systemAddress);
}
void LobbyServer::SendEmail_DBResult(bool dbSuccess, bool querySuccess, const char *queryErrorMessage, LobbyDBSpec::SendEmail_Data *res)
{
	LobbyServerUser *lsu = GetUserByID(res->id.databaseRowId);
	if (lsu==0)
		return;

	RakNet::BitStream reply, emailNotify;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_SEND_EMAIL);

	if (dbSuccess==false)
	{
		reply.Write((unsigned char)LC_RC_DATABASE_FAILURE);
		stringCompressor->EncodeString(queryErrorMessage,512,&reply,0);
	}
	else if (querySuccess==false)
	{
		reply.Write((unsigned char)LC_RC_PERMISSION_DENIED);
		stringCompressor->EncodeString(queryErrorMessage,512,&reply,0);
	}
	else
	{
		reply.Write((unsigned char)LC_RC_SUCCESS);

		// This won't work because we may have sent to multiple recipients.
		// res->Serialize(&reply);
		// The user should redownload the list of sent emails.

		// Send back that we are successful in sending these emails
		emailNotify.Write((unsigned char)ID_LOBBY_GENERAL);
		emailNotify.Write((unsigned char)LOBBY_MSGID_INCOMING_EMAIL);
		emailNotify.Write(lsu->GetID());
		stringCompressor->EncodeString(lsu->data->id.handle.C_String(), 512, &emailNotify, 0);
		stringCompressor->EncodeString(res->subject.C_String(), 4096, &emailNotify, 0);

		// Send incoming email notifications to recipients.
		LobbyServerUser *emailRecipient;
		unsigned i;
		for (i=0; i < res->to.Size(); i++)
		{
			if (res->to[i].hasDatabaseRowId)
				emailRecipient = GetUserByID(res->to[i].databaseRowId);
			else
				emailRecipient = GetUserByHandle(res->to[i].handle.C_String());

			if (emailRecipient)
			{
				Send(&emailNotify,emailRecipient->systemAddress);
			}			
		}
	}

	Send(&reply,lsu->systemAddress);
}
void LobbyServer::GetEmails_DBResult(bool dbSuccess, bool querySuccess, const char *queryErrorMessage, LobbyDBSpec::GetEmails_Data *res)
{
	LobbyServerUser *lsu = GetUserByID(res->id.databaseRowId);
	if (lsu==0)
		return;

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_DOWNLOAD_EMAILS);

	if (dbSuccess==false)
	{
		reply.Write((unsigned char)LC_RC_DATABASE_FAILURE);
		stringCompressor->EncodeString(queryErrorMessage,512,&reply,0);
	}
	else if (querySuccess==false)
	{
		reply.Write((unsigned char)LC_RC_PERMISSION_DENIED);
		stringCompressor->EncodeString(queryErrorMessage,512,&reply,0);
	}
	else
	{
		reply.Write((unsigned char)LC_RC_SUCCESS);
		reply.Write(res->inbox);
		reply.WriteCompressed(res->emails.Size());
		unsigned i;
		for (i=0; i < res->emails.Size(); i++)
		{
			res->emails[i]->Serialize(&reply);
		}		
	}
	Send(&reply,lsu->systemAddress);
}
void LobbyServer::DeleteEmail_DBResult(bool dbSuccess, const char *queryErrorMessage, LobbyDBSpec::DatabaseKey myId, LobbyDBSpec::DeleteEmail_Data *res)
{
	LobbyServerUser *lsu = GetUserByID(myId);
	if (lsu==0)
		return;

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_DELETE_EMAIL);

	if (dbSuccess==false)
	{
		reply.Write((unsigned char)LC_RC_DATABASE_FAILURE);
		stringCompressor->EncodeString(queryErrorMessage,512,&reply,0);
	}
	else
	{
		reply.Write((unsigned char)LC_RC_SUCCESS);
		reply.Write(res->emailMessageID);
	}
	Send(&reply,lsu->systemAddress);
}
void LobbyServer::UpdateEmailStatus_DBResult(bool dbSuccess, const char *queryErrorMessage, LobbyDBSpec::DatabaseKey myId, LobbyDBSpec::UpdateEmailStatus_Data *res)
{
	LobbyServerUser *lsu = GetUserByID(myId);
	if (lsu==0)
		return;

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_UPDATE_EMAIL_STATUS);
	if (dbSuccess==false)
	{
		reply.Write((unsigned char)LC_RC_DATABASE_FAILURE);
		stringCompressor->EncodeString(queryErrorMessage,512,&reply,0);
	}
	else
	{
		reply.Write((unsigned char)LC_RC_SUCCESS);
		reply.Write(res->status);
		reply.Write(res->emailMessageID);
		reply.Write(res->wasOpened);
	}
	Send(&reply,lsu->systemAddress);
}
void LobbyServer::AddToActionHistory_DBResult(bool dbSuccess, const char *queryErrorMessage, LobbyDBSpec::AddToActionHistory_Data *res, SystemAddress systemAddress)
{
	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_ADD_TO_ACTION_HISTORY);
	LobbyServerUser *lsu = GetUserByAddress(systemAddress);
	if (lsu==0)
		return;
	res->id.hasDatabaseRowId=true;
	res->id.handle=lsu->data->id.handle;
	res->id.databaseRowId=lsu->GetID();
	if (dbSuccess==false)
	{
		reply.Write((unsigned char)LC_RC_DATABASE_FAILURE);
		stringCompressor->EncodeString(queryErrorMessage,512,&reply,0);
	}
	else
	{
		reply.Write((unsigned char)LC_RC_SUCCESS);
		res->Serialize(&reply);
	}
	Send(&reply,systemAddress);
}
void LobbyServer::GetActionHistory_DBResult(bool dbSuccess, const char *queryErrorMessage, LobbyDBSpec::GetActionHistory_Data *res, SystemAddress systemAddress)
{
	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_DOWNLOAD_ACTION_HISTORY);
	if (dbSuccess==false)
	{
		reply.Write((unsigned char)LC_RC_DATABASE_FAILURE);
		stringCompressor->EncodeString(queryErrorMessage,512,&reply,0);
	}
	else
	{
		reply.Write((unsigned char)LC_RC_SUCCESS);
		res->Serialize(&reply);
	}
	Send(&reply,systemAddress);
}
void LobbyServer::SubmitMatch_DBResult(bool dbSuccess, const char *queryErrorMessage, RankingServerDBSpec::SubmitMatch_Data *res, SystemAddress systemAddress)
{
	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_SUBMIT_MATCH);
	if (dbSuccess==false)
	{
		reply.Write((unsigned char)LC_RC_DATABASE_FAILURE);
		stringCompressor->EncodeString(queryErrorMessage,512,&reply,0);
	}
	else
	{
		reply.Write((unsigned char)LC_RC_SUCCESS);
		//res->Serialize(&reply);
	}
	Send(&reply,systemAddress);
}
void LobbyServer::GetRatingForParticipant_DBResult(bool dbSuccess, const char *queryErrorMessage, RankingServerDBSpec::GetRatingForParticipant_Data *res, SystemAddress systemAddress)
{
	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_DOWNLOAD_RATING);
	if (dbSuccess==false)
	{
		reply.Write((unsigned char)LC_RC_DATABASE_FAILURE);
		stringCompressor->EncodeString(queryErrorMessage,512,&reply,0);
	}
	else
	{
		reply.Write((unsigned char)LC_RC_SUCCESS);
		res->Serialize(&reply);
	}
	Send(&reply,systemAddress);
}
void LobbyServer::UpdateClanMember_DBResult(bool dbSuccess, const char *queryErrorMessage, LobbyDBSpec::UpdateClanMember_Data *res)
{
}
void LobbyServer::UpdateClan_DBResult(bool dbSuccess, const char *queryErrorMessage, LobbyDBSpec::UpdateClan_Data *res)
{
}
void LobbyServer::CreateClan_DBResult(bool dbSuccess, const char *queryErrorMessage, LobbyDBSpec::CreateClan_Data *res)
{
}
void LobbyServer::ChangeClanHandle_DBResult(bool dbSuccess, const char *queryErrorMessage, LobbyDBSpec::ChangeClanHandle_Data *res)
{
}
void LobbyServer::DeleteClan_DBResult(bool dbSuccess, const char *queryErrorMessage, LobbyDBSpec::DeleteClan_Data *res)
{
}
void LobbyServer::GetClans_DBResult(bool dbSuccess, const char *queryErrorMessage, LobbyDBSpec::GetClans_Data *res)
{
}
void LobbyServer::RemoveFromClan_DBResult(bool dbSuccess, const char *queryErrorMessage, LobbyDBSpec::RemoveFromClan_Data *res)
{
}
void LobbyServer::AddToClanBoard_DBResult(bool dbSuccess, const char *queryErrorMessage, LobbyDBSpec::AddToClanBoard_Data *res)
{
}
void LobbyServer::RemoveFromClanBoard_DBResult(bool dbSuccess, const char *queryErrorMessage, LobbyDBSpec::RemoveFromClanBoard_Data *res)
{
}
void LobbyServer::GetClanBoard_DBResult(bool dbSuccess, const char *queryErrorMessage, LobbyDBSpec::GetClanBoard_Data *res)
{
}
void LobbyServer::SetTitleLoginId_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_SET_TITLE_LOGIN_ID);

	LobbyServerUser *user = GetUserByAddress(packet->systemAddress);
	if (user==0)
		return;

	char titleLoginPW[512];
	int titleLoginPWLength;
	char titleIdStr[512];
	bs.ReadAlignedBytesSafe((char*)titleLoginPW,titleLoginPWLength,512);
	stringCompressor->DecodeString(titleIdStr, sizeof(titleIdStr), &bs, 0);

	TitleValidationDBSpec::AddTitle_Data *title=GetTitleDataByPassword(titleLoginPW, titleLoginPWLength);
	if (title)
	{
		reply.Write((unsigned char)LC_RC_SUCCESS);
		user->title=title;
	}
	else
	{
		reply.Write((unsigned char)LC_RC_BAD_TITLE_ID);
	}
	Send(&reply,packet->systemAddress);
}
void LobbyServer::Logoff_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	RemoveUser(packet->systemAddress);

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_LOGOFF);
	Send(&reply,packet->systemAddress);
}
void LobbyServer::RegisterAccount_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyDBSpec::CreateUser_Data *input = AllocCreateUser_Data();
	char titleLoginPW[512];
	int titleLoginPWLength;
	char titleIdStr[512];
	bs.ReadAlignedBytesSafe(titleLoginPW,titleLoginPWLength,512);
	stringCompressor->DecodeString(titleIdStr, sizeof(titleIdStr), &bs, 0);
	input->Deserialize(&bs);

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_REGISTER_ACCOUNT);

	TitleValidationDBSpec::AddTitle_Data *title=GetTitleDataByPassword(titleLoginPW, titleLoginPWLength);
	if (title)
	{
		if (title->allowClientAccountCreation)
		{
			CreateUser_DBQuery(packet->systemAddress, input);
		}
		else
		{
			reply.Write((unsigned char)LC_RC_PERMISSION_DENIED);
			Send(&reply,packet->systemAddress);
			input->Deref();
		}
	}
	else
	{
		reply.Write((unsigned char)LC_RC_BAD_TITLE_ID);
		Send(&reply,packet->systemAddress);
		input->Deref();
	}
}
void LobbyServer::UpdateAccount_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_UPDATE_ACCOUNT);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu)
	{
		if (lsu->title==0)
		{
			reply.Write((unsigned char)LC_RC_UNKNOWN_PERMISSIONS);
		}
		else
		{
			LobbyDBSpec::UpdateUser_Data *input = AllocUpdateUser_Data();
			input->DeserializeBooleans(&bs);
			if (lsu->title->defaultAllowUpdateAccountBalance==false && input->updateAccountBalance==true)
			{
				reply.Write((unsigned char)LC_RC_PERMISSION_DENIED);
				stringCompressor->EncodeString("Title default permission AllowUpdateAccountBalance==false",512,&reply,0);
			}
			else if (lsu->title->defaultAllowUpdateAdminLevel==false && input->updateAdminLevel==true)
			{
				reply.Write((unsigned char)LC_RC_PERMISSION_DENIED);
				stringCompressor->EncodeString("Title default permission AllowUpdateAdminLevel==false",512,&reply,0);
			}
			else if (lsu->title->defaultAllowUpdateAccountNumber==false && input->updateAccountNumber==true)
			{
				reply.Write((unsigned char)LC_RC_PERMISSION_DENIED);
				stringCompressor->EncodeString("Title default permission AllowUpdateAccountNumber==false",512,&reply,0);
			}
			else if (lsu->title->defaultAllowUpdateCCInfo==false && input->updateCCInfo==true)
			{
				reply.Write((unsigned char)LC_RC_PERMISSION_DENIED);
				stringCompressor->EncodeString("Title default permission AllowUpdateCCInfo==false",512,&reply,0);
			}
			else
			{
				// Read twice, once into our struct in memory, again into the struct on the stack to pass to the database
				int readOffset = bs.GetReadOffset();
				input->Deserialize(&bs, &input->input, false);
				bs.SetReadOffset(readOffset);
				input->Deserialize(&bs, &lsu->data->output, false);
				reply.Write((unsigned char)LC_RC_SUCCESS);

				// Don't wait for DB reply back, just assume it works
				input->id.hasDatabaseRowId=true;
				input->id.databaseRowId=lsu->data->id.databaseRowId;
				UpdateUser_DBQuery(input);
			}
		}
	}

	Send(&reply,packet->systemAddress);
}
void LobbyServer::ChangeHandle_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	char newHandle[512];
	stringCompressor->DecodeString(newHandle, 512, &bs, 0);


	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu)
	{
		RakNet::BitStream reply;
		reply.Write((unsigned char)ID_LOBBY_GENERAL);
		reply.Write((unsigned char)LOBBY_MSGID_CHANGE_HANDLE);
		if (lsu->title==0)
		{
			reply.Write((unsigned char)LC_RC_UNKNOWN_PERMISSIONS);
			Send(&reply,packet->systemAddress);
		}
		else
		{
			if (lsu->title->defaultAllowUpdateHandle)
			{
				UpdateHandle_DBQuery(newHandle, lsu->data->id.databaseRowId);
			}
			else
			{
				reply.Write((unsigned char)LC_RC_PERMISSION_DENIED);
				stringCompressor->EncodeString("Title default permission AllowUpdateHandle==false",512,&reply,0);
				Send(&reply,packet->systemAddress);
			}
		}
	}
}
void LobbyServer::RetrievePasswordRecoveryQuestion_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	char userIdStr[512];
	stringCompressor->DecodeString(userIdStr, sizeof(userIdStr), &bs, 0);

	RetrievePasswordRecoveryQuestion_DBQuery(packet->systemAddress, userIdStr);
}
void LobbyServer::RetrievePassword_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	char userIdStr[512];
	char passwordRecoveryAnswer[512];
	stringCompressor->DecodeString(userIdStr, sizeof(userIdStr), &bs, 0);
	stringCompressor->DecodeString(passwordRecoveryAnswer, sizeof(passwordRecoveryAnswer), &bs, 0);


	RetrievePassword_DBQuery(packet->systemAddress, userIdStr, passwordRecoveryAnswer);
}
void LobbyServer::DownloadFriends_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);
	bool ascendingSort;
	bs.Read(ascendingSort);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu)
	{
		GetFriends_DBQuery(packet->systemAddress, lsu->GetID(), ascendingSort);
	}
}
void LobbyServer::SendAddFriendInvite_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	FriendInvitation_PC noticeStruct;
	unsigned char language;
//	LobbyClientUserId friendId;
	
	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	LobbyServerUser *potentialFriend;
	LobbyClientUserId userId;
	char userHandle[512];
	potentialFriend=DeserializeHandleOrId(userHandle,userId,&bs);

	bs.Read(language);
	stringCompressor->DecodeString(noticeStruct.messageBody, sizeof(noticeStruct.messageBody), &bs, language);

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_SEND_ADD_FRIEND_INVITE);

	if (potentialFriend==0)
	{
		reply.Write((unsigned char)LC_RC_DISCONNECTED_USER_ID);
	}
	else if (lsu==potentialFriend)
	{
		// Cannot be a friend to yourself!
		reply.Write((unsigned char)LC_RC_INVALID_INPUT);
	}
	else
	{
		if (lsu->IsIgnoredUser(potentialFriend->GetID()))
		{
			reply.Write((unsigned char)LC_RC_IGNORED);
		}
		else
		{
			if (lsu->IsFriend(potentialFriend->GetID())==false)
			{
				RakNet::BitStream invitationBS;
				invitationBS.Write((unsigned char)ID_LOBBY_GENERAL);
				invitationBS.Write((unsigned char)LOBBY_MSGID_NOTIFY_ADD_FRIEND_RESPONSE);
				noticeStruct._invitee=potentialFriend->GetID();
				noticeStruct._invitor=lsu->GetID();
				noticeStruct.messageBodyEncodingLanguage=language;
				strncpy(noticeStruct.invitorHandle, lsu->data->id.handle.C_String(), sizeof(noticeStruct.invitorHandle)-1);
				noticeStruct.invitorHandle[sizeof(noticeStruct.invitorHandle)-1]=0;
				strncpy(noticeStruct.inviteeHandle, potentialFriend->data->id.handle.C_String(), sizeof(noticeStruct.inviteeHandle)-1);
				noticeStruct.inviteeHandle[sizeof(noticeStruct.inviteeHandle)-1]=0;
				noticeStruct.request=true;
				noticeStruct.Serialize(&invitationBS);
				Send(&invitationBS, potentialFriend->systemAddress);

				// Potential friend acceptance list
				PotentialFriend p;
				p.invitor=lsu->data->id.databaseRowId;
				p.invitee=potentialFriend->data->id.databaseRowId;
				AddToPotentialFriends(p);
			}

			reply.Write((unsigned char)LC_RC_SUCCESS);
		}
	}

	Send(&reply,packet->systemAddress);

}
void LobbyServer::AcceptAddFriendInvite_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyClientUserId friendId;
	unsigned char messageBodyEncodingLanguage;
	char messageBody[4096];
	bs.Read(messageBodyEncodingLanguage);
	bs.Read(friendId);
	stringCompressor->DecodeString(messageBody, sizeof(messageBody), &bs, messageBodyEncodingLanguage);
	if (messageBody[0]==0)
	{
		RakAssert(DEFAULT_FRIEND_ACCEPT_BODY[0]);
		strncpy(messageBody, DEFAULT_FRIEND_ACCEPT_BODY, sizeof(messageBody)-1 );
		messageBody[sizeof(messageBody)-1]=0;
	}

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu)
	{
		RakNet::BitStream reply;
		reply.Write((unsigned char)ID_LOBBY_GENERAL);
		reply.Write((unsigned char)LOBBY_MSGID_ACCEPT_ADD_FRIEND_INVITE);

		LobbyServerUser *potentialFriend = GetUserByID(friendId);
		if (potentialFriend==0)
		{
			// Not online
			reply.Write((unsigned char)LC_RC_DISCONNECTED_USER_ID);
			Send(&reply,packet->systemAddress);
		}
		else if (lsu->IsFriend(friendId))
		{
			// Already a friend
			reply.Write((unsigned char)LC_RC_ALREADY_CURRENT_STATE);
			Send(&reply,packet->systemAddress);
		}
		else
		{
			PotentialFriend p;
			p.invitor=friendId;
			p.invitee=lsu->data->id.databaseRowId;
			if (IsInPotentialFriends(p))
			{
				// No longer a potential friend if you are actually a friend. Also should be removed if the db query failed anyway.
				RemoveFromPotentialFriends(p);

				// Start the database query, send success IM when done.
				AddFriend_DBQuery(lsu->GetID(), potentialFriend->GetID(), messageBody, messageBodyEncodingLanguage);
			}
			else
			{
				// No pending friend invitations to accept
				reply.Write((unsigned char)LC_RC_PERMISSION_DENIED);
				Send(&reply,packet->systemAddress);
			}
		}
	}
}
void LobbyServer::DeclineAddFriendInvite_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	FriendInvitation_PC noticeStruct;
	LobbyClientUserId friendId;
	bs.Read(noticeStruct.messageBodyEncodingLanguage);
	bs.Read(friendId);
	stringCompressor->DecodeString(noticeStruct.messageBody, sizeof(noticeStruct.messageBody), &bs, noticeStruct.messageBodyEncodingLanguage);
	if (noticeStruct.messageBody[0]==0)
	{
		RakAssert(DEFAULT_FRIEND_DECLINE_BODY[0]);
		strncpy(noticeStruct.messageBody, DEFAULT_FRIEND_DECLINE_BODY, sizeof(noticeStruct.messageBody)-1 );
		noticeStruct.messageBody[sizeof(noticeStruct.messageBody)-1]=0;
	}

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu)
	{
		RakNet::BitStream reply;
		reply.Write((unsigned char)ID_LOBBY_GENERAL);
		reply.Write((unsigned char)LOBBY_MSGID_DECLINE_ADD_FRIEND_INVITE);

		LobbyServerUser *potentialFriend = GetUserByID(friendId);
		if (potentialFriend==0)
		{
			// Not online
			reply.Write((unsigned char)LC_RC_DISCONNECTED_USER_ID);
		}
		else if (lsu->IsFriend(friendId))
		{
			// Currently a friend
			reply.Write((unsigned char)LC_RC_INVALID_INPUT);
		}
		else
		{
			PotentialFriend p;
			p.invitor=friendId;
			p.invitee=lsu->data->id.databaseRowId;
			if (IsInPotentialFriends(p))
			{
				// No longer a potential friend if you are actually a friend. Also should be removed if the db query failed anyway.
				RemoveFromPotentialFriends(p);

//				SendIM(lsu, potentialFriend, messageBody, language);


				// Invite rejected
				RakNet::BitStream invitationBS;
				invitationBS.Write((unsigned char)ID_LOBBY_GENERAL);
				invitationBS.Write((unsigned char)LOBBY_MSGID_NOTIFY_ADD_FRIEND_RESPONSE);
				noticeStruct._invitee=lsu->GetID(); // Replying switches lsu and potentialFriend
				noticeStruct._invitor=potentialFriend->GetID();// Replying switches lsu and potentialFriend
				strncpy(noticeStruct.invitorHandle, potentialFriend->data->id.handle.C_String(), sizeof(noticeStruct.invitorHandle)-1);
				noticeStruct.invitorHandle[sizeof(noticeStruct.invitorHandle)-1]=0;
				strncpy(noticeStruct.inviteeHandle, lsu->data->id.handle.C_String(), sizeof(noticeStruct.inviteeHandle)-1);
				noticeStruct.inviteeHandle[sizeof(noticeStruct.inviteeHandle)-1]=0;
				noticeStruct.request=false;
				noticeStruct.accepted=false;
				noticeStruct.Serialize(&invitationBS);
				Send(&invitationBS, potentialFriend->systemAddress);


				reply.Write((unsigned char)LC_RC_SUCCESS);
			}
			else
			{
				// No pending friend invitations to accept
				reply.Write((unsigned char)LC_RC_INVALID_INPUT);
			}
		}

		Send(&reply,packet->systemAddress);
	}
}
void LobbyServer::DownloadIgnoreList_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	bool ascendingSort;
	bs.Read(ascendingSort);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	// Do DB query to download the ignore list
	GetIgnoreList_DBQuery(packet->systemAddress, lsu->GetID(), ascendingSort);	

}
void LobbyServer::UpdateIgnoreList_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	char actionsStr[4096];

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	LobbyServerUser *ignoredUser;
	LobbyClientUserId userId;
	char userHandle[512];
	
	bool hasUserId;
	stringCompressor->DecodeString(userHandle,512,&bs,0);
	bs.Read(hasUserId);
	if (hasUserId)
	{
		bs.Read(userId);
	}
	else
	{
		if (userHandle[0]==0)
		{
			// Nobody specified
			RakNet::BitStream reply;
			reply.Write((unsigned char)ID_LOBBY_GENERAL);
			reply.Write((unsigned char)LOBBY_MSGID_UPDATE_IGNORE_LIST);
			reply.Write((unsigned char)LC_RC_INVALID_INPUT);
			Send(&reply,packet->systemAddress);
			return;
		}

		ignoredUser = GetUserByHandle(userHandle);
		if (ignoredUser)
		{
			hasUserId=true;
			userId=ignoredUser->GetID();
		}
	}

	stringCompressor->DecodeString(actionsStr, 4096, &bs, 0);

	if (hasUserId && lsu->IsIgnoredUser(userId))
	{
		// This guy is already ignored
		RakNet::BitStream reply;
		reply.Write((unsigned char)ID_LOBBY_GENERAL);
		reply.Write((unsigned char)LOBBY_MSGID_UPDATE_IGNORE_LIST);
		reply.Write((unsigned char)LC_RC_ALREADY_CURRENT_STATE);
		Send(&reply,packet->systemAddress);
		return;
	}

	// Do DB query to update the ignore list for a particular user
	AddToIgnoreList_DBQuery(hasUserId, userId, userHandle, lsu->GetID(), actionsStr);

}
void LobbyServer::RemoveFromIgnoreList_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyClientUserId userId;
	bs.Read(userId);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	if (lsu->IsIgnoredUser(userId)==false)
	{
		// This guy is not ignored to begin with
		RakNet::BitStream reply;
		reply.Write((unsigned char)ID_LOBBY_GENERAL);
		reply.Write((unsigned char)LOBBY_MSGID_REMOVE_FROM_IGNORE_LIST);
		reply.Write((unsigned char)LC_RC_ALREADY_CURRENT_STATE);
		Send(&reply,packet->systemAddress);
		return;
	}

	// if not ignored, just return reply.Write((unsigned char)LC_RC_INVALID_INPUT);

	// Do DB query to remove a user from the ignore list
	RemoveFromIgnoreList_DBQuery(userId, lsu->GetID());
}
void LobbyServer::DownloadEmails_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);
	bool ascendingSort, inbox;
	unsigned char language;
	bs.Read(language);
	bs.Read(ascendingSort);
	bs.Read(inbox);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	GetEmails_DBQuery( packet->systemAddress, lsu->GetID(), inbox, ascendingSort);
}
void LobbyServer::SendEmail_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);
	unsigned char language;
	bs.Read(language);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	LobbyDBSpec::SendEmail_Data *input = AllocSendEmail_Data();
	input->Deserialize(&bs);

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_SEND_EMAIL);
	if (input->to.Size()==0 || input->to.Size()>MAX_ALLOWED_EMAIL_RECIPIENTS)
	{
		// No recipients!
		reply.Write((unsigned char)LC_RC_INVALID_INPUT);
		Send(&reply,packet->systemAddress);
		delete input;
		return;
	}
	else if (input->subject.IsEmpty() && input->body.IsEmpty())
	{
		// Empty email
		reply.Write((unsigned char)LC_RC_INVALID_INPUT);
		Send(&reply,packet->systemAddress);
		delete input;
		return;
	}

	input->id.hasDatabaseRowId=true;
	input->id.databaseRowId=lsu->GetID();

	SendEmail_DBQuery(input);
}
void LobbyServer::DeleteEmail_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);
	EmailId emailId;

	bs.Read(emailId);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	DeleteEmail_DBQuery(lsu->GetID(), emailId);
}
void LobbyServer::UpdateEmailStatus_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);
	int newStatus;
	EmailId emailId;
	bool wasOpened;

	bs.Read(emailId);
	bs.Read(newStatus);
	bs.Read(wasOpened);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	UpdateEmailStatus_DBQuery(lsu->GetID(), emailId, newStatus, wasOpened);
}
void LobbyServer::SendIM_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	char *chatBinaryData=0;
	int chatBinaryLength;
	unsigned char languageId;
	char chatMessage[4096];

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;


	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_SEND_IM);
	
	LobbyServerUser *target;
	LobbyClientUserId userId;
	char userHandle[512];
	target=DeserializeHandleOrId(userHandle,userId,&bs);
		
	if (target==0)
	{
		reply.Write((unsigned char)LC_RC_UNKNOWN_USER_ID);
		Send(&reply,packet->systemAddress);
		return;
	}
	if (target==lsu)
	{
		reply.Write((unsigned char)LC_RC_INVALID_INPUT);
		stringCompressor->EncodeString("Cannot IM yourself.", 512, &reply, 0);
		Send(&reply,packet->systemAddress);
		return;
	}

	
	bs.Read(languageId);
	stringCompressor->DecodeString(chatMessage, 4096, &bs, languageId);
	bs.ReadAlignedBytesSafeAlloc(&chatBinaryData, chatBinaryLength, MAX_BINARY_DATA_LENGTH);

	if (chatMessage[0]==0 && chatBinaryLength==0)
	{
		reply.Write((unsigned char)LC_RC_INVALID_INPUT);
		stringCompressor->EncodeString("Both chat message and binary data empty.", 512, &reply, 0);
	}
	else if (target->IsIgnoredUser(lsu->GetID()))
	{
		reply.Write((unsigned char)LC_RC_IGNORED);
	}
	else
	{
		SendIM(lsu, target, chatMessage, languageId);

		reply.Write((unsigned char)LC_RC_SUCCESS);
	}
	
	Send(&reply,packet->systemAddress);
	delete [] chatBinaryData;
}
void LobbyServer::CreateRoom_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	char roomName[512];
	char roomPassword[512];
	bool roomIsHidden;
	int publicSlots, privateSlots;
	bool allowSpectators;
	bool hasCustomProperties;
	DataStructures::Table customProperties;

	stringCompressor->DecodeString(roomName, 512, &bs, 0);
	stringCompressor->DecodeString(roomPassword, 512, &bs, 0);
	bs.Read(roomIsHidden);
	bs.ReadCompressed(publicSlots);
	bs.ReadCompressed(privateSlots);
	bs.Read(allowSpectators);
	bs.Read(hasCustomProperties);
	if (hasCustomProperties)
	{
		TableSerializer::DeserializeTable(&bs, &customProperties);
	}

	if (publicSlots > LOBBY_SERVER_MAX_PUBLIC_SLOTS)
		publicSlots=LOBBY_SERVER_MAX_PUBLIC_SLOTS;
	if (privateSlots > LOBBY_SERVER_MAX_PRIVATE_SLOTS)
		privateSlots=LOBBY_SERVER_MAX_PRIVATE_SLOTS;


	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_CREATE_ROOM);

	// Check validity
	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;
	if (lsu->status!=LOBBY_USER_STATUS_IN_LOBBY_IDLE ||
		publicSlots+privateSlots<2 ||
		roomName[0]==0)
	{
		reply.Write((unsigned char)LC_RC_INVALID_INPUT);
		Send(&reply,packet->systemAddress);
		return;
	}
	if (customProperties.GetColumns().Size() > RAKNET_LOBBY_MAX_USER_DEFINED_COLUMNS)
	{
		reply.Write((unsigned char)LC_RC_INVALID_INPUT);
		Send(&reply,packet->systemAddress);
		return;
	}
	if (lsu->title==0)
	{
		reply.Write((unsigned char)LC_RC_BAD_TITLE_ID);
		Send(&reply,packet->systemAddress);
		return;
	}
	if (hasCustomProperties)
	{
		// Check that reserved column names were not used
		unsigned i,j;
		for (j=0; j < customProperties.GetColumns().Size(); j++)
		{
			for (i=0; i < DefaultTableColumns::TC_LOBBY_ROOM_PTR; i++)
			{
				if (stricmp(DefaultTableColumns::GetColumnName(i),customProperties.GetColumns()[j].columnName)==0)
				{
					reply.Write((unsigned char)LC_RC_INVALID_INPUT);
					Send(&reply,packet->systemAddress);
					return;
				}
			}
		}

		if (customProperties.GetRowCount()!=1)
		{
			reply.Write((unsigned char)LC_RC_INVALID_INPUT);
			Send(&reply,packet->systemAddress);
			return;
		}
	}

	// Check that room name is not already in use.
	LobbyRoom* lobbyRoom = GetLobbyRoomByTitle(roomName, lsu->title->titleId);
	if (lobbyRoom)
	{
		reply.Write((unsigned char)LC_RC_NAME_IN_USE);
		Send(&reply,packet->systemAddress);
		return;
	}

	LobbyRoomsTableContainer* lobbyTitle = GetLobbyRoomsTableContainerById(lsu->title->titleId);
	if (lobbyTitle==0)
	{
		// Create this game
		lobbyTitle = new LobbyRoomsTableContainer;
		lobbyTitle->Initialize(lsu->title->titleId);
		lobbyTitles.Insert(lsu->title->titleId, lobbyTitle, true);
	}

	unsigned int rowId = lobbyTitle->GetFreeRowId();
	DataStructures::Table::Row *roomsTableRow = lobbyTitle->roomsTable->AddRow(rowId);

	// Add user-specified columns that do not already exist
	if (hasCustomProperties)
	{
		unsigned int j, columnIndex;
		for (j=0; j < customProperties.GetColumns().Size(); j++)
		{
			columnIndex = lobbyTitle->roomsTable->ColumnIndex(customProperties.ColumnName(j));
			if (columnIndex==-1)
			{
				if (lobbyTitle->roomsTable->GetColumnCount() < RAKNET_LOBBY_MAX_USER_DEFINED_COLUMNS+DefaultTableColumns::TC_TABLE_COLUMNS_COUNT)
					columnIndex = lobbyTitle->roomsTable->AddColumn(customProperties.ColumnName(j), customProperties.GetColumnType(j));
				else
				{
					// Too many user columns! Don't add this column after all
					continue;
				}
			}

			// Add user rows values
			DataStructures::Table::Row *userRow = customProperties.GetRowByIndex(0,0);
			*(roomsTableRow->cells[columnIndex])=*(userRow->cells[j]);
		}
	}

	// Add required rows
	lobbyRoom = new LobbyRoom;

	// Setup the room
	lobbyRoom->rowId=rowId;
	lobbyRoom->publicSlots=publicSlots;
	lobbyRoom->privateSlots=privateSlots;
	lobbyRoom->titleId=lsu->title->titleId;
	lobbyRoom->SetRoomName(roomName);
	lobbyRoom->SetTitleName(lsu->title->titleName.C_String());
	lobbyRoom->SetRoomPassword(roomPassword);
	lobbyRoom->allowSpectators=allowSpectators;
	lobbyRoom->roomIsHidden=roomIsHidden;
	lobbyRoom->rowInTable=roomsTableRow;
	lobbyRoom->AddRoomMemberToFirstAvailableSlot(lsu->GetID()); // Adds you to an open slot if possible, otherwise a reserved slot
	lobbyRoom->SetModerator(lsu->GetID());
	lobbyRoom->rowInTable = roomsTableRow;

	// Write the room updates to the row in the table for the queries
	lobbyRoom->WriteToRow(true);

	lsu->readyToPlay=false;
	lsu->currentRoom=lobbyRoom;
	lsu->status=LOBBY_USER_STATUS_IN_ROOM;

	reply.Write((unsigned char)LC_RC_SUCCESS);
	lobbyRoom->Serialize(&reply);
	DataStructures::List<int> skipColumnIndices;
	skipColumnIndices.Insert(DefaultTableColumns::TC_LOBBY_ROOM_PTR);
	TableSerializer::SerializeColumns(lobbyTitle->roomsTable,&reply,skipColumnIndices);
	TableSerializer::SerializeRow(roomsTableRow,lobbyRoom->rowId,lobbyTitle->roomsTable->GetColumns(),&reply,skipColumnIndices);
	SerializeClientSafeCreateUserData(lsu,&reply);
	Send(&reply,packet->systemAddress);
}
void LobbyServer::DownloadRooms_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_DOWNLOAD_ROOMS);

	if (lsu->title==0)
	{
		reply.Write((unsigned char)LC_RC_BAD_TITLE_ID);
		Send(&reply,packet->systemAddress);
		return;
	}

	LobbyRoomsTableContainer* lobbyTitle = GetLobbyRoomsTableContainerById(lsu->title->titleId);
	if (lobbyTitle==0 || lobbyTitle->roomsTable->GetRowCount()==0)
	{
		// No rooms available
		reply.Write((unsigned char)LC_RC_SUCCESS);
		reply.Write(false);
		Send(&reply,packet->systemAddress);
		return;
	}
	
	DataStructures::Table::FilterQuery *query;
	unsigned int numQueries;
	TableSerializer::DeserializeFilterQueryList(&bs, &query, &numQueries, RAKNET_LOBBY_MAX_USER_DEFINED_COLUMNS);

	// All columns except the one with the internal pointer
	unsigned columnIds[DefaultTableColumns::TC_TABLE_COLUMNS_COUNT+RAKNET_LOBBY_MAX_USER_DEFINED_COLUMNS];
	unsigned i,columnIdCount;
	for (i=0,columnIdCount=0; i < lobbyTitle->roomsTable->GetColumnCount(); i++)
	{
		if (i!=DefaultTableColumns::TC_LOBBY_ROOM_PTR)
			columnIds[columnIdCount++]=i;
	}

	DataStructures::Table resultTable;
	// Run this query filter on the user's current selected game.
	// No filters means return all rows.
	lobbyTitle->roomsTable->QueryTable(columnIds,columnIdCount,query,numQueries,0,0,&resultTable);

	// Return possibly rooms available
	reply.Write((unsigned char)LC_RC_SUCCESS);
	reply.Write(true);
	TableSerializer::SerializeTable(&resultTable,&reply);
	Send(&reply,packet->systemAddress);

	TableSerializer::DeallocateQueryList(query, numQueries);
}
void LobbyServer::LeaveRoom_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_LEAVE_ROOM);
	if (lsu->currentRoom==0)
	{
		reply.Write((unsigned char)LC_RC_NOT_IN_ROOM);
		Send(&reply,packet->systemAddress);
		return;
	}

	// Leave room
	bool roomIsDead;
	RemoveRoomMember(lsu->currentRoom, lsu->GetID(), &roomIsDead, false, lsu->systemAddress);
	lsu->currentRoom=0;

	// Set user state to in lobby
	lsu->status=LOBBY_USER_STATUS_IN_LOBBY_IDLE;

	// TODO - Update members in lobby

	reply.Write((unsigned char)LC_RC_SUCCESS);
	reply.Write(roomIsDead);
	Send(&reply,packet->systemAddress);

}
void LobbyServer::JoinRoom_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	char roomPassword[512];
	LobbyClientRoomId roomRowId;
	bool joinAsSpectator;

	bs.Read(roomRowId);
	bs.Read(joinAsSpectator);
	stringCompressor->DecodeString(roomPassword,512,&bs,0);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_JOIN_ROOM);
	if (lsu->currentRoom)
	{
		reply.Write((unsigned char)LC_RC_ALREADY_IN_ROOM);
		Send(&reply,packet->systemAddress);
		return;
	}

	LobbyRoom* room = GetLobbyRoomByRowId(roomRowId, lsu->title->titleId);
	if (room==0)
	{
		reply.Write((unsigned char)LC_RC_ROOM_DOES_NOT_EXIST);
		Send(&reply,packet->systemAddress);
		return;
	}

	if (room->roomPassword && strcmp(roomPassword, room->roomPassword)!=0)
	{
		reply.Write((unsigned char)LC_RC_PERMISSION_DENIED);
		stringCompressor->EncodeString("Wrong password.", 512, &reply, 0);
		Send(&reply,packet->systemAddress);
		return;
	}

	bool gotIntoRoom;
	if (joinAsSpectator)
	{
		if (room->allowSpectators==false)
		{
			reply.Write((unsigned char)LC_RC_PERMISSION_DENIED);
			stringCompressor->EncodeString("This room does not allow spectators", 512, &reply, 0);
			Send(&reply,packet->systemAddress);
			return;
		}

		room->AddRoomMemberAsSpectator(lsu->GetID());
		gotIntoRoom=true;
	}
	else
	{
		if (room->HasPrivateSlotInvite(lsu->GetID()))
			gotIntoRoom=room->AddRoomMemberToFirstAvailableSlot(lsu->GetID());
		else
			gotIntoRoom=room->AddRoomMemberToPublicSlot(lsu->GetID());

	}

	if (gotIntoRoom)
	{
		reply.Write((unsigned char)LC_RC_SUCCESS);
		FlagEnteredAndSerializeRoom(lsu, reply, room);		
	}
	else
	{
		if (room->GetAvailablePrivateSlots()>0)
		{
			reply.Write((unsigned char)LC_RC_ROOM_ONLY_HAS_RESERVED);
		}
		else
		{
			reply.Write((unsigned char)LC_RC_ROOM_FULL);
		}
	}

	Send(&reply,packet->systemAddress);

	if (gotIntoRoom)
	{
		// Serialize that the room has an additional member, to all members currently in the room, except yourself
		SendNewMemberToRoom(lsu, room, joinAsSpectator);


		// Update the table of rooms
		room->WriteToRow(false);
	}
}
void LobbyServer::SendNewMemberToRoom(LobbyServerUser *lsu, LobbyRoom* room, bool joinAsSpectator)
{
	// Serialize new room state to all members
	RakNet::BitStream bs;
	bs.Write((unsigned char)ID_LOBBY_GENERAL);
	bs.Write((unsigned char)LOBBY_MSGID_NOTIFY_ROOM_MEMBER_JOIN);

	RoomMemberJoin_PC join;
	join.memberAddress=lsu->systemAddress;
	join._joinedMember=lsu->GetID();
	join.isSpectator=joinAsSpectator;
	join.isInOpenSlot=room->IsInPublicSlot(join._joinedMember);
	join.isInReservedSlot=room->IsInPrivateSlot(join._joinedMember);
	join.Serialize(&bs);

	// Serialize the new member's data to all other members
	SerializeClientSafeCreateUserData(lsu,&bs);

	SendToRoomMembers(room, &bs, true, lsu->GetID());
}
void LobbyServer::FlagEnteredAndSerializeRoom(LobbyServerUser *lsu, RakNet::BitStream &reply, LobbyRoom* room)
{
	lsu->readyToPlay=false;
	lsu->currentRoom=room;
	lsu->status=LOBBY_USER_STATUS_IN_ROOM;

	room->Serialize(&reply);

	// Serialize each individuals member's data, including your own
	unsigned i;
	LobbyServerUser* user;

	reply.WriteCompressed(room->publicSlotMembers.Size());
	for (i=0; i < room->publicSlotMembers.Size(); i++)
	{
		user=GetUserByID(room->publicSlotMembers[i]);
		RakAssert(user);
		SerializeClientSafeCreateUserData(user,&reply);
	}

	reply.WriteCompressed(room->privateSlotMembers.Size());
	for (i=0; i < room->privateSlotMembers.Size(); i++)
	{
		user=GetUserByID(room->privateSlotMembers[i]);
		RakAssert(user);
		SerializeClientSafeCreateUserData(user,&reply);
	}

	reply.WriteCompressed(room->spectators.Size());
	for (i=0; i < room->spectators.Size(); i++)
	{
		user=GetUserByID(room->spectators[i]);
		RakAssert(user);
		SerializeClientSafeCreateUserData(user,&reply);
	}
}
void LobbyServer::JoinRoomByFilter_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;
	bool joinAsSpectator;

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_JOIN_ROOM_BY_FILTER);

	if (lsu->title==0)
	{
		reply.Write((unsigned char)LC_RC_BAD_TITLE_ID);
		Send(&reply,packet->systemAddress);
		return;
	}

	LobbyRoomsTableContainer* lobbyTitle = GetLobbyRoomsTableContainerById(lsu->title->titleId);
	if (lobbyTitle==0 || lobbyTitle->roomsTable->GetRowCount()==0)
	{
		// No rooms available
		reply.Write((unsigned char)LC_RC_ROOM_DOES_NOT_EXIST);
		Send(&reply,packet->systemAddress);
		return;
	}

	DataStructures::Table::FilterQuery *query;
	unsigned int numQueries;
	TableSerializer::DeserializeFilterQueryList(&bs, &query, &numQueries, RAKNET_LOBBY_MAX_USER_DEFINED_COLUMNS);

	bs.Read(joinAsSpectator);

	DataStructures::Table resultTable;
	// Run this query filter on the user's current selected game.
	// No filters means return all rows.
	lobbyTitle->roomsTable->QueryTable(0,0,query,numQueries,0,0,&resultTable);

	// Find the first room with no password and at least one open slot
	unsigned i;
	for (i=0; i < resultTable.GetRowCount(); i++)
	{
		DataStructures::Table::Row *row = resultTable.GetRowByIndex(i,0);
		if (row->cells[DefaultTableColumns::TC_OPEN_PUBLIC_SLOTS]>0)
		{
			LobbyRoom* room = (LobbyRoom*) row->cells[DefaultTableColumns::TC_LOBBY_ROOM_PTR]->ptr;
			if (room->roomPassword==0 &&
				(joinAsSpectator==false || room->allowSpectators==true))
			{
				if (joinAsSpectator)
					room->AddRoomMemberAsSpectator(lsu->GetID());
				else
					room->AddRoomMemberToPublicSlot(lsu->GetID());

				// Found a room, return success
				reply.Write((unsigned char)LC_RC_SUCCESS);
				FlagEnteredAndSerializeRoom(lsu,reply,room);
				Send(&reply,packet->systemAddress);

				// Serialize that the room has an additional member, to all members currently in the room, except yourself
				SendNewMemberToRoom(lsu, room, joinAsSpectator);

				// Update the table of rooms, to reflect the new member
				room->WriteToRow(false);
				return;
			}

		}
	}

	reply.Write((unsigned char)LC_RC_ROOM_DOES_NOT_EXIST);
	Send(&reply,packet->systemAddress);
}
void LobbyServer::RoomChat_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	unsigned char languageId;
	char chatMessage[4096];
	char *chatBinaryData=0;
	int chatBinaryLength;

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	if (lsu->currentRoom==0)
		return; // Intentionally no reply

	bs.Read(languageId);
	stringCompressor->DecodeString(chatMessage, 4096, &bs, languageId);
	bs.ReadAlignedBytesSafeAlloc(&chatBinaryData, chatBinaryLength, MAX_BINARY_DATA_LENGTH);

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_NOTIFY_ROOM_CHAT);
	reply.Write(lsu->GetID());
	stringCompressor->EncodeString(lsu->data->id.handle.C_String(), 512, &reply, 0);
	reply.Write(languageId);
	stringCompressor->EncodeString(chatMessage, 4096, &reply, languageId);
	reply.WriteAlignedBytesSafe(chatBinaryData,chatBinaryLength, MAX_BINARY_DATA_LENGTH);

	SendToRoomNotIgnoredNotSelf(lsu, &reply);
	
	delete [] chatBinaryData;
}
void LobbyServer::SetReadyToPlayStatus_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);
	bool isReady;

	bs.Read(isReady);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	if (lsu->currentRoom==0)
		return;

	lsu->readyToPlay=isReady;

	// Broadcast to all in room
	RakNet::BitStream broadcast;
	broadcast.Write((unsigned char)ID_LOBBY_GENERAL);
	broadcast.Write((unsigned char)LOBBY_MSGID_NOTIFY_ROOM_MEMBER_READY_STATE);
	RoomMemberReadyStateChange_PC output;
	output._member=lsu->GetID();
	output.isReady=isReady;
	output.Serialize(&broadcast);
	SendToRoomMembers(lsu->currentRoom, &broadcast, true, lsu->GetID());
}
void LobbyServer::InviteToRoom_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	unsigned char languageId;
	char chatMessage[4096];
	char *chatBinaryData=0;
	int chatBinaryLength;
	bool inviteToPrivateSlot;

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_INVITE_TO_ROOM);
	if (lsu->currentRoom==0)
	{
		reply.Write((unsigned char)LC_RC_NOT_IN_ROOM);
		Send(&reply,packet->systemAddress);
		return;
	}

	LobbyServerUser *invitee;
	LobbyClientUserId userId;
	char userHandle[512];
	invitee=DeserializeHandleOrId(userHandle,userId,&bs);

	bs.Read(inviteToPrivateSlot);

	if (invitee==0)
	{
		reply.Write((unsigned char)LC_RC_DISCONNECTED_USER_ID);
		Send(&reply,packet->systemAddress);
		return;
	}

	if (invitee->currentRoom == lsu->currentRoom)
	{
		reply.Write((unsigned char)LC_RC_USER_ALREADY_IN_ROOM);
		Send(&reply,packet->systemAddress);
		return;
	}

	if (inviteToPrivateSlot==false || lsu->currentRoom->AddPrivateSlotInvite(invitee->GetID()))
	{
		reply.Write((unsigned char)LC_RC_SUCCESS);

		bs.Read(languageId);
		stringCompressor->DecodeString(chatMessage, 4096, &bs, languageId);
		bs.ReadAlignedBytesSafeAlloc(&chatBinaryData, chatBinaryLength, MAX_BINARY_DATA_LENGTH);

		RakNet::BitStream targetMessage;
		targetMessage.Write((unsigned char)ID_LOBBY_GENERAL);
		targetMessage.Write((unsigned char)LOBBY_MSGID_NOTIFY_ROOM_INVITE);
		targetMessage.Write(inviteToPrivateSlot);
		targetMessage.Write(lsu->GetID());
		targetMessage.WriteAlignedBytesSafe(chatBinaryData,chatBinaryLength, MAX_BINARY_DATA_LENGTH);
		stringCompressor->EncodeString(lsu->data->id.handle.C_String(), 512, &targetMessage, 0);
		targetMessage.Write(languageId);
		stringCompressor->EncodeString(chatMessage, 4096, &targetMessage, languageId);
		targetMessage.Write(lsu->currentRoom->rowId);
		stringCompressor->EncodeString(lsu->currentRoom->roomName, 512, &targetMessage, 0);
		Send(&targetMessage, invitee->systemAddress);
		delete [] chatBinaryData;
	}
	else
	{
		reply.Write((unsigned char)LC_RC_PERMISSION_DENIED);
		stringCompressor->EncodeString("Room invite queue full, probably a bug. Please email support.", 512, &reply, 0);
	}


	Send(&reply,packet->systemAddress);
}
void LobbyServer::KickRoomMember_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyClientUserId userId;
	bs.Read(userId);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_KICK_ROOM_MEMBER);
	if (lsu->currentRoom==0)
	{
		reply.Write((unsigned char)LC_RC_NOT_IN_ROOM);
		Send(&reply,packet->systemAddress);
		return;
	}
	
	if (lsu->currentRoom->GetModerator()!=lsu->GetID())
	{
		reply.Write((unsigned char)LC_RC_MODERATOR_ONLY);
		Send(&reply,packet->systemAddress);
		return;
	}

	LobbyServerUser *targetUser = GetUserByID(userId);
	if (targetUser==0)
	{
		reply.Write((unsigned char)LC_RC_DISCONNECTED_USER_ID);
		Send(&reply,packet->systemAddress);
		return;
	}

	if (targetUser->currentRoom != lsu->currentRoom)
	{
		reply.Write((unsigned char)LC_RC_USER_NOT_IN_ROOM);
		Send(&reply,packet->systemAddress);
		return;
	}

	if (lsu==targetUser)
	{
		// Can't kick yourself!
		reply.Write((unsigned char)LC_RC_INVALID_INPUT);
		Send(&reply,packet->systemAddress);
		return;
	}

	// Kick OK
	reply.Write((unsigned char)LC_RC_SUCCESS);
	Send(&reply,packet->systemAddress);

	bool roomIsDead;
	RemoveRoomMember(targetUser->currentRoom, targetUser->GetID(), &roomIsDead, true, targetUser->systemAddress);
	targetUser->currentRoom=0;

	// Set user state to in lobby
	targetUser->status=LOBBY_USER_STATUS_IN_LOBBY_IDLE;

	// TODO - Update members in lobby

	// Tell the guy he was kicked out, and by who
	RakNet::BitStream kickNoticeBS;
	kickNoticeBS.Write((unsigned char)ID_LOBBY_GENERAL);
	kickNoticeBS.Write((unsigned char)LOBBY_MSGID_NOTIFY_KICKED_OUT_OF_ROOM);
	KickedOutOfRoom_PC noticeStruct;
	noticeStruct.roomWasDestroyed=roomIsDead;
	noticeStruct._member=lsu->GetID();
	strncpy(noticeStruct.memberHandle, lsu->data->id.handle.C_String(), sizeof(noticeStruct.memberHandle-1));
	noticeStruct.Serialize(&kickNoticeBS);
	Send(&kickNoticeBS, targetUser->systemAddress);
}
void LobbyServer::SetRoomIsHidden_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	bool roomIsHidden;
	bs.Read(roomIsHidden);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_SET_INVITE_ONLY);
	if (lsu->currentRoom==0)
	{
		reply.Write((unsigned char)LC_RC_NOT_IN_ROOM);
		Send(&reply,packet->systemAddress);
		return;
	}

	if (lsu->currentRoom->GetModerator()!=lsu->GetID())
	{
		reply.Write((unsigned char)LC_RC_MODERATOR_ONLY);
		Send(&reply,packet->systemAddress);
		return;
	}

	// Action OK
	reply.Write((unsigned char)LC_RC_SUCCESS);
	reply.Write(roomIsHidden);
	Send(&reply,packet->systemAddress);

	if (lsu->currentRoom->roomIsHidden!=roomIsHidden)
	{
		lsu->currentRoom->roomIsHidden=roomIsHidden;
		lsu->currentRoom->WriteToRow();

		RakNet::BitStream noticeBS;
		noticeBS.Write((unsigned char)ID_LOBBY_GENERAL);
		noticeBS.Write((unsigned char)LOBBY_MSGID_NOTIFY_ROOM_SET_INVITE_ONLY);
		RoomSetroomIsHidden_PC output;
		output.isroomIsHidden=roomIsHidden;
		output.Serialize(&noticeBS);
		SendToRoomMembers(lsu->currentRoom, &noticeBS, true, lsu->GetID());
	}
}
void LobbyServer::SetRoomAllowSpectators_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	bool allowSpectators;
	bs.Read(allowSpectators);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_SET_ROOM_ALLOW_SPECTATORS);
	if (lsu->currentRoom==0)
	{
		reply.Write((unsigned char)LC_RC_NOT_IN_ROOM);
		Send(&reply,packet->systemAddress);
		return;
	}

	if (lsu->currentRoom->GetModerator()!=lsu->GetID())
	{
		reply.Write((unsigned char)LC_RC_MODERATOR_ONLY);
		Send(&reply,packet->systemAddress);
		return;
	}

	// Action OK
	reply.Write((unsigned char)LC_RC_SUCCESS);
	reply.Write(allowSpectators);
	Send(&reply,packet->systemAddress);

	if (lsu->currentRoom->allowSpectators!=allowSpectators)
	{
		lsu->currentRoom->allowSpectators=allowSpectators;
		lsu->currentRoom->WriteToRow();

		RakNet::BitStream noticeBS;
		noticeBS.Write((unsigned char)ID_LOBBY_GENERAL);
		noticeBS.Write((unsigned char)LOBBY_MSGID_NOTIFY_ROOM_SET_ALLOW_SPECTATORS);
		RoomSetAllowSpectators_PC output;
		output.allowSpectators=allowSpectators;
		output.Serialize(&noticeBS);
		SendToRoomMembers(lsu->currentRoom, &noticeBS, true, lsu->GetID());
	}
}
void LobbyServer::ChangeNumSlots_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	int publicSlots;
	int privateSlots;

	bs.ReadCompressed(publicSlots);
	bs.ReadCompressed(privateSlots);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_CHANGE_NUM_SLOTS);
	if (lsu->currentRoom==0)
	{
		reply.Write((unsigned char)LC_RC_NOT_IN_ROOM);
		Send(&reply,packet->systemAddress);
		return;
	}

	if (lsu->currentRoom->GetModerator()!=lsu->GetID())
	{
		reply.Write((unsigned char)LC_RC_MODERATOR_ONLY);
		Send(&reply,packet->systemAddress);
		return;
	}

	// Action OK
	reply.Write((unsigned char)LC_RC_SUCCESS);
	reply.WriteCompressed(publicSlots);
	reply.WriteCompressed(privateSlots);
	Send(&reply,packet->systemAddress);

	if (lsu->currentRoom->publicSlots!=publicSlots ||
		lsu->currentRoom->privateSlots!=privateSlots)
	{
		lsu->currentRoom->publicSlots=publicSlots;
		lsu->currentRoom->privateSlots=privateSlots;
		lsu->currentRoom->WriteToRow();

		RakNet::BitStream noticeBS;
		noticeBS.Write((unsigned char)ID_LOBBY_GENERAL);
		noticeBS.Write((unsigned char)LOBBY_MSGID_NOTIFY_ROOM_CHANGE_NUM_SLOTS);
		RoomChangeNumSlots_PC output;
		output.newPublicSlots=publicSlots;
		output.newPrivateSlots=privateSlots;
		output.Serialize(&noticeBS);
		SendToRoomMembers(lsu->currentRoom, &noticeBS, true, lsu->GetID());
	}
}
void LobbyServer::GrantModerator_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyClientUserId userId;
	bs.Read(userId);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_GRANT_MODERATOR);
	if (lsu->currentRoom==0)
	{
		reply.Write((unsigned char)LC_RC_NOT_IN_ROOM);
		Send(&reply,packet->systemAddress);
		return;
	}

	if (lsu->currentRoom->GetModerator()!=lsu->GetID())
	{
		reply.Write((unsigned char)LC_RC_MODERATOR_ONLY);
		Send(&reply,packet->systemAddress);
		return;
	}

	LobbyServerUser *targetUser = GetUserByID(userId);
	if (targetUser==0)
	{
		reply.Write((unsigned char)LC_RC_DISCONNECTED_USER_ID);
		Send(&reply,packet->systemAddress);
		return;
	}

	if (targetUser->currentRoom != lsu->currentRoom)
	{
		reply.Write((unsigned char)LC_RC_USER_NOT_IN_ROOM);
		Send(&reply,packet->systemAddress);
		return;
	}

	if (lsu==targetUser)
	{
		// Can't change moderator to yourself
		reply.Write((unsigned char)LC_RC_INVALID_INPUT);
		stringCompressor->EncodeString("You are already the moderator.", 512, &reply, 0);
		Send(&reply,packet->systemAddress);
		return;
	}

	if (targetUser->currentRoom->IsInSpectatorSlot(targetUser->GetID()))
	{
		// Can't make a spectator a moderator
		reply.Write((unsigned char)LC_RC_INVALID_INPUT);
		stringCompressor->EncodeString("Spectators cannot be moderators.", 512, &reply, 0);
		Send(&reply,packet->systemAddress);
		return;
	}

	// Grant OK
	reply.Write((unsigned char)LC_RC_SUCCESS);
	reply.Write(userId);
	Send(&reply,packet->systemAddress);

	lsu->currentRoom->SetModerator(userId);

	RakNet::BitStream noticeBS;
	noticeBS.Write((unsigned char)ID_LOBBY_GENERAL);
	noticeBS.Write((unsigned char)LOBBY_MSGID_NOTIFY_ROOM_NEW_MODERATOR);
	NewModerator_PC output;
	output._member=userId;
	output.Serialize(&noticeBS);
	SendToRoomMembers(lsu->currentRoom, &noticeBS, true, lsu->GetID());

}
void LobbyServer::StartGame_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_START_GAME);
	if (lsu->currentRoom==0)
	{
		reply.Write((unsigned char)LC_RC_NOT_IN_ROOM);
		Send(&reply,packet->systemAddress);
		return;
	}

	if (lsu->currentRoom->GetModerator()!=lsu->GetID())
	{
		reply.Write((unsigned char)LC_RC_MODERATOR_ONLY);
		Send(&reply,packet->systemAddress);
		return;
	}

	// At least 2 players, and all users ready
	if (lsu->currentRoom->publicSlotMembers.Size()+lsu->currentRoom->privateSlotMembers.Size()<2)
	{
		reply.Write((unsigned char)LC_RC_NOT_ENOUGH_PARTICIPANTS);
		Send(&reply,packet->systemAddress);
		return;
	}

	unsigned i;
	LobbyServerUser *roomMember;
	bool allReady=true;
	for (i=0; i < lsu->currentRoom->publicSlotMembers.Size(); i++)
	{
		roomMember = GetUserByID(lsu->currentRoom->publicSlotMembers[i]);
		RakAssert(roomMember);
		if (roomMember->readyToPlay==false)
		{
			allReady=false;
			break;
		}
	}

	if (allReady)
	{
		for (i=0; i < lsu->currentRoom->privateSlotMembers.Size(); i++)
		{
			roomMember = GetUserByID(lsu->currentRoom->privateSlotMembers[i]);
			RakAssert(roomMember);
			if (roomMember->readyToPlay==false)
			{
				allReady=false;
				break;
			}
		}
	}

	if (allReady==false)
	{
		reply.Write((unsigned char)LC_RC_NOT_READY);
		Send(&reply,packet->systemAddress);
		return;
	}

	// Action OK
	reply.Write((unsigned char)LC_RC_SUCCESS);
	Send(&reply,packet->systemAddress);

	// Notify all members of game start
	RakNet::BitStream gameStartNotification;
	gameStartNotification.Write((unsigned char)ID_LOBBY_GENERAL);
	gameStartNotification.Write((unsigned char)LOBBY_MSGID_NOTIFY_START_GAME);
	gameStartNotification.Write(false); // Not quick match
	gameStartNotification.Write(lsu->systemAddress); // We are the moderator
	gameStartNotification.Write(lsu->title->titleId);
	gameStartNotification.WriteCompressed((unsigned short) (lsu->currentRoom->publicSlotMembers.Size() + lsu->currentRoom->privateSlotMembers.Size()) );
	gameStartNotification.WriteCompressed((unsigned short) lsu->currentRoom->spectators.Size());
	LobbyServerUser *user;
	for (i=0; i < lsu->currentRoom->publicSlotMembers.Size(); i++)
	{
		user=GetUserByID(lsu->currentRoom->publicSlotMembers[i]);
		SerializeClientSafeCreateUserData(user, &gameStartNotification);
	}
	for (i=0; i < lsu->currentRoom->privateSlotMembers.Size(); i++)
	{
		user=GetUserByID(lsu->currentRoom->privateSlotMembers[i]);
		SerializeClientSafeCreateUserData(user, &gameStartNotification);
	}
	for (i=0; i < lsu->currentRoom->spectators.Size(); i++)
	{
		user=GetUserByID(lsu->currentRoom->spectators[i]);
		SerializeClientSafeCreateUserData(user, &gameStartNotification);
	}
	SendToRoomMembers(lsu->currentRoom, &gameStartNotification, false, 0);

	// Everyone is back to the lobby. They probably should just disconnect.
	//DestroyRoom(lsu->currentRoom);
	LobbyRoom *room = lsu->currentRoom;
	ForceAllRoomMembersToLobby(room);
	RemoveRoom(room);
}
void LobbyServer::SubmitMatch_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	RankingServerDBSpec::SubmitMatch_Data *input = AllocSubmitMatch_Data();
	input->Deserialize(&bs);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	if (IsTrustedIp(packet->systemAddress, lsu->title->titleId)==false)
	{
		RakNet::BitStream reply;
		reply.Write((unsigned char)ID_LOBBY_GENERAL);
		reply.Write((unsigned char)LOBBY_MSGID_SUBMIT_MATCH);
		reply.Write((unsigned char)LC_RC_PERMISSION_DENIED);
		Send(&reply,packet->systemAddress);
	}
	else
		SubmitMatch_DBQuery(packet->systemAddress, input);
}
void LobbyServer::DownloadRating_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	RankingServerDBSpec::GetRatingForParticipant_Data *input = AllocGetRatingForParticipant_Data();;
	input->Deserialize(&bs);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	GetRating_DBQuery(packet->systemAddress, input);
}
void LobbyServer::QuickMatch_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	int requiredSlots, timeoutInSeconds;
	bs.ReadCompressed(requiredSlots);
	bs.ReadCompressed(timeoutInSeconds);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_QUICK_MATCH);

	if (lsu->status==LOBBY_USER_STATUS_IN_ROOM)
	{
		reply.Write((unsigned char)LC_RC_BUSY);
	}
	else if (lsu->status==LOBBY_USER_STATUS_WAITING_ON_QUICK_MATCH)
	{
		reply.Write((unsigned char)LC_RC_IN_PROGRESS);
	}
	else if (lsu->status!=LOBBY_USER_STATUS_IN_LOBBY_IDLE)
	{
		reply.Write((unsigned char)LC_RC_INVALID_INPUT);
	}
	else if (timeoutInSeconds < 1)
	{
		reply.Write((unsigned char)LC_RC_INVALID_INPUT);
	}
	else if (requiredSlots<2 || requiredSlots>128)
	{
		reply.Write((unsigned char)LC_RC_INVALID_INPUT);
	}
	else if (lsu->title==0)
	{
		reply.Write((unsigned char)LC_RC_BAD_TITLE_ID);
	}
	else
	{
		reply.Write((unsigned char)LC_RC_SUCCESS);

		// Limit to 30 seconds until I get a more fair algorithm in place
		if (timeoutInSeconds > 30)
			timeoutInSeconds = 30;

		lsu->status=LOBBY_USER_STATUS_WAITING_ON_QUICK_MATCH;

		// Add to quick match list
#ifdef _DEBUG
		unsigned i;
		for (i=0; i < quickMatchUsers.Size(); i++)
			if (quickMatchUsers[i]==lsu)
				RakAssert(0);
#endif
		quickMatchUsers.Insert(lsu);
		lsu->quickMatchStopWaiting=RakNet::GetTime()+timeoutInSeconds*1000;
		lsu->quickMatchUserRequirement=requiredSlots;
	}
	Send(&reply,packet->systemAddress);
}
void LobbyServer::CancelQuickMatch_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_CANCEL_QUICK_MATCH);
	if (lsu->status==LOBBY_USER_STATUS_WAITING_ON_QUICK_MATCH)
	{
		reply.Write((unsigned char)LC_RC_SUCCESS);
		lsu->status=LOBBY_USER_STATUS_IN_LOBBY_IDLE;

		// Remove from quick match list
		RemoveFromQuickMatchList(lsu);
		
	}
	else
	{
		reply.Write((unsigned char)LC_RC_INVALID_INPUT);
	}

	
	Send(&reply,packet->systemAddress);
}
void LobbyServer::DownloadActionHistory_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	bool ascendingSort;
	bs.Read(ascendingSort);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	DownloadActionHistory_DBQuery(packet->systemAddress, ascendingSort, lsu->GetID());
}
void LobbyServer::AddToActionHistory_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyDBSpec::AddToActionHistory_Data* input = AllocAddToActionHistory_Data();
	input->Deserialize(&bs);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_ADD_TO_ACTION_HISTORY);

	if ((lsu->title && lsu->title->defaultAllowClientsUploadActionHistory) ||
		IsTrustedIp(packet->systemAddress, lsu->title->titleId))
	{
		input->id.databaseRowId=lsu->GetID();
		input->id.hasDatabaseRowId=true;
		AddToActionHistory_DBQuery(packet->systemAddress, input);
	}
	else
	{
		if (lsu->title==0)
			reply.Write((unsigned char)LC_RC_UNKNOWN_PERMISSIONS);
		else
			reply.Write((unsigned char)LC_RC_PERMISSION_DENIED);
		Send(&reply,packet->systemAddress);
	}
}
void LobbyServer::CreateClan_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	LobbyDBSpec::CreateClan_Data *createClan = AllocCreateClan_Data();
	bool failIfAlreadyInClan;
	createClan->initialClanData.Deserialize(&bs);
	createClan->leaderData.Deserialize(&bs);
	bs.Read(failIfAlreadyInClan);

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_CREATE_CLAN);

	// 1. Fail if (already in clan or pending clan integer>0) and failIfAlreadyInClan==true
	// 2. Increment pending clan integer.
	// 3. Process LobbyDBSpec::CreateClan_Data to database
	// If successful, unset processing, create clan in memory
	// Decrement pending clan integer
	// 4. Reply to user. Wait until DB processing is done as this could fail due to duplicate names.

}
void LobbyServer::LeaveClan_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	ClanId clanId;
	bool dissolveIfClanLeader;
	bool autoSendEmailIfClanDissolved;
	bs.Read(clanId);
	bs.Read(dissolveIfClanLeader);
	bs.Read(autoSendEmailIfClanDissolved);

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_LEAVE_CLAN);

	// 1. GetClanByUser(); // If clanId==0, return clan if user only has one. Else return clan lookup
	// 2. Wait until DB processing done. Otherwise we could leave a clan that wasn't created yet, but still be in that clan due to late processing by the DB.
	// 3. If dissolveIfClanLeader, and clan leader, add delete clan DB command.
	// 3A. LOBBY_MSGID_NOTIFY_ for every online member include the leader that the clan is dissolved.
	// 3B. If autoSendEmailIfClanDissolved, do so for every member
	// 3C. Delete clan and clan members out of memory
}
void LobbyServer::DownloadClans_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	LobbyClientUserId userId;
	char userHandle[512];
	LobbyServerUser *target=DeserializeHandleOrId(userHandle,userId,&bs);
	bool ascendingSort;
	bs.Read(ascendingSort);

	// If target==0 && userId==0 && userHandle==0 then target=lsu

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_DOWNLOAD_CLANS);

	/// Do DB query. Don't just use memory in case it changed from an external source.
}
void LobbyServer::SendClanJoinInvitation_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	ClanId clanId;
	bs.Read(clanId);
	LobbyClientUserId userId;
	char userHandle[512];
	LobbyServerUser *target=DeserializeHandleOrId(userHandle,userId,&bs);

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_SEND_CLAN_JOIN_INVITATION);

	// If unknown clan, use only clan. If 0 or >1 clans, return failure
	// If target==lsu, can't invite yourself
	// If no clan, return failure
	// If not leader or subleader, return failure
	// If member is blacklisted and you are not leader, return failure
	// If reply pending, fail (already processed)
	// Tag reply pending, to avoid contention
	// Process DB
	// If success
	//		if (target) send LOBBY_MSGID_NOTIFY_
	// Return result

}
void LobbyServer::WithdrawClanJoinInvitation_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	ClanId clanId;
	bs.Read(clanId);
	LobbyClientUserId userId;
	char userHandle[512];
	LobbyServerUser *target=DeserializeHandleOrId(userHandle,userId,&bs);

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_WITHDRAW_CLAN_JOIN_INVITATION);

	// If unknown clan, use only clan. If 0 or >1 clans, return failure
	// If target==lsu, can't uninvite yourself
	// If no clan, return failure
	// If not leader or subleader, return failure
	// If no such invitation pending, return failure
	// If reply pending, fail (already processed)
	// Tag reply pending, to avoid contention
	// Process DB
	// If success
	//		if (target) send LOBBY_MSGID_NOTIFY_
	// Return result
}
void LobbyServer::AcceptClanJoinInvitation_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	LobbyClientUserId clanId;
	char clanHandle[512];
	LobbyServerClan *clan=DeserializeClanHandleOrId(clanHandle,clanId,&bs);
	LobbyDBSpec::UpdateClanMember_Data *updateClanMember = AllocUpdateClanMember_Data();
	updateClanMember->Deserialize(&bs);
	bool failIfAlreadyInClan;
	bs.Read(failIfAlreadyInClan);

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_ACCEPT_CLAN_JOIN_INVITATION);

	// If unknown clan, use only clan. If 0 clans, return failure.
	// If already in a clan and set, fail
	// If no such invitation, fail
	// If reply pending, fail
	// Tag reply pending, to avoid contention
	// Process DB
	// If success, add member to clan, send LOBBY_MSGID_NOTIFY_ to online clan members.
	// Update memory
	// Return result
}
void LobbyServer::RejectClanJoinInvitation_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	LobbyClientUserId clanId;
	char clanHandle[512];
	LobbyServerClan *clan=DeserializeClanHandleOrId(clanHandle,clanId,&bs);
	bool oneClanPerUser;
	bs.Read(oneClanPerUser);

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_REJECT_CLAN_JOIN_INVITATION);

	// if unknown clan, fail
	// If no such invitation, fail
	// If reply pending, fail
	// Tag reply pending, to avoid contention
	// Process DB
	// Send LOBBY_MSGID_NOTIFY_ to whoever originally invited this member
	// Return result
}
void LobbyServer::DownloadMyClanJoinInvitations_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	bool ascendingSort;
	bs.Read(ascendingSort);
	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_DOWNLOAD_MY_CLAN_JOIN_INVITATIONS);

	// Process DB
	// Return result
}
void LobbyServer::SendClanMemberJoinRequest_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	LobbyClientUserId clanId;
	char clanHandle[512];
	LobbyServerClan *clan=DeserializeClanHandleOrId(clanHandle,clanId,&bs);
	LobbyDBSpec::UpdateClanMember_Data *updateClanMember = AllocUpdateClanMember_Data();
	updateClanMember->Deserialize(&bs);

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_SEND_CLAN_MEMBER_JOIN_REQUEST);

	// if unknown clan, fail
	// If already invited, fail
	// If blacklisted, fail
	// If reply pending, fail (already processed)
	// Tag reply pending, to avoid contention
	// Process DB
	// If db success, send LOBBY_MSGID_NOTIFY_ to all leaders and subleaders online
	// Return result
}
void LobbyServer::WithdrawClanMemberJoinRequest_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	LobbyClientUserId clanId;
	char clanHandle[512];
	LobbyServerClan *clan=DeserializeClanHandleOrId(clanHandle,clanId,&bs);

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_WITHDRAW_CLAN_MEMBER_JOIN_REQUEST);

	// if unknown clan, fail
	// If no pending request, fail
	// If reply pending, fail (already processed)
	// Tag reply pending, to avoid contention
	// Process DB
	// If db success, send LOBBY_MSGID_NOTIFY_ to all leaders and subleaders online
	// Return result
}
void LobbyServer::AcceptClanMemberJoinRequest_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	ClanId clanId;
	bs.Read(clanId);
	LobbyClientUserId userId;
	char userHandle[512];
	LobbyServerUser *target=DeserializeHandleOrId(userHandle,userId,&bs);
	bool failIfAlreadyInClan;
	bs.Read(failIfAlreadyInClan);

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_ACCEPT_CLAN_MEMBER_JOIN_REQUEST);

	// if unknown clan, fail
	// If no pending request for this user, fail
	// If not leader or subleader, fail
	// If target is already in a clan and set, fail
	// If reply pending, fail (already processed)
	// Tag reply pending, to avoid contention
	// Process DB
	// If db success, send LOBBY_MSGID_NOTIFY_ to all members
	// Return result
}
void LobbyServer::RejectClanMemberJoinRequest_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	ClanId clanId;
	bs.Read(clanId);
	LobbyClientUserId userId;
	char userHandle[512];
	LobbyServerUser *target=DeserializeHandleOrId(userHandle,userId,&bs);

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_REJECT_CLAN_MEMBER_JOIN_REQUEST);

	// if unknown clan, fail
	// If no pending request for this user, fail
	// If not leader or subleader, fail
	// If reply pending, fail (already processed)
	// Tag reply pending, to avoid contention
	// Process DB
	// If db success, send LOBBY_MSGID_NOTIFY_ to all leaders and subleaders
	// Return result
}
void LobbyServer::DownloadMyClanMemberJoinRequests_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	bool ascendingSort;
	bs.Read(ascendingSort);

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_DOWNLOAD_MY_CLAN_MEMBER_JOIN_REQUESTS);

	// Just a DB query
}
void LobbyServer::SetClanMemberRank_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	ClanId clanId;
	bs.Read(clanId);
	LobbyClientUserId userId;
	char userHandle[512];
	LobbyServerUser *target=DeserializeHandleOrId(userHandle,userId,&bs);
	LobbyDBSpec::ClanMemberStatus newRank;
	bs.Read(newRank);

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_SET_CLAN_MEMBER_RANK);

	// if unknown clan, fail
	// If not leader fail
	// Process DB
	// If db success, send LOBBY_MSGID_NOTIFY_ to all members
	// Return result
}
void LobbyServer::ClanKickAndBlacklistUser_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	ClanId clanId;
	bs.Read(clanId);
	LobbyClientUserId userId;
	char userHandle[512];
	LobbyServerUser *target=DeserializeHandleOrId(userHandle,userId,&bs);
	bool kick;
	bool blacklist;
	bs.Read(kick);
	bs.Read(blacklist);

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_CLAN_KICK_AND_BLACKLIST_USER);

	// if unknown clan, fail
	// If not leader fail
	// Process DB
	// If db success, if kicked, send LOBBY_MSGID_NOTIFY_ to all members. if banned and not kicked, send to all leaders and subleaders
	// Return result
}
void LobbyServer::ClanUnblacklistUser_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	ClanId clanId;
	bs.Read(clanId);
	LobbyClientUserId userId;
	char userHandle[512];
	LobbyServerUser *target=DeserializeHandleOrId(userHandle,userId,&bs);

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_CLAN_UNBLACKLIST_USER);
	
	// if unknown clan, fail
	// If not leader fail
	// If not blacklisted, fail
	// Process DB
}
void LobbyServer::DownloadMyBlacklistsToClans_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	bool ascendingSort;
	bs.Read(ascendingSort);

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_DOWNLOAD_MY_BLACKLISTS_TO_CLANS);

	// Download my clans, filter out blacklists, reply
}
void LobbyServer::AddPostToClanBoard_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	ClanId clanId;
	bs.Read(clanId);
	LobbyDBSpec::AddToClanBoard_Data *addToClanBoard = AllocAddToClanBoard_Data();
	addToClanBoard->Deserialize(&bs);
	bool failIfNotLeader;
	bool failIfNotMember;
	bool failIfBlacklisted;
	bs.Read(failIfNotLeader);
	bs.Read(failIfNotMember);
	bs.Read(failIfBlacklisted);

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_ADD_POST_TO_CLAN_BOARD);

	// If unknown clan, fail
	// If one of 3 failure checks, fail
	// Do DB Query
	// Reply
}
void LobbyServer::RemovePostFromClanBoard_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	ClanId clanId;
	bs.Read(clanId);
	ClanBoardPostId postId;
	bs.Read(postId);
	bool allowMemberToRemoveOwnPosts;
	bs.Read(allowMemberToRemoveOwnPosts);

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_REMOVE_POST_FROM_CLAN_BOARD);

	// If unknown clan, fail
	// If one of 1 failure checks and not leader or subleader, fail
	// Do DB Query
	// Reply
}
void LobbyServer::DownloadMyClanBoards_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	bool ascendingSort;
	bs.Read(ascendingSort);

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_DOWNLOAD_MY_CLAN_BOARDS);

	// Do DB Query for each of my clans
	// Reply
}
void LobbyServer::DownloadClanBoard_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	LobbyClientUserId clanId;
	char clanHandle[512];
	LobbyServerClan *clan=DeserializeClanHandleOrId(clanHandle,clanId,&bs);
	bool ascendingSort;
	bs.Read(ascendingSort);


	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_DOWNLOAD_CLAN_BOARD);

	// If no such clan, fail
	// Do DB Query for this one clan
	// Reply
}
void LobbyServer::DownloadClanMember_NetMsg(Packet *packet)
{
	RakNet::BitStream bs(packet->data, packet->length, false);
	bs.IgnoreBytes(sizeof(char) * 2);

	LobbyServerUser *lsu = GetUserByAddress(packet->systemAddress);
	if (lsu==0)
		return;

	LobbyClientUserId clanId;
	char clanHandle[512];
	LobbyServerClan *clan=DeserializeClanHandleOrId(clanHandle,clanId,&bs);
	LobbyClientUserId userId;
	char userHandle[512];
	LobbyServerUser *user=DeserializeHandleOrId(userHandle,userId,&bs);

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_DOWNLOAD_CLAN_MEMBER);

	// If no such clan or user, fail
	// Do DB Query
	// Reply
}
void LobbyServer::ClearUsers(void)
{
	unsigned i;
	for (i=0; i < usersBySysAddr.Size(); i++)
	{
		usersBySysAddr[i]->data->Deref();
		delete usersBySysAddr[i];
	}
	usersBySysAddr.Clear(true);
	usersByID.Clear(true);
}
void LobbyServer::DestroyRoom(LobbyRoom *room)
{
	// Notify all remaining members (mostly spectators?) that the room is closed
	RakNet::BitStream bs;
	bs.Write((unsigned char)ID_LOBBY_GENERAL);
	bs.Write((unsigned char)LOBBY_MSGID_NOTIFY_ROOM_DESTROYED);
	SendToRoomMembers(room, &bs);
	ForceAllRoomMembersToLobby(room);

	RemoveRoom(room);
}
void LobbyServer::RemoveUser(SystemAddress sysAddr)
{
	unsigned friendsIndex;
	unsigned index;
	bool objectExists;
	index = usersBySysAddr.GetIndexFromKey(sysAddr, &objectExists);
	if (objectExists)
	{
		LobbyServerUser *lsu = usersBySysAddr[index];
		RemoveFromPotentialFriends(lsu->data->id.databaseRowId);

		for (friendsIndex=0; friendsIndex < lsu->friends.Size(); friendsIndex++)
		{
			LobbyServerUser* _friend = GetUserByID(lsu->friends[friendsIndex]->friendId.databaseRowId);
			if (_friend)
			{
				NotifyFriendStatusChange(_friend->systemAddress, lsu->GetID(), lsu->data->id.handle.C_String(), FSC_FRIEND_OFFLINE);
			}
		}

		// Remove from the room
		if (lsu->currentRoom)
		{
			bool roomIsDead;
			RemoveRoomMember(lsu->currentRoom, lsu->GetID(), &roomIsDead, false, lsu->systemAddress);
		}

		usersByID.Remove(usersBySysAddr[index]->GetID());
		delete usersBySysAddr[index];
		usersBySysAddr.RemoveAtIndex(index);
		

		RemoveFromQuickMatchList(lsu);
	}
}
void LobbyServer::RemoveRoomMember(LobbyRoom *room, LobbyDBSpec::DatabaseKey memberId, bool *roomIsDead, bool wasKicked, SystemAddress memberAddr)
{
	// Note to self: Don't forget to update the member's room pointer, if necessary.

	*roomIsDead=false;
	if (room==0)
		return;
	bool gotNewModerator;
	room->RemoveRoomMember(memberId, &gotNewModerator);
	if (room->RoomIsDead())
	{
		DestroyRoom(room);
	}
	else
	{
		// Serialize new room state to all members
		RakNet::BitStream bs;
		bs.Write((unsigned char)ID_LOBBY_GENERAL);
		bs.Write((unsigned char)LOBBY_MSGID_NOTIFY_ROOM_MEMBER_DROP);

		RoomMemberDrop_PC drop;
		drop.memberAddress=memberAddr;
		drop._droppedMember=memberId;
		drop.wasKicked=wasKicked;
		drop.gotNewModerator=gotNewModerator;
		drop._newModerator=room->GetModerator();
		drop.Serialize(&bs);

		SendToRoomMembers(room, &bs);

		// Update the table of rooms
		room->WriteToRow(false);
	}
}
void LobbyServer::SendToRoomMembers(LobbyRoom *room, RakNet::BitStream *bs, bool hasSkipId, LobbyDBSpec::DatabaseKey skipId)
{
	LobbyServerUser* user;

	unsigned i;
	for (i=0; i < room->publicSlotMembers.Size(); i++)
	{
		if (hasSkipId && skipId==room->publicSlotMembers[i])
			continue;
		user=GetUserByID(room->publicSlotMembers[i]);
		Send(bs, user->systemAddress);
	}
	for (i=0; i < room->privateSlotMembers.Size(); i++)
	{
		if (hasSkipId && skipId==room->privateSlotMembers[i])
			continue;
		user=GetUserByID(room->privateSlotMembers[i]);
		Send(bs, user->systemAddress);
	}

	for (i=0; i < room->spectators.Size(); i++)
	{
		if (hasSkipId && skipId==room->spectators[i])
			continue;
		user=GetUserByID(room->spectators[i]);
		Send(bs, user->systemAddress);
	}
}
void LobbyServer::ForceAllRoomMembersToLobby(LobbyRoom *room)
{
	LobbyServerUser* user;

	unsigned i;
	for (i=0; i < room->publicSlotMembers.Size(); i++)
	{
		user=GetUserByID(room->publicSlotMembers[i]);
		user->currentRoom=0;
		user->status=LOBBY_USER_STATUS_IN_LOBBY_IDLE;
	}
	for (i=0; i < room->privateSlotMembers.Size(); i++)
	{
		user=GetUserByID(room->privateSlotMembers[i]);
		user->currentRoom=0;
		user->status=LOBBY_USER_STATUS_IN_LOBBY_IDLE;
	}

	for (i=0; i < room->spectators.Size(); i++)
	{
		user=GetUserByID(room->spectators[i]);
		user->currentRoom=0;
		user->status=LOBBY_USER_STATUS_IN_LOBBY_IDLE;
	}
}
void LobbyServer::RemoveRoom(LobbyRoom *room)
{
	// Remove the room from the table
	LobbyRoomsTableContainer* lobbyTitle;
	bool objectExists;
	unsigned index = lobbyTitles.GetIndexFromKey(room->titleId, &objectExists);
	RakAssert(objectExists);
	lobbyTitle=lobbyTitles[index];
	lobbyTitle->roomsTable->RemoveRow(room->rowId);

	if (lobbyTitle->roomsTable->GetRowCount()==0)
	{
		// If roomsTable is empty, delete it, and the lobbyGame, and remove the lobbyGame pointer from the lobbyGames list.
		// (Destructor does all this)
		delete lobbyTitle;
	}

	lobbyTitles.RemoveAtIndex(index);

	// Delete the room.
	delete room;

	// TODO - update room lists of users in lobby
}
LobbyServerUser* LobbyServer::GetUserByAddress(SystemAddress sysAddr)
{
	LobbyServerUser *out;
	if (usersBySysAddr.GetElementFromKey(sysAddr, out))
		return out;
	return 0;
}
LobbyServerUser* LobbyServer::GetUserByHandle(const char *handle)
{
	unsigned i;
	for (i=0; i < usersBySysAddr.Size(); i++)
		if (stricmp(usersBySysAddr[i]->data->id.handle.C_String(),handle)==0)
			return usersBySysAddr[i];
	return 0;
}
LobbyServerUser* LobbyServer::GetUserByID(LobbyDBSpec::DatabaseKey id)
{
	LobbyServerUser *out;
	if (usersByID.GetElementFromKey(id, out))
		return out;
	return 0;
}
void LobbyServer::SetOrderingChannel(char oc)
{
	orderingChannel=oc;
}
void LobbyServer::SetSendPriority(PacketPriority pp)
{
	packetPriority=pp;
}
bool LobbyServer::Send( const RakNet::BitStream * bitStream, SystemAddress target )
{
	return rakPeer->Send(bitStream, packetPriority, RELIABLE_ORDERED, orderingChannel, target, false);
}
void LobbyServer::ClearTitles(void)
{
	unsigned i;
	for (i=0; i < titles.Size(); i++)
		titles[i]->Deref();
	
	titles.Clear(true);
}
void LobbyServer::ClearLobbyGames(void)
{
	unsigned i;
	for (i=0; i < lobbyTitles.Size(); i++)
	{
		delete lobbyTitles[i];
	}

	lobbyTitles.Clear(true);
}
LobbyRoom* LobbyServer::GetLobbyRoomByTitle(const char *title, TitleValidationDBSpec::DatabaseKey titleId)
{
	LobbyRoomsTableContainer* lobbyGame = GetLobbyRoomsTableContainerById(titleId);
	if (lobbyGame==0)
		return 0;

	if (lobbyGame->roomsTable==0)
		return 0;

	DataStructures::Table resultTable;
	unsigned columnIndexSubset=DefaultTableColumns::TC_LOBBY_ROOM_PTR;
	DataStructures::Table::FilterQuery filter;
	DataStructures::Table::Cell filterCell;
	filterCell.Set(title);
	filter.cellValue=&filterCell;
	filter.columnIndex=DefaultTableColumns::TC_ROOM_NAME;
	filter.operation=DataStructures::Table::QF_EQUAL;

	lobbyGame->roomsTable->QueryTable(&columnIndexSubset, 1, &filter, 1, 0, 0, &resultTable);
	if (resultTable.GetRowCount()>0)
	{
		DataStructures::Table::Row *row = resultTable.GetRowByIndex(0,0);
		LobbyRoom* lobbyRoom = (LobbyRoom*) row->cells[0]->ptr;
		return lobbyRoom;
	}
	return 0;
}
LobbyRoom* LobbyServer::GetLobbyRoomByRowId(LobbyClientRoomId rowId, TitleValidationDBSpec::DatabaseKey titleId)
{
	LobbyRoomsTableContainer* lobbyGame = GetLobbyRoomsTableContainerById(titleId);
	if (lobbyGame==0)
		return 0;

	if (lobbyGame->roomsTable==0)
		return 0;

	DataStructures::Table::Row *row = lobbyGame->roomsTable->GetRowByID(rowId);
	if (row==0)
		return 0;

	LobbyRoom* lobbyRoom = (LobbyRoom*) row->cells[DefaultTableColumns::TC_LOBBY_ROOM_PTR]->ptr;
	return lobbyRoom;
}
LobbyRoomsTableContainer* LobbyServer::GetLobbyRoomsTableContainerById(TitleValidationDBSpec::DatabaseKey gameId)
{
	bool objectExists;
	unsigned index = lobbyTitles.GetIndexFromKey(gameId, &objectExists);
	if (objectExists)
		return lobbyTitles[index];
	return 0;
}
TitleValidationDBSpec::AddTitle_Data* LobbyServer::GetTitleDataByPassword(char *titlePassword, int titlePasswordLength)
{
	unsigned i;
	for (i=0; i < titles.Size(); i++)
	{
		if (titles[i]->titlePasswordLength==titlePasswordLength &&
			memcmp(titles[i]->titlePassword, titlePassword, titlePasswordLength)==0)
			return titles[i];
	}
	return 0;
}
TitleValidationDBSpec::AddTitle_Data* LobbyServer::GetTitleDataById(TitleValidationDBSpec::DatabaseKey titleId)
{
	unsigned i;
	for (i=0; i < titles.Size(); i++)
	{
		if (titles[i]->titleId==titleId)
			return titles[i];
	}
	return 0;
}
bool LobbyServer::SendIM(LobbyServerUser *sender, LobbyServerUser *recipient, const char *messageBody, unsigned char language)
{
	if (messageBody==0 || messageBody[0]==0)
		return false;

	RakNet::BitStream reply;
	reply.Write((unsigned char)ID_LOBBY_GENERAL);
	reply.Write((unsigned char)LOBBY_MSGID_RECEIVE_IM);
	reply.Write(language);
	reply.Write(sender->data->id.databaseRowId);
	stringCompressor->EncodeString(sender->data->id.handle.C_String(), 512, &reply, language);
	stringCompressor->EncodeString(messageBody, 4096, &reply, language);
	Send(&reply, recipient->systemAddress);	
	return true;
}
bool LobbyServer::IsInPotentialFriends(LobbyServer::PotentialFriend p)
{
	unsigned i;
	for (i=0; i < potentialFriends.Size(); i++)
	{
		if (potentialFriends[i].invitee==p.invitee &&
			potentialFriends[i].invitor==p.invitor)
			return true;
	}
	return false;
}
void LobbyServer::AddToPotentialFriends(LobbyServer::PotentialFriend p)
{
	if (IsInPotentialFriends(p))
		return;
	potentialFriends.Insert(p);
}
void LobbyServer::RemoveFromPotentialFriends(LobbyServer::PotentialFriend p)
{
	unsigned i=0;
	while (i < potentialFriends.Size())
	{
		if ((potentialFriends[i].invitee==p.invitee &&
			potentialFriends[i].invitor==p.invitor) ||
			(potentialFriends[i].invitor==p.invitee &&
			potentialFriends[i].invitee==p.invitor)
			)
		{
			potentialFriends.RemoveAtIndex(i);
		}
		else
			i++;
	}
}
void LobbyServer::RemoveFromPotentialFriends(LobbyDBSpec::DatabaseKey user)
{
	unsigned i=0;
	while (i < potentialFriends.Size())
	{
		if (potentialFriends[i].invitee==user || potentialFriends[i].invitor==user)
		{
			potentialFriends.RemoveAtIndex(i);
		}
		else
			i++;
	}
}

void LobbyServer::RemoveFromQuickMatchList(LobbyServerUser *user)
{
	unsigned i;
	for (i=0; i < quickMatchUsers.Size(); i++)
	{
		if (quickMatchUsers[i]==user)
		{
			quickMatchUsers.RemoveAtIndex(i);
			break;
		}
	}
}
void LobbyServer::SerializeClientSafeCreateUserData(LobbyServerUser *user, RakNet::BitStream *bs)
{
	stringCompressor->EncodeString(user->data->id.handle.C_String(),512,bs,0);
	bs->Write(user->GetID());
	user->data->output.SerializeBinaryData(bs);
	user->data->output.SerializeCaptions(bs);
	user->data->output.SerializeAdminLevel(bs);
	bs->Write(user->systemAddress);
	bs->Write(user->readyToPlay);
}

void LobbyServer::SendToRoomNotIgnoredNotSelf(LobbyServerUser* lsu, RakNet::BitStream *reply)
{
	LobbyRoom *room = lsu->currentRoom;
	unsigned i;
	LobbyServerUser* user;

	for (i=0; i < room->publicSlotMembers.Size(); i++)
	{
		if (lsu->GetID()==room->publicSlotMembers[i])
			continue;
		user=GetUserByID(room->publicSlotMembers[i]);
		if (user->IsIgnoredUser(lsu->GetID())==false)
			Send(reply, user->systemAddress);
	}
	for (i=0; i < room->privateSlotMembers.Size(); i++)
	{
		if (lsu->GetID()==room->privateSlotMembers[i])
			continue;
		user=GetUserByID(room->privateSlotMembers[i]);
		if (user->IsIgnoredUser(lsu->GetID())==false)
			Send(reply, user->systemAddress);
	}

	for (i=0; i < room->spectators.Size(); i++)
	{
		if (lsu->GetID()==room->spectators[i])
			continue;
		user=GetUserByID(room->spectators[i]);
		if (user->IsIgnoredUser(lsu->GetID())==false)
			Send(reply, user->systemAddress);
	}
}

LobbyServerUser * LobbyServer::DeserializeHandleOrId(char *userHandle, LobbyClientUserId &userId, RakNet::BitStream *bs)
{
	LobbyServerUser *target;
	bool hasUserId;
	stringCompressor->DecodeString(userHandle,512,bs,0);
	bs->Read(hasUserId);
	if (hasUserId)
	{
		bs->Read(userId);
		target = GetUserByID(userId);
	}
	else
	{
		if (userHandle[0]==0)
			return 0;

		target = GetUserByHandle(userHandle);
	}
	return target;
}
LobbyServerClan * LobbyServer::DeserializeClanHandleOrId(char *clanHandle, ClanId &clanId, RakNet::BitStream *bs)
{
	return 0;
}
bool LobbyServer::IsTrustedIp(SystemAddress systemAddress, TitleValidationDBSpec::DatabaseKey titleId )
{
	unsigned i;
	for (i=0; i < trustedIpList.Size(); i++)
	{
		if (trustedIpList[i]->systemAddress.binaryAddress==systemAddress.binaryAddress &&
			trustedIpList[i]->gameDbId.primaryKey==titleId)
			return true;
	}
	return false;
}
#ifdef _MSC_VER
#pragma warning( pop )
#endif
