mirror of
https://github.com/dhewm/dhewm3-sdk.git
synced 2024-11-23 04:52:20 +00:00
f17741ba84
This is based on https://github.com/dhewm/dhewm3/pull/500 by https://github.com/jayaddison See also https://github.com/blendogames/quadrilateralcowboy/pull/4
1758 lines
51 KiB
C++
1758 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.WriteInt( 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.WriteInt( 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.WriteInt( 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.WriteInt( 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.WriteInt( 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.WriteInt( 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.WriteInt( 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.WriteInt( sourceAreas[ i ] );
|
|
} else {
|
|
msg.WriteInt( 0 );
|
|
}
|
|
}
|
|
gameLocal.pvs.WritePVS( pvsHandle, msg );
|
|
#endif
|
|
for ( i = 0; i < ENTITY_PVS_SIZE; i++ ) {
|
|
msg.WriteDeltaInt( 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( "%s", 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.ReadInt();
|
|
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.ReadInt();
|
|
|
|
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.ReadInt() );
|
|
#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.ReadInt() != 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.ReadInt();
|
|
}
|
|
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.ReadDeltaInt( 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.ReadInt();
|
|
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.ReadInt();
|
|
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.ReadInt() );
|
|
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.ReadInt();
|
|
|
|
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.ReadInt();
|
|
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.ReadInt();
|
|
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() ) {
|
|
idStr::Copynz( 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;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
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;
|
|
}
|