#include "../idlib/precompiled.h" #pragma hdrstop #include "Game_local.h" // RAVEN BEGIN // bdube: client entities #include "client/ClientEffect.h" // shouchard: ban list support #define BANLIST_FILENAME "banlist.txt" // RAVEN END /* =============================================================================== Client running game code: - entity events don't work and should not be issued - entities should never be spawned outside idGameLocal::ClientReadSnapshot =============================================================================== */ // adds tags to the network protocol to detect when things go bad ( internal consistency ) // NOTE: this changes the network protocol #ifndef ASYNC_WRITE_TAGS #define ASYNC_WRITE_TAGS 0 #endif idCVar net_clientShowSnapshot( "net_clientShowSnapshot", "0", CVAR_GAME | CVAR_INTEGER, "", 0, 3, idCmdSystem::ArgCompletion_Integer<0,3> ); idCVar net_clientShowSnapshotRadius( "net_clientShowSnapshotRadius", "128", CVAR_GAME | CVAR_FLOAT, "" ); idCVar net_clientMaxPrediction( "net_clientMaxPrediction", "1000", CVAR_SYSTEM | CVAR_INTEGER | CVAR_NOCHEAT, "maximum number of milliseconds a client can predict ahead of server." ); idCVar net_clientLagOMeter( "net_clientLagOMeter", "0", CVAR_GAME | CVAR_BOOL | CVAR_NOCHEAT | PC_CVAR_ARCHIVE, "draw prediction graph" ); // RAVEN BEGIN // ddynerman: performance profiling int net_entsInSnapshot; int net_snapshotSize; extern const int ASYNC_PLAYER_FRAG_BITS; // RAVEN END /* ================ idGameLocal::InitAsyncNetwork ================ */ void idGameLocal::InitAsyncNetwork( void ) { memset( clientEntityStates, 0, sizeof( clientEntityStates ) ); memset( clientPVS, 0, sizeof( clientPVS ) ); memset( clientSnapshots, 0, sizeof( clientSnapshots ) ); eventQueue.Init(); // NOTE: now that we removed spawning by typeNum, we could stick to a >0 entityDefBits // not making the change at this point because of proto69 back compat stuff entityDefBits = -( idMath::BitsForInteger( declManager->GetNumDecls( DECL_ENTITYDEF ) ) + 1 ); localClientNum = 0; // on a listen server SetLocalUser will set this right realClientTime = 0; isNewFrame = true; } /* ================ idGameLocal::ShutdownAsyncNetwork ================ */ void idGameLocal::ShutdownAsyncNetwork( void ) { entityStateAllocator.Shutdown(); snapshotAllocator.Shutdown(); eventQueue.Shutdown(); memset( clientEntityStates, 0, sizeof( clientEntityStates ) ); memset( clientPVS, 0, sizeof( clientPVS ) ); memset( clientSnapshots, 0, sizeof( clientSnapshots ) ); } /* ================ idGameLocal::InitLocalClient ================ */ void idGameLocal::InitLocalClient( int clientNum ) { isServer = false; isClient = true; localClientNum = clientNum; } /* ================ idGameLocal::ServerAllowClient clientId is the ID of the connecting client - can later be mapped to a clientNum by calling networkSystem->ServerGetClientNum( clientId ) ================ */ allowReply_t idGameLocal::ServerAllowClient( int clientId, int numClients, const char *IP, const char *guid, const char *password, const char *privatePassword, char reason[ MAX_STRING_CHARS ] ) { reason[0] = '\0'; // RAVEN BEGIN // shouchard: ban support if ( IsGuidBanned( guid ) ) { idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107239" ); return ALLOW_NO; } // RAVEN END if ( serverInfo.GetInt( "si_pure" ) && !mpGame.IsPureReady() ) { idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107139" ); return ALLOW_NOTYET; } if ( !serverInfo.GetInt( "si_maxPlayers" ) ) { idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107140" ); return ALLOW_NOTYET; } // completely full if ( numClients >= serverInfo.GetInt( "si_maxPlayers" ) ) { idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107141" ); return ALLOW_NOTYET; } // check private clients if( serverInfo.GetInt( "si_privatePlayers" ) > 0 ) { // just in case somehow we have a stale private clientId that matches a new client mpGame.RemovePrivatePlayer( clientId ); const char *privatePass = cvarSystem->GetCVarString( "g_privatePassword" ); if( privatePass[ 0 ] == '\0' ) { common->Warning( "idGameLocal::ServerAllowClient() - si_privatePlayers > 0 with no g_privatePassword" ); cmdSystem->BufferCommandText( CMD_EXEC_NOW, "say si_privatePlayers is set but g_privatePassword is empty" ); idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107142" ); return ALLOW_NOTYET; } int numPrivateClients = cvarSystem->GetCVarInteger( "si_numPrivatePlayers" ); // private clients that take up public slots are considered public clients numPrivateClients = idMath::ClampInt( 0, serverInfo.GetInt( "si_privatePlayers" ), numPrivateClients ); if ( !idStr::Cmp( privatePass, privatePassword ) ) { // once this client spawns in, they'll be marked private mpGame.AddPrivatePlayer( clientId ); } else if( (numClients - numPrivateClients) >= (serverInfo.GetInt( "si_maxPlayers" ) - serverInfo.GetInt( "si_privatePlayers" )) ) { // if the number of public clients is greater than or equal to the number of public slots, require a private slot idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107141" ); return ALLOW_NOTYET; } } if ( !cvarSystem->GetCVarBool( "si_usePass" ) ) { return ALLOW_YES; } const char *pass = cvarSystem->GetCVarString( "g_password" ); if ( pass[ 0 ] == '\0' ) { common->Warning( "si_usePass is set but g_password is empty" ); cmdSystem->BufferCommandText( CMD_EXEC_NOW, "say si_usePass is set but g_password is empty" ); // avoids silent misconfigured state idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107142" ); return ALLOW_NOTYET; } if ( !idStr::Cmp( pass, password ) ) { return ALLOW_YES; } idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107143" ); Printf( "Rejecting client %s from IP %s: invalid password\n", guid, IP ); return ALLOW_BADPASS; } /* ================ idGameLocal::ServerClientConnect ================ */ void idGameLocal::ServerClientConnect( int clientNum, const char *guid ) { // make sure no parasite entity is left if ( entities[ clientNum ] ) { common->DPrintf( "ServerClientConnect: remove old player entity\n" ); delete entities[ clientNum ]; } unreliableMessages[ clientNum ].Init( 0 ); userInfo[ clientNum ].Clear(); mpGame.ServerClientConnect( clientNum ); Printf( "client %d connected.\n", clientNum ); } /* ================ idGameLocal::ServerClientBegin ================ */ void idGameLocal::ServerClientBegin( int clientNum ) { idBitMsg outMsg; byte msgBuf[MAX_GAME_MESSAGE_SIZE]; // spawn the player SpawnPlayer( clientNum ); if ( clientNum == localClientNum ) { mpGame.EnterGame( clientNum ); } // ddynerman: connect time ((idPlayer*)entities[ clientNum ])->SetConnectTime( time ); // send message to spawn the player at the clients outMsg.Init( msgBuf, sizeof( msgBuf ) ); outMsg.BeginWriting(); outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SPAWN_PLAYER ); outMsg.WriteByte( clientNum ); outMsg.WriteLong( spawnIds[ clientNum ] ); networkSystem->ServerSendReliableMessage( -1, outMsg ); if( gameType != GAME_TOURNEY ) { ((idPlayer*)entities[ clientNum ])->JoinInstance( 0 ); } else { // instance 0 might be empty in Tourney ((idPlayer*)entities[ clientNum ])->JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( 0 ) ); } //RAVEN BEGIN //asalmon: This client has finish loading and will be spawned mark them as ready. #ifdef _XENON Live()->ClientReady(clientNum); #endif //RAVEN END } /* ================ idGameLocal::ServerClientDisconnect clientNum == MAX_CLIENTS for cleanup of server demo recording data ================ */ void idGameLocal::ServerClientDisconnect( int clientNum ) { int i; idBitMsg outMsg; byte msgBuf[MAX_GAME_MESSAGE_SIZE]; if ( clientNum < MAX_CLIENTS ) { outMsg.Init( msgBuf, sizeof( msgBuf ) ); outMsg.BeginWriting(); outMsg.WriteByte( GAME_RELIABLE_MESSAGE_DELETE_ENT ); outMsg.WriteBits( ( spawnIds[ clientNum ] << GENTITYNUM_BITS ) | clientNum, 32 ); // see GetSpawnId networkSystem->ServerSendReliableMessage( -1, outMsg ); } // free snapshots stored for this client FreeSnapshotsOlderThanSequence( clientNum, 0x7FFFFFFF ); // free entity states stored for this client for ( i = 0; i < MAX_GENTITIES; i++ ) { if ( clientEntityStates[ clientNum ][ i ] ) { entityStateAllocator.Free( clientEntityStates[ clientNum ][ i ] ); clientEntityStates[ clientNum ][ i ] = NULL; } } // clear the client PVS memset( clientPVS[ clientNum ], 0, sizeof( clientPVS[ clientNum ] ) ); if ( clientNum == MAX_CLIENTS ) { return; } // only drop MP clients if we're in multiplayer and the server isn't going down if ( gameLocal.isMultiplayer && !(gameLocal.isListenServer && clientNum == gameLocal.localClientNum ) ) { // idMultiplayerGame::DisconnectClient will do the delete in MP mpGame.DisconnectClient( clientNum ); } else { // delete the player entity delete entities[ clientNum ]; } } /* ================ idGameLocal::ServerWriteInitialReliableMessages Send reliable messages to initialize the client game up to a certain initial state. ================ */ void idGameLocal::ServerWriteInitialReliableMessages( int clientNum ) { int i; idBitMsg outMsg; byte msgBuf[MAX_GAME_MESSAGE_SIZE]; // spawn players for ( i = 0; i < MAX_CLIENTS; i++ ) { if ( entities[i] == NULL || i == clientNum ) { continue; } outMsg.Init( msgBuf, sizeof( msgBuf ) ); outMsg.BeginWriting( ); outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SPAWN_PLAYER ); outMsg.WriteByte( i ); outMsg.WriteLong( spawnIds[ i ] ); networkSystem->ServerSendReliableMessage( clientNum, outMsg ); } // update portals for opened doors int numPortals = gameRenderWorld->NumPortals(); outMsg.Init( msgBuf, sizeof( msgBuf ) ); outMsg.BeginWriting(); outMsg.WriteByte( GAME_RELIABLE_MESSAGE_PORTALSTATES ); outMsg.WriteLong( numPortals ); for ( i = 0; i < numPortals; i++ ) { outMsg.WriteBits( gameRenderWorld->GetPortalState( (qhandle_t) (i+1) ) , NUM_RENDER_PORTAL_BITS ); } networkSystem->ServerSendReliableMessage( clientNum, outMsg ); mpGame.ServerWriteInitialReliableMessages( clientNum ); } /* ================ idGameLocal::FreeSnapshotsOlderThanSequence ================ */ void idGameLocal::FreeSnapshotsOlderThanSequence( int clientNum, int sequence ) { snapshot_t *snapshot, *lastSnapshot, *nextSnapshot; entityState_t *state; for ( lastSnapshot = NULL, snapshot = clientSnapshots[clientNum]; snapshot; snapshot = nextSnapshot ) { nextSnapshot = snapshot->next; if ( snapshot->sequence < sequence ) { for ( state = snapshot->firstEntityState; state; state = snapshot->firstEntityState ) { snapshot->firstEntityState = snapshot->firstEntityState->next; entityStateAllocator.Free( state ); } if ( lastSnapshot ) { lastSnapshot->next = snapshot->next; } else { clientSnapshots[clientNum] = snapshot->next; } snapshotAllocator.Free( snapshot ); } else { lastSnapshot = snapshot; } } } /* ================ idGameLocal::ApplySnapshot ================ */ bool idGameLocal::ApplySnapshot( int clientNum, int sequence ) { snapshot_t *snapshot, *lastSnapshot, *nextSnapshot; entityState_t *state; FreeSnapshotsOlderThanSequence( clientNum, sequence ); for ( lastSnapshot = NULL, snapshot = clientSnapshots[clientNum]; snapshot; snapshot = nextSnapshot ) { nextSnapshot = snapshot->next; if ( snapshot->sequence == sequence ) { for ( state = snapshot->firstEntityState; state; state = state->next ) { if ( clientEntityStates[clientNum][state->entityNumber] ) { entityStateAllocator.Free( clientEntityStates[clientNum][state->entityNumber] ); } clientEntityStates[clientNum][state->entityNumber] = state; } // ~512 bytes memcpy( clientPVS[clientNum], snapshot->pvs, sizeof( snapshot->pvs ) ); if ( lastSnapshot ) { lastSnapshot->next = nextSnapshot; } else { clientSnapshots[clientNum] = nextSnapshot; } snapshotAllocator.Free( snapshot ); return true; } else { lastSnapshot = snapshot; } } return false; } /* ================ idGameLocal::WriteGameStateToSnapshot ================ */ void idGameLocal::WriteGameStateToSnapshot( idBitMsgDelta &msg ) const { int i; for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { msg.WriteFloat( globalShaderParms[i] ); } mpGame.WriteToSnapshot( msg ); } /* ================ idGameLocal::ReadGameStateFromSnapshot ================ */ void idGameLocal::ReadGameStateFromSnapshot( const idBitMsgDelta &msg ) { int i; for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { globalShaderParms[i] = msg.ReadFloat(); } mpGame.ReadFromSnapshot( msg ); } /* ================ idGameLocal::ServerWriteSnapshot Write a snapshot of the current game state for the given client. ================ */ // RAVEN BEGIN // jnewquist: Use dword array to match pvs array so we don't have endianness problems. void idGameLocal::ServerWriteSnapshot( int clientNum, int sequence, idBitMsg &msg, dword *clientInPVS, int numPVSClients, int lastSnapshotFrame ) { // RAVEN END int i, msgSize, msgWriteBit; idPlayer *player, *spectated = NULL; idEntity *ent; pvsHandle_t pvsHandle; idBitMsgDelta deltaMsg; snapshot_t *snapshot; entityState_t *base, *newBase; int numSourceAreas, sourceAreas[ idEntity::MAX_PVS_AREAS ]; player = static_cast( entities[ clientNum ] ); if ( !player ) { return; } if ( player->spectating && player->spectator != clientNum && entities[ player->spectator ] ) { spectated = static_cast< idPlayer * >( entities[ player->spectator ] ); } else { spectated = player; } // free too old snapshots // ( that's a security, normal acking from server keeps a smaller backlog of snaps ) FreeSnapshotsOlderThanSequence( clientNum, sequence - 64 ); // allocate new snapshot snapshot = snapshotAllocator.Alloc(); snapshot->sequence = sequence; snapshot->firstEntityState = NULL; snapshot->next = clientSnapshots[clientNum]; clientSnapshots[clientNum] = snapshot; memset( snapshot->pvs, 0, sizeof( snapshot->pvs ) ); // get PVS for this player // don't use PVSAreas for networking - PVSAreas depends on animations (and md5 bounds), which are not synchronized numSourceAreas = gameRenderWorld->BoundsInAreas( spectated->GetPlayerPhysics()->GetAbsBounds(), sourceAreas, idEntity::MAX_PVS_AREAS ); pvsHandle = gameLocal.pvs.SetupCurrentPVS( sourceAreas, numSourceAreas, PVS_NORMAL ); #if ASYNC_WRITE_TAGS idRandom tagRandom; tagRandom.SetSeed( random.RandomInt() ); msg.WriteLong( tagRandom.GetSeed() ); #endif // write unreliable messages unreliableMessages[ clientNum ].FlushTo( msg ); #if ASYNC_WRITE_TAGS msg.WriteLong( tagRandom.RandomInt() ); #endif // create the snapshot for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { // if the entity is not in the player PVS if ( !ent->PhysicsTeamInPVS( pvsHandle ) && ent->entityNumber != clientNum ) { continue; } // RAVEN BEGIN // ddynerman: don't transmit entities not in your clip world if ( ent->GetInstance() != player->GetInstance() ) { continue; } // RAVEN END // if the entity is a map entity, mark it in PVS if ( isMapEntity[ ent->entityNumber ] ) { snapshot->pvs[ ent->entityNumber >> 5 ] |= 1 << ( ent->entityNumber & 31 ); } // if that entity is not marked for network synchronization if ( !ent->fl.networkSync ) { continue; } // add the entity to the snapshot PVS snapshot->pvs[ ent->entityNumber >> 5 ] |= 1 << ( ent->entityNumber & 31 ); // save the write state to which we can revert when the entity didn't change at all msg.SaveWriteState( msgSize, msgWriteBit ); // write the entity to the snapshot msg.WriteBits( ent->entityNumber, GENTITYNUM_BITS ); base = clientEntityStates[clientNum][ent->entityNumber]; if ( base ) { base->state.BeginReading(); } newBase = entityStateAllocator.Alloc(); newBase->entityNumber = ent->entityNumber; newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); newBase->state.BeginWriting(); deltaMsg.InitWriting( base ? &base->state : NULL, &newBase->state, &msg ); deltaMsg.WriteBits( spawnIds[ ent->entityNumber ], 32 - GENTITYNUM_BITS ); assert( ent->entityDefNumber > 0 ); deltaMsg.WriteBits( ent->entityDefNumber, entityDefBits ); // write the class specific data to the snapshot ent->WriteToSnapshot( deltaMsg ); if ( !deltaMsg.HasChanged() ) { msg.RestoreWriteState( msgSize, msgWriteBit ); entityStateAllocator.Free( newBase ); } else { newBase->next = snapshot->firstEntityState; snapshot->firstEntityState = newBase; #if ASYNC_WRITE_TAGS msg.WriteLong( tagRandom.RandomInt() ); #endif } } msg.WriteBits( ENTITYNUM_NONE, GENTITYNUM_BITS ); // write the PVS to the snapshot #if ASYNC_WRITE_PVS for ( i = 0; i < idEntity::MAX_PVS_AREAS; i++ ) { if ( i < numSourceAreas ) { msg.WriteLong( sourceAreas[ i ] ); } else { msg.WriteLong( 0 ); } } gameLocal.pvs.WritePVS( pvsHandle, msg ); #endif for ( i = 0; i < ENTITY_PVS_SIZE; i++ ) { msg.WriteDeltaLong( clientPVS[clientNum][i], snapshot->pvs[i] ); } // free the PVS pvs.FreeCurrentPVS( pvsHandle ); // write the game and player state to the snapshot base = clientEntityStates[clientNum][ENTITYNUM_NONE]; // ENTITYNUM_NONE is used for the game and player state if ( base ) { base->state.BeginReading(); } newBase = entityStateAllocator.Alloc(); newBase->entityNumber = ENTITYNUM_NONE; newBase->next = snapshot->firstEntityState; snapshot->firstEntityState = newBase; newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); newBase->state.BeginWriting(); deltaMsg.InitWriting( base ? &base->state : NULL, &newBase->state, &msg ); if ( player->spectating && player->spectator != player->entityNumber && entities[ player->spectator ] ) { assert( entities[ player->spectator ]->IsType( idPlayer::GetClassType() ) ); deltaMsg.WriteBits( player->spectator, idMath::BitsForInteger( MAX_CLIENTS ) ); static_cast< idPlayer * >( gameLocal.entities[ player->spectator ] )->WritePlayerStateToSnapshot( deltaMsg ); } else { deltaMsg.WriteBits( player->entityNumber, idMath::BitsForInteger( MAX_CLIENTS ) ); player->WritePlayerStateToSnapshot( deltaMsg ); } WriteGameStateToSnapshot( deltaMsg ); // copy the client PVS string // RAVEN BEGIN // JSinger: Changed to call optimized memcpy // jnewquist: Use dword array to match pvs array so we don't have endianness problems. const int numDwords = ( numPVSClients + 31 ) >> 5; for ( i = 0; i < numDwords; i++ ) { clientInPVS[i] = snapshot->pvs[i]; } // RAVEN END } /* =============== idGameLocal::ServerWriteServerDemoSnapshot =============== */ void idGameLocal::ServerWriteServerDemoSnapshot( int sequence, idBitMsg &msg, int lastSnapshotFrame ) { snapshot_t *snapshot; int i, msgSize, msgWriteBit; idEntity *ent; entityState_t *base, *newBase; idBitMsgDelta deltaMsg; bool ret = ServerApplySnapshot( MAX_CLIENTS, sequence - 1 ); ret = ret; // silence warning assert( ret || sequence == 1 ); // past the first snapshot of the server demo stream, there's always exactly one to clear snapshot = snapshotAllocator.Alloc(); snapshot->sequence = sequence; snapshot->firstEntityState = NULL; snapshot->next = clientSnapshots[ MAX_CLIENTS ]; clientSnapshots[ MAX_CLIENTS ] = snapshot; unreliableMessages[ MAX_CLIENTS ].FlushTo( msg ); for ( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { if ( !ent->fl.networkSync ) { continue; } // only record instance 0 ( tourney games ) if ( ent->GetInstance() != 0 ) { continue; } msg.SaveWriteState( msgSize, msgWriteBit ); msg.WriteBits( ent->entityNumber, GENTITYNUM_BITS ); base = clientEntityStates[ MAX_CLIENTS ][ ent->entityNumber ]; if ( base ) { base->state.BeginReading(); } newBase = entityStateAllocator.Alloc(); newBase->entityNumber = ent->entityNumber; newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); newBase->state.BeginWriting(); deltaMsg.InitWriting( base ? &base->state : NULL, &newBase->state, &msg ); deltaMsg.WriteBits( spawnIds[ ent->entityNumber ], 32 - GENTITYNUM_BITS ); assert( ent->entityDefNumber > 0 ); deltaMsg.WriteBits( ent->entityDefNumber, entityDefBits ); ent->WriteToSnapshot( deltaMsg ); if ( !deltaMsg.HasChanged() ) { msg.RestoreWriteState( msgSize, msgWriteBit ); entityStateAllocator.Free( newBase ); } else { newBase->next = snapshot->firstEntityState; snapshot->firstEntityState = newBase; } } msg.WriteBits( ENTITYNUM_NONE, GENTITYNUM_BITS ); // write player states and game states base = clientEntityStates[MAX_CLIENTS][ENTITYNUM_NONE]; // ENTITYNUM_NONE is used for the game and player state if ( base ) { base->state.BeginReading(); } newBase = entityStateAllocator.Alloc(); newBase->entityNumber = ENTITYNUM_NONE; newBase->next = snapshot->firstEntityState; snapshot->firstEntityState = newBase; newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); newBase->state.BeginWriting(); deltaMsg.InitWriting( base ? &base->state : NULL, &newBase->state, &msg ); // all the players for ( i = 0; i < numClients; i++ ) { if ( entities[i] ) { assert( entities[i]->IsType( idPlayer::GetClassType() ) ); idPlayer *p = static_cast< idPlayer * >( entities[i] ); p->WritePlayerStateToSnapshot( deltaMsg ); } } // and the game state WriteGameStateToSnapshot( deltaMsg ); } /* ================ idGameLocal::ServerApplySnapshot ================ */ bool idGameLocal::ServerApplySnapshot( int clientNum, int sequence ) { return ApplySnapshot( clientNum, sequence ); } /* ================ idGameLocal::NetworkEventWarning ================ */ void idGameLocal::NetworkEventWarning( const entityNetEvent_t *event, const char *fmt, ... ) { char buf[1024]; int length = 0; va_list argptr; int entityNum = event->spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ); int id = event->spawnId >> GENTITYNUM_BITS; length += idStr::snPrintf( buf+length, sizeof(buf)-1-length, "event %d for entity %d %d: ", event->event, entityNum, id ); va_start( argptr, fmt ); length = idStr::vsnPrintf( buf+length, sizeof(buf)-1-length, fmt, argptr ); va_end( argptr ); idStr::Append( buf, sizeof(buf), "\n" ); common->DWarning( buf ); } /* ================ idGameLocal::ServerProcessEntityNetworkEventQueue ================ */ void idGameLocal::ServerProcessEntityNetworkEventQueue( void ) { idEntity *ent; entityNetEvent_t *event; idBitMsg eventMsg; while ( eventQueue.Start() ) { event = eventQueue.Start(); if ( event->time > time ) { break; } idEntityPtr< idEntity > entPtr; if( !entPtr.SetSpawnId( event->spawnId ) ) { NetworkEventWarning( event, "Entity does not exist any longer, or has not been spawned yet." ); } else { ent = entPtr.GetEntity(); assert( ent ); eventMsg.Init( event->paramsBuf, sizeof( event->paramsBuf ) ); eventMsg.SetSize( event->paramsSize ); eventMsg.BeginReading(); if ( !ent->ServerReceiveEvent( event->event, event->time, eventMsg ) ) { NetworkEventWarning( event, "unknown event" ); } } #ifdef _DEBUG entityNetEvent_t* freedEvent = eventQueue.Dequeue(); assert( freedEvent == event ); #else eventQueue.Dequeue(); #endif eventQueue.Free( event ); } } /* ================ idGameLocal::ServerSendChatMessage ================ */ void idGameLocal::ServerSendChatMessage( int to, const char *name, const char *text, const char *parm ) { idBitMsg outMsg; byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; outMsg.Init( msgBuf, sizeof( msgBuf ) ); outMsg.BeginWriting(); outMsg.WriteByte( GAME_RELIABLE_MESSAGE_CHAT ); outMsg.WriteString( name ); outMsg.WriteString( text ); outMsg.WriteString( parm ); networkSystem->ServerSendReliableMessage( to, outMsg ); if ( to == -1 || to == localClientNum ) { idStr temp = va( "%s%s", common->GetLocalizedString( text ), parm ); mpGame.AddChatLine( "%s^0: %s", name, temp.c_str() ); } } /* ================ idGameLocal::ServerProcessReliableMessage ================ */ void idGameLocal::ServerProcessReliableMessage( int clientNum, const idBitMsg &msg ) { int id; id = msg.ReadByte(); switch( id ) { case GAME_RELIABLE_MESSAGE_CHAT: case GAME_RELIABLE_MESSAGE_TCHAT: { char name[128]; char text[128]; char parm[128]; msg.ReadString( name, sizeof( name ) ); msg.ReadString( text, sizeof( text ) ); // This parameter is ignored - it is only used when going to client from server msg.ReadString( parm, sizeof( parm ) ); mpGame.ProcessChatMessage( clientNum, id == GAME_RELIABLE_MESSAGE_TCHAT, name, text, NULL ); break; } case GAME_RELIABLE_MESSAGE_VCHAT: { int index = msg.ReadLong(); bool team = msg.ReadBits( 1 ) != 0; mpGame.ProcessVoiceChat( clientNum, team, index ); break; } case GAME_RELIABLE_MESSAGE_KILL: { mpGame.WantKilled( clientNum ); break; } case GAME_RELIABLE_MESSAGE_DROPWEAPON: { mpGame.DropWeapon( clientNum ); break; } case GAME_RELIABLE_MESSAGE_CALLVOTE: { mpGame.ServerCallVote( clientNum, msg ); break; } case GAME_RELIABLE_MESSAGE_CASTVOTE: { bool vote = ( msg.ReadByte() != 0 ); mpGame.CastVote( clientNum, vote ); break; } // RAVEN BEGIN // shouchard: multivalue votes case GAME_RELIABLE_MESSAGE_CALLPACKEDVOTE: { mpGame.ServerCallPackedVote( clientNum, msg ); break; } // RAVEN END #if 0 // uncomment this if you want to track when players are in a menu case GAME_RELIABLE_MESSAGE_MENU: { bool menuUp = ( msg.ReadBits( 1 ) != 0 ); break; } #endif case GAME_RELIABLE_MESSAGE_EVENT: { entityNetEvent_t *event; // allocate new event event = eventQueue.Alloc(); eventQueue.Enqueue( event, idEventQueue::OUTOFORDER_DROP ); event->spawnId = msg.ReadBits( 32 ); event->event = msg.ReadByte(); event->time = msg.ReadLong(); event->paramsSize = msg.ReadBits( idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); if ( event->paramsSize ) { if ( event->paramsSize > MAX_EVENT_PARAM_SIZE ) { NetworkEventWarning( event, "invalid param size" ); return; } msg.ReadByteAlign(); msg.ReadData( event->paramsBuf, event->paramsSize ); } break; } // RAVEN BEGIN // jscott: voice comms case GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT: case GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_ECHO: case GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_TEST: case GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_ECHO_TEST: { mpGame.ReceiveAndForwardVoiceData( clientNum, msg, id - GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT ); break; } // ddynerman: stats case GAME_RELIABLE_MESSAGE_STAT: { int client = msg.ReadByte(); statManager->SendStat( clientNum, client ); break; } // shouchard: voice chat case GAME_RELIABLE_MESSAGE_VOICECHAT_MUTING: { int clientDest = msg.ReadByte(); bool mute = ( 0 != msg.ReadByte() ); mpGame.ServerHandleVoiceMuting( clientNum, clientDest, mute ); break; } // shouchard: server admin case GAME_RELIABLE_MESSAGE_SERVER_ADMIN: { int commandType = msg.ReadByte(); int clientNum = msg.ReadByte(); if ( SERVER_ADMIN_REMOVE_BAN == commandType ) { mpGame.HandleServerAdminRemoveBan( "" ); } else if ( SERVER_ADMIN_KICK == commandType ) { mpGame.HandleServerAdminKickPlayer( clientNum ); } else if ( SERVER_ADMIN_FORCE_SWITCH == commandType ) { mpGame.HandleServerAdminForceTeamSwitch( clientNum ); } else { Warning( "Server admin packet with bad type %d", commandType ); } break; } // mekberg: get ban list for server case GAME_RELIABLE_MESSAGE_GETADMINBANLIST: { ServerSendBanList( clientNum ); break; } // RAVEN END default: { Warning( "Unknown client->server reliable message: %d", id ); break; } } } /* ================ idGameLocal::ClientShowSnapshot ================ */ void idGameLocal::ClientShowSnapshot( int clientNum ) const { int baseBits; idEntity *ent; idPlayer *player; idMat3 viewAxis; idBounds viewBounds; entityState_t *base; if ( !net_clientShowSnapshot.GetInteger() ) { return; } player = static_cast( entities[clientNum] ); if ( !player ) { return; } viewAxis = player->viewAngles.ToMat3(); viewBounds = player->GetPhysics()->GetAbsBounds().Expand( net_clientShowSnapshotRadius.GetFloat() ); for( ent = snapshotEntities.Next(); ent != NULL; ent = ent->snapshotNode.Next() ) { if ( net_clientShowSnapshot.GetInteger() == 1 && ent->snapshotBits == 0 ) { continue; } const idBounds &entBounds = ent->GetPhysics()->GetAbsBounds(); if ( !entBounds.IntersectsBounds( viewBounds ) ) { continue; } base = clientEntityStates[clientNum][ent->entityNumber]; if ( base ) { baseBits = base->state.GetNumBitsWritten(); } else { baseBits = 0; } if ( net_clientShowSnapshot.GetInteger() == 2 && baseBits == 0 ) { continue; } gameRenderWorld->DebugBounds( colorGreen, entBounds ); gameRenderWorld->DrawText( va( "%d: %s (%d,%d bytes of %d,%d)\n", ent->entityNumber, ent->name.c_str(), ent->snapshotBits >> 3, ent->snapshotBits & 7, baseBits >> 3, baseBits & 7 ), entBounds.GetCenter(), 0.1f, colorWhite, viewAxis, 1 ); } } /* ================ idGameLocal::UpdateLagometer ================ */ void idGameLocal::UpdateLagometer( int aheadOfServer, int dupeUsercmds ) { int i, j, ahead; for ( i = 0; i < LAGO_HEIGHT; i++ ) { memmove( (byte *)lagometer + LAGO_WIDTH * 4 * i, (byte *)lagometer + LAGO_WIDTH * 4 * i + 4, ( LAGO_WIDTH - 1 ) * 4 ); } j = LAGO_WIDTH - 1; for ( i = 0; i < LAGO_HEIGHT; i++ ) { lagometer[i][j][0] = lagometer[i][j][1] = lagometer[i][j][2] = lagometer[i][j][3] = 0; } ahead = idMath::Rint( (float)aheadOfServer / 16.0f ); if ( ahead >= 0 ) { for ( i = 2 * Max( 0, 5 - ahead ); i < 2 * 5; i++ ) { lagometer[i][j][1] = 255; lagometer[i][j][3] = 255; } } else { for ( i = 2 * 5; i < 2 * ( 5 + Min( 10, -ahead ) ); i++ ) { lagometer[i][j][0] = 255; lagometer[i][j][1] = 255; lagometer[i][j][3] = 255; } } for ( i = LAGO_HEIGHT - 2 * Min( 6, dupeUsercmds ); i < LAGO_HEIGHT; i++ ) { lagometer[i][j][0] = 255; lagometer[i][j][1] = 255; lagometer[i][j][3] = 255; } } /* ================ idGameLocal::ClientReadSnapshot ================ */ void idGameLocal::ClientReadSnapshot( int clientNum, int snapshotSequence, const int gameFrame, const int gameTime, const int dupeUsercmds, const int aheadOfServer, const idBitMsg &msg ) { int i, entityDefNumber, numBitsRead; idEntity *ent; idPlayer *player, *spectated; pvsHandle_t pvsHandle; idDict args; idBitMsgDelta deltaMsg; snapshot_t *snapshot; entityState_t *base, *newBase; int spawnId; int numSourceAreas, sourceAreas[ idEntity::MAX_PVS_AREAS ]; int proto69TypeNum = 0; bool proto69 = ( gameLocal.GetCurrentDemoProtocol() == 69 ); const idDeclEntityDef *decl; if ( net_clientLagOMeter.GetBool() && renderSystem && !IsServerDemo() ) { UpdateLagometer( aheadOfServer, dupeUsercmds ); if ( !renderSystem->UploadImage( LAGO_IMAGE, (byte *)lagometer, LAGO_IMG_WIDTH, LAGO_IMG_HEIGHT ) ) { common->Printf( "lagometer: UploadImage failed. turning off net_clientLagOMeter\n" ); net_clientLagOMeter.SetBool( false ); } } InitLocalClient( clientNum ); gameRenderWorld->DebugClear( time ); // update the game time framenum = gameFrame; time = gameTime; // RAVEN BEGIN // bdube: use GetMSec access rather than USERCMD_TIME previousTime = time - GetMSec(); // RAVEN END // so that StartSound/StopSound doesn't risk skipping isNewFrame = true; // clear the snapshot entity list snapshotEntities.Clear(); // allocate new snapshot snapshot = snapshotAllocator.Alloc(); snapshot->sequence = snapshotSequence; snapshot->firstEntityState = NULL; snapshot->next = clientSnapshots[clientNum]; clientSnapshots[clientNum] = snapshot; #if ASYNC_WRITE_TAGS idRandom tagRandom; tagRandom.SetSeed( msg.ReadLong() ); #endif ClientReadUnreliableMessages( msg ); #if ASYNC_WRITE_TAGS if ( msg.ReadLong() != tagRandom.RandomInt() ) { Error( "error after read unreliable" ); } #endif // read all entities from the snapshot for ( i = msg.ReadBits( GENTITYNUM_BITS ); i != ENTITYNUM_NONE; i = msg.ReadBits( GENTITYNUM_BITS ) ) { base = clientEntityStates[clientNum][i]; if ( base ) { base->state.BeginReading(); } newBase = entityStateAllocator.Alloc(); newBase->entityNumber = i; newBase->next = snapshot->firstEntityState; snapshot->firstEntityState = newBase; newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); newBase->state.BeginWriting(); numBitsRead = msg.GetNumBitsRead(); deltaMsg.InitReading( base ? &base->state : NULL, &newBase->state, &msg ); spawnId = deltaMsg.ReadBits( 32 - GENTITYNUM_BITS ); if ( proto69 ) { proto69TypeNum = deltaMsg.ReadBits( idClass::GetTypeNumBits() ); } entityDefNumber = deltaMsg.ReadBits( entityDefBits ); ent = entities[i]; // if there is no entity or an entity of the wrong type if ( !ent || ent->entityDefNumber != entityDefNumber || spawnId != spawnIds[ i ] ) { if ( i < MAX_CLIENTS && ent ) { // SPAWN_PLAYER should be taking care of spawning the entity with the right spawnId common->Warning( "ClientReadSnapshot: recycling client entity %d\n", i ); } delete ent; spawnCount = spawnId; args.Clear(); args.SetInt( "spawn_entnum", i ); args.Set( "name", va( "entity%d", i ) ); // assume any items spawned from a server-snapshot are in our instance if ( gameLocal.GetLocalPlayer() ) { args.SetInt( "instance", gameLocal.GetLocalPlayer()->GetInstance() ); } if ( entityDefNumber >= 0 ) { if ( entityDefNumber >= declManager->GetNumDecls( DECL_ENTITYDEF ) ) { Error( "server has %d entityDefs instead of %d", entityDefNumber, declManager->GetNumDecls( DECL_ENTITYDEF ) ); } decl = static_cast< const idDeclEntityDef * >( declManager->DeclByIndex( DECL_ENTITYDEF, entityDefNumber, false ) ); assert( decl && decl->GetType() == DECL_ENTITYDEF ); args.Set( "classname", decl->GetName() ); if ( !SpawnEntityDef( args, &ent ) || !entities[i] ) { Error( "Failed to spawn entity with classname '%s' of type '%s'", decl->GetName(), decl->dict.GetString( "spawnclass" ) ); } } else { // we no longer support spawning entities by type num only. we would only hit this when playing 1.2 demos for backward compatibility assert( proto69 ); switch ( proto69TypeNum ) { case 183: ent = SpawnEntityType( rvViewWeapon::GetClassType(), &args, true ); break; case 182: ent = SpawnEntityType( idAnimatedEntity::GetClassType(), &args, true ); ent->fl.networkSync = true; break; default: Error( "Unexpected protocol 69 typenum (%d) for spawning entity by type", proto69TypeNum ); } if ( !entities[i] ) { Error( "Failed to spawn entity by typenum %d ( protocol 69 backwards compat )", proto69TypeNum ); } } if ( i < MAX_CLIENTS && i >= numClients ) { numClients = i + 1; } } // add the entity to the snapshot list ent->snapshotNode.AddToEnd( snapshotEntities ); ent->snapshotSequence = snapshotSequence; // RAVEN BEGIN // bdube: stale network entities // Ensure the clipmodel is relinked when transitioning from state if ( ent->fl.networkStale ) { ent->GetPhysics()->LinkClip(); } // RAVEN END // read the class specific data from the snapshot ent->ReadFromSnapshot( deltaMsg ); // once we read new snapshot data, unstale the ent if( ent->fl.networkStale ) { ent->ClientUnstale(); ent->fl.networkStale = false; } ent->snapshotBits = msg.GetNumBitsRead() - numBitsRead; #if ASYNC_WRITE_TAGS if ( msg.ReadLong() != tagRandom.RandomInt() ) { //cmdSystem->BufferCommandText( CMD_EXEC_NOW, "writeGameState" ); assert( entityDefNumber >= 0 ); assert( entityDefNumber < declManager->GetNumDecls( DECL_ENTITYDEF ) ); const char * classname = declManager->DeclByIndex( DECL_ENTITYDEF, entityDefNumber, false )->GetName(); Error( "write to and read from snapshot out of sync for classname '%s'\n", classname ); } #endif } player = static_cast( entities[clientNum] ); if ( !player ) { return; } if ( player->spectating && player->spectator != clientNum && entities[ player->spectator ] ) { spectated = static_cast< idPlayer * >( entities[ player->spectator ] ); } else { spectated = player; } // get PVS for this player // don't use PVSAreas for networking - PVSAreas depends on animations (and md5 bounds), which are not synchronized numSourceAreas = gameRenderWorld->BoundsInAreas( spectated->GetPlayerPhysics()->GetAbsBounds(), sourceAreas, idEntity::MAX_PVS_AREAS ); pvsHandle = gameLocal.pvs.SetupCurrentPVS( sourceAreas, numSourceAreas, PVS_NORMAL ); // read the PVS from the snapshot #if ASYNC_WRITE_PVS int serverPVS[idEntity::MAX_PVS_AREAS]; i = numSourceAreas; while ( i < idEntity::MAX_PVS_AREAS ) { sourceAreas[ i++ ] = 0; } for ( i = 0; i < idEntity::MAX_PVS_AREAS; i++ ) { serverPVS[ i ] = msg.ReadLong(); } if ( memcmp( sourceAreas, serverPVS, idEntity::MAX_PVS_AREAS * sizeof( int ) ) ) { common->Warning( "client PVS areas != server PVS areas, sequence 0x%x", snapshotSequence ); for ( i = 0; i < idEntity::MAX_PVS_AREAS; i++ ) { common->DPrintf( "%3d ", sourceAreas[ i ] ); } common->DPrintf( "\n" ); for ( i = 0; i < idEntity::MAX_PVS_AREAS; i++ ) { common->DPrintf( "%3d ", serverPVS[ i ] ); } common->DPrintf( "\n" ); } gameLocal.pvs.ReadPVS( pvsHandle, msg ); #endif for ( i = 0; i < ENTITY_PVS_SIZE; i++ ) { snapshot->pvs[i] = msg.ReadDeltaLong( clientPVS[clientNum][i] ); } // RAVEN BEGIN // ddynerman: performance profiling net_entsInSnapshot += snapshotEntities.Num(); net_snapshotSize += msg.GetSize(); // RAVEN END // add entities in the PVS that haven't changed since the last applied snapshot idEntity *nextSpawnedEnt; for( ent = spawnedEntities.Next(); ent != NULL; ent = nextSpawnedEnt ) { nextSpawnedEnt = ent->spawnNode.Next(); // if the entity is already in the snapshot if ( ent->snapshotSequence == snapshotSequence ) { continue; } // if the entity is not in the snapshot PVS if ( !( snapshot->pvs[ent->entityNumber >> 5] & ( 1 << ( ent->entityNumber & 31 ) ) ) ) { if ( !ent->fl.networkSync ) { // don't do stale / unstale on entities that are not marked network sync continue; } if ( ent->PhysicsTeamInPVS( pvsHandle ) ) { if ( ent->entityNumber >= MAX_CLIENTS && isMapEntity[ ent->entityNumber ] ) { // server says it's not in PVS, client says it's in PVS // if that happens on map entities, most likely something is wrong // I can see that moving pieces along several PVS could be a legit situation though // this is a band aid, which means something is not done right elsewhere if ( net_warnStale.GetInteger() > 1 || ( net_warnStale.GetInteger() == 1 && !ent->fl.networkStale ) ) { common->Warning( "client thinks map entity 0x%x (%s) is stale, sequence 0x%x", ent->entityNumber, ent->name.c_str(), snapshotSequence ); } } // RAVEN BEGIN // bdube: hide while not in snapshot if ( !ent->fl.networkStale ) { if ( ent->ClientStale() ) { delete ent; ent = NULL; } else { ent->fl.networkStale = true; } } } else { if ( !ent->fl.networkStale ) { if ( ent->ClientStale() ) { delete ent; ent = NULL; } else { ent->fl.networkStale = true; } } } // RAVEN END continue; } // add the entity to the snapshot list ent->snapshotNode.AddToEnd( snapshotEntities ); ent->snapshotSequence = snapshotSequence; ent->snapshotBits = 0; // RAVEN BEGIN // bdube: hide while not in snapshot // Ensure the clipmodel is relinked when transitioning from state if ( ent->fl.networkStale ) { ent->GetPhysics()->LinkClip(); } // RAVEN END base = clientEntityStates[clientNum][ent->entityNumber]; if ( !base ) { // entity has probably fl.networkSync set to false // non netsynced map entities go in and out of PVS, and may need stale/unstale calls if ( ent->fl.networkStale ) { ent->ClientUnstale(); ent->fl.networkStale = false; } continue; } if ( !ent->fl.networkSync ) { // this is not supposed to happen // it did however, when restarting a map with a different inhibit of entities caused entity numbers to be laid differently // an idLight would occupy the entity number of an idItem for instance, and although it's not network-synced ( static level light ), // the presence of a base would cause the system to think that it is and corrupt things // we changed the map population so the entity numbers are kept the same no matter how things are inhibited // this code is left as a fall-through fixup / sanity type of thing // if this still happens, it's likely "client thinks map entity is stale" is happening as well, and we're still at risk of corruption Warning( "ClientReadSnapshot: entity %d of type %s is not networkSync and has a snapshot base", ent->entityNumber, ent->GetType()->classname ); entityStateAllocator.Free( clientEntityStates[clientNum][ent->entityNumber] ); clientEntityStates[clientNum][ent->entityNumber] = NULL; continue; } base->state.BeginReading(); deltaMsg.InitReading( &base->state, NULL, (const idBitMsg *)NULL ); spawnId = deltaMsg.ReadBits( 32 - GENTITYNUM_BITS ); if ( proto69 ) { deltaMsg.ReadBits( idClass::GetTypeNumBits() ); } entityDefNumber = deltaMsg.ReadBits( entityDefBits ); // read the class specific data from the base state ent->ReadFromSnapshot( deltaMsg ); // after snapshot read, notify client of unstale if ( ent->fl.networkStale ) { ent->ClientUnstale(); ent->fl.networkStale = false; } } // RAVEN BEGIN // ddynerman: add the ambient lights to the snapshot entities for( int i = 0; i < ambientLights.Num(); i++ ) { ambientLights[ i ]->snapshotNode.AddToEnd( snapshotEntities ); ambientLights[ i ]->fl.networkStale = false; } // RAVEN END // free the PVS pvs.FreeCurrentPVS( pvsHandle ); // read the game and player state from the snapshot base = clientEntityStates[clientNum][ENTITYNUM_NONE]; // ENTITYNUM_NONE is used for the game and player state if ( base ) { base->state.BeginReading(); } newBase = entityStateAllocator.Alloc(); newBase->entityNumber = ENTITYNUM_NONE; newBase->next = snapshot->firstEntityState; snapshot->firstEntityState = newBase; newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); newBase->state.BeginWriting(); deltaMsg.InitReading( base ? &base->state : NULL, &newBase->state, &msg ); int targetPlayer = deltaMsg.ReadBits( idMath::BitsForInteger( MAX_CLIENTS ) ); if ( entities[ targetPlayer ] ) { static_cast< idPlayer* >( entities[ targetPlayer ] )->ReadPlayerStateFromSnapshot( deltaMsg ); } else { player->ReadPlayerStateFromSnapshot( deltaMsg ); } ReadGameStateFromSnapshot( deltaMsg ); // visualize the snapshot ClientShowSnapshot( clientNum ); // process entity events ClientProcessEntityNetworkEventQueue(); } /* =============== idGameLocal::ClientReadServerDemoSnapshot server demos use a slightly different snapshot format mostly, we don't need to transmit any PVS visibility information, as we transmit the whole entity activity plus, we read that data to the virtual 'seeing it all' MAX_CLIENTS client =============== */ void idGameLocal::ClientReadServerDemoSnapshot( int sequence, const int gameFrame, const int gameTime, const idBitMsg &msg ) { int i; snapshot_t *snapshot; entityState_t *base, *newBase; idBitMsgDelta deltaMsg; int numBitsRead, spawnId, entityDefNumber; idEntity *ent; idDict args; const idDeclEntityDef *decl; int proto69TypeNum = 0; bool proto69 = ( gameLocal.GetCurrentDemoProtocol() == 69 ); bool ret = ClientApplySnapshot( MAX_CLIENTS, sequence - 1 ); ret = ret; // silence warning assert( ret || sequence == 1 ); // past the first snapshot of the server demo stream, there's always exactly one to clear gameRenderWorld->DebugClear( time ); framenum = gameFrame; time = gameTime; previousTime = time - GetMSec(); isNewFrame = true; snapshotEntities.Clear(); snapshot = snapshotAllocator.Alloc(); snapshot->sequence = sequence; snapshot->firstEntityState = NULL; snapshot->next = clientSnapshots[ MAX_CLIENTS ]; clientSnapshots[ MAX_CLIENTS ] = snapshot; ClientReadUnreliableMessages( msg ); for ( i = msg.ReadBits( GENTITYNUM_BITS ); i != ENTITYNUM_NONE; i = msg.ReadBits( GENTITYNUM_BITS ) ) { base = clientEntityStates[ MAX_CLIENTS ][i]; if ( base ) { base->state.BeginReading(); } newBase = entityStateAllocator.Alloc(); newBase->entityNumber = i; newBase->next = snapshot->firstEntityState; snapshot->firstEntityState = newBase; newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); newBase->state.BeginWriting(); numBitsRead = msg.GetNumBitsRead(); deltaMsg.InitReading( base ? &base->state : NULL, &newBase->state, &msg ); spawnId = deltaMsg.ReadBits( 32 - GENTITYNUM_BITS ); if ( proto69 ) { proto69TypeNum = deltaMsg.ReadBits( idClass::GetTypeNumBits() ); } entityDefNumber = deltaMsg.ReadBits( entityDefBits ); ent = entities[i]; // if there is no entity or an entity of the wrong type if ( !ent || ent->entityDefNumber != entityDefNumber || spawnId != spawnIds[ i ] ) { if ( i < MAX_CLIENTS && ent ) { // SpawnPlayer should be taking care of spawning the entity with the right spawnId common->Warning( "ClientReadServerDemoSnapshot: recycling client entity %d\n", i ); } delete ent; spawnCount = spawnId; args.Clear(); args.SetInt( "spawn_entnum", i ); args.Set( "name", va( "entity%d", i ) ); // assume any items spawned from a server-snapshot are in our instance // FIXME: hu ho gonna have to rework that for server demos if ( gameLocal.GetLocalPlayer() ) { args.SetInt( "instance", gameLocal.GetLocalPlayer()->GetInstance() ); } if ( entityDefNumber >= 0 ) { if ( entityDefNumber >= declManager->GetNumDecls( DECL_ENTITYDEF ) ) { Error( "server has %d entityDefs instead of %d", entityDefNumber, declManager->GetNumDecls( DECL_ENTITYDEF ) ); } decl = static_cast< const idDeclEntityDef * >( declManager->DeclByIndex( DECL_ENTITYDEF, entityDefNumber, false ) ); assert( decl && decl->GetType() == DECL_ENTITYDEF ); args.Set( "classname", decl->GetName() ); if ( !SpawnEntityDef( args, &ent ) || !entities[i] ) { Error( "Failed to spawn entity with classname '%s' of type '%s'", decl->GetName(), decl->dict.GetString( "spawnclass" ) ); } } else { // we no longer support spawning entities by type num only. we would only hit this when playing 1.2 demos for backward compatibility assert( proto69 ); switch ( proto69TypeNum ) { case 183: ent = SpawnEntityType( rvViewWeapon::GetClassType(), &args, true ); break; case 182: ent = SpawnEntityType( idAnimatedEntity::GetClassType(), &args, true ); ent->fl.networkSync = true; break; default: Error( "Unexpected protocol 69 typenum (%d) for spawning entity by type", proto69TypeNum ); } if ( !entities[i] ) { Error( "Failed to spawn entity by typenum %d ( protocol 69 backwards compat )", proto69TypeNum ); } } if ( i < MAX_CLIENTS && i >= numClients ) { numClients = i + 1; } } // add the entity to the snapshot list ent->snapshotNode.AddToEnd( snapshotEntities ); ent->snapshotSequence = sequence; // RAVEN BEGIN // bdube: stale network entities // Ensure the clipmodel is relinked when transitioning from state if ( ent->fl.networkStale ) { ent->GetPhysics()->LinkClip(); } // RAVEN END // read the class specific data from the snapshot ent->ReadFromSnapshot( deltaMsg ); // once we read new snapshot data, unstale the ent if( ent->fl.networkStale ) { ent->ClientUnstale(); ent->fl.networkStale = false; } ent->snapshotBits = msg.GetNumBitsRead() - numBitsRead; } // add entities that haven't changed since the last applied snapshot idEntity *nextSpawnedEnt; for( ent = spawnedEntities.Next(); ent != NULL; ent = nextSpawnedEnt ) { nextSpawnedEnt = ent->spawnNode.Next(); // if the entity is already in the snapshot if ( ent->snapshotSequence == sequence ) { continue; } // add the entity to the snapshot list ent->snapshotNode.AddToEnd( snapshotEntities ); ent->snapshotSequence = sequence; ent->snapshotBits = 0; // Ensure the clipmodel is relinked when transitioning from stale if ( ent->fl.networkStale ) { ent->GetPhysics()->LinkClip(); } base = clientEntityStates[ MAX_CLIENTS ][ ent->entityNumber ]; if ( !base ) { // entity has probably fl.networkSync set to false // non netsynced map entities go in and out of PVS, and may need stale/unstale calls if ( ent->fl.networkStale ) { ent->ClientUnstale(); ent->fl.networkStale = false; } continue; } base->state.BeginReading(); deltaMsg.InitReading( &base->state, NULL, (const idBitMsg *)NULL ); spawnId = deltaMsg.ReadBits( 32 - GENTITYNUM_BITS ); if ( proto69 ) { deltaMsg.ReadBits( idClass::GetTypeNumBits() ); } entityDefNumber = deltaMsg.ReadBits( entityDefBits ); // if the entity is not the right type if ( ent->entityDefNumber != entityDefNumber ) { // should never happen common->DWarning( "entity '%s' is not the right type ( 0x%x, expected 0x%x )", ent->GetName(), ent->entityDefNumber, entityDefNumber ); continue; } // read the class specific data from the base state ent->ReadFromSnapshot( deltaMsg ); // after snapshot read, notify client of unstale if( ent->fl.networkStale ) { // FIXME: does this happen ( in a server demo replay? ) assert( false ); ent->ClientUnstale(); ent->fl.networkStale = false; } } // RAVEN BEGIN // ddynerman: add the ambient lights to the snapshot entities for( i = 0; i < ambientLights.Num(); i++ ) { ambientLights[ i ]->snapshotNode.AddToEnd( snapshotEntities ); ambientLights[ i ]->fl.networkStale = false; } // RAVEN END // visualize the snapshot // FIXME // ClientShowSnapshot( MAX_CLIENTS ); // read the game and player states base = clientEntityStates[MAX_CLIENTS][ENTITYNUM_NONE]; // ENTITYNUM_NONE is used for the game and player state if ( base ) { base->state.BeginReading(); } newBase = entityStateAllocator.Alloc(); newBase->entityNumber = ENTITYNUM_NONE; newBase->next = snapshot->firstEntityState; snapshot->firstEntityState = newBase; newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); newBase->state.BeginWriting(); deltaMsg.InitReading( base ? &base->state : NULL, &newBase->state, &msg ); // all the players for ( i = 0; i < numClients; i++ ) { if ( entities[i] ) { assert( entities[i]->IsType( idPlayer::GetClassType() ) ); idPlayer *p = static_cast< idPlayer * >( entities[i] ); p->ReadPlayerStateFromSnapshot( deltaMsg ); } } // the game state ReadGameStateFromSnapshot( deltaMsg ); // process entity events ClientProcessEntityNetworkEventQueue(); } /* ================ idGameLocal::ClientApplySnapshot ================ */ bool idGameLocal::ClientApplySnapshot( int clientNum, int sequence ) { return ApplySnapshot( clientNum, sequence ); } /* ================ idGameLocal::ClientProcessEntityNetworkEventQueue ================ */ void idGameLocal::ClientProcessEntityNetworkEventQueue( void ) { idEntity *ent; entityNetEvent_t *event; idBitMsg eventMsg; while( eventQueue.Start() ) { event = eventQueue.Start(); // only process forward, in order if ( event->time > time ) { break; } idEntityPtr< idEntity > entPtr; if( !entPtr.SetSpawnId( event->spawnId ) ) { if( !gameLocal.entities[ event->spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ) ] ) { // if new entity exists in this position, silently ignore NetworkEventWarning( event, "Entity does not exist any longer, or has not been spawned yet." ); } } else { ent = entPtr.GetEntity(); assert( ent ); eventMsg.Init( event->paramsBuf, sizeof( event->paramsBuf ) ); eventMsg.SetSize( event->paramsSize ); eventMsg.BeginReading(); if ( !ent->ClientReceiveEvent( event->event, event->time, eventMsg ) ) { NetworkEventWarning( event, "unknown event" ); } } #ifdef _DEBUG entityNetEvent_t* freedEvent = eventQueue.Dequeue(); assert( freedEvent == event ); #else eventQueue.Dequeue(); #endif eventQueue.Free( event ); } } // RAVEN BEGIN // bdube: client side hitscan /* ================ idGameLocal::ClientHitScan ================ */ void idGameLocal::ClientHitScan( const idBitMsg &msg ) { int hitscanDefIndex; idVec3 muzzleOrigin; idVec3 dir; idVec3 fxOrigin; const idDeclEntityDef *decl; int num_hitscans; int i; idEntity *owner; assert( isClient ); hitscanDefIndex = msg.ReadLong(); decl = static_cast< const idDeclEntityDef *>( declManager->DeclByIndex( DECL_ENTITYDEF, hitscanDefIndex ) ); if ( !decl ) { common->Warning( "idGameLocal::ClientHitScan: entity def index %d not found\n", hitscanDefIndex ); return; } num_hitscans = decl->dict.GetInt( "hitscans", "1" ); owner = entities[ msg.ReadBits( idMath::BitsForInteger( MAX_CLIENTS ) ) ]; muzzleOrigin[0] = msg.ReadFloat(); muzzleOrigin[1] = msg.ReadFloat(); muzzleOrigin[2] = msg.ReadFloat(); fxOrigin[0] = msg.ReadFloat(); fxOrigin[1] = msg.ReadFloat(); fxOrigin[2] = msg.ReadFloat(); // one direction sent per hitscan for( i = 0; i < num_hitscans; i++ ) { dir = msg.ReadDir( 24 ); gameLocal.HitScan( decl->dict, muzzleOrigin, dir, fxOrigin, owner ); } } // RAVEN END /* ================ idGameLocal::ClientProcessReliableMessage ================ */ void idGameLocal::ClientProcessReliableMessage( int clientNum, const idBitMsg &msg ) { int id; idDict backupSI; InitLocalClient( clientNum ); if ( serverDemo ) { assert( demoState == DEMO_PLAYING ); int record_type = msg.ReadByte(); assert( record_type < DEMO_RECORD_COUNT ); // if you need to do some special filtering: switch ( record_type ) { case DEMO_RECORD_CLIENTNUM: { msg.ReadByte(); /* int client = msg.ReadByte(); if ( client != -1 ) { // reliable was targetted if ( followPlayer != client ) { // we're free flying or following someone else return; } } */ break; } case DEMO_RECORD_EXCLUDE: { int exclude = msg.ReadByte(); exclude = exclude; // silence warning assert( exclude != -1 ); /* if ( exclude == followPlayer ) { return; } */ break; } } } id = msg.ReadByte(); switch( id ) { case GAME_RELIABLE_MESSAGE_SPAWN_PLAYER: { int client = msg.ReadByte(); int spawnId = msg.ReadLong(); if ( !entities[ client ] ) { SpawnPlayer( client ); entities[ client ]->FreeModelDef(); } // fix up the spawnId to match what the server says // otherwise there is going to be a bogus delete/new of the client entity in the first ClientReadFromSnapshot spawnIds[ client ] = spawnId; break; } case GAME_RELIABLE_MESSAGE_DELETE_ENT: { int spawnId = msg.ReadBits( 32 ); idEntityPtr< idEntity > entPtr; if( !entPtr.SetSpawnId( spawnId ) ) { break; } if( entPtr.GetEntity() && entPtr.GetEntity()->entityNumber < MAX_CLIENTS ) { delete entPtr.GetEntity(); gameLocal.mpGame.UpdatePlayerRanks(); } else { delete entPtr.GetEntity(); } break; } case GAME_RELIABLE_MESSAGE_CHAT: case GAME_RELIABLE_MESSAGE_TCHAT: { // (client should never get a TCHAT though) char name[128]; char text[128]; char parm[128]; msg.ReadString( name, sizeof( name ) ); msg.ReadString( text, sizeof( text ) ); msg.ReadString( parm, sizeof( parm ) ); idStr temp = va( "%s%s", common->GetLocalizedString( text ), parm ); mpGame.AddChatLine( "%s^0: %s\n", name, temp.c_str() ); break; } case GAME_RELIABLE_MESSAGE_DB: { msg_evt_t msg_evt = (msg_evt_t)msg.ReadByte(); int parm1, parm2; parm1 = msg.ReadByte( ); parm2 = msg.ReadByte( ); mpGame.PrintMessageEvent( -1, msg_evt, parm1, parm2 ); break; } case GAME_RELIABLE_MESSAGE_EVENT: { entityNetEvent_t *event; // allocate new event event = eventQueue.Alloc(); eventQueue.Enqueue( event, idEventQueue::OUTOFORDER_IGNORE ); event->spawnId = msg.ReadBits( 32 ); event->event = msg.ReadByte(); event->time = msg.ReadLong(); event->paramsSize = msg.ReadBits( idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); if ( event->paramsSize ) { if ( event->paramsSize > MAX_EVENT_PARAM_SIZE ) { NetworkEventWarning( event, "invalid param size" ); return; } msg.ReadByteAlign(); msg.ReadData( event->paramsBuf, event->paramsSize ); } break; } case GAME_RELIABLE_MESSAGE_SERVERINFO: { idDict info; msg.ReadDeltaDict( info, NULL ); SetServerInfo( info ); break; } case GAME_RELIABLE_MESSAGE_RESTART: { MapRestart(); break; } case GAME_RELIABLE_MESSAGE_STARTVOTE: { char voteString[ MAX_STRING_CHARS ]; int clientNum = msg.ReadByte( ); msg.ReadString( voteString, sizeof( voteString ) ); mpGame.ClientStartVote( clientNum, voteString ); break; } case GAME_RELIABLE_MESSAGE_PRINT: { char str[ MAX_PRINT_LEN ] = { '\0' }; msg.ReadString( str, MAX_PRINT_LEN ); mpGame.PrintMessage( -1, str ); break; } // RAVEN BEGIN // shouchard: multifield vote stuff case GAME_RELIABLE_MESSAGE_STARTPACKEDVOTE: { voteStruct_t voteData; memset( &voteData, 0, sizeof( voteData ) ); int clientNum = msg.ReadByte(); voteData.m_fieldFlags = msg.ReadShort(); char mapName[256]; if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_KICK ) ) { voteData.m_kick = msg.ReadByte(); } if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_MAP ) ) { msg.ReadString( mapName, sizeof( mapName ) ); voteData.m_map = mapName; } if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_GAMETYPE ) ) { voteData.m_gameType = msg.ReadByte(); } if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TIMELIMIT ) ) { voteData.m_timeLimit = msg.ReadByte(); } if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_FRAGLIMIT ) ) { voteData.m_fragLimit = msg.ReadShort(); } if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TOURNEYLIMIT ) ) { voteData.m_tourneyLimit = msg.ReadShort(); } if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CAPTURELIMIT ) ) { voteData.m_captureLimit = msg.ReadShort(); } if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_BUYING ) ) { voteData.m_buying = msg.ReadByte(); } if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TEAMBALANCE ) ) { voteData.m_teamBalance = msg.ReadByte(); } if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CONTROLTIME ) ) { voteData.m_controlTime = msg.ReadShort(); } mpGame.ClientStartPackedVote( clientNum, voteData ); break; } // RAVEN END case GAME_RELIABLE_MESSAGE_UPDATEVOTE: { int result = msg.ReadByte( ); int yesCount = msg.ReadByte( ); int noCount = msg.ReadByte( ); // RAVEN BEGIN // shouchard: multifield vote stuff int multiVote = msg.ReadByte( ); voteStruct_t voteData; char mapNameBuffer[256]; memset( &voteData, 0, sizeof( voteData ) ); if ( multiVote ) { voteData.m_fieldFlags = msg.ReadShort(); voteData.m_kick = msg.ReadByte(); msg.ReadString( mapNameBuffer, sizeof( mapNameBuffer ) ); voteData.m_map = mapNameBuffer; voteData.m_gameType = msg.ReadByte(); voteData.m_timeLimit = msg.ReadByte(); voteData.m_fragLimit = msg.ReadShort(); voteData.m_tourneyLimit = msg.ReadShort(); voteData.m_captureLimit = msg.ReadShort(); voteData.m_buying = msg.ReadByte(); voteData.m_teamBalance = msg.ReadByte(); if ( gameLocal.GetCurrentDemoProtocol() == 69 ) { voteData.m_controlTime = 0; } else { voteData.m_controlTime = msg.ReadShort(); } } mpGame.ClientUpdateVote( (idMultiplayerGame::vote_result_t)result, yesCount, noCount, voteData ); // RAVEN END break; } case GAME_RELIABLE_MESSAGE_PORTALSTATES: { int numPortals = msg.ReadLong(); assert( numPortals == gameRenderWorld->NumPortals() ); for ( int i = 0; i < numPortals; i++ ) { gameRenderWorld->SetPortalState( (qhandle_t) (i+1), msg.ReadBits( NUM_RENDER_PORTAL_BITS ) ); } break; } case GAME_RELIABLE_MESSAGE_PORTAL: { qhandle_t portal = msg.ReadLong(); int blockingBits = msg.ReadBits( NUM_RENDER_PORTAL_BITS ); assert( portal > 0 && portal <= gameRenderWorld->NumPortals() ); gameRenderWorld->SetPortalState( portal, blockingBits ); break; } case GAME_RELIABLE_MESSAGE_STARTSTATE: { mpGame.ClientReadStartState( msg ); break; } // RAVEN BEGIN // bdube: case GAME_RELIABLE_MESSAGE_ITEMACQUIRESOUND: mpGame.PlayGlobalItemAcquireSound( msg.ReadBits ( gameLocal.entityDefBits ) ); break; // ddynerman: death messagse case GAME_RELIABLE_MESSAGE_DEATH: { int attackerEntityNumber = msg.ReadByte( ); int attackerScore = -1; if( attackerEntityNumber >= 0 && attackerEntityNumber < MAX_CLIENTS ) { attackerScore = msg.ReadBits( ASYNC_PLAYER_FRAG_BITS ); } int victimEntityNumber = msg.ReadByte( ); int victimScore = -1; if( victimEntityNumber >= 0 && victimEntityNumber < MAX_CLIENTS ) { victimScore = msg.ReadBits( ASYNC_PLAYER_FRAG_BITS ); } idPlayer* attacker = (attackerEntityNumber != 255 ? static_cast(gameLocal.entities[ attackerEntityNumber ]) : NULL); idPlayer* victim = (victimEntityNumber != 255 ? static_cast(gameLocal.entities[ victimEntityNumber ]) : NULL); int methodOfDeath = msg.ReadByte( ); mpGame.ReceiveDeathMessage( attacker, attackerScore, victim, victimScore, methodOfDeath ); break; } // ddynerman: game state case GAME_RELIABLE_MESSAGE_GAMESTATE: { mpGame.GetGameState()->ReceiveState( msg ); break; } // ddynerman: game stats case GAME_RELIABLE_MESSAGE_STAT: { statManager->ReceiveStat( msg ); break; } // asalmon: game stats for Xenon receive all client stats case GAME_RELIABLE_MESSAGE_ALL_STATS: { statManager->ReceiveAllStats( msg ); break; } // ddynerman: multiple instances case GAME_RELIABLE_MESSAGE_SET_INSTANCE: { mpGame.ClientSetInstance( msg ); break; } // ddynerman: awards case GAME_RELIABLE_MESSAGE_INGAMEAWARD: { statManager->ReceiveInGameAward( msg ); break; } // mekberg: get ban list for server case GAME_RELIABLE_MESSAGE_GETADMINBANLIST: { mpBanInfo_t banInfo; char name[MAX_STRING_CHARS]; char guid[MAX_STRING_CHARS]; FlushBanList( ); while ( msg.ReadString( name, MAX_STRING_CHARS ) && msg.ReadString( guid, MAX_STRING_CHARS ) ) { banInfo.name = name; strncpy( banInfo.guid, guid, CLIENT_GUID_LENGTH ); banList.Append( banInfo ); } break; } // RAVEN END // RAVEN END default: { Error( "Unknown server->client reliable message: %d", id ); break; } } } // RAVEN BEGIN /* ================ idGameLocal::ClientRun Called once each client render frame (before any ClientPrediction frames have been run) ================ */ void idGameLocal::ClientRun( void ) { if( isMultiplayer ) { mpGame.ClientRun(); } } /* ================ idGameLocal::ClientEndFrame Called once each client render frame (after all ClientPrediction frames have been run) ================ */ void idGameLocal::ClientEndFrame( void ) { if( isMultiplayer ) { mpGame.ClientEndFrame(); } } /* ================ idGameLocal::ProcessRconReturn ================ */ void idGameLocal::ProcessRconReturn( bool success ) { if( isMultiplayer ) { mpGame.ProcessRconReturn( success ); } } /* ================ idGameLocal::ResetGuiRconStatus ================ */ void idGameLocal::ResetRconGuiStatus( void ) { if( isMultiplayer ) { mpGame.ResetRconGuiStatus( ); } } // RAVEN END /* ================ idGameLocal::ClientPrediction server demos: clientNum == MAX_CLIENTS ================ */ gameReturn_t idGameLocal::ClientPrediction( int clientNum, const usercmd_t *clientCmds, bool lastPredictFrame, ClientStats_t *cs ) { idEntity *ent; idPlayer *player; // may be NULL when predicting for a server demo gameReturn_t ret; ret.sessionCommand[ 0 ] = '\0'; if ( clientNum == MAX_CLIENTS ) { // clientCmds[ MAX_CLIENTS ] has the local interaction // firing -> cycle follow players, jump -> free fly and cycle map spawns int btn_mask; player = NULL; oldUsercmd = usercmd; usercmd = clientCmds[ MAX_CLIENTS ]; btn_mask = usercmd.buttons ^ oldUsercmd.buttons; if ( usercmd.buttons & btn_mask & BUTTON_ATTACK ) { // find the next suitable player to follow int delta = 0; while ( true ) { int i_next = GetNextClientNum( followPlayer ); if ( followPlayer < i_next ) { delta += i_next - followPlayer; } else { delta += numClients - followPlayer + i_next; } if ( delta > numClients ) { // tried them all, no fit followPlayer = -1; break; } followPlayer = i_next; if ( !entities[ followPlayer ] ) { continue; } idPlayer *p = static_cast< idPlayer * >( entities[ followPlayer ] ); if ( p->spectating ) { continue; } // Tourney games, we only record instance 0, only cycle on instance 0 players if ( p->GetInstance() != 0 ) { continue; } break; } } if ( usercmd.upmove & !oldUsercmd.upmove ) { if ( followPlayer != -1 ) { // set yourself up a bit above whoever you were following freeView.SetFreeView( followPlayer ); } else { // pick a random spawn spot to start flying from freeView.PickRandomSpawn(); } followPlayer = -1; } player = NULL; if ( followPlayer >= 0 ) { player = static_cast< idPlayer* >( entities[ followPlayer ] ); if ( !player ) { // that player we were following was removed from the game freeView.PickRandomSpawn(); } else if ( player->spectating ) { // our followed player went spectator, go free fly freeView.SetFreeView( followPlayer ); player = NULL; followPlayer = -1; } } if ( !player && !freeView.Initialized() ) { freeView.PickRandomSpawn(); } } else { player = static_cast( entities[clientNum] ); } // RAVEN BEGIN // bdube: added advanced debug support if ( g_showDebugHud.GetInteger() && net_entsInSnapshot && net_snapshotSize) { gameDebug.SetInt( "snap_ents", net_entsInSnapshot ); gameDebug.SetInt( "snap_size", net_snapshotSize ); net_entsInSnapshot = 0; net_snapshotSize = 0; } if ( clientNum == localClientNum ) { gameDebug.BeginFrame( ); gameLog->BeginFrame( time ); } isLastPredictFrame = lastPredictFrame; // RAVEN END // check for local client lag if ( player ) { if ( networkSystem->ClientGetTimeSinceLastPacket() >= net_clientMaxPrediction.GetInteger() ) { player->isLagged = true; } else { player->isLagged = false; } } InitLocalClient( clientNum ); // update the game time framenum++; previousTime = time; // RAVEN BEGIN // bdube: use GetMSec access rather than USERCMD_TIME time += GetMSec(); // RAVEN END // update the real client time and the new frame flag if ( time > realClientTime ) { realClientTime = time; isNewFrame = true; } else { isNewFrame = false; } if ( cs ) { cs->isLastPredictFrame = isLastPredictFrame; cs->isLagged = player ? player->isLagged : false; cs->isNewFrame = isNewFrame; } // set the user commands for this frame // RAVEN BEGIN usercmds = clientCmds; // RAVEN END if ( clientNum == MAX_CLIENTS && !player ) { freeView.Fly( usercmd ); } // TMP bool verbose = cvarSystem->GetCVarBool( "verbose_predict" ); // run prediction on all entities from the last snapshot for ( ent = snapshotEntities.Next(); ent != NULL; ent = ent->snapshotNode.Next() ) { #if 0 ent->thinkFlags |= TH_PHYSICS; ent->ClientPredictionThink(); #else // don't force TH_PHYSICS on, only call ClientPredictionThink if thinkFlags != 0 // it's better to synchronize TH_PHYSICS on specific entities when needed ( movers may be trouble ) // thinkMask is a temp thing see if there are problems with only checking for TH_PHYSICS if ( ent->thinkFlags != 0 ) { if ( verbose ) { common->Printf( "%d: %s %d\n", ent->entityNumber, ent->GetType()->classname, ent->thinkFlags ); } ent->ClientPredictionThink(); } else { if ( verbose ) { common->Printf( "skip %d: %s %d\n", ent->entityNumber, ent->GetType()->classname, ent->thinkFlags ); } } #endif } // RAVEN BEGIN // bdube: client entities // run client entities if ( isNewFrame ) { // rjohnson: only run the entire logic when it is a new frame rvClientEntity* cent; for ( cent = clientSpawnedEntities.Next(); cent != NULL; cent = cent->spawnNode.Next() ) { cent->Think(); } } // RAVEN END // service any pending events idEvent::ServiceEvents(); // show any debug info for this frame if ( isNewFrame ) { RunDebugInfo(); D_DrawDebugLines(); } if ( sessionCommand.Length() ) { strncpy( ret.sessionCommand, sessionCommand, sizeof( ret.sessionCommand ) ); sessionCommand = ""; } // RAVEN BEGIN // ddynerman: client logging/debugging if ( clientNum == localClientNum ) { gameDebug.EndFrame(); gameLog->EndFrame(); } // RAVEN END g_simpleItems.ClearModified(); return ret; } /* =============== idGameLocal::Tokenize =============== */ void idGameLocal::Tokenize( idStrList &out, const char *in ) { char buf[ MAX_STRING_CHARS ]; char *token, *next; idStr::Copynz( buf, in, MAX_STRING_CHARS ); token = buf; next = strchr( token, ';' ); while ( token ) { if ( next ) { *next = '\0'; } idStr::ToLower( token ); out.Append( token ); if ( next ) { token = next + 1; next = strchr( token, ';' ); } else { token = NULL; } } } /* =============== idGameLocal::DownloadRequest =============== */ bool idGameLocal::DownloadRequest( const char *IP, const char *guid, const char *paks, char urls[ MAX_STRING_CHARS ] ) { if ( !cvarSystem->GetCVarInteger( "net_serverDownload" ) ) { return false; } if ( cvarSystem->GetCVarInteger( "net_serverDownload" ) == 1 ) { // 1: single URL redirect if ( !strlen( cvarSystem->GetCVarString( "si_serverURL" ) ) ) { common->Warning( "si_serverURL not set" ); return false; } idStr::snPrintf( urls, MAX_STRING_CHARS, "1;%s", cvarSystem->GetCVarString( "si_serverURL" ) ); return true; } else { // 2: table of pak URLs // first token is the game pak if requested, empty if not requested by the client // there may be empty tokens for paks the server couldn't pinpoint - the order matters idStr reply = "2;"; idStrList dlTable, pakList; bool matchAll = false; int i, j; if ( !idStr::Icmp( cvarSystem->GetCVarString( "net_serverDlTable" ), "*" ) ) { matchAll = true; } else { Tokenize( dlTable, cvarSystem->GetCVarString( "net_serverDlTable" ) ); } Tokenize( pakList, paks ); for ( i = 0; i < pakList.Num(); i++ ) { if ( i > 0 ) { reply += ";"; } if ( pakList[ i ][ 0 ] == '\0' ) { if ( i == 0 ) { // pak 0 will always miss when client doesn't ask for game bin common->DPrintf( "no game pak request\n" ); } else { common->DPrintf( "no pak %d\n", i ); } continue; } if ( matchAll ) { idStr url = cvarSystem->GetCVarString( "net_serverDlBaseURL" ); url.AppendPath( pakList[i] ); reply += url; common->Printf( "download for %s: %s\n", IP, url.c_str() ); } else { for ( j = 0; j < dlTable.Num(); j++ ) { if ( !pakList[ i ].Icmp( dlTable[ j ] ) ) { break; } } if ( j == dlTable.Num() ) { common->Printf( "download for %s: pak not matched: %s\n", IP, pakList[ i ].c_str() ); } else { idStr url = cvarSystem->GetCVarString( "net_serverDlBaseURL" ); url.AppendPath( dlTable[ j ] ); reply += url; common->Printf( "download for %s: %s\n", IP, url.c_str() ); } } } idStr::Copynz( urls, reply, MAX_STRING_CHARS ); return true; } } /* =============== idGameLocal::HTTPRequest =============== */ bool idGameLocal::HTTPRequest( const char *IP, const char *file, bool isGamePak ) { return false; } /* =============== idEventQueue::Alloc =============== */ entityNetEvent_t* idEventQueue::Alloc() { entityNetEvent_t* event = eventAllocator.Alloc(); event->prev = NULL; event->next = NULL; return event; } /* =============== idEventQueue::Free =============== */ void idEventQueue::Free( entityNetEvent_t *event ) { // should only be called on an unlinked event! assert( !event->next && !event->prev ); eventAllocator.Free( event ); } /* =============== idEventQueue::Shutdown =============== */ void idEventQueue::Shutdown() { eventAllocator.Shutdown(); this->Init(); } /* =============== idEventQueue::Init =============== */ void idEventQueue::Init( void ) { start = NULL; end = NULL; } /* =============== idEventQueue::Dequeue =============== */ entityNetEvent_t* idEventQueue::Dequeue( void ) { entityNetEvent_t* event = start; if ( !event ) { return NULL; } start = start->next; if ( !start ) { end = NULL; } else { start->prev = NULL; } event->next = NULL; event->prev = NULL; return event; } /* =============== idEventQueue::RemoveLast =============== */ entityNetEvent_t* idEventQueue::RemoveLast( void ) { entityNetEvent_t *event = end; if ( !event ) { return NULL; } end = event->prev; if ( !end ) { start = NULL; } else { end->next = NULL; } event->next = NULL; event->prev = NULL; return event; } /* =============== idEventQueue::Enqueue =============== */ void idEventQueue::Enqueue( entityNetEvent_t *event, outOfOrderBehaviour_t behaviour ) { if ( behaviour == OUTOFORDER_DROP ) { // go backwards through the queue and determine if there are // any out-of-order events while ( end && end->time > event->time ) { entityNetEvent_t *outOfOrder = RemoveLast(); common->DPrintf( "WARNING: new event with id %d ( time %d ) caused removal of event with id %d ( time %d ), game time = %d.\n", event->event, event->time, outOfOrder->event, outOfOrder->time, gameLocal.time ); Free( outOfOrder ); } } else if ( behaviour == OUTOFORDER_SORT && end ) { // NOT TESTED -- sorting out of order packets hasn't been // tested yet... wasn't strictly necessary for // the patch fix. entityNetEvent_t *cur = end; // iterate until we find a time < the new event's while ( cur && cur->time > event->time ) { cur = cur->prev; } if ( !cur ) { // add to start event->next = start; event->prev = NULL; start = event; } else { // insert event->prev = cur; event->next = cur->next; cur->next = event; } return; } // add the new event event->next = NULL; event->prev = NULL; if ( end ) { end->next = event; event->prev = end; } else { start = event; } end = event; } // RAVEN BEGIN // shouchard: ban list stuff here /* ================ idGameLocal::LoadBanList ================ */ void idGameLocal::LoadBanList() { // open file idStr token; idFile *banFile = fileSystem->OpenFileRead( BANLIST_FILENAME ); mpBanInfo_t banInfo; if ( NULL == banFile ) { common->DPrintf( "idGameLocal::LoadBanList: unable to open ban list file!\n" ); // fixme: need something better here return; } // parse file (read three consecutive strings per banInfo (real complex ;) ) ) while ( banFile->ReadString( token ) > 0 ) { // name banInfo.name = token; // guid if ( banFile->ReadString( token ) > 0 && token.Length() >= 11 ) { idStr::Copynz( banInfo.guid, token.c_str(), CLIENT_GUID_LENGTH ); banList.Append( banInfo ); continue; } gameLocal.Warning( "idGameLocal::LoadBanList: Potential curruption of banlist file (%s).", BANLIST_FILENAME ); } fileSystem->CloseFile( banFile ); banListLoaded = true; banListChanged = false; } /* ================ idGameLocal::SaveBanList ================ */ void idGameLocal::SaveBanList() { if ( !banListChanged ) { return; } // open file idFile *banFile = fileSystem->OpenFileWrite( BANLIST_FILENAME ); if ( NULL == banFile ) { common->DPrintf( "idGameLocal::SaveBanList: unable to open ban list file!\n" ); // fixme: need something better here return; } for ( int i = 0; i < banList.Num(); i++ ) { const mpBanInfo_t& banInfo = banList[ i ]; char temp[ 16 ] = { 0, }; banFile->WriteString( va( "%s", banInfo.name.c_str() ) ); idStr::Copynz( temp, banInfo.guid, CLIENT_GUID_LENGTH ); banFile->WriteString( temp ); // idStr::Copynz( temp, (const char*)banInfo.ip, 15 ); // banFile->WriteString( "255.255.255.255" ); } fileSystem->CloseFile( banFile ); banListChanged = false; } /* ================ idGameLocal::FlushBanList ================ */ void idGameLocal::FlushBanList() { banList.Clear(); banListLoaded = false; banListChanged = false; } /* ================ idGameLocal::IsPlayerBanned ================ */ bool idGameLocal::IsPlayerBanned( const char *name ) { assert( name ); if ( !banListLoaded ) { LoadBanList(); } // check vs. each line in the list, if we found one return true for ( int i = 0; i < banList.Num(); i++ ) { if ( 0 == idStr::Icmp( name, banList[ i ].name ) ) { return true; } } return false; } /* ================ idGameLocal::IsGuidBanned ================ */ bool idGameLocal::IsGuidBanned( const char *guid ) { assert( guid ); if ( !banListLoaded ) { LoadBanList(); } // check vs. each line in the list, if we found one return true for ( int i = 0; i < banList.Num(); i++ ) { if ( 0 == idStr::Icmp( guid, banList[ i ].guid ) ) { return true; } } return false; } /* ================ idGameLocal::AddGuidToBanList ================ */ void idGameLocal::AddGuidToBanList( const char *guid ) { assert( guid ); if ( !banListLoaded ) { LoadBanList(); } mpBanInfo_t banInfo; char name[ 512 ]; // TODO: clean this up gameLocal.GetPlayerName( gameLocal.GetClientNumByGuid( guid ), name ); banInfo.name = name; idStr::Copynz( banInfo.guid, guid, CLIENT_GUID_LENGTH ); // SIMDProcessor->Memset( banInfo.ip, 0xFF, 15 ); banList.Append( banInfo ); banListChanged = true; } /* ================ idGameLocal::RemoveGuidFromBanList ================ */ void idGameLocal::RemoveGuidFromBanList( const char *guid ) { assert( guid ); if ( !banListLoaded ) { LoadBanList(); } // check vs. each line in the list, if we find a match remove it. for ( int i = 0; i < banList.Num(); i++ ) { if ( 0 == idStr::Icmp( guid, banList[ i ].guid ) ) { banList.RemoveIndex( i ); banListChanged = true; return; } } } /* ================ idGameLocal::RegisterClientGuid ================ */ void idGameLocal::RegisterClientGuid( int clientNum, const char *guid ) { assert( clientNum >= 0 && clientNum < MAX_CLIENTS ); assert( guid ); memset( clientGuids[ clientNum ], 0, CLIENT_GUID_LENGTH ); // just in case idStr::Copynz( clientGuids[ clientNum ], guid, CLIENT_GUID_LENGTH ); } /* ================ idGameLocal::GetBanListCount ================ */ int idGameLocal::GetBanListCount() { if ( !banListLoaded ) { LoadBanList(); } return banList.Num(); } /* ================ idGameLocal::GetBanListEntry ================ */ const mpBanInfo_t* idGameLocal::GetBanListEntry( int entry ) { if ( !banListLoaded ) { LoadBanList(); } if ( entry < 0 || entry >= banList.Num() ) { return NULL; } return &banList[ entry ]; } /* ================ idGameLocal::GetGuidByClientNum ================ */ const char *idGameLocal::GetGuidByClientNum( int clientNum ) { assert( clientNum >= 0 && clientNum < numClients ); return clientGuids[ clientNum ]; } /* ================ idGameLocal::GetClientNumByGuid ================ */ int idGameLocal::GetClientNumByGuid( const char * guid ) { assert( guid ); for ( int i = 0; i < MAX_CLIENTS; i++ ) { if ( !idStr::Icmp( networkSystem->GetClientGUID( i ), guid ) ) { return i; } } return -1; } // mekberg: send ban list to client /* ================ idGameLocal::ServerSendBanList ================ */ void idGameLocal::ServerSendBanList( int clientNum ) { idBitMsg outMsg; byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; outMsg.Init( msgBuf, sizeof( msgBuf ) ); outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GETADMINBANLIST ) ; if ( !banListLoaded ) { LoadBanList(); } int i; int c = banList.Num(); for ( i = 0; i < c; i++ ) { outMsg.WriteString( banList[ i ].name.c_str() ); outMsg.WriteString( banList[ i ].guid, CLIENT_GUID_LENGTH ); } networkSystem->ServerSendReliableMessage( clientNum, outMsg ); } // mekberg: so we can populate ban list outside of multiplayer game /* =================== idGameLocal::PopulateBanList =================== */ void idGameLocal::PopulateBanList( idUserInterface* hud ) { if ( !hud ) { return; } int bans = GetBanListCount(); for ( int i = 0; i < bans; i++ ) { const mpBanInfo_t * banInfo = GetBanListEntry( i ); hud->SetStateString( va( "sa_banList_item_%d", i ), va( "%d: %s\t%s", i+1, banInfo->name.c_str(), banInfo->guid ) ); } hud->DeleteStateVar( va( "sa_banList_item_%d", bans ) ); hud->SetStateString( "sa_banList_sel_0", "-1" ); // used to trigger a redraw, was slow, and doesn't seem to do anything so took it out. fixes #13675 hud->StateChanged( gameLocal.time, false ); } // RAVEN END /* ================ idGameLocal::ServerSendInstanceReliableMessageExcluding Works like networkSystem->ServerSendReliableMessageExcluding, but only sends to entities in the owner's instance ================ */ void idGameLocal::ServerSendInstanceReliableMessageExcluding( const idEntity* owner, int excludeClient, const idBitMsg& msg ) { int i; assert( isServer ); if ( owner == NULL ) { networkSystem->ServerSendReliableMessageExcluding( excludeClient, msg ); return; } for( i = 0; i < numClients; i++ ) { if ( i == excludeClient ) { continue; } if( entities[ i ] == NULL ) { continue; } if( entities[ i ]->GetInstance() != owner->GetInstance() ) { continue; } networkSystem->ServerSendReliableMessage( i, msg ); } } /* ================ idGameLocal::ServerSendInstanceReliableMessage Works like networkSystem->ServerSendReliableMessage, but only sends to entities in the owner's instance ================ */ void idGameLocal::ServerSendInstanceReliableMessage( const idEntity* owner, int clientNum, const idBitMsg& msg ) { int i; assert( isServer ); if( owner == NULL ) { networkSystem->ServerSendReliableMessage( clientNum, msg ); return; } if( clientNum == -1 ) { for( i = 0; i < numClients; i++ ) { if( entities[ i ] == NULL ) { continue; } if( entities[ i ]->GetInstance() != owner->GetInstance() ) { continue; } networkSystem->ServerSendReliableMessage( i, msg ); } } else { if( entities[ clientNum ] && entities[ clientNum ]->GetInstance() == owner->GetInstance() ) { networkSystem->ServerSendReliableMessage( clientNum, msg ); } } } /* =============== idGameLocal::SendUnreliableMessage for spectating support, we have to loop through the clients and emit to the spectator client too note that a clientNum == -1 means send to everyone =============== */ void idGameLocal::SendUnreliableMessage( const idBitMsg &msg, const int clientNum ) { int icl; idPlayer *player; for ( icl = 0; icl < numClients; icl++ ) { if ( icl == localClientNum ) { // not to local client // note that if local is spectated he will still get it continue; } if ( !entities[ icl ] ) { continue; } if ( icl != clientNum ) { player = static_cast< idPlayer * >( entities[ icl ] ); // drop all clients except the ones that follow the client we emit to if ( !player->spectating || player->spectator != clientNum ) { continue; } } unreliableMessages[ icl ].Add( msg.GetData(), msg.GetSize(), false ); } if ( demoState == DEMO_RECORDING ) { // record the type and destination for remap on readback idBitMsg dest; byte msgBuf[ 16 ]; dest.Init( msgBuf, sizeof( msgBuf ) ); dest.WriteByte( GAME_UNRELIABLE_RECORD_CLIENTNUM ); dest.WriteByte( clientNum ); unreliableMessages[ MAX_CLIENTS ].AddConcat( dest.GetData(), dest.GetSize(), msg.GetData(), msg.GetSize(), false ); } } /* =============== idGameLocal::SendUnreliableMessagePVS instanceEnt to NULL for no instance checks excludeClient to -1 for no exclusions =============== */ void idGameLocal::SendUnreliableMessagePVS( const idBitMsg &msg, const idEntity *instanceEnt, int area1, int area2 ) { int icl; int matchInstance = instanceEnt ? instanceEnt->GetInstance() : -1; idPlayer *player; int areas[ 2 ]; int numEvAreas; numEvAreas = 0; if ( area1 != -1 ) { areas[ 0 ] = area1; numEvAreas++; } if ( area2 != -1 ) { areas[ numEvAreas ] = area2; numEvAreas++; } for ( icl = 0; icl < numClients; icl++ ) { if ( icl == localClientNum ) { // local client is always excluded continue; } if ( !entities[ icl ] ) { continue; } if ( matchInstance >= 0 && entities[ icl ]->GetInstance() != matchInstance ) { continue; } if ( clientsPVS[ icl ].i < 0 ) { // clients for which we don't have PVS info won't get anything continue; } player = static_cast< idPlayer * >( entities[ icl ] ); // if no areas are given, this is a global emit if ( numEvAreas ) { // ony send if pvs says this client can see it if ( !pvs.InCurrentPVS( clientsPVS[ icl ], areas, numEvAreas ) ) { continue; } } unreliableMessages[ icl ].Add( msg.GetData(), msg.GetSize(), false ); } if ( demoState == DEMO_RECORDING ) { // record the target areas to the message idBitMsg dest; byte msgBuf[ 16 ]; // Tourney games: only record from instance 0 if ( !instanceEnt || instanceEnt->GetInstance() == 0 ) { dest.Init( msgBuf, sizeof( msgBuf ) ); dest.WriteByte( GAME_UNRELIABLE_RECORD_AREAS ); dest.WriteLong( area1 ); dest.WriteLong( area2 ); unreliableMessages[ MAX_CLIENTS ].AddConcat( dest.GetData(), dest.GetSize(), msg.GetData(), msg.GetSize(), false ); } } } /* =============== idGameLocal::ClientReadUnreliableMessages =============== */ void idGameLocal::ClientReadUnreliableMessages( const idBitMsg &_msg ) { idMsgQueue localQueue; int size; byte msgBuf[MAX_GAME_MESSAGE_SIZE]; idBitMsg msg; localQueue.ReadFrom( _msg ); msg.Init( msgBuf, sizeof( msgBuf ) ); while ( localQueue.Get( msg.GetData(), msg.GetMaxSize(), size, false ) ) { msg.SetSize( size ); msg.BeginReading(); ProcessUnreliableMessage( msg ); msg.BeginWriting(); } } /* =============== idGameLocal::DemoReplayInAreas checks if our current demo replay view ( server demo ) matches the areas given =============== */ bool idGameLocal::IsDemoReplayInAreas( int area1, int area2 ) { int areas[2]; int numAreas; idVec3 view; pvsHandle_t handle; bool ret; numAreas = 0; if ( area1 != -1 ) { areas[ 0 ] = area1; numAreas++; } if ( area2 != -1 ) { areas[ numAreas ] = area2; numAreas++; } assert( serverDemo ); assert( demoState == DEMO_PLAYING ); if ( followPlayer == -1 ) { view = freeView.GetOrigin(); } else { view = entities[ followPlayer ]->GetPhysics()->GetOrigin(); } // could probably factorize this, at least for processing all unreliable messages, maybe at a higher level of the loop? handle = pvs.SetupCurrentPVS( view ); ret = pvs.InCurrentPVS( handle, areas, numAreas ); pvs.FreeCurrentPVS( handle ); return ret; } /* =============== idGameLocal::ProcessUnreliableMessage =============== */ void idGameLocal::ProcessUnreliableMessage( const idBitMsg &msg ) { if ( serverDemo ) { assert( demoState == DEMO_PLAYING ); int record_type = msg.ReadByte(); assert( record_type < GAME_UNRELIABLE_RECORD_COUNT ); switch ( record_type ) { case GAME_UNRELIABLE_RECORD_CLIENTNUM: { int client = msg.ReadByte(); if ( client != -1 ) { // unreliable was targetted if ( followPlayer != client ) { // either free flying, or following someone else return; } } break; } case GAME_UNRELIABLE_RECORD_AREAS: { int area1 = msg.ReadLong(); int area2 = msg.ReadLong(); if ( !IsDemoReplayInAreas( area1, area2 ) ) { return; } break; } } } int type = msg.ReadByte(); switch ( type ) { case GAME_UNRELIABLE_MESSAGE_EVENT: { idEntityPtr p; int spawnId = msg.ReadBits( 32 ); p.SetSpawnId( spawnId ); if ( p.GetEntity() ) { p.GetEntity()->ClientReceiveEvent( msg.ReadByte(), time, msg ); } else { Warning( "ProcessUnreliableMessage: no local entity 0x%x for event %d", spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ), msg.ReadByte() ); } break; } case GAME_UNRELIABLE_MESSAGE_EFFECT: { idCQuat quat; idVec3 origin, origin2; rvClientEffect* effect; effectCategory_t category; const idDecl *decl; decl = idGameLocal::ReadDecl( msg, DECL_EFFECT ); origin.x = msg.ReadFloat( ); origin.y = msg.ReadFloat( ); origin.z = msg.ReadFloat( ); quat.x = msg.ReadFloat( ); quat.y = msg.ReadFloat( ); quat.z = msg.ReadFloat( ); bool loop = msg.ReadBits( 1 ) != 0; origin2.x = msg.ReadFloat( ); origin2.y = msg.ReadFloat( ); origin2.z = msg.ReadFloat( ); category = ( effectCategory_t )msg.ReadByte(); if ( bse->CanPlayRateLimited( category ) ) { effect = new rvClientEffect( decl ); effect->SetOrigin( origin ); effect->SetAxis( quat.ToMat3() ); effect->Play( time, loop, origin2 ); } break; } case GAME_UNRELIABLE_MESSAGE_HITSCAN: { ClientHitScan( msg ); break; } #ifdef _USE_VOICECHAT case GAME_UNRELIABLE_MESSAGE_VOICEDATA_SERVER: { mpGame.ReceiveAndPlayVoiceData( msg ); break; } #else case GAME_UNRELIABLE_MESSAGE_VOICEDATA_SERVER: { break; } #endif default: { Error( "idGameLocal::ProcessUnreliableMessage() - Unknown unreliable message '%d'\n", type ); } } } /* =============== idGameLocal::WriteNetworkInfo =============== */ void idGameLocal::WriteNetworkInfo( idFile* file, int clientNum ) { int i, j; snapshot_t *snapshot; entityState_t *entityState; if ( !IsServerDemo() ) { // save the current states for ( i = 0; i < MAX_GENTITIES; i++ ) { entityState = clientEntityStates[clientNum][i]; file->WriteBool( !!entityState ); if ( entityState ) { file->WriteInt( entityState->entityNumber ); file->WriteInt( entityState->state.GetSize() ); file->Write( entityState->state.GetData(), entityState->state.GetSize() ); } } // save the PVS states for ( i = 0; i < MAX_CLIENTS; i++ ) { for ( j = 0; j < ENTITY_PVS_SIZE; j++ ) { file->WriteInt( clientPVS[i][j] ); } } } // players ( including local client ) j = 0; for ( i = 0; i < MAX_CLIENTS; i++ ) { if ( !entities[i] ) { continue; } j++; } file->WriteInt( j ); for ( i = 0; i < MAX_CLIENTS; i++ ) { if ( !entities[i] ) { continue; } file->WriteInt( i ); file->WriteInt( spawnIds[ i ] ); } if ( !IsServerDemo() ) { // write number of snapshots so on readback we know how many to allocate i = 0; for ( snapshot = clientSnapshots[ clientNum ]; snapshot; snapshot = snapshot->next ) { i++; } file->WriteInt( i ); for ( snapshot = clientSnapshots[ clientNum ]; snapshot; snapshot = snapshot->next ) { file->WriteInt( snapshot->sequence ); // write number of entity states in the snapshot i = 0; for ( entityState = snapshot->firstEntityState; entityState; entityState = entityState->next ) { i++; } file->WriteInt( i ); for ( entityState = snapshot->firstEntityState; entityState; entityState = entityState->next ) { file->WriteInt( entityState->entityNumber ); file->WriteInt( entityState->state.GetSize() ); file->Write( entityState->state.GetData(), entityState->state.GetSize() ); } file->Write( snapshot->pvs, sizeof( snapshot->pvs ) ); } } // write the 'initial reliables' data mpGame.WriteNetworkInfo( file, clientNum ); } /* =============== idGameLocal::ReadNetworkInfo =============== */ void idGameLocal::ReadNetworkInfo( int gameTime, idFile* file, int clientNum ) { int i, j, num, numStates, stateSize; snapshot_t *snapshot, **lastSnap; entityState_t *entityState, **lastState; int proto69TypeNum = 0; bool proto69 = ( gameLocal.GetCurrentDemoProtocol() == 69 ); assert( clientNum == MAX_CLIENTS || !IsServerDemo() ); InitLocalClient( clientNum ); time = gameTime; previousTime = gameTime; // force new frame realClientTime = 0; isNewFrame = true; // clear the snapshot entity list snapshotEntities.Clear(); if ( !IsServerDemo() ) { for ( i = 0; i < MAX_GENTITIES; i++ ) { bool isValid; file->ReadBool( isValid ); if ( isValid ) { clientEntityStates[clientNum][i] = entityStateAllocator.Alloc(); entityState = clientEntityStates[clientNum][i]; entityState->next = NULL; file->ReadInt( entityState->entityNumber ); file->ReadInt( stateSize ); entityState->state.Init( entityState->stateBuf, sizeof( entityState->stateBuf ) ); entityState->state.SetSize( stateSize ); file->Read( entityState->state.GetData(), stateSize ); } else { clientEntityStates[clientNum][i] = NULL; } } for ( i = 0; i < MAX_CLIENTS; i++ ) { for ( j = 0; j < ENTITY_PVS_SIZE; j++ ) { file->ReadInt( clientPVS[i][j] ); } } } // spawn player entities. ( numClients is not a count but the watermark of client indexes ) file->ReadInt( num ); for ( i = 0; i < num; i++ ) { int icl, spawnId; file->ReadInt( icl ); file->ReadInt( spawnId ); SpawnPlayer( icl ); spawnIds[ icl ] = spawnId; numClients = icl + 1; } if ( !IsServerDemo() ) { file->ReadInt( num ); lastSnap = &clientSnapshots[ localClientNum ]; for ( i = 0; i < num; i++ ) { snapshot = snapshotAllocator.Alloc(); snapshot->firstEntityState = NULL; snapshot->next = NULL; file->ReadInt( snapshot->sequence ); file->ReadInt( numStates ); lastState = &snapshot->firstEntityState; for ( j = 0; j < numStates; j++ ) { entityState = entityStateAllocator.Alloc(); file->ReadInt( entityState->entityNumber ); file->ReadInt( stateSize ); entityState->state.Init( entityState->stateBuf, sizeof( entityState->stateBuf ) ); entityState->state.SetSize( stateSize ); file->Read( entityState->state.GetData(), stateSize ); entityState->next = NULL; assert( !(*lastState ) ); *lastState = entityState; lastState = &entityState->next; } file->Read( snapshot->pvs, sizeof( snapshot->pvs ) ); assert( !(*lastSnap) ); *lastSnap = snapshot; lastSnap = &snapshot->next; } // spawn entities for ( i = 0; i < ENTITYNUM_NONE; i++ ) { int spawnId, entityDefNumber; idBitMsgDelta deltaMsg; idDict args; entityState_t *base = clientEntityStates[clientNum][i]; idEntity *ent = entities[i]; const idDeclEntityDef *decl; if ( !base ) { continue; } base->state.BeginReading(); deltaMsg.InitReading( &base->state, NULL, NULL ); spawnId = deltaMsg.ReadBits( 32 - GENTITYNUM_BITS ); if ( proto69 ) { proto69TypeNum = deltaMsg.ReadBits( idClass::GetTypeNumBits() ); } entityDefNumber = deltaMsg.ReadBits( entityDefBits ); if ( !ent || ent->entityDefNumber != entityDefNumber || spawnId != spawnIds[ i ] ) { delete ent; spawnCount = spawnId; args.Clear(); args.SetInt( "spawn_entnum", i ); args.Set( "name", va( "entity%d", i ) ); // assume any items spawned from a server-snapshot are in our instance if( gameLocal.GetLocalPlayer() ) { args.SetInt( "instance", gameLocal.GetLocalPlayer()->GetInstance() ); } if ( entityDefNumber >= 0 ) { if ( entityDefNumber >= declManager->GetNumDecls( DECL_ENTITYDEF ) ) { Error( "server has %d entityDefs instead of %d", entityDefNumber, declManager->GetNumDecls( DECL_ENTITYDEF ) ); } decl = static_cast< const idDeclEntityDef * >( declManager->DeclByIndex( DECL_ENTITYDEF, entityDefNumber, false ) ); assert( decl && decl->GetType() == DECL_ENTITYDEF ); args.Set( "classname", decl->GetName() ); if ( !SpawnEntityDef( args, &ent ) || !entities[i] ) { Error( "Failed to spawn entity with classname '%s' of type '%s'", decl->GetName(), decl->dict.GetString("spawnclass") ); } } else { // we no longer support spawning entities by type num only. we would only hit this when playing 1.2 demos for backward compatibility assert( proto69 ); switch ( proto69TypeNum ) { case 183: ent = SpawnEntityType( rvViewWeapon::GetClassType(), &args, true ); break; case 182: ent = SpawnEntityType( idAnimatedEntity::GetClassType(), &args, true ); ent->fl.networkSync = true; break; default: Error( "Unexpected protocol 69 typenum (%d) for spawning entity by type", proto69TypeNum ); } if ( !entities[i] ) { Error( "Failed to spawn entity by typenum %d ( protocol 69 backwards compat )", proto69TypeNum ); } } } // add the entity to the snapshot list ent->snapshotNode.AddToEnd( snapshotEntities ); // read the class specific data from the snapshot ent->ReadFromSnapshot( deltaMsg ); // this is useful. for instance on idPlayer, resets stuff so powerups actually appear ent->ClientUnstale(); } { // specific state read for game and player state idBitMsgDelta deltaMsg; entityState_t *base = clientEntityStates[clientNum][ENTITYNUM_NONE]; idPlayer *player; int targetPlayer; // it's possible to have a recording start right at CS_INGAME and not have a base for reading this yet if ( base ) { base->state.BeginReading(); deltaMsg.InitReading( &base->state, NULL, NULL ); targetPlayer = deltaMsg.ReadBits( idMath::BitsForInteger( MAX_CLIENTS ) ); player = static_cast< idPlayer* >( entities[ targetPlayer ] ); if ( !player ) { Error( "ReadNetworkInfo: no local player entity" ); return; } player->ReadPlayerStateFromSnapshot( deltaMsg ); ReadGameStateFromSnapshot( deltaMsg ); } } // set self spectating state according to userinfo settings GetLocalPlayer()->Spectate( idStr::Icmp( userInfo[ clientNum ].GetString( "ui_spectate" ), "Spectate" ) == 0 ); } // read the 'initial reliables' data mpGame.ReadNetworkInfo( file, clientNum ); } /* ============ idGameLocal::SetDemoState ============ */ void idGameLocal::SetDemoState( demoState_t state, bool _serverDemo, bool _timeDemo ) { if ( demoState == DEMO_RECORDING && state == DEMO_NONE ) { ServerClientDisconnect( MAX_CLIENTS ); } demoState = state; serverDemo = _serverDemo; timeDemo = _timeDemo; if ( demoState == DEMO_NONE ) { demo_hud = NULL; demo_mphud = NULL; demo_cursor = NULL; } } /* =============== idGameLocal::ValidateDemoProtocol =============== */ bool idGameLocal::ValidateDemoProtocol( int minor_ref, int minor ) { // 1.1 beta : 67 // 1.1 final: 68 // 1.2 : 69 // 1.3 : 71 // let 1.3 play 1.2 demos - keep a careful eye on snapshotting changes demo_protocol = minor; return ( minor_ref == minor || ( minor_ref == 71 && minor == 69 ) ); } /* =============== idGameLocal::RandomSpawn =============== */ idPlayerStart *idGameLocal::RandomSpawn( void ) { return spawnSpots[ random.RandomInt( spawnSpots.Num() ) ]; }