doom3-bfg/neo/d3xp/Game_network.cpp

1549 lines
40 KiB
C++

/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition 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 BFG Edition 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 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition 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 BFG Edition 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 "precompiled.h"
#pragma hdrstop
#include "Game_local.h"
#include "../framework/Common_local.h"
static const int SNAP_GAMESTATE = 0;
static const int SNAP_SHADERPARMS = 1;
static const int SNAP_PORTALS = 2;
static const int SNAP_PLAYERSTATE = SNAP_PORTALS + 1;
static const int SNAP_PLAYERSTATE_END = SNAP_PLAYERSTATE + MAX_PLAYERS;
static const int SNAP_ENTITIES = SNAP_PLAYERSTATE_END;
static const int SNAP_ENTITIES_END = SNAP_ENTITIES + MAX_GENTITIES;
static const int SNAP_LAST_CLIENT_FRAME = SNAP_ENTITIES_END;
static const int SNAP_LAST_CLIENT_FRAME_END = SNAP_LAST_CLIENT_FRAME + MAX_PLAYERS;
/*
===============================================================================
Client running game code:
- entity events don't work and should not be issued
- entities should never be spawned outside idGameLocal::ClientReadSnapshot
===============================================================================
*/
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 );
extern idCVar net_clientMaxPrediction;
idCVar cg_predictedSpawn_debug( "cg_predictedSpawn_debug", "0", CVAR_BOOL, "Debug predictive spawning of presentables" );
idCVar g_clientFire_checkLineOfSightDebug( "g_clientFire_checkLineOfSightDebug", "0", CVAR_BOOL, "" );
/*
================
idGameLocal::InitAsyncNetwork
================
*/
void idGameLocal::InitAsyncNetwork()
{
eventQueue.Init();
savedEventQueue.Init();
entityDefBits = -( idMath::BitsForInteger( declManager->GetNumDecls( DECL_ENTITYDEF ) ) + 1 );
realClientTime = 0;
fast.Set( 0, 0, 0 );
slow.Set( 0, 0, 0 );
isNewFrame = true;
clientSmoothing = net_clientSmoothing.GetFloat();
lastCmdRunTimeOnClient.Zero();
lastCmdRunTimeOnServer.Zero();
usercmdLastClientMilliseconds.Zero();
}
/*
================
idGameLocal::ShutdownAsyncNetwork
================
*/
void idGameLocal::ShutdownAsyncNetwork()
{
eventQueue.Shutdown();
savedEventQueue.Shutdown();
}
/*
================
idGameLocal::ServerRemapDecl
================
*/
int idGameLocal::ServerRemapDecl( int clientNum, declType_t type, int index )
{
return index;
}
/*
================
idGameLocal::ClientRemapDecl
================
*/
int idGameLocal::ClientRemapDecl( declType_t type, int index )
{
return index;
}
/*
================
idGameLocal::SyncPlayersWithLobbyUsers
================
*/
void idGameLocal::SyncPlayersWithLobbyUsers( bool initial )
{
idLobbyBase& lobby = session->GetActingGameStateLobbyBase();
if( !lobby.IsHost() )
{
return;
}
idStaticList< lobbyUserID_t, MAX_CLIENTS > newLobbyUsers;
// First, loop over lobby users, and see if we find a lobby user that we haven't registered
for( int i = 0; i < lobby.GetNumLobbyUsers(); i++ )
{
lobbyUserID_t lobbyUserID1 = lobby.GetLobbyUserIdByOrdinal( i );
if( !lobbyUserID1.IsValid() )
{
continue;
}
if( !initial && !lobby.IsLobbyUserLoaded( lobbyUserID1 ) )
{
continue;
}
// Now, see if we find this lobby user in our list
bool found = false;
for( int j = 0; j < MAX_PLAYERS; j++ )
{
idPlayer* player = static_cast<idPlayer*>( entities[ j ] );
if( player == NULL )
{
continue;
}
lobbyUserID_t lobbyUserID2 = lobbyUserIDs[j];
if( lobbyUserID1 == lobbyUserID2 )
{
found = true;
break;
}
}
if( !found )
{
// If we didn't find it, we need to create a player and assign it to this new lobby user
newLobbyUsers.Append( lobbyUserID1 );
}
}
// Validate connected players
for( int i = 0; i < MAX_PLAYERS; i++ )
{
idPlayer* player = static_cast<idPlayer*>( entities[ i ] );
if( player == NULL )
{
continue;
}
lobbyUserID_t lobbyUserID = lobbyUserIDs[i];
if( !lobby.IsLobbyUserValid( lobbyUserID ) )
{
delete entities[ i ];
mpGame.DisconnectClient( i );
lobbyUserIDs[i] = lobbyUserID_t();
continue;
}
lobby.EnableSnapshotsForLobbyUser( lobbyUserID );
}
while( newLobbyUsers.Num() > 0 )
{
// Find a free player data slot to use for this new player
int freePlayerDataIndex = -1;
for( int i = 0; i < MAX_PLAYERS; ++i )
{
idPlayer* player = static_cast<idPlayer*>( entities[ i ] );
if( player == NULL )
{
freePlayerDataIndex = i;
break;
}
}
if( freePlayerDataIndex == -1 )
{
break; // No player data slots (this shouldn't happen)
}
lobbyUserID_t lobbyUserID = newLobbyUsers[0];
newLobbyUsers.RemoveIndex( 0 );
mpGame.ServerClientConnect( freePlayerDataIndex );
Printf( "client %d connected.\n", freePlayerDataIndex );
lobbyUserIDs[ freePlayerDataIndex ] = lobbyUserID;
// Clear this player's old usercmds.
common->ResetPlayerInput( freePlayerDataIndex );
common->UpdateLevelLoadPacifier();
// spawn the player
SpawnPlayer( freePlayerDataIndex );
common->UpdateLevelLoadPacifier();
ServerWriteInitialReliableMessages( freePlayerDataIndex, lobbyUserID );
}
}
/*
================
idGameLocal::ServerSendNetworkSyncCvars
================
*/
void idGameLocal::ServerSendNetworkSyncCvars()
{
if( ( cvarSystem->GetModifiedFlags() & CVAR_NETWORKSYNC ) == 0 )
{
return;
}
cvarSystem->ClearModifiedFlags( CVAR_NETWORKSYNC );
idBitMsg outMsg;
byte msgBuf[MAX_GAME_MESSAGE_SIZE];
idLobbyBase& lobby = session->GetActingGameStateLobbyBase();
outMsg.InitWrite( msgBuf, sizeof( msgBuf ) );
outMsg.BeginWriting();
idDict syncedCvars;
cvarSystem->MoveCVarsToDict( CVAR_NETWORKSYNC, syncedCvars, true );
outMsg.WriteDeltaDict( syncedCvars, NULL );
lobby.SendReliable( GAME_RELIABLE_MESSAGE_SYNCEDCVARS, outMsg, false );
idLib::Printf( "Sending networkSync cvars:\n" );
syncedCvars.Print();
}
/*
================
idGameLocal::ServerWriteInitialReliableMessages
Send reliable messages to initialize the client game up to a certain initial state.
================
*/
void idGameLocal::ServerWriteInitialReliableMessages( int clientNum, lobbyUserID_t lobbyUserID )
{
if( clientNum == GetLocalClientNum() )
{
// We don't need to send messages to ourself
return;
}
idBitMsg outMsg;
byte msgBuf[MAX_GAME_MESSAGE_SIZE];
idLobbyBase& lobby = session->GetActingGameStateLobbyBase();
outMsg.InitWrite( msgBuf, sizeof( msgBuf ) );
outMsg.BeginWriting();
idDict syncedCvars;
cvarSystem->MoveCVarsToDict( CVAR_NETWORKSYNC, syncedCvars, true );
outMsg.WriteDeltaDict( syncedCvars, NULL );
lobby.SendReliableToLobbyUser( lobbyUserID, GAME_RELIABLE_MESSAGE_SYNCEDCVARS, outMsg );
idLib::Printf( "Sending initial networkSync cvars:\n" );
syncedCvars.Print();
// send all saved events
for( entityNetEvent_t* event = savedEventQueue.Start(); event; event = event->next )
{
outMsg.InitWrite( msgBuf, sizeof( msgBuf ) );
outMsg.BeginWriting();
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 );
}
lobby.SendReliableToLobbyUser( lobbyUserID, GAME_RELIABLE_MESSAGE_EVENT, outMsg );
}
mpGame.ServerWriteInitialReliableMessages( clientNum, lobbyUserID );
}
/*
================
idGameLocal::SaveEntityNetworkEvent
================
*/
void idGameLocal::SaveEntityNetworkEvent( const idEntity* ent, int eventId, const idBitMsg* msg )
{
entityNetEvent_t* event = savedEventQueue.Alloc();
event->spawnId = GetSpawnId( ent );
event->event = eventId;
event->time = time;
if( msg )
{
event->paramsSize = msg->GetSize();
memcpy( event->paramsBuf, msg->GetReadData(), msg->GetSize() );
}
else
{
event->paramsSize = 0;
}
savedEventQueue.Enqueue( event, idEventQueue::OUTOFORDER_IGNORE );
}
/*
================
idGameLocal::ServerWriteSnapshot
Write a snapshot of the current game state
================
*/
void idGameLocal::ServerWriteSnapshot( idSnapShot& ss )
{
ss.SetTime( fast.time );
byte buffer[ MAX_ENTITY_STATE_SIZE ];
idBitMsg msg;
// First write the generic game state to the snapshot
msg.InitWrite( buffer, sizeof( buffer ) );
mpGame.WriteToSnapshot( msg );
ss.S_AddObject( SNAP_GAMESTATE, ~0U, msg, "Game State" );
// Update global shader parameters
msg.InitWrite( buffer, sizeof( buffer ) );
for( int i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ )
{
msg.WriteFloat( globalShaderParms[i] );
}
ss.S_AddObject( SNAP_SHADERPARMS, ~0U, msg, "Shader Parms" );
// update portals for opened doors
msg.InitWrite( buffer, sizeof( buffer ) );
int numPortals = gameRenderWorld->NumPortals();
msg.WriteLong( numPortals );
for( int i = 0; i < numPortals; i++ )
{
msg.WriteBits( gameRenderWorld->GetPortalState( ( qhandle_t )( i + 1 ) ) , NUM_RENDER_PORTAL_BITS );
}
ss.S_AddObject( SNAP_PORTALS, ~0U, msg, "Portal State" );
idEntity* skyEnt = portalSkyEnt.GetEntity();
pvsHandle_t portalSkyPVS;
portalSkyPVS.i = -1;
if( skyEnt != NULL )
{
portalSkyPVS = pvs.SetupCurrentPVS( skyEnt->GetPVSAreas(), skyEnt->GetNumPVSAreas() );
}
// Build PVS data for each player and write their player state to the snapshot as well
pvsHandle_t pvsHandles[ MAX_PLAYERS ];
for( int i = 0; i < MAX_PLAYERS; i++ )
{
idPlayer* player = static_cast<idPlayer*>( entities[ i ] );
if( player == NULL )
{
pvsHandles[i].i = -1;
continue;
}
idPlayer* spectated = player;
if( player->spectating && player->spectator != i && entities[ player->spectator ] )
{
spectated = static_cast< idPlayer* >( entities[ player->spectator ] );
}
msg.InitWrite( buffer, sizeof( buffer ) );
spectated->WritePlayerStateToSnapshot( msg );
ss.S_AddObject( SNAP_PLAYERSTATE + i, ~0U, msg, "Player State" );
int sourceAreas[ idEntity::MAX_PVS_AREAS ];
int numSourceAreas = gameRenderWorld->BoundsInAreas( spectated->GetPlayerPhysics()->GetAbsBounds(), sourceAreas, idEntity::MAX_PVS_AREAS );
pvsHandles[i] = pvs.SetupCurrentPVS( sourceAreas, numSourceAreas, PVS_NORMAL );
if( portalSkyPVS.i >= 0 )
{
pvsHandle_t tempPVS = pvs.MergeCurrentPVS( pvsHandles[i], portalSkyPVS );
pvs.FreeCurrentPVS( pvsHandles[i] );
pvsHandles[i] = tempPVS;
}
// Write the last usercmd processed by the server so that clients know
// when to stop predicting.
msg.BeginWriting();
msg.WriteLong( usercmdLastClientMilliseconds[i] );
ss.S_AddObject( SNAP_LAST_CLIENT_FRAME + i, ~0U, msg, "Last client frame" );
}
if( portalSkyPVS.i >= 0 )
{
pvs.FreeCurrentPVS( portalSkyPVS );
}
// Add all entities to the snapshot
for( idEntity* ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() )
{
if( ent->GetSkipReplication() )
{
continue;
}
msg.InitWrite( buffer, sizeof( buffer ) );
msg.WriteBits( spawnIds[ ent->entityNumber ], 32 - GENTITYNUM_BITS );
msg.WriteBits( ent->GetType()->typeNum, idClass::GetTypeNumBits() );
msg.WriteBits( ServerRemapDecl( -1, DECL_ENTITYDEF, ent->entityDefNumber ), entityDefBits );
msg.WriteBits( ent->GetPredictedKey(), 32 );
if( ent->fl.networkSync )
{
// write the class specific data to the snapshot
ent->WriteToSnapshot( msg );
}
ss.S_AddObject( SNAP_ENTITIES + ent->entityNumber, ~0U, msg, ent->GetName() );
}
// Free PVS handles for all the players
for( int i = 0; i < MAX_PLAYERS; i++ )
{
if( pvsHandles[i].i < 0 )
{
continue;
}
pvs.FreeCurrentPVS( pvsHandles[i] );
}
}
/*
================
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()
{
while( eventQueue.Start() )
{
entityNetEvent_t* 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
{
idEntity* ent = entPtr.GetEntity();
assert( ent );
idBitMsg eventMsg;
eventMsg.InitRead( 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 = eventQueue.Dequeue();
verify( freedEvent == event );
eventQueue.Free( event );
}
}
/*
================
idGameLocal::ProcessReliableMessage
================
*/
void idGameLocal::ProcessReliableMessage( int clientNum, int type, const idBitMsg& msg )
{
if( session->GetActingGameStateLobbyBase().IsPeer() )
{
ClientProcessReliableMessage( type, msg );
}
else
{
ServerProcessReliableMessage( clientNum, type, msg );
}
}
/*
================
idGameLocal::ServerProcessReliableMessage
================
*/
void idGameLocal::ServerProcessReliableMessage( int clientNum, int type, const idBitMsg& msg )
{
if( clientNum < 0 )
{
return;
}
switch( type )
{
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, type == 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_DROPWEAPON:
{
mpGame.DropWeapon( clientNum );
break;
}
case GAME_RELIABLE_MESSAGE_EVENT:
{
// allocate new event
entityNetEvent_t* 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;
}
case GAME_RELIABLE_MESSAGE_SPECTATE:
{
bool spec = msg.ReadBool();
idPlayer* player = GetClientByNum( clientNum );
if( serverInfo.GetBool( "si_spectators" ) )
{
// never let spectators go back to game while sudden death is on
if( mpGame.GetGameState() == idMultiplayerGame::SUDDENDEATH && !spec && player->wantSpectate )
{
// Don't allow the change
}
else
{
if( player->wantSpectate && !spec )
{
player->forceRespawn = true;
}
player->wantSpectate = spec;
}
}
else
{
// If the server turned off si_spectators while a player is spectating, then any spectate message forces the player out of spectate mode
if( player->wantSpectate )
{
player->forceRespawn = true;
}
player->wantSpectate = false;
}
break;
}
case GAME_RELIABLE_MESSAGE_CLIENT_HITSCAN_HIT:
{
const int attackerNum = msg.ReadShort();
const int victimNum = msg.ReadShort();
idVec3 dir;
msg.ReadVectorFloat( dir );
const int damageDefIndex = msg.ReadLong();
const float damageScale = msg.ReadFloat();
const int location = msg.ReadLong();
if( gameLocal.entities[victimNum] == NULL )
{
break;
}
if( gameLocal.entities[attackerNum] == NULL )
{
break;
}
idPlayer& victim = static_cast< idPlayer& >( *gameLocal.entities[victimNum] );
idPlayer& attacker = static_cast< idPlayer& >( *gameLocal.entities[attackerNum] );
if( victim.GetPhysics() == NULL )
{
break;
}
if( attacker.weapon.GetEntity() == NULL )
{
break;
}
if( location == INVALID_JOINT )
{
break;
}
// Line of sight check. As a basic precaution against cheating,
// the server performs a ray intersection from the client's position
// to the joint he hit on the target.
idVec3 muzzleOrigin;
idMat3 muzzleAxis;
attacker.weapon.GetEntity()->GetProjectileLaunchOriginAndAxis( muzzleOrigin, muzzleAxis );
idVec3 targetLocation = victim.GetRenderEntity()->origin + victim.GetRenderEntity()->joints[location].ToVec3() * victim.GetRenderEntity()->axis;
trace_t tr;
gameLocal.clip.Translation( tr, muzzleOrigin, targetLocation, NULL, mat3_identity, MASK_SHOT_RENDERMODEL, &attacker );
idEntity* hitEnt = gameLocal.entities[ tr.c.entityNum ];
if( hitEnt != &victim )
{
break;
}
const idDeclEntityDef* damageDef = static_cast<const idDeclEntityDef*>( declManager->DeclByIndex( DECL_ENTITYDEF, damageDefIndex, false ) );
if( damageDef != NULL )
{
victim.Damage( NULL, gameLocal.entities[attackerNum], dir, damageDef->GetName(), damageScale, location );
}
break;
}
default:
{
Warning( "Unknown reliable message (%d) from client %d", type, clientNum );
break;
}
}
}
/*
================
idGameLocal::ClientReadSnapshot
================
*/
void idGameLocal::ClientReadSnapshot( const idSnapShot& ss )
{
if( GetLocalClientNum() < 0 )
{
return;
}
// if prediction is off, enable local client smoothing
//localPlayer->SetSelfSmooth( dupeUsercmds > 2 );
// clear any debug lines from a previous frame
gameRenderWorld->DebugClearLines( time );
// clear any debug polygons from a previous frame
gameRenderWorld->DebugClearPolygons( time );
SelectTimeGroup( false );
// so that StartSound/StopSound doesn't risk skipping
isNewFrame = true;
// clear the snapshot entity list
snapshotEntities.Clear();
// read all entities from the snapshot
for( int o = 0; o < ss.NumObjects(); o++ )
{
idBitMsg msg;
int snapObjectNum = ss.GetObjectMsgByIndex( o, msg );
if( snapObjectNum < 0 )
{
assert( false );
continue;
}
if( snapObjectNum == SNAP_GAMESTATE )
{
mpGame.ReadFromSnapshot( msg );
continue;
}
if( snapObjectNum == SNAP_SHADERPARMS )
{
for( int i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ )
{
globalShaderParms[i] = msg.ReadFloat();
}
continue;
}
if( snapObjectNum == SNAP_PORTALS )
{
// update portals for opened doors
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 ) );
}
continue;
}
if( snapObjectNum >= SNAP_PLAYERSTATE && snapObjectNum < SNAP_PLAYERSTATE_END )
{
int playerNumber = snapObjectNum - SNAP_PLAYERSTATE;
idPlayer* otherPlayer = static_cast< idPlayer* >( entities[ playerNumber ] );
// Don't process Player Snapshots that are disconnected.
const int lobbyIndex = session->GetActingGameStateLobbyBase().GetLobbyUserIndexFromLobbyUserID( lobbyUserIDs[ playerNumber ] );
if( lobbyIndex < 0 || session->GetActingGameStateLobbyBase().IsLobbyUserConnected( lobbyIndex ) == false )
{
continue;
}
if( otherPlayer != NULL )
{
otherPlayer->ReadPlayerStateFromSnapshot( msg );
if( otherPlayer != entities[ GetLocalClientNum() ] ) // This happens when we spectate another player
{
idWeapon* weap = otherPlayer->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();
}
}
}
continue;
}
if( snapObjectNum >= SNAP_LAST_CLIENT_FRAME && snapObjectNum < SNAP_LAST_CLIENT_FRAME_END )
{
int playerNumber = snapObjectNum - SNAP_LAST_CLIENT_FRAME;
// Don't process Player Snapshots that are disconnected.
const int lobbyIndex = session->GetActingGameStateLobbyBase().GetLobbyUserIndexFromLobbyUserID( lobbyUserIDs[ playerNumber ] );
if( lobbyIndex < 0 || session->GetActingGameStateLobbyBase().IsLobbyUserConnected( lobbyIndex ) == false )
{
continue;
}
usercmdLastClientMilliseconds[playerNumber] = msg.ReadLong();
continue;
}
if( snapObjectNum < SNAP_ENTITIES || snapObjectNum >= SNAP_ENTITIES_END )
{
continue;
}
int entityNumber = snapObjectNum - SNAP_ENTITIES;
if( msg.GetSize() == 0 )
{
delete entities[entityNumber];
continue;
}
bool debug = false;
int spawnId = msg.ReadBits( 32 - GENTITYNUM_BITS );
int typeNum = msg.ReadBits( idClass::GetTypeNumBits() );
int entityDefNumber = ClientRemapDecl( DECL_ENTITYDEF, msg.ReadBits( entityDefBits ) );
const int predictedKey = msg.ReadBits( 32 );
idTypeInfo* typeInfo = idClass::GetType( typeNum );
if( !typeInfo )
{
idLib::Error( "Unknown type number %d for entity %d with class number %d", typeNum, entityNumber, entityDefNumber );
}
// If there is no entity on this client, but the server's entity matches a predictionKey, move the client's
// predicted entity to the normal, replicated area in the entity list.
if( entities[entityNumber] == NULL )
{
if( predictedKey != idEntity::INVALID_PREDICTION_KEY )
{
idLib::PrintfIf( debug, "Looking for predicted key %d.\n", predictedKey );
idEntity* predictedEntity = FindPredictedEntity( predictedKey, typeInfo );
if( predictedEntity != NULL )
{
// This presentable better be in the proper place in the list or bad things will happen if we move this presentable around
assert( predictedEntity->GetEntityNumber() >= ENTITYNUM_FIRST_NON_REPLICATED );
continue;
#if 0
idProjectile* predictedProjectile = idProjectile::CastTo( predictedEntity );
if( predictedProjectile != NULL )
{
for( int i = 0; i < MAX_PLAYERS; i++ )
{
if( entities[i] == NULL )
{
continue;
}
idPlayer* player = idPlayer::CastTo( entities[i] );
if( player != NULL )
{
if( player->GetUniqueProjectile() == predictedProjectile )
{
// Set new spawn id
player->TrackUniqueProjectile( predictedProjectile );
}
}
}
}
idLib::PrintfIf( debug, "Found predicted EntNum old:%i new:%i spawnID:%i\n", predictedEntity->GetEntityNumber(), entityNumber, spawnId >> GENTITYNUM_BITS );
// move the entity
RemoveEntityFromHash( predictedEntity->name.c_str(), predictedEntity );
UnregisterEntity( predictedEntity );
assert( entities[predictedEntity->GetEntityNumber()] == NULL );
predictedEntity->spawnArgs.SetInt( "spawn_entnum", entityNumber );
RegisterEntity( predictedEntity, spawnId, predictedEntity->spawnArgs );
predictedEntity->SetName( "" );
// now mark us as no longer predicted
predictedEntity->BecomeReplicated();
#endif
}
//TODO make this work with non-client preditced entities
/* else {
idLib::Warning( "Could not find predicted entity - key: %d. EntityIndex: %d", predictedKey, entityNum );
} */
}
}
idEntity* ent = entities[entityNumber];
// if there is no entity or an entity of the wrong type
if( !ent || ent->GetType()->typeNum != typeNum || ent->entityDefNumber != entityDefNumber || spawnId != spawnIds[ entityNumber ] )
{
delete ent;
spawnCount = spawnId;
if( entityNumber < MAX_CLIENTS )
{
commonLocal.GetUCmdMgr().ResetPlayer( entityNumber );
SpawnPlayer( entityNumber );
ent = entities[ entityNumber ];
ent->FreeModelDef();
}
else
{
idDict args;
args.SetInt( "spawn_entnum", entityNumber );
args.Set( "name", va( "entity%d", entityNumber ) );
if( entityDefNumber >= 0 )
{
if( entityDefNumber >= declManager->GetNumDecls( DECL_ENTITYDEF ) )
{
Error( "server has %d entityDefs instead of %d", entityDefNumber, declManager->GetNumDecls( DECL_ENTITYDEF ) );
}
const char* classname = declManager->DeclByIndex( DECL_ENTITYDEF, entityDefNumber, false )->GetName();
args.Set( "classname", classname );
if( !SpawnEntityDef( args, &ent ) || !entities[entityNumber] || entities[entityNumber]->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[entityNumber] || entities[entityNumber]->GetType()->typeNum != typeNum )
{
Error( "Failed to spawn entity of type '%s'", typeInfo->classname );
}
}
if( ent != NULL )
{
// Fixme: for now, force all think flags on. We'll need to figure out how we want dormancy to work on clients
// (but for now since clientThink is so light weight, this is ok)
ent->BecomeActive( TH_ANIMATE );
ent->BecomeActive( TH_THINK );
ent->BecomeActive( TH_PHYSICS );
}
if( entityNumber < MAX_CLIENTS && entityNumber >= numClients )
{
numClients = entityNumber + 1;
}
}
}
if( ss.ObjectIsStaleByIndex( o ) )
{
if( ent->entityNumber >= MAX_CLIENTS && ent->entityNumber < mapSpawnCount && !ent->spawnArgs.GetBool( "net_dynamic", "0" ) ) //_D3XP
{
// server says it's not 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( "map entity 0x%x (%s) is stale", ent->entityNumber, ent->name.c_str() );
}
else
{
ent->snapshotStale = true;
ent->FreeModelDef();
// possible fix for left over lights on CTF flag
ent->FreeLightDef();
ent->UpdateVisuals();
ent->GetPhysics()->UnlinkClip();
}
}
else
{
// add the entity to the snapshot list
ent->snapshotNode.AddToEnd( snapshotEntities );
int snapshotChanged = ss.ObjectChangedCountByIndex( o );
msg.SetHasChanged( ent->snapshotChanged != snapshotChanged );
ent->snapshotChanged = snapshotChanged;
ent->FlagNewSnapshot();
// read the class specific data from the snapshot
if( msg.GetRemainingReadBits() > 0 )
{
ent->ReadFromSnapshot_Ex( msg );
ent->snapshotBits = msg.GetSize();
}
// Set after ReadFromSnapshot so we can detect coming unstale
ent->snapshotStale = false;
}
}
// process entity events
ClientProcessEntityNetworkEventQueue();
}
/*
================
idGameLocal::ClientProcessEntityNetworkEventQueue
================
*/
void idGameLocal::ClientProcessEntityNetworkEventQueue()
{
while( eventQueue.Start() )
{
entityNetEvent_t* event = eventQueue.Start();
// only process forward, in order
if( event->time > this->serverTime )
{
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
{
idEntity* ent = entPtr.GetEntity();
assert( ent );
idBitMsg eventMsg;
eventMsg.InitRead( event->paramsBuf, sizeof( event->paramsBuf ) );
eventMsg.SetSize( event->paramsSize );
eventMsg.BeginReading();
if( !ent->ClientReceiveEvent( event->event, event->time, eventMsg ) )
{
NetworkEventWarning( event, "unknown event" );
}
}
verify( eventQueue.Dequeue() == event );
eventQueue.Free( event );
}
}
/*
================
idGameLocal::ClientProcessReliableMessage
================
*/
void idGameLocal::ClientProcessReliableMessage( int type, const idBitMsg& msg )
{
switch( type )
{
case GAME_RELIABLE_MESSAGE_SYNCEDCVARS:
{
idDict syncedCvars;
msg.ReadDeltaDict( syncedCvars, NULL );
idLib::Printf( "Got networkSync cvars:\n" );
syncedCvars.Print();
cvarSystem->ResetFlaggedVariables( CVAR_NETWORKSYNC );
cvarSystem->SetCVarsFromDict( syncedCvars );
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( msg_evt, parm1, parm2 );
break;
}
case GAME_RELIABLE_MESSAGE_EVENT:
{
// allocate new event
entityNetEvent_t* 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_RESTART:
{
MapRestart();
break;
}
case GAME_RELIABLE_MESSAGE_TOURNEYLINE:
{
int line = msg.ReadByte( );
idPlayer* p = static_cast< idPlayer* >( entities[ GetLocalClientNum() ] );
if( !p )
{
break;
}
p->tourneyLine = line;
break;
}
case GAME_RELIABLE_MESSAGE_STARTSTATE:
{
mpGame.ClientReadStartState( msg );
break;
}
case GAME_RELIABLE_MESSAGE_WARMUPTIME:
{
mpGame.ClientReadWarmupTime( msg );
break;
}
case GAME_RELIABLE_MESSAGE_LOBBY_COUNTDOWN:
{
int timeRemaining = msg.ReadLong();
Shell_UpdateClientCountdown( timeRemaining );
break;
}
case GAME_RELIABLE_MESSAGE_RESPAWN_AVAILABLE:
{
idPlayer* p = static_cast< idPlayer* >( entities[ GetLocalClientNum() ] );
if( p )
{
p->ShowRespawnHudMessage();
}
break;
}
case GAME_RELIABLE_MESSAGE_MATCH_STARTED_TIME:
{
mpGame.ClientReadMatchStartedTime( msg );
break;
}
case GAME_RELIABLE_MESSAGE_ACHIEVEMENT_UNLOCK:
{
mpGame.ClientReadAchievementUnlock( msg );
break;
}
default:
{
Error( "Unknown reliable message (%d) from host", type );
break;
}
}
}
/*
================
idGameLocal::ClientRunFrame
================
*/
void idGameLocal::ClientRunFrame( idUserCmdMgr& cmdMgr, bool lastPredictFrame, gameReturn_t& ret )
{
idEntity* ent;
// update the game time
previousTime = FRAME_TO_MSEC( framenum );
framenum++;
time = FRAME_TO_MSEC( framenum );
idPlayer* player = static_cast<idPlayer*>( entities[GetLocalClientNum()] );
if( !player )
{
// service any pending events
idEvent::ServiceEvents();
return;
}
// check for local client lag
idLobbyBase& lobby = session->GetActingGameStateLobbyBase();
if( lobby.GetPeerTimeSinceLastPacket( lobby.PeerIndexForHost() ) >= net_clientMaxPrediction.GetInteger() )
{
player->isLagged = true;
}
else
{
player->isLagged = false;
}
// update the real client time and the new frame flag
if( time > realClientTime )
{
realClientTime = time;
isNewFrame = true;
}
else
{
isNewFrame = false;
}
slow.Set( time, previousTime, realClientTime );
fast.Set( time, previousTime, realClientTime );
// run prediction on all active entities
for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() )
{
ent->thinkFlags |= TH_PHYSICS;
if( ent->entityNumber != GetLocalClientNum() )
{
ent->ClientThink( netInterpolationInfo.serverGameMs, netInterpolationInfo.pct, true );
}
else
{
RunAllUserCmdsForPlayer( cmdMgr, ent->entityNumber );
}
}
// service any pending events
idEvent::ServiceEvents();
// show any debug info for this frame
if( isNewFrame )
{
RunDebugInfo();
D_DrawDebugLines();
}
BuildReturnValue( 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::FindPredictedEntity
========================
*/
idEntity* idGameLocal::FindPredictedEntity( uint32 predictedKey, idTypeInfo* type )
{
for( idEntity* predictedEntity = activeEntities.Next(); predictedEntity != NULL; predictedEntity = predictedEntity->activeNode.Next() )
{
if( !verify( predictedEntity != NULL ) )
{
continue;
}
if( !predictedEntity->IsReplicated() && predictedEntity->GetPredictedKey() == predictedKey )
{
if( predictedEntity->GetType() != type )
{
idLib::Warning( "Mismatched presentable type. Predicted: %s Actual: %s", predictedEntity->GetType()->classname, type->classname );
}
return predictedEntity;
}
}
return NULL;
}
/*
========================
idGameLocal::GeneratePredictionKey
========================
*/
uint32 idGameLocal::GeneratePredictionKey( idWeapon* weapon, idPlayer* playerAttacker, int overrideKey )
{
if( overrideKey != -1 )
{
uint32 predictedKey = overrideKey;
int peerIndex = -1;
if( common->IsServer() )
{
peerIndex = session->GetActingGameStateLobbyBase().PeerIndexFromLobbyUser( lobbyUserIDs[ playerAttacker->entityNumber ] );
}
else
{
peerIndex = session->GetActingGameStateLobbyBase().PeerIndexOnHost();
}
predictedKey |= ( peerIndex << 28 );
return predictedKey;
}
uint32 predictedKey = idEntity::INVALID_PREDICTION_KEY;
int peerIndex = -1;
// Get key - fireCount or throwCount
//if ( weapon != NULL ) {
if( common->IsClient() )
{
predictedKey = playerAttacker->GetClientFireCount();
}
else
{
predictedKey = playerAttacker->usercmd.fireCount;
}
//} else {
// predictedKey = ( playerAttacker->GetThrowCount() );
//}
// Get peer index
if( common->IsServer() )
{
peerIndex = session->GetActingGameStateLobbyBase().PeerIndexFromLobbyUser( lobbyUserIDs[ playerAttacker->entityNumber ] );
}
else
{
peerIndex = session->GetActingGameStateLobbyBase().PeerIndexOnHost();
}
if( cg_predictedSpawn_debug.GetBool() )
{
idLib::Printf( "GeneratePredictionKey. predictedKey: %d peedIndex: %d\n", predictedKey, peerIndex );
}
predictedKey |= ( peerIndex << 28 );
return predictedKey;
}
/*
===============
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()
{
start = NULL;
end = NULL;
}
/*
===============
idEventQueue::Dequeue
===============
*/
entityNetEvent_t* idEventQueue::Dequeue()
{
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()
{
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;
}