1758 lines
61 KiB
C++
1758 lines
61 KiB
C++
/*
|
|
* Original work: Copyright (c) 2014, Oculus VR, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* RakNet License.txt file in the licenses directory of this source tree. An additional grant
|
|
* of patent rights can be found in the RakNet Patents.txt file in the same directory.
|
|
*
|
|
*
|
|
* Modified work: Copyright (c) 2016-2019, SLikeSoft UG (haftungsbeschränkt)
|
|
*
|
|
* This source code was modified by SLikeSoft. Modifications are licensed under the MIT-style
|
|
* license found in the license.txt file in the root directory of this source tree.
|
|
*/
|
|
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <stdlib.h>
|
|
#include <limits> // used for std::numeric_limits
|
|
#include "slikenet/GetTime.h"
|
|
#include "slikenet/Rand.h"
|
|
#include "slikenet/peerinterface.h"
|
|
#include "slikenet/MessageIdentifiers.h"
|
|
#include "slikenet/FullyConnectedMesh2.h"
|
|
#include "slikenet/TeamManager.h"
|
|
#include "slikenet/Kbhit.h"
|
|
#include "slikenet/sleep.h"
|
|
#include "slikenet/types.h"
|
|
#include "slikenet/BitStream.h"
|
|
#include "slikenet/SocketLayer.h"
|
|
#include "slikenet/ReplicaManager3.h"
|
|
#include "slikenet/NetworkIDManager.h"
|
|
#include "slikenet/Gets.h"
|
|
#include "slikenet/Itoa.h"
|
|
#include "slikenet/NatPunchthroughClient.h"
|
|
#include "slikenet/NatTypeDetectionClient.h"
|
|
#include "miniupnpc.h"
|
|
#include "upnpcommands.h"
|
|
#include "upnperrors.h"
|
|
#include "slikenet/TCPInterface.h"
|
|
#include "slikenet/ReadyEvent.h"
|
|
#include "slikenet/PacketLogger.h"
|
|
#include "slikenet/RPC4Plugin.h"
|
|
#include "slikenet/HTTPConnection2.h"
|
|
#include "slikenet/linux_adapter.h"
|
|
#include "slikenet/osx_adapter.h"
|
|
// See http://www.digip.org/jansson/doc/2.4/
|
|
// This is used to make it easier to parse the JSON returned from the master server
|
|
#include "jansson.h"
|
|
|
|
#define DEFAULT_SERVER_PORT "61111"
|
|
// Public test server
|
|
#define DEFAULT_SERVER_ADDRESS "natpunch.slikesoft.com"
|
|
#define NAT_TYPE_DETECTION_SERVER 0
|
|
#define USE_UPNP 1
|
|
#define MASTER_SERVER_ADDRESS "masterserver2.raknet.com"
|
|
//#define MASTER_SERVER_ADDRESS "localhost"
|
|
#define MASTER_SERVER_PORT 80
|
|
//#define MASTER_SERVER_PORT 8888
|
|
|
|
using namespace SLNet;
|
|
|
|
// ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
// Plugins demonstrated by the sample ComphrensivePCGame
|
|
// ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
// Purpose: UDP network communication
|
|
// Required?: Yes
|
|
RakPeerInterface *rakPeer;
|
|
|
|
// Purpose: Sophisticated team management
|
|
// Required?: No, but provides a flexible and full-featured implementation that supports host migration, mid-game joins, and advanced team management.
|
|
TeamManager *teamManager;
|
|
|
|
// Purpose: Game object replication
|
|
// Required?: No, but manages object replication automatically. Some form of this is required for almost every game
|
|
ReplicaManager3 *gameReplicaManager3;
|
|
|
|
// Purpose: Lookup game objects given ID. Used by ReplicaManager3
|
|
// Required?: Required to use ReplicaManager3, and some form of this is required for almost every game
|
|
NetworkIDManager *networkIDManager;
|
|
|
|
// Purpose: Connect to RakNet's hosted master server
|
|
// Required?: Steam and consoles provide server solutions, so usually not if you are releasing only on those platforms.
|
|
TCPInterface *tcp;
|
|
|
|
// Purpose: If UPNP fails, used to connect across routers.
|
|
// Required?: Steam and consoles provide server solutions that do this automatically. Otherwise, if UPNP fails you need this.
|
|
// Note: See the project NAT Punchthrough / NATCompleteServer for a sample server implementation
|
|
NatPunchthroughClient *natPunchthroughClient;
|
|
|
|
#ifdef NAT_TYPE_DETECTION_SERVER
|
|
// Purpose: Determine what type of router we are behind, if any
|
|
// Required?: No, but game sessions that are impossible to join can be filtered out from game listings
|
|
NatTypeDetectionClient *natTypeDetectionClient;
|
|
#endif
|
|
|
|
// Purpose: Used to call C functions on remote systems. Convenience function.
|
|
// Required?: No
|
|
RPC4 *rpc4;
|
|
|
|
// Purpose: Used for players to ready / unready in the lobby in a way that is properly synchronized under peer to peer
|
|
// Required?: No, but provides a more robust way to ready/unready in a peer to peer game
|
|
ReadyEvent *readyEvent;
|
|
|
|
// Purpose: Automatically determine and migrate the host of a peer to peer session. Avoid partial connection failures when joining peer-to-peer games.
|
|
// Required?: Yes, for peer to peer games that need a session host. Not necessary for client/server
|
|
FullyConnectedMesh2 *fullyConnectedMesh2;
|
|
|
|
// Purpose: Helper class to parse http connection events
|
|
// Required?: Technically no since you could parse the strings yourself. But it's a lot of work otherwise.
|
|
HTTPConnection2 *httpConnection2;
|
|
|
|
// ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
// Sample game classes using ReplicaManager3
|
|
// ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
// Forward declarations
|
|
class User;
|
|
class Team;
|
|
void PostRoomToMaster(void);
|
|
|
|
// Game contains the list of objects, and serializes if the game is locked, the name of the game, and if it is in gameplay or in the lobby
|
|
// Game is created on startup, rather than over the network, so is replicated as a static object.
|
|
// see Help/replicamanager3.html under the topic Static Objects for more details about how this class is Replicated
|
|
class Game : public Replica3
|
|
{
|
|
public:
|
|
enum Phase
|
|
{
|
|
CONNECTING_TO_SERVER,
|
|
DETERMINE_NAT_TYPE,
|
|
SEARCH_FOR_GAMES,
|
|
NAT_PUNCH_TO_GAME_HOST,
|
|
CONNECTING_TO_GAME_HOST,
|
|
VERIFIED_JOIN,
|
|
IN_LOBBY_WAITING_FOR_HOST,
|
|
IN_LOBBY_WITH_HOST,
|
|
IN_GAME,
|
|
EXIT_SAMPLE,
|
|
};
|
|
Game()
|
|
{
|
|
myNatType=NAT_TYPE_UNKNOWN;
|
|
masterServerRow = -1;
|
|
Reset();
|
|
whenToNextUpdateMasterServer = 0;
|
|
masterServerQueryResult = 0;
|
|
}
|
|
virtual ~Game()
|
|
{
|
|
if (masterServerQueryResult)
|
|
json_decref(masterServerQueryResult);
|
|
}
|
|
virtual void WriteAllocationID(SLNet::Connection_RM3 *destinationConnection, SLNet::BitStream *allocationIdBitstream) const
|
|
{
|
|
// unused parameters
|
|
(void)destinationConnection;
|
|
(void)allocationIdBitstream;
|
|
}
|
|
virtual RM3ConstructionState QueryConstruction(SLNet::Connection_RM3 *destinationConnection, ReplicaManager3 *replicaManager3)
|
|
{
|
|
// unused parameters
|
|
(void)replicaManager3;
|
|
|
|
if (fullyConnectedMesh2->IsConnectedHost())
|
|
return QueryConstruction_PeerToPeer(destinationConnection, R3P2PM_STATIC_OBJECT_CURRENTLY_AUTHORITATIVE);
|
|
else
|
|
return QueryConstruction_PeerToPeer(destinationConnection, R3P2PM_STATIC_OBJECT_NOT_CURRENTLY_AUTHORITATIVE);
|
|
}
|
|
virtual bool QueryRemoteConstruction(SLNet::Connection_RM3 *sourceConnection)
|
|
{
|
|
// unused parameters
|
|
(void)sourceConnection;
|
|
|
|
return true;
|
|
}
|
|
virtual void SerializeConstruction(SLNet::BitStream *constructionBitstream, SLNet::Connection_RM3 *destinationConnection)
|
|
{
|
|
// unused parameters
|
|
(void)constructionBitstream;
|
|
(void)destinationConnection;
|
|
}
|
|
virtual bool DeserializeConstruction(SLNet::BitStream *constructionBitstream, SLNet::Connection_RM3 *sourceConnection)
|
|
{
|
|
// unused parameters
|
|
(void)constructionBitstream;
|
|
(void)sourceConnection;
|
|
|
|
return true;
|
|
}
|
|
virtual void SerializeConstructionExisting(SLNet::BitStream *constructionBitstream, SLNet::Connection_RM3 *destinationConnection)
|
|
{
|
|
// unused parameters
|
|
(void)destinationConnection;
|
|
|
|
constructionBitstream->Write(gameName);
|
|
constructionBitstream->Write(lockGame);
|
|
constructionBitstream->Write(gameInLobby);
|
|
constructionBitstream->Write(masterServerRow);
|
|
}
|
|
virtual void DeserializeConstructionExisting(SLNet::BitStream *constructionBitstream, SLNet::Connection_RM3 *sourceConnection)
|
|
{
|
|
// unused parameters
|
|
(void)sourceConnection;
|
|
|
|
constructionBitstream->Read(gameName);
|
|
constructionBitstream->Read(lockGame);
|
|
constructionBitstream->Read(gameInLobby);
|
|
constructionBitstream->Read(masterServerRow);
|
|
printf("Downloaded game. locked=%i. inLobby=%i\n", lockGame, gameInLobby);
|
|
}
|
|
virtual void SerializeDestruction(SLNet::BitStream *destructionBitstream, SLNet::Connection_RM3 *destinationConnection)
|
|
{
|
|
// unused parameters
|
|
(void)destructionBitstream;
|
|
(void)destinationConnection;
|
|
}
|
|
virtual bool DeserializeDestruction(SLNet::BitStream *destructionBitstream, SLNet::Connection_RM3 *sourceConnection)
|
|
{
|
|
// unused parameters
|
|
(void)destructionBitstream;
|
|
(void)sourceConnection;
|
|
|
|
return true;
|
|
}
|
|
virtual SLNet::RM3ActionOnPopConnection QueryActionOnPopConnection(SLNet::Connection_RM3 *droppedConnection) const
|
|
{
|
|
// unused parameters
|
|
(void)droppedConnection;
|
|
|
|
return RM3AOPC_DO_NOTHING;
|
|
}
|
|
virtual void DeallocReplica(SLNet::Connection_RM3 *sourceConnection)
|
|
{
|
|
// unused parameters
|
|
(void)sourceConnection;
|
|
|
|
delete this;
|
|
}
|
|
virtual SLNet::RM3QuerySerializationResult QuerySerialization(SLNet::Connection_RM3 *destinationConnection)
|
|
{
|
|
if (fullyConnectedMesh2->IsConnectedHost())
|
|
return QuerySerialization_PeerToPeer(destinationConnection, R3P2PM_STATIC_OBJECT_CURRENTLY_AUTHORITATIVE);
|
|
else
|
|
return QuerySerialization_PeerToPeer(destinationConnection, R3P2PM_STATIC_OBJECT_NOT_CURRENTLY_AUTHORITATIVE);
|
|
}
|
|
virtual RM3SerializationResult Serialize(SLNet::SerializeParameters *serializeParameters)
|
|
{
|
|
serializeParameters->outputBitstream[0].Write(lockGame);
|
|
serializeParameters->outputBitstream[0].Write(gameInLobby);
|
|
serializeParameters->outputBitstream[0].Write(masterServerRow);
|
|
return RM3SR_BROADCAST_IDENTICALLY;
|
|
}
|
|
virtual void Deserialize(SLNet::DeserializeParameters *deserializeParameters)
|
|
{
|
|
if (deserializeParameters->bitstreamWrittenTo[0])
|
|
{
|
|
bool b;
|
|
deserializeParameters->serializationBitstream[0].Read(b);
|
|
if (b!=lockGame)
|
|
{
|
|
lockGame=b;
|
|
if (lockGame)
|
|
printf("Game is no longer locked\n");
|
|
else
|
|
printf("Game is now locked\n");
|
|
}
|
|
deserializeParameters->serializationBitstream[0].Read(b);
|
|
if (b!=gameInLobby)
|
|
{
|
|
gameInLobby=b;
|
|
if (gameInLobby)
|
|
{
|
|
readyEvent->DeleteEvent(0);
|
|
game->EnterPhase(Game::IN_LOBBY_WITH_HOST);
|
|
printf("Game is now in the lobby\n");
|
|
}
|
|
else
|
|
{
|
|
readyEvent->ForceCompletion(0);
|
|
game->EnterPhase(Game::IN_GAME);
|
|
}
|
|
}
|
|
deserializeParameters->serializationBitstream[0].Read(masterServerRow);
|
|
}
|
|
}
|
|
void EnterPhase(Phase newPhase)
|
|
{
|
|
phase = newPhase;
|
|
switch (newPhase)
|
|
{
|
|
case CONNECTING_TO_SERVER:
|
|
{
|
|
char port[256];
|
|
printf("Enter address of server running the NATCompleteServer project.\nEnter for default: ");
|
|
Gets(game->serverIPAddr, 256);
|
|
if (game->serverIPAddr[0]==0)
|
|
strcpy_s(game->serverIPAddr, DEFAULT_SERVER_ADDRESS);
|
|
printf("Enter server port, or enter for default: ");
|
|
Gets(port, 256);
|
|
if (port[0]==0)
|
|
strcpy_s(port, DEFAULT_SERVER_PORT);
|
|
const int intServerPort = atoi(port);
|
|
if ((intServerPort < 0) || (intServerPort > std::numeric_limits<unsigned short>::max())) {
|
|
printf("Specified server port %d is outside valid bounds [0, %u]", intServerPort, std::numeric_limits<unsigned short>::max());
|
|
phase = EXIT_SAMPLE;
|
|
break;
|
|
}
|
|
ConnectionAttemptResult car = rakPeer->Connect(serverIPAddr, static_cast<unsigned short>(intServerPort), 0, 0);
|
|
if (car!= SLNet::CONNECTION_ATTEMPT_STARTED)
|
|
{
|
|
printf("Failed connect call to %s. Code=%i\n", serverIPAddr, car);
|
|
phase = EXIT_SAMPLE;
|
|
}
|
|
}
|
|
break;
|
|
#ifdef NAT_TYPE_DETECTION_SERVER
|
|
case DETERMINE_NAT_TYPE:
|
|
printf("Determining NAT type...\n");
|
|
natTypeDetectionClient->DetectNATType(natPunchServerAddress);
|
|
break;
|
|
#endif
|
|
case SEARCH_FOR_GAMES:
|
|
SearchForGames();
|
|
break;
|
|
case NAT_PUNCH_TO_GAME_HOST:
|
|
printf("Attempting NAT punch to host of game session...\n");
|
|
break;
|
|
case IN_LOBBY_WITH_HOST:
|
|
printf("(1) to join team 1.\n");
|
|
printf("(2) to join team 2.\n");
|
|
printf("(R)eady to start\n");
|
|
printf("(U)nready to start\n");
|
|
break;
|
|
case IN_GAME:
|
|
printf("Game started.\n(C)hat in-game\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Reset(void)
|
|
{
|
|
lockGame=false;
|
|
gameInLobby=true;
|
|
}
|
|
|
|
void SearchForGames(void)
|
|
{
|
|
printf("Downloading rooms...\n");
|
|
|
|
RakString rsRequest = RakString::FormatForGET(
|
|
MASTER_SERVER_ADDRESS "/testServer?__gameId=comprehensivePCGame");
|
|
httpConnection2->TransmitRequest(rsRequest, MASTER_SERVER_ADDRESS, MASTER_SERVER_PORT);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------
|
|
// Serialized variables
|
|
// ---------------------------------------------------------------------------------
|
|
// Shows up in game listings
|
|
RakString gameName;
|
|
// If host locked the game, join queries are rejected
|
|
bool lockGame;
|
|
// Game is either in the lobby or in gameplay
|
|
bool gameInLobby;
|
|
// Which row of the master server our game was uploaded to
|
|
// Returned from the POST request
|
|
// This is serialized so if the host migrates, the new host takes over that row. Otherwise, a new row would be written for the same game.
|
|
int masterServerRow;
|
|
|
|
// ---------------------------------------------------------------------------------
|
|
// Not serialized variables
|
|
// ---------------------------------------------------------------------------------
|
|
// Store what type of router I am behind
|
|
SLNet::NATTypeDetectionResult myNatType;
|
|
Phase phase;
|
|
// NAT punchthrough server runs RakNet project NatCompleteServer with NAT_TYPE_DETECTION_SERVER, and NAT_PUNCHTHROUGH_SERVER
|
|
RakNetGUID natPunchServerGuid;
|
|
SystemAddress natPunchServerAddress;
|
|
char serverIPAddr[256];
|
|
// Just tracks what other objects have been created
|
|
DataStructures::List<User*> users;
|
|
DataStructures::List<Team*> teams;
|
|
|
|
// Master server has to be refreshed periodically so it knows we didn't crash
|
|
SLNet::Time whenToNextUpdateMasterServer;
|
|
|
|
// Helper function to store and read the JSON from the GET request
|
|
void SetMasterServerQueryResult(json_t *root)
|
|
{
|
|
if (masterServerQueryResult)
|
|
json_decref(masterServerQueryResult);
|
|
masterServerQueryResult = root;
|
|
}
|
|
|
|
json_t* GetMasterServerQueryResult(void)
|
|
{
|
|
if (masterServerQueryResult == 0)
|
|
return 0;
|
|
void *iter = json_object_iter(masterServerQueryResult);
|
|
while (iter)
|
|
{
|
|
const char *firstKey = json_object_iter_key(iter);
|
|
if (_stricmp(firstKey, "GET")==0)
|
|
{
|
|
return json_object_iter_value(iter);
|
|
}
|
|
iter = json_object_iter_next(masterServerQueryResult, iter);
|
|
RakAssert(iter != 0);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// The GET request returns a string. I use http://www.digip.org/jansson/ to parse the string, and store the results.
|
|
json_t *masterServerQueryResult;
|
|
json_t *jsonArray;
|
|
|
|
} *game;
|
|
|
|
|
|
// Team represents a list of players
|
|
// It uses TM_Team from the TeamManager plugin to do actual team functionality, store which players are on which teams, and networking
|
|
// It derives from Replica3 in order to replicate the teams across the network
|
|
class Team : public Replica3
|
|
{
|
|
public:
|
|
Team()
|
|
{
|
|
game->teams.Push(this, _FILE_AND_LINE_); tmTeam.SetOwner(this);
|
|
}
|
|
virtual ~Team()
|
|
{
|
|
game->teams.RemoveAtIndex(game->teams.GetIndexOf(this));
|
|
}
|
|
virtual void WriteAllocationID(SLNet::Connection_RM3 *destinationConnection, SLNet::BitStream *allocationIdBitstream) const
|
|
{
|
|
// unused parameters
|
|
(void)destinationConnection;
|
|
|
|
allocationIdBitstream->Write("Team");
|
|
}
|
|
virtual RM3ConstructionState QueryConstruction(SLNet::Connection_RM3 *destinationConnection, ReplicaManager3 *replicaManager3)
|
|
{
|
|
// unused parameters
|
|
(void)replicaManager3;
|
|
|
|
// This implementation has the host create the Team instances initially
|
|
// If the original host disconnects, the new host as determined by FullyConnectedMesh2 takes over replication duties
|
|
if (fullyConnectedMesh2->IsConnectedHost())
|
|
return QueryConstruction_PeerToPeer(destinationConnection, R3P2PM_MULTI_OWNER_CURRENTLY_AUTHORITATIVE);
|
|
else
|
|
return QueryConstruction_PeerToPeer(destinationConnection, R3P2PM_MULTI_OWNER_NOT_CURRENTLY_AUTHORITATIVE);
|
|
}
|
|
virtual bool QueryRemoteConstruction(SLNet::Connection_RM3 *sourceConnection)
|
|
{
|
|
// unused parameters
|
|
(void)sourceConnection;
|
|
|
|
return true;
|
|
}
|
|
virtual void SerializeConstruction(SLNet::BitStream *constructionBitstream, SLNet::Connection_RM3 *destinationConnection)
|
|
{
|
|
// unused parameters
|
|
(void)destinationConnection;
|
|
|
|
constructionBitstream->Write(teamName);
|
|
}
|
|
virtual bool DeserializeConstruction(SLNet::BitStream *constructionBitstream, SLNet::Connection_RM3 *sourceConnection)
|
|
{
|
|
// unused parameters
|
|
(void)sourceConnection;
|
|
|
|
constructionBitstream->Read(teamName);
|
|
printf("Downloaded team. name=%s\n", teamName.C_String());
|
|
// When ReplicaManager3 creates the team from a network command, the TeamManager class has to be informed of the new TM_Team instance
|
|
teamManager->GetWorldAtIndex(0)->ReferenceTeam(&tmTeam, GetNetworkID(), false);
|
|
return true;
|
|
}
|
|
|
|
virtual void PostSerializeConstruction(SLNet::BitStream *constructionBitstream, SLNet::Connection_RM3 *destinationConnection)
|
|
{
|
|
// unused parameters
|
|
(void)destinationConnection;
|
|
|
|
tmTeam.SerializeConstruction(constructionBitstream);
|
|
}
|
|
virtual void PostDeserializeConstruction(SLNet::BitStream *constructionBitstream, SLNet::Connection_RM3 *sourceConnection)
|
|
{
|
|
// unused parameters
|
|
(void)sourceConnection;
|
|
|
|
tmTeam.DeserializeConstruction(teamManager, constructionBitstream);
|
|
}
|
|
|
|
virtual void SerializeDestruction(SLNet::BitStream *destructionBitstream, SLNet::Connection_RM3 *destinationConnection)
|
|
{
|
|
// unused parameters
|
|
(void)destructionBitstream;
|
|
(void)destinationConnection;
|
|
}
|
|
virtual bool DeserializeDestruction(SLNet::BitStream *destructionBitstream, SLNet::Connection_RM3 *sourceConnection)
|
|
{
|
|
// unused parameters
|
|
(void)destructionBitstream;
|
|
(void)sourceConnection;
|
|
|
|
return true;
|
|
}
|
|
virtual SLNet::RM3ActionOnPopConnection QueryActionOnPopConnection(SLNet::Connection_RM3 *droppedConnection) const
|
|
{
|
|
// unused parameters
|
|
(void)droppedConnection;
|
|
|
|
// Do not destroy the object when the connection that created it disconnects.
|
|
return RM3AOPC_DO_NOTHING;
|
|
}
|
|
virtual void DeallocReplica(SLNet::Connection_RM3 *sourceConnection)
|
|
{
|
|
// unused parameters
|
|
(void)sourceConnection;
|
|
|
|
delete this;
|
|
}
|
|
virtual SLNet::RM3QuerySerializationResult QuerySerialization(SLNet::Connection_RM3 *destinationConnection)
|
|
{
|
|
// Whoever is currently the host serializes the class
|
|
if (fullyConnectedMesh2->IsConnectedHost())
|
|
return QuerySerialization_PeerToPeer(destinationConnection, R3P2PM_MULTI_OWNER_CURRENTLY_AUTHORITATIVE);
|
|
else
|
|
return QuerySerialization_PeerToPeer(destinationConnection, R3P2PM_MULTI_OWNER_NOT_CURRENTLY_AUTHORITATIVE);
|
|
}
|
|
virtual RM3SerializationResult Serialize(SLNet::SerializeParameters *serializeParameters)
|
|
{
|
|
// unused parameters
|
|
(void)serializeParameters;
|
|
|
|
return RM3SR_BROADCAST_IDENTICALLY;
|
|
}
|
|
virtual void Deserialize(SLNet::DeserializeParameters *deserializeParameters)
|
|
{
|
|
// unused parameters
|
|
(void)deserializeParameters;
|
|
}
|
|
|
|
// Team data managed by the TeamManager plugin
|
|
TM_Team tmTeam;
|
|
|
|
// Example of team data not managed by TeamManager
|
|
RakString teamName;
|
|
};
|
|
|
|
// User represents a player in the game
|
|
// Each system creates one user on startup. Other users are downloaded
|
|
// User also contains a TM_TeamMember instance, since users join teams
|
|
class User : public Replica3
|
|
{
|
|
public:
|
|
User()
|
|
{
|
|
game->users.Push(this, _FILE_AND_LINE_);
|
|
tmTeamMember.SetOwner(this);
|
|
natType=NAT_TYPE_UNKNOWN;
|
|
}
|
|
virtual ~User()
|
|
{
|
|
game->users.RemoveAtIndex(game->users.GetIndexOf(this));
|
|
}
|
|
virtual void WriteAllocationID(SLNet::Connection_RM3 *destinationConnection, SLNet::BitStream *allocationIdBitstream) const
|
|
{
|
|
// unused parameters
|
|
(void)destinationConnection;
|
|
|
|
allocationIdBitstream->Write("User");
|
|
}
|
|
virtual RM3ConstructionState QueryConstruction(SLNet::Connection_RM3 *destinationConnection, ReplicaManager3 *replicaManager3)
|
|
{
|
|
// unused parameters
|
|
(void)replicaManager3;
|
|
|
|
// Whoever created the user replicates it.
|
|
return QueryConstruction_PeerToPeer(destinationConnection);
|
|
}
|
|
virtual bool QueryRemoteConstruction(SLNet::Connection_RM3 *sourceConnection)
|
|
{
|
|
// unused parameters
|
|
(void)sourceConnection;
|
|
|
|
return true;
|
|
}
|
|
virtual void SerializeConstruction(SLNet::BitStream *constructionBitstream, SLNet::Connection_RM3 *destinationConnection)
|
|
{
|
|
// unused parameters
|
|
(void)destinationConnection;
|
|
|
|
constructionBitstream->Write(userName);
|
|
constructionBitstream->WriteCasted<unsigned char>(natType);
|
|
constructionBitstream->Write(playerGuid);
|
|
constructionBitstream->Write(playerAddress);
|
|
}
|
|
virtual bool DeserializeConstruction(SLNet::BitStream *constructionBitstream, SLNet::Connection_RM3 *sourceConnection)
|
|
{
|
|
// unused parameters
|
|
(void)sourceConnection;
|
|
|
|
// The TeamManager plugin has to be informed of TM_TeamMember instances created over the network
|
|
teamManager->GetWorldAtIndex(0)->ReferenceTeamMember(&tmTeamMember, GetNetworkID());
|
|
constructionBitstream->Read(userName);
|
|
constructionBitstream->ReadCasted<unsigned char>(natType);
|
|
constructionBitstream->Read(playerGuid);
|
|
constructionBitstream->Read(playerAddress);
|
|
return true;
|
|
}
|
|
virtual void PostSerializeConstruction(SLNet::BitStream *constructionBitstream, SLNet::Connection_RM3 *destinationConnection)
|
|
{
|
|
// unused parameters
|
|
(void)destinationConnection;
|
|
|
|
// TeamManager requires that TM_Team was created before TM_TeamMember that uses it.
|
|
// PostSerializeConstruction and PostDeserializeConstruction ensure that all objects have been created before serialization
|
|
tmTeamMember.SerializeConstruction(constructionBitstream);
|
|
}
|
|
virtual void PostDeserializeConstruction(SLNet::BitStream *constructionBitstream, SLNet::Connection_RM3 *sourceConnection)
|
|
{
|
|
// unused parameters
|
|
(void)sourceConnection;
|
|
|
|
tmTeamMember.DeserializeConstruction(teamManager, constructionBitstream);
|
|
printf("Downloaded user. name=%s", userName.C_String());
|
|
if (tmTeamMember.GetCurrentTeam()==0)
|
|
printf(" not on a team\n");
|
|
else
|
|
printf(" on team %s\n", ((Team*)(tmTeamMember.GetCurrentTeam()->GetOwner()))->teamName.C_String());
|
|
|
|
// Update the user count on the master server as new users join
|
|
if (fullyConnectedMesh2->IsConnectedHost())
|
|
PostRoomToMaster();
|
|
}
|
|
|
|
virtual void SerializeDestruction(SLNet::BitStream *destructionBitstream, SLNet::Connection_RM3 *destinationConnection)
|
|
{
|
|
// unused parameters
|
|
(void)destructionBitstream;
|
|
(void)destinationConnection;
|
|
}
|
|
virtual bool DeserializeDestruction(SLNet::BitStream *destructionBitstream, SLNet::Connection_RM3 *sourceConnection)
|
|
{
|
|
// unused parameters
|
|
(void)destructionBitstream;
|
|
(void)sourceConnection;
|
|
|
|
return true;
|
|
}
|
|
virtual SLNet::RM3ActionOnPopConnection QueryActionOnPopConnection(SLNet::Connection_RM3 *droppedConnection) const
|
|
{
|
|
return QueryActionOnPopConnection_PeerToPeer(droppedConnection);
|
|
}
|
|
virtual void DeallocReplica(SLNet::Connection_RM3 *sourceConnection)
|
|
{
|
|
// unused parameters
|
|
(void)sourceConnection;
|
|
|
|
delete this;
|
|
}
|
|
virtual SLNet::RM3QuerySerializationResult QuerySerialization(SLNet::Connection_RM3 *destinationConnection)
|
|
{
|
|
return QuerySerialization_PeerToPeer(destinationConnection);
|
|
}
|
|
virtual RM3SerializationResult Serialize(SLNet::SerializeParameters *serializeParameters)
|
|
{
|
|
// unused parameters
|
|
(void)serializeParameters;
|
|
|
|
return RM3SR_BROADCAST_IDENTICALLY;
|
|
}
|
|
virtual void Deserialize(SLNet::DeserializeParameters *deserializeParameters)
|
|
{
|
|
// unused parameters
|
|
(void)deserializeParameters;
|
|
}
|
|
|
|
// Team data managed by the TeamManager plugin
|
|
TM_TeamMember tmTeamMember;
|
|
RakString userName;
|
|
NATTypeDetectionResult natType;
|
|
RakNetGUID playerGuid;
|
|
SystemAddress playerAddress;
|
|
};
|
|
|
|
|
|
// Required by ReplicaManager3. Acts as a class factory for Replica3 derived instances
|
|
class SampleConnectionRM3 : public Connection_RM3
|
|
{
|
|
public:
|
|
SampleConnectionRM3(const SystemAddress &_systemAddress, RakNetGUID _guid) : Connection_RM3(_systemAddress, _guid)
|
|
{
|
|
}
|
|
virtual ~SampleConnectionRM3()
|
|
{
|
|
}
|
|
virtual Replica3 *AllocReplica(SLNet::BitStream *allocationIdBitstream, ReplicaManager3 *replicaManager3)
|
|
{
|
|
// unused parameters
|
|
(void)replicaManager3;
|
|
|
|
RakString objectType;
|
|
// Types are written by WriteAllocationID()
|
|
allocationIdBitstream->Read(objectType);
|
|
if (objectType=="User") return new User;
|
|
if (objectType=="Team") return new Team;
|
|
RakAssert("Unknown type in AllocReplica" && 0);
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
// Required by ReplicaManager3. Acts as a class factory for Connection_RM3 derived instances
|
|
class SampleRM3 : public ReplicaManager3
|
|
{
|
|
public:
|
|
SampleRM3()
|
|
{
|
|
}
|
|
virtual ~SampleRM3()
|
|
{
|
|
}
|
|
virtual Connection_RM3* AllocConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID) const
|
|
{
|
|
return new SampleConnectionRM3(systemAddress,rakNetGUID);
|
|
}
|
|
virtual void DeallocConnection(Connection_RM3 *connection) const
|
|
{
|
|
delete connection;
|
|
}
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
// Helper functions
|
|
// ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
// Demonstrates how to use the RPC4 plugin
|
|
void InGameChat(SLNet::BitStream *userData, Packet *packet)
|
|
{
|
|
// unused parameters
|
|
(void)packet;
|
|
|
|
RakString rs;
|
|
userData->Read(rs);
|
|
printf("%s\n", rs.C_String());
|
|
}
|
|
// Register the function where it is defined, which is easier than maintaining a bunch of RegisterSlot() calls in main()
|
|
RPC4GlobalRegistration __InGameChat("InGameChat", InGameChat, 0);
|
|
|
|
// Write roomName and a list of NATTypeDetectionResult to a bitStream
|
|
void SerializeToJSON(RakString &outputString, RakString &roomName, DataStructures::List<NATTypeDetectionResult> &natTypes)
|
|
{
|
|
outputString.Set("'roomName': '%s', 'guid': '%s', 'natTypes' : [ ", roomName.C_String(), rakPeer->GetMyGUID().ToString());
|
|
for (unsigned short i=0; i < natTypes.Size(); i++)
|
|
{
|
|
if (i!=0)
|
|
outputString += ", ";
|
|
RakString appendStr("{'type': %i}", natTypes[i]);
|
|
outputString += appendStr;
|
|
}
|
|
outputString += " ] ";
|
|
}
|
|
|
|
// A system has connected and is ready to participate in the game
|
|
// Register this system with the plugins that need to know about new participants
|
|
// This operation happens after FullyConnectedMesh2 has told us about who the host is.
|
|
void RegisterGameParticipant(RakNetGUID guid)
|
|
{
|
|
Connection_RM3 *connection = gameReplicaManager3->AllocConnection(rakPeer->GetSystemAddressFromGuid(guid), guid);
|
|
if (gameReplicaManager3->PushConnection(connection)==false)
|
|
gameReplicaManager3->DeallocConnection(connection);
|
|
teamManager->GetWorldAtIndex(0)->AddParticipant(guid);
|
|
readyEvent->AddToWaitList(0, guid);
|
|
}
|
|
|
|
// Upload details about the current game state to the cloud
|
|
// This is the responsibility of the system that initially created that room.
|
|
// If that system disconnects, the new host, as determined by FullyConnectedMesh2 will reupload the room
|
|
void PostRoomToMaster(void)
|
|
{
|
|
BitStream bsOut;
|
|
RakString jsonSerializedRoom;
|
|
DataStructures::List<NATTypeDetectionResult> natTypes;
|
|
for (unsigned int i=0; i < game->users.Size(); i++)
|
|
natTypes.Push(game->users[i]->natType, _FILE_AND_LINE_);
|
|
SerializeToJSON(jsonSerializedRoom, game->gameName, natTypes);
|
|
|
|
RakString rowStr;
|
|
if (game->masterServerRow!=-1)
|
|
rowStr.Set("\"__rowId\": %i,", game->masterServerRow);
|
|
|
|
// See http://masterserver2.raknet.com/
|
|
RakString rsRequest = RakString::FormatForPOST(
|
|
(const char*) MASTER_SERVER_ADDRESS "/testServer",
|
|
"text/plain; charset=UTF-8",
|
|
RakString("{'__gameId': 'comprehensivePCGame', '__clientReqId': '0', %s '__timeoutSec': '30', %s }", rowStr.C_String(), jsonSerializedRoom.C_String()));
|
|
|
|
// Refresh the room again slightly less than every 30 seconds
|
|
game->whenToNextUpdateMasterServer = SLNet::GetTime() + 30000 - 1000;
|
|
|
|
httpConnection2->TransmitRequest(rsRequest, MASTER_SERVER_ADDRESS, MASTER_SERVER_PORT);
|
|
|
|
printf("Posted game session. In room.\n");
|
|
}
|
|
void ReleaseRoomFromCloud(void)
|
|
{
|
|
RakString rsRequest = RakString::FormatForDELETE(
|
|
RakString(MASTER_SERVER_ADDRESS "/testServer?__gameId=comprehensivePCGame&__rowId=%i", game->masterServerRow));
|
|
httpConnection2->TransmitRequest(rsRequest, MASTER_SERVER_ADDRESS, MASTER_SERVER_PORT);
|
|
game->masterServerRow=-1;
|
|
}
|
|
|
|
void CreateRoom(void)
|
|
{
|
|
size_t arraySize;
|
|
if (game->GetMasterServerQueryResult())
|
|
arraySize = json_array_size(game->GetMasterServerQueryResult());
|
|
else
|
|
arraySize = 0;
|
|
|
|
if (arraySize > 0)
|
|
{
|
|
printf("Enter room name: ");
|
|
char rn[128];
|
|
Gets(rn, 128);
|
|
if (rn[0]==0)
|
|
strcpy_s(rn, "Unnamed");
|
|
game->gameName = rn;
|
|
}
|
|
else
|
|
{
|
|
game->gameName = "Default room name";
|
|
}
|
|
|
|
// Upload the room to the server
|
|
PostRoomToMaster();
|
|
|
|
// Room owner creates two teams and registers them for replication
|
|
Team *team1 = new Team;
|
|
team1->SetNetworkIDManager(networkIDManager);
|
|
team1->teamName = "Team1";
|
|
teamManager->GetWorldAtIndex(0)->ReferenceTeam(&team1->tmTeam, team1->GetNetworkID(), false);
|
|
Team *team2 = new Team;
|
|
team2->SetNetworkIDManager(networkIDManager);
|
|
team2->teamName = "Team2";
|
|
teamManager->GetWorldAtIndex(0)->ReferenceTeam(&team2->tmTeam, team2->GetNetworkID(), false);
|
|
|
|
game->EnterPhase(Game::IN_LOBBY_WAITING_FOR_HOST);
|
|
|
|
// So that time spent in single player does not count towards which system has been running the longest in multiplayer
|
|
fullyConnectedMesh2->ResetHostCalculation();
|
|
|
|
printf("(E)xit session\n");
|
|
}
|
|
|
|
#if USE_UPNP!=0
|
|
struct UPNPOpenWorkerArgs
|
|
{
|
|
char buff[256];
|
|
unsigned short portToOpen;
|
|
unsigned int timeout;
|
|
void *userData;
|
|
void (*resultCallback)(bool success, unsigned short portToOpen, void *userData);
|
|
void (*progressCallback)(const char *progressMsg, void *userData);
|
|
};
|
|
RAK_THREAD_DECLARATION(UPNPOpenWorker)
|
|
{
|
|
UPNPOpenWorkerArgs *args = ( UPNPOpenWorkerArgs * ) arguments;
|
|
bool success=false;
|
|
|
|
// Behind a NAT. Try to open with UPNP to avoid doing NAT punchthrough
|
|
struct UPNPDev * devlist = 0;
|
|
devlist = upnpDiscover(args->timeout, 0, 0, 0, 0, 0);
|
|
if (devlist)
|
|
{
|
|
if (args->progressCallback)
|
|
args->progressCallback("List of UPNP devices found on the network :\n", args->userData);
|
|
struct UPNPDev * device;
|
|
for(device = devlist; device; device = device->pNext)
|
|
{
|
|
sprintf_s(args->buff, " desc: %s\n st: %s\n\n", device->descURL, device->st);
|
|
if (args->progressCallback)
|
|
args->progressCallback(args->buff, args->userData);
|
|
}
|
|
|
|
char lanaddr[64]; /* my ip address on the LAN */
|
|
struct UPNPUrls urls;
|
|
struct IGDdatas data;
|
|
if (UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr))==1)
|
|
{
|
|
char iport[32];
|
|
Itoa(args->portToOpen, iport,10);
|
|
char eport[32];
|
|
strcpy_s(eport, iport);
|
|
|
|
// Version miniupnpc-1.6.20120410
|
|
int r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype,
|
|
eport, iport, lanaddr, 0, "UDP", 0, "0");
|
|
|
|
if(r!=UPNPCOMMAND_SUCCESS)
|
|
printf("AddPortMapping(%s, %s, %s) failed with code %d (%s)\n",
|
|
eport, iport, lanaddr, r, strupnperror(r));
|
|
|
|
char intPort[6];
|
|
char intClient[16];
|
|
|
|
// Version miniupnpc-1.6.20120410
|
|
char desc[128];
|
|
char enabled[128];
|
|
char leaseDuration[128];
|
|
r = UPNP_GetSpecificPortMappingEntry(urls.controlURL,
|
|
data.first.servicetype,
|
|
eport, "UDP",
|
|
intClient, intPort,
|
|
desc, enabled, leaseDuration);
|
|
|
|
if(r!=UPNPCOMMAND_SUCCESS)
|
|
{
|
|
sprintf_s(args->buff, "GetSpecificPortMappingEntry() failed with code %d (%s)\n",
|
|
r, strupnperror(r));
|
|
if (args->progressCallback)
|
|
args->progressCallback(args->buff, args->userData);
|
|
}
|
|
else
|
|
{
|
|
if (args->progressCallback)
|
|
args->progressCallback("UPNP success.\n", args->userData);
|
|
// game->myNatType=NAT_TYPE_SUPPORTS_UPNP;
|
|
|
|
success=true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (args->resultCallback)
|
|
args->resultCallback(success, args->portToOpen, args->userData);
|
|
SLNet::OP_DELETE(args, _FILE_AND_LINE_);
|
|
return 1;
|
|
}
|
|
|
|
void UPNPOpenAsynch(unsigned short portToOpen,
|
|
unsigned int timeout,
|
|
void (*progressCallback)(const char *progressMsg, void *userData),
|
|
void (*resultCallback)(bool success, unsigned short portToOpen, void *userData),
|
|
void *userData
|
|
)
|
|
{
|
|
UPNPOpenWorkerArgs *args = SLNet::OP_NEW<UPNPOpenWorkerArgs>(_FILE_AND_LINE_);
|
|
args->portToOpen = portToOpen;
|
|
args->timeout = timeout;
|
|
args->userData = userData;
|
|
args->progressCallback = progressCallback;
|
|
args->resultCallback = resultCallback;
|
|
RakThread::Create(UPNPOpenWorker, args);
|
|
}
|
|
|
|
void UPNPProgressCallback(const char *progressMsg, void *userData)
|
|
{
|
|
// unused parameters
|
|
(void)userData;
|
|
|
|
printf(progressMsg);
|
|
}
|
|
void UPNPResultCallback(bool success, unsigned short portToOpen, void *userData)
|
|
{
|
|
// unused parameters
|
|
(void)portToOpen;
|
|
(void)userData;
|
|
|
|
if (success)
|
|
game->myNatType=NAT_TYPE_SUPPORTS_UPNP;
|
|
game->EnterPhase(Game::SEARCH_FOR_GAMES);
|
|
}
|
|
|
|
void OpenUPNP(void)
|
|
{
|
|
printf("Discovering UPNP...\n");
|
|
|
|
DataStructures::List<RakNetSocket2* > sockets;
|
|
rakPeer->GetSockets(sockets);
|
|
UPNPOpenAsynch(sockets[0]->GetBoundAddress().GetPort(), 2000, UPNPProgressCallback, UPNPResultCallback, 0);
|
|
}
|
|
|
|
#endif
|
|
|
|
// ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
// main
|
|
// ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
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");
|
|
|
|
/*
|
|
json_t *jsonObject = json_object();
|
|
json_object_set(jsonObject, "testKey", json_string("\"") );
|
|
char *ds = json_dumps(jsonObject,0);
|
|
*/
|
|
|
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
// Allocate plugins. See declaration in this file for description of each
|
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
rakPeer= SLNet::RakPeerInterface::GetInstance();
|
|
teamManager=TeamManager::GetInstance();
|
|
fullyConnectedMesh2=FullyConnectedMesh2::GetInstance();
|
|
networkIDManager = NetworkIDManager::GetInstance();
|
|
tcp = TCPInterface::GetInstance();
|
|
natPunchthroughClient = NatPunchthroughClient::GetInstance();
|
|
#ifdef NAT_TYPE_DETECTION_SERVER
|
|
natTypeDetectionClient = NatTypeDetectionClient::GetInstance();
|
|
#endif
|
|
rpc4 = RPC4::GetInstance();
|
|
readyEvent = ReadyEvent::GetInstance();
|
|
gameReplicaManager3=new SampleRM3;
|
|
httpConnection2 = HTTPConnection2::GetInstance();
|
|
|
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
// Attach plugins
|
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
rakPeer->AttachPlugin(fullyConnectedMesh2);
|
|
rakPeer->AttachPlugin(teamManager);
|
|
rakPeer->AttachPlugin(natPunchthroughClient);
|
|
#ifdef NAT_TYPE_DETECTION_SERVER
|
|
rakPeer->AttachPlugin(natTypeDetectionClient);
|
|
#endif
|
|
rakPeer->AttachPlugin(rpc4);
|
|
rakPeer->AttachPlugin(readyEvent);
|
|
rakPeer->AttachPlugin(gameReplicaManager3);
|
|
/// TCPInterface supports plugins too
|
|
tcp->AttachPlugin(httpConnection2);
|
|
|
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
// 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
|
|
gameReplicaManager3->SetNetworkIDManager(networkIDManager);
|
|
// Do not automatically count new connections, but do drop lost connections automatically
|
|
gameReplicaManager3->SetAutoManageConnections(false,true);
|
|
|
|
// Reference static game objects that always exist
|
|
game = new Game;
|
|
game->SetNetworkIDManager(networkIDManager);
|
|
game->SetNetworkID(0);
|
|
gameReplicaManager3->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
|
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
SLNet::SocketDescriptor sd;
|
|
sd.socketFamily=AF_INET; // Only IPV4 supports broadcast on 255.255.255.255
|
|
sd.port=0;
|
|
SLNET_VERIFY(rakPeer->Startup(8, &sd, 1) == RAKNET_STARTED);
|
|
rakPeer->SetMaximumIncomingConnections(8);
|
|
rakPeer->SetTimeoutTime(30000, SLNet::UNASSIGNED_SYSTEM_ADDRESS);
|
|
printf("Our guid is %s\n", rakPeer->GetGuidFromSystemAddress(SLNet::UNASSIGNED_SYSTEM_ADDRESS).ToString());
|
|
printf("Started on %s\n", rakPeer->GetMyBoundAddress().ToString(true));
|
|
|
|
// Start TCPInterface and begin connecting to the NAT punchthrough server
|
|
tcp->Start(0,0,1);
|
|
|
|
// Connect to hosting server
|
|
game->EnterPhase(Game::CONNECTING_TO_SERVER);
|
|
|
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
// Read packets loop
|
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
int 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->natPunchServerAddress=packet->systemAddress;
|
|
game->natPunchServerGuid=packet->guid;
|
|
|
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
// PC self-hosted servers only: Use the NAT punch server to determine NAT type. Attempt to open router if needed.
|
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
if (NAT_TYPE_DETECTION_SERVER)
|
|
{
|
|
game->EnterPhase(Game::DETERMINE_NAT_TYPE);
|
|
}
|
|
else
|
|
{
|
|
OpenUPNP();
|
|
}
|
|
}
|
|
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->natPunchServerGuid)
|
|
{
|
|
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.g);
|
|
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:
|
|
{
|
|
SLNet::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)
|
|
{
|
|
if (game->phase==Game::IN_LOBBY_WAITING_FOR_HOST)
|
|
game->phase=Game::IN_LOBBY_WITH_HOST;
|
|
PostRoomToMaster();
|
|
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++)
|
|
gameReplicaManager3->Reference(game->teams[i]);
|
|
}
|
|
|
|
// Reference the user we created (host or not)
|
|
for (unsigned int i=0; i < game->users.Size(); i++)
|
|
gameReplicaManager3->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!= SLNet::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_NAT_TYPE_DETECTION_RESULT:
|
|
{
|
|
game->myNatType = (SLNet::NATTypeDetectionResult) packet->data[1];
|
|
printf("NAT Type is %s (%s)\n", NATTypeDetectionResultToString(game->myNatType), NATTypeDetectionResultToStringFriendly(game->myNatType));
|
|
|
|
if (game->myNatType!= SLNet::NAT_TYPE_NONE)
|
|
{
|
|
OpenUPNP();
|
|
}
|
|
|
|
if (game->myNatType== SLNet::NAT_TYPE_PORT_RESTRICTED || game->myNatType== SLNet::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;
|
|
DataStructures::List<BitStream*> userData;
|
|
fullyConnectedMesh2->GetVerifiedJoinRequiredProcessingList(packet->guid, addresses, guids, userData);
|
|
for (unsigned int i=0; i < guids.Size(); i++)
|
|
natPunchthroughClient->OpenNAT(guids[i], game->natPunchServerAddress);
|
|
}
|
|
break;
|
|
|
|
case ID_FCM2_VERIFIED_JOIN_CAPABLE:
|
|
printf("Client is capable of joining FullyConnectedMesh2.\n");
|
|
if (game->lockGame)
|
|
{
|
|
SLNet::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 (gameReplicaManager3->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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// The following code is TCP operations for talking to the master server, and parsing the reply
|
|
SystemAddress sa;
|
|
// This is kind of crappy, but for TCP plugins, always do HasCompletedConnectionAttempt, then Receive(), then HasFailedConnectionAttempt(),HasLostConnection()
|
|
sa = tcp->HasCompletedConnectionAttempt();
|
|
for (packet = tcp->Receive(); packet; tcp->DeallocatePacket(packet), packet = tcp->Receive())
|
|
;
|
|
sa = tcp->HasFailedConnectionAttempt();
|
|
sa = tcp->HasLostConnection();
|
|
|
|
RakString stringTransmitted;
|
|
RakString hostTransmitted;
|
|
RakString responseReceived;
|
|
SystemAddress hostReceived;
|
|
ptrdiff_t contentOffset;
|
|
if (httpConnection2->GetResponse(stringTransmitted, hostTransmitted, responseReceived, hostReceived, contentOffset))
|
|
{
|
|
if (responseReceived.IsEmpty()==false)
|
|
{
|
|
if (contentOffset==-1)
|
|
{
|
|
// No content
|
|
printf(responseReceived.C_String());
|
|
}
|
|
else
|
|
{
|
|
json_error_t error;
|
|
json_t *root = json_loads(responseReceived.C_String() + contentOffset, JSON_REJECT_DUPLICATES, &error);
|
|
if (!root)
|
|
{
|
|
printf("Error parsing JSON\n");
|
|
}
|
|
else
|
|
{
|
|
void *iter = json_object_iter(root);
|
|
while (iter)
|
|
{
|
|
const char *firstKey = json_object_iter_key(iter);
|
|
if (_stricmp(firstKey, "GET")==0)
|
|
{
|
|
game->SetMasterServerQueryResult(root);
|
|
root=0;
|
|
|
|
json_t* jsonArray = json_object_iter_value(iter);
|
|
size_t arraySize = json_array_size(jsonArray);
|
|
for (unsigned int i=0; i < arraySize; i++)
|
|
{
|
|
json_t* object = json_array_get(jsonArray, i);
|
|
json_t* roomNameVal = json_object_get(object, "roomName");
|
|
RakAssert(roomNameVal->type==JSON_STRING);
|
|
json_t* natTypesVal = json_object_get(object, "natTypes");
|
|
RakAssert(natTypesVal->type==JSON_ARRAY);
|
|
size_t natTypesSize = json_array_size(natTypesVal);
|
|
printf("Room name: %s. Players: %i\n", json_string_value(roomNameVal), natTypesSize);
|
|
}
|
|
|
|
if (arraySize==0)
|
|
printf("No rooms.\n");
|
|
|
|
|
|
printf("(J)oin room\n");
|
|
printf("(C)reate room\n");
|
|
printf("(S)earch rooms\n");
|
|
break;
|
|
}
|
|
else if (_stricmp(firstKey, "POST")==0)
|
|
{
|
|
RakAssert(_stricmp(firstKey, "POST")==0);
|
|
|
|
json_t* jsonObject = json_object_iter_value(iter);
|
|
json_t* val1 = json_object_get(jsonObject, "__rowId");
|
|
RakAssert(val1->type==JSON_INTEGER);
|
|
game->masterServerRow = (int) json_integer_value(val1);
|
|
|
|
printf("Session posted to row %i\n", game->masterServerRow);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
iter = json_object_iter_next(root, iter);
|
|
RakAssert(iter != 0);
|
|
}
|
|
}
|
|
|
|
json_decref(root);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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')
|
|
{
|
|
size_t arraySize = 0;
|
|
json_t *jsonArray = game->GetMasterServerQueryResult();
|
|
if (jsonArray)
|
|
{
|
|
arraySize = json_array_size(jsonArray);
|
|
}
|
|
|
|
// Join room
|
|
if (arraySize==0)
|
|
{
|
|
printf("No rooms to join.\n");
|
|
}
|
|
else
|
|
{
|
|
int index;
|
|
if (arraySize>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 >= arraySize)
|
|
{
|
|
printf("Index out of range.\n");
|
|
}
|
|
else
|
|
{
|
|
json_t* object = json_array_get(jsonArray, index);
|
|
json_t* guidVal = json_object_get(object, "guid");
|
|
RakAssert(guidVal->type==JSON_STRING);
|
|
RakNetGUID clientGUID;
|
|
clientGUID.FromString(json_string_value(guidVal));
|
|
if (clientGUID!=rakPeer->GetMyGUID())
|
|
{
|
|
natPunchthroughClient->OpenNAT(clientGUID, game->natPunchServerAddress);
|
|
game->EnterPhase(Game::NAT_PUNCH_TO_GAME_HOST);
|
|
}
|
|
else
|
|
{
|
|
printf("Cannot join your own room\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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);
|
|
gameReplicaManager3->Clear(false);
|
|
gameReplicaManager3->Reference(game);
|
|
|
|
game->Reset();
|
|
game->EnterPhase(Game::SEARCH_FOR_GAMES);
|
|
}
|
|
else if (ch=='q' || ch=='Q')
|
|
{
|
|
printf("Quitting.\n");
|
|
|
|
RakString rspost = RakString::FormatForGET(
|
|
RakString(MASTER_SERVER_ADDRESS "/testServer?row=%i", game->masterServerRow));
|
|
httpConnection2->TransmitRequest(rspost, MASTER_SERVER_ADDRESS, MASTER_SERVER_PORT);
|
|
|
|
game->EnterPhase(Game::EXIT_SAMPLE);
|
|
}
|
|
}
|
|
}
|
|
|
|
// The game host updates the master server
|
|
SLNet::Time t = SLNet::GetTime();
|
|
if ((fullyConnectedMesh2->IsConnectedHost() || game->users.Size()==1) &&
|
|
t > game->whenToNextUpdateMasterServer &&
|
|
(game->phase == Game::IN_LOBBY_WITH_HOST ||
|
|
game->phase == Game::IN_GAME ||
|
|
game->phase == Game::IN_LOBBY_WAITING_FOR_HOST)
|
|
)
|
|
{
|
|
PostRoomToMaster();
|
|
}
|
|
|
|
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;
|
|
|
|
RakPeerInterface::DestroyInstance(rakPeer);
|
|
TeamManager::DestroyInstance(teamManager);
|
|
FullyConnectedMesh2::DestroyInstance(fullyConnectedMesh2);
|
|
NatPunchthroughClient::DestroyInstance(natPunchthroughClient);
|
|
NatTypeDetectionClient::DestroyInstance(natTypeDetectionClient);
|
|
RPC4::DestroyInstance(rpc4);
|
|
ReadyEvent::DestroyInstance(readyEvent);
|
|
delete gameReplicaManager3;
|
|
NetworkIDManager::DestroyInstance(networkIDManager);
|
|
HTTPConnection2::DestroyInstance(httpConnection2);
|
|
|
|
return 1;
|
|
}
|
|
|