Source to src/d_net.c
// 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 <windows.h>
#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"
//#include "d_console.h"
#include "dconsole.h" //11.26.98 for compatibility
void WriteDebug(char *);
#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
char MsgText[256];
void WriteDebug(char *);
//
// 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;
void HandleKeyboard(void);
//
//
//
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 ; i<l ; i++)
c += ((unsigned *)&netbuffer->retransmitfrom)[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(debugfile)
{
int i;
int realretrans;
if (netbuffer->checksum & NCMD_RETRANSMIT)
realretrans = ExpandTics (netbuffer->retransmitfrom);
else
realretrans = -1;
sprintf(MsgText,"send (%i + %i, R %i) [%i] ",
ExpandTics(netbuffer->starttic),
netbuffer->numtics, realretrans, doomcom->datalength);
WriteDebug(MsgText);
for(i=0 ; i<doomcom->datalength ; i++)
{
sprintf (MsgText,"%i ",((byte *)netbuffer)[i]);
WriteDebug(MsgText);
}
sprintf (MsgText,"\n");
WriteDebug(MsgText);
}
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 ())
{
if (debugfile)
fprintf (debugfile,"bad packet length %i\n",doomcom->datalength);
return false;
}
if (NetbufferChecksum () != (netbuffer->checksum&NCMD_CHECKSUM) )
{
if (debugfile)
fprintf (debugfile,"bad packet checksum\n");
return false;
}
if (debugfile)
{
int realretrans;
int i;
if(netbuffer->checksum & NCMD_SETUP)
fprintf (debugfile,"setup packet\n");
else
{
if(netbuffer->checksum & NCMD_RETRANSMIT)
realretrans = ExpandTics (netbuffer->retransmitfrom);
else
realretrans = -1;
fprintf (debugfile,"get %i = (%i + %i, R %i)[%i] ",
doomcom->remotenode,
ExpandTics(netbuffer->starttic),
netbuffer->numtics, realretrans, doomcom->datalength);
for (i=0 ; i<doomcom->datalength ; i++)
fprintf (debugfile,"%i ",((byte *)netbuffer)[i]);
fprintf (debugfile,"\n");
}
}
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);
if (debugfile)
fprintf (debugfile,"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])
{
if (debugfile)
fprintf (debugfile,"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
if (debugfile)
fprintf (debugfile,"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
{
//WriteDebug("Went to listen\n"); //This happens alot! 11.9
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<newtics ; i++)
{
// 11.4.98 dlw UNUSED EMPTY: I_StartTic();
D_ProcessEvents();
if (maketic - gameticdiv >= BACKUPTICS/2-1)
break; // can't hold any more
//printf ("mk:%i ",maketic);
//WriteDebug("NetUpdate calling G_BuildTiccmd...\n");
G_BuildTiccmd(&localcmds[maketic%BACKUPTICS]);
maketic++;
}
if(singletics)return; // singletic update is syncronous
// send the packet to the other nodes
for (i=0 ; i<doomcom->numnodes ; 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);
//WriteDebug("NCMD_RETRANSMIT: Hsend\n"); //11.9.98
}
else
{
netbuffer->retransmitfrom = 0;
HSendPacket(i, 0); //11.9.98 find out how much this happens
//WriteDebug("Single Player/Console Player sent Packet\n");
}
}
// listen for other packets
listen:
GetPackets();
}
extern HACCEL ghAccel;
extern BOOL bQuit;
//
// CheckAbort
//
void CheckAbort(void)
{
event_t *ev;
int stoptic;
MSG msg;
stoptic = I_GetTime()+2;
while(I_GetTime() < stoptic)
{
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if(msg.message == WM_QUIT)
{
bQuit = TRUE;
break;
}
if(!TranslateAccelerator(msg.hwnd, ghAccel, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
HandleKeyboard();
// 11.4.98 dlw UNUSED EMPTY: I_StartTic();
}
// 11.4.98 dlw UNUSED EMPTY: 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];
static int timeout = 0;
autostart = true;
memset(gotinfo,0,sizeof(gotinfo));
if(doomcom->consoleplayer)
{
// listen for setup info from key player
WriteDebug("listening for network start info...Console\n");
while (1)
{
//timeout += I_GetTime();
//if (timeout > 8400)
// I_Error("Network game synchronization timed out...\n");
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
sprintf (MsgText,"sending network start info...\n");
WriteDebug(MsgText);
do
{
CheckAbort ();
for (i=0 ; i<doomcom->numnodes ; 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 ; i<doomcom->numnodes ; 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 ; i<MAXNETNODES ; i++)
{
nodeingame[i] = false;
nettics[i] = 0;
remoteresend[i] = false; // set when local needs tics
resendto[i] = 0; // which tic to start sending
}
// I_InitNetwork sets doomcom and netgame
I_InitNetwork();
if(doomcom->id != DOOMCOM_ID)
I_Error("Doomcom buffer invalid!");
netbuffer = &doomcom->data;
consoleplayer = displayplayer = doomcom->consoleplayer;
if(netgame) D_ArbitrateNetStart();
//printf ("startskill %i deathmatch: %i startmap: %i startepisode: %i\n",
sprintf (MsgText, "startskill %i deathmatch: %i startmap: %i startepisode: %i\n",
startskill, deathmatch, startmap, startepisode);
WriteDebug(MsgText);
// read values out of doomcom
ticdup = doomcom->ticdup;
maxsend = BACKUPTICS/(2*ticdup)-1;
if(maxsend<1) maxsend = 1;
for(i=0 ; i<doomcom->numplayers ; i++)
playeringame[i] = true;
for(i=0 ; i<doomcom->numnodes ; i++)
nodeingame[i] = true;
//printf ("player %i of %i (%i nodes)\n",
sprintf (MsgText, "player %i of %i (%i nodes)\n",
consoleplayer+1, doomcom->numplayers, doomcom->numnodes);
WriteDebug(MsgText);
}
//
// D_QuitNetGame
// Called before quitting to leave a net game
// without hanging the other players
//
void D_QuitNetGame(void)
{
int i, j;
if(debugfile)
fclose(debugfile);
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 ; j<doomcom->numnodes ; j++)
if (nodeingame[j])
HSendPacket (j, NCMD_EXIT);
I_WaitVBL(1);
}
WSACleanup();
}
//
// 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; //11.9.98 dlw UNUSED really
// get real tics
entertic = I_GetTime()/ticdup;
realtics = entertic - oldentertics;
oldentertics = entertic;
// get available tics
//WriteDebug("NetUpdate...\n");
NetUpdate();
//WriteDebug("NetUpdate done...\n");
lowtic = MAXINT;
// numplaying = 0; 11.9.98 dlw opt
for(i = 0; i < doomcom->numnodes; i++)
{
if(nodeingame[i])
{
// numplaying++; 11.9.98 dlw
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++;
if (debugfile)
fprintf(debugfile, "=======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 < MAXPLAYERS; i++)
for(i=0; i<doomcom->numplayers; i++)
if(playeringame[i]) break;
//if(consoleplayer == i)
// {
// the key player does not adapt
// }
// else
if(consoleplayer != i)
{
if(nettics[0] <= nettics[nodeforplayer[i]])
{
gametime--;
// printf ("-");
}
frameskip[frameon&3] = (oldnettics > nettics[nodeforplayer[i]]);
oldnettics = nettics[0];
if(frameskip[0] && frameskip[1] && frameskip[2] && frameskip[3])
{
skiptics = 1;
// printf ("+");
}
}
}// !demoplayback
// wait for new tics if needed
while(lowtic < gametic/ticdup + counts)
{
NetUpdate();
lowtic = MAXINT;
for (i = 0; i < doomcom->numnodes; 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
//WriteDebug("I_GetTime...\n");
if(I_GetTime()/ticdup - entertic >= 20)
{
CO_Ticker();
//WriteDebug("M_Ticker...\n");
M_Ticker();
return;
}
}
// run the count * ticdup dics
while(counts--)
{
for (i = 0; i < ticdup; i++)
{
if(gametic/ticdup > lowtic)
I_Error ("gametic>lowtic");
if(advancedemo)
{
//WriteDebug("D_DoAdvanceDemo...\n");
D_DoAdvanceDemo();
}
CO_Ticker();
//WriteDebug("M_Ticker...\n");
M_Ticker();
//WriteDebug("G_Ticker...\n");
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; j < MAXPLAYERS; j++) 11.9.98 dlw optimize
for(j = 0; j < doomcom->numplayers; j++)
{
cmd = &netcmds[j][buf];
cmd->chatchar = 0;
if(cmd->buttons & BT_SPECIAL)
cmd->buttons = 0;
}
}
}
//WriteDebug("NetUpdate - 2...\n");
NetUpdate(); // check for new console commands
}
}