// Emacs style mode select -*- C++ -*- //----------------------------------------------------------------------------- // // $Id:$ // // Copyright (C) 1993-1996 by id Software, Inc. // // This source is available for distribution and/or modification // only under the terms of the DOOM Source Code License as // published by id Software. All rights reserved. // // The source is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License // for more details. // // $Log:$ // // DESCRIPTION: // DOOM Network game communication and protocol, // all OS independend parts. // //----------------------------------------------------------------------------- static const char rcsid[] = "$Id: d_net.c,v 1.3 1997/02/03 22:01:47 b1 Exp $"; #include "m_menu.h" #include "i_system.h" #include "i_video.h" #include "i_net.h" #include "g_game.h" #include "doomdef.h" #include "doomstat.h" #define NCMD_EXIT 0x80000000 #define NCMD_RETRANSMIT 0x40000000 #define NCMD_SETUP 0x20000000 #define NCMD_KILL 0x10000000 // kill game #define NCMD_CHECKSUM 0x0fffffff doomcom_t* doomcom; doomdata_t* netbuffer; // points inside doomcom // // NETWORKING // // gametic is the tic about to (or currently being) run // maketic is the tick that hasn't had control made for it yet // nettics[] has the maketics for all players // // a gametic cannot be run until nettics[] > gametic for all players // #define RESENDCOUNT 10 #define PL_DRONE 0x80 // bit flag in doomdata->player ticcmd_t localcmds[BACKUPTICS]; ticcmd_t netcmds[MAXPLAYERS][BACKUPTICS]; int nettics[MAXNETNODES]; boolean nodeingame[MAXNETNODES]; // set false as nodes leave game boolean remoteresend[MAXNETNODES]; // set when local needs tics int resendto[MAXNETNODES]; // set when remote needs tics int resendcount[MAXNETNODES]; int nodeforplayer[MAXPLAYERS]; int maketic; int lastnettic; int skiptics; int ticdup; int maxsend; // BACKUPTICS/(2*ticdup)-1 void D_ProcessEvents (void); void G_BuildTiccmd (ticcmd_t *cmd); void D_DoAdvanceDemo (void); boolean reboundpacket; doomdata_t reboundstore; // // // int NetbufferSize (void) { return (int)&(((doomdata_t *)0)->cmds[netbuffer->numtics]); } // // Checksum // unsigned NetbufferChecksum (void) { unsigned c; int i,l; c = 0x1234567; // FIXME -endianess? #ifdef NORMALUNIX return 0; // byte order problems #endif l = (NetbufferSize () - (int)&(((doomdata_t *)0)->retransmitfrom))/4; for (i=0 ; iretransmitfrom)[i] * (i+1); return c & NCMD_CHECKSUM; } // // // int ExpandTics (int low) { int delta; delta = low - (maketic&0xff); if (delta >= -64 && delta <= 64) return (maketic&~0xff) + low; if (delta > 64) return (maketic&~0xff) - 256 + low; if (delta < -64) return (maketic&~0xff) + 256 + low; I_Error ("ExpandTics: strange value %i at maketic %i",low,maketic); return 0; } // // HSendPacket // void HSendPacket (int node, int flags ) { netbuffer->checksum = NetbufferChecksum () | flags; if (!node) { reboundstore = *netbuffer; reboundpacket = true; return; } if (demoplayback) return; if (!netgame) I_Error ("Tried to transmit to another node"); doomcom->command = CMD_SEND; doomcom->remotenode = node; doomcom->datalength = NetbufferSize (); #if 0 // AJH HACK if (debugfile) { int i; int realretrans; if (netbuffer->checksum & NCMD_RETRANSMIT) realretrans = ExpandTics (netbuffer->retransmitfrom); else realretrans = -1; I_DBGprintf("send (%i + %i, R %i) [%i] ", ExpandTics(netbuffer->starttic), netbuffer->numtics, realretrans, doomcom->datalength); for (i=0 ; idatalength ; i++) I_DBGprintf("%i ",((byte *)netbuffer)[i]); I_DBGprintf("\n"); } #endif I_NetCmd (); } // // HGetPacket // Returns false if no packet is waiting // boolean HGetPacket (void) { if (reboundpacket) { *netbuffer = reboundstore; doomcom->remotenode = 0; reboundpacket = false; return true; } if (!netgame) return false; if (demoplayback) return false; doomcom->command = CMD_GET; I_NetCmd (); if (doomcom->remotenode == -1) return false; if (doomcom->datalength != NetbufferSize ()) { I_DBGprintf("bad packet length %i\n",doomcom->datalength); return false; } if (NetbufferChecksum () != (netbuffer->checksum&NCMD_CHECKSUM) ) { I_DBGprintf("bad packet checksum\n"); return false; } #if 0 // AJH uGFX Hack if (debugfile) { int realretrans; int i; if (netbuffer->checksum & NCMD_SETUP) I_DBGprintf("setup packet\n"); else { if (netbuffer->checksum & NCMD_RETRANSMIT) realretrans = ExpandTics (netbuffer->retransmitfrom); else realretrans = -1; I_DBGprintf("get %i = (%i + %i, R %i)[%i] ", doomcom->remotenode, ExpandTics(netbuffer->starttic), netbuffer->numtics, realretrans, doomcom->datalength); for (i=0 ; idatalength ; i++) I_DBGprintf("%i ",((byte *)netbuffer)[i]); I_DBGprintf("\n"); } } #endif return true; } // // GetPackets // char exitmsg[80]; void GetPackets (void) { int netconsole; int netnode; ticcmd_t *src, *dest; int realend; int realstart; while ( HGetPacket() ) { if (netbuffer->checksum & NCMD_SETUP) continue; // extra setup packet netconsole = netbuffer->player & ~PL_DRONE; netnode = doomcom->remotenode; // to save bytes, only the low byte of tic numbers are sent // Figure out what the rest of the bytes are realstart = ExpandTics (netbuffer->starttic); realend = (realstart+netbuffer->numtics); // check for exiting the game if (netbuffer->checksum & NCMD_EXIT) { if (!nodeingame[netnode]) continue; nodeingame[netnode] = false; playeringame[netconsole] = false; strcpy (exitmsg, "Player 1 left the game"); exitmsg[7] += netconsole; players[consoleplayer].message = exitmsg; if (demorecording) G_CheckDemoStatus (); continue; } // check for a remote game kill if (netbuffer->checksum & NCMD_KILL) I_Error ("Killed by network driver"); nodeforplayer[netconsole] = netnode; // check for retransmit request if ( resendcount[netnode] <= 0 && (netbuffer->checksum & NCMD_RETRANSMIT) ) { resendto[netnode] = ExpandTics(netbuffer->retransmitfrom); I_DBGprintf ("retransmit from %i\n", resendto[netnode]); resendcount[netnode] = RESENDCOUNT; } else resendcount[netnode]--; // check for out of order / duplicated packet if (realend == nettics[netnode]) continue; if (realend < nettics[netnode]) { I_DBGprintf ("out of order packet (%i + %i)\n" , realstart,netbuffer->numtics); continue; } // check for a missed packet if (realstart > nettics[netnode]) { // stop processing until the other system resends the missed tics I_DBGprintf("missed tics from %i (%i - %i)\n", netnode, realstart, nettics[netnode]); remoteresend[netnode] = true; continue; } // update command store from the packet { int start; remoteresend[netnode] = false; start = nettics[netnode] - realstart; src = &netbuffer->cmds[start]; while (nettics[netnode] < realend) { dest = &netcmds[netconsole][nettics[netnode]%BACKUPTICS]; nettics[netnode]++; *dest = *src; src++; } } } } // // NetUpdate // Builds ticcmds for console player, // sends out a packet // int gametime; void NetUpdate (void) { int nowtime; int newtics; int i,j; int realstart; int gameticdiv; // check time nowtime = I_GetTime ()/ticdup; newtics = nowtime - gametime; gametime = nowtime; if (newtics <= 0) // nothing new to update goto listen; if (skiptics <= newtics) { newtics -= skiptics; skiptics = 0; } else { skiptics -= newtics; newtics = 0; } netbuffer->player = consoleplayer; // build new ticcmds for console player gameticdiv = gametic/ticdup; for (i=0 ; i= BACKUPTICS/2-1) break; // can't hold any more //I_printf("mk:%i ",maketic); G_BuildTiccmd (&localcmds[maketic%BACKUPTICS]); maketic++; } if (singletics) return; // singletic update is syncronous // send the packet to the other nodes for (i=0 ; inumnodes ; i++) if (nodeingame[i]) { netbuffer->starttic = realstart = resendto[i]; netbuffer->numtics = maketic - realstart; if (netbuffer->numtics > BACKUPTICS) I_Error ("NetUpdate: netbuffer->numtics > BACKUPTICS"); resendto[i] = maketic - doomcom->extratics; for (j=0 ; j< netbuffer->numtics ; j++) netbuffer->cmds[j] = localcmds[(realstart+j)%BACKUPTICS]; if (remoteresend[i]) { netbuffer->retransmitfrom = nettics[i]; HSendPacket (i, NCMD_RETRANSMIT); } else { netbuffer->retransmitfrom = 0; HSendPacket (i, 0); } } // listen for other packets listen: GetPackets (); } // // CheckAbort // void CheckAbort (void) { event_t *ev; int stoptic; stoptic = I_GetTime () + 2; while (I_GetTime() < stoptic) I_StartTic (); I_StartTic (); for ( ; eventtail != eventhead ; eventtail = (++eventtail)&(MAXEVENTS-1) ) { ev = &events[eventtail]; if (ev->type == ev_keydown && ev->data1 == KEY_ESCAPE) I_Error ("Network game synchronization aborted."); } } // // D_ArbitrateNetStart // void D_ArbitrateNetStart (void) { int i; boolean gotinfo[MAXNETNODES]; autostart = true; memset (gotinfo,0,sizeof(gotinfo)); if (doomcom->consoleplayer) { // listen for setup info from key player I_printf ("listening for network start info...\n"); while (1) { CheckAbort (); if (!HGetPacket ()) continue; if (netbuffer->checksum & NCMD_SETUP) { if (netbuffer->player != VERSION) I_Error ("Different DOOM versions cannot play a net game!"); startskill = netbuffer->retransmitfrom & 15; deathmatch = (netbuffer->retransmitfrom & 0xc0) >> 6; nomonsters = (netbuffer->retransmitfrom & 0x20) > 0; respawnparm = (netbuffer->retransmitfrom & 0x10) > 0; startmap = netbuffer->starttic & 0x3f; startepisode = netbuffer->starttic >> 6; return; } } } else { // key player, send the setup info I_printf ("sending network start info...\n"); do { CheckAbort (); for (i=0 ; inumnodes ; i++) { netbuffer->retransmitfrom = startskill; if (deathmatch) netbuffer->retransmitfrom |= (deathmatch<<6); if (nomonsters) netbuffer->retransmitfrom |= 0x20; if (respawnparm) netbuffer->retransmitfrom |= 0x10; netbuffer->starttic = startepisode * 64 + startmap; netbuffer->player = VERSION; netbuffer->numtics = 0; HSendPacket (i, NCMD_SETUP); } #if 1 for(i = 10 ; i && HGetPacket(); --i) { if((netbuffer->player&0x7f) < MAXNETNODES) gotinfo[netbuffer->player&0x7f] = true; } #else while (HGetPacket ()) { gotinfo[netbuffer->player&0x7f] = true; } #endif for (i=1 ; inumnodes ; i++) if (!gotinfo[i]) break; } while (i < doomcom->numnodes); } } // // D_CheckNetGame // Works out player numbers among the net participants // extern int viewangleoffset; void D_CheckNetGame (void) { int i; for (i=0 ; iid != DOOMCOM_ID) I_Error ("Doomcom buffer invalid!"); netbuffer = &doomcom->data; consoleplayer = displayplayer = doomcom->consoleplayer; if (netgame) D_ArbitrateNetStart (); I_printf ("startskill %i deathmatch: %i startmap: %i startepisode: %i\n", startskill, deathmatch, startmap, startepisode); // read values out of doomcom ticdup = doomcom->ticdup; maxsend = BACKUPTICS/(2*ticdup)-1; if (maxsend<1) maxsend = 1; for (i=0 ; inumplayers ; i++) playeringame[i] = true; for (i=0 ; inumnodes ; i++) nodeingame[i] = true; I_printf ("player %i of %i (%i nodes)\n", consoleplayer+1, doomcom->numplayers, doomcom->numnodes); } // // D_QuitNetGame // Called before quitting to leave a net game // without hanging the other players // void D_QuitNetGame (void) { int i, j; if (!netgame || !usergame || consoleplayer == -1 || demoplayback) return; // send a bunch of packets for security netbuffer->player = consoleplayer; netbuffer->numtics = 0; for (i=0 ; i<4 ; i++) { for (j=1 ; jnumnodes ; j++) if (nodeingame[j]) HSendPacket (j, NCMD_EXIT); I_WaitVBL (1); } } // // TryRunTics // int frametics[4]; int frameon; int frameskip[4]; int oldnettics; extern boolean advancedemo; void TryRunTics (void) { int i; int lowtic; int entertic; static int oldentertics; int realtics; int availabletics; int counts; int numplaying; // get real tics entertic = I_GetTime ()/ticdup; realtics = entertic - oldentertics; oldentertics = entertic; // get available tics NetUpdate (); lowtic = MAXINT; numplaying = 0; for (i=0 ; inumnodes ; i++) { if (nodeingame[i]) { numplaying++; if (nettics[i] < lowtic) lowtic = nettics[i]; } } availabletics = lowtic - gametic/ticdup; // decide how many tics to run if (realtics < availabletics-1) counts = realtics+1; else if (realtics < availabletics) counts = realtics; else counts = availabletics; if (counts < 1) counts = 1; frameon++; I_DBGprintf("=======real: %i avail: %i game: %i\n", realtics, availabletics,counts); if (!demoplayback) { // ideally nettics[0] should be 1 - 3 tics above lowtic // if we are consistantly slower, speed up time for (i=0 ; i nettics[nodeforplayer[i]]); oldnettics = nettics[0]; if (frameskip[0] && frameskip[1] && frameskip[2] && frameskip[3]) { skiptics = 1; // I_printf ("+"); } } }// demoplayback // wait for new tics if needed while (lowtic < gametic/ticdup + counts) { NetUpdate (); lowtic = MAXINT; for (i=0 ; inumnodes ; i++) if (nodeingame[i] && nettics[i] < lowtic) lowtic = nettics[i]; if (lowtic < gametic/ticdup) I_Error ("TryRunTics: lowtic < gametic"); // don't stay in here forever -- give the menu a chance to work if (I_GetTime ()/ticdup - entertic >= 20) { M_Ticker (); return; } } // run the count * ticdup dics while (counts--) { for (i=0 ; i lowtic) I_Error ("gametic>lowtic"); if (advancedemo) D_DoAdvanceDemo (); M_Ticker (); G_Ticker (); gametic++; // modify command for duplicated tics if (i != ticdup-1) { ticcmd_t *cmd; int buf; int j; buf = (gametic/ticdup)%BACKUPTICS; for (j=0 ; jchatchar = 0; if (cmd->buttons & BT_SPECIAL) cmd->buttons = 0; } } } NetUpdate (); // check for new console commands } } 9'>379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604