Files
FC1/CryGame/XServer.cpp
romkazvo 34d6c5d489 123
2023-08-07 19:29:24 +08:00

1624 lines
39 KiB
C++

//////////////////////////////////////////////////////////////////////
//
// Game Source Code
//
// File: XServer.cpp
// Description: SXServerInfos && CXServer implementations.
//
// History:
// - August 3, 2001: Created by Alberto Demichelis
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "XSystemServer.h"
#include <I3dengine.h>
#include <IEntitySystem.h>
#include "XPlayer.h"
#include "Spectator.h"
#include "XVehicleSystem.h"
#include "PlayerSystem.h"
#include "XVehicle.h"
#include "ScriptObjectPlayer.h"
#include "ScriptObjectVehicle.h"
#include "ScriptObjectSpectator.h"
#include "TagPoint.h"
#include <IRenderer.h>
#if defined(LINUX)
#include "WinBase.h"
#endif
//////////////////////////////////////////////////////////////////////////////////////////////
void CXServer::OnSpawnContainer( CEntityDesc &ed,IEntity *pEntity )
{
m_pISystem->OnSpawnContainer(ed,pEntity);
}
const char *CXServer::GetMsgName( XSERVERMSG inValue )
{
switch(inValue)
{
#define ADDNAME(name) case XSERVERMSG_##name: return(#name);
ADDNAME(UPDATEENTITY)
ADDNAME(ADDENTITY)
ADDNAME(REMOVEENTITY)
ADDNAME(TIMESTAMP)
ADDNAME(TEXT)
ADDNAME(SETPLAYERSCORE)
ADDNAME(SETENTITYSTATE)
// ADDNAME(OBITUARY)
ADDNAME(SETTEAMSCORE)
ADDNAME(SETTEAMFLAGS)
ADDNAME(SETPLAYER)
ADDNAME(CLIENTSTRING)
ADDNAME(CMD)
ADDNAME(SETTEAM)
ADDNAME(ADDTEAM)
ADDNAME(REMOVETEAM)
ADDNAME(SETENTITYNAME)
ADDNAME(BINDENTITY)
ADDNAME(SCOREBOARD)
ADDNAME(SETGAMESTATE)
ADDNAME(TEAMS)
ADDNAME(SYNCVAR)
ADDNAME(EVENTSCHEDULE)
ADDNAME(UNIDENTIFIED)
#undef ADDNAME
default: assert(0);
}
return 0;
}
///////////////////////////////////////////////
void CXServer::OnSpawn(IEntity *ent, CEntityDesc & ed )
{
m_pISystem->OnSpawn(ent,ed);
bool bSend = true;
bSend=!m_bIsLoadingLevel; // during loading we don't sync entities
XSlotMap::iterator i = m_mapXSlots.begin();
while(i != m_mapXSlots.end())
{
i->second->OnSpawnEntity(ed,ent,bSend);
++i;
}
}
///////////////////////////////////////////////
void CXServer::OnRemove(IEntity *ent)
{
XSlotMap::iterator i = m_mapXSlots.begin();
while(i != m_mapXSlots.end())
{
//TRACE("CXServer::OnRemove [%d]",ent->GetId());
i->second->OnRemoveEntity(ent);
++i;
}
}
///////////////////////////////////////////////
unsigned int CXServer::GetMaxUpdateRate() const
{
assert(sv_maxupdaterate);
return sv_maxupdaterate->GetIVal();
}
//////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////
CXServer::CXServer(CXGame *pGame, WORD nPort, const char *szName, bool listen)
{
assert(pGame);
m_pGame = pGame;
m_pTimer = pGame->m_pSystem->GetITimer();
IConsole *pConsole=m_pGame->GetSystem()->GetIConsole(); assert(pConsole);
sv_name = pConsole->GetCVar("sv_name");
sv_password = pConsole->GetCVar("sv_password");
sv_maxplayers = pConsole->GetCVar("sv_maxplayers");
sv_maxupdaterate = pConsole->GetCVar("sv_maxupdaterate");
sv_maxrate = pConsole->GetCVar("sv_maxrate");
sv_maxrate_lan = pConsole->GetCVar("sv_maxrate_lan");
sv_netstats = pConsole->GetCVar("sv_netstats");
sv_max_scheduling_delay = pConsole->GetCVar("sv_max_scheduling_delay");
sv_min_scheduling_delay = pConsole->GetCVar("sv_min_scheduling_delay");
m_bIsLoadingLevel=false;
m_bListen = listen;
m_mapXSlots.clear();
float timeout = m_pGame->sv_timeout->GetFVal()*1000;
// create the entity system sink
m_pGame->GetSystem()->GetIEntitySystem()->SetSink(this);
// create the system interface
m_pISystem = new CXSystemServer(this,m_pGame,m_pGame->m_pLog);
// get this info before we set the server
m_pGame->GetSystem()->SetForceNonDevMode(!m_pGame->IsDevModeEnable());
// fill m_ServerInfo structure
GetServerInfo();
// create the server
m_pIServer = m_pGame->CreateServer(this,nPort,m_bListen);
if (!m_pIServer)
{
m_pGame->m_pLog->Log("!!---------Server creation failed---------!!");
m_bOK = false;
return;
}
else
m_bOK = true;
m_pIServer->SetSecuritySink(this);
m_pIServer->SetVariable(cnvDataStreamTimeout,(unsigned int)timeout);
m_ServerInfos.nPort = nPort;
m_ServerInfos.VersionInfo = GetISystem()->GetFileVersion();
// initialise the game context
m_GameContext.dwNetworkVersion = NETWORK_FORMAT_VERSION;
m_GameContext.strMission = "";
m_ScriptObjectServer.Create(m_pGame->GetScriptSystem(),m_pISystem,m_pGame);
m_ScriptObjectServer.SetServer(this);
m_bInDestruction=false;
LoadBanList();
IScriptSystem *pScriptSystem = GetISystem()->GetIScriptSystem();
assert(pScriptSystem);
_SmartScriptObject pMapCycle(pScriptSystem);
pScriptSystem->GetGlobalValue("MapCycle", pMapCycle);
if (((IScriptObject *)pMapCycle) != 0)
{
HSCRIPTFUNCTION pfnInit = 0;
if (pMapCycle->GetValue("Init", pfnInit))
{
pScriptSystem->BeginCall(pfnInit);
pScriptSystem->PushFuncParam((IScriptObject *)pMapCycle);
pScriptSystem->EndCall();
pScriptSystem->ReleaseFunc(pfnInit);
}
}
}
//////////////////////////////////////////////////////////////////////////
unsigned CXServer::MemStats()
{
unsigned size = sizeof *this;
XSlotMap::iterator itr=m_mapXSlots.begin();
for(; itr!=m_mapXSlots.end(); itr++)
size += (itr->second)->MemStats() + sizeof (CXServerSlot*);
return size;
}
///////////////////////////////////////////////
CXServer::~CXServer()
{
// ClearSlots should remove all players later anyway
// if (m_pGame->m_pUbiSoftClient)
// {
// m_pGame->m_pUbiSoftClient->Server_RemoveAllPlayers();
// }
m_bInDestruction=true;
// update the network, to process any pending disconnect
UpdateXServerNetwork();
// m_pGame->m_pLog->Log("~CXServer 1");
// shut down server game rules. (incorrect place but left for SP stability)
m_ServerRules.ShutDown();
// m_pGame->m_pLog->Log("~CXServer 2");
// delele the entity system sink
m_pGame->GetSystem()->GetIEntitySystem()->RemoveSink(this);
// m_pGame->m_pLog->Log("~CXServer 3");
// remove the slots which are still connected
ClearSlots();
// update the network, to process any pending disconnect
UpdateXServerNetwork();
// shut down server game rules. (correct place)
// m_ServerRules.ShutDown();
m_pGame->GetScriptSystem()->SetGlobalToNull("GameRules"); // workaround to minimize risk
// m_pGame->m_pLog->Log("~CXServer 4");
// release the IServer interface
SAFE_RELEASE(m_pIServer);
// m_pGame->m_pLog->Log("~CXServer 5");
// release the system interface
SAFE_RELEASE(m_pISystem);
if(m_pGame->GetScriptSystem())
{
m_pGame->GetScriptSystem()->SetGlobalToNull("Server");
//Never force Lua GC, m_pScriptSystem->ForceGarbageCollection();
}
m_pGame = NULL;
}
bool CXServer::IsInDestruction() const
{
return m_bInDestruction;
}
void CXServer::DrawNetStats(IRenderer *pRenderer)
{
if (!pRenderer)
return;
if(sv_netstats->GetIVal() & 0x1) // display net statistics
{
int y=30;
XSlotMap::iterator i = m_mapXSlots.begin();
SlotNetStats ss;
while(i != m_mapXSlots.end())
{
CXServerSlot *pSlot = i->second;
pSlot->GetNetStats(ss);
pRenderer->TextToScreen(10.0f,(float)y,"[%s] PING[%d] LOST[%d] ULOST[%d] MAXSNAP=%dbits LASTSNAP=%dbits",
ss.name.c_str(),ss.ping,ss.packetslost,ss.upacketslost,ss.maxsnapshotbitsize,ss.lastsnapshotbitsize);
y+=3;
++i;
}
float fIncomingKbPerSec,fOutgoingKbPerSec;
DWORD nIncomingPacketsPerSec,nOutgoingPacketsPerSec;
m_pIServer->GetBandwidth(fIncomingKbPerSec,fOutgoingKbPerSec,nIncomingPacketsPerSec,nOutgoingPacketsPerSec);
float fPacketSize=(20+8)*8.0f/1024.0f; // 20bytes IP + 8bytes UDP in kBit
y++;
pRenderer->TextToScreen(10.0f,(float)y,"BANDWIDTH IN=%.2f/%.2f Kbits/sec (%d) OUT=%.2f/%.2f Kbits/sec (%d)",
fIncomingKbPerSec, fIncomingKbPerSec+fPacketSize*nIncomingPacketsPerSec, nIncomingPacketsPerSec,
fOutgoingKbPerSec, fOutgoingKbPerSec+fPacketSize*nOutgoingPacketsPerSec, nOutgoingPacketsPerSec);
// just for internal testing purpose
#ifndef REDUCED_FOR_PUBLIC_RELEASE
y+=3;
pRenderer->TextToScreen(0,(float)y,"Subpackets produced (might not be sent):");
static DWORD sResetCount=0;
bool bNewDataArrived = m_NetStats.GetResetCount()!=sResetCount;
sResetCount=m_NetStats.GetResetCount();
for(int i=0;;i++)
{
DWORD dwRelCount,dwUnrelCount,dwItem;
size_t nBitSize;
if(!m_NetStats.GetStats(i,dwItem,dwRelCount,dwUnrelCount,nBitSize))
break;
pRenderer->TextToScreen(10,y+(i+1)*3.0f,"(Rel:%d Unrel:%d Bits:%d) %s",dwRelCount,dwUnrelCount,nBitSize,GetMsgName((XCLIENTMSG)dwItem));
if(bNewDataArrived)
m_pGame->m_pLog->Log("Server (Rel:%d Unrel:%d Bits:%d) %s",dwRelCount,dwUnrelCount,nBitSize,GetMsgName((XCLIENTMSG)dwItem));
}
if(bNewDataArrived)
m_pGame->m_pLog->Log("-------<netstats end>");
#endif
}
const float fX = 10.0f;
const float fY = 440.0f;
const float fGraphWidth = 780.0f;
const float fGraphHeight = 150.0f;
if(sv_netstats->GetIVal() & 0x2) // display a updatecount graph
{
pRenderer->Set2DMode(1, 800, 600);
pRenderer->SetMaterialColor(1,1,1,1);
float fScale = fGraphWidth / 1000.0f;
XSlotMap::iterator sit = m_mapXSlots.begin();
CXServerSlot *pSlot=0;
if(sit!=m_mapXSlots.end())
pSlot=sit->second;
if(pSlot)
{
IEntity *pPlayerEntity = m_pISystem->GetEntity(pSlot->GetPlayerId());
if(pPlayerEntity)
{
Vec3d vPos = pPlayerEntity->GetPos();
int n = 0;
for (NetEntityListItor eit = pSlot->m_Snapshot.m_lstNetEntities.begin(); eit != pSlot->m_Snapshot.m_lstNetEntities.end(); ++eit)
{
float fDist = sqrtf(eit->GetDistanceTo(vPos));
if(eit->GetEntity()->GetClassId()==SYNCHED2DTABLE_CLASS_ID) // no positional entity
fDist = 500.0f;
if(fDist >= 1000)
continue;
int i;
if(m_pGame->GetSystem()->GetIEntitySystem()->IsDynamicEntityId(eit->GetEntity()->GetId()))
i=(int)(0xffff-eit->GetEntity()->GetId());
else
i=1000-(int)(eit->GetEntity()->GetId());
if(i>=1000)i=1000-1; // clamp in max range
if(i<0)i=0; // clamp in min range
float x = fX + fDist * fScale;
float h = m_NetStats.GetSumGraphValue(i);
float y = fY + fGraphHeight - h;
pRenderer->Draw2dLine(x, y, x, y+h);
}
pRenderer->Draw2dLine(fX, fY+fGraphHeight, fX + fGraphWidth, fY+fGraphHeight);
}
}
pRenderer->Set2DMode(0, 0, 0);
}
}
///////////////////////////////////////////////
bool CXServer::CreateServerSlot(IServerSlot *pIServerSlot)
{
// check if there are to many players
if((int)(m_mapXSlots.size())>=sv_maxplayers->GetIVal())
{
// if this is not a dedicated server and
// we have less than one slot, it means that our local client cannot connect
// so let's open a slot for him
if (!m_pGame->m_pSystem->IsDedicated() && sv_maxplayers->GetIVal() < 1)
{
sv_maxplayers->Set(1);
}
else
{
NET_TRACE("<<NET>>REJECTING CONNECTION SERVER FULL");
pIServerSlot->Disconnect("@ServerFull");
return false;
}
}
CXServerSlot *pSlot = new CXServerSlot(this,pIServerSlot);
NET_TRACE("<<NET>>REGISTERING SERVER SLOT");
RegisterSlot(pSlot);
return true;
}
#define ADDSTRING(c, s) { (c) += s; c.push_back('\0'); }
#define ADDBOOL(c, b) { (c).push_back(b ? '\1' : '\0'); }
#define ADDCHAR(c, ch) { (c).push_back((char)ch); }
#if defined(WIN64)
#define ADDINT(c, i) { char t=(char)(i & 0x000000FF);c+=t;t=(char)((i&0x0000FF00)>>8);c+=t;t=(char)((i&0x00FF0000)>>16);c+=t;t=(char)((i&0xFF000000)>>24);c+=t;}
#else
#define ADDINT(c, i) { for(int j=0;j<4;j++) (c).push_back(((char *)&(i))[j]); }
#endif
#define ADDRULE(c, name, value) (c) += name; (c).push_back('\0'); (c) += value; (c).push_back('\0');
//-------------------------------------------------------------------------------------------------
bool CXServer::GetServerInfoStatus(string &szServerStatus)
{
if (!GetServerInfo())
return false;
int nPort = m_ServerInfos.nPort;
char szVersion[128];
m_ServerInfos.VersionInfo.ToString(szVersion);
ADDINT(szServerStatus, nPort);
ADDCHAR(szServerStatus, m_ServerInfos.nComputerType);
ADDSTRING(szServerStatus, szVersion);
ADDSTRING(szServerStatus, m_ServerInfos.strName);
ADDSTRING(szServerStatus, m_ServerInfos.strMod);
ADDSTRING(szServerStatus, m_ServerInfos.strGameType);
ADDSTRING(szServerStatus, m_ServerInfos.strMap);
ADDCHAR(szServerStatus, m_ServerInfos.nPlayers);
ADDCHAR(szServerStatus, m_ServerInfos.nMaxPlayers);
ADDBOOL(szServerStatus, (m_ServerInfos.nServerFlags & SXServerInfos::FLAG_PASSWORD));
ADDBOOL(szServerStatus, (m_ServerInfos.nServerFlags & SXServerInfos::FLAG_CHEATS));
ADDBOOL(szServerStatus, (m_ServerInfos.nServerFlags & SXServerInfos::FLAG_NET));
ADDBOOL(szServerStatus, (m_ServerInfos.nServerFlags & SXServerInfos::FLAG_PUNKBUSTER));
return true;
}
//-------------------------------------------------------------------------------------------------
bool CXServer::GetServerInfoStatus(string &szName, string &szGameType, string &szMap, string &szVersion, bool *pbPassword, int *piPlayers, int *piMaxPlayers)
{
if (!GetServerInfo())
return false;
szName = m_ServerInfos.strName;
szGameType = m_ServerInfos.strGameType;
szMap = m_ServerInfos.strMap;
char szLocalVersion[128];
m_ServerInfos.VersionInfo.ToString(szLocalVersion);
szVersion = szLocalVersion;
*pbPassword = (m_ServerInfos.nServerFlags & SXServerInfos::FLAG_PASSWORD);
*piPlayers = m_ServerInfos.nPlayers;
*piMaxPlayers = m_ServerInfos.nMaxPlayers;
return true;
}
//-------------------------------------------------------------------------------------------------
bool CXServer::GetServerInfoRules(string &szServerRules)
{
IScriptSystem *pSS = GetISystem()->GetIScriptSystem();
_SmartScriptObject QueryHandler(pSS, 1);
if (!pSS->GetGlobalValue("QueryHandler", (IScriptObject *)QueryHandler))
{
return false;
}
_SmartScriptObject ServerRules(pSS, 1);
pSS->BeginCall("QueryHandler", "GetServerRules");
pSS->PushFuncParam((IScriptObject *)QueryHandler);
pSS->EndCall((IScriptObject *)ServerRules);
int nPos = szServerRules.size();
int nRules = 0;
for (int i = 1; i <= ServerRules->Count(); i++)
{
_SmartScriptObject Rule(pSS, 1);
if (ServerRules->GetAt(i, (IScriptObject *)Rule))
{
char *szRuleName = 0;
char *szRuleValue = 0;
Rule->GetAt(1, szRuleName);
Rule->GetAt(2, szRuleValue);
if (szRuleValue && szRuleName)
{
++nRules;
ADDRULE(szServerRules, szRuleName, szRuleValue);
}
}
}
szServerRules.insert(nPos, 1, (char)nRules);
return true;
}
//-------------------------------------------------------------------------------------------------
bool CXServer::GetServerInfoPlayers(string *vszStrings[4], int &nStrings)
{
IScriptSystem *pSS = GetISystem()->GetIScriptSystem();
_SmartScriptObject QueryHandler(pSS, 1);
if (!pSS->GetGlobalValue("QueryHandler", (IScriptObject *)QueryHandler))
{
return false;
}
_SmartScriptObject PlayerStats(pSS, 1);
pSS->BeginCall("QueryHandler", "GetPlayerStats");
pSS->PushFuncParam((IScriptObject *)QueryHandler);
pSS->EndCall((IScriptObject *)PlayerStats);
string szPlayer;
string vszRString[6];
string *pszCurrent = &vszRString[0];
int iCurrentPos = pszCurrent->size();
int nPlayers = 0;
nStrings = 1;
for (int i = 1; i <= PlayerStats->Count(); i++)
{
_SmartScriptObject Player(pSS, 1);
if (PlayerStats->GetAt(i, (IScriptObject *)Player))
{
char *szName = 0;
char *szTeam = 0;
char *szSkin = 0;
int iScore = 0;
int iPing = 0;
int iTime = 0;
Player->GetValue("Name", (const char* &)szName);
Player->GetValue("Team", (const char* &)szTeam);
Player->GetValue("Skin", (const char* &)szSkin);
Player->GetValue("Score", iScore);
Player->GetValue("Ping", iPing);
Player->GetValue("Time", iTime);
szPlayer.resize(0);
ADDSTRING(szPlayer, szName ? szName : "");
ADDSTRING(szPlayer, szTeam ? szTeam : "");
ADDSTRING(szPlayer, szSkin ? szSkin : "");
ADDINT(szPlayer, iScore);
ADDINT(szPlayer, iPing);
ADDINT(szPlayer, iTime);
if (pszCurrent->size() + szPlayer.size() > SERVER_QUERY_PACKET_SIZE)
{
pszCurrent->insert(iCurrentPos, 1, (char)nPlayers);
++pszCurrent;
++nStrings;
if (nStrings <= SERVER_QUERY_MAX_PACKETS)
{
iCurrentPos = pszCurrent->size();
}
else
{
nStrings = SERVER_QUERY_MAX_PACKETS;
break;
}
nPlayers = 0;
}
(*pszCurrent) += szPlayer;
++nPlayers;
}
}
pszCurrent->insert(iCurrentPos, 1, (char)nPlayers);
for (int i = 0; i < nStrings; i++)
{
char n = i+1;
ADDCHAR(*vszStrings[i], n);
ADDCHAR(*vszStrings[i], nStrings);
(*vszStrings[i]) += vszRString[i];
}
return true;
}
#undef ADDSTRING
#undef ADDINT
#undef ADDBOOL
#undef ADDRULE
///////////////////////////////////////////////
bool CXServer::GetServerInfo()
{
const char *szGameType = m_ServerRules.GetGameType();
if(!szGameType)
return false;
IGameMods *pGameMods=m_pGame->GetModsInterface();
if(!pGameMods)
return false; // Game Init failed?
m_ServerInfos.strName = sv_name->GetString();
m_ServerInfos.strGameType = szGameType;
m_ServerInfos.strMod = pGameMods->GetCurrentMod();
m_ServerInfos.nPlayers = GetNumPlayers();
m_ServerInfos.nMaxPlayers = sv_maxplayers->GetIVal();
m_ServerInfos.nServerFlags = 0;
if (sv_password->GetString() && (strlen(sv_password->GetString()) > 0))
{
m_ServerInfos.nServerFlags |= SXServerInfos::FLAG_PASSWORD;
}
if(m_pIServer->GetServerType()!=eMPST_LAN)
{
m_ServerInfos.nServerFlags |= SXServerInfos::FLAG_NET;
}
if (m_pGame->IsDevModeEnable())
{
m_ServerInfos.nServerFlags |= SXServerInfos::FLAG_CHEATS;
}
ICVar *pPunkBusterVar = GetISystem()->GetIConsole()->GetCVar("sv_punkbuster");
if (pPunkBusterVar && pPunkBusterVar->GetIVal() != 0)
{
m_ServerInfos.nServerFlags |= SXServerInfos::FLAG_PUNKBUSTER;
}
m_ServerInfos.nComputerType = 0;
return true;
}
//////////////////////////////////////////////////////////////////////////
bool CXServer::ProcessXMLInfoRequest( const char *sRequest,const char *sRespone,int nResponseMaxLength )
{
XmlNodeRef reqNode = GetISystem()->LoadXmlFromString( sRequest );
if (!reqNode)
return false;
if (strlen(sRespone) > 0)
return true;
return false;
}
///////////////////////////////////////////////
void CXServer::RegisterSlot(CXServerSlot *pSlot)
{
NET_TRACE("<<NET>>CXServer::RegisterSlot %d",pSlot->GetID());
m_mapXSlots.insert(XSlotMap::iterator::value_type(pSlot->GetID(),pSlot));
}
///////////////////////////////////////////////
void CXServer::UnregisterXSlot(DWORD nClientID)
{
NET_TRACE("<<NET>>CXServer::UnregisterXSlot");
}
///////////////////////////////////////////////
void CXServer::ClearSlots()
{
XSlotMap::iterator itor;
//Disconnect all slots
itor = m_mapXSlots.begin();
while(itor != m_mapXSlots.end())
{
CXServerSlot *pSlot=itor->second;
if (m_pGame->IsMultiplayer() && !pSlot->IsLocalHost())
pSlot->Disconnect("@ServerShutdown");
++itor;
}
//Update The network to send the disconnection
UpdateXServerNetwork();
itor = m_mapXSlots.begin();
while(itor != m_mapXSlots.end())
{
delete itor->second;
++itor;
}
m_mapXSlots.clear();
}
///////////////////////////////////////////////
void CXServer::Update()
{
// limit sv_maxplayers
{
if(sv_maxplayers->GetIVal()>MAXPLAYERS_LIMIT)
sv_maxplayers->Set(MAXPLAYERS_LIMIT);
int iPlayerCount=GetNumPlayers();
if(sv_maxplayers->GetIVal()<iPlayerCount)
sv_maxplayers->Set(iPlayerCount);
}
// kick "excess" players
// if this is a dedicated server, minimum can be 0, if we are not, minimum must be, so that our local client is not kicked
while(m_mapXSlots.size() > (int)(max(sv_maxplayers->GetIVal(), (m_pGame->m_pSystem->IsDedicated() ? 0 : 1))))
{
CXServerSlot *pSlot = (m_mapXSlots.rbegin()->second);
if (!pSlot->IsLocalHost())
{
pSlot->Disconnect("@Kicked");
}
}
// update server rules.
m_ServerRules.Update();
if(!m_pIServer) return;
UpdateXServerNetwork();
float time = m_pTimer->GetCurrTime();
bool sendevents=m_pGame->UseFixedStep() && m_pGame->HasScheduledEvents();
// Garbage collection and update of the slots
XSlotMap::iterator i = m_mapXSlots.begin();
while(i != m_mapXSlots.end())
{
CXServerSlot *pSlot = i->second;
if(pSlot->IsXServerSlotGarbage())
{
delete pSlot;
XSlotMap::iterator inext = i; inext++;
m_mapXSlots.erase(i);
i = inext;
// NET_TRACE("<<NET>>CXServer::Update::Remove A Garbage"); // <<FIXME>> remove...
}
else
{
ASSERT(pSlot->m_Snapshot.GetSendPerSecond());
float fRelTime = time - pSlot->m_Snapshot.GetLastUpdate();
bool sendsnap = m_pGame->IsMultiplayer() && (fRelTime)>(1.f/pSlot->m_Snapshot.GetSendPerSecond());
if(fRelTime<0) // timer was reseted
{
fRelTime=0;
sendsnap=true;
}
pSlot->Update(sendsnap,sendevents&&sendsnap);
++i;
}
}
//incrementing the random seed
// m_pISystem->SetRandomSeed(m_pISystem->GetRandomSeed()+1);
UpdateXServerNetwork(); // [Anton] if we formed a snapshot - then send it now
///////////////////////////////////////////////////////////////////////
//SNAPSHOT UPDATE
///////////////////////////////////////////////////////////////////////
m_NetStats.Update(m_pTimer->GetCurrTimePrecise()); // keep statistics for one sec
m_NetStats.AddToUpdateCount(1);
}
void CXServer::UpdateXServerNetwork()
{
FUNCTION_PROFILER( GetISystem(), PROFILE_GAME );
if(m_pIServer)
m_pIServer->Update(GetCurrentTime());
};
///////////////////////////////////////////////
// return the current context
bool CXServer::GetContext(SXGameContext &ctxOut)
{
ctxOut = m_GameContext;
return false;
}
void CXServer::AddRespawnPoint(ITagPoint *pPoint)
{
m_pGame->m_pLog->Log("CXServer::AddRespawnPoint '%s'",pPoint->GetName());
m_vRespawnPoints.insert(RespawnPointMap::iterator::value_type(pPoint->GetName(),pPoint));
m_CurRespawnPoint=m_vRespawnPoints.begin();
}
//////////////////////////////////////////////////////////////////////////
void CXServer::RemoveRespawnPoint(ITagPoint *pPoint)
{
RespawnPointMap::iterator itor=m_vRespawnPoints.begin();
while(itor!=m_vRespawnPoints.end())
{
if((itor->second)==pPoint)
{
m_vRespawnPoints.erase(itor);
return;
}
++itor;
}
}
///////////////////////////////////////////////
// get a specific respawn point
ITagPoint* CXServer::GetFirstRespawnPoint()
{
if(m_vRespawnPoints.empty())
return NULL;
m_CurRespawnPoint=m_vRespawnPoints.begin();
return m_CurRespawnPoint->second;
}
///////////////////////////////////////////////
// get a specific respawn point
ITagPoint* CXServer::GetNextRespawnPoint()
{
if(m_vRespawnPoints.empty())
return NULL;
++m_CurRespawnPoint;
if(m_CurRespawnPoint==m_vRespawnPoints.end())
return 0;
return m_CurRespawnPoint->second;
}
///////////////////////////////////////////////
// get a specific respawn point
ITagPoint* CXServer::GetPrevRespawnPoint()
{
if(m_vRespawnPoints.empty())
return NULL;
RespawnPointMap::reverse_iterator revCurRespawnPoint(m_CurRespawnPoint);
if( revCurRespawnPoint!=m_vRespawnPoints.rend() )
{
if(++revCurRespawnPoint == m_vRespawnPoints.rend() )
{
m_CurRespawnPoint = m_vRespawnPoints.begin();
}
else
m_CurRespawnPoint = revCurRespawnPoint.base();
}
else
{
revCurRespawnPoint = m_vRespawnPoints.rbegin();
++revCurRespawnPoint;
m_CurRespawnPoint = revCurRespawnPoint.base();
}
// if(m_CurRespawnPoint==m_vRespawnPoints.end())
// {
// m_CurRespawnPoint = m_vRespawnPoints.begin();
// return NULL;
// }
return m_CurRespawnPoint->second;
}
///////////////////////////////////////////////
// get a specific respawn point
ITagPoint* CXServer::GetRespawnPoint(char *name)
{
RespawnPointMap::iterator itr=m_vRespawnPoints.find(name);
if(itr == m_vRespawnPoints.end())
return NULL;
return itr->second;
}
///////////////////////////////////////////////
// get a random respawn point
ITagPoint* CXServer::GetRandomRespawnPoint(const char *sFilter)
{
if(m_vRespawnPoints.empty())
{
m_pGame->m_pLog->Log("CXServer::GetRandomRespawnPoint NO RESPAWN POINT");
return NULL; // no respawn point
}
int RandomRespawn = 0;
ITagPoint *point;
int count;
RespawnPointMap::iterator itr;
if(sFilter==NULL)
{
count=m_vRespawnPoints.size();
itr=m_vRespawnPoints.begin();
}
else
{
count=m_vRespawnPoints.count(sFilter);
if(!count)
{
m_pGame->m_pLog->Log("CXServer::GetRandomRespawnPoint NO RESPAWN POINT[%s]",sFilter);
return false; // no respawn point
}
itr=m_vRespawnPoints.find(sFilter);
}
RandomRespawn=rand() % count;
// m_pGame->m_pLog->Log("CXServer::GetRandomRespawnPoint '%s' %d/%d",
// sFilter?sFilter:"<no filter>",RandomRespawn,count);
while( RandomRespawn-- )
++itr;
// point = m_vRespawnPoints[RandomRespawn];
point = itr->second;
ASSERT(point);
/*
if(point)
{
Vec3 pos;
point->GetPos(pos);
m_pGame->m_pLog->Log("CXServer::GetRandomRespawnPoint (%.2f %.2f %.2f)",
pos.x,pos.y,pos.z );
}
else
m_pGame->m_pLog->Log("CXServer::GetRandomRespawnPoint (0)");
*/
return point;
}
///////////////////////////////////////////////
void CXServer::BroadcastReliable(XSERVERMSG msg, CStream &stmIn,bool bSecondaryChannel)
{
CStream stmToSend;
stmToSend.WritePkd(msg);
stmToSend.Write(stmIn);
// now... broadcast !
XSlotMap::iterator i;
for(i=m_mapXSlots.begin(); i!=m_mapXSlots.end(); ++i)
{
CXServerSlot *pSlot = i->second;
if(!pSlot->IsXServerSlotGarbage() && pSlot->IsReady() && pSlot->IsContextReady())
pSlot->SendReliable(stmToSend,bSecondaryChannel);
}
}
void CXServer::BroadcastUnreliable(XSERVERMSG msg, CStream &stmIn,int nExclude)
{
CStream stmToSend;
stmToSend.WritePkd(msg);
stmToSend.Write(stmIn);
// now... broadcast !
XSlotMap::iterator i;
for(i=m_mapXSlots.begin(); i!=m_mapXSlots.end(); ++i)
{
CXServerSlot *pSlot = i->second;
if(!pSlot->IsXServerSlotGarbage() && pSlot->IsReady() && !nExclude==pSlot->GetID() && pSlot->IsContextReady())
pSlot->SendUnreliable(stmToSend);
}
}
void CXServer::BroadcastText(const char *sText, float fLifeTime)
{
// now... broadcast !
XSlotMap::iterator i;
for(i=m_mapXSlots.begin(); i!=m_mapXSlots.end(); ++i)
{
CXServerSlot *pSlot = i->second;
if(!pSlot->IsXServerSlotGarbage() && pSlot->IsReady() && pSlot->IsContextReady())
pSlot->SendText(sText, fLifeTime);
}
}
void CXServer::OnClientMsgText(EntityId sender,CStream &stm)
{
TextMessage tm;
tm.Read(stm);
m_ServerRules.OnClientMsgText(sender,tm);
}
void CXServer::BindEntity(EntityId idParent,EntityId idChild,unsigned char cParam)
{
IEntity *pChild=m_pISystem->GetEntity(idChild);
IEntity *pParent=m_pISystem->GetEntity(idParent);
CStream stm;
stm.Write(idParent);
stm.Write(idChild);
stm.Write(cParam);
stm.Write(true);
stm.Write(pParent->GetAngles(1));
stm.Write(pChild->GetAngles(1));
BroadcastReliable(XSERVERMSG_BINDENTITY,stm,false);
}
void CXServer::UnbindEntity(EntityId idParent,EntityId idChild,unsigned char cParam)
{
IEntity *pChild=m_pISystem->GetEntity(idChild);
IEntity *pParent=m_pISystem->GetEntity(idParent);
CStream stm;
stm.Write(idParent);
stm.Write(idChild);
stm.Write(cParam);
stm.Write(false);
stm.Write(pParent->GetAngles(1));
stm.Write(pChild->GetAngles(1));
BroadcastReliable(XSERVERMSG_BINDENTITY,stm,false);
}
void CXServer::OnMapChanged()
{
m_ServerRules.MapChanged();
};
int CXServer::GetNumPlayers()
{
//the num of players is the num of connected slots also if are spectators the count
return m_mapXSlots.size();
}
void CXServer::AddToTeam(const char *sTeam,int eid)
{
int nTID=m_pISystem->GetTeamId(sTeam);
if(nTID!=-1)
{
m_pISystem->SetTeam(eid,nTID);
CStream stm;
WRITE_COOKIE(stm);
stm.Write((EntityId)eid);
stm.Write((BYTE)nTID);
WRITE_COOKIE(stm);
BroadcastReliable(XSERVERMSG_SETTEAM, stm,false);
}
}
void CXServer::RemoveFromTeam(int eid)
{
m_pISystem->SetTeam(eid,0xFF);
CStream stm;
BYTE nNoTeam=0xFF;
WRITE_COOKIE(stm);
stm.Write((EntityId)eid);
stm.Write(nNoTeam);
WRITE_COOKIE(stm);
BroadcastReliable(XSERVERMSG_SETTEAM, stm,false);
}
void CXServer::AddTeam(const char *sTeam)
{
int nTID=m_pISystem->GetTeamId(sTeam);
if(nTID==-1)
{
nTID=m_pISystem->AddTeam(sTeam);
CStream stm;
WRITE_COOKIE(stm);
stm.Write(sTeam);
stm.Write((BYTE)nTID);
WRITE_COOKIE(stm);
BroadcastReliable(XSERVERMSG_ADDTEAM, stm,false);
}
}
void CXServer::RemoveTeam(const char *sTeam)
{
int nTID=m_pISystem->GetTeamId(sTeam);
if(nTID!=-1)
{
m_pISystem->RemoveTeam(nTID);
CStream stm;
stm.Write((BYTE)nTID);
BroadcastReliable(XSERVERMSG_REMOVETEAM, stm,false);
}
}
void CXServer::SendRequestScriptHash( EntityId Entity, const char *szPath, const char *szKey )
{
CStream stm;
IBitStream *pBitStream = m_pGame->GetIBitStream();
uint32 dwServerStartHash=0; // todo change
pBitStream->WriteBitStream(stm,Entity,eEntityId); // e.g. INVALID_WID for globals, otherwise it's and entity
pBitStream->WriteBitStream(stm,szPath,255,eASCIIText); // e.g. "cnt.myTable"
pBitStream->WriteBitStream(stm,szKey,255,eASCIIText); // e.g. "luaFunc1" or ""
pBitStream->WriteBitStream(stm,dwServerStartHash,eDoNotCompress); // start hash
BroadcastReliable(XSERVERMSG_REQUESTSCRIPTHASH,stm,true); // send in secondary channel to prevent stall
m_pGame->m_pLog->Log("RequestScriptHash '%s' '%s'",szPath,szKey);
}
void CXServer::SetTeamScore(const char *sTeam,int score)
{
int nTID=m_pISystem->GetTeamId(sTeam);
if(nTID!=-1)
{
CStream stm;
WRITE_COOKIE(stm);
int curscore=m_pISystem->GetTeamScore(nTID);
if(curscore==score)return;
m_pISystem->SetTeamScore(nTID,score);
stm.Write((BYTE)nTID);
stm.Write((short)score);
WRITE_COOKIE(stm);
BroadcastReliable(XSERVERMSG_SETTEAMSCORE, stm,true);
}
}
void CXServer::SetTeamFlags(const char *sTeam,int flags)
{
int nTID=m_pISystem->GetTeamId(sTeam);
if(nTID!=-1)
{
CStream stm;
WRITE_COOKIE(stm);
int curflags=m_pISystem->GetTeamFlags(nTID);
if(curflags==flags)return;
m_pISystem->SetTeamFlags(nTID,flags);
stm.Write((BYTE)nTID);
stm.WritePkd(flags);
WRITE_COOKIE(stm);
BroadcastReliable(XSERVERMSG_SETTEAMFLAGS, stm,true);
}
}
void CXServer::BroadcastCommand(const char *sCmd)
{
// now... broadcast !
XSlotMap::iterator i;
for(i=m_mapXSlots.begin(); i!=m_mapXSlots.end(); ++i)
{
CXServerSlot *pSlot = i->second;
if(!pSlot->IsXServerSlotGarbage() && pSlot->IsReady() && pSlot->IsContextReady())
pSlot->SendCommand(sCmd);
}
}
void CXServer::BroadcastCommand(const char *sCmd, const Vec3 &invPos, const Vec3 &invNormal,
const EntityId inId, const unsigned char incUserByte )
{
// now... broadcast !
XSlotMap::iterator i;
for(i=m_mapXSlots.begin(); i!=m_mapXSlots.end(); ++i)
{
CXServerSlot *pSlot = i->second;
if(!pSlot->IsXServerSlotGarbage() && pSlot->IsReady() && pSlot->IsContextReady())
pSlot->SendCommand(sCmd, invPos, invNormal, inId, incUserByte);
}
}
//////////////////////////////////////////////////////////////////////////
void CXServer::SyncVariable(ICVar *p)
{
CStream stm;
stm.Write(p->GetName());
stm.Write(p->GetString());
XSlotMap::iterator i = m_mapXSlots.begin();
while(i != m_mapXSlots.end())
{
CXServerSlot *pSlot = i->second;
if(pSlot->IsXServerSlotGarbage() || (!pSlot->IsReady()) )
{
++i;
continue;
}
pSlot->SendReliableMsg(XSERVERMSG_SYNCVAR,stm,true,p->GetName());
++i;
}
}
//////////////////////////////////////////////////////////////////////////
void CXServer::SyncAIState(void )
{
CStream stm;
// [marco] petar add your data into the stream - please
// make it as small as possible
int nDummy=123;
stm.Write(nDummy);
XSlotMap::iterator i = m_mapXSlots.begin();
while(i != m_mapXSlots.end())
{
CXServerSlot *pSlot = i->second;
if (pSlot->IsXServerSlotGarbage() || (!pSlot->IsReady()) )
{
++i;
continue;
}
// [marco] petar, now it broadcast AI state to all clients -
// if you need to send only to a certain client which is better
// please restrict the range
// I suppose you need it reliable - in this
// case do not abuse bandwidth
pSlot->SendReliableMsg(XSERVERMSG_AISTATE,stm,false);
++i;
} // i
}
//////////////////////////////////////////////////////////////////////////
unsigned int CXServer::GetSchedulingDelay()
{
assert(sv_min_scheduling_delay);
assert(sv_max_scheduling_delay);
if(!sv_min_scheduling_delay || !sv_max_scheduling_delay)
{
m_pGame->m_pLog->LogError("CXServer::GetSchedulingDelay error");
return 100;
}
unsigned int nDelay=sv_min_scheduling_delay->GetIVal();
unsigned int nMaxDelay=sv_max_scheduling_delay->GetIVal();
XSlotMap::iterator i = m_mapXSlots.begin();
for(;i!=m_mapXSlots.end();++i)
{
CXServerSlot *slot=i->second; assert(slot);
if(slot)
nDelay = max(nDelay,min(nMaxDelay, slot->GetPing()*3>>2)); // half-ping multiplied by 1.5
}
return m_pGame->SnapTime(nDelay*0.001f,1.0f);
}
//////////////////////////////////////////////////////////////////////////
CXServerSlot* CXServer::GetServerSlotByIP( unsigned int clientIP ) const
{
for(XSlotMap::const_iterator itor=m_mapXSlots.begin();itor!=m_mapXSlots.end();++itor)
{
CXServerSlot *slot=itor->second;
if(slot->GetIServerSlot()->GetClientIP() == clientIP)
return itor->second;
}
return 0;
}
//-------------------------------------------------------------------------------------------------
bool CXServer::IsIDBanned(const BannedID &ID)
{
for (BannedIDListItor it = m_vBannedIDList.begin(); it != m_vBannedIDList.end(); ++it)
{
if (ID == *it)
{
return true;
}
}
return false;
}
//-------------------------------------------------------------------------------------------------
void CXServer::BanID(const BannedID &ID)
{
if (!IsIDBanned(ID))
{
m_vBannedIDList.push_back(ID);
}
SaveBanList(1, 0);
}
//-------------------------------------------------------------------------------------------------
void CXServer::UnbanID(const BannedID &ID)
{
for (BannedIDListItor it = m_vBannedIDList.begin(); it != m_vBannedIDList.end(); ++it)
{
if (ID == *it)
{
m_vBannedIDList.erase(it);
SaveBanList(1, 0);
return;
}
}
}
//-------------------------------------------------------------------------------------------------
bool CXServer::IsIPBanned(const unsigned int dwIP)
{
for (BannedIPListItor it = m_vBannedIPList.begin(); it != m_vBannedIPList.end(); ++it)
{
if (dwIP == *it)
{
return true;
}
}
return false;
}
//-------------------------------------------------------------------------------------------------
void CXServer::BanIP(const unsigned int dwIP)
{
if (!IsIPBanned(dwIP))
{
m_vBannedIPList.push_back(dwIP);
}
SaveBanList(0, 1);
}
//-------------------------------------------------------------------------------------------------
void CXServer::UnbanIP(const unsigned int dwIP)
{
for (BannedIPListItor it = m_vBannedIPList.begin(); it != m_vBannedIPList.end(); ++it)
{
if (dwIP == *it)
{
m_vBannedIPList.erase(it);
SaveBanList(0, 1);
return;
}
}
}
//////////////////////////////////////////////////////////////////////////
void CXServer::CheaterFound( const unsigned int dwIP,int type,const char *sMsg )
{
CXServerSlot *pSlot = GetServerSlotByIP(dwIP);
if (pSlot)
{
string str = string("Client ") + pSlot->GetName() +" detected to be a $3Cheater";
BroadcastText( str.c_str() );
CryLogAlways( "<CHEATER> #%d %s",pSlot->GetID(),(const char*)pSlot->GetName() );
// Kick offender.
if (!pSlot->IsLocalHost())
{
if (m_pGame->sv_cheater_kick->GetIVal())
pSlot->Disconnect( sMsg );
}
}
if (m_pGame->sv_cheater_ban->GetIVal() && m_pGame->sv_cheater_kick->GetIVal())
BanIP(dwIP);
}
//////////////////////////////////////////////////////////////////////////
bool CXServer::GetSlotInfo( const unsigned int dwIP,SSlotInfo &info , int nameOnly )
{
memset ( &info , 0 , sizeof ( info ) ) ;
CXServerSlot *pSlot = GetServerSlotByIP(dwIP);
if (pSlot)
{
strncpy( info.playerName,pSlot->GetName(),sizeof(info.playerName) );
info.playerName[sizeof(info.playerName)-1] = 0;
if ( *info.playerName == 0 && pSlot->CanSpawn() ) strcpy ( info.playerName , "*NO NAME*" ) ;
if ( nameOnly ) return true;
IEntity *pPlayerEntity = m_pISystem->GetEntity(pSlot->GetPlayerId());
if(pPlayerEntity && pPlayerEntity->GetContainer())
{
CPlayer *pPlayer = NULL;
pPlayerEntity->GetContainer()->QueryContainerInterface(CIT_IPLAYER,(void **)&pPlayer);
if (pPlayer)
{
info.score = pPlayer->m_stats.score;
info.deaths = pPlayer->m_stats.deaths;
}
}
return true;
}
return false;
}
void CXServer::SaveBanList(bool bSaveID, bool bSaveIP)
{
if (bSaveIP)
{
GetISystem()->GetILog()->Log("\001Saving banned IP list...");
FILE *hFile = fopen("bannedip.txt", "w+");
if (!hFile)
{
GetISystem()->GetILog()->Log("\001Failed to open bannedip.txt for writing!");
return;
}
for (BannedIPList::iterator it = m_vBannedIPList.begin(); it != m_vBannedIPList.end(); ++it)
{
char szLine[256];
CIPAddress ip;
ip.m_Address.ADDR = *it;
sprintf(szLine, "%s\n", ip.GetAsString());
fputs(szLine, hFile);
#if defined(LINUX)
RemoveCRLF(szLine);
#endif
}
fclose(hFile);
}
if (bSaveID)
{
GetISystem()->GetILog()->Log("\001Saving banned ID list...");
FILE *hFile = fopen("bannedid.txt", "w+");
if (!hFile)
{
GetISystem()->GetILog()->Log("\001Failed to open bannedid.txt for writing!");
return;
}
for (BannedIDList::iterator it = m_vBannedIDList.begin(); it != m_vBannedIDList.end(); ++it)
{
char szLine[256] = {0};
BannedID ban(*it);
char szBanID[256] = {0};
for (int i = 0; i < ban.bSize; i++)
{
sprintf(&szBanID[i*2], "%02x", ban.vBanID[i]);
}
sprintf(szLine, "%-36s %s\n", szBanID, it->szName.c_str());
fputs(szLine, hFile);
#if defined(LINUX)
RemoveCRLF(szLine);
#endif
}
fclose(hFile);
}
}
void CXServer::LoadBanList(bool bLoadID, bool bLoadIP)
{
if (bLoadIP)
{
// load banned ips
m_vBannedIPList.clear();
GetISystem()->GetILog()->Log("\001Loading banned IP list...");
FILE *hFile = fopen("bannedip.txt", "r");
if (!hFile)
{
GetISystem()->GetILog()->Log("\001ERROR: failed to open bannedip.txt for reading!");
return;
}
char szLine[1024] = {0};
while(fgets(szLine, 1024, hFile))
{
char szIP[1024] = {0};
// remove trailing spaces
if (sscanf(szLine, "%s", szIP) != 1)
{
continue;
}
CIPAddress ip(0, szIP);
if (ip.GetAsUINT())
{
m_vBannedIPList.push_back(ip.GetAsUINT());
}
}
fclose(hFile);
hFile = 0;
}
if (bLoadID)
{
// load banned ids
m_vBannedIDList.clear();
GetISystem()->GetILog()->Log("\001Loading banned ID list...");
FILE *hFile = fopen("bannedid.txt", "r");
if (!hFile)
{
GetISystem()->GetILog()->Log("ERROR: failed to open bannedid.txt for reading!");
return;
}
char szLine[1024] = {0};
while(fgets(szLine, 1024, hFile))
{
char szName[1024] = {0};
char szBanID[1024] = {0};
char szByte[5] = {'0', 'x', 0, 0, 0};
// remove trailing spaces
if (sscanf(szLine, "%s %s", szBanID, szName) != 2)
{
if (sscanf(szLine, "%s", szBanID) != 1)
{
continue;
}
szName[0] = 0;
}
int isize = strlen(szBanID);
BannedID ban;
ban.bSize = isize >> 1;
ban.szName = szName;
// must be at least a dword
if (ban.bSize < 8)
{
continue;
}
for (int i = 0; i < ban.bSize; i++)
{
szByte[2] = szBanID[i*2];
szByte[3] = szBanID[i*2+1];
int b = 0;
sscanf(szByte, "%x", &b);
ban.vBanID[i] = b;
}
m_vBannedIDList.push_back(ban);
}
fclose(hFile);
}
}