1815 lines
56 KiB
C++
1815 lines
56 KiB
C++
// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
|
|
// Copyright (C) 2010 Winch Gate Property Limited
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as
|
|
// published by the Free Software Foundation, either version 3 of the
|
|
// License, or (at your option) any later version.
|
|
//
|
|
// This program 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 Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
#include "stdpch.h"
|
|
|
|
#include <nel/misc/stop_watch.h>
|
|
#include <nel/misc/bit_set.h>
|
|
|
|
#include <nel/net/login_server.h>
|
|
#include <nel/net/login_cookie.h>
|
|
|
|
#include "fe_types.h"
|
|
#include "fe_receive_sub.h"
|
|
#include "game_share/action_factory.h"
|
|
#include "game_share/action_position.h"
|
|
#include "game_share/action_sync.h"
|
|
#include "game_share/action_disconnection.h"
|
|
#include "game_share/action_association.h"
|
|
#include "game_share/action_generic.h"
|
|
#include "game_share/action_login.h"
|
|
#include "game_share/action_sint64.h"
|
|
#include "game_share/action_dummy.h"
|
|
#include "game_share/action_block.h"
|
|
#include "game_share/action_target_slot.h"
|
|
#include "game_share/tick_event_handler.h"
|
|
#include "game_share/synchronised_message.h"
|
|
#include "history.h"
|
|
#include "fe_send_sub.h"
|
|
#include "frontend_service.h"
|
|
#include "fe_stat.h"
|
|
|
|
#include "uid_impulsions.h"
|
|
#include "id_impulsions.h"
|
|
|
|
#include "game_share/ryzom_entity_id.h"
|
|
#include "game_share/system_message.h"
|
|
#ifdef ENABLE_MEM_DELTA
|
|
#include "../entities_game_service/mem_debug.h"
|
|
#else
|
|
#define MEM_DELTA_MULTI_LAST2(a, b)
|
|
#define MEM_DELTA_MULTI2_MID(a, b)
|
|
#define MEM_DELTA_MULTI2(a, b)
|
|
#endif
|
|
|
|
using namespace std;
|
|
using namespace NLNET;
|
|
using namespace NLMISC;
|
|
using namespace CLFECOMMON;
|
|
|
|
|
|
const TUid BaseAutoAllocUserid = 1000000000;
|
|
TUid CurrentAutoAllocUserid = BaseAutoAllocUserid;
|
|
|
|
|
|
bool verbosePacketLost = false;
|
|
|
|
extern void flushMessagesToSend();
|
|
|
|
//void cbGwTrConnection ( TClientId clientId);
|
|
void cbGwTrDisconnection ( TClientId clientId );
|
|
|
|
|
|
extern CVariable<string> SaveShardRootGameShare;
|
|
extern CVariable<bool> VerboseFEStatsTime;
|
|
|
|
|
|
/*
|
|
* Return the string for the message invalidity reasons
|
|
*/
|
|
string getBadMessageString( uint32 reasons )
|
|
{
|
|
string s;
|
|
if ( reasons & InsufficientSize )
|
|
s += " Insufficient size;";
|
|
if ( reasons & NotSystemLoginCode )
|
|
s += " Not system login code;";
|
|
if ( reasons & BadCookie )
|
|
s += " Bad cookie;";
|
|
if ( reasons & BadSystemCode )
|
|
s += " Bad system code;";
|
|
if ( reasons & HackedSizeInBuffer )
|
|
s += " Hacked size in buffer;";
|
|
if ( reasons & AccessClosed )
|
|
s += " Access closed;";
|
|
if ( reasons & IrrelevantSystemMessage )
|
|
s += " Irrelevant System Message;";
|
|
if ( reasons & MalformedAction )
|
|
s += " Malformed action;";
|
|
if ( reasons & UnknownExceptionType )
|
|
s += " Unknown Exception;";
|
|
if ( reasons & UnknownFormatType )
|
|
s += " Unknown format type;";
|
|
if ( reasons & UnauthorizedCharacterSlot )
|
|
s += " Unauthorized character slot;";
|
|
return s;
|
|
}
|
|
|
|
|
|
/*
|
|
* Init
|
|
*/
|
|
void CFeReceiveSub::init( uint16 firstAcceptableFrontendPort, uint16 lastAcceptableFrontendPort, uint32 dgrammaxlength, CHistory *history, TClientIdCont *clientidcont )
|
|
{
|
|
// Preconditions
|
|
nlassert( firstAcceptableFrontendPort != 0 );
|
|
nlassert( lastAcceptableFrontendPort >= firstAcceptableFrontendPort );
|
|
nlassert( dgrammaxlength != 0 );
|
|
nlassert( history && clientidcont );
|
|
nlassert( (_ReceiveTask==NULL) && (_ReceiveThread==NULL) );
|
|
|
|
// Start external datagram socket
|
|
nlinfo( "FERECV: Starting external datagram socket" );
|
|
_ReceiveTask = new CFEReceiveTask( firstAcceptableFrontendPort, lastAcceptableFrontendPort, dgrammaxlength );
|
|
_CurrentReadQueue = &_Queue2;
|
|
_ReceiveTask->setWriteQueue( &_Queue1 );
|
|
nlassert( _ReceiveTask != NULL );
|
|
_ReceiveThread = IThread::create( _ReceiveTask );
|
|
nlassert( _ReceiveThread != NULL );
|
|
_ReceiveThread->start();
|
|
|
|
// Setup current message placeholder
|
|
_CurrentInMsg = new TReceivedMessage();
|
|
|
|
_History = history;
|
|
|
|
_ClientIdCont = clientidcont;
|
|
_ClientIdCont->resize( MaxNbClients+1 ); // because the client ids start at 1
|
|
TClientIdCont::iterator iclient;
|
|
for ( iclient=_ClientIdCont->begin(); iclient!=_ClientIdCont->end(); ++iclient )
|
|
{
|
|
*iclient = NULL;
|
|
}
|
|
|
|
// Setup connection stat log
|
|
_ConnectionStatDisp = new CFileDisplayer( string("data_shard_local/connections.stat"), false, "ConnectionStats" );
|
|
ConnectionStatLog = new CLog( CLog::LOG_NO );
|
|
ConnectionStatLog->addDisplayer( _ConnectionStatDisp );
|
|
}
|
|
|
|
|
|
/*
|
|
* Release
|
|
*/
|
|
void CFeReceiveSub::release()
|
|
{
|
|
if ( ConnectionStatLog )
|
|
delete ConnectionStatLog;
|
|
if ( _ConnectionStatDisp )
|
|
delete _ConnectionStatDisp;
|
|
|
|
nlassert( _ReceiveTask != NULL );
|
|
nlassert( _ReceiveThread != NULL );
|
|
|
|
_ReceiveTask->requireExit();
|
|
#ifdef NL_OS_UNIX
|
|
// Send dummy data to a bound udp socket, to wake-up the receive thread
|
|
vector<CInetAddress> addrlist = CInetAddress::localAddresses();
|
|
if ( ! addrlist.empty() )
|
|
{
|
|
CUdpSock tempSock;
|
|
uint8 dummyByte = 0;
|
|
addrlist[0].setPort( _ReceiveTask->DataSock->localAddr().port() );
|
|
tempSock.sendTo( &dummyByte, sizeof(dummyByte), addrlist[0] );
|
|
}
|
|
#else
|
|
// Under Windows, closing the socket wakes-up recvfrom().
|
|
_ReceiveTask->DataSock->close();
|
|
#endif
|
|
_ReceiveThread->wait();
|
|
|
|
delete _ReceiveThread;
|
|
delete _ReceiveTask;
|
|
_ReceiveTask = NULL;
|
|
_ReceiveThread = NULL;
|
|
|
|
nlassert( _CurrentInMsg != NULL );
|
|
delete _CurrentInMsg;
|
|
_CurrentInMsg = NULL;
|
|
}
|
|
|
|
|
|
// Receive time except CVision::update()
|
|
CStopWatch RecvWatch;
|
|
|
|
|
|
|
|
|
|
/*
|
|
* Update
|
|
* Deprecated: replaced by modules (see initModules() in frontend_service.cpp)
|
|
*/
|
|
/*void CFeReceiveSub::update()
|
|
{
|
|
swapReadQueues();
|
|
|
|
readIncomingData();
|
|
}*/
|
|
|
|
/*
|
|
* Read incoming data from the current read queue
|
|
*/
|
|
void CFeReceiveSub::readIncomingData()
|
|
{
|
|
CFrontEndService::instance()->ReceiveWatch.start();
|
|
RecvWatch.start();
|
|
|
|
// Remove the clients who need to
|
|
TClientsToRemove::iterator ictr;
|
|
for ( ictr=_ClientsToRemove.begin(); ictr!=_ClientsToRemove.end(); )
|
|
{
|
|
if ( (*ictr).second == 0 )
|
|
{
|
|
// If the clientid has been removed
|
|
removeClientById( (*ictr).first );
|
|
ictr = _ClientsToRemove.erase( ictr );
|
|
}
|
|
else
|
|
{
|
|
--((*ictr).second); // wait, see addToRemoveList()
|
|
++ictr;
|
|
}
|
|
}
|
|
|
|
// Read queue of messages received from clients
|
|
while ( ! _CurrentReadQueue->empty() )
|
|
{
|
|
//nlinfo( "Read queue size = %u", _CurrentReadQueue->size() );
|
|
_CurrentReadQueue->front( _CurrentInMsg->data() );
|
|
_CurrentReadQueue->pop();
|
|
nlassert( ! _CurrentReadQueue->empty() );
|
|
_CurrentReadQueue->front( _CurrentInMsg->VAddrFrom );
|
|
_CurrentReadQueue->pop();
|
|
_CurrentInMsg->vectorToAddress();
|
|
|
|
#ifndef MEASURE_RECEIVE_TASK
|
|
handleIncomingMsg();
|
|
#endif
|
|
}
|
|
|
|
// Measure and display rate evenly (at a low frequency)
|
|
static TTime lastdisplay = CTime::getLocalTime();
|
|
if ( CTime::getLocalTime() - lastdisplay > 5000 )
|
|
{
|
|
// Message stats
|
|
if ( VerboseFEStatsTime.get() )
|
|
displayDatagramStats( lastdisplay );
|
|
|
|
lastdisplay = CTime::getLocalTime();
|
|
_RcvCounter = 0;
|
|
CFrontEndService::instance()->sendSub()->sendCounter() = 0;
|
|
|
|
// Hacking detection: message size
|
|
uint nbrej = _ReceiveTask->nbNewRejectedDatagrams();
|
|
if ( nbrej != 0 )
|
|
{
|
|
nlinfo( "FEHACK: Rejected big messages: %u", nbrej );
|
|
}
|
|
|
|
// Hacking detection: bad identification
|
|
if ( ! _UnidentifiedFlyingClients.empty() )
|
|
{
|
|
nldebug( "FEHACK: Received messages from %u unidentified clients:", _UnidentifiedFlyingClients.size() );
|
|
THackingAddrSet::iterator ias;
|
|
for ( ias=_UnidentifiedFlyingClients.begin(); ias!=_UnidentifiedFlyingClients.end(); ++ias )
|
|
{
|
|
nlinfo( "FEHACK: * User %u %s --> %u msg, reasons:%s", (*ias).second.UserId, (*ias).first.asString().c_str(), (*ias).second.InvalidMsgCounter, getBadMessageString( (*ias).second.Reasons ).c_str() );
|
|
}
|
|
_UnidentifiedFlyingClients.clear();
|
|
}
|
|
}
|
|
|
|
RecvWatch.stop();
|
|
if ( VerboseFEStatsTime.get() && (RecvWatch.getDuration() > 20) ) // does not include the vision updating
|
|
{
|
|
nlinfo( "FETIME: Time of receiving: AllButVision peak=%u", RecvWatch.getDuration() );
|
|
}
|
|
CFrontEndService::instance()->ReceiveWatch.stop();
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Swap receive queues (to avoid high contention between the receive thread and the reading thread)
|
|
*/
|
|
void CFeReceiveSub::swapReadQueues()
|
|
{
|
|
if ( _CurrentReadQueue == &_Queue1 )
|
|
{
|
|
_CurrentReadQueue = &_Queue2;
|
|
_ReceiveTask->setWriteQueue( &_Queue1 );
|
|
//nlinfo( "** Write1 Read2 ** Read=%p Write=%p", &_Queue2, &_Queue1 );
|
|
}
|
|
else
|
|
{
|
|
_CurrentReadQueue = &_Queue1;
|
|
_ReceiveTask->setWriteQueue( &_Queue2 );
|
|
//nlinfo( "** Read1 Write2 ** Read=%p Write=%p", &_Queue1, &_Queue2 );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Handles client addition/removal/identification, then calls handleReceivedMsg() (if not removal)
|
|
*/
|
|
void CFeReceiveSub::handleIncomingMsg()
|
|
{
|
|
nlassert( _History && _CurrentInMsg );
|
|
|
|
#ifndef SIMUL_CLIENTS
|
|
|
|
//nldebug( "FERECV: Handling incoming message" );
|
|
|
|
// Retrieve client info or add one
|
|
THostMap::iterator ihm = _ClientMap.find( _CurrentInMsg->AddrFrom );
|
|
if ( ihm == _ClientMap.end() )
|
|
{
|
|
if ( _CurrentInMsg->eventType() == TReceivedMessage::User )
|
|
{
|
|
// Handle message for a new client
|
|
handleReceivedMsg( NULL );
|
|
}
|
|
else
|
|
{
|
|
nlinfo( "FERECV: Not removing already removed client" );
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Already existing
|
|
if ( _CurrentInMsg->eventType() == TReceivedMessage::RemoveClient )
|
|
{
|
|
// Remove client
|
|
nlinfo( "FERECV: Disc event for client %u", GETCLIENTA(ihm)->clientId() );
|
|
removeFromRemoveList(GETCLIENTA(ihm)->clientId() );
|
|
removeClientById( GETCLIENTA(ihm)->clientId() );
|
|
|
|
// Do not call handleReceivedMsg()
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// Handle message
|
|
H_AUTO(HandleRecvdMsgNotNew);
|
|
handleReceivedMsg( GETCLIENTA(ihm) );
|
|
}
|
|
}
|
|
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
*
|
|
*/
|
|
bool CFeReceiveSub::acceptUserIdConnection( TUid userId )
|
|
{
|
|
for (THostMap::iterator it = CFrontEndService::instance()->receiveSub()->clientMap().begin(); it != CFrontEndService::instance()->receiveSub()->clientMap().end(); it++)
|
|
{
|
|
if ( GETCLIENTA(it)->Uid == userId)
|
|
{
|
|
nlwarning( "Can't accept connection of uid %u (already connected: client %hu)", userId, GETCLIENTA(it)->clientId() );
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Add client
|
|
*/
|
|
CClientHost *CFeReceiveSub::addClient( const NLNET::CInetAddress& addrfrom, TUid userId, const string &userName, const string &userPriv, const std::string & userExtended, const std::string & languageId, const NLNET::CLoginCookie &cookie, uint32 instanceId, uint8 authorizedCharSlot, bool sendCLConnect )
|
|
{
|
|
MEM_DELTA_MULTI_LAST2(Client,AddClient);
|
|
if ( ! acceptUserIdConnection( userId ) )
|
|
{
|
|
cbDisconnectClient( userId , "FS CFeReceiveSub::addClient"); // simulate a request by the login system (NeL Launcher bypass mode)
|
|
return NULL;
|
|
}
|
|
|
|
CClientHost *clienthost;
|
|
|
|
// Create client object and add it into the client map
|
|
TClientId clientid = _ClientIdPool.getNewClientId();
|
|
if ( clientid == InvalidClientId )
|
|
{
|
|
nlwarning( "Front-end is full, new client %s rejected", addrfrom.asString().c_str() );
|
|
return NULL;
|
|
}
|
|
|
|
MEM_DELTA_MULTI2_MID(Client,BeforeNewCClientHost); // 0
|
|
/// callback the gateway transport
|
|
// cbGwTrConnection(clientid);
|
|
|
|
THostMap::iterator cmPreviousEnd = _ClientMap.end();
|
|
{
|
|
MEM_DELTA_MULTI2(CClientHost,New);
|
|
clienthost = new CClientHost( addrfrom, clientid );
|
|
}
|
|
nlassert( clienthost );
|
|
nlinfo( "Adding client %u (uid %u name %s priv '%s') at %s", clientid, userId, userName.c_str(), userPriv.c_str(), addrfrom.asIPString().c_str() );
|
|
ConnectionStatLog->displayNL( "Adding client %u (uid %u name %s priv '%s') at %s", clientid, userId, userName.c_str(), userPriv.c_str(), addrfrom.asIPString().c_str() );
|
|
//MEM_DELTA_MULTI2_MID(Client,BeforeInsertClient); // = CClientHost
|
|
|
|
if ( ! _ClientMap.insert( make_pair( addrfrom, clienthost ) ).second )
|
|
{
|
|
nlwarning( "Problem: Inserted twice the same address in the client map" );
|
|
|
|
|
|
}
|
|
MEM_DELTA_MULTI2_MID(Client,AfterInsertClient); // 24
|
|
CFrontEndService::instance()->PrioSub.VisionProvider.DistanceSpreader.notifyClientAddition( cmPreviousEnd );
|
|
CFrontEndService::instance()->PrioSub.Prioritizer.SortSpreader.notifyClientAddition( cmPreviousEnd );
|
|
/*CFrontEndService::instance()->PrioSub.Prioritizer.PositionSpreader.notifyClientAddition( cmPreviousEnd );
|
|
CFrontEndService::instance()->PrioSub.Prioritizer.OrientationSpreader.notifyClientAddition( cmPreviousEnd );
|
|
CFrontEndService::instance()->PrioSub.Prioritizer.DiscreetSpreader.notifyClientAddition( cmPreviousEnd );*/
|
|
// Set entity ids
|
|
(*_ClientIdCont)[clientid] = clienthost;
|
|
_History->addClient( clientid );
|
|
clienthost->Uid = userId;
|
|
clienthost->UserName = userName;
|
|
clienthost->UserPriv = userPriv;
|
|
clienthost->UserExtended = userExtended;
|
|
clienthost->LanguageId = languageId;
|
|
clienthost->LoginCookie = cookie;
|
|
clienthost->InstanceId = instanceId;
|
|
clienthost->AuthorizedCharSlot = authorizedCharSlot;
|
|
|
|
clienthost->initSendCycle( false );
|
|
CFrontEndService::instance()->sendSub()->setSendBufferAddress( clientid, &addrfrom );
|
|
|
|
//This must be commented out when the GPMS always activates slot 0
|
|
//CFrontEndService::instance()->PrioSub.Prioritizer.addEntitySeenByClient( clientid, 0 );
|
|
|
|
#ifdef MEASURE_SENDING
|
|
#error should not be active
|
|
static uint8 buf [500];
|
|
nlassert( CFrontEndService::instance()->sendSub()->clientBandwidth()/8 < 500 );
|
|
CFrontEndService::instance()->sendSub()->outBox( clientid ).serialBuffer( buf, CFrontEndService::instance()->sendSub()->clientBandwidth()/8 );
|
|
return clienthost;
|
|
#endif
|
|
|
|
// Take current time as receive timestamp (set it at login to avoid immediate probe)
|
|
clienthost->setReceiveTimeNow();
|
|
MEM_DELTA_MULTI2_MID(Client,AfterReceiveTimeNow);
|
|
|
|
// Send UserId to the EGS
|
|
//nldebug( "%u: Sending user id %u for C%hu: to EGS", CTickEventHandler::getGameCycle(), userId, clientid );
|
|
if ( sendCLConnect )
|
|
{
|
|
if ( ! CFrontEndService::instance()->ShardDown )
|
|
{
|
|
CMessage msgout( "CL_CONNECT" );
|
|
msgout.serial( userId );
|
|
string un = userName;
|
|
msgout.serial( un );
|
|
string up = userPriv;
|
|
msgout.serial( up );
|
|
string ux = userExtended;
|
|
msgout.serial( ux );
|
|
string langId = languageId;
|
|
msgout.serial( langId );
|
|
string addrStr = addrfrom.asIPString();
|
|
msgout.serial( addrStr );
|
|
CLoginCookie logcook = cookie;
|
|
nlinfo("WEB: sent cookie %s to EGS from player %d", cookie.toString().c_str(), userId);
|
|
msgout.serial( logcook );
|
|
sendMessageViaMirror( "EGS", msgout ); // because the EGS can despawn an entity
|
|
}
|
|
else
|
|
{
|
|
// The client object will be removed when the client exits
|
|
return NULL;
|
|
}
|
|
|
|
MEM_DELTA_MULTI2_MID(Client,AfterSendCLConnect);
|
|
TClientId mon = CFrontEndService::instance()->MonitoredClient;
|
|
if ( mon != 0 && clientid == mon )
|
|
{
|
|
nlinfo( "CLTMON: Client %u is connecting", mon );
|
|
}
|
|
|
|
beepIfAllowed( 660, 150 );
|
|
beepIfAllowed( 880, 150 );
|
|
}
|
|
|
|
//CFrontEndService::instance()->validateUserWebEntry(userName, cookie.toString());
|
|
MEM_DELTA_MULTI2_MID(Client,BeforeReturn);
|
|
|
|
return clienthost;
|
|
}
|
|
|
|
|
|
/*
|
|
* Remove a client by iterator on THostMap (see also byId)
|
|
* // DEPRECATED
|
|
*/
|
|
/*void CFeReceiveSub::removeClientByAddr( THostMap::iterator iclient )
|
|
{
|
|
if ( iclient == _ClientMap.end() )
|
|
{
|
|
// It may have already been removed on purpose
|
|
return;
|
|
}
|
|
nlinfo( "FERECV: Removing client %u at %s", GETCLIENTA(iclient)->clientId(), GETCLIENTA(iclient)->address().asIPString().c_str() );
|
|
doRemoveClient( GETCLIENTA(iclient) );
|
|
//nlassert( GETCLIENTA(iclient)->clientId() < _ClientIdCont->size() );
|
|
//(*_ClientIdCont)[ GETCLIENTA(iclient)->clientId() ] = NULL;
|
|
//delete GETCLIENTA(iclient);
|
|
|
|
CFrontEndService::instance()->PrioSub.VisionProvider.DistanceSpreader.notifyClientRemoval( iclient );
|
|
CFrontEndService::instance()->PrioSub.Prioritizer.PositionSpreader.notifyClientRemoval( iclient );
|
|
CFrontEndService::instance()->PrioSub.Prioritizer.OrientationSpreader.notifyClientRemoval( iclient );
|
|
CFrontEndService::instance()->PrioSub.Prioritizer.DiscreetSpreader.notifyClientRemoval( iclient );
|
|
|
|
_ClientMap.erase( iclient );
|
|
}*/
|
|
|
|
|
|
/*
|
|
*
|
|
*/
|
|
void CFeReceiveSub::setClientInLimboMode( CClientHost *client )
|
|
{
|
|
LimboClients.insert( make_pair( client->Uid, CLimboClient( client ) ) );
|
|
removeClientFromMap( client );
|
|
client->resetClientVision();
|
|
removeClientLinks( client );
|
|
deleteClient( client );
|
|
}
|
|
|
|
|
|
/*
|
|
*
|
|
*/
|
|
CClientHost *CFeReceiveSub::exitFromLimboMode( const CLimboClient& lc )
|
|
{
|
|
nldebug( "Restoring user %u from limbo mode", lc.Uid );
|
|
|
|
CClientHost *client = addClient( lc.AddrFrom, lc.Uid, lc.UserName, lc.UserPriv, lc.UserExtended, lc.LanguageId, lc.LoginCookie, 0xF, false );
|
|
NLMISC::TGameCycle tick = CTickEventHandler::getGameCycle();
|
|
client->setFirstSentPacket( client->sendNumber()+1, tick );
|
|
client->setSynchronizeState();
|
|
client->QuitId = lc.QuitId;
|
|
return client;
|
|
}
|
|
|
|
|
|
/*
|
|
*
|
|
*/
|
|
void CFeReceiveSub::removeClientFromMap( CClientHost *client )
|
|
{
|
|
THostMap::iterator icm = _ClientMap.find( clientIdCont()[client->clientId()]->address() );
|
|
CFrontEndService::instance()->PrioSub.VisionProvider.DistanceSpreader.notifyClientRemoval( icm );
|
|
CFrontEndService::instance()->PrioSub.Prioritizer.SortSpreader.notifyClientRemoval( icm );
|
|
/*CFrontEndService::instance()->PrioSub.Prioritizer.PositionSpreader.notifyClientRemoval( icm );
|
|
CFrontEndService::instance()->PrioSub.Prioritizer.OrientationSpreader.notifyClientRemoval( icm );
|
|
CFrontEndService::instance()->PrioSub.Prioritizer.DiscreetSpreader.notifyClientRemoval( icm );*/
|
|
|
|
_ClientMap.erase( client->address() );
|
|
}
|
|
|
|
/*
|
|
* Remove a client by iterator on TClientIdCont (see also byAddr)
|
|
*/
|
|
void CFeReceiveSub::removeClientById( TClientId clientid, bool crashed )
|
|
{
|
|
MEM_DELTA_MULTI2(RemClient,RemoveClientById);
|
|
|
|
TClientIdCont::iterator ici = _ClientIdCont->begin() + clientid;
|
|
nlassert( ici >= _ClientIdCont->begin() && ici < _ClientIdCont->end() );
|
|
|
|
if ( (*ici) != NULL )
|
|
{
|
|
// Bypass disconnection process if already engaged (can occur if WS requests it several times but not necessary for this case if it can't be inserted twice in the remove list)
|
|
if ( ! GETCLIENTI(ici)->isDisconnected() )
|
|
{
|
|
nlinfo( "FERECV: Removing client %hu at %s", GETCLIENTI(ici)->clientId(), GETCLIENTI(ici)->address().asIPString().c_str() );
|
|
doRemoveClient( GETCLIENTI(ici), crashed );
|
|
removeClientFromMap( GETCLIENTI(ici) );
|
|
}
|
|
else
|
|
{
|
|
// Not expected because cbDisconnectClient prevents from adding twice the same client
|
|
nlwarning( "Bypassing extra-removal of client %hu (uid %u)", GETCLIENTI(ici)->clientId(), GETCLIENTI(ici)->Uid );
|
|
}
|
|
}
|
|
|
|
//delete GETCLIENTI(ici);
|
|
//*ici = NULL; // later
|
|
}
|
|
|
|
|
|
/*
|
|
* Utility method to remove a client
|
|
*/
|
|
void CFeReceiveSub::doRemoveClient( CClientHost *client, bool crashed )
|
|
{
|
|
MEM_DELTA_MULTI2(RemClient,DoRemoveClient);
|
|
|
|
// Tell the other clients that this one has disconnected
|
|
/// DEBUG BEN don't send the disconnection to the others yet
|
|
/*
|
|
THostMap::iterator ihm;
|
|
for ( ihm=_ClientMap.begin(); ihm!=_ClientMap.end(); ++ihm )
|
|
{
|
|
if ( GETCLIENTA(ihm) != client && _Vision->see(client->entityId(), GETCLIENTA(ihm)->entityId()))
|
|
{
|
|
// Creating a new action object for each one is required
|
|
CAction *action = CActionFactory::getInstance()->create( ACTION_DISCONNECTION_CODE );
|
|
action->FTEntityId = client->entityId();
|
|
GETCLIENTA(ihm)->ImpulseEncoder.add(action, 1);
|
|
}
|
|
}
|
|
*/
|
|
|
|
//
|
|
// Remove the client from the EGS
|
|
//
|
|
|
|
if ( client->Uid != 0xFFFFFFFF )
|
|
{
|
|
// Handle the case when the id is not set yet (uid instead of id)
|
|
CMessage msgout( "CL_DISCONNECT" );
|
|
//Sid sid = client->sid();
|
|
//msgout.serial( sid );
|
|
msgout.serial( client->Uid );
|
|
msgout.serial( crashed );
|
|
sendMessageViaMirror( "EGS", msgout );
|
|
nlinfo( "Sent CL_DISCONNECT for client %hu (uid %u) to EGS", client->clientId(), client->Uid );
|
|
ConnectionStatLog->displayNL( "Sent CL_DISCONNECT for client %hu (uid %u) to EGS", client->clientId(), client->Uid );
|
|
|
|
// unless user wants to reconnect, disconnect him from login service
|
|
//if( ! findInReconnectList( client->Uid ) )
|
|
//nldebug( "disconnecting client %d from login service", client->clientId() );
|
|
CLoginServer::clientDisconnected( client->Uid );
|
|
|
|
}
|
|
|
|
CFrontEndService::instance()->sendSub()->disableSendBuffer( client->clientId() );
|
|
|
|
// Here, we don't free the client's id yet (neither remove the entity/client in the history),
|
|
// because CPrioSub::processVision() will need it when removing the corresponding couples.
|
|
// It will call freeIdsOfRemovedClients() after that.
|
|
_RemovedClientEntities.push_back( client );
|
|
client->disconnect();
|
|
|
|
TClientId mon = CFrontEndService::instance()->MonitoredClient;
|
|
if ( mon != 0 && client->clientId() == mon )
|
|
{
|
|
nlinfo( "CLTMON: Client %u is disconnecting", mon );
|
|
}
|
|
|
|
|
|
/* TEMP
|
|
* End perf measures
|
|
*/
|
|
//CHTimer::display();
|
|
|
|
/*
|
|
* Display prio stats
|
|
*/
|
|
#ifdef MEASURE_FRONTEND_TABLES
|
|
nlinfo( "-- Stats for client 1 --" );
|
|
TEventPerSeenEntityCounterFrame::displayAll( "Dist", DistCntClt1 );
|
|
TEventPerSeenEntityCounterFrame::displayAll( "Delta", DeltaCntClt1 );
|
|
TEventPerSeenEntityCounterFrame::displayAll( "Prio", PrioCntClt1 );
|
|
TEventPerSeenEntityCounterFrame::displayAll( "PosSent", PosSentCntClt1 );
|
|
#endif
|
|
}
|
|
|
|
|
|
/*
|
|
*
|
|
*/
|
|
void CFeReceiveSub::removeClientLinks( CClientHost *clienthost )
|
|
{
|
|
if ( ! clienthost->eId().isUnknownId() )
|
|
EntityToClient.removeEId( clienthost->eId() );
|
|
|
|
if ( clienthost->entityIndex().isValid() )
|
|
EntityToClient.removeEntityIndex( clienthost->entityIndex() );
|
|
}
|
|
|
|
|
|
/*
|
|
*
|
|
*/
|
|
void CFeReceiveSub::deleteClient( CClientHost *clienthost )
|
|
{
|
|
nldebug( "deleting client %d", clienthost->clientId() );
|
|
//CFrontEndService::instance()->unvalidateUserWebEntry(clienthost->UserName);
|
|
|
|
// Callback gateway transport
|
|
cbGwTrDisconnection(clienthost->clientId());
|
|
|
|
(*_ClientIdCont)[ clienthost->clientId() ] = NULL;
|
|
_History->removeClient( clienthost->clientId() );
|
|
_ClientIdPool.releaseId( clienthost->clientId() );
|
|
MEM_DELTA_MULTI2(CClientHost,Delete);
|
|
delete clienthost;
|
|
}
|
|
|
|
|
|
/*
|
|
* After a client has been removed from the tables, free its identifier
|
|
*/
|
|
void CFeReceiveSub::freeIdsOfRemovedClients()
|
|
{
|
|
vector<CClientHost*>::iterator ic;
|
|
for ( ic=_RemovedClientEntities.begin(); ic!=_RemovedClientEntities.end(); ++ic )
|
|
{
|
|
MEM_DELTA_MULTI2(RemClient,FreeIdsOfRemovedClients);
|
|
CClientHost *clienthost = *ic;
|
|
|
|
// Display info
|
|
nlinfo( "FE: Freeing client %u (%s)", clienthost->clientId(), clienthost->address().asString().c_str() );
|
|
clienthost->displayClientProperties( false );
|
|
|
|
// Reset vision and links in tables
|
|
clienthost->resetClientVision();
|
|
removeClientLinks( clienthost );
|
|
|
|
// Remove all about the client and delete object
|
|
deleteClient( clienthost );
|
|
|
|
beepIfAllowed( 880, 150 );
|
|
beepIfAllowed( 660, 150 );
|
|
|
|
}
|
|
_RemovedClientEntities.clear();
|
|
}
|
|
|
|
|
|
NLMISC::CBitMemStream Msgin( true ); // global to avoid useless realloc
|
|
|
|
// This variable helps testing the bug that produced the warning
|
|
// "handleReceivedMsg : FERECV: client %d sent messages dated in future"
|
|
// after a Stall Mode recovery.
|
|
//CVariable<bool> BypassForceSynchronizeState("fs", "BypassForceSynchronizeState", "", false);
|
|
|
|
|
|
/*
|
|
* handleReceivedMsg
|
|
*
|
|
* clienthost is NULL when handle message from a new client, in this case the first CAction *must* be a CActionLogin with a valid cookie
|
|
*
|
|
*/
|
|
void CFeReceiveSub::handleReceivedMsg( CClientHost *clienthost )
|
|
{
|
|
// Preconditions
|
|
//nlassert( _History );
|
|
//nlassert( _CurrentInMsg && (! _CurrentInMsg->data().empty()) );
|
|
//nlinfo( "RECEIVING NOW (%u)", CTickEventHandler::getGameCycle() );
|
|
|
|
// Prepare message to read
|
|
uint32 currentsize = _CurrentInMsg->userSize();
|
|
//nlinfo( "currentsize: %u", currentsize );
|
|
|
|
memcpy( Msgin.bufferToFill( currentsize ), _CurrentInMsg->userDataR(), currentsize );
|
|
|
|
try
|
|
{
|
|
uint32 receivednumber;
|
|
bool systemMode;
|
|
uint32 sendnumberack;
|
|
uint32 ackbitmask;
|
|
|
|
// Read received number and mode bit
|
|
Msgin.serialAndLog1( receivednumber );
|
|
Msgin.serialAndLog1( systemMode );
|
|
|
|
// for debug purpose
|
|
/*if (receivednumber < 1000)
|
|
nlinfo("FSRCV-DBG: received packet %d (%s) from client %d %s", receivednumber, systemMode ? "SYSTEM" : "NORMAL", clienthost ? clienthost->clientId() : -1, clienthost ? clienthost->eId().toString().c_str() : "<Unknown>");*/
|
|
|
|
// Check incoming client login
|
|
if (clienthost == NULL)
|
|
{
|
|
MEM_DELTA_MULTI_LAST2(ReceivedMsg,AddClient);
|
|
if ( ! _AccessOpen || ! CFrontEndService::instance()->AcceptClients )
|
|
{
|
|
rejectReceivedMessage( AccessClosed );
|
|
return;
|
|
}
|
|
|
|
CLoginCookie lc;
|
|
bool validCookie = false;
|
|
string result;
|
|
|
|
uint8 code = 255;
|
|
|
|
if ( systemMode )
|
|
Msgin.serialAndLog1(code);
|
|
|
|
if ( (!systemMode) || (code != SYSTEM_LOGIN_CODE) )
|
|
{
|
|
rejectReceivedMessage( NotSystemLoginCode );
|
|
return;
|
|
}
|
|
|
|
Msgin.serial(lc);
|
|
|
|
string userName = "NotSet";
|
|
string userPriv;
|
|
string userExtended;
|
|
uint32 instanceId, charSlot;
|
|
//if ( removeFromReconnectList( lc.getUserId() ) )
|
|
// validCookie = true;
|
|
//else
|
|
result = CLoginServer::isValidCookie (lc, userName, userPriv, userExtended, instanceId, charSlot);
|
|
validCookie = result.empty();
|
|
if ( ! validCookie )
|
|
{
|
|
// Not a valid client
|
|
rejectReceivedMessage( BadCookie, lc.getUserId() );
|
|
return;
|
|
}
|
|
|
|
uint32 uid;
|
|
if ( lc.getUserId() == 0xDEADBEEF )
|
|
{
|
|
// The client has neither been authenticated nor provided a user id, but is allowed to connect (dev mode)
|
|
nlinfo ("%s using AutoAllocUserid", _CurrentInMsg->AddrFrom.asString().c_str() );
|
|
string filename = CPath::standardizePath( SaveShardRootGameShare.get() ) + CPath::standardizePath( IService::getInstance()->SaveFilesDirectory.get() ) + "auto_uid_map.bin";
|
|
|
|
// Get previously allocated user ids
|
|
if ( _AutoUidMap.empty() )
|
|
{
|
|
// Load from file
|
|
try
|
|
{
|
|
CIFile file( filename );
|
|
sint v = file.serialVersion( 0 );
|
|
file.serialCont( _AutoUidMap );
|
|
}
|
|
catch (const Exception&)
|
|
{
|
|
nlinfo( "No AutoAllocUserid data found yet" );
|
|
}
|
|
|
|
// Init CurrentAutoAllocUserid
|
|
TUid maxUid = 0;
|
|
for ( TAutoUidMap::const_iterator itaum=_AutoUidMap.begin(); itaum!=_AutoUidMap.end(); ++itaum )
|
|
{
|
|
if ( (*itaum).second > maxUid )
|
|
maxUid = (*itaum).second;
|
|
}
|
|
CurrentAutoAllocUserid = std::max( BaseAutoAllocUserid, maxUid + 1 );
|
|
}
|
|
|
|
// Look up the address
|
|
TAutoUidMap::iterator itaum = _AutoUidMap.find( _CurrentInMsg->AddrFrom.internalIPAddress() );
|
|
if ( itaum != _AutoUidMap.end() )
|
|
{
|
|
// This ip address is already known: if not already connected, give the same user id back.
|
|
// (two clients either on the same machine or connected through a gateway won't get the same user id)
|
|
uid = (*itaum).second;
|
|
while ( findClientHostByUid( uid ) != NULL )
|
|
{
|
|
uid = CurrentAutoAllocUserid++;
|
|
}
|
|
(*itaum).second = uid; // store the new user id if the previous one is not available
|
|
}
|
|
else
|
|
{
|
|
// This ip address is new to us, give a new user id and store it
|
|
do { uid = CurrentAutoAllocUserid++; }
|
|
while ( findClientHostByUid( uid ) != NULL );
|
|
_AutoUidMap.insert( std::make_pair( _CurrentInMsg->AddrFrom.internalIPAddress(), uid ) );
|
|
|
|
}
|
|
|
|
// Save the allocated user ids
|
|
try
|
|
{
|
|
COFile file( filename );
|
|
file.serialVersion( 0 );
|
|
file.serialCont( _AutoUidMap );
|
|
}
|
|
catch (const Exception &e)
|
|
{
|
|
nlwarning( "Could not save AutoAllocUserid data: %s", e.what() );
|
|
}
|
|
}
|
|
else
|
|
uid = lc.getUserId();
|
|
|
|
string languageId;
|
|
Msgin.serial(languageId);
|
|
|
|
// ALWAYS REMOVE CLIENT FROM LIMBO!
|
|
LimboClients.erase(uid);
|
|
|
|
clienthost = addClient( _CurrentInMsg->AddrFrom, uid, userName, userPriv, userExtended, languageId, lc, instanceId, (uint8)charSlot );
|
|
|
|
// Check if the addition worked
|
|
if ( clienthost != NULL )
|
|
{
|
|
nldebug( "FERECV: received LOGIN(%s) from client %d", lc.toString().c_str(), clienthost->clientId() );
|
|
|
|
NLMISC::TGameCycle tick = CTickEventHandler::getGameCycle();
|
|
clienthost->setFirstSentPacket( clienthost->sendNumber()+1, tick );
|
|
clienthost->setSynchronizeState();
|
|
|
|
computeStats( clienthost, receivednumber, currentsize, false );
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Take current time as receive timestamp
|
|
clienthost->setReceiveTimeNow();
|
|
|
|
// Check system messages
|
|
if ( systemMode )
|
|
{
|
|
H_AUTO(CheckSystemMsgs);
|
|
uint8 code;
|
|
Msgin.serialAndLog1(code);
|
|
|
|
if (code == SYSTEM_DISCONNECTION_CODE)
|
|
{
|
|
H_AUTO(SystemDisconnectionCode);
|
|
// Make stats and set client's receive number
|
|
// and don't acknowledge packet number because this is a system message
|
|
computeStats( clienthost, receivednumber, currentsize, false );
|
|
|
|
nlinfo( "FERECV: Client %u is disconnecting", clienthost->clientId() );
|
|
removeFromRemoveList( clienthost->clientId() );
|
|
// false because the client, in this case, didn't crashed
|
|
removeClientById( clienthost->clientId(), false );
|
|
// actions are automatically removed when deleting blocks
|
|
return;
|
|
}
|
|
|
|
if (code == SYSTEM_QUIT_CODE)
|
|
{
|
|
H_AUTO(SystemQuitCode);
|
|
uint32 quitId;
|
|
|
|
Msgin.serialAndLog1(quitId);
|
|
|
|
nlinfo("Received QUIT %d from client %d (current client QuitId = %d)", quitId, clienthost->clientId(), clienthost->QuitId);
|
|
|
|
if (quitId > clienthost->QuitId)
|
|
{
|
|
clienthost->QuitId = quitId;
|
|
|
|
// Make stats and set client's receive number
|
|
// and don't acknowledge packet number because this is a system message
|
|
computeStats( clienthost, receivednumber, currentsize, false );
|
|
|
|
CFrontEndService::instance()->sendSub()->disableSendBuffer( clienthost->clientId() );
|
|
|
|
// Send disconnection to EGS...
|
|
CMessage msgout1( "CL_DISCONNECT" );
|
|
msgout1.serial( clienthost->Uid );
|
|
bool crashed = false;
|
|
msgout1.serial( crashed );
|
|
sendMessageViaMirror( "EGS", msgout1 );
|
|
|
|
// ...and then a new connection to EGS
|
|
CMessage msgout2( "CL_CONNECT" );
|
|
msgout2.serial( clienthost->Uid );
|
|
msgout2.serial( clienthost->UserName );
|
|
msgout2.serial( clienthost->UserPriv );
|
|
msgout2.serial( clienthost->UserExtended );
|
|
msgout2.serial( clienthost->LanguageId );
|
|
string addrStr = clienthost->address().asIPString();
|
|
msgout2.serial( addrStr );
|
|
msgout2.serial( const_cast<CInetAddress&>(clienthost->address()) );
|
|
msgout2.serial( clienthost->LoginCookie );
|
|
sendMessageViaMirror( "EGS", msgout2 );
|
|
|
|
nlinfo( "Client %hu (uid %u) reset", clienthost->clientId(), clienthost->Uid );
|
|
|
|
// Put client aside, then remove it
|
|
setClientInLimboMode( clienthost );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// Make stats and set client's receive number
|
|
// and don't acknowledge packet number because this is a system message
|
|
computeStats( clienthost, receivednumber, currentsize, false );
|
|
|
|
// Skip double messages or swapped packets
|
|
if ( receivednumber < clienthost->receiveNumber()+1 )
|
|
{
|
|
nldebug("FERECV: Received an old packet from client %u, expected %d, received %d", clienthost->clientId(), clienthost->receiveNumber()+1, receivednumber);
|
|
computeStats( clienthost, receivednumber, currentsize, false );
|
|
return;
|
|
}
|
|
else if ( (receivednumber > clienthost->receiveNumber()+1) && (!systemMode) )
|
|
{
|
|
LOG_PACKET_LOST("FERECV: Lost packet(s) from client %u, expected %d, received %d", clienthost->clientId(), clienthost->receiveNumber()+1, receivednumber);
|
|
}
|
|
|
|
{
|
|
// if (BypassForceSynchronizeState.get() == 1)
|
|
// {
|
|
// if ((code == SYSTEM_ACK_SYNC_CODE) && (clienthost->ConnectionState == CClientHost::ForceSynchronize))
|
|
// {
|
|
// clienthost->setSynchronizeState();
|
|
// nldebug("Accepting Ack_Sync during Stall Recovery");
|
|
// }
|
|
// }
|
|
|
|
H_AUTO(SwitchConnectionState);
|
|
switch (clienthost->ConnectionState)
|
|
{
|
|
case CClientHost::Synchronize:
|
|
//case CClientHost::ForceSynchronize: // in ForceSynchronize, don't let Synchronize be replaced by Connected (ignore this ack_sync, subsequent synchronization will bring a new ack_sync)
|
|
// in synchronize, only accept ACK_SYNC
|
|
if (code == SYSTEM_ACK_SYNC_CODE)
|
|
{
|
|
H_AUTO(SystemAckSyncCode);
|
|
//nldebug("FERECV: received ACK_SYNC from client %d", clienthost->clientId());
|
|
clienthost->setConnectedState();
|
|
|
|
// decode long ack
|
|
uint32 frontack;
|
|
uint32 backack;
|
|
CBitSet longackbitfield;
|
|
uint32 syncCode;
|
|
|
|
Msgin.serialAndLog1(frontack);
|
|
Msgin.serialAndLog1(backack);
|
|
// use this bitfield as a rolling buffer, as long as frontack-backack < NumBitsInLongAck
|
|
Msgin.serial(longackbitfield);
|
|
|
|
Msgin.serialAndLog1(syncCode);
|
|
|
|
// check the received sync is correct
|
|
if (syncCode == clienthost->LastSentSync)
|
|
{
|
|
// process ack
|
|
uint ack = std::max(backack, clienthost->LastReceivedAck+1);
|
|
//nlinfo("BEN-DBG: ACK_SYNC, start reading longackbitfield from client %d", clienthost->clientId());
|
|
for (; ack<=frontack; ++ack)
|
|
{
|
|
bool ackvalue = longackbitfield[ack&(NumBitsInLongAck-1)];
|
|
//nlinfo("BEN-DBG: ACK_SYNC, ack %d: %s", ack, (ackvalue ? "true" : "false"));
|
|
_History->ack(clienthost->clientId(), ack, ackvalue);
|
|
if (ackvalue)
|
|
{
|
|
clienthost->ImpulseEncoder.ack(ack);
|
|
}
|
|
}
|
|
//nlinfo("BEN-DBG: ACK_SYNC, end reading longackbitfield from client %d", clienthost->clientId());
|
|
|
|
clienthost->LastReceivedAck = frontack;
|
|
|
|
++clienthost->LastSentSync;
|
|
}
|
|
else
|
|
{
|
|
rejectReceivedMessage( IrrelevantSystemMessage );
|
|
}
|
|
}
|
|
else if (code != SYSTEM_LOGIN_CODE && code != SYSTEM_ACK_PROBE_CODE)
|
|
{
|
|
nlwarning("FERECV: Received from client %d (state Synchronize) system message %s (%d), expected ACK_SYNC (%d)", clienthost->clientId(), CLFECOMMON::SystemMessagesNames[code&31], code, SYSTEM_ACK_SYNC_CODE);
|
|
}
|
|
break;
|
|
|
|
case CClientHost::Probe:
|
|
// in probe, only accept ACK_PROBE
|
|
if (code == SYSTEM_ACK_PROBE_CODE)
|
|
{
|
|
H_AUTO(SystemAckProbeCode);
|
|
nldebug("FERECV: received ACK_PROBE from client %d", clienthost->clientId());
|
|
uint32 numProbes, probe;
|
|
Msgin.serialAndLog1(numProbes);
|
|
// get next received probe, and compare to the previous one
|
|
// if one step increment consecutive counter, else reset consecutive counter to 1
|
|
for (; numProbes>0; --numProbes)
|
|
{
|
|
Msgin.serialAndLog1(probe);
|
|
if (probe == clienthost->LastReceivedProbe+1)
|
|
{
|
|
nldebug("FERECV: received probe %d, OK", probe);
|
|
clienthost->NumConsecutiveProbes++;
|
|
}
|
|
else
|
|
{
|
|
nldebug("FERECV: received probe %d, expected %d", probe, clienthost->LastReceivedProbe+1);
|
|
clienthost->NumConsecutiveProbes = 1;
|
|
}
|
|
clienthost->LastReceivedProbe = probe;
|
|
}
|
|
|
|
if (clienthost->NumConsecutiveProbes >= 5)
|
|
{
|
|
nldebug("FERECV: received %d consecutive probes, resynchronize...", clienthost->NumConsecutiveProbes);
|
|
clienthost->setSynchronizeState();
|
|
// Setup SYNC for the client
|
|
NLMISC::TGameCycle tick = CTickEventHandler::getGameCycle();
|
|
clienthost->setFirstSentPacket( clienthost->sendNumber()+1, tick );
|
|
// clean up history, packets numbers aso
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nlinfo("FERECV: Received from client %d (state Probe) system message %s (%d), expected ACK_PROBE (%d)", clienthost->clientId(), CLFECOMMON::SystemMessagesNames[code&31], code, SYSTEM_ACK_PROBE_CODE);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
|
|
// if ((code == SYSTEM_ACK_SYNC_CODE) && (clienthost->ConnectionState == CClientHost::ForceSynchronize))
|
|
// nldebug("Ignoring Ack_Sync during Stall Recovery");
|
|
//rejectReceivedMessage( BadSystemCode, uid ); // occurs in normal behaviour
|
|
break;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Skip double messages or swapped packets
|
|
if ( receivednumber < clienthost->receiveNumber()+1 )
|
|
{
|
|
nldebug("FERECV: Received an old packet from client %u, expected %d, received %d", clienthost->clientId(), clienthost->receiveNumber()+1, receivednumber);
|
|
computeStats( clienthost, receivednumber, currentsize, false );
|
|
return;
|
|
}
|
|
else if ( (receivednumber > clienthost->receiveNumber()+1) && (!systemMode) )
|
|
{
|
|
LOG_PACKET_LOST("FERECV: Lost packet(s) from client %u, expected %d, received %d", clienthost->clientId(), clienthost->receiveNumber()+1, receivednumber);
|
|
}
|
|
|
|
|
|
if ( clienthost->ConnectionState != CClientHost::Connected )
|
|
{
|
|
// discard incoming normal message if client is not in connected state
|
|
//nlwarning("FERECV: Received from client %d normal message whereas state is %d", clienthost->clientId(), clienthost->ConnectionState);
|
|
// Make stats and set client's receive number
|
|
// only acknowledge packet number for normal messages in connected state
|
|
computeStats( clienthost, receivednumber, currentsize, false );
|
|
return;
|
|
}
|
|
|
|
// Read acknowledgement numbers and submit them to the history
|
|
Msgin.serialAndLog1( sendnumberack );
|
|
Msgin.serialAndLog1( ackbitmask );
|
|
|
|
/*if (receivednumber < 1000)
|
|
nlinfo("FSRCV-DBG: received normal packet %d (%s) from client %d %s, Ack=%d AckBitMask=%08X", receivednumber, systemMode ? "SYSTEM" : "NORMAL", clienthost ? clienthost->clientId() : -1, clienthost ? clienthost->eId().toString().c_str() : "<Unknown>", sendnumberack, ackbitmask);*/
|
|
|
|
list<CActionBlock> blocks;
|
|
{
|
|
H_AUTO(Unpack);
|
|
while ( (uint32)Msgin.length()*8 - (uint32)Msgin.getPosInBit() >= (uint32)CActionBlock::getHeaderSizeInBits() )
|
|
{
|
|
blocks.push_back(CActionBlock());
|
|
blocks.back().serial(Msgin);
|
|
if ( ! blocks.back().Success )
|
|
{
|
|
computeStats( clienthost, receivednumber, currentsize, false );
|
|
rejectReceivedMessage( MalformedAction, clienthost->Uid );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*if ( blocks.size() == 0 )
|
|
{
|
|
//nlwarning( "Client %s sent 0 action (hacker?)", clienthost ? clienthost->address().asIPString().c_str() : "<null>" );
|
|
// Make stats and set client's receive number
|
|
// only acknowledge packet number for good normal messages
|
|
//computeStats( clienthost, receivednumber, currentsize, false );
|
|
//return;
|
|
}*/
|
|
/* //OBSOLETE: now it is handled by a system msg SYSTEM_DISCONNECTION_CODE
|
|
// if client sends disconnection, remove it, and leave
|
|
else if (!(*blocks.begin()).Actions.empty() && (*blocks.begin()).Actions[0]->Code==ACTION_DISCONNECTION_CODE)
|
|
{
|
|
// actions are automatically removed when deleting blocks
|
|
nlinfo( "FERECV: Client %u is disconnecting", clienthost->clientId() );
|
|
removeFromRemoveList( clienthost->clientId() );
|
|
removeClientById( clienthost->clientId() );
|
|
// Make stats and set client's receive number
|
|
// only acknowledge packet number for good normal messages
|
|
computeStats( clienthost, receivednumber, currentsize, false );
|
|
return;
|
|
}*/
|
|
{
|
|
H_AUTO(ReadAcks);
|
|
// procede with acks
|
|
if (sendnumberack != 0) //clienthost->LastReceivedAck != 0xFFFFFFFF)
|
|
{
|
|
const uint AckBits = sizeof(ackbitmask)*8;
|
|
uint numAcks;
|
|
sint i = 0;
|
|
|
|
if (clienthost->LastReceivedAck != 0xFFFFFFFF)
|
|
{
|
|
numAcks = sendnumberack - clienthost->LastReceivedAck;
|
|
|
|
if (numAcks > AckBits+1)
|
|
{
|
|
// we lost too many acks !!
|
|
// must resync completely !!
|
|
// TODO: ignore all incoming packets from this host until we complete resync
|
|
nlwarning("FE:BEN: Too many ACK from client %d lost [Last=%d,Current=%d], setting to probe state", clienthost->clientId(), clienthost->LastReceivedAck, sendnumberack);
|
|
clienthost->setProbeState();
|
|
// Make stats and set client's receive number
|
|
// only acknowledge packet number for good normal messages. Here a probe issue, don't ack message
|
|
computeStats( clienthost, receivednumber, currentsize, false );
|
|
return;
|
|
}
|
|
|
|
i = -(sint)(numAcks)+1;
|
|
if (i < -(sint)(AckBits))
|
|
i = -(sint)(AckBits);
|
|
}
|
|
|
|
// scan ack bits
|
|
bool hasNegAck = false;
|
|
for (; i<=0; ++i)
|
|
{
|
|
if (i == 0 || (ackbitmask&(1<<(-1-i))) != 0)
|
|
{
|
|
// positive ack
|
|
|
|
// acknowledges sent impulses at for that packet
|
|
clienthost->ImpulseEncoder.ack(sendnumberack+i);
|
|
_History->ack(clienthost->clientId(), sendnumberack+i, true);
|
|
}
|
|
else
|
|
{
|
|
// negative ack
|
|
//nlinfo("FE:BEN: neg ACK from client %d (packet %d)", clienthost->clientId(), sendnumberack+i);
|
|
_History->ack(clienthost->clientId(), sendnumberack+i, false);
|
|
hasNegAck = true;
|
|
}
|
|
}
|
|
|
|
//if (hasNegAck)
|
|
// nlinfo("FE:BEN: Client=%d Packet=%d Ack=%d AckBits=%d LastAck=%d", clienthost->clientId(), receivednumber, sendnumberack, ackbitmask, clienthost->LastReceivedAck);
|
|
}
|
|
/*
|
|
else if (sendnumberack != 0)
|
|
{
|
|
clienthost->ImpulseEncoder.ack(sendnumberack);
|
|
_History->ack(clienthost->clientId(), sendnumberack, true);
|
|
}
|
|
*/
|
|
/*
|
|
if ( sendnumberack != 0 )
|
|
{
|
|
_History->ack( clienthost->clientId(), sendnumberack, ackbitmask, sizeof(ackbitmask)*8 );
|
|
}
|
|
*/
|
|
|
|
clienthost->LastReceivedAck = sendnumberack;
|
|
}
|
|
|
|
{
|
|
H_AUTO(Switch);
|
|
uint j;
|
|
|
|
/*static char debugdisplay[256000];
|
|
static char debugcat[128000];
|
|
debugdisplay[0] = '\0';*/
|
|
|
|
uint numActions = 0;
|
|
|
|
// Make stats and set client's receive number
|
|
// only acknowledge packet number for good normal messages
|
|
// now we can acknowledge the message safely
|
|
computeStats( clienthost, receivednumber, currentsize, true );
|
|
|
|
// Iterate on the action blocks (each block corresponds to one game cycle)
|
|
for (list<CActionBlock>::iterator it=blocks.begin(); it!=blocks.end(); ++it)
|
|
{
|
|
CActionBlock &block = (*it);
|
|
if (block.Cycle <= clienthost->LastReceivedGameCycle)
|
|
{
|
|
//sprintf(debugcat, "old-block[%d][%d actions] ", block.Cycle, block.Actions.size());
|
|
//strcat(debugdisplay, debugcat);
|
|
continue;
|
|
}
|
|
|
|
clienthost->LastReceivedGameCycle = block.Cycle;
|
|
//sprintf(debugcat, "new-block[%d][%d actions] ", block.Cycle, block.Actions.size());
|
|
//strcat(debugdisplay, debugcat);
|
|
|
|
// Iterator on the actions in a block
|
|
for (j=0; j<block.Actions.size(); ++j)
|
|
{
|
|
CAction *action = block.Actions[j];
|
|
++numActions;
|
|
|
|
switch ( action->Code )
|
|
{
|
|
case ACTION_GENERIC_CODE: // forward an impulsion
|
|
{
|
|
CActionGeneric *ag = static_cast<CActionGeneric*>( action );
|
|
//vector<uint8> &vect = ag->get().bufferAsVector ();
|
|
uint8 msgtype;
|
|
if ( clienthost->eId().isUnknownId() )
|
|
{
|
|
//nlinfo( "FERECV: Forwarding IMPULSION_UID (%d bytes) from client %u to IOS", ag->get().length(), clienthost->clientId() );
|
|
routeImpulsionUidFromClient( ag->get(), clienthost->Uid, block.Cycle );
|
|
}
|
|
else
|
|
{
|
|
//msgout.setType( "IMPULSION_ID" );
|
|
msgtype = 1;
|
|
//nlinfo( "FERECV: Forwarding IMPULSION_ID (%d bytes) from client %u to IOS", ag->get().length(), clienthost->clientId() );
|
|
routeImpulsionIdFromClient( ag->get(), clienthost->eId(), block.Cycle );
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ACTION_GENERIC_MULTI_PART_CODE:
|
|
{
|
|
CActionGenericMultiPart *agmp = static_cast<CActionGenericMultiPart*>( action );
|
|
|
|
// manage a generic action (big one that comes by blocks)
|
|
vector<uint8> &v = agmp->PartCont;
|
|
|
|
// add it
|
|
if (clienthost->GenericMultiPartTemp.size() <= agmp->Number)
|
|
clienthost->GenericMultiPartTemp.resize (agmp->Number+1);
|
|
|
|
clienthost->GenericMultiPartTemp[agmp->Number].set(agmp, clienthost);
|
|
}
|
|
break;
|
|
|
|
case ACTION_TARGET_SLOT_CODE:
|
|
{
|
|
// Convert clientid,slot to id
|
|
CActionTargetSlot *ats = static_cast<CActionTargetSlot*>(action);
|
|
TDataSetRow targetindex;
|
|
switch ( ats->Slot )
|
|
{
|
|
case INVALID_SLOT : // Untargetting
|
|
targetindex = TDataSetRow::createFromRawIndex( INVALID_DATASET_INDEX );
|
|
break;
|
|
case 0: // Self targetting
|
|
targetindex = clienthost->entityIndex();
|
|
break;
|
|
default: // Targetting an entity in the vision
|
|
targetindex = CFrontEndService::instance()->PrioSub.VisionArray.getEntityIndex( clienthost->clientId(), ats->Slot );
|
|
}
|
|
routeImpulsionIdSlotFromClient( ats->TargetOrPickup, targetindex, clienthost->entityIndex(), block.Cycle );
|
|
}
|
|
break;
|
|
|
|
// TODO: jarter
|
|
case ACTION_DUMMY_CODE:
|
|
{
|
|
CActionDummy *dummy = static_cast<CActionDummy*>(action);
|
|
if (dummy->Dummy1 - clienthost->LastDummy != 1)
|
|
{
|
|
nlwarning("******* PB: LastDummy=%d dummy->Dummy1=%d", clienthost->LastDummy, dummy->Dummy1);
|
|
}
|
|
clienthost->LastDummy = dummy->Dummy1;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
nlwarning( "Received unknown action code %d from client %d", action->Code, clienthost->clientId() );
|
|
}
|
|
}
|
|
}
|
|
|
|
if (clienthost->LastReceivedGameCycle > CTickEventHandler::getGameCycle())
|
|
{
|
|
nlwarning("FERECV: client %d sent messages dated in future (tick=%d whereas server is at %d)", clienthost->clientId(), clienthost->LastReceivedGameCycle, CTickEventHandler::getGameCycle());
|
|
}
|
|
}
|
|
}
|
|
catch(const EStreamOverflow& )
|
|
{
|
|
TUid userId = clienthost ? clienthost->Uid : 0;
|
|
rejectReceivedMessage( InsufficientSize, userId );
|
|
}
|
|
catch(const EInvalidDataStream& )
|
|
{
|
|
TUid userId = clienthost ? clienthost->Uid : 0;
|
|
rejectReceivedMessage( HackedSizeInBuffer, userId );
|
|
}
|
|
catch (const Exception& )
|
|
{
|
|
TUid userId = clienthost ? clienthost->Uid : 0;
|
|
rejectReceivedMessage( UnknownExceptionType, userId );
|
|
}
|
|
catch ( ... )
|
|
{
|
|
TUid userId = clienthost ? clienthost->Uid : 0;
|
|
rejectReceivedMessage( UnknownFormatType, userId );
|
|
}
|
|
|
|
//nlinfo("FERECV: received packet %d from client %d (%d actions decoded): %s", receivednumber, clienthost->clientId(), numActions, debugcat);
|
|
|
|
// warning: actions in CActionBlock are automatically removed when deleting block
|
|
}
|
|
|
|
|
|
/*
|
|
* Do not accept the current message (hacking detection)
|
|
*/
|
|
void CFeReceiveSub::rejectReceivedMessage( TBadMessageFormatType bmft, TUid userId )
|
|
{
|
|
//nlwarning("FEHACK: Incoming client failed identification: systemMode=%s, code=%d, isValidCookie=%s", (systemMode ? "true" : "false"), code, result.c_str());
|
|
pair<THackingAddrSet::iterator,bool> res = _UnidentifiedFlyingClients.insert( make_pair( _CurrentInMsg->AddrFrom, THackingDesc() ) );
|
|
THackingDesc& desc = (*(res.first)).second;
|
|
if ( ! res.second )
|
|
{
|
|
++desc.InvalidMsgCounter; // increment the counter
|
|
}
|
|
|
|
// Set the userId and the reasons flag
|
|
desc.UserId = userId;
|
|
desc.Reasons |= bmft;
|
|
}
|
|
|
|
|
|
/*
|
|
* Compute stats about received datagrams
|
|
*/
|
|
void CFeReceiveSub::computeStats( CClientHost *clienthost, uint32 currentcounter, uint32 currentsize, bool updateAcknowledge )
|
|
{
|
|
// Host stats
|
|
clienthost->computeHostStats( *_CurrentInMsg, currentcounter, updateAcknowledge );
|
|
|
|
// Global stats
|
|
++_RcvCounter;
|
|
_RcvBytes += currentsize;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Display datagram loss statistics
|
|
*/
|
|
void CFeReceiveSub::displayDatagramStats( TTime lastdisplay )
|
|
{
|
|
uint32 pcsum=0, pcnb=0;
|
|
THostMap::const_iterator ihm;
|
|
for ( ihm=_ClientMap.begin(); ihm!=_ClientMap.end(); ++ihm )
|
|
{
|
|
// Avoid clients that have not sent anything yet
|
|
if ( GETCLIENTA(ihm)->receiveNumber() != 0xFFFFFFFF )
|
|
{
|
|
// Calc percent
|
|
uint32 diff = GETCLIENTA(ihm)->receiveNumber()-GETCLIENTA(ihm)->firstReceiveNumber()+1;
|
|
nlassertex( diff!=0, ("receiveNumber: %u; firstReceiveNumber: %u", GETCLIENTA(ihm)->receiveNumber(), GETCLIENTA(ihm)->firstReceiveNumber()) );
|
|
pcsum += GETCLIENTA(ihm)->datagramLost() * 100 / diff;
|
|
++pcnb;
|
|
|
|
// Reset
|
|
GETCLIENTA(ihm)->resetDatagramLost();
|
|
GETCLIENTA(ihm)->firstReceiveNumber() = GETCLIENTA(ihm)->receiveNumber();
|
|
}
|
|
}
|
|
CFrontEndService *fe = CFrontEndService::instance();
|
|
float loadavg = (pcnb != 0.0f) ? ((float)pcsum/(float)pcnb) : 0.0f;
|
|
|
|
/*float reenableRatio = 0.0f;
|
|
if ( fe->PrioSub.PropDispatcher.NbSetPrio != 0 )
|
|
{
|
|
reenableRatio = (float)fe->PrioSub.PropDispatcher.NbReenable / (float)fe->PrioSub.PropDispatcher.NbSetPrio * 100.0f;
|
|
fe->PrioSub.PropDispatcher.NbSetPrio = 0;
|
|
fe->PrioSub.PropDispatcher.NbReenable = 0;
|
|
}*/
|
|
|
|
uint32 sentMsg = fe->sendSub()->sendCounter();
|
|
uint32 backendrecvtime = fe->BackEndRecvWatch1.getPartialAverage() + fe->BackEndRecvWatch2.getPartialAverage() + fe->BackEndRecvWatch3.getPartialAverage();
|
|
nlinfo( "FESTATS: Ticks: %u - In msg: %u (%u Bps) - Out msg: %u - Loss average: %.2f - Clients: %u - RBackEnd=%u (PVision=%u) User=%u Recv=%u Fill=%u - Cycle=%u ms",
|
|
CTickEventHandler::getGameCycle(),
|
|
_RcvCounter,
|
|
(_RcvBytes-_PrevRcvBytes)*1000/sint32(CTime::getLocalTime()-lastdisplay),
|
|
sentMsg,
|
|
loadavg,
|
|
_ClientMap.size(),
|
|
backendrecvtime,
|
|
fe->ProcessVisionWatch.getPartialAverage(),
|
|
fe->UserDurationPAverage,
|
|
fe->ReceiveWatch.getPartialAverage(),
|
|
fe->SendWatch.getPartialAverage(),
|
|
fe->CycleWatch.getPartialAverage() );
|
|
|
|
_PrevRcvBytes = _RcvBytes;
|
|
}
|
|
|
|
|
|
/*
|
|
* Remove from the list of clients
|
|
*/
|
|
void CFeReceiveSub::removeFromRemoveList( TClientId clientid )
|
|
{
|
|
TClientsToRemove::iterator icr;
|
|
for ( icr=_ClientsToRemove.begin(); icr!=_ClientsToRemove.end(); ++icr )
|
|
{
|
|
if ( (*icr).first == clientid )
|
|
{
|
|
_ClientsToRemove.erase( icr );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32 CFeReceiveSub::getNbClient()
|
|
{
|
|
return (uint32)_ClientMap.size();
|
|
}
|
|
|
|
|
|
/*
|
|
* Find a client host by Uid (slow)
|
|
*/
|
|
CClientHost *CFeReceiveSub::findClientHostByUid( TUid uid, bool exitFromLimbo )
|
|
{
|
|
// First, find in limbo clients
|
|
map<TUid,CLimboClient>::iterator ilc = LimboClients.find( uid );
|
|
if ( exitFromLimbo && (ilc != LimboClients.end()) )
|
|
{
|
|
CLimboClient& lc = (*ilc).second;
|
|
CClientHost *clienthost = exitFromLimboMode( lc );
|
|
//clienthost->QuitId = lc.
|
|
LimboClients.erase( ilc );
|
|
return clienthost;
|
|
}
|
|
else
|
|
{
|
|
// If not found, find in online clients
|
|
THostMap::iterator ihm;
|
|
for ( ihm=clientMap().begin(); ihm!=clientMap().end(); ++ihm )
|
|
{
|
|
if ( GETCLIENTA(ihm)->Uid == uid )
|
|
{
|
|
return GETCLIENTA(ihm);
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* |<entityId>
|
|
* If the first argument is an entity id, it is used to look-up the player (the provided dynamic id
|
|
* is ignored, you can provide 0).
|
|
* entityId not implemented, because using the 'clientByUserId' command is easier.
|
|
*/
|
|
NLMISC_COMMAND( displayPlayerInfo, "Display the properties of a player", "[<clientid> [<sortByDistance(1/0)=1> [<displayAllPropertiesSent(1/0)=0>]]]" )
|
|
{
|
|
// Get the values
|
|
if (args.size() > 0)
|
|
{
|
|
// Try to interpret the first argument as an entity id (beginning by '(')
|
|
/*CEntityId entityId;
|
|
if ( (!args[0].empty()) && (args[0][0] == '(') )
|
|
{
|
|
entityId.fromString( args[0].c_str() );
|
|
}*/
|
|
// If the first argument is not a valid entity id, interpret it as a client id
|
|
TClientId clientid;
|
|
/*if ( entityId.isUnknownId() )
|
|
{*/
|
|
NLMISC::fromString(args[0], clientid);
|
|
/*}
|
|
else
|
|
{
|
|
// Use the dynamic id of this frontend
|
|
entityId.setDynamicId( IService::getInstance()->getServiceId() );
|
|
}*/
|
|
|
|
bool sbd = true;
|
|
bool allProps = false;
|
|
if ( args.size() > 1 )
|
|
{
|
|
sbd = (args[1] == string("1"));
|
|
if ( args.size() > 2 )
|
|
allProps = (args[2] == string("1"));
|
|
}
|
|
CClientHost *clienthost;
|
|
if ( (clientid <= MaxNbClients) && ((clienthost = CFrontEndService::instance()->sendSub()->clientIdCont()[clientid]) != NULL) )
|
|
{
|
|
clienthost->displayClientProperties( true, allProps, sbd, &log );
|
|
}
|
|
else
|
|
{
|
|
log.displayNL( "There is no such a client id" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
log.displayNL( "List of all clients and their properties:" );
|
|
THostMap::iterator ihm;
|
|
for ( ihm=CFrontEndService::instance()->receiveSub()->clientMap().begin();
|
|
ihm!=CFrontEndService::instance()->receiveSub()->clientMap().end(); ++ihm )
|
|
{
|
|
GETCLIENTA(ihm)->displayClientProperties( false, false, false, &log );
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
NLMISC_COMMAND( slotToEntityId, "Get the entityid of a slot of a client (and possibly display info)", "<clientid> <slot> <0/1>" )
|
|
{
|
|
if ( args.size() < 2 )
|
|
return false;
|
|
|
|
TClientId clientid;
|
|
NLMISC::fromString(args[0], clientid);
|
|
if ( clientid <= MaxNbClients )
|
|
{
|
|
TCLEntityId slot;
|
|
NLMISC::fromString(args[1], slot);
|
|
TEntityIndex entityIndex = CFrontEndService::instance()->PrioSub.VisionArray.getEntityIndex( clientid, slot );
|
|
if ( entityIndex.getIndex() < (uint32)TheDataset.maxNbRows() )
|
|
{
|
|
CEntity* entity = TheEntityContainer->getEntity( entityIndex );
|
|
if ( entity )
|
|
{
|
|
CMirrorPropValueRO<TYPE_SHEET> propSheet( TheDataset, entityIndex, DSPropertySHEET );
|
|
log.displayNL( "Slot %hu of client C%hu is %s, sheet %u", (uint16)slot, clientid, TheDataset.getEntityId( entityIndex).toString().c_str(), (uint32)propSheet() );
|
|
if ( args.size() == 3 )
|
|
{
|
|
if ( args[2] == "1" )
|
|
{
|
|
CFrontEndService::instance()->PrioSub.VisionProvider.displayEntityInfo( *entity, entityIndex, &log );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
log.displayNL( "Unable to find entity" );
|
|
}
|
|
else
|
|
{
|
|
log.displayNL( "Found invalid entity index" );
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
NLMISC_COMMAND( displayPlayers, "display the client ids used, and short info about them", "" )
|
|
{
|
|
THostMap::iterator ihm;
|
|
for ( ihm=CFrontEndService::instance()->receiveSub()->clientMap().begin();
|
|
ihm!=CFrontEndService::instance()->receiveSub()->clientMap().end(); ++ihm )
|
|
{
|
|
GETCLIENTA(ihm)->displayShortProps(&log);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
NLMISC_COMMAND( displayShortStats, "Display some stats", "" )
|
|
{
|
|
CFrontEndService *fe = CFrontEndService::instance();
|
|
log.displayNL( "Clients: %u", fe->receiveSub()->clientMap().size() );
|
|
return true;
|
|
}
|
|
|
|
|
|
NLMISC_DYNVARIABLE( uint32, NbPlayers, "Number of connected players" )
|
|
{
|
|
// We can only read the value
|
|
if ( get )
|
|
*pointer = (uint32)CFrontEndService::instance()->receiveSub()->clientMap().size();
|
|
}
|
|
|
|
|
|
NLMISC_COMMAND( displayMonitoredClientInfo, "Display info about the monitored client", "" )
|
|
{
|
|
CFrontEndService *fe = CFrontEndService::instance();
|
|
CClientHost *client = fe->receiveSub()->clientIdCont()[fe->MonitoredClient];
|
|
if ( client )
|
|
{
|
|
client->displayClientProperties( true, false, true, &log );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
NLMISC_COMMAND( openAccess, "Open/close the access for new clients", "<1/0>" )
|
|
{
|
|
bool b, get;
|
|
if ( args.size() == 0 )
|
|
{
|
|
b = CFrontEndService::instance()->receiveSub()->accessOpen();
|
|
get = true;
|
|
}
|
|
else
|
|
{
|
|
b = (args[0] == "1");
|
|
CFrontEndService::instance()->receiveSub()->openAccess( b );
|
|
get = false;
|
|
}
|
|
log.displayNL( "Access is %s %s", get?"currently":"now", b?"open":"closed" );
|
|
return true;
|
|
}
|
|
|
|
|
|
NLMISC_COMMAND( resetAutoAllocUserid, "Reset the auto-incrementable user id (use with caution)", "" )
|
|
{
|
|
CurrentAutoAllocUserid = BaseAutoAllocUserid;
|
|
return true;
|
|
}
|
|
|
|
|
|
NLMISC_COMMAND( clientByUserId, "Get a client id by user id", "<userId>" )
|
|
{
|
|
TUid userId;
|
|
if ( args.empty() )
|
|
return false;
|
|
else
|
|
NLMISC::fromString(args[0], userId);
|
|
|
|
CClientHost *clienthost = CFrontEndService::instance()->receiveSub()->findClientHostByUid( userId );
|
|
if ( clienthost )
|
|
{
|
|
log.displayNL( "Found C%hu E%u %s %s", clienthost->clientId(), clienthost->entityIndex().getIndex(), clienthost->eId().toString().c_str(), clienthost->address().asIPString().c_str() );
|
|
}
|
|
else
|
|
{
|
|
log.displayNL( "Client not found" );
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
NLMISC_COMMAND( dumpClientInfo, "Dump all client info into a file", "<fileName> <clientid> [<sortByDistance(1/0)=1>]" )
|
|
{
|
|
string filename;
|
|
TClientId clientid;
|
|
bool sbd = true;
|
|
if ( args.size() < 2 )
|
|
return false;
|
|
else
|
|
{
|
|
filename = args[0];
|
|
NLMISC::fromString(args[1], clientid);
|
|
if ( args.size() > 2 )
|
|
sbd = (args[2]==string("1"));
|
|
}
|
|
|
|
CClientHost *clienthost;
|
|
if ( (clientid <= MaxNbClients) && ((clienthost = CFrontEndService::instance()->sendSub()->clientIdCont()[clientid]) != NULL) )
|
|
{
|
|
CFileDisplayer *fd = new CFileDisplayer( filename, false, filename.c_str() );
|
|
CLog flog( CLog::LOG_NO );
|
|
flog.addDisplayer( fd );
|
|
clienthost->displayClientProperties( true, true, sbd, &flog );
|
|
delete fd;
|
|
log.displayNL( "File %s saved with info of C%hu", filename.c_str(), clientid );
|
|
// TODO: send the same command to the client
|
|
}
|
|
else
|
|
{
|
|
log.displayNL( "There is no such a client id" );
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
NLMISC_COMMAND( retrieveEntityNames, "Request the IOS to send the names of all online entities, for next calls to other commands", "" )
|
|
{
|
|
CMessage msgout( "RETR_ENTITY_NAMES" );
|
|
CUnifiedNetwork::getInstance()->send( "IOS", msgout );
|
|
return true;
|
|
}
|
|
|
|
|
|
NLMISC_COMMAND( clearEntityNames, "Clear the entity names after retrieveEntityNames", "" )
|
|
{
|
|
CFrontEndService::instance()->EntityNames.clear();
|
|
return true;
|
|
}
|
|
|
|
|
|
NLMISC_COMMAND( verbosePacketLost, "Turn on/off or check the state of verbose logging of packet lost", "" )
|
|
{
|
|
if ( args.size() == 1 )
|
|
{
|
|
if ( args[0] == string("on") || args[0] == string("1") )
|
|
verbosePacketLost=true;
|
|
else if ( args[0] == string("off") || args[0] == string("0") )
|
|
verbosePacketLost=false;
|
|
}
|
|
|
|
log.displayNL( "verbosePacketLost is %s", verbosePacketLost ? "on" : "off" );
|
|
return true;
|
|
}
|