+ // don't choke the connection with packets (obey rate limit)
+ // it is important that this check be last, because it adds a new
+ // frame to the shownetgraph output and any cancelation after this
+ // will produce a nasty spike-like look to the netgraph
+ // we also still send if it is important
+ if (!NetConn_CanSend(cls.netcon) && !important)
+ return;
+ // try to round off the lastpackettime to a multiple of the packet interval
+ // (this causes it to emit packets at a steady beat)
+ if (packettime > 0)
+ cl.lastpackettime = floor(realtime / packettime) * packettime;
+ else
+ cl.lastpackettime = realtime;
+
+ buf.maxsize = sizeof(data);
+ buf.cursize = 0;
+ buf.data = data;
+
+ // send the movement message
+ // PROTOCOL_QUAKE clc_move = 16 bytes total
+ // PROTOCOL_QUAKEDP clc_move = 16 bytes total
+ // PROTOCOL_NEHAHRAMOVIE clc_move = 16 bytes total
+ // PROTOCOL_DARKPLACES1 clc_move = 19 bytes total
+ // PROTOCOL_DARKPLACES2 clc_move = 25 bytes total
+ // PROTOCOL_DARKPLACES3 clc_move = 25 bytes total
+ // PROTOCOL_DARKPLACES4 clc_move = 19 bytes total
+ // PROTOCOL_DARKPLACES5 clc_move = 19 bytes total
+ // PROTOCOL_DARKPLACES6 clc_move = 52 bytes total
+ // PROTOCOL_DARKPLACES7 clc_move = 56 bytes total per move (can be up to 16 moves)
+ // PROTOCOL_QUAKEWORLD clc_move = 34 bytes total (typically, but can reach 43 bytes, or even 49 bytes with roll)
+
+ // set prydon cursor info
+ CL_UpdatePrydonCursor();
+
+ if (cls.protocol == PROTOCOL_QUAKEWORLD || cls.signon == SIGNONS)
+ {
+ switch (cls.protocol)
+ {
+ case PROTOCOL_QUAKEWORLD:
+ MSG_WriteByte(&buf, qw_clc_move);
+ // save the position for a checksum byte
+ checksumindex = buf.cursize;
+ MSG_WriteByte(&buf, 0);
+ // packet loss percentage
+ for (j = 0, packetloss = 0;j < NETGRAPH_PACKETS;j++)
+ if (cls.netcon->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
+ packetloss++;
+ packetloss = packetloss * 100 / NETGRAPH_PACKETS;
+ MSG_WriteByte(&buf, packetloss);
+ // write most recent 3 moves
+ QW_MSG_WriteDeltaUsercmd(&buf, &nullcmd, &cl.movecmd[2]);
+ QW_MSG_WriteDeltaUsercmd(&buf, &cl.movecmd[2], &cl.movecmd[1]);
+ QW_MSG_WriteDeltaUsercmd(&buf, &cl.movecmd[1], &cl.cmd);
+ // calculate the checksum
+ buf.data[checksumindex] = COM_BlockSequenceCRCByteQW(buf.data + checksumindex + 1, buf.cursize - checksumindex - 1, cls.netcon->outgoing_unreliable_sequence);
+ // if delta compression history overflows, request no delta
+ if (cls.netcon->outgoing_unreliable_sequence - cl.qw_validsequence >= QW_UPDATE_BACKUP-1)
+ cl.qw_validsequence = 0;
+ // request delta compression if appropriate
+ if (cl.qw_validsequence && !cl_nodelta.integer && cls.state == ca_connected && !cls.demorecording)
+ {
+ cl.qw_deltasequence[cls.netcon->outgoing_unreliable_sequence & QW_UPDATE_MASK] = cl.qw_validsequence;
+ MSG_WriteByte(&buf, qw_clc_delta);
+ MSG_WriteByte(&buf, cl.qw_validsequence & 255);
+ }
+ else
+ cl.qw_deltasequence[cls.netcon->outgoing_unreliable_sequence & QW_UPDATE_MASK] = -1;
+ break;
+ case PROTOCOL_QUAKE:
+ case PROTOCOL_QUAKEDP:
+ case PROTOCOL_NEHAHRAMOVIE:
+ case PROTOCOL_NEHAHRABJP:
+ case PROTOCOL_NEHAHRABJP2:
+ case PROTOCOL_NEHAHRABJP3:
+ // 5 bytes
+ MSG_WriteByte (&buf, clc_move);
+ MSG_WriteFloat (&buf, cl.cmd.time); // last server packet time
+ // 3 bytes (6 bytes in proquake)
+ if (cls.proquake_servermod == 1) // MOD_PROQUAKE
+ {
+ for (i = 0;i < 3;i++)
+ MSG_WriteAngle16i (&buf, cl.cmd.viewangles[i]);
+ }
+ else
+ {
+ for (i = 0;i < 3;i++)
+ MSG_WriteAngle8i (&buf, cl.cmd.viewangles[i]);
+ }
+ // 6 bytes
+ MSG_WriteCoord16i (&buf, cl.cmd.forwardmove);
+ MSG_WriteCoord16i (&buf, cl.cmd.sidemove);
+ MSG_WriteCoord16i (&buf, cl.cmd.upmove);
+ // 2 bytes
+ MSG_WriteByte (&buf, cl.cmd.buttons);
+ MSG_WriteByte (&buf, cl.cmd.impulse);
+ break;
+ case PROTOCOL_DARKPLACES2:
+ case PROTOCOL_DARKPLACES3:
+ // 5 bytes
+ MSG_WriteByte (&buf, clc_move);
+ MSG_WriteFloat (&buf, cl.cmd.time); // last server packet time
+ // 12 bytes
+ for (i = 0;i < 3;i++)
+ MSG_WriteAngle32f (&buf, cl.cmd.viewangles[i]);
+ // 6 bytes
+ MSG_WriteCoord16i (&buf, cl.cmd.forwardmove);
+ MSG_WriteCoord16i (&buf, cl.cmd.sidemove);
+ MSG_WriteCoord16i (&buf, cl.cmd.upmove);
+ // 2 bytes
+ MSG_WriteByte (&buf, cl.cmd.buttons);
+ MSG_WriteByte (&buf, cl.cmd.impulse);
+ break;
+ case PROTOCOL_DARKPLACES1:
+ case PROTOCOL_DARKPLACES4:
+ case PROTOCOL_DARKPLACES5:
+ // 5 bytes
+ MSG_WriteByte (&buf, clc_move);
+ MSG_WriteFloat (&buf, cl.cmd.time); // last server packet time
+ // 6 bytes
+ for (i = 0;i < 3;i++)
+ MSG_WriteAngle16i (&buf, cl.cmd.viewangles[i]);
+ // 6 bytes
+ MSG_WriteCoord16i (&buf, cl.cmd.forwardmove);
+ MSG_WriteCoord16i (&buf, cl.cmd.sidemove);
+ MSG_WriteCoord16i (&buf, cl.cmd.upmove);
+ // 2 bytes
+ MSG_WriteByte (&buf, cl.cmd.buttons);
+ MSG_WriteByte (&buf, cl.cmd.impulse);
+ case PROTOCOL_DARKPLACES6:
+ case PROTOCOL_DARKPLACES7:
+ // set the maxusercmds variable to limit how many should be sent
+ maxusercmds = bound(1, cl_netrepeatinput.integer + 1, min(3, CL_MAX_USERCMDS));
+ // when movement prediction is off, there's not much point in repeating old input as it will just be ignored
+ if (!cl.cmd.predicted)
+ maxusercmds = 1;
+
+ // send the latest moves in order, the old ones will be
+ // ignored by the server harmlessly, however if the previous
+ // packets were lost these moves will be used
+ //
+ // this reduces packet loss impact on gameplay.
+ for (j = 0, cmd = &cl.movecmd[maxusercmds-1];j < maxusercmds;j++, cmd--)
+ {
+ // don't repeat any stale moves
+ if (cmd->sequence && cmd->sequence < cls.servermovesequence)
+ continue;
+ // 5/9 bytes
+ MSG_WriteByte (&buf, clc_move);
+ if (cls.protocol != PROTOCOL_DARKPLACES6)
+ MSG_WriteLong (&buf, cmd->predicted ? cmd->sequence : 0);
+ MSG_WriteFloat (&buf, cmd->time); // last server packet time
+ // 6 bytes
+ for (i = 0;i < 3;i++)
+ MSG_WriteAngle16i (&buf, cmd->viewangles[i]);
+ // 6 bytes
+ MSG_WriteCoord16i (&buf, cmd->forwardmove);
+ MSG_WriteCoord16i (&buf, cmd->sidemove);
+ MSG_WriteCoord16i (&buf, cmd->upmove);
+ // 5 bytes
+ MSG_WriteLong (&buf, cmd->buttons);
+ MSG_WriteByte (&buf, cmd->impulse);
+ // PRYDON_CLIENTCURSOR
+ // 30 bytes
+ MSG_WriteShort (&buf, (short)(cmd->cursor_screen[0] * 32767.0f));
+ MSG_WriteShort (&buf, (short)(cmd->cursor_screen[1] * 32767.0f));
+ MSG_WriteFloat (&buf, cmd->cursor_start[0]);
+ MSG_WriteFloat (&buf, cmd->cursor_start[1]);
+ MSG_WriteFloat (&buf, cmd->cursor_start[2]);
+ MSG_WriteFloat (&buf, cmd->cursor_impact[0]);
+ MSG_WriteFloat (&buf, cmd->cursor_impact[1]);
+ MSG_WriteFloat (&buf, cmd->cursor_impact[2]);
+ MSG_WriteShort (&buf, cmd->cursor_entitynumber);
+ }
+ break;
+ case PROTOCOL_UNKNOWN:
+ break;
+ }
+ }
+
+ if (cls.protocol != PROTOCOL_QUAKEWORLD && buf.cursize)
+ {
+ // ack entity frame numbers received since the last input was sent
+ // (redundent to improve handling of client->server packet loss)
+ // if cl_netrepeatinput is 1 and client framerate matches server
+ // framerate, this is 10 bytes, if client framerate is lower this
+ // will be more...
+ int i, j;
+ int oldsequence = cl.cmd.sequence - bound(1, cl_netrepeatinput.integer + 1, 3);
+ if (oldsequence < 1)
+ oldsequence = 1;
+ for (i = 0;i < LATESTFRAMENUMS;i++)
+ {
+ j = (cl.latestframenumsposition + i) % LATESTFRAMENUMS;
+ if (cl.latestsendnums[j] >= oldsequence)
+ {
+ if (developer_networkentities.integer >= 10)
+ Con_Printf("send clc_ackframe %i\n", cl.latestframenums[j]);
+ MSG_WriteByte(&buf, clc_ackframe);
+ MSG_WriteLong(&buf, cl.latestframenums[j]);
+ }
+ }
+ }
+
+ // PROTOCOL_DARKPLACES6 = 67 bytes per packet
+ // PROTOCOL_DARKPLACES7 = 71 bytes per packet
+
+ // acknowledge any recently received data blocks
+ for (i = 0;i < CL_MAX_DOWNLOADACKS && (cls.dp_downloadack[i].start || cls.dp_downloadack[i].size);i++)
+ {
+ MSG_WriteByte(&buf, clc_ackdownloaddata);
+ MSG_WriteLong(&buf, cls.dp_downloadack[i].start);
+ MSG_WriteShort(&buf, cls.dp_downloadack[i].size);
+ cls.dp_downloadack[i].start = 0;
+ cls.dp_downloadack[i].size = 0;
+ }
+
+ // send the reliable message (forwarded commands) if there is one
+ if (buf.cursize || cls.netcon->message.cursize)
+ NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol, max(20*(buf.cursize+40), cl_rate.integer), false);
+
+ if (quemove)
+ {
+ // update the cl.movecmd array which holds the most recent moves,
+ // because we now need a new slot for the next input
+ for (i = CL_MAX_USERCMDS - 1;i >= 1;i--)
+ cl.movecmd[i] = cl.movecmd[i-1];
+ cl.movecmd[0].msec = 0;
+ cl.movecmd[0].frametime = 0;
+ }
+
+ // clear button 'click' states
+ in_attack.state &= ~2;
+ in_jump.state &= ~2;
+ in_button3.state &= ~2;
+ in_button4.state &= ~2;
+ in_button5.state &= ~2;
+ in_button6.state &= ~2;
+ in_button7.state &= ~2;
+ in_button8.state &= ~2;
+ in_use.state &= ~2;
+ in_button9.state &= ~2;
+ in_button10.state &= ~2;
+ in_button11.state &= ~2;
+ in_button12.state &= ~2;
+ in_button13.state &= ~2;
+ in_button14.state &= ~2;
+ in_button15.state &= ~2;
+ in_button16.state &= ~2;
+ // clear impulse
+ in_impulse = 0;