void CL_WriteUsercmd( sizebuf_t *msg, int from, int to ) { usercmd_t nullcmd; usercmd_t *f, *t; ASSERT( from == -1 || ( from >= 0 && from < MULTIPLAYER_BACKUP )); ASSERT( to >= 0 && to < MULTIPLAYER_BACKUP ); if( from == -1 ) { Q_memset( &nullcmd, 0, sizeof( nullcmd )); f = &nullcmd; } else { f = &cl.cmds[from]; } t = &cl.cmds[to]; // write it into the buffer MSG_WriteDeltaUsercmd( msg, f, t ); }
/* * CL_WriteUcmdsToMessage */ void CL_WriteUcmdsToMessage( msg_t *msg ) { usercmd_t *cmd; usercmd_t *oldcmd; usercmd_t nullcmd; unsigned int resendCount; unsigned int i; unsigned int ucmdFirst; unsigned int ucmdHead; if( !msg || cls.state < CA_ACTIVE || cls.demo.playing ) return; // find out what ucmds we have to send ucmdFirst = cls.ucmdAcknowledged + 1; ucmdHead = cl.cmdNum + 1; if( cl_ucmdMaxResend->integer > CMD_BACKUP * 0.5 ) Cvar_SetValue( "cl_ucmdMaxResend", CMD_BACKUP * 0.5 ); else if( cl_ucmdMaxResend->integer < 1 ) Cvar_SetValue( "cl_ucmdMaxResend", 1 ); // find what is our resend count (resend doesn't include the newly generated ucmds) // and move the start back to the resend start if( ucmdFirst <= cls.ucmdSent + 1 ) resendCount = 0; else resendCount = ( cls.ucmdSent + 1 ) - ucmdFirst; if( resendCount > (unsigned int)cl_ucmdMaxResend->integer ) resendCount = (unsigned int)cl_ucmdMaxResend->integer; if( ucmdFirst > ucmdHead ) ucmdFirst = ucmdHead; // if this happens, the player is in a freezing lag. Send him the less possible data if( ( ucmdHead - ucmdFirst ) + resendCount > CMD_MASK * 0.5 ) resendCount = 0; // move the start backwards to the resend point ucmdFirst = ( ucmdFirst > resendCount ) ? ucmdFirst - resendCount : ucmdFirst; if( ( ucmdHead - ucmdFirst ) > CMD_MASK ) // ran out of updates, seduce the send to try to recover activity ucmdFirst = ucmdHead - 3; // begin a client move command MSG_WriteByte( msg, clc_move ); // (acknowledge server frame snap) // let the server know what the last frame we // got was, so the next message can be delta compressed if( cl.receivedSnapNum <= 0 ) MSG_WriteLong( msg, -1 ); else MSG_WriteLong( msg, cl.snapShots[cl.receivedSnapNum & UPDATE_MASK].serverFrame ); // Write the actual ucmds // write the id number of first ucmd to be sent, and the count MSG_WriteLong( msg, ucmdHead ); MSG_WriteByte( msg, (uint8_t)( ucmdHead - ucmdFirst ) ); // write the ucmds for( i = ucmdFirst; i < ucmdHead; i++ ) { if( i == ucmdFirst ) // first one isn't delta-compressed { cmd = &cl.cmds[i & CMD_MASK]; memset( &nullcmd, 0, sizeof( nullcmd ) ); MSG_WriteDeltaUsercmd( msg, &nullcmd, cmd ); } else // delta compress to previous written { cmd = &cl.cmds[i & CMD_MASK]; oldcmd = &cl.cmds[( i-1 ) & CMD_MASK]; MSG_WriteDeltaUsercmd( msg, oldcmd, cmd ); } } cls.ucmdSent = i; }
void CL_SendCmd (void) { sizebuf_t buf; byte data[128]; usercmd_t *cmd, *oldcmd; int i, checksumIndex, lost; qbool dontdrop; static float pps_balance = 0; static int dropcount = 0; if (cls.demoplayback && !cls.mvdplayback) return; // sendcmds come from the demo #ifdef FTE_PEXT_CHUNKEDDOWNLOADS CL_SendChunkDownloadReq(); #endif // save this command off for prediction i = cls.netchan.outgoing_sequence & UPDATE_MASK; cmd = &cl.frames[i].cmd; cl.frames[i].senttime = cls.realtime; cl.frames[i].receivedtime = -1; // we haven't gotten a reply yet // update network stats table i = cls.netchan.outgoing_sequence&NETWORK_STATS_MASK; network_stats[i].delta = 0; // filled-in later network_stats[i].sentsize = 0; // filled-in later network_stats[i].senttime = cls.realtime; network_stats[i].receivedtime = -1; // get basic movement from keyboard CL_BaseMove (cmd); // allow mice or other external controllers to add to the move if (cl_independentPhysics.value == 0 || (physframe && cl_independentPhysics.value != 0)) { IN_Move(cmd); } // if we are spectator, try autocam if (cl.spectator) Cam_Track(cmd); CL_FinishMove(cmd); cmdtime_msec += cmd->msec; Cam_FinishMove(cmd); if (cls.mvdplayback) { CL_CalcPlayerFPS(&cl.players[cl.playernum], cmd->msec); cls.netchan.outgoing_sequence++; return; } SZ_Init (&buf, data, sizeof(data)); SZ_Write (&buf, cls.cmdmsg.data, cls.cmdmsg.cursize); if (cls.cmdmsg.overflowed) Com_DPrintf("cls.cmdmsg overflowed\n"); SZ_Clear (&cls.cmdmsg); // begin a client move command MSG_WriteByte (&buf, clc_move); // save the position for a checksum byte checksumIndex = buf.cursize; MSG_WriteByte (&buf, 0); // write our lossage percentage lost = CL_CalcNet(); MSG_WriteByte (&buf, (byte)lost); // send this and the previous two cmds in the message, so if the last packet was dropped, it can be recovered dontdrop = false; i = (cls.netchan.outgoing_sequence - 2) & UPDATE_MASK; cmd = &cl.frames[i].cmd; if (cl_c2sImpulseBackup.value >= 2) dontdrop = dontdrop || cmd->impulse; MSG_WriteDeltaUsercmd (&buf, &nullcmd, cmd); oldcmd = cmd; i = (cls.netchan.outgoing_sequence - 1) & UPDATE_MASK; cmd = &cl.frames[i].cmd; if (cl_c2sImpulseBackup.value >= 3) dontdrop = dontdrop || cmd->impulse; MSG_WriteDeltaUsercmd (&buf, oldcmd, cmd); oldcmd = cmd; i = (cls.netchan.outgoing_sequence) & UPDATE_MASK; cmd = &cl.frames[i].cmd; if (cl_c2sImpulseBackup.value >= 1) dontdrop = dontdrop || cmd->impulse; MSG_WriteDeltaUsercmd (&buf, oldcmd, cmd); // calculate a checksum over the move commands buf.data[checksumIndex] = COM_BlockSequenceCRCByte( buf.data + checksumIndex + 1, buf.cursize - checksumIndex - 1, cls.netchan.outgoing_sequence); // request delta compression of entities if (cls.netchan.outgoing_sequence - cl.validsequence >= UPDATE_BACKUP - 1) { cl.validsequence = 0; cl.delta_sequence = 0; } if (cl.delta_sequence && !cl_nodelta.value && cls.state == ca_active) { cl.frames[cls.netchan.outgoing_sequence & UPDATE_MASK].delta_sequence = cl.delta_sequence; MSG_WriteByte (&buf, clc_delta); MSG_WriteByte (&buf, cl.delta_sequence & 255); // network stats table network_stats[cls.netchan.outgoing_sequence&NETWORK_STATS_MASK].delta = 1; } else { cl.frames[cls.netchan.outgoing_sequence & UPDATE_MASK].delta_sequence = -1; } if (cls.demorecording) CL_WriteDemoCmd (cmd); if (cl_c2spps.value) { pps_balance += cls.frametime; // never drop more than 2 messages in a row -- that'll cause PL // and don't drop if one of the last two movemessages have an impulse if (pps_balance > 0 || dropcount >= 2 || dontdrop) { float pps; pps = cl_c2spps.value; if (pps < 10) pps = 10; if (pps > 72) pps = 72; pps_balance -= 1 / pps; // bound pps_balance. FIXME: is there a better way? if (pps_balance > 0.1) pps_balance = 0.1; if (pps_balance < -0.1) pps_balance = -0.1; dropcount = 0; } else { // don't count this message when calculating PL cl.frames[i].receivedtime = -3; // drop this message cls.netchan.outgoing_sequence++; dropcount++; return; } } else { pps_balance = 0; dropcount = 0; } #ifdef FTE_PEXT2_VOICECHAT S_Voip_Transmit(clc_voicechat, &buf); #endif cl.frames[cls.netchan.outgoing_sequence&UPDATE_MASK].sentsize = buf.cursize + 8; // 8 = PACKET_HEADER // network stats table network_stats[cls.netchan.outgoing_sequence&NETWORK_STATS_MASK].sentsize = buf.cursize + 8; // deliver the message Netchan_Transmit (&cls.netchan, buf.cursize, buf.data); }
/* =================== CL_WritePacket Create and send the command packet to the server Including both the reliable commands and the usercmds During normal gameplay, a client packet will contain something like: 4 sequence number 2 qport 4 serverid 4 acknowledged sequence number 4 clc.serverCommandSequence <optional reliable commands> 1 clc_move or clc_moveNoDelta 1 command count <count * usercmds> =================== */ void CL_WritePacket( void ) { msg_t buf; byte data[MAX_MSGLEN]; int i, j; usercmd_t *cmd, *oldcmd; usercmd_t nullcmd; int packetNum; int oldPacketNum; int count; // don't send anything if playing back a demo // if ( cls.state == CA_CINEMATIC ) if ( cls.state == CA_CINEMATIC || CL_IsRunningInGameCinematic()) { return; } MSG_Init( &buf, data, sizeof(data) ); // write any unacknowledged clientCommands for ( i = clc.reliableAcknowledge + 1 ; i <= clc.reliableSequence ; i++ ) { MSG_WriteByte( &buf, clc_clientCommand ); MSG_WriteLong( &buf, i ); MSG_WriteString( &buf, clc.reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] ); } // we want to send all the usercmds that were generated in the last // few packet, so even if a couple packets are dropped in a row, // all the cmds will make it to the server if ( cl_packetdup->integer < 0 ) { Cvar_Set( "cl_packetdup", "0" ); } else if ( cl_packetdup->integer > 5 ) { Cvar_Set( "cl_packetdup", "5" ); } oldPacketNum = (clc.netchan.outgoingSequence - 1 - cl_packetdup->integer) & PACKET_MASK; count = cl.cmdNumber - cl.packetCmdNumber[ oldPacketNum ]; if ( count > MAX_PACKET_USERCMDS ) { count = MAX_PACKET_USERCMDS; Com_Printf("MAX_PACKET_USERCMDS\n"); } if ( count >= 1 ) { // begin a client move command MSG_WriteByte (&buf, clc_move); // write the last reliable message we received MSG_WriteLong( &buf, clc.serverCommandSequence ); // write the current serverId so the server // can tell if this is from the current gameState MSG_WriteLong (&buf, cl.serverId); // write the current time MSG_WriteLong (&buf, cls.realtime); // let the server know what the last messagenum we // got was, so the next message can be delta compressed // FIXME: this could just be a bit flag, with the message implicit // from the unreliable ack of the netchan if (cl_nodelta->integer || !cl.frame.valid) { MSG_WriteLong (&buf, -1); // no compression } else { MSG_WriteLong (&buf, cl.frame.messageNum); } // write the cmdNumber so the server can determine which ones it // has already received MSG_WriteLong( &buf, cl.cmdNumber ); // write the command count MSG_WriteByte( &buf, count ); // write all the commands, including the predicted command memset( &nullcmd, 0, sizeof(nullcmd) ); oldcmd = &nullcmd; for ( i = 0 ; i < count ; i++ ) { j = (cl.cmdNumber - count + i + 1) & CMD_MASK; cmd = &cl.cmds[j]; MSG_WriteDeltaUsercmd (&buf, oldcmd, cmd); oldcmd = cmd; } } // // deliver the message // packetNum = clc.netchan.outgoingSequence & PACKET_MASK; cl.packetTime[ packetNum ] = cls.realtime; cl.packetCmdNumber[ packetNum ] = cl.cmdNumber; clc.lastPacketSentTime = cls.realtime; Netchan_Transmit (&clc.netchan, buf.cursize, buf.data); }
/* ================= CL_SendCmd ================= */ void CL_SendCmd (void) { sizebuf_t buf; byte data[128]; int i; usercmd_t *cmd, *oldcmd; usercmd_t nullcmd; int checksumIndex; // clear buffer memset (&buf, 0, sizeof(buf)); // build a command even if not connected // save this command off for prediction i = cls.netchan.outgoing_sequence & (CMD_BACKUP-1); cmd = &cl.cmds[i]; cl.cmd_time[i] = cls.realtime; // for netgraph ping calculation *cmd = CL_CreateCmd (); cl.cmd = *cmd; if (cls.state == ca_disconnected || cls.state == ca_connecting) return; if (cls.state == ca_connected) { if (cls.netchan.message.cursize || curtime - cls.netchan.last_sent > 1000 ) Netchan_Transmit (&cls.netchan, 0, buf.data); return; } // send a userinfo update if needed if (userinfo_modified) { CL_FixUpGender(); userinfo_modified = false; MSG_WriteByte (&cls.netchan.message, clc_userinfo); MSG_WriteString (&cls.netchan.message, Cvar_Userinfo() ); } SZ_Init (&buf, data, sizeof(data)); // Knightmare- removed this, put ESC-only substitute in keys.c /*if (cmd->buttons && cl.cinematictime > 0 && !cl.attractloop && cls.realtime - cl.cinematictime > 1000) { // skip the rest of the cinematic SCR_FinishCinematic (); }*/ // begin a client move command MSG_WriteByte (&buf, clc_move); // save the position for a checksum byte checksumIndex = buf.cursize; MSG_WriteByte (&buf, 0); // let the server know what the last frame we // got was, so the next message can be delta compressed if (cl_nodelta->value || !cl.frame.valid || cls.demowaiting) MSG_WriteLong (&buf, -1); // no compression else MSG_WriteLong (&buf, cl.frame.serverframe); // send this and the previous cmds in the message, so // if the last packet was dropped, it can be recovered i = (cls.netchan.outgoing_sequence-2) & (CMD_BACKUP-1); cmd = &cl.cmds[i]; memset (&nullcmd, 0, sizeof(nullcmd)); MSG_WriteDeltaUsercmd (&buf, &nullcmd, cmd); oldcmd = cmd; i = (cls.netchan.outgoing_sequence-1) & (CMD_BACKUP-1); cmd = &cl.cmds[i]; MSG_WriteDeltaUsercmd (&buf, oldcmd, cmd); oldcmd = cmd; i = (cls.netchan.outgoing_sequence) & (CMD_BACKUP-1); cmd = &cl.cmds[i]; MSG_WriteDeltaUsercmd (&buf, oldcmd, cmd); // calculate a checksum over the move commands buf.data[checksumIndex] = COM_BlockSequenceCRCByte( buf.data + checksumIndex + 1, buf.cursize - checksumIndex - 1, cls.netchan.outgoing_sequence); // // deliver the message // Netchan_Transmit (&cls.netchan, buf.cursize, buf.data); }
/* ================= CL_SendDefaultCmd ================= */ static void CL_SendDefaultCmd( void ) { size_t cursize, checksumIndex; usercmd_t *cmd, *oldcmd; client_history_t *history; // archive this packet history = &cl.history[cls.netchan->outgoing_sequence & CMD_MASK]; history->cmdNumber = cl.cmdNumber; history->sent = cls.realtime; // for ping calculation history->rcvd = 0; cl.lastTransmitCmdNumber = cl.cmdNumber; // see if we are ready to send this packet if( !ready_to_send_hacked() ) { cls.netchan->outgoing_sequence++; // just drop the packet return; } cl.lastTransmitTime = cls.realtime; cl.lastTransmitCmdNumberReal = cl.cmdNumber; // begin a client move command MSG_WriteByte( clc_move ); // save the position for a checksum byte checksumIndex = 0; if( cls.serverProtocol <= PROTOCOL_VERSION_DEFAULT ) { checksumIndex = msg_write.cursize; SZ_GetSpace( &msg_write, 1 ); } // let the server know what the last frame we // got was, so the next message can be delta compressed if( cl_nodelta->integer || !cl.frame.valid /*|| cls.demowaiting*/ ) { MSG_WriteLong( -1 ); // no compression } else { MSG_WriteLong( cl.frame.number ); } // send this and the previous cmds in the message, so // if the last packet was dropped, it can be recovered cmd = &cl.cmds[( cl.cmdNumber - 2 ) & CMD_MASK]; MSG_WriteDeltaUsercmd( NULL, cmd, cls.protocolVersion ); MSG_WriteByte( cl.lightlevel ); oldcmd = cmd; cmd = &cl.cmds[( cl.cmdNumber - 1 ) & CMD_MASK]; MSG_WriteDeltaUsercmd( oldcmd, cmd, cls.protocolVersion ); MSG_WriteByte( cl.lightlevel ); oldcmd = cmd; cmd = &cl.cmds[cl.cmdNumber & CMD_MASK]; MSG_WriteDeltaUsercmd( oldcmd, cmd, cls.protocolVersion ); MSG_WriteByte( cl.lightlevel ); if( cls.serverProtocol <= PROTOCOL_VERSION_DEFAULT ) { // calculate a checksum over the move commands msg_write.data[checksumIndex] = COM_BlockSequenceCRCByte( msg_write.data + checksumIndex + 1, msg_write.cursize - checksumIndex - 1, cls.netchan->outgoing_sequence ); } P_FRAMES++; // // deliver the message // cursize = cls.netchan->Transmit( cls.netchan, msg_write.cursize, msg_write.data, 1 ); #ifdef _DEBUG if( cl_showpackets->integer ) { Com_Printf( "%"PRIz" ", cursize ); } #endif SZ_Clear( &msg_write ); }
/* ================= CL_SendCmd ================= */ void CL_SendCmd (void) { sizebuf_t buf; byte data[128]; int i; usercmd_t *cmd, *oldcmd; int checksumIndex; int lost; int seq_hash; if (cls.demoplayback) return; // sendcmds come from the demo // save this command off for prediction i = cls.netchan.outgoing_sequence & UPDATE_MASK; cmd = &cl.frames[i].cmd; cl.frames[i].senttime = realtime; cl.frames[i].receivedtime = -1; // we haven't gotten a reply yet // seq_hash = (cls.netchan.outgoing_sequence & 0xffff) ; // ^ QW_CHECK_HASH; seq_hash = cls.netchan.outgoing_sequence; // get basic movement from keyboard CL_BaseMove (cmd); // allow mice or other external controllers to add to the move IN_Move (cmd); // if we are spectator, try autocam if (cl.spectator) Cam_Track(cmd); CL_FinishMove(cmd); Cam_FinishMove(cmd); // send this and the previous cmds in the message, so // if the last packet was dropped, it can be recovered buf.maxsize = 128; buf.cursize = 0; buf.data = data; MSG_WriteByte (&buf, clc_move); // save the position for a checksum byte checksumIndex = buf.cursize; MSG_WriteByte (&buf, 0); // write our lossage percentage lost = CL_CalcNet(); MSG_WriteByte (&buf, (byte)lost); i = (cls.netchan.outgoing_sequence-2) & UPDATE_MASK; cmd = &cl.frames[i].cmd; MSG_WriteDeltaUsercmd (&buf, &nullcmd, cmd); oldcmd = cmd; i = (cls.netchan.outgoing_sequence-1) & UPDATE_MASK; cmd = &cl.frames[i].cmd; MSG_WriteDeltaUsercmd (&buf, oldcmd, cmd); oldcmd = cmd; i = (cls.netchan.outgoing_sequence) & UPDATE_MASK; cmd = &cl.frames[i].cmd; MSG_WriteDeltaUsercmd (&buf, oldcmd, cmd); // calculate a checksum over the move commands buf.data[checksumIndex] = COM_BlockSequenceCRCByte( buf.data + checksumIndex + 1, buf.cursize - checksumIndex - 1, seq_hash); // request delta compression of entities if (cls.netchan.outgoing_sequence - cl.validsequence >= UPDATE_BACKUP-1) cl.validsequence = 0; if (cl.validsequence && !cl_nodelta.value && cls.state == ca_active && !cls.demorecording) { cl.frames[cls.netchan.outgoing_sequence&UPDATE_MASK].delta_sequence = cl.validsequence; MSG_WriteByte (&buf, clc_delta); MSG_WriteByte (&buf, cl.validsequence&255); } else cl.frames[cls.netchan.outgoing_sequence&UPDATE_MASK].delta_sequence = -1; if (cls.demorecording) CL_WriteDemoCmd(cmd); // // deliver the message // Netchan_Transmit (&cls.netchan, buf.cursize, buf.data); }
void CL_SendCmd(void) { sizebuf_t buf; byte data[128]; int i; usercmd_t *cmd, *oldcmd; usercmd_t nullcmd; int checksumIndex; memset(&buf, 0, sizeof(buf)); /* save this command off for prediction */ i = cls.netchan.outgoing_sequence & (CMD_BACKUP - 1); cmd = &cl.cmds[i]; cl.cmd_time[i] = cls.realtime; /* for netgraph ping calculation */ CL_FinalizeCmd(); cl.cmd = *cmd; if ((cls.state == ca_disconnected) || (cls.state == ca_connecting)) { return; } if (cls.state == ca_connected) { if (cls.netchan.message.cursize || (curtime - cls.netchan.last_sent > 1000)) { Netchan_Transmit(&cls.netchan, 0, buf.data); } return; } /* send a userinfo update if needed */ if (userinfo_modified) { CL_FixUpGender(); userinfo_modified = false; MSG_WriteByte(&cls.netchan.message, clc_userinfo); MSG_WriteString(&cls.netchan.message, Cvar_Userinfo()); } SZ_Init(&buf, data, sizeof(data)); /* begin a client move command */ MSG_WriteByte(&buf, clc_move); /* save the position for a checksum byte */ checksumIndex = buf.cursize; MSG_WriteByte(&buf, 0); /* let the server know what the last frame we got was, so the next message can be delta compressed */ if (cl_nodelta->value || !cl.frame.valid) { MSG_WriteLong(&buf, -1); /* no compression */ } else { MSG_WriteLong(&buf, cl.frame.serverframe); } /* send this and the previous cmds in the message, so if the last packet was dropped, it can be recovered */ i = (cls.netchan.outgoing_sequence - 2) & (CMD_BACKUP - 1); cmd = &cl.cmds[i]; memset(&nullcmd, 0, sizeof(nullcmd)); MSG_WriteDeltaUsercmd(&buf, &nullcmd, cmd); oldcmd = cmd; i = (cls.netchan.outgoing_sequence - 1) & (CMD_BACKUP - 1); cmd = &cl.cmds[i]; MSG_WriteDeltaUsercmd(&buf, oldcmd, cmd); oldcmd = cmd; i = (cls.netchan.outgoing_sequence) & (CMD_BACKUP - 1); cmd = &cl.cmds[i]; MSG_WriteDeltaUsercmd(&buf, oldcmd, cmd); /* calculate a checksum over the move commands */ buf.data[checksumIndex] = COM_BlockSequenceCRCByte(buf.data + checksumIndex + 1, buf.cursize - checksumIndex - 1, cls.netchan.outgoing_sequence); /* deliver the message */ Netchan_Transmit(&cls.netchan, buf.cursize, buf.data); /* Reinit the current cmd buffer */ cmd = &cl.cmds[cls.netchan.outgoing_sequence & (CMD_BACKUP - 1)]; memset(cmd, 0, sizeof(*cmd)); }
/* =================== CL_WritePacket Create and send the command packet to the server Including both the reliable commands and the usercmds A client packet will contain something like: 4 sequence number 2 qport 4 serverid 4 acknowledged sequence number 4 clc.serverCommandSequence <optional reliable commands> 1 clc_move or clc_moveNoDelta 1 command count <count * usercmds> =================== */ void CL_WritePacket() { msg_t buf; byte data[ MAX_MSGLEN ]; int i, j; usercmd_t *cmd, *oldcmd; usercmd_t nullcmd; int packetNum; int oldPacketNum; int count; // don't send anything if playing back a demo if ( clc.demoplaying || cls.state == CA_CINEMATIC ) { return; } memset( &nullcmd, 0, sizeof( nullcmd ) ); oldcmd = &nullcmd; MSG_Init( &buf, data, sizeof( data ) ); MSG_Bitstream( &buf ); // write the current serverId so the server // can tell if this is from the current gameState MSG_WriteLong( &buf, cl.serverId ); // write the last message we received, which can // be used for delta compression, and is also used // to tell if we dropped a gamestate MSG_WriteLong( &buf, clc.serverMessageSequence ); // write the last reliable message we received MSG_WriteLong( &buf, clc.serverCommandSequence ); // write any unacknowledged clientCommands // NOTE TTimo: if you verbose this, you will see that there are quite a few duplicates // typically several unacknowledged cp or userinfo commands stacked up for ( i = clc.reliableAcknowledge + 1; i <= clc.reliableSequence; i++ ) { MSG_WriteByte( &buf, clc_clientCommand ); MSG_WriteLong( &buf, i ); MSG_WriteString( &buf, clc.reliableCommands[ i & ( MAX_RELIABLE_COMMANDS - 1 ) ] ); } // we want to send all the usercmds that were generated in the last // few packet, so even if a couple packets are dropped in a row, // all the cmds will make it to the server if ( cl_packetdup->integer < 0 ) { Cvar_Set( "cl_packetdup", "0" ); } else if ( cl_packetdup->integer > 5 ) { Cvar_Set( "cl_packetdup", "5" ); } oldPacketNum = ( clc.netchan.outgoingSequence - 1 - cl_packetdup->integer ) & PACKET_MASK; count = cl.cmdNumber - cl.outPackets[ oldPacketNum ].p_cmdNumber; if ( count > MAX_PACKET_USERCMDS ) { count = MAX_PACKET_USERCMDS; Com_Printf( "MAX_PACKET_USERCMDS" ); } #ifdef USE_VOIP if ( clc.voipOutgoingDataSize > 0 ) { if ( ( clc.voipFlags & VOIP_SPATIAL ) || Com_IsVoipTarget( clc.voipTargets, sizeof( clc.voipTargets ), -1 ) ) { MSG_WriteByte( &buf, clc_voip ); MSG_WriteByte( &buf, clc.voipOutgoingGeneration ); MSG_WriteLong( &buf, clc.voipOutgoingSequence ); MSG_WriteByte( &buf, clc.voipOutgoingDataFrames ); MSG_WriteData( &buf, clc.voipTargets, sizeof( clc.voipTargets ) ); MSG_WriteByte( &buf, clc.voipFlags ); MSG_WriteShort( &buf, clc.voipOutgoingDataSize ); MSG_WriteData( &buf, clc.voipOutgoingData, clc.voipOutgoingDataSize ); // If we're recording a demo, we have to fake a server packet with // this VoIP data so it gets to disk; the server doesn't send it // back to us, and we might as well eliminate concerns about dropped // and misordered packets here. if ( clc.demorecording && !clc.demowaiting ) { const int voipSize = clc.voipOutgoingDataSize; msg_t fakemsg; byte fakedata[ MAX_MSGLEN ]; MSG_Init( &fakemsg, fakedata, sizeof( fakedata ) ); MSG_Bitstream( &fakemsg ); MSG_WriteLong( &fakemsg, clc.reliableAcknowledge ); MSG_WriteByte( &fakemsg, svc_voip ); MSG_WriteShort( &fakemsg, clc.clientNum ); MSG_WriteByte( &fakemsg, clc.voipOutgoingGeneration ); MSG_WriteLong( &fakemsg, clc.voipOutgoingSequence ); MSG_WriteByte( &fakemsg, clc.voipOutgoingDataFrames ); MSG_WriteShort( &fakemsg, clc.voipOutgoingDataSize ); MSG_WriteBits( &fakemsg, clc.voipFlags, VOIP_FLAGCNT ); MSG_WriteData( &fakemsg, clc.voipOutgoingData, voipSize ); MSG_WriteByte( &fakemsg, svc_EOF ); CL_WriteDemoMessage( &fakemsg, 0 ); } clc.voipOutgoingSequence += clc.voipOutgoingDataFrames; clc.voipOutgoingDataSize = 0; clc.voipOutgoingDataFrames = 0; } else { // We have data, but no targets. Silently discard all data clc.voipOutgoingDataSize = 0; clc.voipOutgoingDataFrames = 0; } } #endif if ( count >= 1 ) { if ( cl_showSend->integer ) { Com_Printf( "(%i)", count ); } // begin a client move command if ( cl_nodelta->integer || !cl.snap.valid || clc.demowaiting || clc.serverMessageSequence != cl.snap.messageNum ) { MSG_WriteByte( &buf, clc_moveNoDelta ); } else { MSG_WriteByte( &buf, clc_move ); } // write the command count MSG_WriteByte( &buf, count ); // write all the commands, including the predicted command for ( i = 0; i < count; i++ ) { j = ( cl.cmdNumber - count + i + 1 ) & CMD_MASK; cmd = &cl.cmds[ j ]; MSG_WriteDeltaUsercmd( &buf, oldcmd, cmd ); oldcmd = cmd; } } // // deliver the message // packetNum = clc.netchan.outgoingSequence & PACKET_MASK; cl.outPackets[ packetNum ].p_realtime = cls.realtime; cl.outPackets[ packetNum ].p_serverTime = oldcmd->serverTime; cl.outPackets[ packetNum ].p_cmdNumber = cl.cmdNumber; clc.lastPacketSentTime = cls.realtime; if ( cl_showSend->integer ) { Com_Printf("%i ", buf.cursize ); } MSG_WriteByte( &buf, clc_EOF ); CL_WriteBinaryMessage( &buf ); Netchan_Transmit( &clc.netchan, buf.cursize, buf.data ); // clients never really should have messages large enough // to fragment, but in case they do, fire them all off // at once // TTimo: this causes a packet burst, which is bad karma for winsock // added a WARNING message, we'll see if there are legit situations where this happens while ( clc.netchan.unsentFragments ) { if ( cl_showSend->integer ) { Com_Printf( "WARNING: unsent fragments (not supposed to happen!)" ); } Netchan_TransmitNextFragment( &clc.netchan ); } }