/* ============= SV_EmitPacketEntities Writes a delta update of an entityState_t list to the message. ============= */ static void SV_EmitPacketEntities( clientSnapshot_t *from, clientSnapshot_t *to, msg_t *msg ) { entityState_t *oldent, *newent; int oldindex, newindex; int oldnum, newnum; int from_num_entities; // generate the delta update if ( !from ) { from_num_entities = 0; } else { from_num_entities = from->num_entities; } newent = NULL; oldent = NULL; newindex = 0; oldindex = 0; while ( newindex < to->num_entities || oldindex < from_num_entities ) { if ( newindex >= to->num_entities ) { newnum = 9999; } else { newent = &svs.snapshotEntities[(to->first_entity+newindex) % svs.numSnapshotEntities]; newnum = newent->number; } if ( oldindex >= from_num_entities ) { oldnum = 9999; } else { oldent = &svs.snapshotEntities[(from->first_entity+oldindex) % svs.numSnapshotEntities]; oldnum = oldent->number; } if ( newnum == oldnum ) { // delta update from old position // because the force parm is qfalse, this will not result // in any bytes being emited if the entity has not changed at all MSG_WriteDeltaEntity (msg, oldent, newent, qfalse ); oldindex++; newindex++; continue; } if ( newnum < oldnum ) { // this is a new entity, send it from the baseline MSG_WriteDeltaEntity (msg, &sv.svEntities[newnum].baseline, newent, qtrue ); newindex++; continue; } if ( newnum > oldnum ) { // the old entity isn't present in the new message MSG_WriteDeltaEntity (msg, oldent, NULL, qtrue ); oldindex++; continue; } } MSG_WriteBits( msg, (MAX_GENTITIES-1), GENTITYNUM_BITS ); // end of packetentities }
/* ============= SV_EmitPacketEntities Writes a delta update of an entity_state_t list to the message-> ============= */ void SV_EmitPacketEntities( client_frame_t *from, client_frame_t *to, sizebuf_t *msg ) { entity_state_t *oldent, *newent; int oldindex, newindex; int oldnum, newnum; int from_num_entities; MSG_WriteByte( msg, svc_packetentities ); if( !from ) from_num_entities = 0; else from_num_entities = from->num_entities; newent = NULL; oldent = NULL; newindex = 0; oldindex = 0; while( newindex < to->num_entities || oldindex < from_num_entities ) { if( newindex >= to->num_entities ) newnum = MAX_ENTNUMBER; else { newent = &svs.client_entities[(to->first_entity+newindex)%svs.num_client_entities]; newnum = newent->number; } if( oldindex >= from_num_entities ) oldnum = MAX_ENTNUMBER; else { oldent = &svs.client_entities[(from->first_entity+oldindex)%svs.num_client_entities]; oldnum = oldent->number; } if( newnum == oldnum ) { // delta update from old position // because the force parm is false, this will not result // in any bytes being emited if the entity has not changed at all MSG_WriteDeltaEntity( oldent, newent, msg, false, ( newent->number <= sv_maxclients->integer )); oldindex++; newindex++; continue; } if( newnum < oldnum ) { // this is a new entity, send it from the baseline MSG_WriteDeltaEntity( &svs.baselines[newnum], newent, msg, true, true ); newindex++; continue; } if( newnum > oldnum ) { // remove from message MSG_WriteDeltaEntity( oldent, NULL, msg, false, false ); oldindex++; continue; } } MSG_WriteBits( msg, 0, "svc_packetentities", NET_WORD ); // end of packetentities }
void SV_Baselines_f(void) { int start; entity_state_t nullstate; entity_state_t *base; Com_DPrintf("Baselines() from %s\n", sv_client->name); if (sv_client->state != cs_connected) { Com_Printf("baselines not valid -- already spawned\n"); return; } /* handle the case of a level changing while a client was connecting */ if ((int)strtol(Cmd_Argv(1), (char **)NULL, 10) != svs.spawncount) { Com_Printf("SV_Baselines_f from different level\n"); SV_New_f(); return; } start = (int)strtol(Cmd_Argv(2), (char **)NULL, 10); memset(&nullstate, 0, sizeof(nullstate)); /* write a packet full of data */ while (sv_client->netchan.message.cursize < MAX_MSGLEN / 2 && start < MAX_EDICTS) { base = &sv.baselines[start]; if (base->modelindex || base->sound || base->effects) { MSG_WriteByte(&sv_client->netchan.message, svc_spawnbaseline); MSG_WriteDeltaEntity(&nullstate, base, &sv_client->netchan.message, true, true); } start++; } /* send next command */ if (start == MAX_EDICTS) { MSG_WriteByte(&sv_client->netchan.message, svc_stufftext); MSG_WriteString(&sv_client->netchan.message, va("precache %i\n", svs.spawncount)); } else { MSG_WriteByte(&sv_client->netchan.message, svc_stufftext); MSG_WriteString(&sv_client->netchan.message, va("cmd baselines %i %i\n", svs.spawncount, start)); } }
static void demoFramePack( msg_t *msg, const demoFrame_t *newFrame, const demoFrame_t *oldFrame ) { int i; /* Full or delta frame marker */ MSG_WriteBits( msg, oldFrame ? 0 : 1, 1 ); MSG_WriteLong( msg, newFrame->serverTime ); /* Add the config strings */ for (i = 0;i<MAX_CONFIGSTRINGS;i++) { const char *oldString = !oldFrame ? "" : &oldFrame->string.data[oldFrame->string.offsets[i]]; const char *newString = newFrame->string.data + newFrame->string.offsets[i]; if (strcmp( oldString, newString)) { MSG_WriteShort( msg, i ); MSG_WriteBigString( msg, newString ); } } MSG_WriteShort( msg, MAX_CONFIGSTRINGS ); /* Add the playerstates */ for (i=0; i<MAX_CLIENTS; i++) { const playerState_t *oldPlayer, *newPlayer; if (!newFrame->clientData[i]) continue; oldPlayer = (!oldFrame || !oldFrame->clientData[i]) ? &demoNullPlayerState : &oldFrame->clients[i]; newPlayer = &newFrame->clients[i]; MSG_WriteByte( msg, i ); MSG_WriteDeltaPlayerstate( msg, oldPlayer, newPlayer ); } MSG_WriteByte( msg, MAX_CLIENTS ); /* Add the entities */ for (i=0; i<MAX_GENTITIES-1; i++) { const entityState_t *oldEntity, *newEntity; newEntity = &newFrame->entities[i]; if (oldFrame) { oldEntity = &oldFrame->entities[i]; if (oldEntity->number == (MAX_GENTITIES -1)) oldEntity = 0; } else { oldEntity = 0; } if (newEntity->number != i || newEntity->number >= (MAX_GENTITIES -1)) { newEntity = 0; } else { if (!oldEntity) { oldEntity = &demoNullEntityState; } } MSG_WriteDeltaEntity( msg, oldEntity, newEntity, qfalse ); } MSG_WriteBits( msg, (MAX_GENTITIES-1), GENTITYNUM_BITS ); /* Add the area mask */ MSG_WriteByte( msg, newFrame->areaUsed ); MSG_WriteData( msg, newFrame->areamask, newFrame->areaUsed ); /* Add the command string data */ MSG_WriteLong( msg, newFrame->commandUsed ); MSG_WriteData( msg, newFrame->commandData, newFrame->commandUsed ); }
/* ================ SV_SendClientGameState Sends the first message from the server to a connected client. This will be sent on the initial connection and upon each new map load. It will be resent if the client acknowledges a later message but has the wrong gamestate. ================ */ void SV_SendClientGameState( client_t *client ) { int start; entityState_t *base, nullstate; msg_t msg; byte msgBuffer[MAX_MSGLEN]; Com_DPrintf ("SV_SendGameState() for %s\n", client->name); client->state = CS_PRIMED; // when we receive the first packet from the client, we will // notice that it is from a different serverid and that the // gamestate message was not just sent, forcing a retransmit client->gamestateMessageNum = client->netchan.outgoingSequence; // clear the reliable message list for this client client->reliableSequence = 0; client->reliableAcknowledge = 0; MSG_Init( &msg, msgBuffer, sizeof( msgBuffer ) ); // send the gamestate MSG_WriteByte( &msg, svc_gamestate ); MSG_WriteLong( &msg, client->reliableSequence ); // write the configstrings for ( start = 0 ; start < MAX_CONFIGSTRINGS ; start++ ) { if (sv.configstrings[start][0]) { MSG_WriteByte( &msg, svc_configstring ); MSG_WriteShort( &msg, start ); MSG_WriteString( &msg, sv.configstrings[start] ); } } // write the baselines memset( &nullstate, 0, sizeof( nullstate ) ); for ( start = 0 ; start < MAX_GENTITIES; start++ ) { base = &sv.svEntities[start].baseline; if ( !base->number ) { continue; } MSG_WriteByte( &msg, svc_baseline ); MSG_WriteDeltaEntity( &msg, &nullstate, base, qtrue ); } MSG_WriteByte( &msg, 0 ); // check for overflow if ( msg.overflowed ) { Com_Printf ("WARNING: GameState overflowed for %s\n", client->name); } // deliver this to the client SV_SendMessageToClient( &msg, client ); }
/* * SV_Baselines_f */ static void SV_Baselines_f( client_t *client ) { int start; entity_state_t nullstate; entity_state_t *base; Com_DPrintf( "Baselines() from %s\n", client->name ); if( client->state != CS_CONNECTED ) { Com_Printf( "baselines not valid -- already spawned\n" ); return; } // handle the case of a level changing while a client was connecting if( atoi( Cmd_Argv( 1 ) ) != svs.spawncount ) { Com_Printf( "SV_Baselines_f from different level\n" ); SV_New_f( client ); return; } start = atoi( Cmd_Argv( 2 ) ); if( start < 0 ) start = 0; memset( &nullstate, 0, sizeof( nullstate ) ); // write a packet full of data SV_InitClientMessage( client, &tmpMessage, NULL, 0 ); while( tmpMessage.cursize < FRAGMENT_SIZE * 3 && start < MAX_EDICTS ) { base = &sv.baselines[start]; if( base->modelindex || base->sound || base->effects ) { MSG_WriteByte( &tmpMessage, svc_spawnbaseline ); MSG_WriteDeltaEntity( &nullstate, base, &tmpMessage, qtrue, qtrue ); } start++; } // send next command if( start == MAX_EDICTS ) SV_SendServerCommand( client, "precache %i", svs.spawncount ); else SV_SendServerCommand( client, "cmd baselines %i %i", svs.spawncount, start ); SV_AddReliableCommandsToMessage( client, &tmpMessage ); SV_SendMessageToClient( client, &tmpMessage ); }
void SV_CreateClientGameStateMessage( client_t *client, msg_t *msg ) { int start; entityState_t *base, nullstate; // NOTE, MRE: all server->client messages now acknowledge // let the client know which reliable clientCommands we have received MSG_WriteLong( msg, client->lastClientCommand ); // send any server commands waiting to be sent first. // we have to do this cause we send the client->reliableSequence // with a gamestate and it sets the clc.serverCommandSequence at // the client side SV_UpdateServerCommandsToClient( client, msg ); // send the gamestate MSG_WriteByte( msg, svc_gamestate ); MSG_WriteLong( msg, client->reliableSequence ); // write the configstrings for ( start = 0 ; start < MAX_CONFIGSTRINGS ; start++ ) { if (sv.configstrings[start][0]) { MSG_WriteByte( msg, svc_configstring ); MSG_WriteShort( msg, start ); MSG_WriteBigString( msg, sv.configstrings[start] ); } } // write the baselines Com_Memset( &nullstate, 0, sizeof( nullstate ) ); for ( start = 0 ; start < MAX_GENTITIES; start++ ) { base = &sv.svEntities[start].baseline; if ( !base->number ) { continue; } MSG_WriteByte( msg, svc_baseline ); MSG_WriteDeltaEntity( msg, &nullstate, base, qtrue ); } MSG_WriteByte( msg, svc_EOF ); MSG_WriteLong( msg, client - svs.clients); // write the checksum feed MSG_WriteLong( msg, sv.checksumFeed); // For old RMG system. MSG_WriteShort ( msg, 0 ); }
/* ================== SV_WriteBaselineToClient ================== */ void SV_WriteBaselineToClient( client_t *client, msg_t *msg ) { sharedEntityState_t *base; int start; if ( !client->needBaseline ) { return; } client->needBaseline = qfalse; // write the baselines for ( start = 0 ; start < MAX_GENTITIES; start++ ) { base = (sharedEntityState_t *)DA_ElementPointer( sv.svEntitiesBaseline, start ); if ( !base->number ) { continue; } MSG_WriteByte( msg, svc_baseline ); MSG_WriteDeltaEntity( msg, NULL, base, qtrue ); } }
void SV_CreateClientGameStateMessage( client_t *client, msg_t *msg, qboolean updateServerCommands ) { int start; entityState_t *base, nullstate; // NOTE, MRE: all server->client messages now acknowledge // let the client know which reliable clientCommands we have received MSG_WriteLong( msg, client->lastClientCommand ); if ( updateServerCommands ) { // send any server commands waiting to be sent first. // we have to do this cause we send the client->reliableSequence // with a gamestate and it sets the clc.serverCommandSequence at // the client side SV_UpdateServerCommandsToClient( client, msg ); } // send the gamestate MSG_WriteByte( msg, svc_gamestate ); MSG_WriteLong( msg, client->reliableSequence ); // write the configstrings for ( start = 0 ; start < MAX_CONFIGSTRINGS ; start++ ) { if (sv.configstrings[start][0]) { MSG_WriteByte( msg, svc_configstring ); MSG_WriteShort( msg, start ); MSG_WriteBigString( msg, sv.configstrings[start] ); } } // write the baselines Com_Memset( &nullstate, 0, sizeof( nullstate ) ); for ( start = 0 ; start < MAX_GENTITIES; start++ ) { base = &sv.svEntities[start].baseline; if ( !base->number ) { continue; } MSG_WriteByte( msg, svc_baseline ); MSG_WriteDeltaEntity( msg, &nullstate, base, qtrue ); } MSG_WriteByte( msg, svc_EOF ); MSG_WriteLong( msg, client - svs.clients); // write the checksum feed MSG_WriteLong( msg, sv.checksumFeed); //rwwRMG - send info for the terrain if ( TheRandomMissionManager ) { z_stream zdata; // Send the height map memset(&zdata, 0, sizeof(z_stream)); deflateInit ( &zdata, Z_BEST_COMPRESSION ); unsigned char heightmap[15000]; zdata.next_out = (unsigned char*)heightmap; zdata.avail_out = 15000; zdata.next_in = TheRandomMissionManager->GetLandScape()->GetHeightMap(); zdata.avail_in = TheRandomMissionManager->GetLandScape()->GetRealArea(); deflate(&zdata, Z_SYNC_FLUSH); MSG_WriteShort ( msg, (unsigned short)zdata.total_out ); MSG_WriteBits ( msg, 1, 1 ); MSG_WriteData ( msg, heightmap, zdata.total_out); deflateEnd(&zdata); // Send the flatten map memset(&zdata, 0, sizeof(z_stream)); deflateInit ( &zdata, Z_BEST_COMPRESSION ); zdata.next_out = (unsigned char*)heightmap; zdata.avail_out = 15000; zdata.next_in = TheRandomMissionManager->GetLandScape()->GetFlattenMap(); zdata.avail_in = TheRandomMissionManager->GetLandScape()->GetRealArea(); deflate(&zdata, Z_SYNC_FLUSH); MSG_WriteShort ( msg, (unsigned short)zdata.total_out ); MSG_WriteBits ( msg, 1, 1 ); MSG_WriteData ( msg, heightmap, zdata.total_out); deflateEnd(&zdata); // Seed is needed for misc ents and noise MSG_WriteLong ( msg, TheRandomMissionManager->GetLandScape()->get_rand_seed ( ) ); SV_WriteRMGAutomapSymbols ( msg ); } else { MSG_WriteShort ( msg, 0 ); } }
/* Start a server-side demo. This does it all, create the file and adjust the demo-related stuff in client_t. This is mostly ripped from sv_client.c/SV_SendClientGameState and cl_main.c/CL_Record_f. */ static void SVD_StartDemoFile(client_t *client, const char *path) { int i, len; entityState_t *base, nullstate; msg_t msg; byte buffer[MAX_MSGLEN]; fileHandle_t file; Com_DPrintf("SVD_StartDemoFile\n"); assert(!client->demo_recording); // create the demo file and write the necessary header file = FS_FOpenFileWrite(path); assert(file != 0); MSG_Init(&msg, buffer, sizeof(buffer)); MSG_Bitstream(&msg); // XXX server code doesn't do this, client code does MSG_WriteLong(&msg, client->lastClientCommand); // TODO: or is it client->reliableSequence? MSG_WriteByte(&msg, svc_gamestate); MSG_WriteLong(&msg, client->reliableSequence); for (i = 0; i < MAX_CONFIGSTRINGS; i++) { if (sv.configstrings[i][0]) { MSG_WriteByte(&msg, svc_configstring); MSG_WriteShort(&msg, i); MSG_WriteBigString(&msg, sv.configstrings[i]); } } Com_Memset(&nullstate, 0, sizeof(nullstate)); for (i = 0 ; i < MAX_GENTITIES; i++) { base = &sv.svEntities[i].baseline; if (!base->number) { continue; } MSG_WriteByte(&msg, svc_baseline); MSG_WriteDeltaEntity(&msg, &nullstate, base, qtrue); } MSG_WriteByte(&msg, svc_EOF); MSG_WriteLong(&msg, client - svs.clients); MSG_WriteLong(&msg, sv.checksumFeed); MSG_WriteByte(&msg, svc_EOF); // XXX server code doesn't do this, SV_Netchan_Transmit adds it! len = LittleLong(client->netchan.outgoingSequence-1); FS_Write(&len, 4, file); len = LittleLong (msg.cursize); FS_Write(&len, 4, file); FS_Write(msg.data, msg.cursize, file); FS_Flush(file); // adjust client_t to reflect demo started client->demo_recording = qtrue; client->demo_file = file; client->demo_waiting = qtrue; client->demo_backoff = 1; client->demo_deltas = 0; }
/* ================ SV_SendClientGameState Sends the first message from the server to a connected client. This will be sent on the initial connection and upon each new map load. It will be resent if the client acknowledges a later message but has the wrong gamestate. ================ */ void SV_SendClientGameState( client_t *client ) { int start; entityState_t *base, nullstate; msg_t msg; byte msgBuffer[ MAX_MSGLEN ]; Log::Debug( "SV_SendClientGameState() for %s", client->name ); Log::Debug( "Going from CS_CONNECTED to CS_PRIMED for %s", client->name ); client->state = clientState_t::CS_PRIMED; // when we receive the first packet from the client, we will // notice that it is from a different serverid and that the // gamestate message was not just sent, forcing a retransmit client->gamestateMessageNum = client->netchan.outgoingSequence; MSG_Init( &msg, msgBuffer, sizeof( msgBuffer ) ); // NOTE, MRE: all server->client messages now acknowledge // let the client know which reliable clientCommands we have received MSG_WriteLong( &msg, client->lastClientCommand ); // send any server commands waiting to be sent first. // we have to do this cause we send the client->reliableSequence // with a gamestate and it sets the clc.serverCommandSequence at // the client side SV_UpdateServerCommandsToClient( client, &msg ); // send the gamestate MSG_WriteByte( &msg, svc_gamestate ); MSG_WriteLong( &msg, client->reliableSequence ); // write the configstrings for ( start = 0; start < MAX_CONFIGSTRINGS; start++ ) { if ( sv.configstrings[ start ][ 0 ] ) { MSG_WriteByte( &msg, svc_configstring ); MSG_WriteShort( &msg, start ); MSG_WriteBigString( &msg, sv.configstrings[ start ] ); } } // write the baselines memset( &nullstate, 0, sizeof( nullstate ) ); for ( start = 0; start < MAX_GENTITIES; start++ ) { base = &sv.svEntities[ start ].baseline; if ( !base->number ) { continue; } MSG_WriteByte( &msg, svc_baseline ); MSG_WriteDeltaEntity( &msg, &nullstate, base, true ); } MSG_WriteByte( &msg, svc_EOF ); MSG_WriteLong( &msg, client - svs.clients ); // write the checksum feed MSG_WriteLong( &msg, sv.checksumFeed ); // NERVE - SMF - debug info Log::Debug( "Sending %i bytes in gamestate to client: %li", msg.cursize, ( long )( client - svs.clients ) ); // deliver this to the client SV_SendMessageToClient( &msg, client ); }
/* ================ SV_SendClientGameState Sends the first message from the server to a connected client. This will be sent on the initial connection and upon each new map load. It will be resent if the client acknowledges a later message but has the wrong gamestate. ================ */ void SV_SendClientGameState( client_t *client ) { int start; entityState_t *base, nullstate; msg_t msg; byte msgBuffer[MAX_MSGLEN]; // MW - my attempt to fix illegible server message errors caused by // packet fragmentation of initial snapshot. while(client->state&&client->netchan.unsentFragments) { // send additional message fragments if the last message // was too large to send at once Com_Printf ("[ISM]SV_SendClientGameState() [2] for %s, writing out old fragments\n", client->name); SV_Netchan_TransmitNextFragment(&client->netchan); } Com_DPrintf ("SV_SendClientGameState() for %s\n", client->name); Com_DPrintf( "Going from CS_CONNECTED to CS_PRIMED for %s\n", client->name ); client->state = CS_PRIMED; client->pureAuthentic = 0; // when we receive the first packet from the client, we will // notice that it is from a different serverid and that the // gamestate message was not just sent, forcing a retransmit client->gamestateMessageNum = client->netchan.outgoingSequence; MSG_Init( &msg, msgBuffer, sizeof( msgBuffer ) ); // NOTE, MRE: all server->client messages now acknowledge // let the client know which reliable clientCommands we have received MSG_WriteLong( &msg, client->lastClientCommand ); // send any server commands waiting to be sent first. // we have to do this cause we send the client->reliableSequence // with a gamestate and it sets the clc.serverCommandSequence at // the client side SV_UpdateServerCommandsToClient( client, &msg ); // send the gamestate MSG_WriteByte( &msg, svc_gamestate ); MSG_WriteLong( &msg, client->reliableSequence ); // write the configstrings for ( start = 0 ; start < MAX_CONFIGSTRINGS ; start++ ) { if (sv.configstrings[start][0]) { MSG_WriteByte( &msg, svc_configstring ); MSG_WriteShort( &msg, start ); MSG_WriteBigString( &msg, sv.configstrings[start] ); } } // write the baselines Com_Memset( &nullstate, 0, sizeof( nullstate ) ); for ( start = 0 ; start < MAX_GENTITIES; start++ ) { base = &sv.svEntities[start].baseline; if ( !base->number ) { continue; } MSG_WriteByte( &msg, svc_baseline ); MSG_WriteDeltaEntity( &msg, &nullstate, base, qtrue ); } MSG_WriteByte( &msg, svc_EOF ); MSG_WriteLong( &msg, client - svs.clients); // write the checksum feed MSG_WriteLong( &msg, sv.checksumFeed); //rwwRMG - send info for the terrain if ( TheRandomMissionManager ) { z_stream zdata; // Send the height map memset(&zdata, 0, sizeof(z_stream)); deflateInit ( &zdata, Z_MAX_COMPRESSION ); unsigned char heightmap[15000]; zdata.next_out = (unsigned char*)heightmap; zdata.avail_out = 15000; zdata.next_in = TheRandomMissionManager->GetLandScape()->GetHeightMap(); zdata.avail_in = TheRandomMissionManager->GetLandScape()->GetRealArea(); deflate(&zdata, Z_SYNC_FLUSH); MSG_WriteShort ( &msg, (unsigned short)zdata.total_out ); MSG_WriteBits ( &msg, 1, 1 ); MSG_WriteData ( &msg, heightmap, zdata.total_out); deflateEnd(&zdata); // Send the flatten map memset(&zdata, 0, sizeof(z_stream)); deflateInit ( &zdata, Z_MAX_COMPRESSION ); zdata.next_out = (unsigned char*)heightmap; zdata.avail_out = 15000; zdata.next_in = TheRandomMissionManager->GetLandScape()->GetFlattenMap(); zdata.avail_in = TheRandomMissionManager->GetLandScape()->GetRealArea(); deflate(&zdata, Z_SYNC_FLUSH); MSG_WriteShort ( &msg, (unsigned short)zdata.total_out ); MSG_WriteBits ( &msg, 1, 1 ); MSG_WriteData ( &msg, heightmap, zdata.total_out); deflateEnd(&zdata); // Seed is needed for misc ents and noise MSG_WriteLong ( &msg, TheRandomMissionManager->GetLandScape()->get_rand_seed ( ) ); SV_WriteRMGAutomapSymbols ( &msg ); } else { MSG_WriteShort ( &msg, 0 ); } // deliver this to the client SV_SendMessageToClient( &msg, client ); }
/* ================== SV_Baselines_f ================== */ void SV_Baselines_f (void) { int startPos, start; int maxLen; // Knightmare added entity_state_t nullstate; entity_state_t *base; Com_DPrintf ("Baselines() from %s\n", sv_client->name); if (sv_client->state != cs_connected) { Com_Printf ("baselines not valid -- already spawned\n"); return; } // handle the case of a level changing while a client was connecting if ( atoi(Cmd_Argv(1)) != svs.spawncount ) { Com_Printf ("SV_Baselines_f from different level\n"); SV_New_f (); return; } // Knightmare- use sv_baselines_maxlen for proper bounding in multiplayer maxLen = SV_SetMaxBaselinesSize(); // start = atoi(Cmd_Argv(2)); startPos = atoi(Cmd_Argv(2)); if (startPos < 0) // r1ch's fix for negative index { Com_Printf ("Illegal baselines request (negative index) from %s[%s], dropping client\n", sv_client->name, NET_AdrToString(sv_client->netchan.remote_address)); SV_DropClient (sv_client); return; } start = startPos; memset (&nullstate, 0, sizeof(nullstate)); // write a packet full of data // Knightmare- use maxLen for proper bounding // while ( sv_client->netchan.message.cursize < MAX_MSGLEN/2 && start < MAX_EDICTS) while ( sv_client->netchan.message.cursize < maxLen && start < MAX_EDICTS) { base = &sv.baselines[start]; if (base->modelindex || base->sound || base->effects) { MSG_WriteByte (&sv_client->netchan.message, svc_spawnbaseline); MSG_WriteDeltaEntity (&nullstate, base, &sv_client->netchan.message, true, true); } start++; } // send next command if (start == MAX_EDICTS) { MSG_WriteByte (&sv_client->netchan.message, svc_stufftext); MSG_WriteString (&sv_client->netchan.message, va("precache %i\n", svs.spawncount) ); } else { MSG_WriteByte (&sv_client->netchan.message, svc_stufftext); MSG_WriteString (&sv_client->netchan.message, va("cmd baselines %i %i\n",svs.spawncount, start) ); } }
/* ==================== CL_Record_f record <slot> [<demoname>] Begins recording a demo from the current position ==================== */ void CL_Record( client_t *cl, char *s ) { char name[MAX_OSPATH]; char name_zip[MAX_OSPATH]; byte bufData[MAX_MSGLEN]; msg_t buf; int i; int len; entityState_t *ent; entityState_t nullstate; int clientnum; char *guid; char prefix[MAX_OSPATH]; clientnum = cl - svs.clients; if ( cl->demorecording ) { Com_Printf ("Already recording client %i.\n", clientnum); return; } // if ( cl->state != CA_ACTIVE ) { // Com_Printf ("You must be in a level to record.\n"); // return; // } // // sync 0 doesn't prevent recording, so not forcing it off .. everyone does g_sync 1 ; record ; g_sync 0 .. // if ( NET_IsLocalAddress( cl->serverAddress ) && !Cvar_VariableValue( "g_synchronousClients" ) ) { // Com_Printf (S_COLOR_YELLOW "WARNING: You should set 'g_synchronousClients 1' for smoother demo recording\n"); // } if ( s ) { Com_sprintf (name, sizeof(name), "demos/%s.dm_%d", s, PROTOCOL_VERSION ); } else { int number,n,a,b,c,d; guid = Info_ValueForKey(cl->userinfo, "cl_guid"); if (!Q_stricmp(guid, "")) { guid = "LONGGONE"; } Q_strncpyz(prefix, guid, 9); // scan for a free demo name for ( number = 0 ; number <= 9999 ; number++ ) { if(number < 0 || number > 9999) number = 9999; n = number; a = n / 1000; n -= a*1000; b = n / 100; n -= b*100; c = n / 10; n -= c*10; d = n; Com_sprintf (name, sizeof(name), "demos/%s_%s_%i%i%i%i.dm_%d", prefix, sv_mapname->string, a, b, c, d, PROTOCOL_VERSION ); Com_sprintf (name_zip, sizeof(name_zip), "demos/%s_%s_%i%i%i%i.dm_%d.zip", prefix, sv_mapname->string, a, b, c, d, PROTOCOL_VERSION ); Q_strlwr(name); Q_strlwr(name_zip); if (!FS_FileExists(name) && !FS_FileExists(name_zip)) { break; // file doesn't exist } } } // open the demo file if (!sv_autorecord->integer) { Com_Printf ("recording client %i to %s.\n", clientnum, name); } else { Com_Printf ("Record: %i: %s\n", clientnum, name); } cl->demofile = FS_FOpenFileWrite( name ); if ( !cl->demofile ) { Com_Printf ("ERROR: couldn't open.\n"); return; } // don't start saving messages until a non-delta compressed message is received cl->demowaiting = qtrue; cl->savedemo = qfalse; // demo will not be saved if sv_autorecord 1 and cl's score is too low Q_strncpyz( cl->demoName, name, sizeof( cl->demoName ) ); // write out the gamestate message MSG_Init (&buf, bufData, sizeof(bufData)); MSG_Bitstream(&buf); // NOTE, MRE: all server->client messages now acknowledge MSG_WriteLong( &buf, cl->lastClientCommand );// 0007 - 000A MSG_WriteByte (&buf, svc_gamestate);// 000B MSG_WriteLong (&buf, cl->reliableSequence );// 000C - 000F // write the configstrings for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { if (sv.configstrings[i][0]) { MSG_WriteByte( &buf, svc_configstring ); MSG_WriteShort( &buf, i ); MSG_WriteBigString( &buf, sv.configstrings[i] ); } } // write the baselines Com_Memset( &nullstate, 0, sizeof( nullstate ) ); for ( i = 0 ; i < MAX_GENTITIES; i++ ) { ent = &sv.svEntities[i].baseline; if ( !ent->number ) { continue; } MSG_WriteByte( &buf, svc_baseline ); MSG_WriteDeltaEntity( &buf, &nullstate, ent, qtrue ); } MSG_WriteByte( &buf, svc_EOF ); // finished writing the gamestate stuff // write the client num MSG_WriteLong(&buf, clientnum); // write the checksum feed MSG_WriteLong(&buf, sv.checksumFeed); // finished writing the client packet MSG_WriteByte( &buf, svc_EOF ); // write it to the demo file len = LittleLong( cl->netchan.outgoingSequence-1 ); FS_Write (&len, 4, cl->demofile);// 0000 - 0003 len = LittleLong (buf.cursize); FS_Write (&len, 4, cl->demofile);// 0004 - 0007 FS_Write (buf.data, buf.cursize, cl->demofile);// 0007 - ... // the rest of the demo file will be copied from net messages cl->demorecording = qtrue; }
/* Start a server-side demo. This does it all, create the file and adjust the demo-related stuff in client_t. This is mostly ripped from sv_client.c/SV_SendClientGameState and cl_main.c/CL_Record_f. */ static void SVD_StartDemoFile(client_t *client, const char *path) { int i, len; entityState_t *base, nullstate; msg_t msg; byte buffer[MAX_MSGLEN]; fileHandle_t file; #ifdef USE_DEMO_FORMAT_42 char *s; int v, size; #endif Com_DPrintf("SVD_StartDemoFile\n"); assert(!client->demo_recording); // create the demo file and write the necessary header file = FS_FOpenFileWrite(path); assert(file != 0); /* File_write_header_demo // ADD this fx */ /* HOLBLIN entete demo */ #ifdef USE_DEMO_FORMAT_42 //@Barbatos: get the mod version from the server s = Cvar_VariableString("g_modversion"); size = strlen( s ); len = LittleLong( size ); FS_Write( &len, 4, file ); FS_Write( s , size , file ); v = LittleLong( PROTOCOL_VERSION ); FS_Write ( &v, 4 , file ); len = 0; len = LittleLong( len ); FS_Write ( &len, 4 , file ); FS_Write ( &len, 4 , file ); #endif /* END HOLBLIN entete demo */ MSG_Init(&msg, buffer, sizeof(buffer)); MSG_Bitstream(&msg); // XXX server code doesn't do this, client code does MSG_WriteLong(&msg, client->lastClientCommand); // TODO: or is it client->reliableSequence? MSG_WriteByte(&msg, svc_gamestate); MSG_WriteLong(&msg, client->reliableSequence); for (i = 0; i < MAX_CONFIGSTRINGS; i++) { if (sv.configstrings[i][0]) { MSG_WriteByte(&msg, svc_configstring); MSG_WriteShort(&msg, i); MSG_WriteBigString(&msg, sv.configstrings[i]); } } Com_Memset(&nullstate, 0, sizeof(nullstate)); for (i = 0 ; i < MAX_GENTITIES; i++) { base = &sv.svEntities[i].baseline; if (!base->number) { continue; } MSG_WriteByte(&msg, svc_baseline); MSG_WriteDeltaEntity(&msg, &nullstate, base, qtrue); } MSG_WriteByte(&msg, svc_EOF); MSG_WriteLong(&msg, client - svs.clients); MSG_WriteLong(&msg, sv.checksumFeed); MSG_WriteByte(&msg, svc_EOF); // XXX server code doesn't do this, SV_Netchan_Transmit adds it! len = LittleLong(client->netchan.outgoingSequence-1); FS_Write(&len, 4, file); len = LittleLong (msg.cursize); FS_Write(&len, 4, file); FS_Write(msg.data, msg.cursize, file); #ifdef USE_DEMO_FORMAT_42 // add size of packet in the end for backward play /* holblin */ FS_Write(&len, 4, file); #endif FS_Flush(file); // adjust client_t to reflect demo started client->demo_recording = qtrue; client->demo_file = file; client->demo_waiting = qtrue; client->demo_backoff = 1; client->demo_deltas = 0; }
/* * TV_Downstream_Baselines_f */ static void TV_Downstream_Baselines_f( client_t *client ) { int start; entity_state_t nullstate; msg_t message; uint8_t messageData[MAX_MSGLEN]; if( client->state != CS_CONNECTED ) return; // relay is not ready yet if( client->relay && client->relay->state < CA_ACTIVE ) { TV_Downstream_SendServerCommand( client, "reconnect" ); return; } // handle the case of a level changing while a client was connecting if( atoi( Cmd_Argv( 1 ) ) != ( client->relay ? client->relay->servercount : tvs.lobby.spawncount ) ) { TV_Downstream_New_f( client ); return; } if( !client->relay ) { TV_Downstream_SendServerCommand( client, "precache %i", tvs.lobby.spawncount ); return; } start = atoi( Cmd_Argv( 2 ) ); if( start < 0 ) start = 0; memset( &nullstate, 0, sizeof( nullstate ) ); // write a packet full of data TV_Downstream_InitClientMessage( client, &message, messageData, sizeof( messageData ) ); while( message.cursize < FRAGMENT_SIZE * 3 && start < MAX_EDICTS ) { if( client->relay->baselines[start].number ) { MSG_WriteByte( &message, svc_spawnbaseline ); MSG_WriteDeltaEntity( &nullstate, &client->relay->baselines[start], &message, true, true ); } start++; } // send next command if( start == MAX_EDICTS ) { TV_Downstream_SendServerCommand( client, "precache %i", client->relay->servercount ); } else { TV_Downstream_SendServerCommand( client, "cmd baselines %i %i", client->relay->servercount, start ); } TV_Downstream_AddReliableCommandsToMessage( client, &message ); TV_Downstream_SendMessageToClient( client, &message ); }
void demoCutWriteDemoHeader(fileHandle_t f, clientConnection_t *clcCut, clientActive_t *clCut) { byte bufData[MAX_MSGLEN]; msg_t buf; int i; int len; entityState_t *ent; entityState_t nullstate; char *s; // write out the gamestate message MSG_Init(&buf, bufData, sizeof(bufData)); MSG_Bitstream(&buf); // NOTE, MRE: all server->client messages now acknowledge MSG_WriteLong(&buf, clcCut->reliableSequence); MSG_WriteByte(&buf, svc_gamestate); MSG_WriteLong(&buf, clcCut->serverCommandSequence); // configstrings for (i = 0; i < MAX_CONFIGSTRINGS; i++) { if (!clCut->gameState.stringOffsets[i]) { continue; } s = clCut->gameState.stringData + clCut->gameState.stringOffsets[i]; MSG_WriteByte(&buf, svc_configstring); MSG_WriteShort(&buf, i); MSG_WriteBigString(&buf, s); } // baselines Com_Memset(&nullstate, 0, sizeof(nullstate)); for (i = 0; i < MAX_GENTITIES ; i++) { ent = &clCut->entityBaselines[i]; if ( !ent->number ) { continue; } MSG_WriteByte(&buf, svc_baseline); MSG_WriteDeltaEntity(&buf, &nullstate, ent, qtrue); } MSG_WriteByte(&buf, svc_EOF); // finished writing the gamestate stuff // write the client num MSG_WriteLong(&buf, clcCut->clientNum); // write the checksum feed MSG_WriteLong(&buf, clcCut->checksumFeed); // RMG stuff if ( clcCut->rmgHeightMapSize ) { // Height map MSG_WriteShort(&buf, (unsigned short)clcCut->rmgHeightMapSize); MSG_WriteBits(&buf, 0, 1 ); MSG_WriteData(&buf, clcCut->rmgHeightMap, clcCut->rmgHeightMapSize); // Flatten map MSG_WriteShort(&buf, (unsigned short)clcCut->rmgHeightMapSize); MSG_WriteBits(&buf, 0, 1 ); MSG_WriteData(&buf, clcCut->rmgFlattenMap, clcCut->rmgHeightMapSize); // Seed MSG_WriteLong (&buf, clcCut->rmgSeed); // Automap symbols MSG_WriteShort (&buf, (unsigned short)clcCut->rmgAutomapSymbolCount); for (i = 0; i < clcCut->rmgAutomapSymbolCount; i ++) { MSG_WriteByte(&buf, (unsigned char)clcCut->rmgAutomapSymbols[i].mType); MSG_WriteByte(&buf, (unsigned char)clcCut->rmgAutomapSymbols[i].mSide); MSG_WriteLong(&buf, (long)clcCut->rmgAutomapSymbols[i].mOrigin[0]); MSG_WriteLong(&buf, (long)clcCut->rmgAutomapSymbols[i].mOrigin[1]); } } else { MSG_WriteShort (&buf, 0); } // finished writing the client packet MSG_WriteByte(&buf, svc_EOF); // write it to the demo file len = LittleLong(clcCut->serverMessageSequence - 1); FS_Write(&len, 4, f); len = LittleLong(buf.cursize); FS_Write(&len, 4, f); FS_Write(buf.data, buf.cursize, f); }
void CL_Record(const char *name) { int i; msg_t buf; byte bufData[MAX_MSGLEN]; entityState_t *ent; entityState_t nullstate; char *s; int len; // open the demo file Com_FuncPrinf("Recording to %s.\n", name); clc.demofile = FS_FOpenFileWrite(name); if (!clc.demofile) { Com_FuncPrinf("ERROR: couldn't open.\n"); return; } clc.demorecording = qtrue; Cvar_Set("cl_demorecording", "1"); // fretn Q_strncpyz(clc.demoName, demoName, sizeof(clc.demoName)); Cvar_Set("cl_demofilename", clc.demoName); // bani Cvar_Set("cl_demooffset", "0"); // bani // don't start saving messages until a non-delta compressed message is received clc.demowaiting = qtrue; // write out the gamestate message MSG_Init(&buf, bufData, sizeof(bufData)); MSG_Bitstream(&buf); // NOTE: all server->client messages now acknowledge MSG_WriteLong(&buf, clc.reliableSequence); MSG_WriteByte(&buf, svc_gamestate); MSG_WriteLong(&buf, clc.serverCommandSequence); // configstrings for (i = 0; i < MAX_CONFIGSTRINGS; i++) { if (!cl.gameState.stringOffsets[i]) { continue; } s = cl.gameState.stringData + cl.gameState.stringOffsets[i]; MSG_WriteByte(&buf, svc_configstring); MSG_WriteShort(&buf, i); MSG_WriteBigString(&buf, s); } // baselines memset(&nullstate, 0, sizeof(nullstate)); for (i = 0; i < MAX_GENTITIES; i++) { ent = &cl.entityBaselines[i]; if (!ent->number) { continue; } MSG_WriteByte(&buf, svc_baseline); MSG_WriteDeltaEntity(&buf, &nullstate, ent, qtrue); } MSG_WriteByte(&buf, svc_EOF); // finished writing the gamestate stuff // write the client num MSG_WriteLong(&buf, clc.clientNum); // write the checksum feed MSG_WriteLong(&buf, clc.checksumFeed); // finished writing the client packet MSG_WriteByte(&buf, svc_EOF); // write it to the demo file len = LittleLong(clc.serverMessageSequence - 1); FS_Write(&len, 4, clc.demofile); len = LittleLong(buf.cursize); FS_Write(&len, 4, clc.demofile); FS_Write(buf.data, buf.cursize, clc.demofile); // the rest of the demo file will be copied from net messages }
/* ================ SV_SendClientGameState Sends the first message from the server to a connected client. This will be sent on the initial connection and upon each new map load. It will be resent if the client acknowledges a later message but has the wrong gamestate. ================ */ void SV_SendClientGameState( client_t *client ) { int start; entityState_t *base, nullstate; msg_t msg; byte msgBuffer[MAX_MSGLEN]; // MW - my attempt to fix illegible server message errors caused by // packet fragmentation of initial snapshot. while(client->state&&client->netchan.unsentFragments) { // send additional message fragments if the last message // was too large to send at once Com_Printf ("[ISM]SV_SendClientGameState() [2] for %s, writing out old fragments\n", client->name); SV_Netchan_TransmitNextFragment(&client->netchan); } Com_DPrintf ("SV_SendClientGameState() for %s\n", client->name); Com_DPrintf( "Going from CS_CONNECTED to CS_PRIMED for %s\n", client->name ); client->state = CS_PRIMED; client->pureAuthentic = 0; // when we receive the first packet from the client, we will // notice that it is from a different serverid and that the // gamestate message was not just sent, forcing a retransmit client->gamestateMessageNum = client->netchan.outgoingSequence; MSG_Init( &msg, msgBuffer, sizeof( msgBuffer ) ); // NOTE, MRE: all server->client messages now acknowledge // let the client know which reliable clientCommands we have received MSG_WriteLong( &msg, client->lastClientCommand ); // send any server commands waiting to be sent first. // we have to do this cause we send the client->reliableSequence // with a gamestate and it sets the clc.serverCommandSequence at // the client side SV_UpdateServerCommandsToClient( client, &msg ); // send the gamestate MSG_WriteByte( &msg, svc_gamestate ); MSG_WriteLong( &msg, client->reliableSequence ); // write the configstrings for ( start = 0 ; start < MAX_CONFIGSTRINGS ; start++ ) { if (sv.configstrings[start][0]) { MSG_WriteByte( &msg, svc_configstring ); MSG_WriteShort( &msg, start ); MSG_WriteBigString( &msg, sv.configstrings[start] ); } } // write the baselines Com_Memset( &nullstate, 0, sizeof( nullstate ) ); for ( start = 0 ; start < MAX_GENTITIES; start++ ) { base = &sv.svEntities[start].baseline; if ( !base->number ) { continue; } MSG_WriteByte( &msg, svc_baseline ); MSG_WriteDeltaEntity( &msg, &nullstate, base, qtrue ); } MSG_WriteByte( &msg, svc_EOF ); MSG_WriteLong( &msg, client - svs.clients); // write the checksum feed MSG_WriteLong( &msg, sv.checksumFeed); // deliver this to the client SV_SendMessageToClient( &msg, client ); }
/* ================ SV_SendClientGameState Sends the first message from the server to a connected client. This will be sent on the initial connection and upon each new map load. It will be resent if the client acknowledges a later message but has the wrong gamestate. ================ */ static void SV_SendClientGameState( client_t *client ) { int start; entityState_t *base, nullstate; msg_t msg; byte msgBuffer[MAX_MSGLEN]; Com_DPrintf ("SV_SendClientGameState() for %s\n", client->name); Com_DPrintf( "Going from CS_CONNECTED to CS_PRIMED for %s\n", client->name ); client->state = CS_PRIMED; client->pureAuthentic = 0; client->gotCP = qfalse; // when we receive the first packet from the client, we will // notice that it is from a different serverid and that the // gamestate message was not just sent, forcing a retransmit client->gamestateMessageNum = client->netchan.outgoingSequence; MSG_Init( &msg, msgBuffer, sizeof( msgBuffer ) ); // NOTE, MRE: all server->client messages now acknowledge // let the client know which reliable clientCommands we have received MSG_WriteLong( &msg, client->lastClientCommand ); // send any server commands waiting to be sent first. // we have to do this cause we send the client->reliableSequence // with a gamestate and it sets the clc.serverCommandSequence at // the client side SV_UpdateServerCommandsToClient( client, &msg ); // send the gamestate MSG_WriteByte( &msg, svc_gamestate ); MSG_WriteLong( &msg, client->reliableSequence ); // write the configstrings for ( start = 0 ; start < MAX_CONFIGSTRINGS ; start++ ) { if (sv.configstrings[start][0]) { MSG_WriteByte( &msg, svc_configstring ); MSG_WriteShort( &msg, start ); MSG_WriteBigString( &msg, sv.configstrings[start] ); } } // write the baselines Com_Memset( &nullstate, 0, sizeof( nullstate ) ); for ( start = 0 ; start < MAX_GENTITIES; start++ ) { base = &sv.svEntities[start].baseline; if ( !base->number ) { continue; } MSG_WriteByte( &msg, svc_baseline ); MSG_WriteDeltaEntity( &msg, &nullstate, base, qtrue ); } MSG_WriteByte( &msg, svc_EOF ); MSG_WriteLong( &msg, client - svs.clients); // write the checksum feed MSG_WriteLong( &msg, sv.checksumFeed); // There's something unknown going on here at the moment. // For now we're just going with default of adding a 0 short to keep jampded compatability. MSG_WriteShort( &msg, 0); // deliver this to the client SV_SendMessageToClient( &msg, client ); }
/* ==================== CL_Record_f record <demoname> Begins recording a demo from the current position ==================== */ void CL_Record_f (void) { char name[MAX_OSPATH]; char buf_data[MAX_MSGLEN]; sizebuf_t buf; int i; int len; entity_state_t *ent; entity_state_t nullstate; if (Cmd_Argc() != 2) { Com_Printf ("record <demoname>\n"); return; } if (cls.demorecording) { Com_Printf ("Already recording.\n"); return; } if (cls.state != ca_active) { Com_Printf ("You must be in a level to record.\n"); return; } // // open the demo file // Com_sprintf (name, sizeof(name), "%s/demos/%s.dm2", FS_Gamedir(), Cmd_Argv(1)); Com_Printf ("recording to %s.\n", name); FS_CreatePath (name); cls.demofile = fopen (name, "wb"); if (!cls.demofile) { Com_Printf ("ERROR: couldn't open.\n"); return; } cls.demorecording = true; // don't start saving messages until a non-delta compressed message is received cls.demowaiting = true; // // write out messages to hold the startup information // SZ_Init (&buf, buf_data, sizeof(buf_data)); // send the serverdata MSG_WriteByte (&buf, svc_serverdata); MSG_WriteLong (&buf, PROTOCOL_VERSION); MSG_WriteLong (&buf, 0x10000 + cl.servercount); MSG_WriteByte (&buf, 1); // demos are always attract loops MSG_WriteString (&buf, cl.gamedir); MSG_WriteShort (&buf, cl.playernum); MSG_WriteString (&buf, cl.configstrings[CS_NAME]); // configstrings for (i=0 ; i<MAX_CONFIGSTRINGS ; i++) { if (cl.configstrings[i][0]) { if (buf.cursize + strlen (cl.configstrings[i]) + 32 > buf.maxsize) { // write it out len = LittleLong (buf.cursize); fwrite (&len, 4, 1, cls.demofile); fwrite (buf.data, buf.cursize, 1, cls.demofile); buf.cursize = 0; } MSG_WriteByte (&buf, svc_configstring); MSG_WriteShort (&buf, i); MSG_WriteString (&buf, cl.configstrings[i]); } } // baselines memset (&nullstate, 0, sizeof(nullstate)); for (i=0; i<MAX_EDICTS ; i++) { ent = &cl_entities[i].baseline; if (!ent->modelindex) continue; if (buf.cursize + 64 > buf.maxsize) { // write it out len = LittleLong (buf.cursize); fwrite (&len, 4, 1, cls.demofile); fwrite (buf.data, buf.cursize, 1, cls.demofile); buf.cursize = 0; } MSG_WriteByte (&buf, svc_spawnbaseline); MSG_WriteDeltaEntity (&nullstate, &cl_entities[i].baseline, &buf, true, true); } MSG_WriteByte (&buf, svc_stufftext); MSG_WriteString (&buf, "precache\n"); // write it to the demo file len = LittleLong (buf.cursize); fwrite (&len, 4, 1, cls.demofile); fwrite (buf.data, buf.cursize, 1, cls.demofile); // the rest of the demo file will be individual frames }
__cdecl void SV_WriteSnapshotToClient(client_t* client, msg_t* msg){ snapshotInfo_t snapInfo; int lastframe; int from_num_entities; int newindex, oldindex, newnum, oldnum; clientState_ts *newcs, *oldcs; entityState_t *newent, *oldent; clientSnapshot_t *frame, *oldframe; int i; int snapFlags; int var_x, from_first_entity, from_num_clients, from_first_client; snapInfo.clnum = client - svsHeader.clients; snapInfo.cl = (void*)client; snapInfo.var_01 = 0; snapInfo.var_02 = 0; snapInfo.var_03 = 0; frame = &client->frames[client->netchan.outgoingSequence & PACKET_MASK]; frame->var_03 = svsHeader.time; if(client->deltaMessage <= 0 || client->state != CS_ACTIVE) { oldframe = NULL; lastframe = 0; var_x = 0; } else if(client->netchan.outgoingSequence - client->deltaMessage >= PACKET_BACKUP - 3) { Com_DPrintf("%s: Delta request from out of date packet.\n", client->name); oldframe = NULL; lastframe = 0; var_x = 0; } else if(client->demoDeltaFrameCount <= 0 && client->demorecording){ oldframe = NULL; lastframe = 0; var_x = 0; client->demowaiting = qfalse; Com_DPrintf("Force a nondelta frame for %s for demo recording\n", client->name); if(client->demoMaxDeltaFrames < 1024) { client->demoMaxDeltaFrames <<= 1; } client->demoDeltaFrameCount = client->demoMaxDeltaFrames; } else { oldframe = &client->frames[client->deltaMessage & PACKET_MASK]; lastframe = client->netchan.outgoingSequence - client->deltaMessage; var_x = oldframe->var_03; client->demoDeltaFrameCount--; if(oldframe->first_entity < svsHeader.nextSnapshotEntities - svsHeader.numSnapshotEntities) { Com_PrintWarning("%s: Delta request from out of date entities - delta against entity %i, oldest is %i, current is %i. Their old snapshot had %i entities in it\n", client->name, oldframe->first_entity, svs.nextSnapshotEntities - svs.numSnapshotEntities, svs.nextSnapshotEntities, oldframe->num_entities ); oldframe = NULL; lastframe = 0; var_x = 0; } else if(oldframe->first_client < svsHeader.nextSnapshotClients - svsHeader.numSnapshotClients) { Com_PrintWarning("%s: Delta request from out of date clients - delta against client %i, oldest is %i, current is %i. Their old snapshot had %i clients in it\n", client->name, oldframe->first_client, svs.nextSnapshotClients - svs.numSnapshotClients, svs.nextSnapshotClients, oldframe->num_clients); oldframe = NULL; lastframe = 0; var_x = 0; } } MSG_WriteByte(msg, svc_snapshot); MSG_WriteLong(msg, svsHeader.time); MSG_WriteByte(msg, lastframe); snapInfo.var_01 = var_x; snapFlags = svsHeader.snapFlagServerBit; if(client->rateDelayed){ snapFlags |= 1; } if(client->state == CS_ACTIVE) { client->unksnapshotvar = 1; } else { if(client->state != CS_ZOMBIE){ client->unksnapshotvar = 0; } } if(!client->unksnapshotvar){ snapFlags |= 2; } MSG_WriteByte(msg, snapFlags); if(oldframe) { MSG_WriteDeltaPlayerstate( &snapInfo, msg, svsHeader.time, &oldframe->ps, &frame->ps); from_num_entities = oldframe->num_entities; from_first_entity = oldframe->first_entity; from_num_clients = oldframe->num_clients; from_first_client = oldframe->first_client; } else { MSG_WriteDeltaPlayerstate( &snapInfo, msg, svsHeader.time, 0, &frame->ps); from_num_entities = 0; from_first_entity = 0; from_num_clients = 0; from_first_client = 0; } MSG_ClearLastReferencedEntity(msg); newindex = 0; oldindex = 0; // Com_Printf("\nDelta client: %i:\n", snapInfo.clnum); while ( newindex < frame->num_entities || oldindex < from_num_entities){ if ( newindex >= frame->num_entities ) { newnum = 9999; newent = NULL; } else { newent = &svsHeader.snapshotEntities[( frame->first_entity + newindex ) % svsHeader.numSnapshotEntities]; newnum = newent->number; } if ( oldindex >= from_num_entities ) { oldnum = 9999; oldent = NULL; } else { oldent = &svsHeader.snapshotEntities[( from_first_entity + oldindex ) % svsHeader.numSnapshotEntities]; oldnum = oldent->number; } if ( newnum == oldnum ) { // delta update from old position // because the force parm is qfalse, this will not result // in any bytes being emited if the entity has not changed at all // if(newent->number < 64 || oldent->number < 64) // Com_Printf(" Delta Update Entity - New delta: %i, %x Old delta: %i, %x\n", newent->number, newent, oldent->number, oldent); MSG_WriteDeltaEntity( &snapInfo, msg, svsHeader.time, oldent, newent, qfalse ); oldindex++; newindex++; continue; } if ( newnum < oldnum ) { // this is a new entity, send it from the baseline snapInfo.var_02 = 1; // if(newent->number < 64) // Com_Printf(" Delta Add Entity: %i, %x\n", newent->number, newent); MSG_WriteDeltaEntity( &snapInfo, msg, svsHeader.time, &svsHeader.svEntities[newnum].baseline, newent, qtrue ); snapInfo.var_02 = 0; newindex++; continue; } if ( newnum > oldnum ) { // the old entity isn't present in the new message // if(oldent->number < 64) // Com_Printf(" Delta Remove Entity: %i, %x\n", oldent->number, oldent); MSG_WriteDeltaEntity( &snapInfo, msg, svsHeader.time, oldent, NULL, qtrue ); oldindex++; continue; } } MSG_WriteEntityIndex(&snapInfo, msg, ( MAX_GENTITIES - 1 ), GENTITYNUM_BITS); MSG_ClearLastReferencedEntity(msg); newindex = 0; oldindex = 0; while(newindex < frame->num_clients || oldindex < from_num_clients){ if(newindex >= frame->num_clients){ newnum = 9999; newcs = NULL; }else{ newcs = &svsHeader.snapshotClients[(frame->first_client + newindex) % svsHeader.numSnapshotClients]; newnum = newcs->number; } if(oldindex >= from_num_clients){ oldnum = 9999; oldcs = NULL; }else{ oldcs = &svsHeader.snapshotClients[(from_first_client + oldindex) % svsHeader.numSnapshotClients]; oldnum = oldcs->number; } if ( newnum == oldnum ) { // delta update from old position // because the force parm is qfalse, this will not result // in any bytes being emited if the entity has not changed at all MSG_WriteDeltaClient( &snapInfo, msg, svsHeader.time, oldcs, newcs, qfalse ); oldindex++; newindex++; continue; } if ( newnum < oldnum ) { MSG_WriteDeltaClient( &snapInfo, msg, svsHeader.time, NULL, newcs, qtrue ); newindex++; continue; } if ( newnum > oldnum ) { MSG_WriteDeltaClient( &snapInfo, msg, svsHeader.time, oldcs, NULL, qtrue ); oldindex++; continue; } } MSG_WriteBit0(msg); if(sv_padPackets->integer){ for( i=0 ; i < sv_padPackets->integer ; i++){ MSG_WriteByte( msg, 0); //svc_nop } } }
/* * Writes a delta update of an entity_state_t list to the message. */ void SV_EmitPacketEntities ( client_frame_t *from, client_frame_t *to, sizebuf_t *msg ) { entity_state_t *oldent, *newent; int oldindex, newindex; int oldnum, newnum; int from_num_entities; int bits; MSG_WriteByte( msg, svc_packetentities ); if ( !from ) { from_num_entities = 0; } else { from_num_entities = from->num_entities; } newindex = 0; oldindex = 0; newent = NULL; oldent = NULL; while ( newindex < to->num_entities || oldindex < from_num_entities ) { if ( msg->cursize > MAX_MSGLEN - 150 ) { break; } if ( newindex >= to->num_entities ) { newnum = 9999; } else { newent = &svs.client_entities [ ( to->first_entity + newindex ) % svs.num_client_entities ]; newnum = newent->number; } if ( oldindex >= from_num_entities ) { oldnum = 9999; } else { oldent = &svs.client_entities [ ( from->first_entity + oldindex ) % svs.num_client_entities ]; oldnum = oldent->number; } if ( newnum == oldnum ) { /* delta update from old position because the force parm is false, this will not result in any bytes being emited if the entity has not changed at all note that players are always 'newentities', this updates their oldorigin always and prevents warping */ MSG_WriteDeltaEntity( oldent, newent, msg, false, newent->number <= maxclients->value ); oldindex++; newindex++; continue; } if ( newnum < oldnum ) { /* this is a new entity, send it from the baseline */ MSG_WriteDeltaEntity( &sv.baselines [ newnum ], newent, msg, true, true ); newindex++; continue; } if ( newnum > oldnum ) { /* the old entity isn't present in the new message */ bits = U_REMOVE; if ( oldnum >= 256 ) { bits |= U_NUMBER16 | U_MOREBITS1; } MSG_WriteByte( msg, bits & 255 ); if ( bits & 0x0000ff00 ) { MSG_WriteByte( msg, ( bits >> 8 ) & 255 ); } if ( bits & U_NUMBER16 ) { MSG_WriteShort( msg, oldnum ); } else { MSG_WriteByte( msg, oldnum ); } oldindex++; continue; }
/* ============= SV_EmitPacketEntities Writes a delta update of an entity_state_t list to the message-> ============= */ void SV_EmitPacketEntities( sv_client_t *cl, client_frame_t *to, sizebuf_t *msg ) { entity_state_t *oldent, *newent; int oldindex, newindex; int oldnum, newnum; int from_num_entities; client_frame_t *from; // this is the frame that we are going to delta update from if( cl->delta_sequence != -1 ) { from = &cl->frames[cl->delta_sequence & SV_UPDATE_MASK]; from_num_entities = from->num_entities; // the snapshot's entities may still have rolled off the buffer, though if( from->first_entity <= svs.next_client_entities - svs.num_client_entities ) { MsgDev( D_WARN, "%s: delta request from out of date entities.\n", cl->name ); from = NULL; from_num_entities = 0; BF_WriteByte( msg, svc_packetentities ); BF_WriteWord( msg, to->num_entities ); } else { BF_WriteByte( msg, svc_deltapacketentities ); BF_WriteWord( msg, to->num_entities ); BF_WriteByte( msg, cl->delta_sequence ); } } else { from = NULL; from_num_entities = 0; BF_WriteByte( msg, svc_packetentities ); BF_WriteWord( msg, to->num_entities ); } newent = NULL; oldent = NULL; newindex = 0; oldindex = 0; while( newindex < to->num_entities || oldindex < from_num_entities ) { if( newindex >= to->num_entities ) { newnum = MAX_ENTNUMBER; } else { newent = &svs.packet_entities[(to->first_entity+newindex)%svs.num_client_entities]; newnum = newent->number; } if( oldindex >= from_num_entities ) { oldnum = MAX_ENTNUMBER; } else { oldent = &svs.packet_entities[(from->first_entity+oldindex)%svs.num_client_entities]; oldnum = oldent->number; } if( newnum == oldnum ) { // delta update from old position // because the force parm is false, this will not result // in any bytes being emited if the entity has not changed at all MSG_WriteDeltaEntity( oldent, newent, msg, false, SV_IsPlayerIndex( newent->number ), sv.time ); oldindex++; newindex++; continue; } if( newnum < oldnum ) { // this is a new entity, send it from the baseline MSG_WriteDeltaEntity( &svs.baselines[newnum], newent, msg, true, SV_IsPlayerIndex( newent->number ), sv.time ); newindex++; continue; } if( newnum > oldnum ) { qboolean force; if( EDICT_NUM( oldent->number )->free ) force = true; // entity completely removed from server else force = false; // just removed from delta-message // remove from message MSG_WriteDeltaEntity( oldent, NULL, msg, force, false, sv.time ); oldindex++; continue; } } BF_WriteWord( msg, 0 ); // end of packetentities }
/* * SNAP_BeginDemoRecording */ void SNAP_BeginDemoRecording( int demofile, unsigned int spawncount, unsigned int snapFrameTime, const char *sv_name, unsigned int sv_bitflags, purelist_t *purelist, char *configstrings, entity_state_t *baselines ) { unsigned int i; msg_t msg; uint8_t msg_buffer[MAX_MSGLEN]; purelist_t *purefile; entity_state_t nullstate; entity_state_t *base; MSG_Init( &msg, msg_buffer, sizeof( msg_buffer ) ); SNAP_DemoMetaDataMessage( &msg, "", 0 ); SNAP_RecordDemoMetaDataMessage( demofile, &msg ); // serverdata message MSG_WriteByte( &msg, svc_serverdata ); MSG_WriteLong( &msg, APP_DEMO_PROTOCOL_VERSION ); MSG_WriteLong( &msg, spawncount ); MSG_WriteShort( &msg, (unsigned short)snapFrameTime ); MSG_WriteString( &msg, FS_BaseGameDirectory() ); MSG_WriteString( &msg, FS_GameDirectory() ); MSG_WriteShort( &msg, -1 ); // playernum MSG_WriteString( &msg, sv_name ); // level name MSG_WriteByte( &msg, sv_bitflags & ~SV_BITFLAGS_HTTP ); // sv_bitflags // pure files i = Com_CountPureListFiles( purelist ); if( i > (short)0x7fff ) Com_Error( ERR_DROP, "Error: Too many pure files." ); MSG_WriteShort( &msg, i ); purefile = purelist; while( purefile ) { MSG_WriteString( &msg, purefile->filename ); MSG_WriteLong( &msg, purefile->checksum ); purefile = purefile->next; DEMO_SAFEWRITE( demofile, &msg, false ); } // config strings for( i = 0; i < MAX_CONFIGSTRINGS; i++ ) { const char *configstring = configstrings + i * MAX_CONFIGSTRING_CHARS; if( configstring[0] ) { MSG_WriteByte( &msg, svc_servercs ); MSG_WriteString( &msg, va( "cs %i \"%s\"", i, configstring ) ); DEMO_SAFEWRITE( demofile, &msg, false ); } } // baselines memset( &nullstate, 0, sizeof( nullstate ) ); for( i = 0; i < MAX_EDICTS; i++ ) { base = &baselines[i]; if( base->modelindex || base->sound || base->effects ) { MSG_WriteByte( &msg, svc_spawnbaseline ); MSG_WriteDeltaEntity( &nullstate, base, &msg, true, true ); DEMO_SAFEWRITE( demofile, &msg, false ); } } // client expects the server data to be in a separate packet DEMO_SAFEWRITE( demofile, &msg, true ); MSG_WriteByte( &msg, svc_servercs ); MSG_WriteString( &msg, "precache" ); DEMO_SAFEWRITE( demofile, &msg, true ); }