int main(void)
{
    printf("This project demonstrates an in-game lobby using the team manager plugin.\n");
    printf("Difficulty: Intermediate\n\n");

    rakPeer=RakNet::RakPeerInterface::GetInstance();
    fullyConnectedMesh2=FullyConnectedMesh2::GetInstance();
    teamManager=TeamManager::GetInstance();
    networkIDManager = NetworkIDManager::GetInstance();
    replicaManager3=new SampleRM3;

    // test offline mode
    /*
    TM_TeamMember tmTeamMember;
    TM_Team tmTeam;
    teamManager->AddWorld(0);
    teamManager->GetWorldAtIndex(0)->ReferenceTeam(&tmTeam,1,false);
    teamManager->GetWorldAtIndex(0)->ReferenceTeamMember(&tmTeamMember,0);
    tmTeamMember.RequestTeam(TeamSelection::AnyAvailable());
    RakAssert(tmTeam.GetTeamMembersCount()==1);
    tmTeam.LeaveTeam(&tmTeamMember, 255);
    RakAssert(tmTeam.GetTeamMembersCount()==0);
    */

    rakPeer->AttachPlugin(fullyConnectedMesh2);
    rakPeer->AttachPlugin(teamManager);
    rakPeer->AttachPlugin(replicaManager3);
    rakPeer->AttachPlugin(fullyConnectedMesh2);

    // Make it so all new connections are registered with FullyConnectedMesh2
    fullyConnectedMesh2->SetAutoparticipateConnections(true);
    // Allocate a world instance to be used for team operations
    teamManager->AddWorld(0);
    // Tell ReplicaManager3 which networkIDManager to use for object lookup, used for automatic serialization
    replicaManager3->SetNetworkIDManager(networkIDManager);
    // Tell ReplicaManager3 and TeamManager to not automatically add new connections, because we wait for host calculation to complete from FullyConnectedMesh2 first
    replicaManager3->SetAutoManageConnections(false,true);
    teamManager->SetAutoManageConnections(false);

    // Just setup user data as an example
    Team teams[TEAM_TYPES_COUNT];
    teams[(int)PLAYER_TEAM_1].teamName="Player_Team_1";
    teams[(int)PLAYER_TEAM_2].teamName="Player_Team 2";
    teams[(int)REFEREE_TEAM].teamName="Referee_Team";

    for (unsigned int i=0; i < TEAM_TYPES_COUNT; i++)
    {
        // Static objects require additional setup before calling reference.
        teams[i].SetNetworkIDManager(networkIDManager);
        teams[i].SetNetworkID(i); // NetworkID value doesn't matter, just needs to be unique
        // We serialize teams before team members, this is required by TeamManager during remote object construction. Serialization occurs in the order that Reference() is called on the object
        replicaManager3->Reference(&teams[i]);

        // Register the team with the teamManager plugin
        // Do not apply team balancing to the referee team
        bool balancingAppliesToThisTeam = i!=REFEREE_TEAM;
        teamManager->GetWorldAtIndex(0)->ReferenceTeam(&teams[i].tmTeam,teams[i].GetNetworkID(),balancingAppliesToThisTeam);
        if (i==REFEREE_TEAM)
            teams[i].tmTeam.SetMemberLimit(1,0);
        else
            teams[i].tmTeam.SetMemberLimit(2,0);
    }

    // Only join the referee team on specific request
    teams[REFEREE_TEAM].tmTeam.SetJoinPermissions(ALLOW_JOIN_SPECIFIC_TEAM);

    // Setup my own
    User *user = new User;
    user->userName = rakPeer->GetMyGUID().ToString();

    // Inform ReplicaManager3 of my user
    replicaManager3->Reference(user);
    // Inform TeamManager of my user's team member info
    teamManager->GetWorldAtIndex(0)->ReferenceTeamMember(&user->tmTeamMember,user->GetNetworkID());

    // Startup RakNet
    RakNet::SocketDescriptor sd;
    sd.socketFamily=AF_INET; // Only IPV4 supports broadcast on 255.255.255.255
    sd.port=60000;
    while (SocketLayer::IsPortInUse(sd.port, sd.hostAddress, sd.socketFamily)==true)
        sd.port++;
    StartupResult sr = rakPeer->Startup(8,&sd,1);
    RakAssert(sr==RAKNET_STARTED);
    rakPeer->SetMaximumIncomingConnections(8);
    rakPeer->SetTimeoutTime(30000,RakNet::UNASSIGNED_SYSTEM_ADDRESS);
    printf("Our guid is %s\n", rakPeer->GetGuidFromSystemAddress(RakNet::UNASSIGNED_SYSTEM_ADDRESS).ToString());
    printf("Started on %s\n", rakPeer->GetMyBoundAddress().ToString(true));

    for (int i=0; i < 32; i++)
    {
        if (rakPeer->GetInternalID(RakNet::UNASSIGNED_SYSTEM_ADDRESS,0).GetPort()!=60000+i)
            rakPeer->AdvertiseSystem("255.255.255.255", 60000+i, 0,0,0);
    }

    PrintCommands();

    bool success;
    bool quit=false;
    char ch;
    Packet *packet;
    while (!quit)
    {
        for (packet = rakPeer->Receive(); packet; rakPeer->DeallocatePacket(packet), packet = rakPeer->Receive())
        {
            switch (packet->data[0])
            {
            case ID_DISCONNECTION_NOTIFICATION:
                printf("ID_DISCONNECTION_NOTIFICATION\n");
                break;
            case ID_NEW_INCOMING_CONNECTION:
            {
                printf("ID_NEW_INCOMING_CONNECTION from %s. guid=%s.\n", packet->systemAddress.ToString(true), packet->guid.ToString());
                // Add mid-game joins to ReplicaManager3 as long as we know who the host is
                if (fullyConnectedMesh2->GetConnectedHost()!=UNASSIGNED_RAKNET_GUID)
                {
                    bool success = replicaManager3->PushConnection(replicaManager3->AllocConnection(packet->systemAddress, packet->guid));
                    RakAssert(success);
                    teamManager->GetWorldAtIndex(0)->AddParticipant(packet->guid);
                }
            }
            break;
            case ID_CONNECTION_REQUEST_ACCEPTED:
            {
                printf("ID_CONNECTION_REQUEST_ACCEPTED from %s. guid=%s.\n", packet->systemAddress.ToString(true), packet->guid.ToString());
                // Add mid-game joins to ReplicaManager3 as long as we know who the host is
                if (fullyConnectedMesh2->GetConnectedHost()!=UNASSIGNED_RAKNET_GUID)
                {
                    bool success = replicaManager3->PushConnection(replicaManager3->AllocConnection(packet->systemAddress, packet->guid));
                    RakAssert(success);
                    teamManager->GetWorldAtIndex(0)->AddParticipant(packet->guid);
                }
            }
            break;
            case ID_CONNECTION_LOST:
                printf("ID_CONNECTION_LOST\n");
                break;
            case ID_ADVERTISE_SYSTEM:
                if (packet->guid!=rakPeer->GetMyGUID())
                    rakPeer->Connect(packet->systemAddress.ToString(false), packet->systemAddress.GetPort(),0,0);
                break;
            case ID_FCM2_NEW_HOST:
            {
                if (packet->guid==rakPeer->GetMyGUID())
                    printf("Got new host (ourselves)");
                else
                    printf("Got new host %s, GUID=%s", packet->systemAddress.ToString(true), packet->guid.ToString());
                RakNet::BitStream bs(packet->data,packet->length,false);
                bs.IgnoreBytes(1);
                RakNetGUID oldHost;
                bs.Read(oldHost);
                // If the old host is different, then this message was due to losing connection to the host.
                if (oldHost!=packet->guid)
                    printf(". Oldhost Guid=%s\n", oldHost.ToString());
                else
                    printf("\n");

                if (oldHost==UNASSIGNED_RAKNET_GUID)
                {
                    // First time calculated host. Add existing connections to ReplicaManager3
                    RegisterFCM2Participants();
                }
            }
            break;
            case ID_TEAM_BALANCER_TEAM_ASSIGNED:
            {
                printf("ID_TEAM_BALANCER_TEAM_ASSIGNED for ");
                TM_World *world;
                TM_TeamMember *teamMember;
                teamManager->DecodeTeamAssigned(packet, &world, &teamMember);
                printf("worldId=%i teamMember=%s\n", world->GetWorldId(), ((User*)teamMember->GetOwner())->userName.C_String());
            }
            break;
            case ID_TEAM_BALANCER_REQUESTED_TEAM_FULL:
            {
                printf("ID_TEAM_BALANCER_REQUESTED_TEAM_FULL\n");
            }
            break;
            case ID_TEAM_BALANCER_REQUESTED_TEAM_LOCKED:
            {
                printf("ID_TEAM_BALANCER_REQUESTED_TEAM_LOCKED\n");
            }
            break;
            case ID_TEAM_BALANCER_TEAM_REQUESTED_CANCELLED:
            {
                printf("ID_TEAM_BALANCER_TEAM_REQUESTED_CANCELLED\n");
            }
            break;
            }
        }

        if (kbhit())
        {
            ch=getch();

            if (ch=='A' || ch=='a')
            {
                printf("Request any team\n");
                success = user->tmTeamMember.RequestTeam(TeamSelection::AnyAvailable());
                printf("Success=%i\n", success);
            }
            if (ch=='B' || ch=='b')
            {
                printf("Request specific team\n");
                char buff1[256];
                printf("Enter team index (0-2): ");
                gets(buff1);
                if (buff1[0]!=0 && buff1[0]>='0' && buff1[0]<='2')
                {
                    success = user->tmTeamMember.RequestTeam(TeamSelection::SpecificTeam(&(teams[buff1[0]-'0'].tmTeam)));
                    printf("Success=%i\n", success);
                }
                else
                {
                    printf("Aborted\n");
                }
            }
            if (ch=='C' || ch=='c')
            {
                printf("Request team switch\n");
                char buff1[256];
                printf("Enter team index to join (0-2): ");
                gets(buff1);
                char buff2[256];
                printf("Enter team index to leave (0-2) or leave empty for all: ");
                gets(buff2);
                if (buff1[0]!=0 && buff1[0]>='0' && buff1[0]<='2' &&
                        (buff2[0]==0 || (buff2[0]>='0' && buff2[0]<='2')))
                {
                    if (buff2[0])
                        success = user->tmTeamMember.RequestTeamSwitch(&(teams[buff1[0]-'0'].tmTeam), &teams[buff2[0]-'0'].tmTeam);
                    else
                        success = user->tmTeamMember.RequestTeamSwitch(&(teams[buff1[0]-'0'].tmTeam), 0);
                    printf("Success=%i\n", success);
                }
                else
                {
                    printf("Aborted\n");
                }
            }
            if (ch=='D' || ch=='d')
            {
                printf("Cancel request team\n");
                char buff1[256];
                printf("Enter team index to cancel (0-2) or leave empty for all: ");
                gets(buff1);
                if ((buff1[0]!=0 && buff1[0]>='0' && buff1[0]<='2') || buff1[0]==0)
                {
                    if (buff1[0])
                        success = user->tmTeamMember.CancelTeamRequest(&(teams[buff1[0]-'0'].tmTeam));
                    else
                        success = user->tmTeamMember.CancelTeamRequest(0);
                    printf("Success=%i\n", success);
                }
                else
                {
                    printf("Aborted\n");
                }
            }
            if (ch=='E' || ch=='e')
            {
                printf("Leave specific team\n");
                char buff1[256];
                printf("Enter team index to leave (0-2): ");
                gets(buff1);
                if (buff1[0]!=0 && buff1[0]>='0' && buff1[0]<='2')
                {
                    success = user->tmTeamMember.LeaveTeam(&(teams[buff1[0]-'0'].tmTeam),0);
                    printf("Success=%i\n", success);
                }
                else
                {
                    printf("Aborted\n");
                }

            }
            if (ch=='F' || ch=='f')
            {
                printf("Leave all teams\n");
                success = user->tmTeamMember.LeaveAllTeams(0);
                printf("Success=%i\n", success);

            }
            if (ch=='G' || ch=='g')
            {
                printf("Set team member limit\n");
                char buff1[256];
                printf("Enter team index to operate on (0-2): ");
                gets(buff1);
                char buff2[256];
                printf("Enter limit (0-9): ");
                gets(buff2);
                if (buff1[0]!=0 && buff1[0]>='0' && buff1[0]<='2' &&
                        buff2[0]!=0 && buff2[0]>='0' && buff2[0]<='9')
                {
                    success = teams[buff1[0]-'0'].tmTeam.SetMemberLimit(buff2[0]-'0',0);
                    printf("Success=%i\n", success);
                }
                else
                {
                    printf("Aborted\n");
                }
            }
            if (ch=='H' || ch=='h')
            {
                printf("Turn on balance teams setting\n");
                success = teamManager->GetWorldAtIndex(0)->SetBalanceTeams(true,0);
                printf("Success=%i\n", success);
            }
            if (ch=='I' || ch=='i')
            {
                printf("Turn off balance teams setting\n");
                success = teamManager->GetWorldAtIndex(0)->SetBalanceTeams(false,0);
                printf("Success=%i\n", success);
            }

            if (ch==' ')
            {
                if (teamManager->GetWorldAtIndex(0)->GetBalanceTeams())
                    printf("Team balancing is on\n");
                else
                    printf("Team balancing is off\n");

                for (unsigned int i=0; i < TEAM_TYPES_COUNT; i++)
                {
                    printf("Team %i. %s %i/%i members ", i+1, teams[i].teamName.C_String(), teams[i].tmTeam.GetTeamMembersCount(), teams[i].tmTeam.GetMemberLimit());
                    for (unsigned int j=0; j < teams[i].tmTeam.GetTeamMembersCount(); j++)
                    {
                        User *u = (User *) teams[i].tmTeam.GetTeamMemberByIndex(j)->GetOwner();
                        printf("%s ", u->userName.C_String());
                    }
                    printf("\n");
                }

                unsigned int numUsers = teamManager->GetWorldAtIndex(0)->GetTeamMemberCount();
                for (unsigned int i=0; i < numUsers; i++)
                {
                    User *u = (User *) teamManager->GetWorldAtIndex(0)->GetTeamMemberByIndex(i)->GetOwner();
                    printf("User %i/%i. %s ", i+1, numUsers, u->userName.C_String());
                    u->PrintTeamStatus();
                    printf("\n");
                }
                printf("\n");
            }
            else if (ch=='q' || ch=='Q')
            {
                printf("Quitting.\n");
                quit=true;
            }
        }

        RakSleep(30);
    }

    rakPeer->Shutdown(100);
    replicaManager3->Clear();
    RakNet::RakPeerInterface::DestroyInstance(rakPeer);
    delete replicaManager3;
    RakNet::FullyConnectedMesh2::DestroyInstance(fullyConnectedMesh2);
    RakNet::TeamManager::DestroyInstance(teamManager);
    RakNet::NetworkIDManager::DestroyInstance(networkIDManager);

    for (unsigned int i=0; i < TEAM_TYPES_COUNT; i++)
    {
        // Teams are globally deallocated after NetworkIDManager, so prevent crash on automatic dereference
        teams[i].SetNetworkIDManager(0);
    }

    return 1;
}
int main(void)
{
	printf("Demonstrates networking elements for a P2P game on the PC, self-released,\nwith player hosted game servers\n");
	printf("Difficulty: Advanced\n\n");

	// ---------------------------------------------------------------------------------------------------------------------
	// Allocate plugins. See declaration in this file for description of each
	// ---------------------------------------------------------------------------------------------------------------------
	rakPeer=RakNet::RakPeerInterface::GetInstance();
	teamManager=TeamManager::GetInstance();
	fullyConnectedMesh2=FullyConnectedMesh2::GetInstance();
	networkIDManager = NetworkIDManager::GetInstance();
	cloudClient = CloudClient::GetInstance();
	natPunchthroughClient = NatPunchthroughClient::GetInstance();
#ifdef NAT_TYPE_DETECTION_SERVER
	natTypeDetectionClient = NatTypeDetectionClient::GetInstance();
#endif
	rpc4 = RPC4::GetInstance();
	readyEvent = ReadyEvent::GetInstance();
	replicaManager3=new SampleRM3;

	// ---------------------------------------------------------------------------------------------------------------------
	// Attach plugins
	// ---------------------------------------------------------------------------------------------------------------------
	rakPeer->AttachPlugin(fullyConnectedMesh2);
	rakPeer->AttachPlugin(teamManager);
	rakPeer->AttachPlugin(cloudClient);
	rakPeer->AttachPlugin(natPunchthroughClient);
#ifdef NAT_TYPE_DETECTION_SERVER
	rakPeer->AttachPlugin(natTypeDetectionClient);
#endif
	rakPeer->AttachPlugin(rpc4);
	rakPeer->AttachPlugin(readyEvent);
	rakPeer->AttachPlugin(replicaManager3);

	// ---------------------------------------------------------------------------------------------------------------------
	// Setup plugins: Disable automatically adding new connections. Allocate initial objects and register for replication
	// ---------------------------------------------------------------------------------------------------------------------
	// Allocate a world instance to be used for team operations
	teamManager->AddWorld(0);
	// Do not automatically count new connections
	teamManager->SetAutoManageConnections(false);
	
	// New connections do not count until after login.
	fullyConnectedMesh2->SetAutoparticipateConnections(false);	
		
	// Tell ReplicaManager3 which networkIDManager to use for object lookup, used for automatic serialization
	replicaManager3->SetNetworkIDManager(networkIDManager);
	// Do not automatically count new connections, but do drop lost connections automatically
	replicaManager3->SetAutoManageConnections(false,true);
	
	// Reference static game objects that always exist
	game = new Game;
	game->SetNetworkIDManager(networkIDManager);
	game->SetNetworkID(0);
	replicaManager3->Reference(game);

	// Setup my own user
	User *user = new User;
	user->SetNetworkIDManager(networkIDManager);
	user->userName = rakPeer->GetMyGUID().ToString();
	// Inform TeamManager of my user's team member info
	teamManager->GetWorldAtIndex(0)->ReferenceTeamMember(&user->tmTeamMember,user->GetNetworkID());

	// ---------------------------------------------------------------------------------------------------------------------
	// Startup RakNet on first available port
	// ---------------------------------------------------------------------------------------------------------------------
	RakNet::SocketDescriptor sd;
	sd.socketFamily=AF_INET; // Only IPV4 supports broadcast on 255.255.255.255
	sd.port=0;
	StartupResult sr = rakPeer->Startup(8,&sd,1);
	RakAssert(sr==RAKNET_STARTED);
	rakPeer->SetMaximumIncomingConnections(8);
	rakPeer->SetTimeoutTime(30000,RakNet::UNASSIGNED_SYSTEM_ADDRESS);
	printf("Our guid is %s\n", rakPeer->GetGuidFromSystemAddress(RakNet::UNASSIGNED_SYSTEM_ADDRESS).ToString());
	printf("Started on %s\n", rakPeer->GetMyBoundAddress().ToString(true));
	
	// Connect to master server
	game->EnterPhase(Game::CONNECTING_TO_SERVER);

	// ---------------------------------------------------------------------------------------------------------------------
	// Read packets loop
	// ---------------------------------------------------------------------------------------------------------------------
	char ch;
	Packet *packet;
	while (game->phase!=Game::EXIT_SAMPLE)
	{
		for (packet = rakPeer->Receive(); packet; rakPeer->DeallocatePacket(packet), packet = rakPeer->Receive())
		{
			switch (packet->data[0])
			{
			case ID_NEW_INCOMING_CONNECTION:
				{
					printf("ID_NEW_INCOMING_CONNECTION from %s. guid=%s.\n", packet->systemAddress.ToString(true), packet->guid.ToString());
				}
				break;
			case ID_CONNECTION_REQUEST_ACCEPTED:
				{
					printf("ID_CONNECTION_REQUEST_ACCEPTED from %s,guid=%s\n", packet->systemAddress.ToString(true), packet->guid.ToString());

					if (game->phase==Game::CONNECTING_TO_SERVER)
					{
						game->masterServerAddress=packet->systemAddress;
						game->masterServerGuid=packet->guid;

						// ---------------------------------------------------------------------------------------------------------------------
						// PC self-hosted servers only: Use master server to determine NAT type. Attempt to open router if needed.
						// ---------------------------------------------------------------------------------------------------------------------
						if (NAT_TYPE_DETECTION_SERVER)
						{
							game->EnterPhase(Game::DETERMINE_NAT_TYPE);
						}
						else
						{
							OpenUPNP();
							game->EnterPhase(Game::SEARCH_FOR_GAMES);
						}
					}
					else if (game->phase==Game::CONNECTING_TO_GAME_HOST)
					{
						printf("Asking host to join session...\n");

						// So time in single player does not count towards which system has been running multiplayer the longest
						fullyConnectedMesh2->ResetHostCalculation();

						// Custom message to ask to join the game
						// We first connect to the game host, and the game host is responsible for calling StartVerifiedJoin() for us to join the session
						BitStream bsOut;
						bsOut.Write((MessageID)ID_USER_PACKET_ENUM);
						rakPeer->Send(&bsOut,HIGH_PRIORITY,RELIABLE_ORDERED,0,packet->guid,false);
					}
				}
				break;
			case ID_CONNECTION_LOST:
			case ID_DISCONNECTION_NOTIFICATION:
				if (game->phase==Game::DETERMINE_NAT_TYPE)
				{
					printf("Lost connection during NAT type detection. Reason %s. Retrying...\n", PacketLogger::BaseIDTOString(packet->data[0]));
					game->EnterPhase(Game::CONNECTING_TO_SERVER);
				}
				else if (game->phase==Game::NAT_PUNCH_TO_GAME_HOST)
				{
					printf("Lost connection during NAT punch to game host. Reason %s.\n", PacketLogger::BaseIDTOString(packet->data[0]));
					game->EnterPhase(Game::SEARCH_FOR_GAMES);
				}
				else
				{
					if (packet->guid==game->masterServerGuid)
					{
						printf("Server connection lost. Reason %s.\nGame session is no longer searchable.\n", PacketLogger::BaseIDTOString(packet->data[0]));
					}
					else
					{
						printf("Peer connection lost. Reason %s.\n", PacketLogger::BaseIDTOString(packet->data[0]));
					}
				}
				break;

			case ID_ALREADY_CONNECTED:
				printf("ID_ALREADY_CONNECTED with guid %"PRINTF_64_BIT_MODIFIER"u\n", packet->guid);
				break;

			case ID_INVALID_PASSWORD:
			case ID_NO_FREE_INCOMING_CONNECTIONS:
			case ID_CONNECTION_ATTEMPT_FAILED:
			case ID_CONNECTION_BANNED:
			case ID_IP_RECENTLY_CONNECTED:
			case ID_INCOMPATIBLE_PROTOCOL_VERSION:
				// Note: Failing to connect to another system does not automatically mean we cannot join a session, since that system may be disconnecting from the host simultaneously
				// FullyConnectedMesh2::StartVerifiedJoin() internally handles success or failure and notifies the client through ID_FCM2_VERIFIED_JOIN_FAILED if needed.
				printf("Failed to connect to %s. Reason %s\n", packet->systemAddress.ToString(true), PacketLogger::BaseIDTOString(packet->data[0]));

				if (game->phase==Game::CONNECTING_TO_SERVER)
					game->EnterPhase(Game::EXIT_SAMPLE);
				break;
				
			case ID_FCM2_NEW_HOST:
				{
					RakNet::BitStream bs(packet->data,packet->length,false);
					bs.IgnoreBytes(1);
					RakNetGUID oldHost;
					bs.Read(oldHost);

					if (packet->guid==rakPeer->GetMyGUID())
					{
						if (oldHost!=UNASSIGNED_RAKNET_GUID)
						{
							PostRoomToCloud();
							printf("ID_FCM2_NEW_HOST: Taking over as host from the old host.\nNew options:\n");
						}
						else
						{
							// Room not hosted if we become host the first time since this was done in CreateRoom() already
							printf("ID_FCM2_NEW_HOST: We have become host for the first time. New options:\n");
						}

						printf("(L)ock and unlock game\n");
					}
					else
					{
						if (oldHost!=UNASSIGNED_RAKNET_GUID)
							printf("ID_FCM2_NEW_HOST: A new system %s has become host, GUID=%s\n", packet->systemAddress.ToString(true), packet->guid.ToString());
						else
							printf("ID_FCM2_NEW_HOST: System %s is host, GUID=%s\n", packet->systemAddress.ToString(true), packet->guid.ToString());
					}

					if (oldHost==UNASSIGNED_RAKNET_GUID)
					{
						// First time calculated host. Add existing connections to ReplicaManager3
						DataStructures::List<RakNetGUID> participantList;
						fullyConnectedMesh2->GetParticipantList(participantList);
						for (unsigned int i=0; i < participantList.Size(); i++)
							RegisterGameParticipant(participantList[i]);

						// Reference previously created replicated objects, which cannot be serialized until host is known the first time
						if (packet->guid==rakPeer->GetMyGUID())
						{
							// As host, reference the teams we created
							for (unsigned int i=0; i < game->teams.Size(); i++)
								replicaManager3->Reference(game->teams[i]);
						}

						// Reference the user we created (host or not)
						for (unsigned int i=0; i < game->users.Size(); i++)
							replicaManager3->Reference(game->users[i]);
					}
				}
				break;
			case ID_TEAM_BALANCER_TEAM_ASSIGNED:
				{
					printf("ID_TEAM_BALANCER_TEAM_ASSIGNED for ");
					TM_World *world;
					TM_TeamMember *teamMember;
					teamManager->DecodeTeamAssigned(packet, &world, &teamMember);
					printf("worldId=%i teamMember=%s", world->GetWorldId(), ((User*)teamMember->GetOwner())->userName.C_String());
					if (teamMember->GetCurrentTeam()==0)
						printf(" not on team\n");
					else
						printf(" on team %s\n", ((Team*)(teamMember->GetCurrentTeam()->GetOwner()))->teamName.C_String());
				}
				break;
			case ID_TEAM_BALANCER_REQUESTED_TEAM_FULL:
				{
					printf("ID_TEAM_BALANCER_REQUESTED_TEAM_FULL\n");
				}
				break;
			case ID_TEAM_BALANCER_REQUESTED_TEAM_LOCKED:
				{
					printf("ID_TEAM_BALANCER_REQUESTED_TEAM_LOCKED\n");
				}
				break;
			case ID_TEAM_BALANCER_TEAM_REQUESTED_CANCELLED:
				{
					printf("ID_TEAM_BALANCER_TEAM_REQUESTED_CANCELLED\n");
				}
				break;
			case ID_NAT_TARGET_NOT_CONNECTED:
			case ID_NAT_TARGET_UNRESPONSIVE:
			case ID_NAT_CONNECTION_TO_TARGET_LOST:
			case ID_NAT_PUNCHTHROUGH_FAILED:
				{
					// As with connection failed, this does not automatically mean we cannot join the session
					// We only fail on ID_FCM2_VERIFIED_JOIN_FAILED
					printf("NAT punch to %s failed. Reason %s\n", packet->guid.ToString(), PacketLogger::BaseIDTOString(packet->data[0]));

					if (game->phase==Game::NAT_PUNCH_TO_GAME_HOST)
						game->EnterPhase(Game::SEARCH_FOR_GAMES);
				}

			case ID_NAT_ALREADY_IN_PROGRESS:
				// Can ignore this
				break;

			case ID_NAT_PUNCHTHROUGH_SUCCEEDED:
				{
					if (game->phase==Game::NAT_PUNCH_TO_GAME_HOST || game->phase==Game::VERIFIED_JOIN)
					{
						// Connect to the session host
						ConnectionAttemptResult car = rakPeer->Connect(packet->systemAddress.ToString(false), packet->systemAddress.GetPort(), 0, 0);
						if (car!=RakNet::CONNECTION_ATTEMPT_STARTED)
						{
							printf("Failed connect call to %s. Code=%i\n", packet->systemAddress.ToString(false), car);
							game->EnterPhase(Game::SEARCH_FOR_GAMES);
						}
						else
						{
							if (game->phase==Game::NAT_PUNCH_TO_GAME_HOST)
							{
								printf("NAT punch completed. Connecting to %s (game host)...\n", packet->systemAddress.ToString(true));
								game->EnterPhase(Game::CONNECTING_TO_GAME_HOST);
							}
							else
							{
								printf("NAT punch completed. Connecting to %s (game client)...\n", packet->systemAddress.ToString(true));
							}
						}
					}
				}
				break;


			case ID_CLOUD_GET_RESPONSE:
				{
					cloudClient->DeallocateWithDefaultAllocator(&cloudQueryResult);
					cloudClient->OnGetReponse(&cloudQueryResult, packet);
					unsigned int rowIndex;

					for (rowIndex=0; rowIndex < cloudQueryResult.rowsReturned.Size(); rowIndex++)
					{
						RakNet::CloudQueryRow *row = cloudQueryResult.rowsReturned[rowIndex];
						printf("%i. ", rowIndex);
						PrintRow(row);
					}

					printf("(J)oin room\n");
					printf("(C)reate room\n");
					printf("(S)earch rooms\n");
				}
				break;
			
			case ID_NAT_TYPE_DETECTION_RESULT:
				{
					game->myNatType = (RakNet::NATTypeDetectionResult) packet->data[1];
					printf("NAT Type is %s (%s)\n", NATTypeDetectionResultToString(game->myNatType), NATTypeDetectionResultToStringFriendly(game->myNatType));

					if (game->myNatType!=RakNet::NAT_TYPE_NONE)
					{
						OpenUPNP();
					}

					if (game->myNatType==RakNet::NAT_TYPE_PORT_RESTRICTED || game->myNatType==RakNet::NAT_TYPE_SYMMETRIC)
					{
						printf("Note: Your router must support UPNP or have the user manually forward ports.\n");
						printf("Otherwise NATPunchthrough may not always succeed.\n");
					}

					game->EnterPhase(Game::SEARCH_FOR_GAMES);
				}
				break;
				
			case ID_READY_EVENT_ALL_SET:
				printf("Got ID_READY_EVENT_ALL_SET from %s\n", packet->systemAddress.ToString(true));
				printf("All users ready.\n");
				if (fullyConnectedMesh2->IsConnectedHost())
					printf("New options:\n(B)egin gameplay\n");
				break;

			case ID_READY_EVENT_SET:
				printf("Got ID_READY_EVENT_SET from %s\n", packet->systemAddress.ToString(true));
				break;

			case ID_READY_EVENT_UNSET:
				printf("Got ID_READY_EVENT_UNSET from %s\n", packet->systemAddress.ToString(true));
				break;

			// ID_USER_PACKET_ENUM is used by this sample as a custom message to ask to join a game
			case ID_USER_PACKET_ENUM:
				if (game->phase > Game::SEARCH_FOR_GAMES)
				{
					printf("Got request from client to join session.\nExecuting StartVerifiedJoin()\n");
					fullyConnectedMesh2->StartVerifiedJoin(packet->guid);
				}
				else
				{
					BitStream bsOut;
					bsOut.Write((MessageID)(ID_USER_PACKET_ENUM+1));
					rakPeer->Send(&bsOut,HIGH_PRIORITY,RELIABLE_ORDERED,0,packet->guid,false);
				}
				break;
			// ID_USER_PACKET_ENUM+1 is used by this sample as a custom message to reject a join game request
			// Requests may also be later rejected through FullyConnectedMesh2::RespondOnVerifiedJoinCapable() to send ID_FCM2_VERIFIED_JOIN_REJECTED
			case (ID_USER_PACKET_ENUM+1):
				printf("Join request denied\n");
				game->EnterPhase(Game::SEARCH_FOR_GAMES);
				break;
			case ID_FCM2_VERIFIED_JOIN_START:
				{
					game->EnterPhase(Game::VERIFIED_JOIN);

					// This message means the session host sent us a list of systems in the session
					// Once we connect to, or fail to connect to, each of these systems we will get ID_FCM2_VERIFIED_JOIN_FAILED, ID_FCM2_VERIFIED_JOIN_ACCEPTED, or ID_FCM2_VERIFIED_JOIN_REJECTED
					printf("Host sent us system list. Doing NAT punch to each system...\n");
					DataStructures::List<SystemAddress> addresses;
					DataStructures::List<RakNetGUID> guids;
					fullyConnectedMesh2->GetVerifiedJoinRequiredProcessingList(packet->guid, addresses, guids);
					for (unsigned int i=0; i < guids.Size(); i++)
						natPunchthroughClient->OpenNAT(guids[i], game->masterServerAddress);
				}
				break;

			case ID_FCM2_VERIFIED_JOIN_CAPABLE:
				printf("Client is capable of joining FullyConnectedMesh2.\n");
				if (game->lockGame)
				{
					RakNet::BitStream bsOut;
					bsOut.Write("Game is locked");
					fullyConnectedMesh2->RespondOnVerifiedJoinCapable(packet, false, &bsOut);
				}
				else
					fullyConnectedMesh2->RespondOnVerifiedJoinCapable(packet, true, 0);
				break;

			case ID_FCM2_VERIFIED_JOIN_ACCEPTED:
				{
					DataStructures::List<RakNetGUID> systemsAccepted;
					bool thisSystemAccepted;
					fullyConnectedMesh2->GetVerifiedJoinAcceptedAdditionalData(packet, &thisSystemAccepted, systemsAccepted, 0);
					if (thisSystemAccepted)
						printf("Game join request accepted\n");
					else
						printf("System %s joined the mesh\n", systemsAccepted[0].ToString());

					// Add the new participant to the game if we already know who the host is. Otherwise do this
					// once ID_FCM2_NEW_HOST arrives
					if (fullyConnectedMesh2->GetConnectedHost()!=UNASSIGNED_RAKNET_GUID)
					{
						// FullyConnectedMesh2 already called AddParticipant() for each accepted system
						// Still need to add those systems to the other plugins though
						for (unsigned int i=0; i < systemsAccepted.Size(); i++)
							RegisterGameParticipant(systemsAccepted[i]);

						if (thisSystemAccepted)
							game->EnterPhase(Game::IN_LOBBY_WITH_HOST);
					}
					else
					{
						if (thisSystemAccepted)
							game->EnterPhase(Game::IN_LOBBY_WAITING_FOR_HOST);
					}

					printf("(E)xit room\n");
				}
				break;

			case ID_FCM2_VERIFIED_JOIN_REJECTED:
				{
					BitStream additionalData;
					fullyConnectedMesh2->GetVerifiedJoinRejectedAdditionalData(packet, &additionalData);
					RakString reason;
					additionalData.Read(reason);
					printf("Join rejected. Reason=%s\n", reason.C_String());
					rakPeer->CloseConnection(packet->guid, true);
					game->EnterPhase(Game::SEARCH_FOR_GAMES);
					break;
				}

			case ID_REPLICA_MANAGER_DOWNLOAD_COMPLETE:
				{
					if (replicaManager3->GetAllConnectionDownloadsCompleted()==true)
					{
						printf("Completed all remote downloads\n");

						if (game->gameInLobby)
							game->EnterPhase(Game::IN_LOBBY_WITH_HOST);
						else
							game->EnterPhase(Game::IN_GAME);
					}

					break;
				}
			}
		}

		if (kbhit())
		{
			ch=getch();

			if (game->phase==Game::SEARCH_FOR_GAMES)
			{
				if (ch=='c' || ch=='C')
				{
					CreateRoom();
				}
				if (ch=='s' || ch=='S')
				{
					game->SearchForGames();
				}
				else if (ch=='j' || ch=='J')
				{
					// Join room
					if (cloudQueryResult.rowsReturned.Size()==0)
					{
						printf("No rooms to join.\n");
					}
					else
					{
						int index;
						if (cloudQueryResult.rowsReturned.Size()>1)
						{
							printf("Enter index of room to join.\n");
							char indexstr[64];
							Gets(indexstr,64);
							index = atoi(indexstr);
						}
						else
						{
							index = 0;
						}

						if (index < 0 || (unsigned int) index >= cloudQueryResult.rowsReturned.Size())
						{
							printf("Index out of range.\n");
						}
						else
						{
							CloudQueryRow *row = cloudQueryResult.rowsReturned[index];
							// Connect to the session host using NATPunchthrough
							natPunchthroughClient->OpenNAT(row->clientGUID, game->masterServerAddress);
							game->EnterPhase(Game::NAT_PUNCH_TO_GAME_HOST);
						}
					}
				}
			}
			else
			{
				if (game->phase==Game::IN_GAME)
				{
					if (ch=='c' || ch=='C')
					{
						DataStructures::List<RakNetGUID> participantList;
						fullyConnectedMesh2->GetParticipantList(participantList);

						if (participantList.Size()>0)
						{
							printf("Enter in-game chat message: ");
							char str[256];
							Gets(str, 256);
							RakString rs;
							// Don't use RakString constructor to assign str, or will process % escape characters
							rs=str;
							BitStream bsOut;
							bsOut.Write(rs);
							for (unsigned int i=0; i < participantList.Size(); i++)
								rpc4->Signal("InGameChat", &bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, participantList[i], false, false);
						}
					}
				}

				if (ch=='1')
				{
					user->tmTeamMember.RequestTeamSwitch(&game->teams[0]->tmTeam, 0);
				}
				else if (ch=='2')
				{
					user->tmTeamMember.RequestTeamSwitch(&game->teams[1]->tmTeam, 0);
				}
				else if (ch=='r' || ch=='R')
				{
					if (readyEvent->SetEvent(0, true))
						printf("We are ready to start.\n");
				}
				else if (ch=='u' || ch=='U')
				{
					if (readyEvent->SetEvent(0, false))
						printf("We are no longer ready to start.\n");
				}
				else if (ch=='l' || ch=='L')
				{
					if (fullyConnectedMesh2->IsConnectedHost())
					{
						if (game->lockGame)
						{
							printf("Game is no longer locked\n");
							game->lockGame=false;
						}
						else
						{
							printf("Game is now locked\n");
							game->lockGame=true;
						}
					}
				}
				else if (ch=='b' || ch=='B')
				{
					if (fullyConnectedMesh2->IsConnectedHost())
					{
						if (game->gameInLobby)
						{
							readyEvent->ForceCompletion(0);
							game->gameInLobby=false;
							game->EnterPhase(Game::IN_GAME);
						}
						else
						{
							readyEvent->DeleteEvent(0);
							printf("Game ended, and now in lobby\n");
							game->gameInLobby=true;
							game->EnterPhase(Game::IN_LOBBY_WITH_HOST);
						}
					}
				}
				else if (ch=='e' || ch=='E')
				{
					// Disconnect from FullyConnectedMesh2 participants
					DataStructures::List<RakNetGUID> participantList;
					fullyConnectedMesh2->GetParticipantList(participantList);
					for (unsigned int i=0; i < participantList.Size(); i++)
						rakPeer->CloseConnection(participantList[i], true);

					// User instances are deleted automatically from ReplicaManager3.
					// However, teams are not deleted since the Team class can migrate between systems. So delete Team instances manually
					while (game->teams.Size())
						delete game->teams[game->teams.Size()-1];

					// If we were the host, no longer list this session
					// The new host will call PostRoomToCloud to reupload under a new IP address on ID_FCM2_NEW_HOST
					ReleaseRoomFromCloud();

					// Clear out state data from plugins
					fullyConnectedMesh2->Clear();
					readyEvent->DeleteEvent(0);
					replicaManager3->Clear(false);
					replicaManager3->Reference(game);

					game->Reset();
					game->EnterPhase(Game::SEARCH_FOR_GAMES);
				}
				else if (ch=='q' || ch=='Q')
				{
					printf("Quitting.\n");
					// Disconnecting from the master server automatically releases from cloud
					game->EnterPhase(Game::EXIT_SAMPLE);
				}
			}
		}

		RakSleep(30);
	}

	rakPeer->Shutdown(100);

	while (game->teams.Size())
		delete game->teams[game->teams.Size()-1];
	while (game->users.Size())
		delete game->users[game->users.Size()-1];
	delete game;

	cloudClient->DeallocateWithDefaultAllocator(&cloudQueryResult);
	RakPeerInterface::DestroyInstance(rakPeer);
	TeamManager::DestroyInstance(teamManager);
	FullyConnectedMesh2::DestroyInstance(fullyConnectedMesh2);
	cloudClient->DeallocateWithDefaultAllocator(&cloudQueryResult);
	CloudClient::DestroyInstance(cloudClient);
	NatPunchthroughClient::DestroyInstance(natPunchthroughClient);
	NatTypeDetectionClient::DestroyInstance(natTypeDetectionClient);
	RPC4::DestroyInstance(rpc4);
	ReadyEvent::DestroyInstance(readyEvent);
	delete replicaManager3;
	NetworkIDManager::DestroyInstance(networkIDManager);

	return 1;
}