Purple Martians
Technical Code Descriptions

Netgame - Client Control Change
Client's controls have changed Server receives cdat
Client's controls have changed Every frame, the client's controls are compared with their previous state. If they have changed: - an entry is put in the client's local game moves array (in case the change needs to be played back later) - a 'cdat' packet with the control change and frame number is sent to the server - the change is applied to the client's local game state
void mwPlayer::proc_player_input(void)
{
...
   if (loc[p].comp_move != comp_move_from_players_current_controls(p))   // player's controls have changed
   {
      mGameMoves.add_game_move(mLoop.frame_num, 5, p, loc[p].comp_move);	
	  if (cm == 4)   // in client mode, send cdat packet, and apply move directly to controls
	  {
         set_controls_from_comp_move(p, loc[p].comp_move);
         mNetgame.client_send_cdat(p);

void mwNetgame::client_send_cdat(int p)
{
   char data[1024] = {0}; int pos;
   mPacketBuffer.PacketName(data, pos, "cdat");
   mPacketBuffer.PacketPutInt1(data, pos, p);
   mPacketBuffer.PacketPutInt4(data, pos, mLoop.frame_num);
   mPacketBuffer.PacketPutInt1(data, pos, mPlayer.loc[p].comp_move);
   ClientSend(data, pos);
   mPlayer.loc[p].client_cdat_packets_tx++;
}
Server receives cdat When the server receives a 'cdat' packet from a client, the server compares the frame number of the client's control change to the oldest state the server has a copy of. If the cdat's frame number is earlier, it arrived too late to be applied and is discarded. Otherwise the client's control change is added to the server's game moves array. 'server_dirty_frame' is also set. This tells the server how far back it needs to rewind to apply late input.
void mwNetgame::server_proc_cdat_packet(int i)
{
   int p              = mPacketBuffer.PacketGetInt1(i);
   int cdat_frame_num = mPacketBuffer.PacketGetInt4(i);
   int cm             = mPacketBuffer.PacketGetInt1(i);
   double timestamp   = mPacketBuffer.rx_buf[i].timestamp;

   mPlayer.loc[p].client_cdat_packets_tx++;

   // calculate game_move_sync
   mPlayer.loc[p].server_game_move_sync = cdat_frame_num - mLoop.frame_num;

   // calculate game_move_dsync
   mPlayer.loc[p].game_move_dsync = ( (double) mPlayer.loc[p].server_game_move_sync * 0.025) + mLoop.frame_start_timestamp - timestamp;

   // add to average tally
   mTally_game_move_dsync_avg_last_sec[p].add_data(mPlayer.loc[p].game_move_dsync);

   // determine the cutoff frame for late cdats
   int of = mStateHistory[0].oldest_state_frame_num;

   if (cdat_frame_num < of)
   {
      mPlayer.syn[p].late_cdats++;
      mTally_late_cdats_last_sec[p].add_data(1); // add to tally
      mLog.addf(LOG_NET_cdat, p, "rx cdat p:%d fn:[%d] sync:[%d] late - droppped\n", p, cdat_frame_num, mPlayer.loc[p].server_game_move_sync);
   }
   else
   {
      mGameMoves.add_game_move(cdat_frame_num, 5, p, cm); // add to game_move array
      mLog.addf(LOG_NET_cdat, p, "rx cdat p:%d fn:[%d] sync:[%d] gmep:[%d] - entered\n", p, cdat_frame_num, mPlayer.loc[p].server_game_move_sync, mGameMoves.entry_pos);
      if (cdat_frame_num < server_dirty_frame) server_dirty_frame = cdat_frame_num;
   }
}