////////////////////////////////////////////////////////////////////// // // 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 #include #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 #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("-------"); #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("<>REJECTING CONNECTION SERVER FULL"); pIServerSlot->Disconnect("@ServerFull"); return false; } } CXServerSlot *pSlot = new CXServerSlot(this,pIServerSlot); NET_TRACE("<>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("<>CXServer::RegisterSlot %d",pSlot->GetID()); m_mapXSlots.insert(XSlotMap::iterator::value_type(pSlot->GetID(),pSlot)); } /////////////////////////////////////////////// void CXServer::UnregisterXSlot(DWORD nClientID) { NET_TRACE("<>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()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("<>CXServer::Update::Remove A Garbage"); // <> 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:"",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( " #%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); } }