2014-05-19 14:01:06 -07:00
/* Copyright (C) 2014 Wildfire Games.
2009-04-18 10:00:33 -07:00
* This file is part of 0 A . D .
*
* 0 A . D . is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 2 of the License , or
* ( at your option ) any later version .
*
* 0 A . D . is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with 0 A . D . If not , see < http : //www.gnu.org/licenses/>.
*/
2009-04-11 10:00:39 -07:00
# include "precompiled.h"
2010-06-07 15:19:05 -07:00
# include "NetServer.h"
2010-06-30 14:41:04 -07:00
# include "NetClient.h"
# include "NetMessage.h"
2009-04-11 10:00:39 -07:00
# include "NetSession.h"
2010-07-03 06:15:01 -07:00
# include "NetStats.h"
2010-06-30 14:41:04 -07:00
# include "NetTurnManager.h"
2010-06-07 15:19:05 -07:00
2010-08-10 14:49:33 -07:00
# include "lib/external_libraries/enet.h"
2010-06-07 15:19:05 -07:00
# include "ps/CLogger.h"
2014-11-17 15:29:49 -08:00
# include "ps/ConfigDB.h"
2010-06-30 14:41:04 -07:00
# include "scriptinterface/ScriptInterface.h"
2014-11-13 03:19:28 -08:00
# include "scriptinterface/ScriptRuntime.h"
2010-05-19 17:59:01 -07:00
# include "simulation2/Simulation2.h"
2009-04-11 10:00:39 -07:00
2013-12-18 08:08:56 -08:00
# if CONFIG2_MINIUPNPC
2013-12-12 18:23:02 -08:00
# include <miniupnpc/miniwget.h>
# include <miniupnpc/miniupnpc.h>
# include <miniupnpc/upnpcommands.h>
# include <miniupnpc/upnperrors.h>
2013-12-18 08:08:56 -08:00
# endif
2013-12-12 18:23:02 -08:00
2010-06-30 14:41:04 -07:00
# define DEFAULT_SERVER_NAME L"Unnamed Server"
# define DEFAULT_WELCOME_MESSAGE L"Welcome"
# define MAX_CLIENTS 8
2009-04-11 10:00:39 -07:00
2011-05-29 13:57:28 -07:00
static const int CHANNEL_COUNT = 1 ;
2010-10-31 15:00:28 -07:00
/**
* enet_host_service timeout ( msecs ) .
* Smaller numbers may hurt performance ; larger numbers will
* hurt latency responding to messages from game thread .
*/
static const int HOST_SERVICE_TIMEOUT = 50 ;
2010-06-30 14:41:04 -07:00
CNetServer * g_NetServer = NULL ;
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
static CStr DebugName ( CNetServerSession * session )
2009-04-11 10:00:39 -07:00
{
2010-06-30 14:41:04 -07:00
if ( session = = NULL )
return " [unknown host] " ;
if ( session - > GetGUID ( ) . empty ( ) )
return " [unauthed host] " ;
return " [ " + session - > GetGUID ( ) . substr ( 0 , 8 ) + " ...] " ;
}
2009-04-11 10:00:39 -07:00
2011-10-27 09:46:48 -07:00
/**
* Async task for receiving the initial game state to be forwarded to another
* client that is rejoining an in - progress network game .
*/
class CNetFileReceiveTask_ServerRejoin : public CNetFileReceiveTask
{
NONCOPYABLE ( CNetFileReceiveTask_ServerRejoin ) ;
public :
CNetFileReceiveTask_ServerRejoin ( CNetServerWorker & server , u32 hostID )
: m_Server ( server ) , m_RejoinerHostID ( hostID )
{
}
virtual void OnComplete ( )
{
// We've received the game state from an existing player - now
// we need to send it onwards to the newly rejoining player
// Find the session corresponding to the rejoining host (if any)
CNetServerSession * session = NULL ;
for ( size_t i = 0 ; i < m_Server . m_Sessions . size ( ) ; + + i )
{
if ( m_Server . m_Sessions [ i ] - > GetHostID ( ) = = m_RejoinerHostID )
{
session = m_Server . m_Sessions [ i ] ;
break ;
}
}
if ( ! session )
{
2015-01-22 12:31:30 -08:00
LOGMESSAGE ( " Net server: rejoining client disconnected before we sent to it " ) ;
2011-10-27 09:46:48 -07:00
return ;
}
// Store the received state file, and tell the client to start downloading it from us
// TODO: this will get kind of confused if there's multiple clients downloading in parallel;
// they'll race and get whichever happens to be the latest received by the server,
// which should still work but isn't great
m_Server . m_JoinSyncFile = m_Buffer ;
CJoinSyncStartMessage message ;
session - > SendMessage ( & message ) ;
}
private :
CNetServerWorker & m_Server ;
u32 m_RejoinerHostID ;
} ;
2010-10-31 15:00:28 -07:00
/*
* XXX : We use some non - threadsafe functions from the worker thread .
* See http : //trac.wildfiregames.com/ticket/654
*/
CNetServerWorker : : CNetServerWorker ( int autostartPlayers ) :
m_AutostartPlayers ( autostartPlayers ) ,
m_Shutdown ( false ) ,
m_ScriptInterface ( NULL ) ,
m_NextHostID ( 1 ) , m_Host ( NULL ) , m_Stats ( NULL )
2010-06-30 14:41:04 -07:00
{
m_State = SERVER_STATE_UNCONNECTED ;
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
m_ServerTurnManager = NULL ;
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
m_ServerName = DEFAULT_SERVER_NAME ;
m_WelcomeMessage = DEFAULT_WELCOME_MESSAGE ;
2009-04-11 10:00:39 -07:00
}
2010-10-31 15:00:28 -07:00
CNetServerWorker : : ~ CNetServerWorker ( )
2009-04-11 10:00:39 -07:00
{
2010-10-31 15:00:28 -07:00
if ( m_State ! = SERVER_STATE_UNCONNECTED )
{
// Tell the thread to shut down
{
CScopeLock lock ( m_WorkerMutex ) ;
m_Shutdown = true ;
}
// Wait for it to shut down cleanly
pthread_join ( m_WorkerThread , NULL ) ;
}
// Clean up resources
2010-07-03 06:15:01 -07:00
delete m_Stats ;
2010-07-02 14:28:48 -07:00
for ( size_t i = 0 ; i < m_Sessions . size ( ) ; + + i )
{
2010-07-06 12:54:17 -07:00
m_Sessions [ i ] - > DisconnectNow ( NDR_UNEXPECTED_SHUTDOWN ) ;
2010-07-02 14:28:48 -07:00
delete m_Sessions [ i ] ;
}
if ( m_Host )
{
enet_host_destroy ( m_Host ) ;
}
2010-07-06 12:54:17 -07:00
delete m_ServerTurnManager ;
2009-04-11 10:00:39 -07:00
}
2010-10-31 15:00:28 -07:00
bool CNetServerWorker : : SetupConnection ( )
2009-04-11 10:00:39 -07:00
{
2011-04-30 06:01:45 -07:00
ENSURE ( m_State = = SERVER_STATE_UNCONNECTED ) ;
ENSURE ( ! m_Host ) ;
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
// Bind to default host
ENetAddress addr ;
addr . host = ENET_HOST_ANY ;
2010-07-06 12:54:17 -07:00
addr . port = PS_DEFAULT_PORT ;
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
// Create ENet server
2011-05-29 13:57:28 -07:00
m_Host = enet_host_create ( & addr , MAX_CLIENTS , CHANNEL_COUNT , 0 , 0 ) ;
2010-06-30 14:41:04 -07:00
if ( ! m_Host )
2009-04-11 10:00:39 -07:00
{
2015-01-22 12:31:30 -08:00
LOGERROR ( " Net server: enet_host_create failed " ) ;
2009-04-11 10:00:39 -07:00
return false ;
}
2010-10-31 15:00:28 -07:00
m_Stats = new CNetStatsTable ( ) ;
2010-07-04 12:59:05 -07:00
if ( CProfileViewer : : IsInitialised ( ) )
g_ProfileViewer . AddRootTable ( m_Stats ) ;
2010-07-03 06:15:01 -07:00
2010-06-30 14:41:04 -07:00
m_State = SERVER_STATE_PREGAME ;
2010-10-31 15:00:28 -07:00
// Launch the worker thread
int ret = pthread_create ( & m_WorkerThread , NULL , & RunThread , this ) ;
2011-04-30 06:01:45 -07:00
ENSURE ( ret = = 0 ) ;
2010-10-31 15:00:28 -07:00
2013-12-18 08:08:56 -08:00
# if CONFIG2_MINIUPNPC
2013-12-17 09:03:49 -08:00
// Launch the UPnP thread
ret = pthread_create ( & m_UPnPThread , NULL , & SetupUPnP , NULL ) ;
ENSURE ( ret = = 0 ) ;
2013-12-18 08:08:56 -08:00
# endif
2013-12-17 09:03:49 -08:00
2013-12-17 06:21:49 -08:00
return true ;
}
2013-12-12 18:23:02 -08:00
2013-12-18 08:08:56 -08:00
# if CONFIG2_MINIUPNPC
2013-12-17 09:03:49 -08:00
void * CNetServerWorker : : SetupUPnP ( void * )
2013-12-17 06:21:49 -08:00
{
2013-12-12 18:23:02 -08:00
// Values we want to set.
2013-12-14 17:02:26 -08:00
char psPort [ 6 ] ;
sprintf_s ( psPort , ARRAY_SIZE ( psPort ) , " %d " , PS_DEFAULT_PORT ) ;
const char * leaseDuration = " 0 " ; // Indefinite/permanent lease duration.
const char * description = " 0AD Multiplayer " ;
const char * protocall = " UDP " ;
2013-12-12 18:23:02 -08:00
char internalIPAddress [ 64 ] ;
char externalIPAddress [ 40 ] ;
// Variables to hold the values that actually get set.
char intClient [ 40 ] ;
char intPort [ 6 ] ;
char duration [ 16 ] ;
// Intermediate variables.
struct UPNPUrls urls ;
struct IGDdatas data ;
2014-08-06 07:11:04 -07:00
struct UPNPDev * devlist = NULL ;
2013-12-17 09:03:49 -08:00
// Cached root descriptor URL.
2013-12-29 15:56:18 -08:00
std : : string rootDescURL ;
2014-11-17 15:29:49 -08:00
CFG_GET_VAL ( " network.upnprootdescurl " , rootDescURL ) ;
2013-12-29 15:56:18 -08:00
if ( ! rootDescURL . empty ( ) )
2015-01-22 12:36:24 -08:00
LOGMESSAGE ( " Net server: attempting to use cached root descriptor URL: %s " , rootDescURL . c_str ( ) ) ;
2013-12-17 09:03:49 -08:00
2014-08-06 07:11:04 -07:00
int ret = 0 ;
bool allocatedUrls = false ;
2013-12-17 06:21:49 -08:00
2014-08-06 07:11:04 -07:00
// Try a cached URL first
if ( ! rootDescURL . empty ( ) & & UPNP_GetIGDFromUrl ( rootDescURL . c_str ( ) , & urls , & data , internalIPAddress , sizeof ( internalIPAddress ) ) )
{
2015-01-22 12:36:24 -08:00
LOGMESSAGE ( " Net server: using cached IGD = %s " , urls . controlURL ) ;
2014-08-06 07:11:04 -07:00
ret = 1 ;
}
// No cached URL, or it did not respond. Try getting a valid UPnP device for 10 seconds.
else if ( ( devlist = upnpDiscover ( 10000 , 0 , 0 , 0 , 0 , 0 ) ) ! = NULL )
{
ret = UPNP_GetValidIGD ( devlist , & urls , & data , internalIPAddress , sizeof ( internalIPAddress ) ) ;
allocatedUrls = ret ! = 0 ; // urls is allocated on non-zero return values
}
else
2013-12-17 06:21:49 -08:00
{
2015-01-22 12:31:30 -08:00
LOGMESSAGE ( " Net server: upnpDiscover failed and no working cached URL. " ) ;
2013-12-17 09:03:49 -08:00
return NULL ;
2013-12-17 06:21:49 -08:00
}
2013-12-17 09:03:49 -08:00
2013-12-17 06:21:49 -08:00
switch ( ret )
{
2014-08-06 07:11:04 -07:00
case 0 :
2015-01-22 12:31:30 -08:00
LOGMESSAGE ( " Net server: No IGD found " ) ;
2014-08-06 07:11:04 -07:00
break ;
2013-12-17 06:21:49 -08:00
case 1 :
2015-01-22 12:36:24 -08:00
LOGMESSAGE ( " Net server: found valid IGD = %s " , urls . controlURL ) ;
2013-12-17 06:21:49 -08:00
break ;
case 2 :
2015-01-22 12:36:24 -08:00
LOGMESSAGE ( " Net server: found a valid, not connected IGD = %s, will try to continue anyway " , urls . controlURL ) ;
2013-12-17 06:21:49 -08:00
break ;
case 3 :
2015-01-22 12:36:24 -08:00
LOGMESSAGE ( " Net server: found a UPnP device unrecognized as IGD = %s, will try to continue anyway " , urls . controlURL ) ;
2013-12-17 06:21:49 -08:00
break ;
default :
debug_warn ( L " Unrecognized return value from UPNP_GetValidIGD " ) ;
}
2013-12-12 18:23:02 -08:00
2013-12-17 06:21:49 -08:00
// Try getting our external/internet facing IP. TODO: Display this on the game-setup page for conviniance.
ret = UPNP_GetExternalIPAddress ( urls . controlURL , data . first . servicetype , externalIPAddress ) ;
if ( ret ! = UPNPCOMMAND_SUCCESS )
2013-12-14 17:02:26 -08:00
{
2015-01-22 12:36:24 -08:00
LOGMESSAGE ( " Net server: GetExternalIPAddress failed with code %d (%s) " , ret , strupnperror ( ret ) ) ;
2013-12-17 09:03:49 -08:00
return NULL ;
2013-12-17 06:21:49 -08:00
}
2015-01-22 12:36:24 -08:00
LOGMESSAGE ( " Net server: ExternalIPAddress = %s " , externalIPAddress ) ;
2013-12-12 18:23:02 -08:00
2013-12-17 06:21:49 -08:00
// Try to setup port forwarding.
ret = UPNP_AddPortMapping ( urls . controlURL , data . first . servicetype , psPort , psPort ,
internalIPAddress , description , protocall , 0 , leaseDuration ) ;
if ( ret ! = UPNPCOMMAND_SUCCESS )
{
2015-01-22 12:36:24 -08:00
LOGMESSAGE ( " Net server: AddPortMapping(%s, %s, %s) failed with code %d (%s) " ,
2013-12-17 06:21:49 -08:00
psPort , psPort , internalIPAddress , ret , strupnperror ( ret ) ) ;
2013-12-17 09:03:49 -08:00
return NULL ;
2013-12-17 06:21:49 -08:00
}
2013-12-12 18:23:02 -08:00
2013-12-17 06:21:49 -08:00
// Check that the port was actually forwarded.
ret = UPNP_GetSpecificPortMappingEntry ( urls . controlURL ,
data . first . servicetype ,
psPort , protocall ,
2015-01-05 12:05:53 -08:00
# if defined(MINIUPNPC_API_VERSION) && MINIUPNPC_API_VERSION >= 10
2014-05-19 14:01:06 -07:00
NULL /*remoteHost*/ ,
# endif
2013-12-17 06:21:49 -08:00
intClient , intPort , NULL /*desc*/ ,
NULL /*enabled*/ , duration ) ;
2013-12-17 09:03:49 -08:00
2013-12-17 06:21:49 -08:00
if ( ret ! = UPNPCOMMAND_SUCCESS )
{
2015-01-22 12:36:24 -08:00
LOGMESSAGE ( " Net server: GetSpecificPortMappingEntry() failed with code %d (%s) " , ret , strupnperror ( ret ) ) ;
2013-12-17 09:03:49 -08:00
return NULL ;
2013-12-14 17:02:26 -08:00
}
2013-12-17 09:03:49 -08:00
2015-01-22 12:36:24 -08:00
LOGMESSAGE ( " Net server: External %s:%s %s is redirected to internal %s:%s (duration=%s) " ,
2013-12-17 06:21:49 -08:00
externalIPAddress , psPort , protocall , intClient , intPort , duration ) ;
2013-12-14 17:02:26 -08:00
2013-12-17 09:03:49 -08:00
// Cache root descriptor URL to try to avoid discovery next time.
2013-12-29 15:56:18 -08:00
g_ConfigDB . SetValueString ( CFG_USER , " network.upnprootdescurl " , urls . controlURL ) ;
2013-12-17 06:21:49 -08:00
g_ConfigDB . WriteFile ( CFG_USER ) ;
2015-01-22 12:36:24 -08:00
LOGMESSAGE ( " Net server: cached UPnP root descriptor URL as %s " , urls . controlURL ) ;
2013-12-12 18:23:02 -08:00
2013-12-17 06:21:49 -08:00
// Make sure everything is properly freed.
2014-08-06 07:11:04 -07:00
if ( allocatedUrls )
FreeUPNPUrls ( & urls ) ;
2013-12-17 06:21:49 -08:00
freeUPNPDevlist ( devlist ) ;
2013-12-17 09:03:49 -08:00
return NULL ;
2009-04-11 10:00:39 -07:00
}
2013-12-18 08:08:56 -08:00
# endif // CONFIG2_MINIUPNPC
2009-04-11 10:00:39 -07:00
2010-10-31 15:00:28 -07:00
bool CNetServerWorker : : SendMessage ( ENetPeer * peer , const CNetMessage * message )
2009-04-11 10:00:39 -07:00
{
2011-04-30 06:01:45 -07:00
ENSURE ( m_Host ) ;
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
CNetServerSession * session = static_cast < CNetServerSession * > ( peer - > data ) ;
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
return CNetHost : : SendMessage ( message , peer , DebugName ( session ) . c_str ( ) ) ;
2009-04-11 10:00:39 -07:00
}
2010-10-31 15:00:28 -07:00
bool CNetServerWorker : : Broadcast ( const CNetMessage * message )
2009-04-11 10:00:39 -07:00
{
2011-04-30 06:01:45 -07:00
ENSURE ( m_Host ) ;
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
bool ok = true ;
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
// Send to all sessions that are active and has finished authentication
for ( size_t i = 0 ; i < m_Sessions . size ( ) ; + + i )
{
if ( m_Sessions [ i ] - > GetCurrState ( ) = = NSS_PREGAME | | m_Sessions [ i ] - > GetCurrState ( ) = = NSS_INGAME )
{
if ( ! m_Sessions [ i ] - > SendMessage ( message ) )
ok = false ;
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
// TODO: this does lots of repeated message serialisation if we have lots
// of remote peers; could do it more efficiently if that's a real problem
}
}
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
return ok ;
2009-04-11 10:00:39 -07:00
}
2010-10-31 15:00:28 -07:00
void * CNetServerWorker : : RunThread ( void * data )
2009-04-11 10:00:39 -07:00
{
2010-10-31 15:00:28 -07:00
debug_SetThreadName ( " NetServer " ) ;
2009-04-11 10:00:39 -07:00
2010-10-31 15:00:28 -07:00
static_cast < CNetServerWorker * > ( data ) - > Run ( ) ;
return NULL ;
}
void CNetServerWorker : : Run ( )
{
2014-03-28 13:26:32 -07:00
// The script runtime uses the profiler and therefore the thread must be registered before the runtime is created
g_Profiler2 . RegisterCurrentThread ( " Net server " ) ;
2010-10-31 15:00:28 -07:00
// To avoid the need for JS_SetContextThread, we create and use and destroy
// the script interface entirely within this network thread
2015-01-24 06:46:52 -08:00
m_ScriptInterface = new ScriptInterface ( " Engine " , " Net server " , ScriptInterface : : CreateRuntime ( g_ScriptRuntime ) ) ;
m_GameAttributes . set ( m_ScriptInterface - > GetJSRuntime ( ) , JS : : UndefinedValue ( ) ) ;
2010-10-31 15:00:28 -07:00
while ( true )
2010-06-30 14:41:04 -07:00
{
2010-10-31 15:00:28 -07:00
if ( ! RunStep ( ) )
break ;
2009-04-11 10:00:39 -07:00
2010-10-31 15:00:28 -07:00
// Implement autostart mode
if ( m_State = = SERVER_STATE_PREGAME & & ( int ) m_PlayerAssignments . size ( ) = = m_AutostartPlayers )
StartGame ( ) ;
2010-07-06 12:54:17 -07:00
2010-10-31 15:00:28 -07:00
// Update profiler stats
m_Stats - > LatchHostState ( m_Host ) ;
}
2015-01-24 06:46:52 -08:00
2011-10-27 09:46:48 -07:00
// Clear roots before deleting their context
2015-01-24 06:46:52 -08:00
m_GameAttributes . clear ( ) ;
2011-10-27 09:46:48 -07:00
m_SavedCommands . clear ( ) ;
2009-04-11 10:00:39 -07:00
2010-10-31 15:00:28 -07:00
SAFE_DELETE ( m_ScriptInterface ) ;
}
2009-04-11 10:00:39 -07:00
2010-10-31 15:00:28 -07:00
bool CNetServerWorker : : RunStep ( )
{
// Check for messages from the game thread.
// (Do as little work as possible while the mutex is held open,
// to avoid performance problems and deadlocks.)
2014-08-02 15:21:50 -07:00
2014-11-13 03:19:28 -08:00
m_ScriptInterface - > GetRuntime ( ) - > MaybeIncrementalGC ( 0.5f ) ;
2014-09-22 13:13:04 -07:00
2014-08-02 15:21:50 -07:00
JSContext * cx = m_ScriptInterface - > GetContext ( ) ;
JSAutoRequest rq ( cx ) ;
2009-04-11 10:00:39 -07:00
2010-10-31 15:00:28 -07:00
std : : vector < std : : pair < int , CStr > > newAssignPlayer ;
std : : vector < bool > newStartGame ;
2014-04-26 11:34:34 -07:00
std : : vector < std : : pair < CStr , int > > newPlayerReady ;
std : : vector < bool > newPlayerResetReady ;
2010-10-31 15:00:28 -07:00
std : : vector < std : : string > newGameAttributes ;
std : : vector < u32 > newTurnLength ;
2010-06-30 14:41:04 -07:00
2010-10-31 15:00:28 -07:00
{
CScopeLock lock ( m_WorkerMutex ) ;
2010-07-06 12:54:17 -07:00
2010-10-31 15:00:28 -07:00
if ( m_Shutdown )
return false ;
2009-04-11 10:00:39 -07:00
2010-10-31 15:00:28 -07:00
newStartGame . swap ( m_StartGameQueue ) ;
2014-04-26 11:34:34 -07:00
newPlayerReady . swap ( m_PlayerReadyQueue ) ;
newPlayerResetReady . swap ( m_PlayerResetReadyQueue ) ;
2010-10-31 15:00:28 -07:00
newAssignPlayer . swap ( m_AssignPlayerQueue ) ;
newGameAttributes . swap ( m_GameAttributesQueue ) ;
newTurnLength . swap ( m_TurnLengthQueue ) ;
}
2009-04-11 10:00:39 -07:00
2010-10-31 15:00:28 -07:00
for ( size_t i = 0 ; i < newAssignPlayer . size ( ) ; + + i )
AssignPlayer ( newAssignPlayer [ i ] . first , newAssignPlayer [ i ] . second ) ;
2009-04-11 10:00:39 -07:00
2014-04-26 11:34:34 -07:00
for ( size_t i = 0 ; i < newPlayerReady . size ( ) ; + + i )
SetPlayerReady ( newPlayerReady [ i ] . first , newPlayerReady [ i ] . second ) ;
if ( ! newPlayerResetReady . empty ( ) )
ClearAllPlayerReady ( ) ;
2010-10-31 15:00:28 -07:00
if ( ! newGameAttributes . empty ( ) )
2014-08-02 15:21:50 -07:00
{
JS : : RootedValue gameAttributesVal ( cx ) ;
GetScriptInterface ( ) . ParseJSON ( newGameAttributes . back ( ) , & gameAttributesVal ) ;
UpdateGameAttributes ( & gameAttributesVal ) ;
}
2010-07-02 14:28:48 -07:00
2010-10-31 15:00:28 -07:00
if ( ! newTurnLength . empty ( ) )
SetTurnLength ( newTurnLength . back ( ) ) ;
2009-04-11 10:00:39 -07:00
2010-10-31 15:00:28 -07:00
// Do StartGame last, so we have the most up-to-date game attributes when we start
if ( ! newStartGame . empty ( ) )
StartGame ( ) ;
2011-10-27 09:46:48 -07:00
// Perform file transfers
for ( size_t i = 0 ; i < m_Sessions . size ( ) ; + + i )
m_Sessions [ i ] - > GetFileTransferer ( ) . Poll ( ) ;
2010-10-31 15:00:28 -07:00
// Process network events:
ENetEvent event ;
int status = enet_host_service ( m_Host , & event , HOST_SERVICE_TIMEOUT ) ;
if ( status < 0 )
{
2015-01-22 12:31:30 -08:00
LOGERROR ( " CNetServerWorker: enet_host_service failed (%d) " , status ) ;
2010-10-31 15:00:28 -07:00
// TODO: notify game that the server has shut down
return false ;
}
2009-04-11 10:00:39 -07:00
2010-10-31 15:00:28 -07:00
if ( status = = 0 )
{
// Reached timeout with no events - try again
return true ;
}
// Process the event:
switch ( event . type )
{
case ENET_EVENT_TYPE_CONNECT :
{
// Report the client address
char hostname [ 256 ] = " (error) " ;
enet_address_get_host_ip ( & event . peer - > address , hostname , ARRAY_SIZE ( hostname ) ) ;
2015-01-22 12:36:24 -08:00
LOGMESSAGE ( " Net server: Received connection from %s:%u " , hostname , ( unsigned int ) event . peer - > address . port ) ;
2010-10-31 15:00:28 -07:00
// Set up a session object for this peer
CNetServerSession * session = new CNetServerSession ( * this , event . peer ) ;
m_Sessions . push_back ( session ) ;
SetupSession ( session ) ;
2011-04-30 06:01:45 -07:00
ENSURE ( event . peer - > data = = NULL ) ;
2010-10-31 15:00:28 -07:00
event . peer - > data = session ;
HandleConnect ( session ) ;
break ;
}
case ENET_EVENT_TYPE_DISCONNECT :
{
// If there is an active session with this peer, then reset and delete it
CNetServerSession * session = static_cast < CNetServerSession * > ( event . peer - > data ) ;
if ( session )
2009-04-11 10:00:39 -07:00
{
2015-01-22 12:36:24 -08:00
LOGMESSAGE ( " Net server: Disconnected %s " , DebugName ( session ) . c_str ( ) ) ;
2010-10-31 15:00:28 -07:00
// Remove the session first, so we won't send player-update messages to it
// when updating the FSM
m_Sessions . erase ( remove ( m_Sessions . begin ( ) , m_Sessions . end ( ) , session ) , m_Sessions . end ( ) ) ;
session - > Update ( ( uint ) NMT_CONNECTION_LOST , NULL ) ;
delete session ;
event . peer - > data = NULL ;
}
2009-04-11 10:00:39 -07:00
2010-10-31 15:00:28 -07:00
break ;
}
case ENET_EVENT_TYPE_RECEIVE :
{
// If there is an active session with this peer, then process the message
CNetServerSession * session = static_cast < CNetServerSession * > ( event . peer - > data ) ;
if ( session )
{
// Create message from raw data
CNetMessage * msg = CNetMessageFactory : : CreateMessage ( event . packet - > data , event . packet - > dataLength , GetScriptInterface ( ) ) ;
if ( msg )
2010-06-30 14:41:04 -07:00
{
2015-01-22 12:36:24 -08:00
LOGMESSAGE ( " Net server: Received message %s of size %lu from %s " , msg - > ToString ( ) . c_str ( ) , ( unsigned long ) msg - > GetSerializedLength ( ) , DebugName ( session ) . c_str ( ) ) ;
2009-04-11 10:00:39 -07:00
2010-10-31 15:00:28 -07:00
HandleMessageReceive ( msg , session ) ;
2010-06-30 14:41:04 -07:00
2010-10-31 15:00:28 -07:00
delete msg ;
2010-06-30 14:41:04 -07:00
}
2010-10-31 15:00:28 -07:00
}
2010-06-30 14:41:04 -07:00
2010-10-31 15:00:28 -07:00
// Done using the packet
enet_packet_destroy ( event . packet ) ;
2009-04-11 10:00:39 -07:00
2010-10-31 15:00:28 -07:00
break ;
}
2010-12-11 04:35:50 -08:00
case ENET_EVENT_TYPE_NONE :
break ;
2009-04-11 10:00:39 -07:00
}
2010-10-31 15:00:28 -07:00
return true ;
2010-06-30 14:41:04 -07:00
}
2009-04-11 10:00:39 -07:00
2010-10-31 15:00:28 -07:00
void CNetServerWorker : : HandleMessageReceive ( const CNetMessage * message , CNetServerSession * session )
2010-06-30 14:41:04 -07:00
{
2011-10-27 09:46:48 -07:00
// Handle non-FSM messages first
Status status = session - > GetFileTransferer ( ) . HandleMessageReceive ( message ) ;
if ( status ! = INFO : : SKIPPED )
return ;
if ( message - > GetType ( ) = = NMT_FILE_TRANSFER_REQUEST )
{
CFileTransferRequestMessage * reqMessage = ( CFileTransferRequestMessage * ) message ;
// Rejoining client got our JoinSyncStart after we received the state from
// another client, and has now requested that we forward it to them
ENSURE ( ! m_JoinSyncFile . empty ( ) ) ;
session - > GetFileTransferer ( ) . StartResponse ( reqMessage - > m_RequestID , m_JoinSyncFile ) ;
return ;
}
2010-06-30 14:41:04 -07:00
// Update FSM
bool ok = session - > Update ( message - > GetType ( ) , ( void * ) message ) ;
if ( ! ok )
2015-01-22 12:31:30 -08:00
LOGERROR ( " Net server: Error running FSM update (type=%d state=%d) " , ( int ) message - > GetType ( ) , ( int ) session - > GetCurrState ( ) ) ;
2010-06-30 14:41:04 -07:00
}
2009-04-11 10:00:39 -07:00
2010-10-31 15:00:28 -07:00
void CNetServerWorker : : SetupSession ( CNetServerSession * session )
2010-06-30 14:41:04 -07:00
{
void * context = session ;
// Set up transitions for session
2010-07-06 12:54:17 -07:00
session - > AddTransition ( NSS_UNCONNECTED , ( uint ) NMT_CONNECTION_LOST , NSS_UNCONNECTED ) ;
session - > AddTransition ( NSS_HANDSHAKE , ( uint ) NMT_CONNECTION_LOST , NSS_UNCONNECTED ) ;
2010-06-30 14:41:04 -07:00
session - > AddTransition ( NSS_HANDSHAKE , ( uint ) NMT_CLIENT_HANDSHAKE , NSS_AUTHENTICATE , ( void * ) & OnClientHandshake , context ) ;
2010-07-02 14:28:48 -07:00
2010-07-06 12:54:17 -07:00
session - > AddTransition ( NSS_AUTHENTICATE , ( uint ) NMT_CONNECTION_LOST , NSS_UNCONNECTED ) ;
2010-06-30 14:41:04 -07:00
session - > AddTransition ( NSS_AUTHENTICATE , ( uint ) NMT_AUTHENTICATE , NSS_PREGAME , ( void * ) & OnAuthenticate , context ) ;
2010-07-02 14:28:48 -07:00
session - > AddTransition ( NSS_PREGAME , ( uint ) NMT_CONNECTION_LOST , NSS_UNCONNECTED , ( void * ) & OnDisconnect , context ) ;
2010-06-30 14:41:04 -07:00
session - > AddTransition ( NSS_PREGAME , ( uint ) NMT_CHAT , NSS_PREGAME , ( void * ) & OnChat , context ) ;
2014-04-26 11:34:34 -07:00
session - > AddTransition ( NSS_PREGAME , ( uint ) NMT_READY , NSS_PREGAME , ( void * ) & OnReady , context ) ;
2010-06-30 14:41:04 -07:00
session - > AddTransition ( NSS_PREGAME , ( uint ) NMT_LOADED_GAME , NSS_INGAME , ( void * ) & OnLoadedGame , context ) ;
2010-07-02 14:28:48 -07:00
2014-06-19 20:22:40 -07:00
session - > AddTransition ( NSS_JOIN_SYNCING , ( uint ) NMT_CONNECTION_LOST , NSS_UNCONNECTED , ( void * ) & OnDisconnect , context ) ;
2011-10-27 09:46:48 -07:00
session - > AddTransition ( NSS_JOIN_SYNCING , ( uint ) NMT_LOADED_GAME , NSS_INGAME , ( void * ) & OnJoinSyncingLoadedGame , context ) ;
2010-07-02 14:28:48 -07:00
session - > AddTransition ( NSS_INGAME , ( uint ) NMT_CONNECTION_LOST , NSS_UNCONNECTED , ( void * ) & OnDisconnect , context ) ;
2010-06-30 14:41:04 -07:00
session - > AddTransition ( NSS_INGAME , ( uint ) NMT_CHAT , NSS_INGAME , ( void * ) & OnChat , context ) ;
session - > AddTransition ( NSS_INGAME , ( uint ) NMT_SIMULATION_COMMAND , NSS_INGAME , ( void * ) & OnInGame , context ) ;
session - > AddTransition ( NSS_INGAME , ( uint ) NMT_SYNC_CHECK , NSS_INGAME , ( void * ) & OnInGame , context ) ;
session - > AddTransition ( NSS_INGAME , ( uint ) NMT_END_COMMAND_BATCH , NSS_INGAME , ( void * ) & OnInGame , context ) ;
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
// Set first state
session - > SetFirstState ( NSS_HANDSHAKE ) ;
2009-04-11 10:00:39 -07:00
}
2010-10-31 15:00:28 -07:00
bool CNetServerWorker : : HandleConnect ( CNetServerSession * session )
2009-04-11 10:00:39 -07:00
{
2010-06-30 14:41:04 -07:00
CSrvHandshakeMessage handshake ;
handshake . m_Magic = PS_PROTOCOL_MAGIC ;
handshake . m_ProtocolVersion = PS_PROTOCOL_VERSION ;
handshake . m_SoftwareVersion = PS_PROTOCOL_VERSION ;
return session - > SendMessage ( & handshake ) ;
}
2009-04-11 10:00:39 -07:00
2010-10-31 15:00:28 -07:00
void CNetServerWorker : : OnUserJoin ( CNetServerSession * session )
2009-04-11 10:00:39 -07:00
{
2010-06-30 14:41:04 -07:00
AddPlayer ( session - > GetGUID ( ) , session - > GetUserName ( ) ) ;
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
CGameSetupMessage gameSetupMessage ( GetScriptInterface ( ) ) ;
2015-01-24 06:46:52 -08:00
gameSetupMessage . m_Data = m_GameAttributes . get ( ) ;
2010-06-30 14:41:04 -07:00
session - > SendMessage ( & gameSetupMessage ) ;
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
CPlayerAssignmentMessage assignMessage ;
ConstructPlayerAssignmentMessage ( assignMessage ) ;
session - > SendMessage ( & assignMessage ) ;
2009-04-11 10:00:39 -07:00
}
2010-10-31 15:00:28 -07:00
void CNetServerWorker : : OnUserLeave ( CNetServerSession * session )
2010-07-02 14:28:48 -07:00
{
RemovePlayer ( session - > GetGUID ( ) ) ;
2010-11-02 17:21:52 -07:00
2014-06-19 20:22:40 -07:00
if ( m_ServerTurnManager & & session - > GetCurrState ( ) ! = NSS_JOIN_SYNCING )
2010-11-02 17:21:52 -07:00
m_ServerTurnManager - > UninitialiseClient ( session - > GetHostID ( ) ) ; // TODO: only for non-observers
// TODO: ought to switch the player controlled by that client
// back to AI control, or something?
2010-07-02 14:28:48 -07:00
}
2010-10-31 15:00:28 -07:00
void CNetServerWorker : : AddPlayer ( const CStr & guid , const CStrW & name )
2009-04-11 10:00:39 -07:00
{
2014-03-29 08:09:42 -07:00
// Find all player IDs in active use; we mustn't give them to a second player (excluding the unassigned ID: -1)
2010-06-30 14:41:04 -07:00
std : : set < i32 > usedIDs ;
for ( PlayerAssignmentMap : : iterator it = m_PlayerAssignments . begin ( ) ; it ! = m_PlayerAssignments . end ( ) ; + + it )
2014-03-29 08:09:42 -07:00
if ( it - > second . m_Enabled & & it - > second . m_PlayerID ! = - 1 )
2011-10-27 09:46:48 -07:00
usedIDs . insert ( it - > second . m_PlayerID ) ;
// If the player is rejoining after disconnecting, try to give them
// back their old player ID
i32 playerID = - 1 ;
// Try to match GUID first
for ( PlayerAssignmentMap : : iterator it = m_PlayerAssignments . begin ( ) ; it ! = m_PlayerAssignments . end ( ) ; + + it )
{
if ( ! it - > second . m_Enabled & & it - > first = = guid & & usedIDs . find ( it - > second . m_PlayerID ) = = usedIDs . end ( ) )
{
playerID = it - > second . m_PlayerID ;
m_PlayerAssignments . erase ( it ) ; // delete the old mapping, since we've got a new one now
2014-05-19 09:02:42 -07:00
goto found ;
2011-10-27 09:46:48 -07:00
}
}
// Try to match username next
2014-05-19 09:02:42 -07:00
for ( PlayerAssignmentMap : : iterator it = m_PlayerAssignments . begin ( ) ; it ! = m_PlayerAssignments . end ( ) ; + + it )
2011-10-27 09:46:48 -07:00
{
2014-05-19 09:02:42 -07:00
if ( ! it - > second . m_Enabled & & it - > second . m_Name = = name & & usedIDs . find ( it - > second . m_PlayerID ) = = usedIDs . end ( ) )
2011-10-27 09:46:48 -07:00
{
2014-05-19 09:02:42 -07:00
playerID = it - > second . m_PlayerID ;
m_PlayerAssignments . erase ( it ) ; // delete the old mapping, since we've got a new one now
goto found ;
2011-10-27 09:46:48 -07:00
}
}
2009-04-11 10:00:39 -07:00
2014-05-19 09:02:42 -07:00
// Otherwise leave the player ID as -1 (observer) and let gamesetup change it as needed.
2009-04-11 10:00:39 -07:00
2014-05-19 09:02:42 -07:00
found :
2010-06-30 14:41:04 -07:00
PlayerAssignment assignment ;
2011-10-27 09:46:48 -07:00
assignment . m_Enabled = true ;
2010-06-30 14:41:04 -07:00
assignment . m_Name = name ;
assignment . m_PlayerID = playerID ;
2014-04-26 11:34:34 -07:00
assignment . m_Status = 0 ;
2010-06-30 14:41:04 -07:00
m_PlayerAssignments [ guid ] = assignment ;
// Send the new assignments to all currently active players
// (which does not include the one that's just joining)
SendPlayerAssignments ( ) ;
2009-04-11 10:00:39 -07:00
}
2010-10-31 15:00:28 -07:00
void CNetServerWorker : : RemovePlayer ( const CStr & guid )
2009-04-11 10:00:39 -07:00
{
2011-10-27 09:46:48 -07:00
m_PlayerAssignments [ guid ] . m_Enabled = false ;
2009-04-11 10:00:39 -07:00
2010-07-02 14:28:48 -07:00
SendPlayerAssignments ( ) ;
2009-04-11 10:00:39 -07:00
}
2014-04-26 11:34:34 -07:00
void CNetServerWorker : : SetPlayerReady ( const CStr & guid , const int ready )
{
m_PlayerAssignments [ guid ] . m_Status = ready ;
SendPlayerAssignments ( ) ;
}
void CNetServerWorker : : ClearAllPlayerReady ( )
{
for ( PlayerAssignmentMap : : iterator it = m_PlayerAssignments . begin ( ) ; it ! = m_PlayerAssignments . end ( ) ; + + it )
it - > second . m_Status = 0 ;
SendPlayerAssignments ( ) ;
}
2010-10-31 15:00:28 -07:00
void CNetServerWorker : : AssignPlayer ( int playerID , const CStr & guid )
2009-04-11 10:00:39 -07:00
{
2010-06-30 14:41:04 -07:00
// Remove anyone who's already assigned to this player
for ( PlayerAssignmentMap : : iterator it = m_PlayerAssignments . begin ( ) ; it ! = m_PlayerAssignments . end ( ) ; + + it )
2009-04-11 10:00:39 -07:00
{
2010-06-30 14:41:04 -07:00
if ( it - > second . m_PlayerID = = playerID )
it - > second . m_PlayerID = - 1 ;
2009-04-11 10:00:39 -07:00
}
2010-06-30 14:41:04 -07:00
// Update this host's assignment if it exists
if ( m_PlayerAssignments . find ( guid ) ! = m_PlayerAssignments . end ( ) )
m_PlayerAssignments [ guid ] . m_PlayerID = playerID ;
SendPlayerAssignments ( ) ;
2009-04-11 10:00:39 -07:00
}
2010-10-31 15:00:28 -07:00
void CNetServerWorker : : ConstructPlayerAssignmentMessage ( CPlayerAssignmentMessage & message )
2009-04-11 10:00:39 -07:00
{
2010-06-30 14:41:04 -07:00
for ( PlayerAssignmentMap : : iterator it = m_PlayerAssignments . begin ( ) ; it ! = m_PlayerAssignments . end ( ) ; + + it )
2009-04-11 10:00:39 -07:00
{
2011-10-27 09:46:48 -07:00
if ( ! it - > second . m_Enabled )
continue ;
2010-06-30 14:41:04 -07:00
CPlayerAssignmentMessage : : S_m_Hosts h ;
h . m_GUID = it - > first ;
h . m_Name = it - > second . m_Name ;
h . m_PlayerID = it - > second . m_PlayerID ;
2014-04-26 11:34:34 -07:00
h . m_Status = it - > second . m_Status ;
2010-06-30 14:41:04 -07:00
message . m_Hosts . push_back ( h ) ;
2009-04-11 10:00:39 -07:00
}
}
2010-10-31 15:00:28 -07:00
void CNetServerWorker : : SendPlayerAssignments ( )
2009-04-11 10:00:39 -07:00
{
2010-06-30 14:41:04 -07:00
CPlayerAssignmentMessage message ;
ConstructPlayerAssignmentMessage ( message ) ;
Broadcast ( & message ) ;
2009-04-11 10:00:39 -07:00
}
2010-10-31 15:00:28 -07:00
ScriptInterface & CNetServerWorker : : GetScriptInterface ( )
2009-04-11 10:00:39 -07:00
{
2010-06-30 14:41:04 -07:00
return * m_ScriptInterface ;
2009-04-11 10:00:39 -07:00
}
2010-10-31 15:00:28 -07:00
void CNetServerWorker : : SetTurnLength ( u32 msecs )
2010-08-13 09:42:53 -07:00
{
if ( m_ServerTurnManager )
m_ServerTurnManager - > SetTurnLength ( msecs ) ;
}
2010-10-31 15:00:28 -07:00
bool CNetServerWorker : : OnClientHandshake ( void * context , CFsmEvent * event )
2009-04-11 10:00:39 -07:00
{
2011-04-30 06:01:45 -07:00
ENSURE ( event - > GetType ( ) = = ( uint ) NMT_CLIENT_HANDSHAKE ) ;
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
CNetServerSession * session = ( CNetServerSession * ) context ;
2010-10-31 15:00:28 -07:00
CNetServerWorker & server = session - > GetServer ( ) ;
2009-04-11 10:00:39 -07:00
2010-07-06 12:54:17 -07:00
CCliHandshakeMessage * message = ( CCliHandshakeMessage * ) event - > GetParamRef ( ) ;
if ( message - > m_ProtocolVersion ! = PS_PROTOCOL_VERSION )
2010-06-30 14:41:04 -07:00
{
2010-07-06 12:54:17 -07:00
session - > Disconnect ( NDR_INCORRECT_PROTOCOL_VERSION ) ;
return false ;
2009-04-11 10:00:39 -07:00
}
2010-07-06 12:54:17 -07:00
CSrvHandshakeResponseMessage handshakeResponse ;
handshakeResponse . m_UseProtocolVersion = PS_PROTOCOL_VERSION ;
handshakeResponse . m_Message = server . m_WelcomeMessage ;
handshakeResponse . m_Flags = 0 ;
session - > SendMessage ( & handshakeResponse ) ;
2010-06-30 14:41:04 -07:00
return true ;
2009-04-11 10:00:39 -07:00
}
2010-10-31 15:00:28 -07:00
bool CNetServerWorker : : OnAuthenticate ( void * context , CFsmEvent * event )
2009-04-11 10:00:39 -07:00
{
2011-04-30 06:01:45 -07:00
ENSURE ( event - > GetType ( ) = = ( uint ) NMT_AUTHENTICATE ) ;
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
CNetServerSession * session = ( CNetServerSession * ) context ;
2010-10-31 15:00:28 -07:00
CNetServerWorker & server = session - > GetServer ( ) ;
2009-04-11 10:00:39 -07:00
2011-10-27 09:46:48 -07:00
CAuthenticateMessage * message = ( CAuthenticateMessage * ) event - > GetParamRef ( ) ;
CStrW username = server . DeduplicatePlayerName ( SanitisePlayerName ( message - > m_Name ) ) ;
bool isRejoining = false ;
2010-07-06 12:54:17 -07:00
if ( server . m_State ! = SERVER_STATE_PREGAME )
{
2011-10-27 09:46:48 -07:00
// isRejoining = true; // uncomment this to test rejoining even if the player wasn't connected previously
2010-07-06 12:54:17 -07:00
2011-10-27 09:46:48 -07:00
// Search for an old disconnected player of the same name
// (TODO: if GUIDs were stable, we should use them instead)
for ( PlayerAssignmentMap : : iterator it = server . m_PlayerAssignments . begin ( ) ; it ! = server . m_PlayerAssignments . end ( ) ; + + it )
{
if ( ! it - > second . m_Enabled & & it - > second . m_Name = = username )
{
isRejoining = true ;
break ;
}
}
// Players who weren't already in the game are not allowed to join now that it's started
if ( ! isRejoining )
{
2015-01-22 12:37:38 -08:00
LOGMESSAGE ( " Refused connection after game start from not-previously-known user \" %s \" " , utf8_from_wstring ( username ) ) ;
2011-10-27 09:46:48 -07:00
session - > Disconnect ( NDR_SERVER_ALREADY_IN_GAME ) ;
return true ;
}
}
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
// TODO: check server password etc?
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
u32 newHostID = server . m_NextHostID + + ;
2009-04-11 10:00:39 -07:00
2010-07-02 14:28:48 -07:00
session - > SetUserName ( username ) ;
2010-06-30 14:41:04 -07:00
session - > SetGUID ( message - > m_GUID ) ;
session - > SetHostID ( newHostID ) ;
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
CAuthenticateResultMessage authenticateResult ;
2011-10-29 07:53:13 -07:00
authenticateResult . m_Code = isRejoining ? ARC_OK_REJOINING : ARC_OK ;
2010-06-30 14:41:04 -07:00
authenticateResult . m_HostID = newHostID ;
authenticateResult . m_Message = L " Logged in " ;
session - > SendMessage ( & authenticateResult ) ;
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
server . OnUserJoin ( session ) ;
2009-04-11 10:00:39 -07:00
2011-10-27 09:46:48 -07:00
if ( isRejoining )
{
// Request a copy of the current game state from an existing player,
// so we can send it on to the new player
// Assume session 0 is most likely the local player, so they're
// the most efficient client to request a copy from
CNetServerSession * sourceSession = server . m_Sessions . at ( 0 ) ;
sourceSession - > GetFileTransferer ( ) . StartTask (
shared_ptr < CNetFileReceiveTask > ( new CNetFileReceiveTask_ServerRejoin ( server , newHostID ) )
) ;
session - > SetNextState ( NSS_JOIN_SYNCING ) ;
}
2010-06-30 14:41:04 -07:00
return true ;
2009-04-11 10:00:39 -07:00
}
2010-10-31 15:00:28 -07:00
bool CNetServerWorker : : OnInGame ( void * context , CFsmEvent * event )
2009-04-11 10:00:39 -07:00
{
2010-06-30 14:41:04 -07:00
// TODO: should split each of these cases into a separate method
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
CNetServerSession * session = ( CNetServerSession * ) context ;
2010-10-31 15:00:28 -07:00
CNetServerWorker & server = session - > GetServer ( ) ;
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
CNetMessage * message = ( CNetMessage * ) event - > GetParamRef ( ) ;
if ( message - > GetType ( ) = = ( uint ) NMT_SIMULATION_COMMAND )
{
CSimulationMessage * simMessage = static_cast < CSimulationMessage * > ( message ) ;
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
// Send it back to all clients immediately
server . Broadcast ( simMessage ) ;
2009-04-11 10:00:39 -07:00
2011-10-27 09:46:48 -07:00
// Save all the received commands
if ( server . m_SavedCommands . size ( ) < simMessage - > m_Turn + 1 )
server . m_SavedCommands . resize ( simMessage - > m_Turn + 1 ) ;
server . m_SavedCommands [ simMessage - > m_Turn ] . push_back ( * simMessage ) ;
2010-06-30 14:41:04 -07:00
// TODO: we should do some validation of ownership (clients can't send commands on behalf of opposing players)
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
// TODO: we shouldn't send the message back to the client that first sent it
}
else if ( message - > GetType ( ) = = ( uint ) NMT_SYNC_CHECK )
{
CSyncCheckMessage * syncMessage = static_cast < CSyncCheckMessage * > ( message ) ;
server . m_ServerTurnManager - > NotifyFinishedClientUpdate ( session - > GetHostID ( ) , syncMessage - > m_Turn , syncMessage - > m_Hash ) ;
}
else if ( message - > GetType ( ) = = ( uint ) NMT_END_COMMAND_BATCH )
{
CEndCommandBatchMessage * endMessage = static_cast < CEndCommandBatchMessage * > ( message ) ;
server . m_ServerTurnManager - > NotifyFinishedClientCommands ( session - > GetHostID ( ) , endMessage - > m_Turn ) ;
}
return true ;
2009-04-11 10:00:39 -07:00
}
2010-10-31 15:00:28 -07:00
bool CNetServerWorker : : OnChat ( void * context , CFsmEvent * event )
2009-04-11 10:00:39 -07:00
{
2011-04-30 06:01:45 -07:00
ENSURE ( event - > GetType ( ) = = ( uint ) NMT_CHAT ) ;
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
CNetServerSession * session = ( CNetServerSession * ) context ;
2010-10-31 15:00:28 -07:00
CNetServerWorker & server = session - > GetServer ( ) ;
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
CChatMessage * message = ( CChatMessage * ) event - > GetParamRef ( ) ;
2010-07-02 14:28:48 -07:00
2010-08-14 12:45:22 -07:00
message - > m_GUID = session - > GetGUID ( ) ;
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
server . Broadcast ( message ) ;
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
return true ;
2009-04-11 10:00:39 -07:00
}
2014-04-26 11:34:34 -07:00
bool CNetServerWorker : : OnReady ( void * context , CFsmEvent * event )
{
ENSURE ( event - > GetType ( ) = = ( uint ) NMT_READY ) ;
CNetServerSession * session = ( CNetServerSession * ) context ;
CNetServerWorker & server = session - > GetServer ( ) ;
CReadyMessage * message = ( CReadyMessage * ) event - > GetParamRef ( ) ;
message - > m_GUID = session - > GetGUID ( ) ;
server . Broadcast ( message ) ;
return true ;
}
2010-10-31 15:00:28 -07:00
bool CNetServerWorker : : OnLoadedGame ( void * context , CFsmEvent * event )
2009-04-11 10:00:39 -07:00
{
2011-04-30 06:01:45 -07:00
ENSURE ( event - > GetType ( ) = = ( uint ) NMT_LOADED_GAME ) ;
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
CNetServerSession * session = ( CNetServerSession * ) context ;
2010-10-31 15:00:28 -07:00
CNetServerWorker & server = session - > GetServer ( ) ;
2009-04-11 10:00:39 -07:00
2011-10-27 09:46:48 -07:00
// We're in the loading state, so wait until every player has loaded before
// starting the game
ENSURE ( server . m_State = = SERVER_STATE_LOADING ) ;
2010-06-30 14:41:04 -07:00
server . CheckGameLoadStatus ( session ) ;
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
return true ;
2009-04-11 10:00:39 -07:00
}
2011-10-27 09:46:48 -07:00
bool CNetServerWorker : : OnJoinSyncingLoadedGame ( void * context , CFsmEvent * event )
{
// A client rejoining an in-progress game has now finished loading the
// map and deserialized the initial state.
// The simulation may have progressed since then, so send any subsequent
// commands to them and set them as an active player so they can participate
// in all future turns.
//
// (TODO: if it takes a long time for them to receive and execute all these
// commands, the other players will get frozen for that time and may be unhappy;
// we could try repeating this process a few times until the client converges
// on the up-to-date state, before setting them as active.)
ENSURE ( event - > GetType ( ) = = ( uint ) NMT_LOADED_GAME ) ;
CNetServerSession * session = ( CNetServerSession * ) context ;
CNetServerWorker & server = session - > GetServer ( ) ;
CLoadedGameMessage * message = ( CLoadedGameMessage * ) event - > GetParamRef ( ) ;
u32 turn = message - > m_CurrentTurn ;
u32 readyTurn = server . m_ServerTurnManager - > GetReadyTurn ( ) ;
// Send them all commands received since their saved state,
// and turn-ended messages for any turns that have already been processed
for ( size_t i = turn + 1 ; i < std : : max ( readyTurn + 1 , ( u32 ) server . m_SavedCommands . size ( ) ) ; + + i )
{
if ( i < server . m_SavedCommands . size ( ) )
for ( size_t j = 0 ; j < server . m_SavedCommands [ i ] . size ( ) ; + + j )
session - > SendMessage ( & server . m_SavedCommands [ i ] [ j ] ) ;
if ( i < = readyTurn )
{
CEndCommandBatchMessage endMessage ;
endMessage . m_Turn = i ;
endMessage . m_TurnLength = server . m_ServerTurnManager - > GetSavedTurnLength ( i ) ;
session - > SendMessage ( & endMessage ) ;
}
}
// Tell the turn manager to expect commands from this new client
server . m_ServerTurnManager - > InitialiseClient ( session - > GetHostID ( ) , readyTurn ) ;
// Tell the client that everything has finished loading and it should start now
CLoadedGameMessage loaded ;
loaded . m_CurrentTurn = readyTurn ;
session - > SendMessage ( & loaded ) ;
return true ;
}
2010-10-31 15:00:28 -07:00
bool CNetServerWorker : : OnDisconnect ( void * context , CFsmEvent * event )
2010-07-02 14:28:48 -07:00
{
2011-04-30 06:01:45 -07:00
ENSURE ( event - > GetType ( ) = = ( uint ) NMT_CONNECTION_LOST ) ;
2010-07-02 14:28:48 -07:00
CNetServerSession * session = ( CNetServerSession * ) context ;
2010-10-31 15:00:28 -07:00
CNetServerWorker & server = session - > GetServer ( ) ;
2010-07-02 14:28:48 -07:00
2010-07-06 12:54:17 -07:00
server . OnUserLeave ( session ) ;
2010-07-02 14:28:48 -07:00
return true ;
}
2010-10-31 15:00:28 -07:00
void CNetServerWorker : : CheckGameLoadStatus ( CNetServerSession * changedSession )
2009-04-11 10:00:39 -07:00
{
2010-06-30 14:41:04 -07:00
for ( size_t i = 0 ; i < m_Sessions . size ( ) ; + + i )
2009-04-11 10:00:39 -07:00
{
2010-06-30 14:41:04 -07:00
if ( m_Sessions [ i ] ! = changedSession & & m_Sessions [ i ] - > GetCurrState ( ) ! = NSS_INGAME )
return ;
2009-04-11 10:00:39 -07:00
}
2010-06-30 14:41:04 -07:00
CLoadedGameMessage loaded ;
2011-10-27 09:46:48 -07:00
loaded . m_CurrentTurn = 0 ;
2010-06-30 14:41:04 -07:00
Broadcast ( & loaded ) ;
m_State = SERVER_STATE_INGAME ;
2009-04-11 10:00:39 -07:00
}
2010-10-31 15:00:28 -07:00
void CNetServerWorker : : StartGame ( )
2009-04-11 10:00:39 -07:00
{
2010-06-30 14:41:04 -07:00
m_ServerTurnManager = new CNetServerTurnManager ( * this ) ;
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
for ( size_t i = 0 ; i < m_Sessions . size ( ) ; + + i )
2011-10-27 09:46:48 -07:00
m_ServerTurnManager - > InitialiseClient ( m_Sessions [ i ] - > GetHostID ( ) , 0 ) ; // TODO: only for non-observers
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
m_State = SERVER_STATE_LOADING ;
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
// Send the final setup state to all clients
2015-01-24 06:46:52 -08:00
UpdateGameAttributes ( & m_GameAttributes . get ( ) ) ;
2010-06-30 14:41:04 -07:00
SendPlayerAssignments ( ) ;
2009-04-11 10:00:39 -07:00
2010-06-30 14:41:04 -07:00
CGameStartMessage gameStart ;
Broadcast ( & gameStart ) ;
}
2014-08-02 15:21:50 -07:00
void CNetServerWorker : : UpdateGameAttributes ( JS : : MutableHandleValue attrs )
2010-06-30 14:41:04 -07:00
{
2015-01-24 06:46:52 -08:00
m_GameAttributes . set ( m_ScriptInterface - > GetJSRuntime ( ) , attrs ) ;
2010-06-30 14:41:04 -07:00
if ( ! m_Host )
return ;
CGameSetupMessage gameSetupMessage ( GetScriptInterface ( ) ) ;
2015-01-24 06:46:52 -08:00
gameSetupMessage . m_Data . set ( m_GameAttributes . get ( ) ) ;
2010-06-30 14:41:04 -07:00
Broadcast ( & gameSetupMessage ) ;
2009-04-11 10:00:39 -07:00
}
2010-07-02 14:28:48 -07:00
2010-10-31 15:00:28 -07:00
CStrW CNetServerWorker : : SanitisePlayerName ( const CStrW & original )
2010-07-02 14:28:48 -07:00
{
const size_t MAX_LENGTH = 32 ;
CStrW name = original ;
name . Replace ( L " [ " , L " { " ) ; // remove GUI tags
name . Replace ( L " ] " , L " } " ) ; // remove for symmetry
// Restrict the length
if ( name . length ( ) > MAX_LENGTH )
name = name . Left ( MAX_LENGTH ) ;
// Don't allow surrounding whitespace
name . Trim ( PS_TRIM_BOTH ) ;
// Don't allow empty name
if ( name . empty ( ) )
name = L " Anonymous " ;
return name ;
}
2010-10-31 15:00:28 -07:00
CStrW CNetServerWorker : : DeduplicatePlayerName ( const CStrW & original )
2010-07-02 14:28:48 -07:00
{
CStrW name = original ;
2010-07-06 12:54:17 -07:00
// Try names "Foo", "Foo (2)", "Foo (3)", etc
2010-07-02 14:28:48 -07:00
size_t id = 2 ;
while ( true )
{
bool unique = true ;
for ( size_t i = 0 ; i < m_Sessions . size ( ) ; + + i )
{
if ( m_Sessions [ i ] - > GetUserName ( ) = = name )
{
unique = false ;
break ;
}
}
if ( unique )
return name ;
2011-02-17 12:08:20 -08:00
name = original + L " ( " + CStrW : : FromUInt ( id + + ) + L " ) " ;
2010-07-02 14:28:48 -07:00
}
}
2010-10-31 15:00:28 -07:00
CNetServer : : CNetServer ( int autostartPlayers ) :
m_Worker ( new CNetServerWorker ( autostartPlayers ) )
{
}
CNetServer : : ~ CNetServer ( )
{
delete m_Worker ;
}
bool CNetServer : : SetupConnection ( )
{
2013-12-17 06:57:26 -08:00
return m_Worker - > SetupConnection ( ) ;
2010-10-31 15:00:28 -07:00
}
void CNetServer : : AssignPlayer ( int playerID , const CStr & guid )
{
CScopeLock lock ( m_Worker - > m_WorkerMutex ) ;
m_Worker - > m_AssignPlayerQueue . push_back ( std : : make_pair ( playerID , guid ) ) ;
}
2014-04-26 11:34:34 -07:00
void CNetServer : : SetPlayerReady ( const CStr & guid , int ready )
{
CScopeLock lock ( m_Worker - > m_WorkerMutex ) ;
m_Worker - > m_PlayerReadyQueue . push_back ( std : : make_pair ( guid , ready ) ) ;
}
void CNetServer : : ClearAllPlayerReady ( )
{
CScopeLock lock ( m_Worker - > m_WorkerMutex ) ;
m_Worker - > m_PlayerResetReadyQueue . push_back ( false ) ;
}
2010-10-31 15:00:28 -07:00
void CNetServer : : StartGame ( )
{
CScopeLock lock ( m_Worker - > m_WorkerMutex ) ;
m_Worker - > m_StartGameQueue . push_back ( true ) ;
}
2014-08-02 15:21:50 -07:00
void CNetServer : : UpdateGameAttributes ( JS : : MutableHandleValue attrs , ScriptInterface & scriptInterface )
2010-10-31 15:00:28 -07:00
{
// Pass the attributes as JSON, since that's the easiest safe
// cross-thread way of passing script data
2014-08-02 15:21:50 -07:00
std : : string attrsJSON = scriptInterface . StringifyJSON ( attrs , false ) ;
2010-10-31 15:00:28 -07:00
CScopeLock lock ( m_Worker - > m_WorkerMutex ) ;
m_Worker - > m_GameAttributesQueue . push_back ( attrsJSON ) ;
}
void CNetServer : : SetTurnLength ( u32 msecs )
{
CScopeLock lock ( m_Worker - > m_WorkerMutex ) ;
m_Worker - > m_TurnLengthQueue . push_back ( msecs ) ;
}