dhewm3-sdk/game/Game_network.cpp
Daniel Gibson 9cc5bb2352 Use idStr::Copynz() instead of strncpy()
to guarantee \0-termination
(only the applicable parts of that dhewm3 commit)
2022-05-17 07:17:46 +02:00

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( 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;
}