Source to src/d_net.c


Enter a symbol's name here to quickly find it.

// 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
	}
}