Initial revision
[theoddone33/hhexen.git] / base / d_net.c
1
2 //**************************************************************************
3 //**
4 //** d_net.c : Heretic 2 : Raven Software, Corp.
5 //**
6 //** $RCSfile$
7 //** $Revision$
8 //** $Date$
9 //** $Author$
10 //**
11 //** This version has the fixed ticdup code.
12 //**
13 //**************************************************************************
14
15 #include "h2def.h"
16 #include "p_local.h"
17 #include <stdlib.h> // for atoi()
18
19 #define NCMD_EXIT               0x80000000
20 #define NCMD_RETRANSMIT 0x40000000
21 #define NCMD_SETUP              0x20000000
22 #define NCMD_KILL               0x10000000              // kill game
23 #define NCMD_CHECKSUM   0x0fffffff
24
25
26 doomcom_t               *doomcom;
27 doomdata_t              *netbuffer;             // points inside doomcom
28
29
30 /*
31 ==============================================================================
32
33                                                         NETWORKING
34
35 gametic is the tic about to (or currently being) run
36 maketic is the tick that hasn't had control made for it yet
37 nettics[] has the maketics for all players
38
39 a gametic cannot be run until nettics[] > gametic for all players
40
41 ==============================================================================
42 */
43
44 #define RESENDCOUNT     10
45 #define PL_DRONE        0x80                            // bit flag in doomdata->player
46
47 ticcmd_t                localcmds[BACKUPTICS];
48
49 ticcmd_t        netcmds[MAXPLAYERS][BACKUPTICS];
50 int             nettics[MAXNETNODES];
51 boolean                 nodeingame[MAXNETNODES];        // set false as nodes leave game
52 boolean                 remoteresend[MAXNETNODES];      // set when local needs tics
53 int                             resendto[MAXNETNODES];                  // set when remote needs tics
54 int                             resendcount[MAXNETNODES];
55
56 int                             nodeforplayer[MAXPLAYERS];
57
58 int             maketic;
59 int                             lastnettic, skiptics;
60 int                             ticdup;
61 int                             maxsend;        // BACKUPTICS/(2*ticdup)-1
62
63 void H2_ProcessEvents (void);
64 void G_BuildTiccmd (ticcmd_t *cmd);
65 void H2_DoAdvanceDemo (void);
66 extern void ST_NetProgress(void);
67 extern void ST_NetDone(void);
68
69 boolean                 reboundpacket;
70 doomdata_t              reboundstore;
71
72
73 int     NetbufferSize (void)
74 {
75         return (int)&(((doomdata_t *)0)->cmds[netbuffer->numtics]);
76 }
77
78 unsigned NetbufferChecksum (void)
79 {
80         unsigned                c;
81         int             i,l;
82
83         c = 0x1234567;
84
85 #if defined(NeXT) || defined(NORMALUNIX)
86         return 0;                       // byte order problems
87 #endif
88
89         l = (NetbufferSize () - (int)&(((doomdata_t *)0)->retransmitfrom))/4;
90         for (i=0 ; i<l ; i++)
91                 c += ((unsigned *)&netbuffer->retransmitfrom)[i] * (i+1);
92
93         return c & NCMD_CHECKSUM;
94 }
95
96 int ExpandTics (int low)
97 {
98         int     delta;
99
100         delta = low - (maketic&0xff);
101
102         if (delta >= -64 && delta <= 64)
103                 return (maketic&~0xff) + low;
104         if (delta > 64)
105                 return (maketic&~0xff) - 256 + low;
106         if (delta < -64)
107                 return (maketic&~0xff) + 256 + low;
108
109         I_Error ("ExpandTics: strange value %i at maketic %i",low,maketic);
110         return 0;
111 }
112
113
114 //============================================================================
115
116
117 /*
118 ==============
119 =
120 = HSendPacket
121 =
122 ==============
123 */
124
125 void HSendPacket (int node, int flags)
126 {
127         netbuffer->checksum = NetbufferChecksum () | flags;
128
129         if (!node)
130         {
131                 reboundstore = *netbuffer;
132                 reboundpacket = true;
133                 return;
134         }
135
136         if (demoplayback)
137                 return;
138
139         if (!netgame)
140                 I_Error ("Tried to transmit to another node");
141
142         doomcom->command = CMD_SEND;
143         doomcom->remotenode = node;
144         doomcom->datalength = NetbufferSize ();
145
146 if (debugfile)
147 {
148         int             i;
149         int             realretrans;
150         if (netbuffer->checksum & NCMD_RETRANSMIT)
151                 realretrans = ExpandTics (netbuffer->retransmitfrom);
152         else
153                 realretrans = -1;
154         fprintf (debugfile,"send (%i + %i, R %i) [%i] "
155         ,ExpandTics(netbuffer->starttic),netbuffer->numtics, realretrans, doomcom->datalength);
156         for (i=0 ; i<doomcom->datalength ; i++)
157                 fprintf (debugfile,"%i ",((byte *)netbuffer)[i]);
158         fprintf (debugfile,"\n");
159 }
160
161         I_NetCmd ();
162 }
163
164 //==========================================================================
165 //
166 // NET_SendFrags
167 //
168 //==========================================================================
169
170 void NET_SendFrags(player_t *player)
171 {
172         int i;
173         int frags;
174
175         netbuffer->checksum = NetbufferChecksum();
176
177         if (demoplayback)
178         {
179                 return;
180         }
181         if (!netgame)
182         {
183                 I_Error ("Tried to transmit to another node");
184         }
185
186         frags = 0;
187         for(i = 0; i < MAXPLAYERS; i++)
188         {
189                 frags += player->frags[i];
190         }
191         doomcom->command = CMD_FRAG;
192         doomcom->remotenode = frags;
193         doomcom->datalength = NetbufferSize ();
194
195         I_NetCmd ();
196 }
197
198 /*
199 ==============
200 =
201 = HGetPacket
202 =
203 = Returns false if no packet is waiting
204 =
205 ==============
206 */
207
208 boolean HGetPacket (void)
209 {
210         if (reboundpacket)
211         {
212                 *netbuffer = reboundstore;
213                 doomcom->remotenode = 0;
214                 reboundpacket = false;
215                 return true;
216         }
217
218         if (!netgame)
219                 return false;
220         if (demoplayback)
221                 return false;
222
223         doomcom->command = CMD_GET;
224         I_NetCmd ();
225         if (doomcom->remotenode == -1)
226                 return false;
227
228         if (doomcom->datalength != NetbufferSize ())
229         {
230                 if (debugfile)
231                         fprintf (debugfile,"bad packet length %i\n",doomcom->datalength);
232                 return false;
233         }
234
235         if (NetbufferChecksum () != (netbuffer->checksum&NCMD_CHECKSUM) )
236         {
237                 if (debugfile)
238                         fprintf (debugfile,"bad packet checksum\n");
239                 return false;
240         }
241
242 if (debugfile)
243 {
244         int             realretrans;
245                         int     i;
246
247         if (netbuffer->checksum & NCMD_SETUP)
248                 fprintf (debugfile,"setup packet\n");
249         else
250         {
251                 if (netbuffer->checksum & NCMD_RETRANSMIT)
252                         realretrans = ExpandTics (netbuffer->retransmitfrom);
253                 else
254                         realretrans = -1;
255                 fprintf (debugfile,"get %i = (%i + %i, R %i)[%i] ",doomcom->remotenode,
256                 ExpandTics(netbuffer->starttic),netbuffer->numtics, realretrans, doomcom->datalength);
257                 for (i=0 ; i<doomcom->datalength ; i++)
258                         fprintf (debugfile,"%i ",((byte *)netbuffer)[i]);
259                 fprintf (debugfile,"\n");
260         }
261 }
262         return true;
263 }
264
265
266 /*
267 ===================
268 =
269 = GetPackets
270 =
271 ===================
272 */
273
274 char    exitmsg[80];
275
276 void GetPackets (void)
277 {
278         int             netconsole;
279         int             netnode;
280         ticcmd_t        *src, *dest;
281         int             realend;
282         int             realstart;
283
284         while (HGetPacket ())
285         {
286                 if (netbuffer->checksum & NCMD_SETUP)
287                         continue;               // extra setup packet
288
289                 netconsole = netbuffer->player & ~PL_DRONE;
290                 netnode = doomcom->remotenode;
291                 //
292                 // to save bytes, only the low byte of tic numbers are sent
293                 // Figure out what the rest of the bytes are
294                 //
295                 realstart = ExpandTics (netbuffer->starttic);
296                 realend = (realstart+netbuffer->numtics);
297
298                 //
299                 // check for exiting the game
300                 //
301                 if (netbuffer->checksum & NCMD_EXIT)
302                 {
303                         if (!nodeingame[netnode])
304                                 continue;
305                         nodeingame[netnode] = false;
306                         playeringame[netconsole] = false;
307                         strcpy (exitmsg, "PLAYER 1 LEFT THE GAME");
308                         exitmsg[7] += netconsole;
309                         P_SetMessage(&players[consoleplayer], exitmsg, true);
310                         S_StartSound(NULL, SFX_CHAT);
311 //                      players[consoleplayer].message = exitmsg;
312 //                      if (demorecording)
313 //                              G_CheckDemoStatus ();
314                         continue;
315                 }
316
317                 //
318                 // check for a remote game kill
319                 //
320                 if (netbuffer->checksum & NCMD_KILL)
321                         I_Error ("Killed by network driver");
322
323                 nodeforplayer[netconsole] = netnode;
324
325                 //
326                 // check for retransmit request
327                 //
328                 if ( resendcount[netnode] <= 0
329                 && (netbuffer->checksum & NCMD_RETRANSMIT) )
330                 {
331                         resendto[netnode] = ExpandTics(netbuffer->retransmitfrom);
332 if (debugfile)
333 fprintf (debugfile,"retransmit from %i\n", resendto[netnode]);
334                         resendcount[netnode] = RESENDCOUNT;
335                 }
336                 else
337                         resendcount[netnode]--;
338
339                 //
340                 // check for out of order / duplicated packet
341                 //
342                 if (realend == nettics[netnode])
343                         continue;
344
345                 if (realend < nettics[netnode])
346                 {
347 if (debugfile)
348 fprintf (debugfile,"out of order packet (%i + %i)\n" ,realstart,netbuffer->numtics);
349                         continue;
350                 }
351
352                 //
353                 // check for a missed packet
354                 //
355                 if (realstart > nettics[netnode])
356                 {
357                 // stop processing until the other system resends the missed tics
358 if (debugfile)
359 fprintf (debugfile,"missed tics from %i (%i - %i)\n", netnode, realstart, nettics[netnode]);
360                         remoteresend[netnode] = true;
361                         continue;
362                 }
363
364 //
365 // update command store from the packet
366 //
367 {
368         int             start;
369
370                 remoteresend[netnode] = false;
371
372                 start = nettics[netnode] - realstart;
373                 src = &netbuffer->cmds[start];
374
375                 while (nettics[netnode] < realend)
376                 {
377                         dest = &netcmds[netconsole][nettics[netnode]%BACKUPTICS];
378                         nettics[netnode]++;
379                         *dest = *src;
380                         src++;
381                 }
382         }
383 }
384
385 }
386
387 /*
388 =============
389 =
390 = NetUpdate
391 =
392 = Builds ticcmds for console player
393 = sends out a packet
394 =============
395 */
396
397 int      gametime;
398
399 void NetUpdate (void)
400 {
401         int             nowtime;
402         int             newtics;
403         int                             i,j;
404         int                             realstart;
405         int                             gameticdiv;
406
407 //
408 // check time
409 //
410         nowtime = I_GetTime ()/ticdup;
411         newtics = nowtime - gametime;
412         gametime = nowtime;
413
414         if (newtics <= 0)                       // nothing new to update
415                 goto listen;
416
417         if (skiptics <= newtics)
418         {
419                 newtics -= skiptics;
420                 skiptics = 0;
421         }
422         else
423         {
424                 skiptics -= newtics;
425                 newtics = 0;
426         }
427
428
429         netbuffer->player = consoleplayer;
430
431 //
432 // build new ticcmds for console player
433 //
434         gameticdiv = gametic/ticdup;
435         for (i=0 ; i<newtics ; i++)
436         {
437                 I_StartTic ();
438                 H2_ProcessEvents ();
439                 if (maketic - gameticdiv >= BACKUPTICS/2-1)
440                         break;          // can't hold any more
441 //printf ("mk:%i ",maketic);
442                 G_BuildTiccmd (&localcmds[maketic%BACKUPTICS]);
443                 maketic++;
444         }
445
446
447         if (singletics)
448                 return;         // singletic update is syncronous
449
450 //
451 // send the packet to the other nodes
452 //
453         for (i=0 ; i<doomcom->numnodes ; i++)
454                 if (nodeingame[i])
455                 {
456                         netbuffer->starttic = realstart = resendto[i];
457                         netbuffer->numtics = maketic - realstart;
458                         if (netbuffer->numtics > BACKUPTICS)
459                                 I_Error ("NetUpdate: netbuffer->numtics > BACKUPTICS");
460
461                         resendto[i] = maketic - doomcom->extratics;
462
463                         for (j=0 ; j< netbuffer->numtics ; j++)
464                                 netbuffer->cmds[j] =
465                                         localcmds[(realstart+j)%BACKUPTICS];
466
467                         if (remoteresend[i])
468                         {
469                                 netbuffer->retransmitfrom = nettics[i];
470                                 HSendPacket (i, NCMD_RETRANSMIT);
471                         }
472                         else
473                         {
474                                 netbuffer->retransmitfrom = 0;
475                                 HSendPacket (i, 0);
476                         }
477                 }
478
479 //
480 // listen for other packets
481 //
482 listen:
483
484         GetPackets ();
485 }
486
487
488 /*
489 =====================
490 =
491 = CheckAbort
492 =
493 =====================
494 */
495
496 void CheckAbort (void)
497 {
498         event_t *ev;
499         int             stoptic;
500
501         stoptic = I_GetTime () + 2;
502         while (I_GetTime() < stoptic)
503                 I_StartTic ();
504
505         I_StartTic ();
506         for ( ; eventtail != eventhead
507         ; eventtail = (++eventtail)&(MAXEVENTS-1) )
508         {
509                 ev = &events[eventtail];
510                 if (ev->type == ev_keydown && ev->data1 == KEY_ESCAPE)
511                         I_Error ("Network game synchronization aborted.");
512         }
513 }
514
515 /*
516 =====================
517 =
518 = D_ArbitrateNetStart
519 =
520 =====================
521 */
522
523 void D_ArbitrateNetStart (void)
524 {
525         int             i;
526         boolean gotinfo[MAXNETNODES];
527         boolean gotClass[MAXNETNODES];
528
529         autostart = true;
530 fprintf(stderr, "Pre memset\n");
531         memset (gotClass,0,sizeof(gotClass));
532         memset (gotinfo,0,sizeof(gotinfo));
533 fprintf(stderr, "post memset\n");
534         gotClass[doomcom->consoleplayer] = true;
535         do
536         {
537                 i = 0;
538
539                 CheckAbort();
540                 while(HGetPacket())
541                 { // Check for any incoming packets
542                         if(netbuffer->checksum&NCMD_SETUP && netbuffer->starttic >= 64)
543                         {
544                                 
545                                 PlayerClass[netbuffer->player] = netbuffer->starttic&0x3f;
546                                 if(!gotClass[netbuffer->player])
547                                 {
548                                         gotClass[netbuffer->player] = true;
549                                         ST_NetProgress();
550                                         ST_Message("\n");
551                                 }
552                                 if(netbuffer->retransmitfrom)
553                                 { // that node has received info from all other nodes
554                                         gotinfo[netbuffer->player] = true;
555                                 }
556                         }
557                 } 
558                 // Keep sending out packets containing the console class
559                 for(i = 0; i < doomcom->numnodes; i++)
560                 {
561                         netbuffer->player = doomcom->consoleplayer;
562                         netbuffer->starttic = PlayerClass[doomcom->consoleplayer]+64;
563                         netbuffer->retransmitfrom = gotinfo[doomcom->consoleplayer];
564                         netbuffer->numtics = 0;
565                         HSendPacket(i, NCMD_SETUP);
566                 }
567                 for(i = 0; i < doomcom->numnodes; i++)
568                 { // Make sure that all nodes have sent class info
569                         if (!gotClass[i])
570                         {
571                                 ST_Message(".");
572                                 break;
573                         }
574                 }
575                 if(i < doomcom->numnodes)
576                 {
577                         continue;
578                 }
579                 else
580                 { // consoleplayer has received all player classes
581                         if(gotinfo[doomcom->consoleplayer])
582                         {
583                                 CheckAbort();
584                         }
585                         else
586                         {
587                                 gotinfo[doomcom->consoleplayer] = true;
588                                 ST_Message("All player classes received, ready to proceed\n");
589                                 ST_NetDone();
590                         }
591                 }
592                 for (i = 0; i < doomcom->numnodes; i++)
593                 { // Make sure that all nodes are ready to proceed
594                         if (!gotinfo[i])
595                         {
596                                 break;
597                         }
598                 }
599         } while(i < doomcom->numnodes);
600
601         memset (gotinfo,0,sizeof(gotinfo));
602
603         if (doomcom->consoleplayer)
604         {       // listen for setup info from key player
605 //              ST_Message ("listening for network start info...\n");
606                 while (1)
607                 {
608                         CheckAbort ();
609                         if (!HGetPacket ())
610                                 continue;
611                         if(netbuffer->checksum & NCMD_SETUP && netbuffer->starttic < 64)
612                         {
613                                 if (netbuffer->player != VERSION)
614                                         I_Error ("Different HEXEN versions cannot play a net game!");
615                                 startskill = netbuffer->retransmitfrom & 15;
616                                 deathmatch = (netbuffer->retransmitfrom & 0xc0) >> 6;
617                                 nomonsters = (netbuffer->retransmitfrom & 0x20) > 0;
618                                 respawnparm = (netbuffer->retransmitfrom & 0x10) > 0;
619                                 startmap = netbuffer->starttic & 0x3f;
620                                 startepisode = 1;
621                                 return;
622                         }
623                 }
624         }
625         else
626         {       // key player, send the setup info
627 //              ST_Message ("sending network start info...\n");
628                 do
629                 {
630                         CheckAbort ();
631                         for (i=0 ; i<doomcom->numnodes ; i++)
632                         {
633                                 netbuffer->retransmitfrom = startskill;
634                                 if (deathmatch)
635                                         netbuffer->retransmitfrom |= (deathmatch<<6);
636                                 if (nomonsters)
637                                         netbuffer->retransmitfrom |= 0x20;
638                                 if (respawnparm)
639                                         netbuffer->retransmitfrom |= 0x10;
640                                 netbuffer->starttic = startmap&0x3f;
641                                 netbuffer->player = VERSION;
642                                 netbuffer->numtics = 0;
643                                 HSendPacket (i, NCMD_SETUP);
644                         }
645
646 #if 1
647                         for(i = 10 ; i  &&  HGetPacket(); --i)
648                         {
649  if((netbuffer->player&0x7f) < MAXNETNODES)
650                                 gotinfo[netbuffer->player&0x7f] = true;
651                         }
652 #else
653                         while (HGetPacket ())
654                         {
655                                 gotinfo[netbuffer->player&0x7f] = true;
656                         }
657 #endif
658
659                         for (i=1 ; i<doomcom->numnodes ; i++)
660                                 if (!gotinfo[i])
661                                         break;
662                 } while (i < doomcom->numnodes);
663         }
664 }
665
666 /*
667 ===================
668 =
669 = D_CheckNetGame
670 =
671 = Works out player numbers among the net participants
672 ===================
673 */
674
675 extern  int                     viewangleoffset;
676
677 void D_CheckNetGame (void)
678 {
679         int             i;
680         int pClass;
681
682         for (i=0 ; i<MAXNETNODES ; i++)
683         {
684                 nodeingame[i] = false;
685                 nettics[i] = 0;
686                 remoteresend[i] = false;        // set when local needs tics
687                 resendto[i] = 0;                        // which tic to start sending
688         }
689
690 // I_InitNetwork sets doomcom and netgame
691         I_InitNetwork ();
692         if (doomcom->id != DOOMCOM_ID)
693                 I_Error ("Doomcom buffer invalid!");
694         netbuffer = &doomcom->data;
695         consoleplayer = displayplayer = doomcom->consoleplayer;
696         pClass = PCLASS_FIGHTER;
697         if((i = M_CheckParm("-class"))) /* jim parens added to placate gcc */
698         {
699                 pClass = atoi(myargv[i+1]);
700                 if(pClass > PCLASS_MAGE || pClass < PCLASS_FIGHTER)
701                 {
702                         I_Error("Invalid player class: %d\n", pClass);
703                 }
704                 ST_Message("\nPlayer Class: %d\n", pClass);
705         }
706         PlayerClass[consoleplayer] = pClass;
707         if (netgame)
708                 D_ArbitrateNetStart ();
709 //ST_Message ("startskill %i  deathmatch: %i  startmap: %i  startepisode: %i\n", startskill, deathmatch, startmap, startepisode);
710 // read values out of doomcom
711         ticdup = doomcom->ticdup;
712         maxsend = BACKUPTICS/(2*ticdup)-1;
713         if (maxsend<1)
714                 maxsend = 1;
715
716         for (i=0 ; i<doomcom->numplayers ; i++)
717                 playeringame[i] = true;
718         for (i=0 ; i<doomcom->numnodes ; i++)
719                 nodeingame[i] = true;
720
721 //ST_Message ("player %i of %i (%i nodes)\n", consoleplayer+1, doomcom->numplayers, doomcom->numnodes);
722
723 }
724
725 /*
726 ==================
727 =
728 = D_QuitNetGame
729 =
730 = Called before quitting to leave a net game without hanging the
731 = other players
732 =
733 ==================
734 */
735
736 void D_QuitNetGame (void)
737 {
738         int             i, j;
739
740         if (debugfile)
741                 fclose (debugfile);
742
743         if (!netgame || !usergame || consoleplayer == -1 || demoplayback)
744                 return;
745
746 // send a bunch of packets for security
747         netbuffer->player = consoleplayer;
748         netbuffer->numtics = 0;
749         for (i=0 ; i<4 ; i++)
750         {
751                 for (j=1 ; j<doomcom->numnodes ; j++)
752                         if (nodeingame[j])
753                                 HSendPacket (j, NCMD_EXIT);
754                 I_WaitVBL (1);
755         }
756 }
757
758
759
760 /*
761 ===============
762 =
763 = TryRunTics
764 =
765 ===============
766 */
767
768 int     frametics[4], frameon;
769 int     frameskip[4];
770 int             oldnettics;
771 extern  boolean advancedemo;
772
773 void TryRunTics (void)
774 {
775         int             i;
776         int             lowtic;
777         int             entertic;
778         static int              oldentertics;
779         int                             realtics, availabletics;
780         int                             counts;
781         int                             numplaying;
782
783 //
784 // get real tics
785 //
786         entertic = I_GetTime ()/ticdup;
787         realtics = entertic - oldentertics;
788         oldentertics = entertic;
789
790 //
791 // get available tics
792 //
793         NetUpdate ();
794
795         lowtic = MAXINT;
796         numplaying = 0;
797         for (i=0 ; i<doomcom->numnodes ; i++)
798                 if (nodeingame[i])
799                 {
800                         numplaying++;
801                         if (nettics[i] < lowtic)
802                                 lowtic = nettics[i];
803                 }
804         availabletics = lowtic - gametic/ticdup;
805
806
807 //
808 // decide how many tics to run
809 //
810         if (realtics < availabletics-1)
811                 counts = realtics+1;
812         else if (realtics < availabletics)
813                 counts = realtics;
814         else
815                 counts = availabletics;
816         if (counts < 1)
817                 counts = 1;
818
819         frameon++;
820
821 if (debugfile)
822         fprintf (debugfile,"=======real: %i  avail: %i  game: %i\n",realtics, availabletics,counts);
823
824         if (!demoplayback)
825         {
826         //=============================================================================
827         //
828         //      ideally nettics[0] should be 1 - 3 tics above lowtic
829         //      if we are consistantly slower, speed up time
830         //
831                 for (i=0 ; i<MAXPLAYERS ; i++)
832                         if (playeringame[i])
833                                 break;
834                 if (consoleplayer == i)
835                 {       // the key player does not adapt
836                 }
837                 else
838                 {
839                         if (nettics[0] <= nettics[nodeforplayer[i]])
840                         {
841                                 gametime--;
842         //                      printf ("-");
843                         }
844                         frameskip[frameon&3] = (oldnettics > nettics[nodeforplayer[i]]);
845                         oldnettics = nettics[0];
846                         if (frameskip[0] && frameskip[1] && frameskip[2] && frameskip[3])
847                         {
848                                 skiptics = 1;
849         //                      printf ("+");
850                         }
851                 }
852         //=============================================================================
853         }       // demoplayback
854
855         //
856         // wait for new tics if needed
857         //
858                 while (lowtic < gametic/ticdup + counts)
859                 {
860
861                         NetUpdate ();
862                         lowtic = MAXINT;
863
864                         for (i=0 ; i<doomcom->numnodes ; i++)
865                                 if (nodeingame[i] && nettics[i] < lowtic)
866                                         lowtic = nettics[i];
867
868                         if (lowtic < gametic/ticdup)
869                                 I_Error ("TryRunTics: lowtic < gametic");
870
871                         // don't stay in here forever -- give the menu a chance to work
872                         if (I_GetTime ()/ticdup - entertic >= 20)
873                         {
874                                 MN_Ticker ();
875                                 return;
876                         }
877                 }
878
879 //
880 // run the count * ticdup dics
881 //
882         while (counts--)
883         {
884                 for (i=0 ; i<ticdup ; i++)
885                 {
886                         if (gametic/ticdup > lowtic)
887                                 I_Error ("gametic>lowtic");
888                         if (advancedemo)
889                                 H2_DoAdvanceDemo ();
890                         MN_Ticker ();
891                         G_Ticker ();
892                         gametic++;
893                         //
894                         // modify command for duplicated tics
895                         //
896                         if (i != ticdup-1)
897                         {
898                                 ticcmd_t        *cmd;
899                                 int                     buf;
900                                 int                     j;
901
902                                 buf = (gametic/ticdup)%BACKUPTICS;
903                                 for (j=0 ; j<MAXPLAYERS ; j++)
904                                 {
905                                         cmd = &netcmds[j][buf];
906                                         cmd->chatchar = 0;
907                                         if (cmd->buttons & BT_SPECIAL)
908                                                 cmd->buttons = 0;
909                                 }
910                         }
911                 }
912                 NetUpdate ();                                   // check for new console commands
913         }
914 }