/* * Packet-loss-prevention code for DXX-Rebirth * This code will save the data field of a netgame packet to a queue. * Saving is only done for urgent packets. * Each PDATA packet will be ACK'd by the receiver. * If all receivers submitted their ACK packets to initial sender, the packet is removed from the queue. * If a player has not ACK'd a PDATA packet within a second, it will resend to him. * Timeout, disconnects, or changes in player count are handled as well. */ #include "noloss.h" struct pdata_noloss_store noloss_queue[NOLOSS_QUEUE_SIZE]; extern frame_info MySyncPack; extern int MaxXDataSize; extern int N_players; // Adds a packet to our queue // Should be called when an IMPORTANT frameinfo packet is created void noloss_add_packet_to_queue(int urgent, int pkt_num, char *data, ushort data_size) { int i; // Only proceed if this is a version-checked game if (Netgame.protocol_version != MULTI_PROTO_D2X_VER) return; // Only add urgent packets if (!urgent) return; for (i = 0; i < NOLOSS_QUEUE_SIZE; i++) { if (noloss_queue[i].used) continue; noloss_queue[i].used = 1; noloss_queue[i].pkt_initial_timestamp = GameTime; noloss_queue[i].pkt_timestamp = GameTime; noloss_queue[i].pkt_num = pkt_num; memset( &noloss_queue[i].player_ack, 0, sizeof(ubyte)*MAX_PLAYERS ); noloss_queue[i].N_players = N_players; memcpy( &noloss_queue[i].data[0], data, data_size ); noloss_queue[i].data_size = data_size; return; } con_printf(CON_DEBUG, "Noloss queue is full!\n"); } // Send the packet stored in queue list at index to given receiver_pnum // Called from inside noloss_process_queue() void noloss_send_queued_packet(int queue_index) { short_frame_info ShortSyncPack; int objnum = Players[Player_num].objnum; // Update Timestamp noloss_queue[queue_index].pkt_timestamp = GameTime; // Copy the multibuf data to MySyncPack memcpy( &MySyncPack.data[0],&noloss_queue[queue_index].data[0], noloss_queue[queue_index].data_size ); MySyncPack.data_size = noloss_queue[queue_index].data_size; // The following code HEAVILY borrows from network_do_frame() // Create a frameinfo packet if (Netgame.ShortPackets) { #ifdef WORDS_BIGENDIAN ubyte send_data[MAX_DATA_SIZE]; #endif int i; memset(&ShortSyncPack,0,sizeof(short_frame_info)); create_shortpos(&ShortSyncPack.thepos, Objects+objnum, 0); ShortSyncPack.type = PID_PDATA; ShortSyncPack.playernum = Player_num; ShortSyncPack.obj_render_type = Objects[objnum].render_type; ShortSyncPack.level_num = Current_level_num; ShortSyncPack.data_size = MySyncPack.data_size; memcpy (&ShortSyncPack.data[0],&MySyncPack.data[0],MySyncPack.data_size); MySyncPack.numpackets = INTEL_INT(noloss_queue[queue_index].pkt_num); ShortSyncPack.numpackets = MySyncPack.numpackets; #ifndef WORDS_BIGENDIAN for(i=0; ireceiver_pnum != Player_num) return; for (i = 0; i < NOLOSS_QUEUE_SIZE; i++) { if (gotack->pkt_num == noloss_queue[i].pkt_num) { noloss_queue[i].player_ack[gotack->sender_pnum] = 1; break; } } } // Init/Free the queue. Call at start and end of a game or level. void noloss_init_queue(void) { memset(&noloss_queue,0,sizeof(pdata_noloss_store)*NOLOSS_QUEUE_SIZE); } // The main queue-process function. // 1) Check if we can remove a packet from queue (all players positive or packet too old) // 2) Check if there are packets in queue which we need to re-send to player(s) (if packet is older than one second) void noloss_process_queue(void) { int i; for (i = 0; i < NOLOSS_QUEUE_SIZE; i++) { int j, resend = 0; if (!noloss_queue[i].used) continue; // If N_players differs (because of disconnet), we resend to all to make sure we we get the right ACK packets for the right players if (N_players != noloss_queue[i].N_players) { memset( noloss_queue[i].player_ack, 0, sizeof(ubyte)*8 ); noloss_queue[i].N_players = N_players; resend = 1; } else { // Check if at least one connected player has not ACK'd the packet for (j = 0; j < N_players; j++) { if (!noloss_queue[i].player_ack[j] && Players[j].connected && j != Player_num) { resend = 1; } } } // Check if we can remove a packet... if (!resend || ((noloss_queue[i].pkt_initial_timestamp + (F1_0*15) <= GameTime) || (GameTime < noloss_queue[i].pkt_initial_timestamp))) { memset(&noloss_queue[i],0,sizeof(pdata_noloss_store)); } // ... otherwise resend if a second has passed else if ((noloss_queue[i].pkt_timestamp + F1_0 <= GameTime) || (GameTime < noloss_queue[i].pkt_timestamp)) { con_printf(CON_DEBUG, "Re-Sending queued packet %i\n",i); noloss_send_queued_packet(i); } } }