doom3-bfg/doomclassic/doom/d_net.cpp

831 lines
19 KiB
C++

/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#include "Precompiled.h"
#include "globaldata.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 "doomlib.h"
#include "Main.h"
#include "d3xp/Game_local.h"
void I_GetEvents( controller_t * );
void D_ProcessEvents (void);
void G_BuildTiccmd (ticcmd_t *cmd, idUserCmdMgr *, int newTics );
void D_DoAdvanceDemo (void);
extern bool globalNetworking;
//
// NETWORKING
//
// ::g->gametic is the tic about to (or currently being) run
// ::g->maketic is the tick that hasn't had control made for it yet
// ::g->nettics[] has the maketics for all ::g->players
//
// a ::g->gametic cannot be run until ::g->nettics[] > ::g->gametic for all ::g->players
//
#define NET_TIMEOUT 1 * TICRATE
//
//
//
int NetbufferSize (void)
{
int size = (intptr_t)&(((doomdata_t *)0)->cmds[::g->netbuffer->numtics]);
return size;
}
//
// Checksum
//
unsigned NetbufferChecksum (void)
{
unsigned c;
int i,l;
c = 0x1234567;
if ( globalNetworking ) {
l = (NetbufferSize () - (intptr_t)&(((doomdata_t *)0)->retransmitfrom))/4;
for (i=0 ; i<l ; i++)
c += ((unsigned *)&::g->netbuffer->retransmitfrom)[i] * (i+1);
}
return c & NCMD_CHECKSUM;
}
//
//
//
int ExpandTics (int low)
{
int delta;
delta = low - (::g->maketic&0xff);
if (delta >= -64 && delta <= 64)
return (::g->maketic&~0xff) + low;
if (delta > 64)
return (::g->maketic&~0xff) - 256 + low;
if (delta < -64)
return (::g->maketic&~0xff) + 256 + low;
I_Error ("ExpandTics: strange value %i at ::g->maketic %i",low,::g->maketic);
return 0;
}
//
// HSendPacket
//
void
HSendPacket
(int node,
int flags )
{
::g->netbuffer->checksum = NetbufferChecksum () | flags;
if (!node)
{
::g->reboundstore = *::g->netbuffer;
::g->reboundpacket = true;
return;
}
if (::g->demoplayback)
return;
if (!::g->netgame)
I_Error ("Tried to transmit to another node");
::g->doomcom.command = CMD_SEND;
::g->doomcom.remotenode = node;
::g->doomcom.datalength = NetbufferSize ();
if (::g->debugfile)
{
int i;
int realretrans;
if (::g->netbuffer->checksum & NCMD_RETRANSMIT)
realretrans = ExpandTics (::g->netbuffer->retransmitfrom);
else
realretrans = -1;
fprintf (::g->debugfile,"send (%i + %i, R %i) [%i] ",
ExpandTics(::g->netbuffer->starttic),
::g->netbuffer->numtics, realretrans, ::g->doomcom.datalength);
for (i=0 ; i < ::g->doomcom.datalength ; i++)
fprintf (::g->debugfile,"%i ",((byte *)::g->netbuffer)[i]);
fprintf (::g->debugfile,"\n");
}
I_NetCmd ();
}
//
// HGetPacket
// Returns false if no packet is waiting
//
qboolean HGetPacket (void)
{
if (::g->reboundpacket)
{
*::g->netbuffer = ::g->reboundstore;
::g->doomcom.remotenode = 0;
::g->reboundpacket = false;
return true;
}
if (!::g->netgame)
return false;
if (::g->demoplayback)
return false;
::g->doomcom.command = CMD_GET;
I_NetCmd ();
if (::g->doomcom.remotenode == -1)
return false;
if (::g->doomcom.datalength != NetbufferSize ())
{
if (::g->debugfile)
fprintf (::g->debugfile,"bad packet length %i\n",::g->doomcom.datalength);
return false;
}
// ALAN NETWORKING -- this fails a lot on 4 player split debug!!
// TODO: Networking
#ifdef ID_ENABLE_DOOM_CLASSIC_NETWORKING
if ( !gameLocal->IsSplitscreen() && NetbufferChecksum() != (::g->netbuffer->checksum&NCMD_CHECKSUM) )
{
if (::g->debugfile) {
fprintf (::g->debugfile,"bad packet checksum\n");
}
return false;
}
#endif
if (::g->debugfile)
{
int realretrans;
int i;
if (::g->netbuffer->checksum & NCMD_SETUP)
fprintf (::g->debugfile,"setup packet\n");
else
{
if (::g->netbuffer->checksum & NCMD_RETRANSMIT)
realretrans = ExpandTics (::g->netbuffer->retransmitfrom);
else
realretrans = -1;
fprintf (::g->debugfile,"get %i = (%i + %i, R %i)[%i] ",
::g->doomcom.remotenode,
ExpandTics(::g->netbuffer->starttic),
::g->netbuffer->numtics, realretrans, ::g->doomcom.datalength);
for (i=0 ; i < ::g->doomcom.datalength ; i++)
fprintf (::g->debugfile,"%i ",((byte *)::g->netbuffer)[i]);
fprintf (::g->debugfile,"\n");
}
}
return true;
}
//
// GetPackets
//
void GetPackets (void)
{
int netconsole;
int netnode;
ticcmd_t *src, *dest;
int realend;
int realstart;
while ( HGetPacket() )
{
if (::g->netbuffer->checksum & NCMD_SETUP)
continue; // extra setup packet
netconsole = ::g->netbuffer->player & ~PL_DRONE;
netnode = ::g->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 (::g->netbuffer->starttic);
realend = (realstart+::g->netbuffer->numtics);
// check for exiting the game
if (::g->netbuffer->checksum & NCMD_EXIT)
{
if (!::g->nodeingame[netnode])
continue;
::g->nodeingame[netnode] = false;
::g->playeringame[netconsole] = false;
strcpy (::g->exitmsg, "Player 1 left the game");
::g->exitmsg[7] += netconsole;
::g->players[::g->consoleplayer].message = ::g->exitmsg;
if( ::g->demorecording ) {
G_CheckDemoStatus();
}
continue;
}
// check for a remote game kill
/*
if (::g->netbuffer->checksum & NCMD_KILL)
I_Error ("Killed by network driver");
*/
::g->nodeforplayer[netconsole] = netnode;
// check for retransmit request
if ( ::g->resendcount[netnode] <= 0
&& (::g->netbuffer->checksum & NCMD_RETRANSMIT) )
{
::g->resendto[netnode] = ExpandTics(::g->netbuffer->retransmitfrom);
if (::g->debugfile)
fprintf (::g->debugfile,"retransmit from %i\n", ::g->resendto[netnode]);
::g->resendcount[netnode] = RESENDCOUNT;
}
else
::g->resendcount[netnode]--;
// check for out of order / duplicated packet
if (realend == ::g->nettics[netnode])
continue;
if (realend < ::g->nettics[netnode])
{
if (::g->debugfile)
fprintf (::g->debugfile,
"out of order packet (%i + %i)\n" ,
realstart,::g->netbuffer->numtics);
continue;
}
// check for a missed packet
if (realstart > ::g->nettics[netnode])
{
// stop processing until the other system resends the missed tics
if (::g->debugfile)
fprintf (::g->debugfile,
"missed tics from %i (%i - %i)\n",
netnode, realstart, ::g->nettics[netnode]);
::g->remoteresend[netnode] = true;
continue;
}
// update command store from the packet
{
int start;
::g->remoteresend[netnode] = false;
start = ::g->nettics[netnode] - realstart;
src = &::g->netbuffer->cmds[start];
while (::g->nettics[netnode] < realend)
{
dest = &::g->netcmds[netconsole][::g->nettics[netnode]%BACKUPTICS];
::g->nettics[netnode]++;
*dest = *src;
src++;
}
}
}
}
//
// NetUpdate
// Builds ticcmds for console player,
// sends out a packet
//
void NetUpdate ( idUserCmdMgr * userCmdMgr )
{
int nowtime;
int newtics;
int i,j;
int realstart;
int gameticdiv;
// check time
nowtime = I_GetTime ()/::g->ticdup;
newtics = nowtime - ::g->gametime;
::g->gametime = nowtime;
if (newtics <= 0) // nothing new to update
goto listen;
if (::g->skiptics <= newtics)
{
newtics -= ::g->skiptics;
::g->skiptics = 0;
}
else
{
::g->skiptics -= newtics;
newtics = 0;
}
::g->netbuffer->player = ::g->consoleplayer;
// build new ticcmds for console player
gameticdiv = ::g->gametic/::g->ticdup;
for (i=0 ; i<newtics ; i++)
{
//I_GetEvents( ::g->I_StartTicCallback () );
D_ProcessEvents ();
if (::g->maketic - gameticdiv >= BACKUPTICS/2-1) {
printf( "Out of room for ticcmds: maketic = %d, gameticdiv = %d\n", ::g->maketic, gameticdiv );
break; // can't hold any more
}
//I_Printf ("mk:%i ",::g->maketic);
// Grab the latest tech5 command
G_BuildTiccmd (&::g->localcmds[::g->maketic%BACKUPTICS], userCmdMgr, newtics );
::g->maketic++;
}
if (::g->singletics)
return; // singletic update is syncronous
// send the packet to the other ::g->nodes
for (i=0 ; i < ::g->doomcom.numnodes ; i++) {
if (::g->nodeingame[i]) {
::g->netbuffer->starttic = realstart = ::g->resendto[i];
::g->netbuffer->numtics = ::g->maketic - realstart;
if (::g->netbuffer->numtics > BACKUPTICS)
I_Error ("NetUpdate: ::g->netbuffer->numtics > BACKUPTICS");
::g->resendto[i] = ::g->maketic - ::g->doomcom.extratics;
for (j=0 ; j< ::g->netbuffer->numtics ; j++)
::g->netbuffer->cmds[j] =
::g->localcmds[(realstart+j)%BACKUPTICS];
if (::g->remoteresend[i])
{
::g->netbuffer->retransmitfrom = ::g->nettics[i];
HSendPacket (i, NCMD_RETRANSMIT);
}
else
{
::g->netbuffer->retransmitfrom = 0;
HSendPacket (i, 0);
}
}
}
// listen for other packets
listen:
GetPackets ();
}
//
// CheckAbort
//
void CheckAbort (void)
{
// DHM - Time starts at 0 tics when starting a multiplayer game, so we can
// check for timeouts easily. If we're still waiting after N seconds, abort.
if ( I_GetTime() > NET_TIMEOUT ) {
// TOOD: Show error & leave net game.
printf( "NET GAME TIMED OUT!\n" );
//gameLocal->showFatalErrorMessage( XuiLookupStringTable(globalStrings,L"Timed out waiting for match start.") );
D_QuitNetGame();
session->QuitMatch();
common->Dialog().AddDialog( GDM_OPPONENT_CONNECTION_LOST, DIALOG_ACCEPT, NULL, NULL, false );
}
}
//
// D_ArbitrateNetStart
//
bool D_ArbitrateNetStart (void)
{
int i;
::g->autostart = true;
if (::g->doomcom.consoleplayer)
{
// listen for setup info from key player
CheckAbort ();
if (!HGetPacket ())
return false;
if (::g->netbuffer->checksum & NCMD_SETUP)
{
printf( "Received setup info\n" );
if (::g->netbuffer->player != VERSION)
I_Error ("Different DOOM versions cannot play a net game!");
::g->startskill = (skill_t)(::g->netbuffer->retransmitfrom & 15);
::g->deathmatch = (::g->netbuffer->retransmitfrom & 0xc0) >> 6;
::g->nomonsters = (::g->netbuffer->retransmitfrom & 0x20) > 0;
::g->respawnparm = (::g->netbuffer->retransmitfrom & 0x10) > 0;
// VV original xbox doom :: don't do this.. it will be setup from the launcher
//::g->startmap = ::g->netbuffer->starttic & 0x3f;
//::g->startepisode = ::g->netbuffer->starttic >> 6;
return true;
}
return false;
}
else
{
// key player, send the setup info
CheckAbort ();
for (i=0 ; i < ::g->doomcom.numnodes ; i++)
{
printf( "Sending setup info to node %d\n", i );
::g->netbuffer->retransmitfrom = ::g->startskill;
if (::g->deathmatch)
::g->netbuffer->retransmitfrom |= (::g->deathmatch<<6);
if (::g->nomonsters)
::g->netbuffer->retransmitfrom |= 0x20;
if (::g->respawnparm)
::g->netbuffer->retransmitfrom |= 0x10;
::g->netbuffer->starttic = ::g->startepisode * 64 + ::g->startmap;
::g->netbuffer->player = VERSION;
::g->netbuffer->numtics = 0;
HSendPacket (i, NCMD_SETUP);
}
while (HGetPacket ())
{
::g->gotinfo[::g->netbuffer->player&0x7f] = true;
}
for (i=1 ; i < ::g->doomcom.numnodes ; i++) {
if (!::g->gotinfo[i])
break;
}
if (i >= ::g->doomcom.numnodes)
return true;
return false;
}
}
//
// D_CheckNetGame
// Works out player numbers among the net participants
//
void D_CheckNetGame (void)
{
int i;
for (i=0 ; i<MAXNETNODES ; i++)
{
::g->nodeingame[i] = false;
::g->nettics[i] = 0;
::g->remoteresend[i] = false; // set when local needs tics
::g->resendto[i] = 0; // which tic to start sending
}
// I_InitNetwork sets ::g->doomcom and ::g->netgame
I_InitNetwork ();
#ifdef ID_ENABLE_DOOM_CLASSIC_NETWORKING
if (::g->doomcom.id != DOOMCOM_ID)
I_Error ("Doomcom buffer invalid!");
#endif
::g->netbuffer = &::g->doomcom.data;
::g->consoleplayer = ::g->displayplayer = ::g->doomcom.consoleplayer;
}
bool D_PollNetworkStart()
{
int i;
if (::g->netgame)
{
if (D_ArbitrateNetStart () == false)
return false;
}
I_Printf ("startskill %i deathmatch: %i startmap: %i startepisode: %i\n",
::g->startskill, ::g->deathmatch, ::g->startmap, ::g->startepisode);
// read values out of ::g->doomcom
::g->ticdup = ::g->doomcom.ticdup;
::g->maxsend = BACKUPTICS/(2*::g->ticdup)-1;
if (::g->maxsend<1)
::g->maxsend = 1;
for (i=0 ; i < ::g->doomcom.numplayers ; i++)
::g->playeringame[i] = true;
for (i=0 ; i < ::g->doomcom.numnodes ; i++)
::g->nodeingame[i] = true;
I_Printf ("player %i of %i (%i ::g->nodes)\n",
::g->consoleplayer+1, ::g->doomcom.numplayers, ::g->doomcom.numnodes);
return true;
}
//
// D_QuitNetGame
// Called before quitting to leave a net game
// without hanging the other ::g->players
//
void D_QuitNetGame (void)
{
int i;
if ( (!::g->netgame && !::g->usergame) || ::g->consoleplayer == -1 || ::g->demoplayback || ::g->netbuffer == NULL )
return;
// send a quit packet to the other nodes
::g->netbuffer->player = ::g->consoleplayer;
::g->netbuffer->numtics = 0;
for ( i=1; i < ::g->doomcom.numnodes; i++ ) {
if ( ::g->nodeingame[i] ) {
HSendPacket( i, NCMD_EXIT );
}
}
DoomLib::SendNetwork();
for (i=1 ; i<MAXNETNODES ; i++)
{
::g->nodeingame[i] = false;
::g->nettics[i] = 0;
::g->remoteresend[i] = false; // set when local needs tics
::g->resendto[i] = 0; // which tic to start sending
}
//memset (&::g->doomcom, 0, sizeof(::g->doomcom) );
// Reset singleplayer state
::g->doomcom.id = DOOMCOM_ID;
::g->doomcom.ticdup = 1;
::g->doomcom.extratics = 0;
::g->doomcom.numplayers = ::g->doomcom.numnodes = 1;
::g->doomcom.deathmatch = false;
::g->doomcom.consoleplayer = 0;
::g->netgame = false;
::g->netbuffer = &::g->doomcom.data;
::g->consoleplayer = ::g->displayplayer = ::g->doomcom.consoleplayer;
::g->ticdup = ::g->doomcom.ticdup;
::g->maxsend = BACKUPTICS/(2*::g->ticdup)-1;
if (::g->maxsend<1)
::g->maxsend = 1;
for (i=0 ; i < ::g->doomcom.numplayers ; i++)
::g->playeringame[i] = true;
for (i=0 ; i < ::g->doomcom.numnodes ; i++)
::g->nodeingame[i] = true;
}
//
// TryRunTics
//
bool TryRunTics ( idUserCmdMgr * userCmdMgr )
{
int i;
int lowtic_node = -1;
// get real tics
::g->trt_entertic = I_GetTime ()/::g->ticdup;
::g->trt_realtics = ::g->trt_entertic - ::g->oldtrt_entertics;
::g->oldtrt_entertics = ::g->trt_entertic;
// get available tics
NetUpdate ( userCmdMgr );
::g->trt_lowtic = MAXINT;
::g->trt_numplaying = 0;
for (i=0 ; i < ::g->doomcom.numnodes ; i++) {
if (::g->nodeingame[i]) {
::g->trt_numplaying++;
if (::g->nettics[i] < ::g->trt_lowtic) {
::g->trt_lowtic = ::g->nettics[i];
lowtic_node = i;
}
}
}
::g->trt_availabletics = ::g->trt_lowtic - ::g->gametic/::g->ticdup;
// decide how many tics to run
if (::g->trt_realtics < ::g->trt_availabletics-1) {
::g->trt_counts = ::g->trt_realtics+1;
} else if (::g->trt_realtics < ::g->trt_availabletics) {
::g->trt_counts = ::g->trt_realtics;
} else {
::g->trt_counts = ::g->trt_availabletics;
}
if (::g->trt_counts < 1) {
::g->trt_counts = 1;
}
::g->frameon++;
if (::g->debugfile) {
fprintf (::g->debugfile, "=======real: %i avail: %i game: %i\n", ::g->trt_realtics, ::g->trt_availabletics,::g->trt_counts);
}
if ( !::g->demoplayback )
{
// ideally ::g->nettics[0] should be 1 - 3 tics above ::g->trt_lowtic
// if we are consistantly slower, speed up time
for (i=0 ; i<MAXPLAYERS ; i++) {
if (::g->playeringame[i]) {
break;
}
}
if (::g->consoleplayer == i) {
// the key player does not adapt
}
else {
if (::g->nettics[0] <= ::g->nettics[::g->nodeforplayer[i]]) {
::g->gametime--;
//OutputDebugString("-");
}
::g->frameskip[::g->frameon&3] = (::g->oldnettics > ::g->nettics[::g->nodeforplayer[i]]);
::g->oldnettics = ::g->nettics[0];
if (::g->frameskip[0] && ::g->frameskip[1] && ::g->frameskip[2] && ::g->frameskip[3]) {
::g->skiptics = 1;
//OutputDebugString("+");
}
}
}
// wait for new tics if needed
if (::g->trt_lowtic < ::g->gametic/::g->ticdup + ::g->trt_counts)
{
int lagtime = 0;
if (::g->trt_lowtic < ::g->gametic/::g->ticdup) {
I_Error ("TryRunTics: ::g->trt_lowtic < gametic");
}
if ( ::g->lastnettic == 0 ) {
::g->lastnettic = ::g->trt_entertic;
}
lagtime = ::g->trt_entertic - ::g->lastnettic;
// Detect if a client has stopped sending updates, remove them from the game after 5 secs.
if ( common->IsMultiplayer() && (!::g->demoplayback && ::g->netgame) && lagtime >= TICRATE ) {
if ( lagtime > NET_TIMEOUT ) {
if ( lowtic_node == ::g->nodeforplayer[::g->consoleplayer] ) {
#ifdef ID_ENABLE_DOOM_CLASSIC_NETWORKING
#ifndef __PS3__
gameLocal->showFatalErrorMessage( XuiLookupStringTable(globalStrings,L"You have been disconnected from the match.") );
gameLocal->Interface.QuitCurrentGame();
#endif
#endif
} else {
if (::g->nodeingame[lowtic_node]) {
int i, consoleNum = lowtic_node;
for ( i=0; i < ::g->doomcom.numnodes; i++ ) {
if ( ::g->nodeforplayer[i] == lowtic_node ) {
consoleNum = i;
break;
}
}
::g->nodeingame[lowtic_node] = false;
::g->playeringame[consoleNum] = false;
strcpy (::g->exitmsg, "Player 1 left the game");
::g->exitmsg[7] += consoleNum;
::g->players[::g->consoleplayer].message = ::g->exitmsg;
// Stop a demo record now, as playback doesn't support losing players
G_CheckDemoStatus();
}
}
}
}
return false;
}
::g->lastnettic = 0;
// run the count * ::g->ticdup dics
while (::g->trt_counts--)
{
for (i=0 ; i < ::g->ticdup ; i++)
{
if (::g->gametic/::g->ticdup > ::g->trt_lowtic) {
I_Error ("gametic(%d) greater than trt_lowtic(%d), trt_counts(%d)", ::g->gametic, ::g->trt_lowtic, ::g->trt_counts );
return false;
}
if (::g->advancedemo) {
D_DoAdvanceDemo ();
}
M_Ticker ();
G_Ticker ();
::g->gametic++;
// modify command for duplicated tics
if (i != ::g->ticdup-1)
{
ticcmd_t *cmd;
int buf;
int j;
buf = (::g->gametic/::g->ticdup)%BACKUPTICS;
for (j=0 ; j<MAXPLAYERS ; j++)
{
cmd = &::g->netcmds[j][buf];
if (cmd->buttons & BT_SPECIAL)
cmd->buttons = 0;
}
}
}
NetUpdate ( userCmdMgr ); // check for new console commands
}
return true;
}