1463 lines
36 KiB
C++
1463 lines
36 KiB
C++
//////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Game Source Code
|
|
//
|
|
// File: XServerSlot.cpp
|
|
// Description: XServerSlot implementation.
|
|
//
|
|
// History:
|
|
// - August 3, 2001: Created by Alberto Demichelis
|
|
//
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
#include "stdafx.h"
|
|
#include "XServerSlot.h"
|
|
#include "XSystemServer.h"
|
|
|
|
#include "XPlayer.h"
|
|
#include "XVehicle.h"
|
|
#include "Spectator.h" // CSpectator
|
|
#include "AdvCamSystem.h" // CAdvCamSystem
|
|
#include "StreamData.h" // CStreamData_WorldPos
|
|
#include "PlayerSystem.h"
|
|
#include <functional>
|
|
#include "ScriptObjectServerSlot.h"
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////
|
|
CXServerSlot::CXServerSlot(CXServer *pParent, IServerSlot *pSlot)
|
|
{
|
|
m_fLastClientStringTime=0;
|
|
m_wPlayerId = INVALID_WID;
|
|
m_bXServerSlotGarbage = false;
|
|
m_bCanSpawn = false;
|
|
m_bWaitingForContextReady=false;
|
|
m_bLocalHost = false;
|
|
m_pLog=pParent->m_pGame->m_pLog;
|
|
// init the interface with the given pointer
|
|
m_pISSlot = pSlot;
|
|
m_pISSlot->Advise(this);
|
|
// set the parent server
|
|
m_pParent = pParent;
|
|
m_pTimer=pParent->m_pTimer;
|
|
m_Snapshot.Init(pParent,this);
|
|
m_Snapshot.SetSendPerSecond(20);
|
|
m_bForceScoreBoard = false;
|
|
m_fLastScoreBoardTime = 0;
|
|
m_ScriptObjectServerSlot.Create(pParent->m_pGame->GetScriptSystem());
|
|
m_ScriptObjectServerSlot.SetServerSlot(this);
|
|
m_pPhysicalWorld=pParent->m_pGame->m_pSystem->GetIPhysicalWorld();
|
|
m_bReady=false;
|
|
m_nDesyncFrames = 0;
|
|
m_iLastEventSent = -1;
|
|
m_bContextIsReady=false;
|
|
m_ClassId=0;
|
|
m_idClientVehicle = 0;
|
|
m_fClientVehicleSimTime = -1.0f;
|
|
|
|
memset(m_vGlobalID, 0, 64);
|
|
m_bGlobalIDSize = 0;
|
|
memset(m_vAuthID, 0, 64);
|
|
m_bAuthIDSize = 0;
|
|
m_bServerLazyChannelState=false; // start with false on client and serverslot side
|
|
m_bClientLazyChannelState=false; // start with false on client and serverslot side
|
|
m_dwUpdatesSinceLastLazySend=0;
|
|
}
|
|
|
|
///////////////////////////////////////////////
|
|
CXServerSlot::~CXServerSlot()
|
|
{
|
|
//m_pParent->m_pISystem->RemoveEntity(m_wPlayerID);
|
|
m_wPlayerId = INVALID_WID;
|
|
SAFE_RELEASE(m_pISSlot);
|
|
m_pParent = NULL;
|
|
}
|
|
|
|
|
|
bool CXServerSlot::IsEntityOffSync(EntityId id)
|
|
{
|
|
bool bOffSync = true;
|
|
|
|
if (m_pParent->m_pGame->IsMultiplayer() && m_pParent->m_pGame->UseFixedStep())
|
|
{
|
|
std::map<EntityId,int>::iterator itor = m_mapOffSyncEnts.find(id);
|
|
if (itor!=m_mapOffSyncEnts.end())
|
|
m_mapOffSyncEnts.erase(itor);
|
|
else
|
|
{
|
|
// always send full snapshots of driven vehicles, since with absense of scheduling they'll go off-sync anyway,
|
|
// but it will take longer to update them in this case (=more perceived lag)
|
|
IEntity *pEnt = m_pParent->m_pISystem->GetEntity(id);
|
|
CVehicle *pVehicle;
|
|
IEntityContainer *pContainer;
|
|
if (!(pEnt && (pContainer = pEnt->GetContainer()) &&
|
|
pContainer->QueryContainerInterface(CIT_IVEHICLE,(void**)&pVehicle) && pVehicle->HasDriver()))
|
|
bOffSync = false;
|
|
}
|
|
}
|
|
return bOffSync;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////
|
|
void CXServerSlot::OnXServerSlotConnect(const BYTE *pbAuthorizationID, unsigned int uiAuthorizationSize)
|
|
{
|
|
NET_TRACE("<<NET>>CXServerSlot::OnConnect");
|
|
|
|
memcpy(m_vAuthID, pbAuthorizationID, min(64, uiAuthorizationSize));
|
|
m_bAuthIDSize = min(64, uiAuthorizationSize);
|
|
}
|
|
|
|
///////////////////////////////////////////////
|
|
void CXServerSlot::OnXPlayerAuthorization( bool bAllow, const char *szError, const BYTE *pGlobalID,
|
|
unsigned int uiGlobalIDSize )
|
|
{
|
|
//TODO: save the GlobalID
|
|
if (!bAllow)
|
|
{
|
|
Disconnect(szError);
|
|
|
|
return;
|
|
}
|
|
|
|
// this should only be false in LAN games
|
|
if (pGlobalID && uiGlobalIDSize)
|
|
{
|
|
m_bGlobalIDSize = uiGlobalIDSize;
|
|
memcpy(m_vGlobalID, pGlobalID, min(64, uiGlobalIDSize));
|
|
|
|
// check if it is banned or not
|
|
BannedID ID(pGlobalID, uiGlobalIDSize, "");
|
|
|
|
if (m_pParent->IsIDBanned(ID))
|
|
{
|
|
Disconnect("@Banned");
|
|
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
memset(m_vGlobalID, 0, 64);
|
|
m_bGlobalIDSize = 0;
|
|
}
|
|
|
|
ContextSetup();
|
|
ResetPlayTime();
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////
|
|
void CXServerSlot::Disconnect(const char *sCause)
|
|
{
|
|
if(m_pISSlot)
|
|
m_pISSlot->Disconnect(sCause);
|
|
}
|
|
|
|
///////////////////////////////////////////////
|
|
void CXServerSlot::OnXServerSlotDisconnect(const char *szCause)
|
|
{
|
|
// if the player is not fully connected,
|
|
// no action should be taken
|
|
if (m_bContextIsReady)
|
|
{
|
|
m_pParent->GetRules()->OnClientDisconnect(m_ScriptObjectServerSlot.GetScriptObject());
|
|
}
|
|
|
|
m_wPlayerId = INVALID_WID;
|
|
// Unregister this slot
|
|
m_pParent->UnregisterXSlot(GetID());
|
|
|
|
// set as a garbage
|
|
m_bXServerSlotGarbage = true;
|
|
}
|
|
|
|
void CXServerSlot::OnSpawnEntity(CEntityDesc &ed,IEntity *pEntity,bool bSend)
|
|
{
|
|
if(!m_bCanSpawn)
|
|
{
|
|
NET_TRACE("<<NET>>CXServerSlot::OnSpawnEntity[%s] [%d] NOT READY TO SPAWN!",GetName() ,pEntity->GetId());
|
|
return;
|
|
}
|
|
if(bSend)
|
|
{
|
|
CStream stm;
|
|
|
|
ed.Write(m_pParent->m_pGame->GetIBitStream(),stm);
|
|
SendReliableMsg(XSERVERMSG_ADDENTITY,stm,false,(const char *)ed.className);
|
|
}
|
|
NET_TRACE("<<NET>>CXServerSlot::OnSpawnEntity[%s] [%d]",GetName(),pEntity->GetId());
|
|
m_Snapshot.AddEntity(pEntity);
|
|
|
|
if (pEntity->GetPhysics())
|
|
{
|
|
pe_params_flags pf;
|
|
pf.flagsOR = pef_monitor_impulses;
|
|
pEntity->GetPhysics()->SetParams(&pf);
|
|
}
|
|
}
|
|
|
|
void CXServerSlot::BanByID()
|
|
{
|
|
BannedID Banned(m_vGlobalID, m_bGlobalIDSize, GetName());
|
|
|
|
m_pParent->BanID(Banned);
|
|
}
|
|
|
|
void CXServerSlot::BanByIP()
|
|
{
|
|
unsigned int dwIP = GetIServerSlot()->GetClientIP();
|
|
|
|
m_pParent->BanIP(dwIP);
|
|
}
|
|
|
|
BYTE CXServerSlot::GetID()
|
|
{
|
|
return m_pISSlot->GetID();
|
|
}
|
|
|
|
bool CXServerSlot::IsXServerSlotGarbage()
|
|
{
|
|
return m_bXServerSlotGarbage;
|
|
}
|
|
|
|
bool CXServerSlot::IsLocalHost()
|
|
{
|
|
return m_bLocalHost;
|
|
}
|
|
|
|
unsigned int CXServerSlot::GetPing()
|
|
{
|
|
return m_pISSlot->GetPing();
|
|
}
|
|
|
|
|
|
CXServer *CXServerSlot::GetServer()
|
|
{
|
|
return m_pParent;
|
|
}
|
|
|
|
|
|
void CXServerSlot::OnRemoveEntity(IEntity *pEntity)
|
|
{
|
|
if(!m_bCanSpawn)
|
|
{
|
|
NET_TRACE("<<NET>>CXServerSlot::OnRemoveEntity[%s] [%d] NOT READY TO SPAWN!",GetName() ,pEntity->GetId());
|
|
return;
|
|
}
|
|
if(m_Snapshot.RemoveEntity(pEntity))
|
|
{
|
|
CStream stm;
|
|
stm.Write(pEntity->GetId());
|
|
SendReliableMsg(XSERVERMSG_REMOVEENTITY,stm,false,pEntity->GetName());
|
|
NET_TRACE("<<NET>> CXServerSlot::OnRemoveEntity[%s] [%d]",GetName(),pEntity->GetId());
|
|
}
|
|
else
|
|
{
|
|
NET_TRACE("<<NET>> CXServerSlot::OnRemoveEntity[%s] [%d] ##SKIPPED## (WAS NOT IN THE SNAPSHOT)",GetName(),pEntity->GetId());
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXServerSlot::ConvertToValidPlayerName( const char *szName, char* outName, size_t sizeOfOutName )
|
|
{
|
|
assert(szName);
|
|
assert(sizeOfOutName);
|
|
|
|
outName[0]=0;
|
|
|
|
int len = strlen(szName);
|
|
|
|
int iVisibleCharacters=0;
|
|
int i=0;
|
|
bool bail( false );
|
|
for (; (i < len) && (i < sizeOfOutName-1) && (iVisibleCharacters<20) && !bail ; i++)
|
|
{
|
|
switch(szName[i])
|
|
{
|
|
case '%':
|
|
case '@':
|
|
case '#':
|
|
outName[i] = '_';
|
|
iVisibleCharacters++;
|
|
break;
|
|
|
|
case '$': // color encoding
|
|
if(szName[i+1]>='0' && szName[i+1]<='9')
|
|
{
|
|
if( i+1 < sizeOfOutName-1 )
|
|
{
|
|
outName[i] = szName[i];i++;
|
|
outName[i] = szName[i];
|
|
}
|
|
else
|
|
bail = true;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
outName[i] = szName[i];
|
|
iVisibleCharacters++;
|
|
}
|
|
}
|
|
|
|
outName[i] = 0; // 0 termination
|
|
}
|
|
|
|
///////////////////////////////////////////////
|
|
void CXServerSlot::OnContextReady(CStream &stm)
|
|
{
|
|
m_pLog->Log("CXServerSlot::OnContextReady");
|
|
|
|
m_bContextIsReady=true;
|
|
|
|
string sNewPlayerName;
|
|
|
|
stm.Read(m_bLocalHost); //
|
|
stm.Read(sNewPlayerName); // client requested player name
|
|
stm.Read(m_strPlayerModel); // client requested player model
|
|
stm.Read(m_strClientColor); // client requested player color in non team base multiplayer mods
|
|
stm.Read(m_ClientRequestedClassId); //
|
|
|
|
|
|
char sTemp[65];
|
|
//CXServerSlot::ConvertToValidPlayerName(sNewPlayerName.c_str(),sTemp);
|
|
CXServerSlot::ConvertToValidPlayerName(sNewPlayerName.c_str(),sTemp,sizeof(sTemp));
|
|
|
|
m_strPlayerName=sTemp;
|
|
|
|
// m_pLog->Log("RECEIVE p_color=%s",m_strClientColor.c_str());
|
|
|
|
ValidateName();
|
|
|
|
if(m_pParent->m_bIsLoadingLevel)
|
|
{
|
|
m_bWaitingForContextReady=true;
|
|
return;
|
|
}
|
|
|
|
stm.Reset();
|
|
|
|
FinishOnContextReady();
|
|
}
|
|
|
|
///////////////////////////////////////////////
|
|
void CXServerSlot::FinishOnContextReady()
|
|
{
|
|
CPlayerSystem *pPlayerSystem = m_pParent->m_pGame->GetPlayerSystem();
|
|
|
|
IEntityItPtr pEntities=m_pParent->m_pISystem->GetEntities();
|
|
CStream relstm;
|
|
m_pParent->m_pISystem->WriteTeams(relstm);
|
|
SendReliableMsg(XSERVERMSG_TEAMS,relstm,false);
|
|
m_bCanSpawn=true;
|
|
|
|
|
|
|
|
// send all entities to the client in 2 passes
|
|
//
|
|
// first pass ensures all entities are created (only the ones that are not in the map after loading)
|
|
// second pass updates the properties of all entities
|
|
IEntity *pEnt=NULL;
|
|
|
|
pEntities->MoveFirst();
|
|
while(pEnt=pEntities->Next())
|
|
{
|
|
NET_TRACE("<<NET>>ENTITY NAME %s ENTITY NET PRESENCE %d",pEnt->GetName(),pEnt->GetNetPresence() );
|
|
if(pEnt->IsGarbage())
|
|
continue;
|
|
|
|
if(m_pParent->m_pISystem->IsLevelEntity(pEnt->GetId()))
|
|
continue;
|
|
|
|
pEnt->GetEntityDesc(m_ed);
|
|
// m_pLog->LogToFile(">> Send Entity A %d - %s (%f %f %f)", m_ed.id, m_ed.name.c_str(),m_ed.pos.x,m_ed.pos.y,m_ed.pos.z);
|
|
// NET_TRACE("<<NET>>SENDING entity id=%08d class=%03d NetPresence=%s name=%s",m_ed.id,(int)m_ed.ClassId,m_ed.netPresence?"true":"false",m_ed.name.c_str());
|
|
OnSpawnEntity(m_ed,pEnt,true);
|
|
}
|
|
|
|
//send all other entities
|
|
pEnt=NULL;
|
|
pEntities->MoveFirst();
|
|
while(pEnt=pEntities->Next())
|
|
{
|
|
if(pEnt->IsGarbage())
|
|
continue;
|
|
NET_TRACE("<<NET>>ENTITY CLASS %s NAME %s ENTITY NET PRESENCE %d",pEnt->GetEntityClassName(),pEnt->GetName(),pEnt->GetNetPresence() );
|
|
pEnt->GetEntityDesc(m_ed);
|
|
|
|
OnSpawnEntity(m_ed,pEnt,!m_pParent->m_pISystem->IsLevelEntity(pEnt->GetId()));
|
|
|
|
// m_pLog->LogToFile(">> Send Entity B %d - %s (%f %f %f)", m_ed.id, m_ed.name.c_str(),m_ed.pos.x,m_ed.pos.y,m_ed.pos.z);
|
|
// NET_TRACE("<<NET>>SENDING entity id=%08d class=%03d NetPresence=%s name=%s",m_ed.id,(int)m_ed.ClassId,m_ed.netPresence?"true":"false",m_ed.name.c_str());
|
|
}
|
|
|
|
|
|
|
|
|
|
//check if the level is loaded from file
|
|
if(m_bLocalHost)
|
|
{
|
|
if(m_pParent->m_pGame->IsLoadingLevelFromFile())
|
|
return;
|
|
}
|
|
// create the player and register it
|
|
int nClassID=m_pParent->GetRules()->OnClientConnect(m_ScriptObjectServerSlot.GetScriptObject(),m_ClientRequestedClassId);
|
|
|
|
//reset prediction stuff
|
|
m_iLastCommandServerPhysTime = 0;
|
|
m_iLastCommandClientPhysTime = 0;
|
|
m_iClientWorldPhysTimeDelta = 0;
|
|
m_idClientVehicle = 0;
|
|
m_fClientVehicleSimTime = -1.0f;
|
|
NET_TRACE("<<NET>>END CXServerSlot::OnContextReady");
|
|
}
|
|
|
|
///////////////////////////////////////////////
|
|
void CXServerSlot::OnData(CStream &stm)
|
|
{
|
|
if(stm.GetReadPos()!=0)
|
|
{
|
|
CryError( "<CryGame> (CXServerSlot::OnData) Stream read position is zero" );
|
|
return;
|
|
}
|
|
m_PlayerProcessingCmd.Reset();
|
|
ParseIncomingStream(stm);
|
|
}
|
|
|
|
///////////////////////////////////////////////
|
|
void CXServerSlot::SendReliable(CStream &stm,bool bSecondaryChannel)
|
|
{
|
|
assert(m_pParent);
|
|
m_pParent->m_NetStats.AddPacket(XSERVERMSG_UNIDENTIFIED,stm.GetSize(),true);
|
|
|
|
m_pISSlot->SendReliable(stm,bSecondaryChannel);
|
|
}
|
|
|
|
///////////////////////////////////////////////
|
|
void CXServerSlot::SendUnreliable(CStream &stm)
|
|
{
|
|
assert(m_pParent);
|
|
m_pParent->m_NetStats.AddPacket(XSERVERMSG_UNIDENTIFIED,stm.GetSize(),false);
|
|
|
|
m_pISSlot->SendUnreliable(stm);
|
|
}
|
|
|
|
void CXServerSlot::SendText(const char *sText,float fLifeTime)
|
|
{
|
|
TextMessage tm;
|
|
tm.cMessageType=CMD_SAY;
|
|
tm.uiSender=0;
|
|
tm.fLifeTime=fLifeTime;
|
|
// tm.stmPayload.Write(sText);
|
|
tm.m_sText=sText;
|
|
SendTextMessage(tm,true);
|
|
//m_pISSlot->SendUnreliable(stm);
|
|
}
|
|
|
|
void CXServerSlot::SendCommand(const char *sCmd)
|
|
{
|
|
CStream stm;
|
|
stm.Write(sCmd);
|
|
stm.Write(false); // no extra
|
|
SendReliableMsg(XSERVERMSG_CMD,stm,false);
|
|
}
|
|
|
|
void CXServerSlot::SendCommand(const char *sCmd, const Vec3 &_invPos, const Vec3 &_invNormal,
|
|
const EntityId inId, const unsigned char incUserByte )
|
|
{
|
|
Vec3 invPos=_invPos;
|
|
Vec3 invNormal=_invNormal;
|
|
CStream stm;
|
|
stm.Write(sCmd);
|
|
stm.Write(true); // extra
|
|
|
|
{
|
|
bool bPos = invPos!=Vec3(0,0,0);
|
|
|
|
stm.Write(bPos);
|
|
|
|
if(bPos)
|
|
stm.WritePkd(CStreamData_WorldPos(invPos));
|
|
}
|
|
|
|
{
|
|
bool bNormal = invNormal!=Vec3(0,0,0);
|
|
|
|
stm.Write(bNormal);
|
|
|
|
if(bNormal)
|
|
stm.WritePkd(CStreamData_Normal(invNormal));
|
|
}
|
|
|
|
stm.WritePkd(inId);
|
|
stm.WritePkd(incUserByte);
|
|
|
|
SendReliableMsg(XSERVERMSG_CMD,stm,false);
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////
|
|
size_t CXServerSlot::SendReliableMsg( XSERVERMSG msg, CStream &stm,bool bSecondaryChannel, const char *inszName )
|
|
{
|
|
assert(m_pParent);
|
|
|
|
CStream istm;
|
|
istm.WritePkd(msg);
|
|
istm.Write(stm);
|
|
m_pISSlot->SendReliable(istm,bSecondaryChannel);
|
|
|
|
m_pParent->m_NetStats.AddPacket(msg,istm.GetSize(),true);
|
|
|
|
#ifdef NET_PACKET_LOGGING
|
|
// debugging
|
|
{
|
|
FILE *out=fopen("c:/temp/ServerOutPackets.txt","a");
|
|
|
|
if(out)
|
|
{
|
|
size_t size=istm.GetSize();
|
|
BYTE *p=istm.GetPtr();
|
|
|
|
fprintf(out,"Rel Ptr:%p Bits:%4d %s %s ",this,(int)size,CXServer::GetMsgName(msg),inszName);
|
|
|
|
for(DWORD i=0;i<(size+7)/8;i++)
|
|
{
|
|
int iH=p[i]>>4;
|
|
int iL=p[i]&0xf;
|
|
|
|
char cH = iH<10?iH+'0':iH-10+'A';
|
|
char cL = iL<10?iL+'0':iL-10+'A';
|
|
|
|
fputc(cH,out);
|
|
fputc(cL,out);
|
|
}
|
|
|
|
fprintf(out,"\n");
|
|
|
|
fclose(out);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return istm.GetSize();
|
|
}
|
|
|
|
///////////////////////////////////////////////
|
|
size_t CXServerSlot::SendUnreliableMsg(XSERVERMSG msg, CStream &stm, const char *inszName, const bool bWithSize )
|
|
{
|
|
assert(m_pParent);
|
|
|
|
CStream istm;
|
|
istm.WritePkd(msg);
|
|
|
|
if(bWithSize)
|
|
istm.WritePkd((short)stm.GetSize()); // sub packet size (without packet id and size itself)
|
|
|
|
istm.Write(stm);
|
|
m_pISSlot->SendUnreliable(istm);
|
|
|
|
m_pParent->m_NetStats.AddPacket(msg,istm.GetSize(),false);
|
|
|
|
#ifdef NET_PACKET_LOGGING
|
|
// debugging
|
|
{
|
|
FILE *out=fopen("c:/temp/ServerOutPackets.txt","a");
|
|
|
|
if(out)
|
|
{
|
|
size_t size=istm.GetSize();
|
|
BYTE *p=istm.GetPtr();
|
|
|
|
fprintf(out,"Unrel Ptr:%p Bits:%4d %s %s ",this,(int)size,CXServer::GetMsgName(msg),inszName);
|
|
|
|
for(DWORD i=0;i<(size+7)/8;i++)
|
|
{
|
|
int iH=p[i]>>4;
|
|
int iL=p[i]&0xf;
|
|
|
|
char cH = iH<10?iH+'0':iH-10+'A';
|
|
char cL = iL<10?iL+'0':iL-10+'A';
|
|
|
|
fputc(cH,out);
|
|
fputc(cL,out);
|
|
}
|
|
|
|
fprintf(out,"\n");
|
|
|
|
fclose(out);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return istm.GetSize();
|
|
}
|
|
|
|
///////////////////////////////////////////////
|
|
bool CXServerSlot::IsReady()
|
|
{
|
|
return m_pISSlot->IsReady();
|
|
}
|
|
|
|
///////////////////////////////////////////////
|
|
void CXServerSlot::Update(bool send_snap, bool send_events)
|
|
{
|
|
if(m_pTimer->GetCurrTime()-m_fLastClientStringTime>1)
|
|
{
|
|
m_sClientString="";
|
|
}
|
|
|
|
if(m_bWaitingForContextReady && !m_pParent->m_bIsLoadingLevel)
|
|
{
|
|
FinishOnContextReady();
|
|
m_bWaitingForContextReady=false;
|
|
}
|
|
|
|
|
|
|
|
if(m_pISSlot->IsReady())
|
|
{
|
|
if (send_snap)
|
|
m_Snapshot.BuildAndSendSnapshot();
|
|
|
|
if (send_events)
|
|
{
|
|
CStream stm;
|
|
m_pParent->m_pGame->WriteScheduledEvents(stm, m_iLastEventSent, GetClientTimeDelta());
|
|
SendReliableMsg(XSERVERMSG_EVENTSCHEDULE, stm, true);
|
|
}
|
|
}
|
|
|
|
m_fClientVehicleSimTime -= m_pTimer->GetFrameTime();
|
|
}
|
|
|
|
class CCVarSerialize : public ICVarDumpSink
|
|
{
|
|
private:
|
|
CStream *m_pStm;
|
|
public:
|
|
CCVarSerialize(CStream *pStm)
|
|
{
|
|
m_pStm=pStm;
|
|
}
|
|
void OnElementFound(ICVar *pCVar)
|
|
{
|
|
m_pStm->Write(pCVar->GetName());
|
|
m_pStm->Write(pCVar->GetString());
|
|
}
|
|
};
|
|
|
|
///////////////////////////////////////////////
|
|
void CXServerSlot::ContextSetup()
|
|
{
|
|
CStream stm;
|
|
SXGameContext ctx;
|
|
IConsole *pCon=m_pParent->m_pGame->GetSystem()->GetIConsole();
|
|
m_pParent->GetContext(ctx);
|
|
|
|
m_pLog->Log("ContextSetup strMapFolder=%s",ctx.strMapFolder.c_str()); // debug
|
|
|
|
ctx.Write(stm);
|
|
CCVarSerialize t(&stm);
|
|
pCon->DumpCVars(&t,VF_REQUIRE_NET_SYNC);
|
|
|
|
m_pISSlot->ContextSetup(stm);
|
|
}
|
|
|
|
///////////////////////////////////////////////
|
|
void CXServerSlot::SetPlayerID(EntityId idPlayer)
|
|
{
|
|
if(idPlayer==0)
|
|
{
|
|
m_wPlayerId=idPlayer;
|
|
return;
|
|
}
|
|
|
|
IEntity *pPlayer = m_pParent->m_pISystem->GetEntity(idPlayer);
|
|
ASSERT(pPlayer);
|
|
if(pPlayer)
|
|
{
|
|
m_wPlayerId=idPlayer;
|
|
m_ClassId = pPlayer->GetClassId();
|
|
|
|
Vec3 ang=pPlayer->GetAngles();
|
|
CStream outstm;
|
|
WRITE_COOKIE(outstm);
|
|
outstm.Write(pPlayer->GetId());
|
|
outstm.Write(ang);
|
|
WRITE_COOKIE(outstm);
|
|
NET_TRACE("<<NET>>Set player sent angles [%f,%f,%f]",ang.x,ang.y,ang.z);
|
|
SendReliableMsg(XSERVERMSG_SETPLAYER,outstm,false,pPlayer->GetName());
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////
|
|
EntityId CXServerSlot::GetPlayerId() const
|
|
{
|
|
return m_wPlayerId;
|
|
}
|
|
|
|
//////////////////////////////////////////////
|
|
const char *CXServerSlot::GetName()
|
|
{
|
|
return m_strPlayerName.c_str();
|
|
}
|
|
|
|
const char *CXServerSlot::GetModel()
|
|
{
|
|
return m_strPlayerModel.c_str();
|
|
}
|
|
|
|
const char *CXServerSlot::GetColor()
|
|
{
|
|
return m_strClientColor.c_str();
|
|
}
|
|
|
|
//////////////////////////////////////////////
|
|
bool CXServerSlot::ParseIncomingStream(CStream &stm)
|
|
{
|
|
bool bRet = false;
|
|
|
|
XCLIENTMSG lastMsg=XCLIENTMSG_UNKNOWN;
|
|
|
|
do
|
|
{
|
|
XCLIENTMSG Msg;
|
|
|
|
if(!stm.Read(Msg))
|
|
return false;
|
|
|
|
switch(Msg)
|
|
{
|
|
case XCLIENTMSG_CMD:
|
|
OnClientMsgCmd(stm);
|
|
break;
|
|
case XCLIENTMSG_PLAYERPROCESSINGCMD:
|
|
{
|
|
short size;
|
|
|
|
if(!stm.ReadPkd(size)) // sub packet size
|
|
return false;
|
|
|
|
size_t readpos=stm.GetReadPos();
|
|
|
|
OnClientMsgPlayerProcessingCmd(stm);
|
|
|
|
// assert(stm.GetReadPos()==readpos+size); // just for testing
|
|
|
|
stm.Seek(readpos+size);
|
|
}
|
|
break;
|
|
case XCLIENTMSG_TEXT:
|
|
m_pParent->OnClientMsgText(m_wPlayerId,stm);
|
|
break;
|
|
case XCLIENTMSG_JOINTEAMREQUEST:
|
|
OnClientMsgJoinTeamRequest(stm);
|
|
break;
|
|
case XCLIENTMSG_CALLVOTE:
|
|
OnClientMsgCallVote(stm);
|
|
break;
|
|
case XCLIENTMSG_VOTE:
|
|
OnClientMsgVote(stm);
|
|
break;
|
|
case XCLIENTMSG_KILL:
|
|
OnClientMsgKill(stm);
|
|
break;
|
|
case XCLIENTMSG_NAME:
|
|
OnClientMsgName(stm);
|
|
break;
|
|
case XCLIENTMSG_RATE:
|
|
OnClientMsgRate(stm);
|
|
break;
|
|
case XCLIENTMSG_ENTSOFFSYNC:
|
|
OnClientOffSyncEntityList(stm);
|
|
break;
|
|
case XCLIENTMSG_RETURNSCRIPTHASH:
|
|
OnClientReturnScriptHash(stm);
|
|
break;
|
|
case XCLIENTMSG_AISTATE:
|
|
OnClientMsgAIState(stm);
|
|
break;
|
|
|
|
default:
|
|
case XCLIENTMSG_UNKNOWN:
|
|
m_pLog->LogError("SS lastSuccessfulPacketID=%i currentPacketID=%i - wrong data chunk.", lastMsg, (int)Msg);
|
|
break;
|
|
}
|
|
|
|
lastMsg=Msg;
|
|
|
|
} while(!stm.EOS());
|
|
|
|
return bRet;
|
|
}
|
|
|
|
//////////////////////////////////////////////
|
|
void CXServerSlot::OnClientMsgCallVote(CStream &stm)
|
|
{
|
|
string command, arg1;
|
|
stm.Read(command);
|
|
stm.Read(arg1);
|
|
m_pParent->m_ServerRules.CallVote(m_ScriptObjectServerSlot, (char *)command.c_str(), (char *)arg1.c_str());
|
|
};
|
|
|
|
//////////////////////////////////////////////
|
|
void CXServerSlot::OnClientMsgVote(CStream &stm)
|
|
{
|
|
int vote;
|
|
stm.Read(vote);
|
|
m_pParent->m_ServerRules.Vote(m_ScriptObjectServerSlot, vote);
|
|
};
|
|
|
|
//////////////////////////////////////////////
|
|
void CXServerSlot::OnClientMsgKill(CStream &stm)
|
|
{
|
|
m_pParent->m_ServerRules.Kill(m_ScriptObjectServerSlot);
|
|
};
|
|
|
|
void CXServerSlot::OnClientMsgRate(CStream &stm)
|
|
{
|
|
unsigned char cVar;
|
|
|
|
stm.Read(cVar);
|
|
|
|
switch(cVar)
|
|
{
|
|
case 0:
|
|
{
|
|
unsigned int dwBitsPerSecond;
|
|
|
|
stm.Read(dwBitsPerSecond);
|
|
|
|
m_Snapshot.SetClientBitsPerSecond(dwBitsPerSecond);
|
|
}
|
|
break;
|
|
|
|
case 1:
|
|
{
|
|
unsigned int dwUpdatesPerSec;
|
|
stm.Read(dwUpdatesPerSec);
|
|
|
|
m_Snapshot.SetSendPerSecond(dwUpdatesPerSec);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
assert(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXServerSlot::OnClientMsgName(CStream &stm)
|
|
{
|
|
string sName;
|
|
stm.Read(sName);
|
|
|
|
m_pLog->Log("Receive SetName '%s'",sName.c_str());
|
|
|
|
IEntity *pThis=m_pParent->m_pISystem->GetEntity(GetPlayerId());
|
|
if(pThis)
|
|
{
|
|
string sOldName = m_strPlayerName;
|
|
|
|
m_strPlayerName=sName;
|
|
ValidateName(); // validate the name
|
|
sName = m_strPlayerName; // read it back
|
|
|
|
if (sName == sOldName)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//SET THE ENTITY NAME
|
|
CStream nstm;
|
|
WRITE_COOKIE(nstm);
|
|
nstm.Write(pThis->GetId());
|
|
nstm.Write(sName);
|
|
WRITE_COOKIE(nstm);
|
|
m_pParent->BroadcastReliable(XSERVERMSG_SETENTITYNAME,nstm,true);
|
|
|
|
if (m_pParent->m_pGame->IsMultiplayer())
|
|
{
|
|
string sTemp;
|
|
sTemp+=pThis->GetName();
|
|
sTemp+=" @PlayerRenamed ";
|
|
sTemp+=sName;
|
|
m_pParent->BroadcastText(sTemp.c_str());
|
|
pThis->SetName(sName.c_str());
|
|
}
|
|
}
|
|
|
|
NET_TRACE("<<NET>>CXServerSlot::OnClientMsgName(%s)\n",sName.c_str());
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXServerSlot::OnClientMsgAIState(CStream &stm)
|
|
{
|
|
int nDummy;
|
|
stm.Read(nDummy);
|
|
|
|
IEntity *pThis=m_pParent->m_pISystem->GetEntity(GetPlayerId());
|
|
if (pThis)
|
|
{
|
|
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////
|
|
bool CXServerSlot::IsContextReady()
|
|
{
|
|
return m_bContextIsReady;
|
|
}
|
|
|
|
//////////////////////////////////////////////
|
|
void CXServerSlot::SetGameState(int state, int time)
|
|
{
|
|
CStream stm;
|
|
m_nState = state;
|
|
stm.Write((char)state);
|
|
stm.Write((short)time);
|
|
|
|
SendReliableMsg(XSERVERMSG_SETGAMESTATE, stm,false);
|
|
};
|
|
|
|
//////////////////////////////////////////////
|
|
void CXServerSlot::SendScoreBoard()
|
|
{
|
|
// don't send anything if we are loading
|
|
if (m_pParent->m_pGame->IsLoadingLevelFromFile())
|
|
{
|
|
return;
|
|
}
|
|
|
|
float fTime = GetISystem()->GetITimer()->GetCurrTime();
|
|
|
|
// check for timer reset
|
|
if (fTime < m_fLastScoreBoardTime)
|
|
{
|
|
m_fLastScoreBoardTime = 0.0f;
|
|
}
|
|
|
|
if (fTime - m_fLastScoreBoardTime <= 0.200f) // send scoreboard every 200ms only
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_fLastScoreBoardTime = fTime;
|
|
|
|
IScriptSystem *pScriptSystem = GetISystem()->GetIScriptSystem();
|
|
IScriptObject *pGameRules = m_pParent->m_ServerRules.GetScriptObject();
|
|
|
|
// prepare the streams
|
|
CStream stmScoreBoard;
|
|
CScriptObjectStream stmScript;
|
|
|
|
stmScoreBoard.Reset();
|
|
stmScript.Create(pScriptSystem);
|
|
stmScript.Attach(&stmScoreBoard);
|
|
|
|
CXServer::XSlotMap &Slots = m_pParent->GetSlotsMap();
|
|
|
|
for (CXServer::XSlotMap::iterator it = Slots.begin(); it != Slots.end(); ++it)
|
|
{
|
|
CXServerSlot *Slot = it->second;
|
|
|
|
if (Slot->GetPlayerId() != INVALID_WID)
|
|
{
|
|
stmScoreBoard.Write((bool)1);
|
|
|
|
pScriptSystem->BeginCall("GameRules", "GetPlayerScoreInfo");
|
|
pScriptSystem->PushFuncParam(pGameRules);
|
|
pScriptSystem->PushFuncParam(Slot->GetScriptObject());
|
|
pScriptSystem->PushFuncParam(stmScript.GetScriptObject());
|
|
pScriptSystem->EndCall();
|
|
}
|
|
}
|
|
|
|
stmScoreBoard.Write((bool)0);
|
|
|
|
SendUnreliableMsg(XSERVERMSG_SCOREBOARD, stmScoreBoard,"",true); // true=send with size
|
|
|
|
if ((stmScoreBoard.GetSize()>>3) >= 768)
|
|
{
|
|
GetISystem()->GetILog()->Log("sent scoreboard to %s at %gsecs with %d bytes", GetName(), fTime, stmScoreBoard.GetSize()>>3);
|
|
}
|
|
};
|
|
|
|
//////////////////////////////////////////////
|
|
void CXServerSlot::ValidateName()
|
|
{
|
|
if (m_strPlayerName.empty())
|
|
{
|
|
m_strPlayerName = "Jack Carver";
|
|
}
|
|
|
|
CXServer::XSlotMap &slots = m_pParent->GetSlotsMap();
|
|
|
|
for (int i = 0; i < (int)m_strPlayerName.size(); i++)
|
|
{
|
|
switch(m_strPlayerName[i])
|
|
{
|
|
case '@':
|
|
case '%':
|
|
case '\"':
|
|
case '\'':
|
|
m_strPlayerName[i] = '_';
|
|
}
|
|
}
|
|
|
|
for(CXServer::XSlotMap::iterator i = slots.begin(); i != slots.end(); ++i)
|
|
{
|
|
CXServerSlot *slot = i->second;
|
|
|
|
if(slot!=this && strcmp(slot->GetName(), GetName())==0)
|
|
{
|
|
m_strPlayerName += "_"; // better renaming scheme?
|
|
ValidateName(); // keep renaming until no more clashes
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////
|
|
bool CXServerSlot::OccupyLazyChannel()
|
|
{
|
|
if(m_bClientLazyChannelState!=m_bServerLazyChannelState)
|
|
return false;
|
|
|
|
// todo: remove
|
|
// GetISystem()->GetILog()->Log("OccupyLazyChannel %s -> %s",
|
|
// m_bServerLazyChannelState?"true":"false", !m_bServerLazyChannelState?"true":"false"); // debug
|
|
|
|
m_bServerLazyChannelState=!m_bServerLazyChannelState;
|
|
m_dwUpdatesSinceLastLazySend=0; // 0=it wasn't set at all
|
|
|
|
return true;
|
|
}
|
|
|
|
///////////////////////////////////////////////
|
|
bool CXServerSlot::GetServerLazyChannelState()
|
|
{
|
|
return m_bServerLazyChannelState;
|
|
}
|
|
|
|
///////////////////////////////////////////////
|
|
bool CXServerSlot::ShouldSendOverLazyChannel()
|
|
{
|
|
m_dwUpdatesSinceLastLazySend++;
|
|
|
|
// if we should send it the first time or it's time resend it
|
|
bool bRet = m_dwUpdatesSinceLastLazySend==1 || m_dwUpdatesSinceLastLazySend>20;
|
|
|
|
if(bRet)
|
|
m_dwUpdatesSinceLastLazySend=1; // 1=resend again
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////
|
|
void CXServerSlot::OnClientMsgPlayerProcessingCmd(CStream &stm)
|
|
{
|
|
IBitStream *pBitStream = m_pParent->m_pGame->GetIBitStream();
|
|
|
|
stm.Read(m_bClientLazyChannelState);
|
|
|
|
// sync random seed (from the client)
|
|
{
|
|
CPlayer *pPlayer=0;
|
|
|
|
if(m_wPlayerId!=INVALID_WID)
|
|
if(IEntity *pSlotPlayerEntity = m_pParent->m_pISystem->GetEntity(m_wPlayerId))
|
|
pSlotPlayerEntity->GetContainer()->QueryContainerInterface(CIT_IPLAYER,(void**)&pPlayer);
|
|
|
|
bool bSyncSeed;
|
|
|
|
stm.Read(bSyncSeed);
|
|
|
|
if(bSyncSeed)
|
|
{
|
|
uint8 ucStartRandomSeed;
|
|
|
|
stm.Read(ucStartRandomSeed);
|
|
|
|
if(pPlayer)
|
|
pPlayer->m_SynchedRandomSeed.EnableSyncRandomSeedS(ucStartRandomSeed);
|
|
// GetISystem()->GetILog()->Log(">> ClientCmd Read %d %d",(int)m_wPlayerId,(int)ucStartRandomSeed); // debug
|
|
}
|
|
else
|
|
{
|
|
if(pPlayer)
|
|
pPlayer->m_SynchedRandomSeed.DisableSyncRandomSeedS();
|
|
// GetISystem()->GetILog()->Log(">> ClientCmd Read %d off",(int)m_wPlayerId); // debug
|
|
}
|
|
}
|
|
|
|
m_PlayerProcessingCmd.Read(stm,pBitStream);
|
|
|
|
//<<FIXME>> implement
|
|
//if no player is assigned to this serverslot I ignore this chunk
|
|
if(m_wPlayerId == INVALID_WID)
|
|
return;
|
|
|
|
IEntity *pSlotPlayerEntity = m_pParent->m_pISystem->GetEntity(m_wPlayerId);
|
|
if(!pSlotPlayerEntity)
|
|
{
|
|
m_pLog->LogToConsole("m_wPlayerId=%d entity not found",m_wPlayerId);
|
|
return;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//PREDICTION STUFF
|
|
IPhysicalEntity *pPhysEnt=(IPhysicalEntity *)pSlotPlayerEntity->GetPhysics();
|
|
if(pPhysEnt)
|
|
{
|
|
//SERVER
|
|
float fServerDelta=0.0f,fPing=m_pISSlot->GetPing()*0.001f,timeGran = m_pPhysicalWorld->GetPhysVars()->timeGranularity;
|
|
int iPing = m_pParent->m_pGame->QuantizeTime(fPing);
|
|
int iSrvTime = m_pPhysicalWorld->GetiPhysicsTime();
|
|
fPing = iPing*timeGran;
|
|
|
|
if (iPing>0)
|
|
{
|
|
float *pfSlices,fSliceTotal=0,fClientDelta;
|
|
int nSlices = m_PlayerProcessingCmd.GetTimeSlices(pfSlices);
|
|
for(int i=0;i<nSlices;i++)
|
|
fSliceTotal += pfSlices[i];
|
|
fClientDelta = (m_PlayerProcessingCmd.GetPhysicalTime()-m_iLastCommandClientPhysTime)*timeGran;
|
|
if (m_iClientWorldPhysTimeDelta && fClientDelta>-2.0f && fClientDelta<0)
|
|
return; // ignore older client commands that arrived out of order
|
|
// if some previous client command(s) didn't arrive, insert a time slice corresponding to its duration
|
|
if (fClientDelta<2.0f && (fClientDelta-fSliceTotal)>0.00001 && fClientDelta>fSliceTotal)
|
|
m_PlayerProcessingCmd.InsertTimeSlice(fClientDelta-fSliceTotal,0);
|
|
}
|
|
|
|
if(m_iLastCommandServerPhysTime!=0)
|
|
fServerDelta=(iSrvTime-m_iLastCommandServerPhysTime)*timeGran;
|
|
m_PlayerProcessingCmd.SetPhysDelta(fServerDelta);
|
|
m_iLastCommandClientPhysTime = m_PlayerProcessingCmd.GetPhysicalTime();
|
|
|
|
float fCurTime=m_pTimer->GetAsyncCurTime(), fTimeDiff, fTimeThresh;
|
|
fTimeDiff = fabsf(timeGran*(m_iLastCommandClientPhysTime+iPing-m_iClientWorldPhysTimeDelta)-fCurTime);
|
|
fTimeThresh = max(fPing,m_pTimer->GetFrameTime())*1.4f;
|
|
if (fTimeDiff > fTimeThresh)
|
|
m_nDesyncFrames++;
|
|
else
|
|
m_nDesyncFrames = 0;
|
|
if (m_nDesyncFrames>4 || fTimeDiff>fTimeThresh*8 || !m_iClientWorldPhysTimeDelta)
|
|
{
|
|
int iNewTimeDelta = m_iLastCommandClientPhysTime+iPing-m_pParent->m_pGame->QuantizeTime(fCurTime);
|
|
iNewTimeDelta = m_pParent->m_pGame->SnapTime(iNewTimeDelta);
|
|
if (iPing>0 && fabs_tpl((iNewTimeDelta-m_iClientWorldPhysTimeDelta)*timeGran)<3.0f)
|
|
m_pLog->LogToConsole("Adjusting client clock by %+.4f, new delta %.4f (ping %.3f)",
|
|
(iNewTimeDelta-m_iClientWorldPhysTimeDelta)*timeGran, iNewTimeDelta*timeGran, fPing);
|
|
m_iClientWorldPhysTimeDelta = iNewTimeDelta;
|
|
}
|
|
//
|
|
//m_iLastCommandServerPhysTime = m_pPhysicalWorld->GetiPhysicsTime()-iPing;
|
|
m_iLastCommandServerPhysTime = min(iSrvTime,m_iLastCommandClientPhysTime-m_iClientWorldPhysTimeDelta);
|
|
|
|
if (fPing>0)
|
|
{
|
|
fPing = (iSrvTime-m_iLastCommandServerPhysTime)*timeGran;
|
|
m_PlayerProcessingCmd.AddTimeSlice(&fPing);
|
|
}
|
|
}
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
m_idClientVehicle = 0;
|
|
bool bHasVehicle;
|
|
stm.Read(bHasVehicle);
|
|
if (bHasVehicle)
|
|
{
|
|
CPlayer *pPlayer=0;
|
|
CVehicle *pVehicle=0;
|
|
short nSize;
|
|
long nPos;
|
|
pe_status_vehicle sv;
|
|
|
|
EntityId id;
|
|
|
|
pBitStream->ReadBitStream(stm,id,eEntityId);
|
|
// stm.Read(id);
|
|
|
|
IEntity *pEnt = m_pParent->m_pISystem->GetEntity(id);
|
|
stm.Read(nSize);
|
|
nPos = stm.GetReadPos()+nSize;
|
|
|
|
if (!m_bLocalHost &&
|
|
pEnt && pEnt->GetContainer() &&
|
|
pEnt->GetContainer()->QueryContainerInterface(CIT_IVEHICLE, (void**)&pVehicle) &&
|
|
pSlotPlayerEntity->GetContainer()->QueryContainerInterface(CIT_IPLAYER,(void**)&pPlayer) &&
|
|
pVehicle==pPlayer->GetVehicle() &&
|
|
pEnt->GetPhysics()->GetStatus(&sv) && sv.nActiveColliders==0)
|
|
{
|
|
pEnt->Read(stm);
|
|
|
|
pe_params_flags pf;
|
|
pf.flagsOR = pef_update;
|
|
pEnt->GetPhysics()->SetParams(&pf);
|
|
m_pParent->m_pGame->AdvanceReceivedEntities(m_iLastCommandServerPhysTime);
|
|
pf.flagsOR = 0;
|
|
pf.flagsAND = ~pef_update;
|
|
pEnt->GetPhysics()->SetParams(&pf);
|
|
|
|
m_idClientVehicle = id;
|
|
m_fClientVehicleSimTime = 2.0f;
|
|
}
|
|
stm.Seek(nPos);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
Vec3 posClient; bool bClientPos,bInRange;
|
|
stm.Read(bClientPos);
|
|
if (bClientPos)
|
|
{
|
|
stm.Read(bInRange);
|
|
if (bInRange)
|
|
{
|
|
unsigned int num;
|
|
stm.ReadNumberInBits(num,16); posClient.x = num*(1.0f/16);
|
|
stm.ReadNumberInBits(num,16); posClient.y = num*(1.0f/16);
|
|
stm.ReadNumberInBits(num,13); posClient.z = num*(1.0f/16);
|
|
}
|
|
else
|
|
stm.Read(posClient);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//if the player is asking the scoreboard I send it
|
|
if(m_PlayerProcessingCmd.CheckAction(ACTION_SCORE_BOARD) || m_bForceScoreBoard)
|
|
{
|
|
SendScoreBoard();
|
|
}
|
|
else
|
|
{
|
|
m_fLastScoreBoardTime = 0.0f;
|
|
}
|
|
|
|
//when the game is in itermission state it will skip all client inputs
|
|
if(m_nState==CGS_INTERMISSION)
|
|
{
|
|
m_PlayerProcessingCmd.Reset();
|
|
};
|
|
|
|
//if the client entity is a CPlayer
|
|
if (m_pParent->m_pGame->GetPlayerSystem()->IsPlayerClass(pSlotPlayerEntity->GetClassId()))
|
|
{
|
|
CPlayer *pPlayer=NULL;
|
|
|
|
if (pSlotPlayerEntity->GetContainer() && pSlotPlayerEntity->GetContainer()->QueryContainerInterface(CIT_IPLAYER,(void**) &pPlayer))
|
|
{
|
|
//<<FIXME>> move the respawn time in the serverslot
|
|
if( (!pPlayer->IsAlive()))
|
|
{
|
|
|
|
if((m_PlayerProcessingCmd.CheckAction(ACTION_FIRE0)) != 0)
|
|
{
|
|
m_pParent->GetRules()->OnClientRequestRespawn(m_ScriptObjectServerSlot.GetScriptObject(),m_ClassId);
|
|
}
|
|
else
|
|
{
|
|
if (!m_pParent->m_pGame->IsMultiplayer())
|
|
{
|
|
// [marco] for single player, after the fade in is finished,
|
|
// show the last checkpoint menu
|
|
// try to click button always, so it will show the checkpoint
|
|
// menu as soon as the client respawn time is passed
|
|
m_pParent->GetRules()->OnClientRequestRespawn(m_ScriptObjectServerSlot.GetScriptObject(),m_ClassId);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
pPlayer->ProcessCmd(m_pISSlot->GetPing(),m_PlayerProcessingCmd);
|
|
// My player entity, process animations in Client.
|
|
if(!m_bLocalHost) //avoid to process the angles(and recoil) twice
|
|
pPlayer->ProcessAngles(m_PlayerProcessingCmd);
|
|
|
|
/*if (m_pParent->m_pGame->IsMultiplayer() && m_pParent->m_pGame->UseFixedStep() && pPlayer->GetVehicle())
|
|
{
|
|
m_PlayerProcessingCmd.SetPhysicalTime(m_pParent->m_pGame->SnapTime(m_pPhysicalWorld->GetiPhysicsTime())+m_pParent->GetSchedulingDelay());
|
|
m_pParent->m_pGame->ScheduleEvent(pSlotPlayerEntity, m_PlayerProcessingCmd);
|
|
}*/
|
|
}
|
|
}
|
|
else if (pSlotPlayerEntity->GetClassId()==SPECTATOR_CLASS_ID)
|
|
{
|
|
// assert(!bHasVehicle);
|
|
|
|
pe_player_dynamics pd;
|
|
if (!m_bLocalHost && bClientPos)
|
|
{
|
|
pSlotPlayerEntity->SetPos(posClient);
|
|
m_idClientVehicle = m_wPlayerId;
|
|
m_fClientVehicleSimTime = 10.0f;
|
|
pd.bActive = 0;
|
|
}
|
|
else
|
|
pd.bActive = 1;
|
|
if (pPhysEnt)
|
|
pPhysEnt->SetParams(&pd);
|
|
|
|
CSpectator *pSpectator=NULL;
|
|
pSlotPlayerEntity->GetContainer()->QueryContainerInterface(CIT_ISPECTATOR,(void**) &pSpectator);
|
|
if(pSpectator)
|
|
pSpectator->ProcessKeys(m_PlayerProcessingCmd);
|
|
}
|
|
else if (pSlotPlayerEntity->GetClassId()==ADVCAMSYSTEM_CLASS_ID)
|
|
{
|
|
CAdvCamSystem *pAdvCamSystem=NULL;
|
|
pSlotPlayerEntity->GetContainer()->QueryContainerInterface(CIT_IADVCAMSYSTEM,(void**) &pAdvCamSystem);
|
|
if(pAdvCamSystem)
|
|
pAdvCamSystem->ProcessKeys(m_PlayerProcessingCmd);
|
|
}
|
|
}
|
|
|
|
bool CXServerSlot::IsClientSideEntity(IEntity *pEnt)
|
|
{
|
|
IEntityContainer *pIContainer=pEnt->GetContainer();
|
|
|
|
if(!pIContainer)
|
|
return false;
|
|
|
|
CVehicle *pVehicle;
|
|
|
|
// only vehicles are client side simulated
|
|
if(pIContainer->QueryContainerInterface(CIT_IVEHICLE,(void**)&pVehicle))
|
|
return m_fClientVehicleSimTime>0 && pEnt->GetId()==m_idClientVehicle;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////
|
|
void CXServerSlot::OnClientMsgJoinTeamRequest(CStream &stm)
|
|
{
|
|
//check if the team switch is allowed via game rules script
|
|
//if yes,respawn the player/spectator/commander
|
|
BYTE nTeamId;
|
|
string sClass;
|
|
stm.Read(nTeamId);
|
|
stm.Read(sClass);
|
|
|
|
if(m_wPlayerId!=INVALID_WID)
|
|
{
|
|
m_pParent->m_ServerRules.OnClientMsgJoinTeamRequest(this,nTeamId,sClass.c_str());
|
|
}
|
|
|
|
}
|
|
|
|
void CXServerSlot::OnClientMsgCmd(CStream &stm)
|
|
{
|
|
string cmd;
|
|
stm.Read(cmd);
|
|
if(m_wPlayerId!=INVALID_WID)
|
|
{
|
|
m_pParent->m_ServerRules.OnClientCmd(this,cmd.c_str());
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////
|
|
void CXServerSlot::OnClientOffSyncEntityList(CStream &stm)
|
|
{
|
|
unsigned char nEnts;
|
|
EntityId id;
|
|
|
|
stm.Read(nEnts);
|
|
for(;nEnts;nEnts--)
|
|
{
|
|
stm.ReadPkd(id);
|
|
MarkEntityOffSync(id);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////
|
|
// XCLIENTMSG_RETURNSCRIPTHASH
|
|
void CXServerSlot::OnClientReturnScriptHash(CStream &stm)
|
|
{
|
|
IBitStream *pBitStream = m_pParent->m_pGame->GetIBitStream();
|
|
|
|
u32 dwHash;
|
|
|
|
pBitStream->ReadBitStream(stm,dwHash,eDoNotCompress); // returned hash
|
|
|
|
// todo
|
|
|
|
// debug
|
|
// m_pLog->Log("OnClientReturnScriptHash %p",dwHash);
|
|
}
|
|
|
|
void CXServerSlot::MarkEntityOffSync(EntityId id)
|
|
{
|
|
IEntity *pEnt = m_pParent->m_pGame->m_pSystem->GetIEntitySystem()->GetEntity(id);
|
|
if (!pEnt)
|
|
return;
|
|
|
|
Vec3 vBBox[2],sz;
|
|
m_mapOffSyncEnts.insert(std::pair<EntityId,int>(id,0));
|
|
pEnt->GetBBox(vBBox[0],vBBox[1]);
|
|
sz = vBBox[1]-vBBox[0];
|
|
|
|
pe_params_foreign_data pfd;
|
|
IPhysicalEntity **ppEnts;
|
|
int i = m_pPhysicalWorld->GetEntitiesInBox(vBBox[0]-sz*0.3f,vBBox[1]+sz*0.3f,ppEnts,ent_sleeping_rigid|ent_rigid)-1;
|
|
for(; i>=0; i--)
|
|
{
|
|
pEnt = (IEntity*)ppEnts[i]->GetForeignData();
|
|
if (pEnt && pEnt->GetNetPresence())
|
|
m_mapOffSyncEnts.insert(std::pair<EntityId,int>(pEnt->GetId(),0));
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////
|
|
void CXServerSlot::GetBandwidthStats( SServerSlotBandwidthStats &out )
|
|
{
|
|
if(m_pISSlot)
|
|
m_pISSlot->GetBandwidthStats(out);
|
|
else
|
|
out.Reset();
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////
|
|
void CXServerSlot::ResetBandwidthStats()
|
|
{
|
|
if(m_pISSlot)
|
|
m_pISSlot->ResetBandwidthStats();
|
|
}
|
|
|
|
//////////////////////////////////////////////
|
|
void CXServerSlot::SendTextMessage(TextMessage &tm,bool bSecondaryChannel)
|
|
{
|
|
CStream stm;
|
|
tm.Write(stm);
|
|
SendReliableMsg(XSERVERMSG_TEXT,stm,bSecondaryChannel);
|
|
}
|
|
|
|
void CXServerSlot::ClientString(const char *s)
|
|
{
|
|
m_fLastClientStringTime=m_pTimer->GetCurrTime();
|
|
m_sClientString=s;
|
|
}
|
|
|
|
void CXServerSlot::GetNetStats(SlotNetStats &ss)
|
|
{
|
|
ss.ping=m_pISSlot->GetPing()*2;
|
|
ss.packetslost=m_pISSlot->GetPacketsLostCount();
|
|
ss.upacketslost=m_pISSlot->GetUnreliablePacketsLostCount();
|
|
ss.name=m_strPlayerName;
|
|
ss.lastsnapshotbitsize=m_Snapshot.m_nLastSnapshotBitSize;
|
|
ss.maxsnapshotbitsize=m_Snapshot.m_nMaxSnapshotBitSize;
|
|
}
|
|
|
|
|