d_loop.c
  1  //
  2  // Copyright(C) 1993-1996 Id Software, Inc.
  3  // Copyright(C) 2005-2014 Simon Howard
  4  //
  5  // This program is free software; you can redistribute it and/or
  6  // modify it under the terms of the GNU General Public License
  7  // as published by the Free Software Foundation; either version 2
  8  // of the License, or (at your option) any later version.
  9  //
 10  // This program is distributed in the hope that it will be useful,
 11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13  // GNU General Public License for more details.
 14  //
 15  // DESCRIPTION:
 16  //     Main loop code.
 17  //
 18  
 19  #include <stdlib.h>
 20  #include <string.h>
 21  
 22  #include "doomfeatures.h"
 23  
 24  #include "d_event.h"
 25  #include "d_loop.h"
 26  #include "d_ticcmd.h"
 27  
 28  #include "i_system.h"
 29  #include "i_timer.h"
 30  #include "i_video.h"
 31  
 32  #include "m_argv.h"
 33  #include "m_fixed.h"
 34  
 35  #include "net_client.h"
 36  #include "net_gui.h"
 37  #include "net_io.h"
 38  #include "net_query.h"
 39  #include "net_server.h"
 40  #include "net_sdl.h"
 41  #include "net_loop.h"
 42  
 43  // The complete set of data for a particular tic.
 44  
 45  typedef struct
 46  {
 47      ticcmd_t cmds[NET_MAXPLAYERS];
 48      boolean ingame[NET_MAXPLAYERS];
 49  } ticcmd_set_t;
 50  
 51  //
 52  // gametic is the tic about to (or currently being) run
 53  // maketic is the tic that hasn't had control made for it yet
 54  // recvtic is the latest tic received from the server.
 55  //
 56  // a gametic cannot be run until ticcmds are received for it
 57  // from all players.
 58  //
 59  
 60  static ticcmd_set_t ticdata[BACKUPTICS];
 61  
 62  // The index of the next tic to be made (with a call to BuildTiccmd).
 63  
 64  static int maketic;
 65  
 66  // The number of complete tics received from the server so far.
 67  
 68  static int recvtic;
 69  
 70  // The number of tics that have been run (using RunTic) so far.
 71  
 72  int gametic;
 73  
 74  // When set to true, a single tic is run each time TryRunTics() is called.
 75  // This is used for -timedemo mode.
 76  
 77  boolean singletics = false;
 78  
 79  // Index of the local player.
 80  
 81  static int localplayer;
 82  
 83  // Used for original sync code.
 84  
 85  static int      skiptics = 0;
 86  
 87  // Reduce the bandwidth needed by sampling game input less and transmitting
 88  // less.  If ticdup is 2, sample half normal, 3 = one third normal, etc.
 89  
 90  int		ticdup;
 91  
 92  // Amount to offset the timer for game sync.
 93  
 94  fixed_t         offsetms;
 95  
 96  // Use new client syncronisation code
 97  
 98  static boolean  new_sync = true;
 99  
