/* * SV_ParseMoveCommand */ static void SV_ParseMoveCommand( client_t *client, msg_t *msg ) { unsigned int i, ucmdHead, ucmdFirst, ucmdCount; usercmd_t nullcmd; int lastframe; lastframe = MSG_ReadLong( msg ); // read the id of the first ucmd we will receive ucmdHead = (unsigned int)MSG_ReadLong( msg ); // read the number of ucmds we will receive ucmdCount = (unsigned int)MSG_ReadByte( msg ); if( ucmdCount > CMD_MASK ) { SV_DropClient( client, DROP_TYPE_GENERAL, "Error: Ucmd overflow" ); return; } ucmdFirst = ucmdHead > ucmdCount ? ucmdHead - ucmdCount : 0; client->UcmdReceived = ucmdHead < 1 ? 0 : ucmdHead - 1; // read the user commands for( i = ucmdFirst; i < ucmdHead; i++ ) { if( i == ucmdFirst ) { // first one isn't delta compressed memset( &nullcmd, 0, sizeof( nullcmd ) ); // jalfixme: check for too old overflood MSG_ReadDeltaUsercmd( msg, &nullcmd, &client->ucmds[i & CMD_MASK] ); } else { MSG_ReadDeltaUsercmd( msg, &client->ucmds[( i-1 ) & CMD_MASK], &client->ucmds[i & CMD_MASK] ); } } if( client->state != CS_SPAWNED ) { client->lastframe = -1; return; } // calc ping if( lastframe != client->lastframe ) { client->lastframe = lastframe; if( client->lastframe > 0 ) { // FIXME: Medar: ping is in gametime, should be in realtime //client->frame_latency[client->lastframe&(LATENCY_COUNTS-1)] = svs.gametime - (client->frames[client->lastframe & UPDATE_MASK].sentTimeStamp; // this is more accurate. A little bit hackish, but more accurate client->frame_latency[client->lastframe&( LATENCY_COUNTS-1 )] = svs.gametime - ( client->ucmds[client->UcmdReceived & CMD_MASK].serverTimeStamp + svc.snapFrameTime ); } } }
/* ================= CL_ReadDemoUserCmd read the demo usercmd for predicting and smooth movement during playback the demo ================= */ void CL_ReadDemoUserCmd( qboolean discard ) { byte data[1024]; int cmdnumber; int outgoing_sequence; word bytes; FS_Read( cls.demofile, &outgoing_sequence, sizeof( int )); FS_Read( cls.demofile, &cmdnumber, sizeof( int )); FS_Read( cls.demofile, &bytes, sizeof( short )); FS_Read( cls.demofile, data, bytes ); if( !discard ) { usercmd_t nullcmd = {0}; sizebuf_t buf; BF_Init( &buf, "UserCmd", data, sizeof( data )); // always delta'ing from null cl.refdef.cmd = &cl.cmds[cmdnumber & CL_UPDATE_MASK ]; MSG_ReadDeltaUsercmd( &buf, &nullcmd, cl.refdef.cmd ); // NOTE: we need to have the current outgoing sequence correct // so we can do prediction correctly during playback cls.netchan.outgoing_sequence = outgoing_sequence; } }
/* ================== SV_UserMove The message usually contains all the movement commands that were in the last three packets, so that the information in dropped packets can be recovered. On very fast clients, there may be multiple usercmd packed into each of the backup packets. ================== */ static void SV_UserMove( client_t *cl, msg_t *msg ) { int i, start; int cmdNum; int firstNum; int cmdCount; usercmd_t nullcmd; usercmd_t cmds[MAX_PACKET_USERCMDS]; usercmd_t *cmd, *oldcmd; int clientTime; int serverId; cl->reliableAcknowledge = MSG_ReadLong( msg ); serverId = MSG_ReadLong( msg ); clientTime = MSG_ReadLong( msg ); cl->deltaMessage = MSG_ReadLong( msg ); // cmdNum is the command number of the most recent included usercmd cmdNum = MSG_ReadLong( msg ); cmdCount = MSG_ReadByte( msg ); if ( cmdCount < 1 ) { Com_Printf( "cmdCount < 1\n" ); return; } if ( cmdCount > MAX_PACKET_USERCMDS ) { Com_Printf( "cmdCount > MAX_PACKET_USERCMDS\n" ); return; } memset( &nullcmd, 0, sizeof(nullcmd) ); oldcmd = &nullcmd; for ( i = 0 ; i < cmdCount ; i++ ) { cmd = &cmds[i]; MSG_ReadDeltaUsercmd( msg, oldcmd, cmd ); oldcmd = cmd; } // if this is a usercmd from a previous gamestate, // ignore it or retransmit the current gamestate if ( serverId != sv.serverId ) { // if we can tell that the client has dropped the last // gamestate we sent them, resend it if ( cl->netchan.incomingAcknowledged > cl->gamestateMessageNum ) { Com_DPrintf( "%s : dropped gamestate, resending\n", cl->name ); SV_SendClientGameState( cl ); } return; } // if this is the first usercmd we have received // this gamestate, put the client into the world if ( cl->state == CS_PRIMED ) { SV_ClientEnterWorld( cl, &cmds[0], eSavedGameJustLoaded ); if ( sv_mapname->string[0]!='_' ) { char savename[MAX_QPATH]; if ( eSavedGameJustLoaded == eNO ) { SG_WriteSavegame("auto",qtrue); Com_sprintf (savename, sizeof(savename), "auto_%s",sv_mapname->string); SG_WriteSavegame(savename,qtrue);//can't use va becuase it's nested } else if ( qbLoadTransition == qtrue ) { Com_sprintf (savename, sizeof(savename), "hub/%s", sv_mapname->string ); SG_WriteSavegame( savename, qfalse );//save a full one SG_WriteSavegame( "auto", qfalse );//need a copy for auto, too } } eSavedGameJustLoaded = eNO; // the moves can be processed normaly } if ( cl->state != CS_ACTIVE ) { cl->deltaMessage = -1; return; } // if there is a time gap from the last packet to this packet, // fill in with the first command in the packet // with a packetdup of 0, firstNum == cmdNum firstNum = cmdNum - ( cmdCount - 1 ); if ( cl->cmdNum < firstNum - 1 ) { cl->droppedCommands = qtrue; if ( sv_showloss->integer ) { Com_Printf("Lost %i usercmds from %s\n", firstNum - 1 - cl->cmdNum, cl->name); } if ( cl->cmdNum < firstNum - 6 ) { cl->cmdNum = firstNum - 6; // don't generate too many } while ( cl->cmdNum < firstNum - 1 ) { cl->cmdNum++; SV_ClientThink( cl, &cmds[0] ); } } // skip over any usercmd_t we have already executed start = cl->cmdNum - ( firstNum - 1 ); for ( i = start ; i < cmdCount ; i++ ) { SV_ClientThink (cl, &cmds[ i ]); } cl->cmdNum = cmdNum; }
void CL_ParsePlayerinfo (void) { int msec; int flags; player_info_t *info; player_state_t *state; int num; int i; num = MSG_ReadByte (); if (num > MAX_CLIENTS) Sys_Error ("CL_ParsePlayerinfo: bad num"); info = &cl.players[num]; state = &cl.frames[parsecountmod].playerstate[num]; flags = state->flags = MSG_ReadShort (); state->messagenum = cl.parsecount; state->origin[0] = MSG_ReadCoord (); state->origin[1] = MSG_ReadCoord (); state->origin[2] = MSG_ReadCoord (); state->frame = MSG_ReadByte (); // the other player's last move was likely some time // before the packet was sent out, so accurately track // the exact time it was valid at if (flags & PF_MSEC) { msec = MSG_ReadByte (); state->state_time = parsecounttime - msec*0.001; } else state->state_time = parsecounttime; if (flags & PF_COMMAND) MSG_ReadDeltaUsercmd (&nullcmd, &state->command); for (i=0 ; i<3 ; i++) { if (flags & (PF_VELOCITY1<<i) ) state->velocity[i] = MSG_ReadShort(); else state->velocity[i] = 0; } if (flags & PF_MODEL) state->modelindex = MSG_ReadByte (); else state->modelindex = cl_playerindex; if (flags & PF_SKINNUM) state->skinnum = MSG_ReadByte (); else state->skinnum = 0; if (flags & PF_EFFECTS) state->effects = MSG_ReadByte (); else state->effects = 0; if (flags & PF_WEAPONFRAME) state->weaponframe = MSG_ReadByte (); else state->weaponframe = 0; // GAJA: Check if we are dead. We need this info to stop tracing // also we may use this : cl.stats[STAT_HEALTH] <= 0 to check if we are dead if (cl.playernum == num) { if ( (flags & PF_DEAD)) { printf("I AM DEAD: trace_state = %d\n", trace_state); if ((trace_state >= start_trace) && (trace_state != dead_time)) trace_state = stop_trace; } } VectorCopy (state->command.angles, state->viewangles); }
void Parse_Playerinfo (void){ int num,flags, i; usercmd_t nullcmd,command; extern int gflags; player_info_t *p; num = MSG_ReadByte(); p = &players[num]; gflags = flags = MSG_ReadShort (); p->origin[0] = MSG_ReadCoord (); p->origin[1] = MSG_ReadCoord (); p->origin[2] = MSG_ReadCoord (); p->frame = MSG_ReadByte (); if (flags & PF_MSEC) { MSG_ReadByte (); } else { } if (flags & PF_COMMAND) MSG_ReadDeltaUsercmd (&nullcmd, &command, protoversion); for (i = 0; i < 3; i++) { if (flags & (PF_VELOCITY1 << i) ) p->velocity[i] = MSG_ReadShort(); else p->velocity[i] = 0; } if (flags & PF_MODEL) p->modelindex = MSG_ReadByte (); else p->modelindex = playermodel; if (flags & PF_SKINNUM) p->skinnum = MSG_ReadByte (); else p->skinnum = 0; if (flags & PF_EFFECTS) p->effects = MSG_ReadByte (); else p->effects = 0; if (flags & PF_WEAPONFRAME) p->weaponframe = MSG_ReadByte (); else p->weaponframe = 0; }
/* ================== SV_UserMove The message usually contains all the movement commands that were in the last three packets, so that the information in dropped packets can be recovered. On very fast clients, there may be multiple usercmd packed into each of the backup packets. ================== */ static void SV_UserMove( client_t *cl, msg_t *msg, bool delta ) { int i; int cmdCount; usercmd_t nullcmd; usercmd_t cmds[ MAX_PACKET_USERCMDS ]; usercmd_t *cmd, *oldcmd; if ( delta ) { cl->deltaMessage = cl->messageAcknowledge; } else { cl->deltaMessage = -1; } cmdCount = MSG_ReadByte( msg ); if ( cmdCount < 1 ) { Log::Notice( "cmdCount < 1\n" ); return; } if ( cmdCount > MAX_PACKET_USERCMDS ) { Log::Notice( "cmdCount > MAX_PACKET_USERCMDS\n" ); return; } memset( &nullcmd, 0, sizeof( nullcmd ) ); oldcmd = &nullcmd; for ( i = 0; i < cmdCount; i++ ) { cmd = &cmds[ i ]; MSG_ReadDeltaUsercmd( msg, oldcmd, cmd ); // MSG_ReadDeltaUsercmd( msg, oldcmd, cmd ); oldcmd = cmd; } // save time for ping calculation cl->frames[ cl->messageAcknowledge & PACKET_MASK ].messageAcked = svs.time; // if this is the first usercmd we have received // this gamestate, put the client into the world if ( cl->state == clientState_t::CS_PRIMED ) { SV_ClientEnterWorld( cl, &cmds[ 0 ] ); // the moves can be processed normaly } if ( cl->state != clientState_t::CS_ACTIVE ) { cl->deltaMessage = -1; return; } // usually, the first couple commands will be duplicates // of ones we have previously received, but the servertimes // in the commands will cause them to be immediately discarded for ( i = 0; i < cmdCount; i++ ) { // if this is a cmd from before a map_restart ignore it if ( cmds[ i ].serverTime > cmds[ cmdCount - 1 ].serverTime ) { continue; } // extremely lagged or cmd from before a map_restart if ( cmds[ i ].serverTime <= cl->lastUsercmd.serverTime ) { continue; } SV_ClientThink( cl, &cmds[ i ] ); } }
/* =================== SV_ExecuteClientMessage The current net_message is parsed for the given client =================== */ void SV_ExecuteClientMessage(client_t *cl){ int c; char *s; usercmd_t nullcmd; usercmd_t oldest, oldcmd, newcmd; int net_drop; int stringCmdCount; int checksum, calculatedChecksum; int checksumIndex; qboolean move_issued; int lastframe; sv_client = cl; sv_player = sv_client->edict; // only allow one move command move_issued = false; stringCmdCount = 0; while(1){ if(net_message.readcount > net_message.cursize){ Com_Printf("SV_ReadClientMessage: badread\n"); SV_DropClient(cl); return; } c = MSG_ReadByte(&net_message); if(c == -1) break; switch(c){ default: Com_Printf("SV_ReadClientMessage: unknown command char\n"); SV_DropClient(cl); return; case clc_nop: break; case clc_userinfo: strncpy(cl->userinfo, MSG_ReadString(&net_message), sizeof(cl->userinfo) - 1); SV_UserinfoChanged(cl); break; case clc_move: if(move_issued) return; // someone is trying to cheat... move_issued = true; checksumIndex = net_message.readcount; checksum = MSG_ReadByte(&net_message); lastframe = MSG_ReadLong(&net_message); if(lastframe != cl->lastframe){ cl->lastframe = lastframe; if(cl->lastframe > 0){ cl->frame_latency[cl->lastframe&(LATENCY_COUNTS - 1)] = svs.realtime - cl->frames[cl->lastframe & UPDATE_MASK].senttime; } } memset(&nullcmd, 0, sizeof(nullcmd)); MSG_ReadDeltaUsercmd(&net_message, &nullcmd, &oldest); MSG_ReadDeltaUsercmd(&net_message, &oldest, &oldcmd); MSG_ReadDeltaUsercmd(&net_message, &oldcmd, &newcmd); if(cl->state != cs_spawned){ cl->lastframe = -1; break; } // if the checksum fails, ignore the rest of the packet calculatedChecksum = COM_BlockSequenceCRCByte( net_message.data + checksumIndex + 1, net_message.readcount - checksumIndex - 1, cl->netchan.incoming_sequence); if(calculatedChecksum != checksum){ Com_DPrintf("Failed command checksum for %s(%d != %d)/%d\n", cl->name, calculatedChecksum, checksum, cl->netchan.incoming_sequence); return; } if(!paused->value){ net_drop = cl->netchan.dropped; if(net_drop < 20){ // if(net_drop > 2) // Com_Printf("drop %i\n", net_drop); while(net_drop > 2){ SV_ClientThink(cl, &cl->lastcmd); net_drop--; } if(net_drop > 1) SV_ClientThink(cl, &oldest); if(net_drop > 0) SV_ClientThink(cl, &oldcmd); } SV_ClientThink(cl, &newcmd); } cl->lastcmd = newcmd; break; case clc_stringcmd: s = MSG_ReadString(&net_message); // malicious users may try using too many string commands if(++stringCmdCount < MAX_STRINGCMDS) SV_ExecuteUserCommand(s); if(cl->state == cs_zombie) return; // disconnect command break; } } }
void CL_ParsePlayerinfo (void) { int msec; int flags; player_info_t *info; player_state_t *state; int num; int i; num = MSG_ReadByte (); if (num > MAX_CLIENTS) Sys_Error ("CL_ParsePlayerinfo: bad num"); info = &cl.players[num]; state = &cl.frames[parsecountmod].playerstate[num]; flags = state->flags = MSG_ReadShort (); state->messagenum = cl.parsecount; state->origin[0] = MSG_ReadCoord (); state->origin[1] = MSG_ReadCoord (); state->origin[2] = MSG_ReadCoord (); state->frame = MSG_ReadByte (); // the other player's last move was likely some time // before the packet was sent out, so accurately track // the exact time it was valid at if (flags & PF_MSEC) { msec = MSG_ReadByte (); state->state_time = parsecounttime - msec*0.001; } else state->state_time = parsecounttime; if (flags & PF_COMMAND) MSG_ReadDeltaUsercmd (&nullcmd, &state->command); for (i=0 ; i<3 ; i++) { if (flags & (PF_VELOCITY1<<i) ) state->velocity[i] = MSG_ReadShort(); else state->velocity[i] = 0; } if (flags & PF_MODEL) state->modelindex = MSG_ReadByte (); else state->modelindex = cl_playerindex; if (flags & PF_SKINNUM) state->skinnum = MSG_ReadByte (); else state->skinnum = 0; if (flags & PF_EFFECTS) state->effects = MSG_ReadByte (); else state->effects = 0; if (flags & PF_WEAPONFRAME) state->weaponframe = MSG_ReadByte (); else state->weaponframe = 0; VectorCopy (state->command.angles, state->viewangles); }
void SV_ExecuteClientMessage (client_t *cl) { int c; char *s; usercmd_t oldest, oldcmd, newcmd; client_frame_t *frame; vec3_t o; qboolean move_issued = false; //only allow one move command int checksumIndex; byte checksum, calculatedChecksum; int seq_hash; // calc ping time frame = &cl->frames[cl->netchan.incoming_acknowledged & UPDATE_MASK]; frame->ping_time = realtime - frame->senttime; // make sure the reply sequence number matches the incoming // sequence number if (cl->netchan.incoming_sequence >= cl->netchan.outgoing_sequence) cl->netchan.outgoing_sequence = cl->netchan.incoming_sequence; else cl->send_message = false; // don't reply, sequences have slipped // save time for ping calculations cl->frames[cl->netchan.outgoing_sequence & UPDATE_MASK].senttime = realtime; cl->frames[cl->netchan.outgoing_sequence & UPDATE_MASK].ping_time = -1; host_client = cl; sv_player = host_client->edict; // seq_hash = (cl->netchan.incoming_sequence & 0xffff) ; // ^ QW_CHECK_HASH; seq_hash = cl->netchan.incoming_sequence; // mark time so clients will know how much to predict // other players cl->localtime = sv.time; cl->delta_sequence = -1; // no delta unless requested while (1) { if (msg_badread) { Con_Printf ("SV_ReadClientMessage: badread\n"); SV_DropClient (cl); return; } c = MSG_ReadByte (); if (c == -1) break; switch (c) { default: Con_Printf ("SV_ReadClientMessage: unknown command char\n"); SV_DropClient (cl); return; case clc_nop: break; case clc_delta: cl->delta_sequence = MSG_ReadByte (); break; case clc_move: if (move_issued) return; // someone is trying to cheat... move_issued = true; checksumIndex = MSG_GetReadCount(); checksum = (byte)MSG_ReadByte (); // read loss percentage cl->lossage = MSG_ReadByte(); MSG_ReadDeltaUsercmd (&nullcmd, &oldest); MSG_ReadDeltaUsercmd (&oldest, &oldcmd); MSG_ReadDeltaUsercmd (&oldcmd, &newcmd); if ( cl->state != cs_spawned ) break; // if the checksum fails, ignore the rest of the packet calculatedChecksum = COM_BlockSequenceCRCByte( net_message.data + checksumIndex + 1, MSG_GetReadCount() - checksumIndex - 1, seq_hash); if (calculatedChecksum != checksum) { Con_DPrintf ("Failed command checksum for %s(%d) (%d != %d)\n", cl->name, cl->netchan.incoming_sequence, checksum, calculatedChecksum); return; } if (!sv.paused) { SV_PreRunCmd(); if (net_drop < 20) { while (net_drop > 2) { SV_RunCmd (&cl->lastcmd); net_drop--; } if (net_drop > 1) SV_RunCmd (&oldest); if (net_drop > 0) SV_RunCmd (&oldcmd); } SV_RunCmd (&newcmd); SV_PostRunCmd(); } cl->lastcmd = newcmd; cl->lastcmd.buttons = 0; // avoid multiple fires on lag break; case clc_stringcmd: s = MSG_ReadString (); SV_ExecuteUserCommand (s); break; case clc_tmove: o[0] = MSG_ReadCoord(); o[1] = MSG_ReadCoord(); o[2] = MSG_ReadCoord(); // only allowed by spectators if (host_client->spectator) { VectorCopy(o, sv_player->v.origin); SV_LinkEdict(sv_player, false); } break; case clc_upload: SV_NextUpload(); break; } } }