mirror of
https://github.com/dhewm/dhewm3.git
synced 2024-11-23 21:02:11 +00:00
736ec20d4d
Don't include the lazy precompiled.h everywhere, only what's required for the compilation unit. platform.h needs to be included instead to provide all essential defines and types. All includes use the relative path to the neo or the game specific root. Move all idlib related includes from idlib/Lib.h to precompiled.h. precompiled.h still exists for the MFC stuff in tools/. Add some missing header guards.
1759 lines
51 KiB
C++
1759 lines
51 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
Doom 3 GPL Source Code
|
|
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
|
|
|
|
This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
|
|
|
|
Doom 3 Source Code is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Doom 3 Source Code is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
|
|
|
|
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "sys/platform.h"
|
|
#include "framework/FileSystem.h"
|
|
#include "framework/async/NetworkSystem.h"
|
|
#include "renderer/RenderSystem.h"
|
|
|
|
#include "gamesys/SysCmds.h"
|
|
#include "Entity.h"
|
|
#include "Player.h"
|
|
|
|
#include "Game_local.h"
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
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_clientSmoothing( "net_clientSmoothing", "0.8", CVAR_GAME | CVAR_FLOAT, "smooth other clients angles and position.", 0.0f, 0.95f );
|
|
idCVar net_clientSelfSmoothing( "net_clientSelfSmoothing", "0.6", CVAR_GAME | CVAR_FLOAT, "smooth self position if network causes prediction error.", 0.0f, 0.95f );
|
|
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", "1", CVAR_GAME | CVAR_BOOL | CVAR_NOCHEAT | CVAR_ARCHIVE, "draw prediction graph" );
|
|
|
|
/*
|
|
================
|
|
idGameLocal::InitAsyncNetwork
|
|
================
|
|
*/
|
|
void idGameLocal::InitAsyncNetwork( void ) {
|
|
int i, type;
|
|
|
|
for ( i = 0; i < MAX_CLIENTS; i++ ) {
|
|
for ( type = 0; type < declManager->GetNumDeclTypes(); type++ ) {
|
|
clientDeclRemap[i][type].Clear();
|
|
}
|
|
}
|
|
|
|
memset( clientEntityStates, 0, sizeof( clientEntityStates ) );
|
|
memset( clientPVS, 0, sizeof( clientPVS ) );
|
|
memset( clientSnapshots, 0, sizeof( clientSnapshots ) );
|
|
|
|
eventQueue.Init();
|
|
savedEventQueue.Init();
|
|
|
|
entityDefBits = -( idMath::BitsForInteger( declManager->GetNumDecls( DECL_ENTITYDEF ) ) + 1 );
|
|
localClientNum = 0; // on a listen server SetLocalUser will set this right
|
|
realClientTime = 0;
|
|
isNewFrame = true;
|
|
clientSmoothing = net_clientSmoothing.GetFloat();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idGameLocal::ShutdownAsyncNetwork
|
|
================
|
|
*/
|
|
void idGameLocal::ShutdownAsyncNetwork( void ) {
|
|
entityStateAllocator.Shutdown();
|
|
snapshotAllocator.Shutdown();
|
|
eventQueue.Shutdown();
|
|
savedEventQueue.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;
|
|
clientSmoothing = net_clientSmoothing.GetFloat();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idGameLocal::InitClientDeclRemap
|
|
================
|
|
*/
|
|
void idGameLocal::InitClientDeclRemap( int clientNum ) {
|
|
int type, i, num;
|
|
|
|
for ( type = 0; type < declManager->GetNumDeclTypes(); type++ ) {
|
|
|
|
// only implicit materials and sound shaders decls are used
|
|
if ( type != DECL_MATERIAL && type != DECL_SOUND ) {
|
|
continue;
|
|
}
|
|
|
|
num = declManager->GetNumDecls( (declType_t) type );
|
|
clientDeclRemap[clientNum][type].Clear();
|
|
clientDeclRemap[clientNum][type].AssureSize( num, -1 );
|
|
|
|
// pre-initialize the remap with non-implicit decls, all non-implicit decls are always going
|
|
// to be in order and in sync between server and client because of the decl manager checksum
|
|
for ( i = 0; i < num; i++ ) {
|
|
const idDecl *decl = declManager->DeclByIndex( (declType_t) type, i, false );
|
|
if ( decl->IsImplicit() ) {
|
|
// once the first implicit decl is found all remaining decls are considered implicit as well
|
|
break;
|
|
}
|
|
clientDeclRemap[clientNum][type][i] = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idGameLocal::ServerSendDeclRemapToClient
|
|
================
|
|
*/
|
|
void idGameLocal::ServerSendDeclRemapToClient( int clientNum, declType_t type, int index ) {
|
|
idBitMsg outMsg;
|
|
byte msgBuf[MAX_GAME_MESSAGE_SIZE];
|
|
|
|
// if no client connected for this spot
|
|
if ( entities[clientNum] == NULL ) {
|
|
return;
|
|
}
|
|
// increase size of list if required
|
|
if ( index >= clientDeclRemap[clientNum][type].Num() ) {
|
|
clientDeclRemap[clientNum][(int)type].AssureSize( index + 1, -1 );
|
|
}
|
|
// if already remapped
|
|
if ( clientDeclRemap[clientNum][(int)type][index] != -1 ) {
|
|
return;
|
|
}
|
|
|
|
const idDecl *decl = declManager->DeclByIndex( type, index, false );
|
|
if ( decl == NULL ) {
|
|
gameLocal.Error( "server tried to remap bad %s decl index %d", declManager->GetDeclNameFromType( type ), index );
|
|
return;
|
|
}
|
|
|
|
// set the index at the server
|
|
clientDeclRemap[clientNum][(int)type][index] = index;
|
|
|
|
// write update to client
|
|
outMsg.Init( msgBuf, sizeof( msgBuf ) );
|
|
outMsg.BeginWriting();
|
|
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_REMAP_DECL );
|
|
outMsg.WriteByte( type );
|
|
outMsg.WriteLong( index );
|
|
outMsg.WriteString( decl->GetName() );
|
|
networkSystem->ServerSendReliableMessage( clientNum, outMsg );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idGameLocal::ServerRemapDecl
|
|
================
|
|
*/
|
|
int idGameLocal::ServerRemapDecl( int clientNum, declType_t type, int index ) {
|
|
|
|
// only implicit materials and sound shaders decls are used
|
|
if ( type != DECL_MATERIAL && type != DECL_SOUND ) {
|
|
return index;
|
|
}
|
|
|
|
if ( clientNum == -1 ) {
|
|
for ( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
ServerSendDeclRemapToClient( i, type, index );
|
|
}
|
|
} else {
|
|
ServerSendDeclRemapToClient( clientNum, type, index );
|
|
}
|
|
return index;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idGameLocal::ClientRemapDecl
|
|
================
|
|
*/
|
|
int idGameLocal::ClientRemapDecl( declType_t type, int index ) {
|
|
|
|
// only implicit materials and sound shaders decls are used
|
|
if ( type != DECL_MATERIAL && type != DECL_SOUND ) {
|
|
return index;
|
|
}
|
|
|
|
// negative indexes are sometimes used for NULL decls
|
|
if ( index < 0 ) {
|
|
return index;
|
|
}
|
|
|
|
// make sure the index is valid
|
|
if ( clientDeclRemap[localClientNum][(int)type].Num() == 0 ) {
|
|
gameLocal.Error( "client received decl index %d before %s decl remap was initialized", index, declManager->GetDeclNameFromType( type ) );
|
|
return -1;
|
|
}
|
|
if ( index >= clientDeclRemap[localClientNum][(int)type].Num() ) {
|
|
gameLocal.Error( "client received unmapped %s decl index %d from server", declManager->GetDeclNameFromType( type ), index );
|
|
return -1;
|
|
}
|
|
if ( clientDeclRemap[localClientNum][(int)type][index] == -1 ) {
|
|
gameLocal.Error( "client received unmapped %s decl index %d from server", declManager->GetDeclNameFromType( type ), index );
|
|
return -1;
|
|
}
|
|
return clientDeclRemap[localClientNum][type][index];
|
|
}
|
|
|
|
/*
|
|
================
|
|
idGameLocal::ServerAllowClient
|
|
================
|
|
*/
|
|
allowReply_t idGameLocal::ServerAllowClient( int numClients, const char *IP, const char *guid, const char *password, char reason[ MAX_STRING_CHARS ] ) {
|
|
reason[0] = '\0';
|
|
|
|
if ( serverInfo.GetInt( "si_pure" ) && !mpGame.IsPureReady() ) {
|
|
idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_07139" );
|
|
return ALLOW_NOTYET;
|
|
}
|
|
|
|
if ( !serverInfo.GetInt( "si_maxPlayers" ) ) {
|
|
idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_07140" );
|
|
return ALLOW_NOTYET;
|
|
}
|
|
|
|
if ( numClients >= serverInfo.GetInt( "si_maxPlayers" ) ) {
|
|
idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_07141" );
|
|
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_07142" );
|
|
return ALLOW_NOTYET;
|
|
}
|
|
|
|
if ( !idStr::Cmp( pass, password ) ) {
|
|
return ALLOW_YES;
|
|
}
|
|
|
|
idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_07143" );
|
|
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 ];
|
|
}
|
|
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];
|
|
|
|
// initialize the decl remap
|
|
InitClientDeclRemap( clientNum );
|
|
|
|
// send message to initialize decl remap at the client (this is always the very first reliable game message)
|
|
outMsg.Init( msgBuf, sizeof( msgBuf ) );
|
|
outMsg.BeginWriting();
|
|
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_INIT_DECL_REMAP );
|
|
networkSystem->ServerSendReliableMessage( clientNum, outMsg );
|
|
|
|
// spawn the player
|
|
SpawnPlayer( clientNum );
|
|
if ( clientNum == localClientNum ) {
|
|
mpGame.EnterGame( clientNum );
|
|
}
|
|
|
|
// 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 );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idGameLocal::ServerClientDisconnect
|
|
================
|
|
*/
|
|
void idGameLocal::ServerClientDisconnect( int clientNum ) {
|
|
int i;
|
|
idBitMsg outMsg;
|
|
byte msgBuf[MAX_GAME_MESSAGE_SIZE];
|
|
|
|
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 ] ) );
|
|
|
|
// delete the player entity
|
|
delete entities[ clientNum ];
|
|
|
|
mpGame.DisconnectClient( 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];
|
|
entityNetEvent_t *event;
|
|
|
|
// 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 );
|
|
}
|
|
|
|
// send all saved events
|
|
for ( event = savedEventQueue.Start(); event; event = event->next ) {
|
|
outMsg.Init( msgBuf, sizeof( msgBuf ) );
|
|
outMsg.BeginWriting();
|
|
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_EVENT );
|
|
outMsg.WriteBits( event->spawnId, 32 );
|
|
outMsg.WriteByte( event->event );
|
|
outMsg.WriteLong( event->time );
|
|
outMsg.WriteBits( event->paramsSize, idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) );
|
|
if ( event->paramsSize ) {
|
|
outMsg.WriteData( event->paramsBuf, event->paramsSize );
|
|
}
|
|
|
|
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::SaveEntityNetworkEvent
|
|
================
|
|
*/
|
|
void idGameLocal::SaveEntityNetworkEvent( const idEntity *ent, int eventId, const idBitMsg *msg ) {
|
|
entityNetEvent_t *event;
|
|
|
|
event = savedEventQueue.Alloc();
|
|
event->spawnId = GetSpawnId( ent );
|
|
event->event = eventId;
|
|
event->time = time;
|
|
if ( msg ) {
|
|
event->paramsSize = msg->GetSize();
|
|
memcpy( event->paramsBuf, msg->GetData(), msg->GetSize() );
|
|
} else {
|
|
event->paramsSize = 0;
|
|
}
|
|
|
|
savedEventQueue.Enqueue( event, idEventQueue::OUTOFORDER_IGNORE );
|
|
}
|
|
|
|
/*
|
|
================
|
|
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;
|
|
}
|
|
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.
|
|
================
|
|
*/
|
|
void idGameLocal::ServerWriteSnapshot( int clientNum, int sequence, idBitMsg &msg, byte *clientInPVS, int numPVSClients ) {
|
|
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<idPlayer *>( 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
|
|
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
|
|
|
|
// 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;
|
|
}
|
|
|
|
// add the entity to the snapshot pvs
|
|
snapshot->pvs[ ent->entityNumber >> 5 ] |= 1 << ( ent->entityNumber & 31 );
|
|
|
|
// if that entity is not marked for network synchronization
|
|
if ( !ent->fl.networkSync ) {
|
|
continue;
|
|
}
|
|
|
|
// 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.Init( base ? &base->state : NULL, &newBase->state, &msg );
|
|
|
|
deltaMsg.WriteBits( spawnIds[ ent->entityNumber ], 32 - GENTITYNUM_BITS );
|
|
deltaMsg.WriteBits( ent->GetType()->typeNum, idClass::GetTypeNumBits() );
|
|
deltaMsg.WriteBits( ServerRemapDecl( -1, DECL_ENTITYDEF, 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.Init( base ? &base->state : NULL, &newBase->state, &msg );
|
|
if ( player->spectating && player->spectator != player->entityNumber && gameLocal.entities[ player->spectator ] && gameLocal.entities[ player->spectator ]->IsType( idPlayer::Type ) ) {
|
|
static_cast< idPlayer * >( gameLocal.entities[ player->spectator ] )->WritePlayerStateToSnapshot( deltaMsg );
|
|
} else {
|
|
player->WritePlayerStateToSnapshot( deltaMsg );
|
|
}
|
|
WriteGameStateToSnapshot( deltaMsg );
|
|
|
|
// copy the client PVS string
|
|
memcpy( clientInPVS, snapshot->pvs, ( numPVSClients + 7 ) >> 3 );
|
|
LittleRevBytes( clientInPVS, sizeof( int ), sizeof( clientInPVS ) / sizeof ( int ) );
|
|
}
|
|
|
|
/*
|
|
================
|
|
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" );
|
|
}
|
|
}
|
|
|
|
entityNetEvent_t* freedEvent id_attribute((unused)) = eventQueue.Dequeue();
|
|
assert( freedEvent == event );
|
|
eventQueue.Free( event );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idGameLocal::ServerSendChatMessage
|
|
================
|
|
*/
|
|
void idGameLocal::ServerSendChatMessage( int to, const char *name, const char *text ) {
|
|
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, -1, false );
|
|
networkSystem->ServerSendReliableMessage( to, outMsg );
|
|
|
|
if ( to == -1 || to == localClientNum ) {
|
|
mpGame.AddChatLine( "%s^0: %s\n", name, text );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
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];
|
|
|
|
msg.ReadString( name, sizeof( name ) );
|
|
msg.ReadString( text, sizeof( text ) );
|
|
|
|
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;
|
|
}
|
|
#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;
|
|
}
|
|
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<idPlayer *>( 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;
|
|
if ( dupeUsercmds <= 2 ) {
|
|
lagometer[i][j][1] = 255;
|
|
}
|
|
lagometer[i][j][3] = 255;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idGameLocal::ClientReadSnapshot
|
|
================
|
|
*/
|
|
void idGameLocal::ClientReadSnapshot( int clientNum, int sequence, const int gameFrame, const int gameTime, const int dupeUsercmds, const int aheadOfServer, const idBitMsg &msg ) {
|
|
int i, typeNum, entityDefNumber, numBitsRead;
|
|
idTypeInfo *typeInfo;
|
|
idEntity *ent;
|
|
idPlayer *player, *spectated;
|
|
pvsHandle_t pvsHandle;
|
|
idDict args;
|
|
const char *classname;
|
|
idBitMsgDelta deltaMsg;
|
|
snapshot_t *snapshot;
|
|
entityState_t *base, *newBase;
|
|
int spawnId;
|
|
int numSourceAreas, sourceAreas[ idEntity::MAX_PVS_AREAS ];
|
|
idWeapon *weap;
|
|
|
|
if ( net_clientLagOMeter.GetBool() && renderSystem ) {
|
|
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 );
|
|
|
|
// clear any debug lines from a previous frame
|
|
gameRenderWorld->DebugClearLines( time );
|
|
|
|
// clear any debug polygons from a previous frame
|
|
gameRenderWorld->DebugClearPolygons( time );
|
|
|
|
// update the game time
|
|
framenum = gameFrame;
|
|
time = gameTime;
|
|
previousTime = time - msec;
|
|
|
|
// 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 = sequence;
|
|
snapshot->firstEntityState = NULL;
|
|
snapshot->next = clientSnapshots[clientNum];
|
|
clientSnapshots[clientNum] = snapshot;
|
|
|
|
#if ASYNC_WRITE_TAGS
|
|
idRandom tagRandom;
|
|
tagRandom.SetSeed( msg.ReadLong() );
|
|
#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.Init( base ? &base->state : NULL, &newBase->state, &msg );
|
|
|
|
spawnId = deltaMsg.ReadBits( 32 - GENTITYNUM_BITS );
|
|
typeNum = deltaMsg.ReadBits( idClass::GetTypeNumBits() );
|
|
entityDefNumber = ClientRemapDecl( DECL_ENTITYDEF, deltaMsg.ReadBits( entityDefBits ) );
|
|
|
|
typeInfo = idClass::GetType( typeNum );
|
|
if ( !typeInfo ) {
|
|
Error( "Unknown type number %d for entity %d with class number %d", typeNum, i, entityDefNumber );
|
|
}
|
|
|
|
ent = entities[i];
|
|
|
|
// if there is no entity or an entity of the wrong type
|
|
if ( !ent || ent->GetType()->typeNum != typeNum || 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 ) );
|
|
|
|
if ( entityDefNumber >= 0 ) {
|
|
if ( entityDefNumber >= declManager->GetNumDecls( DECL_ENTITYDEF ) ) {
|
|
Error( "server has %d entityDefs instead of %d", entityDefNumber, declManager->GetNumDecls( DECL_ENTITYDEF ) );
|
|
}
|
|
classname = declManager->DeclByIndex( DECL_ENTITYDEF, entityDefNumber, false )->GetName();
|
|
args.Set( "classname", classname );
|
|
if ( !SpawnEntityDef( args, &ent ) || !entities[i] || entities[i]->GetType()->typeNum != typeNum ) {
|
|
Error( "Failed to spawn entity with classname '%s' of type '%s'", classname, typeInfo->classname );
|
|
}
|
|
} else {
|
|
ent = SpawnEntityType( *typeInfo, &args, true );
|
|
if ( !entities[i] || entities[i]->GetType()->typeNum != typeNum ) {
|
|
Error( "Failed to spawn entity of type '%s'", typeInfo->classname );
|
|
}
|
|
}
|
|
if ( i < MAX_CLIENTS && i >= numClients ) {
|
|
numClients = i + 1;
|
|
}
|
|
}
|
|
|
|
// add the entity to the snapshot list
|
|
ent->snapshotNode.AddToEnd( snapshotEntities );
|
|
ent->snapshotSequence = sequence;
|
|
|
|
// read the class specific data from the snapshot
|
|
ent->ReadFromSnapshot( deltaMsg );
|
|
|
|
ent->snapshotBits = msg.GetNumBitsRead() - numBitsRead;
|
|
|
|
#if ASYNC_WRITE_TAGS
|
|
if ( msg.ReadLong() != tagRandom.RandomInt() ) {
|
|
cmdSystem->BufferCommandText( CMD_EXEC_NOW, "writeGameState" );
|
|
if ( entityDefNumber >= 0 && entityDefNumber < declManager->GetNumDecls( DECL_ENTITYDEF ) ) {
|
|
classname = declManager->DeclByIndex( DECL_ENTITYDEF, entityDefNumber, false )->GetName();
|
|
Error( "write to and read from snapshot out of sync for classname '%s' of type '%s'", classname, typeInfo->classname );
|
|
} else {
|
|
Error( "write to and read from snapshot out of sync for type '%s'", typeInfo->classname );
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
player = static_cast<idPlayer *>( entities[clientNum] );
|
|
if ( !player ) {
|
|
return;
|
|
}
|
|
|
|
// if prediction is off, enable local client smoothing
|
|
player->SetSelfSmooth( dupeUsercmds > 2 );
|
|
|
|
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", sequence );
|
|
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] );
|
|
}
|
|
|
|
// add entities in the PVS that haven't changed since the last applied snapshot
|
|
for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
|
|
|
|
// if the entity is already in the snapshot
|
|
if ( ent->snapshotSequence == sequence ) {
|
|
continue;
|
|
}
|
|
|
|
// if the entity is not in the snapshot PVS
|
|
if ( !( snapshot->pvs[ent->entityNumber >> 5] & ( 1 << ( ent->entityNumber & 31 ) ) ) ) {
|
|
if ( ent->PhysicsTeamInPVS( pvsHandle ) ) {
|
|
if ( ent->entityNumber >= MAX_CLIENTS && ent->entityNumber < mapSpawnCount ) {
|
|
// 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
|
|
common->DWarning( "client thinks map entity 0x%x (%s) is stale, sequence 0x%x", ent->entityNumber, ent->name.c_str(), sequence );
|
|
} else {
|
|
ent->FreeModelDef();
|
|
ent->UpdateVisuals();
|
|
ent->GetPhysics()->UnlinkClip();
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// add the entity to the snapshot list
|
|
ent->snapshotNode.AddToEnd( snapshotEntities );
|
|
ent->snapshotSequence = sequence;
|
|
ent->snapshotBits = 0;
|
|
|
|
base = clientEntityStates[clientNum][ent->entityNumber];
|
|
if ( !base ) {
|
|
// entity has probably fl.networkSync set to false
|
|
continue;
|
|
}
|
|
|
|
base->state.BeginReading();
|
|
|
|
deltaMsg.Init( &base->state, NULL, (const idBitMsg *)NULL );
|
|
|
|
spawnId = deltaMsg.ReadBits( 32 - GENTITYNUM_BITS );
|
|
typeNum = deltaMsg.ReadBits( idClass::GetTypeNumBits() );
|
|
entityDefNumber = deltaMsg.ReadBits( entityDefBits );
|
|
|
|
typeInfo = idClass::GetType( typeNum );
|
|
|
|
// if the entity is not the right type
|
|
if ( !typeInfo || ent->GetType()->typeNum != typeNum || ent->entityDefNumber != entityDefNumber ) {
|
|
// should never happen - it does though. with != entityDefNumber only?
|
|
common->DWarning( "entity '%s' is not the right type %p 0x%d 0x%x 0x%x 0x%x", ent->GetName(), typeInfo, ent->GetType()->typeNum, typeNum, ent->entityDefNumber, entityDefNumber );
|
|
continue;
|
|
}
|
|
|
|
// read the class specific data from the base state
|
|
ent->ReadFromSnapshot( deltaMsg );
|
|
}
|
|
|
|
// 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.Init( base ? &base->state : NULL, &newBase->state, &msg );
|
|
if ( player->spectating && player->spectator != player->entityNumber && gameLocal.entities[ player->spectator ] && gameLocal.entities[ player->spectator ]->IsType( idPlayer::Type ) ) {
|
|
static_cast< idPlayer * >( gameLocal.entities[ player->spectator ] )->ReadPlayerStateFromSnapshot( deltaMsg );
|
|
weap = static_cast< idPlayer * >( gameLocal.entities[ player->spectator ] )->weapon.GetEntity();
|
|
if ( weap && ( weap->GetRenderEntity()->bounds[0] == weap->GetRenderEntity()->bounds[1] ) ) {
|
|
// update the weapon's viewmodel bounds so that the model doesn't flicker in the spectator's view
|
|
weap->GetAnimator()->GetBounds( gameLocal.time, weap->GetRenderEntity()->bounds );
|
|
weap->UpdateVisuals();
|
|
}
|
|
} else {
|
|
player->ReadPlayerStateFromSnapshot( deltaMsg );
|
|
}
|
|
ReadGameStateFromSnapshot( deltaMsg );
|
|
|
|
// visualize the snapshot
|
|
ClientShowSnapshot( clientNum );
|
|
|
|
// 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" );
|
|
}
|
|
}
|
|
|
|
entityNetEvent_t* freedEvent id_attribute((unused)) = eventQueue.Dequeue();
|
|
assert( freedEvent == event );
|
|
eventQueue.Free( event );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idGameLocal::ClientProcessReliableMessage
|
|
================
|
|
*/
|
|
void idGameLocal::ClientProcessReliableMessage( int clientNum, const idBitMsg &msg ) {
|
|
int id, line;
|
|
idPlayer *p;
|
|
idDict backupSI;
|
|
|
|
InitLocalClient( clientNum );
|
|
|
|
id = msg.ReadByte();
|
|
switch( id ) {
|
|
case GAME_RELIABLE_MESSAGE_INIT_DECL_REMAP: {
|
|
InitClientDeclRemap( clientNum );
|
|
break;
|
|
}
|
|
case GAME_RELIABLE_MESSAGE_REMAP_DECL: {
|
|
int type, index;
|
|
char name[MAX_STRING_CHARS];
|
|
|
|
type = msg.ReadByte();
|
|
index = msg.ReadLong();
|
|
msg.ReadString( name, sizeof( name ) );
|
|
|
|
const idDecl *decl = declManager->FindType( (declType_t)type, name, false );
|
|
if ( decl != NULL ) {
|
|
if ( index >= clientDeclRemap[clientNum][type].Num() ) {
|
|
clientDeclRemap[clientNum][type].AssureSize( index + 1, -1 );
|
|
}
|
|
clientDeclRemap[clientNum][type][index] = decl->Index();
|
|
}
|
|
break;
|
|
}
|
|
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;
|
|
}
|
|
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];
|
|
msg.ReadString( name, sizeof( name ) );
|
|
msg.ReadString( text, sizeof( text ) );
|
|
mpGame.AddChatLine( "%s^0: %s\n", name, text );
|
|
break;
|
|
}
|
|
case GAME_RELIABLE_MESSAGE_SOUND_EVENT: {
|
|
snd_evt_t snd_evt = (snd_evt_t)msg.ReadByte();
|
|
mpGame.PlayGlobalSound( -1, snd_evt );
|
|
break;
|
|
}
|
|
case GAME_RELIABLE_MESSAGE_SOUND_INDEX: {
|
|
int index = gameLocal.ClientRemapDecl( DECL_SOUND, msg.ReadLong() );
|
|
if ( index >= 0 && index < declManager->GetNumDecls( DECL_SOUND ) ) {
|
|
const idSoundShader *shader = declManager->SoundByIndex( index );
|
|
mpGame.PlayGlobalSound( -1, SND_COUNT, shader->GetName() );
|
|
}
|
|
break;
|
|
}
|
|
case GAME_RELIABLE_MESSAGE_DB: {
|
|
idMultiplayerGame::msg_evt_t msg_evt = (idMultiplayerGame::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 );
|
|
gameLocal.SetServerInfo( info );
|
|
break;
|
|
}
|
|
case GAME_RELIABLE_MESSAGE_RESTART: {
|
|
MapRestart();
|
|
break;
|
|
}
|
|
case GAME_RELIABLE_MESSAGE_TOURNEYLINE: {
|
|
line = msg.ReadByte( );
|
|
p = static_cast< idPlayer * >( entities[ clientNum ] );
|
|
if ( !p ) {
|
|
break;
|
|
}
|
|
p->tourneyLine = line;
|
|
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_UPDATEVOTE: {
|
|
int result = msg.ReadByte( );
|
|
int yesCount = msg.ReadByte( );
|
|
int noCount = msg.ReadByte( );
|
|
mpGame.ClientUpdateVote( (idMultiplayerGame::vote_result_t)result, yesCount, noCount );
|
|
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;
|
|
}
|
|
case GAME_RELIABLE_MESSAGE_WARMUPTIME: {
|
|
mpGame.ClientReadWarmupTime( msg );
|
|
break;
|
|
}
|
|
default: {
|
|
Error( "Unknown server->client reliable message: %d", id );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idGameLocal::ClientPrediction
|
|
================
|
|
*/
|
|
gameReturn_t idGameLocal::ClientPrediction( int clientNum, const usercmd_t *clientCmds, bool lastPredictFrame ) {
|
|
idEntity *ent;
|
|
idPlayer *player;
|
|
gameReturn_t ret;
|
|
|
|
ret.sessionCommand[ 0 ] = '\0';
|
|
|
|
player = static_cast<idPlayer *>( entities[clientNum] );
|
|
if ( !player ) {
|
|
return ret;
|
|
}
|
|
|
|
// check for local client lag
|
|
if ( networkSystem->ClientGetTimeSinceLastPacket() >= net_clientMaxPrediction.GetInteger() ) {
|
|
player->isLagged = true;
|
|
} else {
|
|
player->isLagged = false;
|
|
}
|
|
|
|
InitLocalClient( clientNum );
|
|
|
|
// update the game time
|
|
framenum++;
|
|
previousTime = time;
|
|
time += msec;
|
|
|
|
// update the real client time and the new frame flag
|
|
if ( time > realClientTime ) {
|
|
realClientTime = time;
|
|
isNewFrame = true;
|
|
} else {
|
|
isNewFrame = false;
|
|
}
|
|
|
|
// set the user commands for this frame
|
|
memcpy( usercmds, clientCmds, numClients * sizeof( usercmds[ 0 ] ) );
|
|
|
|
// run prediction on all entities from the last snapshot
|
|
for( ent = snapshotEntities.Next(); ent != NULL; ent = ent->snapshotNode.Next() ) {
|
|
ent->thinkFlags |= TH_PHYSICS;
|
|
ent->ClientPredictionThink();
|
|
}
|
|
|
|
// 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 ) );
|
|
}
|
|
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 request, 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;
|
|
int i, j;
|
|
|
|
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;
|
|
}
|
|
for ( j = 0; j < dlTable.Num(); j++ ) {
|
|
if ( !fileSystem->FilenameCompare( pakList[ i ], 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->DPrintf( "download for %s: %s\n", IP, url.c_str() );
|
|
}
|
|
}
|
|
|
|
idStr::Copynz( urls, reply, MAX_STRING_CHARS );
|
|
return true;
|
|
}
|
|
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;
|
|
}
|