1 // SONIC ROBO BLAST 2
2 //-----------------------------------------------------------------------------
3 // Copyright (C) 1993-1996 by id Software, Inc.
4 // Copyright (C) 1998-2000 by DooM Legacy Team.
5 // Copyright (C) 1999-2020 by Sonic Team Junior.
6 //
7 // This program is free software distributed under the
8 // terms of the GNU General Public License, version 2.
9 // See the 'LICENSE' file for more details.
10 //-----------------------------------------------------------------------------
11 /// \file d_net.c
12 /// \brief SRB2 network game communication and protocol, all OS independent parts.
13 //
14 /// Implement a Sliding window protocol without receiver window
15 /// (out of order reception)
16 /// This protocol uses a mix of "goback n" and "selective repeat" implementation
17 /// The NOTHING packet is sent when connection is idle to acknowledge packets
18
19 #include "doomdef.h"
20 #include "g_game.h"
21 #include "i_net.h"
22 #include "i_system.h"
23 #include "m_argv.h"
24 #include "d_net.h"
25 #include "w_wad.h"
26 #include "d_netfil.h"
27 #include "d_clisrv.h"
28 #include "z_zone.h"
29 #include "i_tcp.h"
30 #include "d_main.h" // srb2home
31
32 //
33 // NETWORKING
34 //
35 // gametic is the tic about to (or currently being) run
36 // Server:
37 // maketic is the tic that hasn't had control made for it yet
38 // nettics is the tic for each node
39 // firstticstosend is the lowest value of nettics
40 // Client:
41 // neededtic is the tic needed by the client to run the game
42 // firstticstosend is used to optimize a condition
43 // Normally maketic >= gametic > 0
44
45 #define FORCECLOSE 0x8000
46 tic_t connectiontimeout = (10*TICRATE);
47
48 /// \brief network packet
49 doomcom_t *doomcom = NULL;
50 /// \brief network packet data, points inside doomcom
51 doomdata_t *netbuffer = NULL;
52
53 #ifdef DEBUGFILE
54 FILE *debugfile = NULL; // put some net info in a file during the game
55 #endif
56
57 #define MAXREBOUND 8
58 static doomdata_t reboundstore[MAXREBOUND];
59 static INT16 reboundsize[MAXREBOUND];
60 static INT32 rebound_head, rebound_tail;
61
62 /// \brief bandwith of netgame
63 INT32 net_bandwidth;
64
65 /// \brief max length per packet
66 INT16 hardware_MAXPACKETLENGTH;
67
68 boolean (*I_NetGet)(void) = NULL;
69 void (*I_NetSend)(void) = NULL;
70 boolean (*I_NetCanSend)(void) = NULL;
71 boolean (*I_NetCanGet)(void) = NULL;
72 void (*I_NetCloseSocket)(void) = NULL;
73 void (*I_NetFreeNodenum)(INT32 nodenum) = NULL;
74 SINT8 (*I_NetMakeNodewPort)(const char *address, const char* port) = NULL;
75 boolean (*I_NetOpenSocket)(void) = NULL;
76 boolean (*I_Ban) (INT32 node) = NULL;
77 void (*I_ClearBans)(void) = NULL;
78 const char *(*I_GetNodeAddress) (INT32 node) = NULL;
79 const char *(*I_GetBanAddress) (size_t ban) = NULL;
80 const char *(*I_GetBanMask) (size_t ban) = NULL;
81 boolean (*I_SetBanAddress) (const char *address, const char *mask) = NULL;
82 boolean *bannednode = NULL;
83
84
85 // network stats
86 static tic_t statstarttic;
87 INT32 getbytes = 0;
88 INT64 sendbytes = 0;
89 static INT32 retransmit = 0, duppacket = 0;
90 static INT32 sendackpacket = 0, getackpacket = 0;
91 INT32 ticruned = 0, ticmiss = 0;
92
93 // globals
94 INT32 getbps, sendbps;
95 float lostpercent, duppercent, gamelostpercent;
96 INT32 packetheaderlength;
97
Net_GetNetStat(void)98 boolean Net_GetNetStat(void)
99 {
100 const tic_t t = I_GetTime();
101 static INT64 oldsendbyte = 0;
102 if (statstarttic+STATLENGTH <= t)
103 {
104 const tic_t df = t-statstarttic;
105 const INT64 newsendbyte = sendbytes - oldsendbyte;
106 sendbps = (INT32)(newsendbyte*TICRATE)/df;
107 getbps = (getbytes*TICRATE)/df;
108 if (sendackpacket)
109 lostpercent = 100.0f*(float)retransmit/(float)sendackpacket;
110 else
111 lostpercent = 0.0f;
112 if (getackpacket)
113 duppercent = 100.0f*(float)duppacket/(float)getackpacket;
114 else
115 duppercent = 0.0f;
116 if (ticruned)
117 gamelostpercent = 100.0f*(float)ticmiss/(float)ticruned;
118 else
119 gamelostpercent = 0.0f;
120
121 ticmiss = ticruned = 0;
122 oldsendbyte = sendbytes;
123 getbytes = 0;
124 sendackpacket = getackpacket = duppacket = retransmit = 0;
125 statstarttic = t;
126
127 return 1;
128 }
129 return 0;
130 }
131
132 // -----------------------------------------------------------------
133 // Some structs and functions for acknowledgement of packets
134 // -----------------------------------------------------------------
135 #define MAXACKPACKETS 96 // Minimum number of nodes (wat)
136 #define MAXACKTOSEND 96
137 #define URGENTFREESLOTNUM 10
138 #define ACKTOSENDTIMEOUT (TICRATE/11)
139
140 #ifndef NONET
141 typedef struct
142 {
143 UINT8 acknum;
144 UINT8 nextacknum;
145 UINT8 destinationnode; // The node to send the ack to
146 tic_t senttime; // The time when the ack was sent
147 UINT16 length; // The packet size
148 UINT16 resentnum; // The number of times the ack has been resent
149 union {
150 SINT8 raw[MAXPACKETLENGTH];
151 doomdata_t data;
152 } pak;
153 } ackpak_t;
154 #endif
155
156 typedef enum
157 {
158 NF_CLOSE = 1, // Flag is set when connection is closing
159 NF_TIMEOUT = 2, // Flag is set when the node got a timeout
160 } node_flags_t;
161
162 #ifndef NONET
163 // Table of packets that were not acknowleged can be resent (the sender window)
164 static ackpak_t ackpak[MAXACKPACKETS];
165 #endif
166
167 typedef struct
168 {
169 // ack return to send (like sliding window protocol)
170 UINT8 firstacktosend;
171
172 // when no consecutive packets are received we keep in mind what packets
173 // we already received in a queue
174 UINT8 acktosend_head;
175 UINT8 acktosend_tail;
176 UINT8 acktosend[MAXACKTOSEND];
177
178 // automatically send keep alive packet when not enough trafic
179 tic_t lasttimeacktosend_sent;
180 // detect connection lost
181 tic_t lasttimepacketreceived;
182
183 // flow control: do not send too many packets with ack
184 UINT8 remotefirstack;
185 UINT8 nextacknum;
186
187 UINT8 flags;
188 } node_t;
189
190 static node_t nodes[MAXNETNODES];
191 #define NODETIMEOUT 14
192
193 #ifndef NONET
194 // return <0 if a < b (mod 256)
195 // 0 if a = n (mod 256)
196 // >0 if a > b (mod 256)
197 // mnemonic: to use it compare to 0: cmpack(a,b)<0 is "a < b" ...
cmpack(UINT8 a,UINT8 b)198 FUNCMATH static INT32 cmpack(UINT8 a, UINT8 b)
199 {
200 register INT32 d = a - b;
201
202 if (d >= 127 || d < -128)
203 return -d;
204 return d;
205 }
206
207 /** Sets freeack to a free acknum and copies the netbuffer in the ackpak table
208 *
209 * \param freeack The address to store the free acknum at
210 * \param lowtimer ???
211 * \return True if a free acknum was found
212 */
GetFreeAcknum(UINT8 * freeack,boolean lowtimer)213 static boolean GetFreeAcknum(UINT8 *freeack, boolean lowtimer)
214 {
215 node_t *node = &nodes[doomcom->remotenode];
216 INT32 i, numfreeslot = 0;
217
218 if (cmpack((UINT8)((node->remotefirstack + MAXACKTOSEND) % 256), node->nextacknum) < 0)
219 {
220 DEBFILE(va("too fast %d %d\n",node->remotefirstack,node->nextacknum));
221 return false;
222 }
223
224 for (i = 0; i < MAXACKPACKETS; i++)
225 if (!ackpak[i].acknum)
226 {
227 // For low priority packets, make sure to let freeslots so urgent packets can be sent
228 if (netbuffer->packettype >= PT_CANFAIL)
229 {
230 numfreeslot++;
231 if (numfreeslot <= URGENTFREESLOTNUM)
232 continue;
233 }
234
235 ackpak[i].acknum = node->nextacknum;
236 ackpak[i].nextacknum = node->nextacknum;
237 node->nextacknum++;
238 if (!node->nextacknum)
239 node->nextacknum++;
240 ackpak[i].destinationnode = (UINT8)(node - nodes);
241 ackpak[i].length = doomcom->datalength;
242 if (lowtimer)
243 {
244 // Lowtime means can't be sent now so try it as soon as possible
245 ackpak[i].senttime = 0;
246 ackpak[i].resentnum = 1;
247 }
248 else
249 {
250 ackpak[i].senttime = I_GetTime();
251 ackpak[i].resentnum = 0;
252 }
253 M_Memcpy(ackpak[i].pak.raw, netbuffer, ackpak[i].length);
254
255 *freeack = ackpak[i].acknum;
256
257 sendackpacket++; // For stat
258
259 return true;
260 }
261 #ifdef PARANOIA
262 CONS_Debug(DBG_NETPLAY, "No more free ackpacket\n");
263 #endif
264 if (netbuffer->packettype < PT_CANFAIL)
265 I_Error("Connection lost\n");
266 return false;
267 }
268
269 /** Counts how many acks are free
270 *
271 * \param urgent True if the type of the packet meant to
272 * use an ack is lower than PT_CANFAIL
273 * If for some reason you don't want use it
274 * for any packet type in particular,
275 * just set to false
276 * \return The number of free acks
277 *
278 */
Net_GetFreeAcks(boolean urgent)279 INT32 Net_GetFreeAcks(boolean urgent)
280 {
281 INT32 i, numfreeslot = 0;
282 INT32 n = 0; // Number of free acks found
283
284 for (i = 0; i < MAXACKPACKETS; i++)
285 if (!ackpak[i].acknum)
286 {
287 // For low priority packets, make sure to let freeslots so urgent packets can be sent
288 if (!urgent)
289 {
290 numfreeslot++;
291 if (numfreeslot <= URGENTFREESLOTNUM)
292 continue;
293 }
294
295 n++;
296 }
297
298 return n;
299 }
300
301 // Get a ack to send in the queue of this node
GetAcktosend(INT32 node)302 static UINT8 GetAcktosend(INT32 node)
303 {
304 nodes[node].lasttimeacktosend_sent = I_GetTime();
305 return nodes[node].firstacktosend;
306 }
307
RemoveAck(INT32 i)308 static void RemoveAck(INT32 i)
309 {
310 INT32 node = ackpak[i].destinationnode;
311 DEBFILE(va("Remove ack %d\n",ackpak[i].acknum));
312 ackpak[i].acknum = 0;
313 if (nodes[node].flags & NF_CLOSE)
314 Net_CloseConnection(node);
315 }
316
317 // We have got a packet, proceed the ack request and ack return
Processackpak(void)318 static boolean Processackpak(void)
319 {
320 INT32 i;
321 boolean goodpacket = true;
322 node_t *node = &nodes[doomcom->remotenode];
323
324 // Received an ack return, so remove the ack in the list
325 if (netbuffer->ackreturn && cmpack(node->remotefirstack, netbuffer->ackreturn) < 0)
326 {
327 node->remotefirstack = netbuffer->ackreturn;
328 // Search the ackbuffer and free it
329 for (i = 0; i < MAXACKPACKETS; i++)
330 if (ackpak[i].acknum && ackpak[i].destinationnode == node - nodes
331 && cmpack(ackpak[i].acknum, netbuffer->ackreturn) <= 0)
332 {
333 RemoveAck(i);
334 }
335 }
336
337 // Received a packet with ack, queue it to send the ack back
338 if (netbuffer->ack)
339 {
340 UINT8 ack = netbuffer->ack;
341 getackpacket++;
342 if (cmpack(ack, node->firstacktosend) <= 0)
343 {
344 DEBFILE(va("Discard(1) ack %d (duplicated)\n", ack));
345 duppacket++;
346 goodpacket = false; // Discard packet (duplicate)
347 }
348 else
349 {
350 // Check if it is not already in the queue
351 for (i = node->acktosend_tail; i != node->acktosend_head; i = (i+1) % MAXACKTOSEND)
352 if (node->acktosend[i] == ack)
353 {
354 DEBFILE(va("Discard(2) ack %d (duplicated)\n", ack));
355 duppacket++;
356 goodpacket = false; // Discard packet (duplicate)
357 break;
358 }
359 if (goodpacket)
360 {
361 // Is a good packet so increment the acknowledge number,
362 // Then search for a "hole" in the queue
363 UINT8 nextfirstack = (UINT8)(node->firstacktosend + 1);
364 if (!nextfirstack)
365 nextfirstack = 1;
366
367 if (ack == nextfirstack)
368 {
369 UINT8 hm1; // head - 1
370 boolean change = true;
371
372 node->firstacktosend = nextfirstack++;
373 if (!nextfirstack)
374 nextfirstack = 1;
375 hm1 = (UINT8)((node->acktosend_head-1+MAXACKTOSEND) % MAXACKTOSEND);
376 while (change)
377 {
378 change = false;
379 for (i = node->acktosend_tail; i != node->acktosend_head;
380 i = (i+1) % MAXACKTOSEND)
381 {
382 if (cmpack(node->acktosend[i], nextfirstack) <= 0)
383 {
384 if (node->acktosend[i] == nextfirstack)
385 {
386 node->firstacktosend = nextfirstack++;
387 if (!nextfirstack)
388 nextfirstack = 1;
389 change = true;
390 }
391 if (i == node->acktosend_tail)
392 {
393 node->acktosend[node->acktosend_tail] = 0;
394 node->acktosend_tail = (UINT8)((i+1) % MAXACKTOSEND);
395 }
396 else if (i == hm1)
397 {
398 node->acktosend[hm1] = 0;
399 node->acktosend_head = hm1;
400 hm1 = (UINT8)((hm1-1+MAXACKTOSEND) % MAXACKTOSEND);
401 }
402 }
403 }
404 }
405 }
406 else // Out of order packet
407 {
408 // Don't increment firsacktosend, put it in asktosend queue
409 // Will be incremented when the nextfirstack comes (code above)
410 UINT8 newhead = (UINT8)((node->acktosend_head+1) % MAXACKTOSEND);
411 DEBFILE(va("out of order packet (%d expected)\n", nextfirstack));
412 if (newhead != node->acktosend_tail)
413 {
414 node->acktosend[node->acktosend_head] = ack;
415 node->acktosend_head = newhead;
416 }
417 else // Buffer full discard packet, sender will resend it
418 { // We can admit the packet but we will not detect the duplication after :(
419 DEBFILE("no more freeackret\n");
420 goodpacket = false;
421 }
422 }
423 }
424 }
425 }
426 return goodpacket;
427 }
428 #endif
429
430 // send special packet with only ack on it
Net_SendAcks(INT32 node)431 void Net_SendAcks(INT32 node)
432 {
433 #ifdef NONET
434 (void)node;
435 #else
436 netbuffer->packettype = PT_NOTHING;
437 M_Memcpy(netbuffer->u.textcmd, nodes[node].acktosend, MAXACKTOSEND);
438 HSendPacket(node, false, 0, MAXACKTOSEND);
439 #endif
440 }
441
442 #ifndef NONET
GotAcks(void)443 static void GotAcks(void)
444 {
445 INT32 i, j;
446
447 for (j = 0; j < MAXACKTOSEND; j++)
448 if (netbuffer->u.textcmd[j])
449 for (i = 0; i < MAXACKPACKETS; i++)
450 if (ackpak[i].acknum && ackpak[i].destinationnode == doomcom->remotenode)
451 {
452 if (ackpak[i].acknum == netbuffer->u.textcmd[j])
453 RemoveAck(i);
454 // nextacknum is first equal to acknum, then when receiving bigger ack
455 // there is big chance the packet is lost
456 // When resent, nextacknum = nodes[node].nextacknum
457 // will redo the same but with different value
458 else if (cmpack(ackpak[i].nextacknum, netbuffer->u.textcmd[j]) <= 0
459 && ackpak[i].senttime > 0)
460 {
461 ackpak[i].senttime--; // hurry up
462 }
463 }
464 }
465 #endif
466
Net_ConnectionTimeout(INT32 node)467 void Net_ConnectionTimeout(INT32 node)
468 {
469 // Don't timeout several times
470 if (nodes[node].flags & NF_TIMEOUT)
471 return;
472 nodes[node].flags |= NF_TIMEOUT;
473
474 // Send a very special packet to self (hack the reboundstore queue)
475 // Main code will handle it
476 reboundstore[rebound_head].packettype = PT_NODETIMEOUT;
477 reboundstore[rebound_head].ack = 0;
478 reboundstore[rebound_head].ackreturn = 0;
479 reboundstore[rebound_head].u.textcmd[0] = (UINT8)node;
480 reboundsize[rebound_head] = (INT16)(BASEPACKETSIZE + 1);
481 rebound_head = (rebound_head+1) % MAXREBOUND;
482
483 // Do not redo it quickly (if we do not close connection it is
484 // for a good reason!)
485 nodes[node].lasttimepacketreceived = I_GetTime();
486 }
487
488 // Resend the data if needed
Net_AckTicker(void)489 void Net_AckTicker(void)
490 {
491 #ifndef NONET
492 INT32 i;
493
494 for (i = 0; i < MAXACKPACKETS; i++)
495 {
496 const INT32 nodei = ackpak[i].destinationnode;
497 node_t *node = &nodes[nodei];
498 if (ackpak[i].acknum && ackpak[i].senttime + NODETIMEOUT < I_GetTime())
499 {
500 if (ackpak[i].resentnum > 20 && (node->flags & NF_CLOSE))
501 {
502 DEBFILE(va("ack %d sent 20 times so connection is supposed lost: node %d\n",
503 i, nodei));
504 Net_CloseConnection(nodei | FORCECLOSE);
505
506 ackpak[i].acknum = 0;
507 continue;
508 }
509 DEBFILE(va("Resend ack %d, %u<%d at %u\n", ackpak[i].acknum, ackpak[i].senttime,
510 NODETIMEOUT, I_GetTime()));
511 M_Memcpy(netbuffer, ackpak[i].pak.raw, ackpak[i].length);
512 ackpak[i].senttime = I_GetTime();
513 ackpak[i].resentnum++;
514 ackpak[i].nextacknum = node->nextacknum;
515 retransmit++; // For stat
516 HSendPacket((INT32)(node - nodes), false, ackpak[i].acknum,
517 (size_t)(ackpak[i].length - BASEPACKETSIZE));
518 }
519 }
520
521 for (i = 1; i < MAXNETNODES; i++)
522 {
523 // This is something like node open flag
524 if (nodes[i].firstacktosend)
525 {
526 // We haven't sent a packet for a long time
527 // Acknowledge packet if needed
528 if (nodes[i].lasttimeacktosend_sent + ACKTOSENDTIMEOUT < I_GetTime())
529 Net_SendAcks(i);
530
531 if (!(nodes[i].flags & NF_CLOSE)
532 && nodes[i].lasttimepacketreceived + connectiontimeout < I_GetTime())
533 {
534 Net_ConnectionTimeout(i);
535 }
536 }
537 }
538 #endif
539 }
540
541 // Remove last packet received ack before resending the ackreturn
542 // (the higher layer doesn't have room, or something else ....)
Net_UnAcknowledgePacket(INT32 node)543 void Net_UnAcknowledgePacket(INT32 node)
544 {
545 #ifdef NONET
546 (void)node;
547 #else
548 INT32 hm1 = (nodes[node].acktosend_head-1+MAXACKTOSEND) % MAXACKTOSEND;
549 DEBFILE(va("UnAcknowledge node %d\n", node));
550 if (!node)
551 return;
552 if (nodes[node].acktosend[hm1] == netbuffer->ack)
553 {
554 nodes[node].acktosend[hm1] = 0;
555 nodes[node].acktosend_head = (UINT8)hm1;
556 }
557 else if (nodes[node].firstacktosend == netbuffer->ack)
558 {
559 nodes[node].firstacktosend--;
560 if (!nodes[node].firstacktosend)
561 nodes[node].firstacktosend = UINT8_MAX;
562 }
563 else
564 {
565 while (nodes[node].firstacktosend != netbuffer->ack)
566 {
567 nodes[node].acktosend_tail = (UINT8)
568 ((nodes[node].acktosend_tail-1+MAXACKTOSEND) % MAXACKTOSEND);
569 nodes[node].acktosend[nodes[node].acktosend_tail] = nodes[node].firstacktosend;
570
571 nodes[node].firstacktosend--;
572 if (!nodes[node].firstacktosend)
573 nodes[node].firstacktosend = UINT8_MAX;
574 }
575 nodes[node].firstacktosend++;
576 if (!nodes[node].firstacktosend)
577 nodes[node].firstacktosend = 1;
578 }
579 #endif
580 }
581
582 #ifndef NONET
583 /** Checks if all acks have been received
584 *
585 * \return True if all acks have been received
586 *
587 */
Net_AllAcksReceived(void)588 static boolean Net_AllAcksReceived(void)
589 {
590 INT32 i;
591
592 for (i = 0; i < MAXACKPACKETS; i++)
593 if (ackpak[i].acknum)
594 return false;
595
596 return true;
597 }
598 #endif
599
600 /** Waits for all ackreturns
601 *
602 * \param timeout Timeout in seconds
603 *
604 */
Net_WaitAllAckReceived(UINT32 timeout)605 void Net_WaitAllAckReceived(UINT32 timeout)
606 {
607 #ifdef NONET
608 (void)timeout;
609 #else
610 tic_t tictac = I_GetTime();
611 timeout = tictac + timeout*NEWTICRATE;
612
613 HGetPacket();
614 while (timeout > I_GetTime() && !Net_AllAcksReceived())
615 {
616 while (tictac == I_GetTime())
617 I_Sleep();
618 tictac = I_GetTime();
619 HGetPacket();
620 Net_AckTicker();
621 }
622 #endif
623 }
624
InitNode(node_t * node)625 static void InitNode(node_t *node)
626 {
627 node->acktosend_head = node->acktosend_tail = 0;
628 node->firstacktosend = 0;
629 node->nextacknum = 1;
630 node->remotefirstack = 0;
631 node->flags = 0;
632 }
633
InitAck(void)634 static void InitAck(void)
635 {
636 INT32 i;
637
638 #ifndef NONET
639 for (i = 0; i < MAXACKPACKETS; i++)
640 ackpak[i].acknum = 0;
641 #endif
642
643 for (i = 0; i < MAXNETNODES; i++)
644 InitNode(&nodes[i]);
645 }
646
647 /** Removes all acks of a given packet type
648 *
649 * \param packettype The packet type to forget
650 *
651 */
Net_AbortPacketType(UINT8 packettype)652 void Net_AbortPacketType(UINT8 packettype)
653 {
654 #ifdef NONET
655 (void)packettype;
656 #else
657 INT32 i;
658 for (i = 0; i < MAXACKPACKETS; i++)
659 if (ackpak[i].acknum && (ackpak[i].pak.data.packettype == packettype
660 || packettype == UINT8_MAX))
661 {
662 ackpak[i].acknum = 0;
663 }
664 #endif
665 }
666
667 // -----------------------------------------------------------------
668 // end of acknowledge function
669 // -----------------------------------------------------------------
670
671 // remove a node, clear all ack from this node and reset askret
Net_CloseConnection(INT32 node)672 void Net_CloseConnection(INT32 node)
673 {
674 #ifdef NONET
675 (void)node;
676 #else
677 INT32 i;
678 boolean forceclose = (node & FORCECLOSE) != 0;
679
680 if (node == -1)
681 {
682 DEBFILE(M_GetText("Net_CloseConnection: node -1 detected!\n"));
683 return; // nope, just ignore it
684 }
685
686 node &= ~FORCECLOSE;
687
688 if (!node)
689 return;
690
691 if (node < 0 || node >= MAXNETNODES) // prevent invalid nodes from crashing the game
692 {
693 DEBFILE(va(M_GetText("Net_CloseConnection: invalid node %d detected!\n"), node));
694 return;
695 }
696
697 nodes[node].flags |= NF_CLOSE;
698
699 // try to Send ack back (two army problem)
700 if (GetAcktosend(node))
701 {
702 Net_SendAcks(node);
703 Net_SendAcks(node);
704 }
705
706 // check if we are waiting for an ack from this node
707 for (i = 0; i < MAXACKPACKETS; i++)
708 if (ackpak[i].acknum && ackpak[i].destinationnode == node)
709 {
710 if (!forceclose)
711 return; // connection will be closed when ack is returned
712 else
713 ackpak[i].acknum = 0;
714 }
715
716 InitNode(&nodes[node]);
717 SV_AbortSendFiles(node);
718 if (server)
719 SV_AbortLuaFileTransfer(node);
720 I_NetFreeNodenum(node);
721 #endif
722 }
723
724 #ifndef NONET
725 //
726 // Checksum
727 //
NetbufferChecksum(void)728 static UINT32 NetbufferChecksum(void)
729 {
730 UINT32 c = 0x1234567;
731 const INT32 l = doomcom->datalength - 4;
732 const UINT8 *buf = (UINT8 *)netbuffer + 4;
733 INT32 i;
734
735 for (i = 0; i < l; i++, buf++)
736 c += (*buf) * (i+1);
737
738 return LONG(c);
739 }
740 #endif
741
742 #ifdef DEBUGFILE
743
fprintfstring(char * s,size_t len)744 static void fprintfstring(char *s, size_t len)
745 {
746 INT32 mode = 0;
747 size_t i;
748
749 for (i = 0; i < len; i++)
750 if (s[i] < 32)
751 {
752 if (!mode)
753 {
754 fprintf(debugfile, "[%d", (UINT8)s[i]);
755 mode = 1;
756 }
757 else
758 fprintf(debugfile, ",%d", (UINT8)s[i]);
759 }
760 else
761 {
762 if (mode)
763 {
764 fprintf(debugfile, "]");
765 mode = 0;
766 }
767 fprintf(debugfile, "%c", s[i]);
768 }
769 if (mode)
770 fprintf(debugfile, "]");
771 }
772
fprintfstringnewline(char * s,size_t len)773 static void fprintfstringnewline(char *s, size_t len)
774 {
775 fprintfstring(s, len);
776 fprintf(debugfile, "\n");
777 }
778
779 /// \warning Keep this up-to-date if you add/remove/rename packet types
780 static const char *packettypename[NUMPACKETTYPE] =
781 {
782 "NOTHING",
783 "SERVERCFG",
784 "CLIENTCMD",
785 "CLIENTMIS",
786 "CLIENT2CMD",
787 "CLIENT2MIS",
788 "NODEKEEPALIVE",
789 "NODEKEEPALIVEMIS",
790 "SERVERTICS",
791 "SERVERREFUSE",
792 "SERVERSHUTDOWN",
793 "CLIENTQUIT",
794
795 "ASKINFO",
796 "SERVERINFO",
797 "PLAYERINFO",
798 "REQUESTFILE",
799 "ASKINFOVIAMS",
800
801 "WILLRESENDGAMESTATE",
802 "CANRECEIVEGAMESTATE",
803 "RECEIVEDGAMESTATE",
804
805 "SENDINGLUAFILE",
806 "ASKLUAFILE",
807 "HASLUAFILE",
808
809 "FILEFRAGMENT",
810 "FILEACK",
811 "FILERECEIVED",
812
813 "TEXTCMD",
814 "TEXTCMD2",
815 "CLIENTJOIN",
816 "NODETIMEOUT",
817 "LOGIN",
818 "PING"
819 };
820
DebugPrintpacket(const char * header)821 static void DebugPrintpacket(const char *header)
822 {
823 fprintf(debugfile, "%-12s (node %d,ack %d,ackret %d,size %d) type(%d) : %s\n",
824 header, doomcom->remotenode, netbuffer->ack, netbuffer->ackreturn, doomcom->datalength,
825 netbuffer->packettype, packettypename[netbuffer->packettype]);
826
827 switch (netbuffer->packettype)
828 {
829 case PT_ASKINFO:
830 case PT_ASKINFOVIAMS:
831 fprintf(debugfile, " time %u\n", (tic_t)LONG(netbuffer->u.askinfo.time));
832 break;
833 case PT_CLIENTJOIN:
834 fprintf(debugfile, " number %d mode %d\n", netbuffer->u.clientcfg.localplayers,
835 netbuffer->u.clientcfg.mode);
836 break;
837 case PT_SERVERTICS:
838 {
839 servertics_pak *serverpak = &netbuffer->u.serverpak;
840 UINT8 *cmd = (UINT8 *)(&serverpak->cmds[serverpak->numslots * serverpak->numtics]);
841 size_t ntxtcmd = &((UINT8 *)netbuffer)[doomcom->datalength] - cmd;
842
843 fprintf(debugfile, " firsttic %u ply %d tics %d ntxtcmd %s\n ",
844 (UINT32)serverpak->starttic, serverpak->numslots, serverpak->numtics, sizeu1(ntxtcmd));
845 /// \todo Display more readable information about net commands
846 fprintfstringnewline((char *)cmd, ntxtcmd);
847 /*fprintfstring((char *)cmd, 3);
848 if (ntxtcmd > 4)
849 {
850 fprintf(debugfile, "[%s]", netxcmdnames[*((cmd) + 3) - 1]);
851 fprintfstring(((char *)cmd) + 4, ntxtcmd - 4);
852 }
853 fprintf(debugfile, "\n");*/
854 break;
855 }
856 case PT_CLIENTCMD:
857 case PT_CLIENT2CMD:
858 case PT_CLIENTMIS:
859 case PT_CLIENT2MIS:
860 case PT_NODEKEEPALIVE:
861 case PT_NODEKEEPALIVEMIS:
862 fprintf(debugfile, " tic %4u resendfrom %u\n",
863 (UINT32)ExpandTics(netbuffer->u.clientpak.client_tic, doomcom->remotenode),
864 (UINT32)ExpandTics (netbuffer->u.clientpak.resendfrom, doomcom->remotenode));
865 break;
866 case PT_TEXTCMD:
867 case PT_TEXTCMD2:
868 fprintf(debugfile, " length %d\n ", netbuffer->u.textcmd[0]);
869 fprintf(debugfile, "[%s]", netxcmdnames[netbuffer->u.textcmd[1] - 1]);
870 fprintfstringnewline((char *)netbuffer->u.textcmd + 2, netbuffer->u.textcmd[0] - 1);
871 break;
872 case PT_SERVERCFG:
873 fprintf(debugfile, " playerslots %d clientnode %d serverplayer %d "
874 "gametic %u gamestate %d gametype %d modifiedgame %d\n",
875 netbuffer->u.servercfg.totalslotnum, netbuffer->u.servercfg.clientnode,
876 netbuffer->u.servercfg.serverplayer, (UINT32)LONG(netbuffer->u.servercfg.gametic),
877 netbuffer->u.servercfg.gamestate, netbuffer->u.servercfg.gametype,
878 netbuffer->u.servercfg.modifiedgame);
879 break;
880 case PT_SERVERINFO:
881 fprintf(debugfile, " '%s' player %d/%d, map %s, filenum %d, time %u \n",
882 netbuffer->u.serverinfo.servername, netbuffer->u.serverinfo.numberofplayer,
883 netbuffer->u.serverinfo.maxplayer, netbuffer->u.serverinfo.mapname,
884 netbuffer->u.serverinfo.fileneedednum,
885 (UINT32)LONG(netbuffer->u.serverinfo.time));
886 fprintfstringnewline((char *)netbuffer->u.serverinfo.fileneeded,
887 (UINT8)((UINT8 *)netbuffer + doomcom->datalength
888 - (UINT8 *)netbuffer->u.serverinfo.fileneeded));
889 break;
890 case PT_SERVERREFUSE:
891 fprintf(debugfile, " reason %s\n", netbuffer->u.serverrefuse.reason);
892 break;
893 case PT_FILEFRAGMENT:
894 fprintf(debugfile, " fileid %d datasize %d position %u\n",
895 netbuffer->u.filetxpak.fileid, (UINT16)SHORT(netbuffer->u.filetxpak.size),
896 (UINT32)LONG(netbuffer->u.filetxpak.position));
897 break;
898 case PT_REQUESTFILE:
899 default: // write as a raw packet
900 fprintfstringnewline((char *)netbuffer->u.textcmd,
901 (UINT8)((UINT8 *)netbuffer + doomcom->datalength - (UINT8 *)netbuffer->u.textcmd));
902 break;
903 }
904 }
905 #endif
906
907 #ifdef PACKETDROP
908 static INT32 packetdropquantity[NUMPACKETTYPE] = {0};
909 static INT32 packetdroprate = 0;
910
Command_Drop(void)911 void Command_Drop(void)
912 {
913 INT32 packetquantity;
914 const char *packetname;
915 size_t i;
916
917 if (COM_Argc() < 2)
918 {
919 CONS_Printf("drop <packettype> [quantity]: drop packets\n"
920 "drop reset: cancel all packet drops\n");
921 return;
922 }
923
924 if (!(stricmp(COM_Argv(1), "reset") && stricmp(COM_Argv(1), "cancel") && stricmp(COM_Argv(1), "stop")))
925 {
926 memset(packetdropquantity, 0, sizeof(packetdropquantity));
927 return;
928 }
929
930 if (COM_Argc() >= 3)
931 {
932 packetquantity = atoi(COM_Argv(2));
933 if (packetquantity <= 0 && COM_Argv(2)[0] != '0')
934 {
935 CONS_Printf("Invalid quantity\n");
936 return;
937 }
938 }
939 else
940 packetquantity = -1;
941
942 packetname = COM_Argv(1);
943
944 if (!(stricmp(packetname, "all") && stricmp(packetname, "any")))
945 for (i = 0; i < NUMPACKETTYPE; i++)
946 packetdropquantity[i] = packetquantity;
947 else
948 {
949 for (i = 0; i < NUMPACKETTYPE; i++)
950 if (!stricmp(packetname, packettypename[i]))
951 {
952 packetdropquantity[i] = packetquantity;
953 return;
954 }
955
956 CONS_Printf("Unknown packet name\n");
957 }
958 }
959
Command_Droprate(void)960 void Command_Droprate(void)
961 {
962 INT32 droprate;
963
964 if (COM_Argc() < 2)
965 {
966 CONS_Printf("Packet drop rate: %d%%\n", packetdroprate);
967 return;
968 }
969
970 droprate = atoi(COM_Argv(1));
971 if ((droprate <= 0 && COM_Argv(1)[0] != '0') || droprate > 100)
972 {
973 CONS_Printf("Packet drop rate must be between 0 and 100!\n");
974 return;
975 }
976
977 packetdroprate = droprate;
978 }
979
980 #ifndef NONET
ShouldDropPacket(void)981 static boolean ShouldDropPacket(void)
982 {
983 return (packetdropquantity[netbuffer->packettype])
984 || (packetdroprate != 0 && rand() < (RAND_MAX * (packetdroprate / 100.f))) || packetdroprate == 100;
985 }
986 #endif
987 #endif
988
989 //
990 // HSendPacket
991 //
HSendPacket(INT32 node,boolean reliable,UINT8 acknum,size_t packetlength)992 boolean HSendPacket(INT32 node, boolean reliable, UINT8 acknum, size_t packetlength)
993 {
994 doomcom->datalength = (INT16)(packetlength + BASEPACKETSIZE);
995 if (node == 0) // Packet is to go back to us
996 {
997 if ((rebound_head+1) % MAXREBOUND == rebound_tail)
998 {
999 #ifdef PARANOIA
1000 CONS_Debug(DBG_NETPLAY, "No more rebound buf\n");
1001 #endif
1002 return false;
1003 }
1004 netbuffer->ack = netbuffer->ackreturn = 0; // don't hold over values from last packet sent/received
1005 M_Memcpy(&reboundstore[rebound_head], netbuffer,
1006 doomcom->datalength);
1007 reboundsize[rebound_head] = doomcom->datalength;
1008 rebound_head = (rebound_head+1) % MAXREBOUND;
1009 #ifdef DEBUGFILE
1010 if (debugfile)
1011 {
1012 doomcom->remotenode = (INT16)node;
1013 DebugPrintpacket("SENDLOCAL");
1014 }
1015 #endif
1016 return true;
1017 }
1018
1019 if (!netgame)
1020 I_Error("Tried to transmit to another node");
1021
1022 #ifdef NONET
1023 (void)node;
1024 (void)reliable;
1025 (void)acknum;
1026 #else
1027 // do this before GetFreeAcknum because this function backups
1028 // the current packet
1029 doomcom->remotenode = (INT16)node;
1030 if (doomcom->datalength <= 0)
1031 {
1032 DEBFILE("HSendPacket: nothing to send\n");
1033 #ifdef DEBUGFILE
1034 if (debugfile)
1035 DebugPrintpacket("TRISEND");
1036 #endif
1037 return false;
1038 }
1039
1040 if (node < MAXNETNODES) // Can be a broadcast
1041 netbuffer->ackreturn = GetAcktosend(node);
1042 else
1043 netbuffer->ackreturn = 0;
1044 if (reliable)
1045 {
1046 if (I_NetCanSend && !I_NetCanSend())
1047 {
1048 if (netbuffer->packettype < PT_CANFAIL)
1049 GetFreeAcknum(&netbuffer->ack, true);
1050
1051 DEBFILE("HSendPacket: Out of bandwidth\n");
1052 return false;
1053 }
1054 else if (!GetFreeAcknum(&netbuffer->ack, false))
1055 return false;
1056 }
1057 else
1058 netbuffer->ack = acknum;
1059
1060 netbuffer->checksum = NetbufferChecksum();
1061 sendbytes += packetheaderlength + doomcom->datalength; // For stat
1062
1063 #ifdef PACKETDROP
1064 // Simulate internet :)
1065 //if (rand() >= (INT32)(RAND_MAX * (PACKETLOSSRATE / 100.f)))
1066 if (!ShouldDropPacket())
1067 {
1068 #endif
1069 #ifdef DEBUGFILE
1070 if (debugfile)
1071 DebugPrintpacket("SENT");
1072 #endif
1073 I_NetSend();
1074 #ifdef PACKETDROP
1075 }
1076 else
1077 {
1078 if (packetdropquantity[netbuffer->packettype] > 0)
1079 packetdropquantity[netbuffer->packettype]--;
1080 #ifdef DEBUGFILE
1081 if (debugfile)
1082 DebugPrintpacket("NOT SENT");
1083 #endif
1084 }
1085 #endif
1086
1087 #endif // ndef NONET
1088
1089 return true;
1090 }
1091
1092 //
1093 // HGetPacket
1094 // Returns false if no packet is waiting
1095 // Check Datalength and checksum
1096 //
HGetPacket(void)1097 boolean HGetPacket(void)
1098 {
1099 //boolean nodejustjoined;
1100
1101 // Get a packet from self
1102 if (rebound_tail != rebound_head)
1103 {
1104 M_Memcpy(netbuffer, &reboundstore[rebound_tail], reboundsize[rebound_tail]);
1105 doomcom->datalength = reboundsize[rebound_tail];
1106 if (netbuffer->packettype == PT_NODETIMEOUT)
1107 doomcom->remotenode = netbuffer->u.textcmd[0];
1108 else
1109 doomcom->remotenode = 0;
1110
1111 rebound_tail = (rebound_tail+1) % MAXREBOUND;
1112 #ifdef DEBUGFILE
1113 if (debugfile)
1114 DebugPrintpacket("GETLOCAL");
1115 #endif
1116 return true;
1117 }
1118
1119 if (!netgame)
1120 return false;
1121
1122 #ifndef NONET
1123
1124 while(true)
1125 {
1126 //nodejustjoined = I_NetGet();
1127 I_NetGet();
1128
1129 if (doomcom->remotenode == -1) // No packet received
1130 return false;
1131
1132 getbytes += packetheaderlength + doomcom->datalength; // For stat
1133
1134 if (doomcom->remotenode >= MAXNETNODES)
1135 {
1136 DEBFILE(va("Received packet from node %d!\n", doomcom->remotenode));
1137 continue;
1138 }
1139
1140 nodes[doomcom->remotenode].lasttimepacketreceived = I_GetTime();
1141
1142 if (netbuffer->checksum != NetbufferChecksum())
1143 {
1144 DEBFILE("Bad packet checksum\n");
1145 //Net_CloseConnection(nodejustjoined ? (doomcom->remotenode | FORCECLOSE) : doomcom->remotenode);
1146 Net_CloseConnection(doomcom->remotenode);
1147 continue;
1148 }
1149
1150 #ifdef DEBUGFILE
1151 if (debugfile)
1152 DebugPrintpacket("GET");
1153 #endif
1154
1155 /*// If a new node sends an unexpected packet, just ignore it
1156 if (nodejustjoined && server
1157 && !(netbuffer->packettype == PT_ASKINFO
1158 || netbuffer->packettype == PT_SERVERINFO
1159 || netbuffer->packettype == PT_PLAYERINFO
1160 || netbuffer->packettype == PT_REQUESTFILE
1161 || netbuffer->packettype == PT_ASKINFOVIAMS
1162 || netbuffer->packettype == PT_CLIENTJOIN))
1163 {
1164 DEBFILE(va("New node sent an unexpected %s packet\n", packettypename[netbuffer->packettype]));
1165 //CONS_Alert(CONS_NOTICE, "New node sent an unexpected %s packet\n", packettypename[netbuffer->packettype]);
1166 Net_CloseConnection(doomcom->remotenode | FORCECLOSE);
1167 continue;
1168 }*/
1169
1170 // Proceed the ack and ackreturn field
1171 if (!Processackpak())
1172 continue; // discarded (duplicated)
1173
1174 // A packet with just ackreturn
1175 if (netbuffer->packettype == PT_NOTHING)
1176 {
1177 GotAcks();
1178 continue;
1179 }
1180 break;
1181 }
1182 #endif // ndef NONET
1183
1184 return true;
1185 }
1186
Internal_Get(void)1187 static boolean Internal_Get(void)
1188 {
1189 doomcom->remotenode = -1;
1190 return false;
1191 }
1192
Internal_Send(void)1193 FUNCNORETURN static ATTRNORETURN void Internal_Send(void)
1194 {
1195 I_Error("Send without netgame\n");
1196 }
1197
Internal_FreeNodenum(INT32 nodenum)1198 static void Internal_FreeNodenum(INT32 nodenum)
1199 {
1200 (void)nodenum;
1201 }
1202
I_NetMakeNode(const char * hostname)1203 SINT8 I_NetMakeNode(const char *hostname)
1204 {
1205 SINT8 newnode = -1;
1206 if (I_NetMakeNodewPort)
1207 {
1208 char *localhostname = strdup(hostname);
1209 char *t = localhostname;
1210 const char *port;
1211 if (!localhostname)
1212 return newnode;
1213 // retrieve portnum from address!
1214 strtok(localhostname, ":");
1215 port = strtok(NULL, ":");
1216
1217 // remove the port in the hostname as we've it already
1218 while ((*t != ':') && (*t != '\0'))
1219 t++;
1220 *t = '\0';
1221
1222 newnode = I_NetMakeNodewPort(localhostname, port);
1223 free(localhostname);
1224 }
1225 return newnode;
1226 }
1227
D_SetDoomcom(void)1228 void D_SetDoomcom(void)
1229 {
1230 if (doomcom) return;
1231 doomcom = Z_Calloc(sizeof (doomcom_t), PU_STATIC, NULL);
1232 doomcom->id = DOOMCOM_ID;
1233 doomcom->numslots = doomcom->numnodes = 1;
1234 doomcom->gametype = 0;
1235 doomcom->consoleplayer = 0;
1236 doomcom->extratics = 0;
1237 }
1238
1239 //
1240 // D_CheckNetGame
1241 // Works out player numbers among the net participants
1242 //
D_CheckNetGame(void)1243 boolean D_CheckNetGame(void)
1244 {
1245 boolean ret = false;
1246
1247 InitAck();
1248 rebound_tail = rebound_head = 0;
1249
1250 statstarttic = I_GetTime();
1251
1252 I_NetGet = Internal_Get;
1253 I_NetSend = Internal_Send;
1254 I_NetCanSend = NULL;
1255 I_NetCloseSocket = NULL;
1256 I_NetFreeNodenum = Internal_FreeNodenum;
1257 I_NetMakeNodewPort = NULL;
1258
1259 hardware_MAXPACKETLENGTH = MAXPACKETLENGTH;
1260 net_bandwidth = 30000;
1261 // I_InitNetwork sets doomcom and netgame
1262 // check and initialize the network driver
1263 multiplayer = false;
1264
1265 // only dos version with external driver will return true
1266 netgame = I_InitNetwork();
1267 if (!netgame && !I_NetOpenSocket)
1268 {
1269 D_SetDoomcom();
1270 netgame = I_InitTcpNetwork();
1271 }
1272
1273 if (netgame)
1274 ret = true;
1275 if (client && netgame)
1276 netgame = false;
1277 server = true; // WTF? server always true???
1278 // no! The deault mode is server. Client is set elsewhere
1279 // when the client executes connect command.
1280 doomcom->ticdup = 1;
1281
1282 if (M_CheckParm("-extratic"))
1283 {
1284 if (M_IsNextParm())
1285 doomcom->extratics = (INT16)atoi(M_GetNextParm());
1286 else
1287 doomcom->extratics = 1;
1288 CONS_Printf(M_GetText("Set extratics to %d\n"), doomcom->extratics);
1289 }
1290
1291 if (M_CheckParm("-bandwidth"))
1292 {
1293 if (M_IsNextParm())
1294 {
1295 net_bandwidth = atoi(M_GetNextParm());
1296 if (net_bandwidth < 1000)
1297 net_bandwidth = 1000;
1298 if (net_bandwidth > 100000)
1299 hardware_MAXPACKETLENGTH = MAXPACKETLENGTH;
1300 CONS_Printf(M_GetText("Network bandwidth set to %d\n"), net_bandwidth);
1301 }
1302 else
1303 I_Error("usage: -bandwidth <byte_per_sec>");
1304 }
1305
1306 software_MAXPACKETLENGTH = hardware_MAXPACKETLENGTH;
1307 if (M_CheckParm("-packetsize"))
1308 {
1309 if (M_IsNextParm())
1310 {
1311 INT32 p = atoi(M_GetNextParm());
1312 if (p < 75)
1313 p = 75;
1314 if (p > hardware_MAXPACKETLENGTH)
1315 p = hardware_MAXPACKETLENGTH;
1316 software_MAXPACKETLENGTH = (UINT16)p;
1317 }
1318 else
1319 I_Error("usage: -packetsize <bytes_per_packet>");
1320 }
1321
1322 if (netgame)
1323 multiplayer = true;
1324
1325 if (doomcom->id != DOOMCOM_ID)
1326 I_Error("Doomcom buffer invalid!");
1327 if (doomcom->numnodes > MAXNETNODES)
1328 I_Error("Too many nodes (%d), max:%d", doomcom->numnodes, MAXNETNODES);
1329
1330 netbuffer = (doomdata_t *)(void *)&doomcom->data;
1331
1332 #ifdef DEBUGFILE
1333 if (M_CheckParm("-debugfile"))
1334 {
1335 char filename[21];
1336 INT32 k = doomcom->consoleplayer - 1;
1337 if (M_IsNextParm())
1338 k = atoi(M_GetNextParm()) - 1;
1339 while (!debugfile && k < MAXPLAYERS)
1340 {
1341 k++;
1342 sprintf(filename, "debug%d.txt", k);
1343 debugfile = fopen(va("%s" PATHSEP "%s", srb2home, filename), "w");
1344 }
1345 if (debugfile)
1346 CONS_Printf(M_GetText("debug output to: %s\n"), va("%s" PATHSEP "%s", srb2home, filename));
1347 else
1348 CONS_Alert(CONS_WARNING, M_GetText("cannot debug output to file %s!\n"), va("%s" PATHSEP "%s", srb2home, filename));
1349 }
1350 #endif
1351
1352 D_ClientServerInit();
1353
1354 return ret;
1355 }
1356
1357 struct pingcell
1358 {
1359 INT32 num;
1360 INT32 ms;
1361 };
1362
pingcellcmp(const void * va,const void * vb)1363 static int pingcellcmp(const void *va, const void *vb)
1364 {
1365 const struct pingcell *a, *b;
1366 a = va;
1367 b = vb;
1368 return ( a->ms - b->ms );
1369 }
1370
1371 /*
1372 New ping command formatted nicely to present ping in
1373 ascending order. And with equally spaced columns.
1374 The caller's ping is presented at the bottom too, for
1375 convenience.
1376 */
1377
Command_Ping_f(void)1378 void Command_Ping_f(void)
1379 {
1380 struct pingcell pingv[MAXPLAYERS];
1381 INT32 pingc;
1382
1383 int name_width = 0;
1384 int ms_width = 0;
1385
1386 int n;
1387 INT32 i;
1388
1389 pingc = 0;
1390 for (i = 1; i < MAXPLAYERS; ++i)
1391 if (playeringame[i])
1392 {
1393 n = strlen(player_names[i]);
1394 if (n > name_width)
1395 name_width = n;
1396
1397 n = playerpingtable[i];
1398 if (n > ms_width)
1399 ms_width = n;
1400
1401 pingv[pingc].num = i;
1402 pingv[pingc].ms = playerpingtable[i];
1403 pingc++;
1404 }
1405
1406 if (ms_width < 10) ms_width = 1;
1407 else if (ms_width < 100) ms_width = 2;
1408 else ms_width = 3;
1409
1410 qsort(pingv, pingc, sizeof (struct pingcell), &pingcellcmp);
1411
1412 for (i = 0; i < pingc; ++i)
1413 {
1414 CONS_Printf("%02d : %-*s %*d ms\n",
1415 pingv[i].num,
1416 name_width, player_names[pingv[i].num],
1417 ms_width, pingv[i].ms);
1418 }
1419
1420 if (!server && playeringame[consoleplayer])
1421 {
1422 CONS_Printf("\nYour ping is %d ms\n", playerpingtable[consoleplayer]);
1423 }
1424 }
1425
D_CloseConnection(void)1426 void D_CloseConnection(void)
1427 {
1428 INT32 i;
1429
1430 if (netgame)
1431 {
1432 // wait the ackreturn with timout of 5 Sec
1433 Net_WaitAllAckReceived(5);
1434
1435 // close all connection
1436 for (i = 0; i < MAXNETNODES; i++)
1437 Net_CloseConnection(i|FORCECLOSE);
1438
1439 InitAck();
1440
1441 if (I_NetCloseSocket)
1442 I_NetCloseSocket();
1443
1444 I_NetGet = Internal_Get;
1445 I_NetSend = Internal_Send;
1446 I_NetCanSend = NULL;
1447 I_NetCloseSocket = NULL;
1448 I_NetFreeNodenum = Internal_FreeNodenum;
1449 I_NetMakeNodewPort = NULL;
1450 netgame = false;
1451 addedtogame = false;
1452 }
1453
1454 D_ResetTiccmds();
1455 }
1456