////////////////////////////////////////////////////////////////////// // // 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 #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::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("<>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("<>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("<>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("<>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("<> CXServerSlot::OnRemoveEntity[%s] [%d]",GetName(),pEntity->GetId()); } else { NET_TRACE("<> 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("<>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("<>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("<>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("<>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("<>END CXServerSlot::OnContextReady"); } /////////////////////////////////////////////// void CXServerSlot::OnData(CStream &stm) { if(stm.GetReadPos()!=0) { CryError( " (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("<>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("<>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); //<> 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-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)) { //<> 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(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(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; }