// Ryzom - MMORPG Framework
// 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 .
#include "stdpch.h"
#include "client_host.h"
#include "frontend_service.h"
#include "game_share/entity_types.h" // required for ifdef
#include "id_impulsions.h"
#include "uid_impulsions.h"
using namespace std;
using namespace NLMISC;
using namespace CLFECOMMON;
#ifdef HALF_FREQUENCY_SENDING_TO_CLIENT
//#pragma message ("HALF_FREQUENCY_SENDING_TO_CLIENT")
#else
//#pragma message ("FULL_FREQUENCY_SENDING_TO_CLIENT")
#endif
/**
* Comparison functor
* Helps sorting by distance the entities seen by a client, by priority
*/
struct TComparePairsByDistance
{
TComparePairsByDistance( CVisionArray *va, TClientId clientId ) : _VisionArray(va), _ClientId(clientId) {}
bool operator() ( CLFECOMMON::TCLEntityId first, CLFECOMMON::TCLEntityId second )
{
return ( _VisionArray->getPairState( _ClientId, first ).DistanceCE < _VisionArray->getPairState( _ClientId, second ).DistanceCE );
}
TClientId _ClientId;
CVisionArray *_VisionArray;
};
// get Pair state
inline TPairState& CClientHost::getPairState(TCLEntityId e)
{
return CFrontEndService::instance()->PrioSub.VisionArray.getPairState( _ClientId, e );
}
// get Pair state
inline const TPairState& CClientHost::getPairState(TCLEntityId e) const
{
return CFrontEndService::instance()->PrioSub.VisionArray.getPairState( _ClientId, e );
}
/*
* Prepare a clean new outbox with current values
*
*/
void CClientHost::setupOutBox( TOutBox& outbox )
{
// only fill outbox headers if connected
// in system mode, fill is done manually because packets are not sent at each update
nlassert ( ConnectionState == Connected );
// a. Add the send number belonging to the destination client
uint32 sendnumber = getNextSendNumber();
outbox.serialAndLog1( sendnumber );
// b. System bit
bool systemMode = false;
outbox.serialBitAndLog( systemMode );
// b. Add the latest receive number belonging to the destination client
outbox.serialAndLog1( _ReceiveNumber );
// c. Add the "toggle" bit for important actions
//OutBox.serialBit( _ToggleBit );
}
/*
* Prepare a clean system header
*/
void CClientHost::setupSystemHeader( TOutBox& outbox, uint8 code)
{
// Only setup header for special states such as Synchronize, Probe...
nlassert(ConnectionState != Connected);
// checks the outbox is really cleared
nlassert(outbox.length() == 0);
// a. Add the send number belonging to the destination client
uint32 sendnumber = getNextSendNumber();
outbox.serialAndLog1( sendnumber );
// b. System bit
bool systemMode = true;
outbox.serialBitAndLog( systemMode );
// c. System message code
outbox.serialAndLog1( code );
}
/*
* Set receive time now
*/
void CClientHost::setReceiveTimeNow()
{
_ReceiveTime = CTime::getLocalTime();
}
/*
* Initialize the client bandwidth
*/
void CClientHost::initClientBandwidth()
{
setClientBandwidth( CFrontEndService::instance()->sendSub()->clientBandwidth() );
}
/*
* CClientHost: Compute host stats
*/
void CClientHost::computeHostStats( const TReceivedMessage& msgin, uint32 currentcounter, bool updateAcknowledge )
{
if ( _ReceiveNumber == 0xFFFFFFFF )
{
_FirstReceiveNumber = currentcounter;
if (updateAcknowledge)
_ReceiveNumber = currentcounter;
}
else if ( currentcounter <= _ReceiveNumber )
{
++_DatagramRepeated;
}
else if ( currentcounter > _ReceiveNumber+1 )
{
_DatagramLost += currentcounter-(_ReceiveNumber+1);
if (updateAcknowledge)
_ReceiveNumber = currentcounter;
}
else if (updateAcknowledge)
{
_ReceiveNumber = currentcounter;
}
}
/*
*
*/
const char *associationStateToString( uint8 as )
{
switch( as )
{
case TPairState::UnusedAssociation: return "Unused"; break;
case TPairState::AwaitingAssAck: return "Assocn"; break;
case TPairState::NormalAssociation: return "Normal"; break;
case TPairState::AwaitingDisAck: return "Disass"; break;
/*
// CHANGED BEN
case CClientEntityIdTranslator::CEntityInfo::UnusedAssociation : return "Unused"; break;
case CClientEntityIdTranslator::CEntityInfo::AwaitingAssAck : return "Assocn"; break;
case CClientEntityIdTranslator::CEntityInfo::NormalAssociation : return "Normal"; break;
case CClientEntityIdTranslator::CEntityInfo::AwaitingDisAck : return "Disass"; break;
*/
/*case CClientEntityIdTranslator::CEntityInfo::SubstitutionBeforeDisAck : return "Substitution before dis ack"; break;
case CClientEntityIdTranslator::CEntityInfo::SubstitutionAfterDisAck : return "Substitution after dis ack"; break;
case CClientEntityIdTranslator::CEntityInfo::CancelledSubstitution : return "Cancelled substitution"; break;*/
default: return "INVALID ASSOCIATION CODE";
}
}
inline std::string getUserName( const TEntityIndex& entityIndex )
{
string name;
if ( entityIndex.isValid() )
{
TClientId clientId = CFrontEndService::instance()->receiveSub()->EntityToClient.getClientId( entityIndex );
if ( clientId != INVALID_CLIENT )
{
CClientHost *client = CFrontEndService::instance()->receiveSub()->clientIdCont()[clientId];
if ( client )
{
name = string(" ") + client->UserName;
}
}
}
return name;
}
/*
* display nlinfo
*/
void CClientHost::displayClientProperties( bool full, bool allProps, bool sortByDistance, NLMISC::CLog *log ) const
{
// General properties
bool invision = false;
TSheetId sheetId = INVALID_SHEETID;
string sheetIdS = "_";
CEntity *sentity = NULL;
if ( (_EntityIndex.isValid()) && (_EntityIndex.getIndex() < (uint32)TheDataset.maxNbRows()) )
{
sentity = TheEntityContainer->getEntity( _EntityIndex );
if ( sentity )
{
invision = true;
CMirrorPropValueRO propSheet( TheDataset, _EntityIndex, DSPropertySHEET );
sheetId = propSheet();
if ( sentity->propertyIsInitialized( PROPERTY_SHEET, DSPropertySHEET, _EntityIndex, (TYPE_SHEET*)NULL ) )
{
sheetIdS = CSheetId(sheetId).toString();
}
}
}
const char *notset = "(not set)";
log->displayNL( "C%hu E%s %s %s sheet %s Uid %u, %s, %s", _ClientId, _EntityIndex.isValid() ? toString("%u", _EntityIndex.getIndex()).c_str() : notset, _Id.isUnknownId() ? notset : _Id.toString().c_str(), getEntityName( _EntityIndex ).c_str(), (sheetId!=~0) ? toString( "%s (%u)", sheetIdS.c_str(), sheetId).c_str() : notset, Uid, UserName.c_str(), invision?"in vision":"not in vision" );
if ( sentity )
{
log->displayNL( "Position (m): %d %d %d - Local: %d %d %d - Mode: %s", sentity->posXm( _EntityIndex ), sentity->posYm( _EntityIndex ), sentity->posZm( _EntityIndex ), sentity->posLocalXm( _EntityIndex ), sentity->posLocalYm( _EntityIndex ), sentity->posLocalZm( _EntityIndex ), (sentity->z( _EntityIndex )&0x1)?"Relative":"Absolute" );
}
log->displayNL( "Host %s, %s, %s", _Address.asString().c_str(), _Disconnected?"disconnected":"connected", _Synchronized?"synchronized":"not synchronized" );
log->displayNL( "Latest activity %u ms ago, state %s", (uint32)(CTime::getLocalTime()-_ReceiveTime),
ConnectionState==Synchronize?"SYNC":ConnectionState==Connected?"CONN":ConnectionState==Probe?"PROBE":ConnectionState==ForceSynchronize?"FORCE_SYNC":"DISC" );
#ifdef HALF_FREQUENCY_SENDING_TO_CLIENT
sint freq = 5;
#else
sint freq = 10;
#endif
if ( _EntityIndex.isValid())
{
CMirrorPropValueRO availableImpulseBitsize( TheDataset, _EntityIndex, DSFirstPropertyAvailableImpulseBitSize );
log->displayNL( "Bit bandwidth usage feeding client at %d Hz: %d bps (max 13312), current throttle %d; including impulsion %d, database throttle %d",
freq, _BitBandwidthUsageAvg*freq, getCurrentThrottle()*freq, _BitImpulsionUsageAvg*freq, availableImpulseBitsize*freq );
// Impulsion stats
log->displayNL( "Nb messages in impulse queues: %u %u %u", ImpulseEncoder.queueSize(0), ImpulseEncoder.queueSize(1), ImpulseEncoder.queueSize(2) );
}
if ( full )
{
log->displayNL( "Vision slots (%hu free):", NbFreeEntityItems );
// Client sees *
vector slots;
sint e;
for ( e=0; e!=MAX_SEEN_ENTITIES_PER_CLIENT; ++e )
{
//uint8 assState;
//if ( (assState = CFrontEndService::instance()->PrioSub.VisionArray.getAssociationState( this, (TCLEntityId)e )) != CClientEntityIdTranslator::CEntityInfo::UnusedAssociation ) // CHANGED BEN
if ( getPairState( (TCLEntityId)e ).AssociationState != TPairState::UnusedAssociation )
{
slots.push_back( e );
}
}
if ( sortByDistance )
{
sort( slots.begin(), slots.end(), TComparePairsByDistance( &(CFrontEndService::instance()->PrioSub.VisionArray), _ClientId ) );
}
vector::iterator islot;
for ( islot=slots.begin(); islot!=slots.end(); ++islot )
{
displaySlotProperties( *islot, allProps, log );
}
// Client is seen by * on this front-end
/*if ( (_EntityIndex.isValid()) && (_EntityIndex < TheDataSet.maxNbRows()) )
{
log->displayNL( "Visibility of this entity by other clients on this front-end:" );
TObserverList::const_iterator iol;
for ( iol= CFrontEndService::instance()->PrioSub.VisionProvider.observerList( _EntityIndex ).begin();
iol!=CFrontEndService::instance()->PrioSub.VisionProvider.observerList( _EntityIndex ).end();
++iol )
{
log->displayNL( "* Seen by client %hu using slot %hu", (*iol).ClientId, (uint16)(*iol).Slot );
}
}
else
{
log->displayNL( "Cannot access the observer list" );
}*/
TheEntityContainer->mirrorInstance().displayRows( _Id, *log );
}
}
/*
* display nlinfo for one slot
*/
void CClientHost::displaySlotProperties( CLFECOMMON::TCLEntityId e, bool full, NLMISC::CLog *log ) const
{
//const CClientEntityIdTranslator::CEntityInfo& info = IdTranslator.getInfo( (TCLEntityId)e );
TEntityIndex seenEntityIndex = getPairState( e ).EntityIndex;
if ( ! seenEntityIndex.isValid() )
return;
CEntity *seenEntity = TheEntityContainer->getEntity( seenEntityIndex );
CAction::TValue vlsx, vlsy, vlsz;
CFrontEndService::instance()->history()->getPosition( _ClientId, e, vlsx, vlsy, vlsz ); // always the absolute pos
TCoord lsx = ((sint32)vlsx)/1000, lsy = ((sint32)vlsy)/1000, lsz = ((sint32)vlsz)/1000;
TCoord sentMileage = CFrontEndService::instance()->history()->getMileage( _ClientId, e );
TCoord mileageDelta = seenEntity->Mileage - sentMileage;
const TPairState& pairState = getPairState( e );
string sheetId, sheetIdS;
if ( seenEntity->propertyIsInitialized( PROPERTY_SHEET, DSPropertySHEET, seenEntityIndex, (TYPE_SHEET*)NULL ) )
{
CMirrorPropValueRO propSheet( TheDataset, seenEntityIndex, DSPropertySHEET );
sheetId = toString("%u", propSheet() );
sheetIdS = CSheetId(propSheet()).toString();
}
else
{
sheetId = "_";
sheetIdS = "_";
}
const CEntityId& seenEntityId = TheDataset.getEntityId( seenEntityIndex );
log->displayNL( "* Slot %d E%u %s %s st%s %s sheet %s (%s) dist(m) %0.1f %s pos %d %d %d, sent %d %d %d %sdelta %d prio %.1f ab %hu",
e,
seenEntityIndex.getIndex(),
(seenEntityId.getType()==RYZOMID::player)?(string("PLAYER")+getUserName(seenEntityIndex)).c_str():RYZOMID::toString( (RYZOMID::TTypeId)seenEntityId.getType() ).c_str(),
seenEntityId.toString().c_str(),
associationStateToString( getPairState( e ).AssociationState),
getEntityName(seenEntityIndex).c_str(),
sheetId.c_str(),
sheetIdS.c_str(),
(float)pairState.DistanceCE/1000.0f,
getDirection( seenEntity, seenEntityIndex ),
seenEntity->posXm( seenEntityIndex ),
seenEntity->posYm( seenEntityIndex ),
seenEntity->posZm( seenEntityIndex ),
lsx, lsy, lsz,
(sentMileage==0)?"(not sent yet) ":"", mileageDelta,
pairState.getPrio(), (uint16)(pairState.AssociationChangeBits & 0x3) );
if ( full )
{
log->displayNL( "| Visual properties of E%u: ", seenEntityIndex.getIndex() );
seenEntity->displayProperties( seenEntityIndex, log, _ClientId, e );
}
}
/*
* Return the cardinal direction from the player to the seen entity
*/
const char * CClientHost::getDirection( CEntity *seenEntity, const TEntityIndex& seenEntityIndex ) const
{
if ( !_EntityIndex.isValid() )
return "-";
CEntity *entity = TheEntityContainer->getEntity( _EntityIndex );
if ( entity == NULL )
return "-";
float dx = (float)(seenEntity->X() - entity->X());
float dy = (float)(seenEntity->Y() - entity->Y());
float angle = (float)fmod( atan2(dy, dx) + (2*Pi), 2*Pi );
uint direction = (uint) ( 8.0f*angle/((float)Pi) );
nlassert ( direction<16 );
const char * txts[] =
{
"E",
"ENE",
"NE",
"NNE",
"N",
"NNW",
"NW",
"WNW",
"W",
"WSW",
"SW",
"SSW",
"S",
"SSE",
"SE",
"ESE",
};
return txts[direction];
}
/*
* display nlinfo (1 line only)
*/
void CClientHost::displayShortProps(NLMISC::CLog *log) const
{
// General properties
bool invision = false;
if ( (_EntityIndex.isValid()) && (_EntityIndex.getIndex() < (uint32)TheDataset.maxNbRows()) )
{
CEntity *sentity = TheEntityContainer->getEntity( _EntityIndex );
if ( sentity != NULL )
invision = true;
}
log->displayNL( "Client %hu: E%u %s '%s' uid %u '%s' %s, %s", _ClientId, _EntityIndex.getIndex(), _Id.toString().c_str(), getEntityName(_EntityIndex).c_str(), Uid, UserName.c_str(), invision?"in vision":"not in vision", _Address.asString().c_str() );
}
/*
* Set the CEntityId
*/
void CClientHost::setEId( const NLMISC::CEntityId& assigned_id )
{
_Id = assigned_id;
}
/*
* Destructor
*/
CClientHost::~CClientHost()
{
//REMOVE_PROPERTY_FROM_EMITER( _Id, uint16, "AvailImpulseBitsize" );
//_Id = CEntityId::Unknown;
}
/*
* Set the entity index
*/
void CClientHost::setEntityIndex( const TEntityIndex& ei )
{
_EntityIndex = ei;
CFrontEndService::instance()->PrioSub.VisionArray.setEntityIndex( _ClientId, 0, ei );
}
void CClientHost::CGenericMultiPartTemp::set (CActionGenericMultiPart *agmp, CClientHost *client)
{
if (NbBlock == 0xFFFFFFFF)
{
// new GenericMultiPart
NbBlock = agmp->NbBlock;
NbCurrentBlock = 0;
TempSize = 0;
Temp.clear();
Temp.resize(NbBlock);
BlockReceived.resize(NbBlock);
for (uint i = 0; i < NbBlock; i++)
BlockReceived[i] = false;
}
nlassert (NbBlock == agmp->NbBlock);
nlassert (NbBlock > agmp->Part);
// check if the block was already received
if (BlockReceived[agmp->Part])
{
return;
}
Temp[agmp->Part] = agmp->PartCont;
BlockReceived[agmp->Part] = true;
NbCurrentBlock++;
TempSize += (uint32)agmp->PartCont.size();
if (NbCurrentBlock == NbBlock)
{
// reform the total action
CBitMemStream bms(true);
uint8 *ptr = bms.bufferToFill (TempSize);
for (uint i = 0; i < Temp.size (); i++)
{
memcpy (ptr, &(Temp[i][0]), Temp[i].size());
ptr += Temp[i].size();
}
NbBlock = 0xFFFFFFFF;
if ( client->eId().isUnknownId() )
{
routeImpulsionUidFromClient(bms, client->Uid, client->LastReceivedGameCycle);
}
else
{
routeImpulsionIdFromClient(bms, client->eId(), client->LastReceivedGameCycle);
}
}
}
void CClientHost::resetClientVision()
{
// Get all entities seen by this client
sint e;
for ( e=0; e!=MAX_SEEN_ENTITIES_PER_CLIENT; ++e )
{
TEntityIndex entityindex = getPairState( (TCLEntityId)e ).EntityIndex;
//if ( CFrontEndService::instance()->PrioSub.VisionArray.getAssociationState( this, (TCLEntityId)e ) != CClientEntityIdTranslator::CEntityInfo::UnusedAssociation ) // CHANGED BEN
if (getPairState( (TCLEntityId)e ).AssociationState != TPairState::UnusedAssociation )
{
// Remove from observer list: the clients who see the entity (and its ceid for them)
/*if ( entityindex != INVALID_ENTITY_INDEX )
{
CFrontEndService::instance()->PrioSub.VisionProvider.removeFromObserverList( entityindex, clienthost->clientId(), (TCLEntityId)e );
}
else
{
nlwarning( "Invalid entity index while trying to remove slot %hu from observer list of leaving client %hu", (uint16)e, clienthost->clientId() );
}*/
// Remove pair from history
CFrontEndService::instance()->history()->removeEntityOfClient( e, clientId() );
// No need to remove Id because they are stored in the client object, which will be deleted
}
}
CFrontEndService::instance()->PrioSub.Prioritizer.removeAllEntitiesSeenByClient( clientId() );
// Reset items
for ( e=0; e!=MAX_SEEN_ENTITIES_PER_CLIENT; ++e )
{
// Reset EntityIndex
getPairState(e).resetItem();
getPairState(e).resetAssociation();
}
}