100  // Callback functions for loop code.
101  
102  static loop_interface_t *loop_interface = NULL;
103  
104  // Current players in the multiplayer game.
105  // This is distinct from playeringame[] used by the game code, which may
106  // modify playeringame[] when playing back multiplayer demos.
107  
108  static boolean local_playeringame[NET_MAXPLAYERS];
109  
110  // Requested player class "sent" to the server on connect.
111  // If we are only doing a single player game then this needs to be remembered
112  // and saved in the game settings.
113  
114  static int player_class;
115  
116  
117  // 35 fps clock adjusted by offsetms milliseconds
118  
119  static int GetAdjustedTime(void)
120  {
121      int time_ms;
122  
123      time_ms = I_GetTimeMS();
124  
125      if (new_sync)
126      {
127  	// Use the adjustments from net_client.c only if we are
128  	// using the new sync mode.
129  
130          time_ms += (offsetms / FRACUNIT);
131      }
132  
133      return (time_ms * TICRATE) / 1000;
134  }
135  
136  static boolean BuildNewTic(void)
137  {
138      int	gameticdiv;
139      ticcmd_t cmd;
140  
141      gameticdiv = gametic/ticdup;
142  
143      I_StartTic ();
144      loop_interface->ProcessEvents();
145  
146      // Always run the menu
147  
148      loop_interface->RunMenu();
149  
150      if (drone)
151      {
152          // In drone mode, do not generate any ticcmds.
153  
154          return false;
155      }
156  
157      if (new_sync)
158      {
159         // If playing single player, do not allow tics to buffer
160         // up very far
161  
162         if (!net_client_connected && maketic - gameticdiv > 2)
163             return false;
164  
165         // Never go more than ~200ms ahead
166  
167         if (maketic - gameticdiv > 8)
168             return false;
169      }
170      else
171      {
172         if (maketic - gameticdiv >= 5)
173             return false;
174      }
175  
176      //printf ("mk:%i ",maketic);
177      memset(&cmd, 0, sizeof(ticcmd_t));
178      loop_interface->BuildTiccmd(&cmd, maketic);
179  
180  #ifdef FEATURE_MULTIPLAYER
181  
182      if (net_client_connected)
183      {
184          NET_CL_SendTiccmd(&cmd, maketic);
185      }
186  
187  #endif
188      ticdata[maketic % BACKUPTICS].cmds[localplayer] = cmd;
189      ticdata[maketic % BACKUPTICS].ingame[localplayer] = true;
190  
191      ++maketic;
192  
193      return true;
194  }
195  
196  //
197  // NetUpdate
198  // Builds ticcmds for console player,
199  // sends out a packet
200  //
201  int      lasttime;
202  
203  void NetUpdate (void)
204  {
205      int nowtime;
206      int newtics;
207      int	i;
208  
209      // If we are running with singletics (timing a demo), this
210      // is all done separately.
211  
212      if (singletics)
213          return;
214  
215  #ifdef FEATURE_MULTIPLAYER
216  
217      // Run network subsystems
218  
219      NET_CL_Run();
220      NET_SV_Run();
221  
222  #endif
223  
224      // check time
225      nowtime = GetAdjustedTime() / ticdup;
226      newtics = nowtime - lasttime;
227  
228      lasttime = nowtime;
229  
230      if (skiptics <= newtics)
231      {
232          newtics -= skiptics;
233          skiptics = 0;
234      }
235      else
236      {
237          skiptics -= newtics;
238          newtics = 0;
239      }
240  
241      // build new ticcmds for console player
242  
243      for (i=0 ; i<newtics ; i++)
244      {
245          if (!BuildNewTic())
246          {
247              break;
248          }
249      }
250  }
251  
252  static void D_Disconnected(void)
253  {
254      // In drone mode, the game cannot continue once disconnected.
255  
256      if (drone)
257      {
258          I_Error("Disconnected from server in drone mode.");
259      }
260  
261      // disconnected from server
262  
263      printf("Disconnected from server.\n");
264  }
265  
266  //
267  // Invoked by the network engine when a complete set of ticcmds is
268  // available.
269  //
270  
271  void D_ReceiveTic(ticcmd_t *ticcmds, boolean *players_mask)
272  {
273      int i;
274  
275      // Disconnected from server?
276  
277      if (ticcmds == NULL && players_mask == NULL)
278      {
279          D_Disconnected();
280          return;
281      }
282  
283      for (i = 0; i < NET_MAXPLAYERS; ++i)
284      {
285          if (!drone && i == localplayer)
286          {
287              // This is us.  Don't overwrite it.
288          }
289          else
290          {
291              ticdata[recvtic % BACKUPTICS].cmds[i] = ticcmds[i];
292              ticdata[recvtic % BACKUPTICS].ingame[i] = players_mask[i];
293          }
294      }
295  
296      ++recvtic;
297  }
298  
299  //
300  // Start game loop
301  //
302  // Called after the screen is set but before the game starts running.
303  //
304  
305  void D_StartGameLoop(void)
306  {
307      lasttime = GetAdjustedTime() / ticdup;
308  }
309  
310  #if ORIGCODE
311  //
312  // Block until the game start message is received from the server.
313  //
314  
315  static void BlockUntilStart(net_gamesettings_t *settings,
316                              netgame_startup_callback_t callback)
317  {
318      while (!NET_CL_GetSettings(settings))
319      {
320          NET_CL_Run();
321          NET_SV_Run();
322  
323          if (!net_client_connected)
324          {
325              I_Error("Lost connection to server");
326          }
327  
328          if (callback != NULL && !callback(net_client_wait_data.ready_players,
329                                            net_client_wait_data.num_players))
330          {
331              I_Error("Netgame startup aborted.");
332          }
333  
334          I_Sleep(100);
335      }
336  }
337  
338  #endif
339  
340  void D_StartNetGame(net_gamesettings_t *settings,
341                      netgame_startup_callback_t callback)
342  {
343  #if ORIGCODE
344      int i;
345  
346      offsetms = 0;
347      recvtic = 0;
348  
349      settings->consoleplayer = 0;
350      settings->num_players = 1;
351      settings->player_classes[0] = player_class;
352  
353      //!
354      // @category net
355      //
356      // Use new network client sync code rather than the classic
357      // sync code. This is currently disabled by default because it
358      // has some bugs.
359      //
360      if (M_CheckParm("-newsync") > 0)
361          settings->new_sync = 1;
362      else
363          settings->new_sync = 0;
364  
365      // TODO: New sync code is not enabled by default because it's
366      // currently broken. 
367      //if (M_CheckParm("-oldsync") > 0)
368      //    settings->new_sync = 0;
369      //else
370      //    settings->new_sync = 1;
371  
372      //!
373      // @category net
374      // @arg <n>
375      //
376      // Send n extra tics in every packet as insurance against dropped
377      // packets.
378      //
379  
380      i = M_CheckParmWithArgs("-extratics", 1);
381  
382      if (i > 0)
383          settings->extratics = atoi(myargv[i+1]);
384      else
385          settings->extratics = 1;
386  
387      //!
388      // @category net
389      // @arg <n>
390      //
391      // Reduce the resolution of the game by a factor of n, reducing
392      // the amount of network bandwidth needed.
393      //
394  
395      i = M_CheckParmWithArgs("-dup", 1);
396  
397      if (i > 0)
398          settings->ticdup = atoi(myargv[i+1]);
399      else
400          settings->ticdup = 1;
401  
402      if (net_client_connected)
403      {
404          // Send our game settings and block until game start is received
405          // from the server.
406  
407          NET_CL_StartGame(settings);
408          BlockUntilStart(settings, callback);
409  
410          // Read the game settings that were received.
411  
412          NET_CL_GetSettings(settings);
413      }
414  
415      if (drone)
416      {
417          settings->consoleplayer = 0;
418      }
419  
420      // Set the local player and playeringame[] values.
421  
422      localplayer = settings->consoleplayer;
423  
424      for (i = 0; i < NET_MAXPLAYERS; ++i)
425      {
426          local_playeringame[i] = i < settings->num_players;
427      }
428  
429      // Copy settings to global variables.
430  
431      ticdup = settings->ticdup;
432      new_sync = settings->new_sync;
433  
434      // TODO: Message disabled until we fix new_sync.
435      //if (!new_sync)
436      //{
437      //    printf("Syncing netgames like Vanilla Doom.\n");
438      //}
439  #else
440      settings->consoleplayer = 0;
441  	settings->num_players = 1;
442  	settings->player_classes[0] = player_class;
443  	settings->new_sync = 0;
444  	settings->extratics = 1;
445  	settings->ticdup = 1;
446  
447  	ticdup = settings->ticdup;
448  	new_sync = settings->new_sync;
449  #endif
450  }
451  
452  boolean D_InitNetGame(net_connect_data_t *connect_data)
453  {
454      boolean result = false;
455  #ifdef FEATURE_MULTIPLAYER
456      net_addr_t *addr = NULL;
457      int i;
458  #endif
459  
460      // Call D_QuitNetGame on exit:
461  
462      I_AtExit(D_QuitNetGame, true);
463  
464      player_class = connect_data->player_class;
465  
466  #ifdef FEATURE_MULTIPLAYER
467  
468      //!
469      // @category net
470      //
471      // Start a multiplayer server, listening for connections.
472      //
473  
474      if (M_CheckParm("-server") > 0
475       || M_CheckParm("-privateserver") > 0)
476      {
477          NET_SV_Init();
478          NET_SV_AddModule(&net_loop_server_module);
479          NET_SV_AddModule(&net_sdl_module);
480          NET_SV_RegisterWithMaster();
481  
482          net_loop_client_module.InitClient();
483          addr = net_loop_client_module.ResolveAddress(NULL);
484      }
485      else
486      {
487          //!
488          // @category net
489          //
490          // Automatically search the local LAN for a multiplayer
491          // server and join it.
492          //
493  
494          i = M_CheckParm("-autojoin");
495  
496          if (i > 0)
497          {
498              addr = NET_FindLANServer();
499  
500              if (addr == NULL)
501              {
502                  I_Error("No server found on local LAN");
503              }
504          }
505  
506          //!
507          // @arg <address>
508          // @category net
509          //
510          // Connect to a multiplayer server running on the given
511          // address.
512          //
513  
514          i = M_CheckParmWithArgs("-connect", 1);
515  
516          if (i > 0)
517          {
518              net_sdl_module.InitClient();
519              addr = net_sdl_module.ResolveAddress(myargv[i+1]);
520  
521              if (addr == NULL)
522              {
523                  I_Error("Unable to resolve '%s'\n", myargv[i+1]);
524              }
525          }
526      }
527  
528      if (addr != NULL)
529      {
530          if (M_CheckParm("-drone") > 0)
531          {
532              connect_data->drone = true;
533          }
534  
535          if (!NET_CL_Connect(addr, connect_data))
536          {
537              I_Error("D_InitNetGame: Failed to connect to %s\n",
538                      NET_AddrToString(addr));
539          }
540  
541          printf("D_InitNetGame: Connected to %s\n", NET_AddrToString(addr));
542  
543          // Wait for launch message received from server.
544  
545          NET_WaitForLaunch();
546  
547          result = true;
548      }
549  #endif
550  
551      return result;
552  }
553  
554  
555  //
556  // D_QuitNetGame
557  // Called before quitting to leave a net game
558  // without hanging the other players
559  //
560  void D_QuitNetGame (void)
561  {
562  #ifdef FEATURE_MULTIPLAYER
563      NET_SV_Shutdown();
564      NET_CL_Disconnect();
565  #endif
566  }
567  
568  static int GetLowTic(void)
569  {
570      int lowtic;
571  
572      lowtic = maketic;
573  
574  #ifdef FEATURE_MULTIPLAYER
575      if (net_client_connected)
576      {
577          if (drone || recvtic < lowtic)
578          {
579              lowtic = recvtic;
580          }
581      }
582  #endif
583  
584      return lowtic;
585  }
586  
587  static int frameon;
588  static int frameskip[4];
589  static int oldnettics;
590  
591  static void OldNetSync(void)
592  {
593      unsigned int i;
594      int keyplayer = -1;
595  
596      frameon++;
597  
598      // ideally maketic should be 1 - 3 tics above lowtic
599      // if we are consistantly slower, speed up time
600  
601      for (i=0 ; i<NET_MAXPLAYERS ; i++)
602      {
603          if (local_playeringame[i])
604          {
605              keyplayer = i;
606              break;
607          }
608      }
609  
610      if (keyplayer < 0)
611      {
612          // If there are no players, we can never advance anyway
613  
614          return;
615      }
616  
617      if (localplayer == keyplayer)
618      {
619          // the key player does not adapt
620      }
621      else
622      {
623          if (maketic <= recvtic)
624          {
625              lasttime--;
626              // printf ("-");
627          }
628  
629          frameskip[frameon & 3] = oldnettics > recvtic;
630          oldnettics = maketic;
631  
632          if (frameskip[0] && frameskip[1] && frameskip[2] && frameskip[3])
633          {
634              skiptics = 1;
635              // printf ("+");
636          }
637      }
638  }
639  
640  // Returns true if there are players in the game:
641  
642  static boolean PlayersInGame(void)
643  {
644      boolean result = false;
645      unsigned int i;
646  
647      // If we are connected to a server, check if there are any players
648      // in the game.
649  
650      if (net_client_connected)
651      {
652          for (i = 0; i < NET_MAXPLAYERS; ++i)
653          {
654              result = result || local_playeringame[i];
655          }
656      }
657  
658      // Whether single or multi-player, unless we are running as a drone,
659      // we are in the game.
660  
661      if (!drone)
662      {
663          result = true;
664      }
665  
666      return result;
667  }
668  
669  // When using ticdup, certain values must be cleared out when running
670  // the duplicate ticcmds.
671  
672  static void TicdupSquash(ticcmd_set_t *set)
673  {
674      ticcmd_t *cmd;
675      unsigned int i;
676  
677      for (i = 0; i < NET_MAXPLAYERS ; ++i)
678      {
679          cmd = &set->cmds[i];
680          cmd->chatchar = 0;
681          if (cmd->buttons & BT_SPECIAL)
682              cmd->buttons = 0;
683      }
684  }
685  
686  // When running in single player mode, clear all the ingame[] array
687  // except the local player.
688  
689  static void SinglePlayerClear(ticcmd_set_t *set)
690  {
691      unsigned int i;
692  
693      for (i = 0; i < NET_MAXPLAYERS; ++i)
694      {
695          if (i != localplayer)
696          {
697              set->ingame[i] = false;
698          }
699      }
700  }
701  
702  //
703  // TryRunTics
704  //
705  
706  void TryRunTics (void)
707  {
708      int	i;
709      int	lowtic;
710      int	entertic;
711      static int oldentertics;
712      int realtics;
713      int	availabletics;
714      int	counts;
715  
716      // get real tics
717      entertic = I_GetTime() / ticdup;
718      realtics = entertic - oldentertics;
719      oldentertics = entertic;
720  
721      // in singletics mode, run a single tic every time this function
722      // is called.
723  
724      if (singletics)
725      {
726          BuildNewTic();
727      }
728      else
729      {
730          NetUpdate ();
731      }
732  
733      lowtic = GetLowTic();
734  
735      availabletics = lowtic - gametic/ticdup;
736  
737      // decide how many tics to run
738  
739      if (new_sync)
740      {
741  	counts = availabletics;
742      }
743      else
744      {
745          // decide how many tics to run
746          if (realtics < availabletics-1)
747              counts = realtics+1;
748          else if (realtics < availabletics)
749              counts = realtics;
750          else
751              counts = availabletics;
752  
753          if (counts < 1)
754              counts = 1;
755  
756          if (net_client_connected)
757          {
758              OldNetSync();
759          }
760      }
761  
762      if (counts < 1)
763  	counts = 1;
764  
765      // wait for new tics if needed
766  
767      while (!PlayersInGame() || lowtic < gametic/ticdup + counts)
768      {
769  	NetUpdate ();
770  
771          lowtic = GetLowTic();
772  
773  	if (lowtic < gametic/ticdup)
774  	    I_Error ("TryRunTics: lowtic < gametic");
775  
776          // Don't stay in this loop forever.  The menu is still running,
777          // so return to update the screen
778  
779  	if (I_GetTime() / ticdup - entertic > 0)
780  	{
781  	    return;
782  	}
783  
784          I_Sleep(1);
785      }
786  
787      // run the count * ticdup dics
788      while (counts--)
789      {
790          ticcmd_set_t *set;
791  
792          if (!PlayersInGame())
793          {
794              return;
795          }
796  
797          set = &ticdata[(gametic / ticdup) % BACKUPTICS];
798  
799          if (!net_client_connected)
800          {
801              SinglePlayerClear(set);
802          }
803  
804  	for (i=0 ; i<ticdup ; i++)
805  	{
806              if (gametic/ticdup > lowtic)
807                  I_Error ("gametic>lowtic");
808  
809              memcpy(local_playeringame, set->ingame, sizeof(local_playeringame));
810  
811              loop_interface->RunTic(set->cmds, set->ingame);
812  	    gametic++;
813  
814  	    // modify command for duplicated tics
815  
816              TicdupSquash(set);
817  	}
818  
819  	NetUpdate ();	// check for new console commands
820      }
821  }
822  
823  void D_RegisterLoopCallbacks(loop_interface_t *i)
824  {
825      loop_interface = i;
826  }