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 }