dhewm3-sdk/d3xp/Game_network.cpp
dhewg afebd7e1e5 Untangle the epic precompiled.h mess
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.
2018-08-20 01:46:28 +02:00

1804 lines
52 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 );
#ifdef _D3XP
// Add portalSky areas to PVS
if ( portalSkyEnt.GetEntity() ) {
pvsHandle_t otherPVS, newPVS;
idEntity *skyEnt = portalSkyEnt.GetEntity();
otherPVS = gameLocal.pvs.SetupCurrentPVS( skyEnt->GetPVSAreas(), skyEnt->GetNumPVSAreas() );
newPVS = gameLocal.pvs.MergeCurrentPVS( pvsHandle, otherPVS );
pvs.FreeCurrentPVS( pvsHandle );
pvs.FreeCurrentPVS( otherPVS );
pvsHandle = newPVS;
}
#endif
#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 );
#ifdef _D3XP
// Add portalSky areas to PVS
if ( portalSkyEnt.GetEntity() ) {
pvsHandle_t otherPVS, newPVS;
idEntity *skyEnt = portalSkyEnt.GetEntity();
otherPVS = gameLocal.pvs.SetupCurrentPVS( skyEnt->GetPVSAreas(), skyEnt->GetNumPVSAreas() );
newPVS = gameLocal.pvs.MergeCurrentPVS( pvsHandle, otherPVS );
pvs.FreeCurrentPVS( pvsHandle );
pvs.FreeCurrentPVS( otherPVS );
pvsHandle = newPVS;
}
#endif
// 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 && !ent->spawnArgs.GetBool("net_dynamic", "0")) { //_D3XP
// 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();
#ifdef CTF
// possible fix for left over lights on CTF flag
ent->FreeLightDef();
#endif
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: {
#ifdef _D3XP
int newServerInfo = msg.ReadBits(1);
if(newServerInfo) {
idDict info;
msg.ReadDeltaDict( info, NULL );
gameLocal.SetServerInfo( info );
}
#endif
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;
}
#ifdef _D3XP
slow.Set( time, previousTime, msec, framenum, realClientTime );
fast.Set( time, previousTime, msec, framenum, realClientTime );
#endif
// 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;
}