SRB2/src/d_net.c

1446 lines
35 KiB
C
Raw Normal View History

2014-03-15 16:59:03 +00:00
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 1998-2000 by DooM Legacy Team.
2018-11-25 12:35:38 +00:00
// Copyright (C) 1999-2018 by Sonic Team Junior.
2014-03-15 16:59:03 +00:00
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file d_net.c
/// \brief SRB2 network game communication and protocol, all OS independent parts.
//
/// Implement a Sliding window protocol without receiver window
/// (out of order reception)
/// This protocol uses a mix of "goback n" and "selective repeat" implementation
/// The NOTHING packet is sent when connection is idle to acknowledge packets
#include "doomdef.h"
#include "g_game.h"
#include "i_net.h"
#include "i_system.h"
#include "m_argv.h"
#include "d_net.h"
#include "w_wad.h"
#include "d_netfil.h"
#include "d_clisrv.h"
#include "z_zone.h"
#include "i_tcp.h"
2018-12-13 18:23:09 +00:00
#include "d_main.h" // srb2home
2014-03-15 16:59:03 +00:00
//
// NETWORKING
//
2016-12-31 18:26:33 +00:00
// gametic is the tic about to (or currently being) run
// Server:
2014-03-15 16:59:03 +00:00
// maketic is the tic that hasn't had control made for it yet
2016-12-31 18:26:33 +00:00
// nettics is the tic for each node
// firstticstosend is the lowest value of nettics
// Client:
// neededtic is the tic needed by the client to run the game
// firstticstosend is used to optimize a condition
// Normally maketic >= gametic > 0
2014-03-15 16:59:03 +00:00
#define FORCECLOSE 0x8000
tic_t connectiontimeout = (10*TICRATE);
2014-03-15 16:59:03 +00:00
/// \brief network packet
doomcom_t *doomcom = NULL;
/// \brief network packet data, points inside doomcom
doomdata_t *netbuffer = NULL;
#ifdef DEBUGFILE
2014-03-15 16:59:03 +00:00
FILE *debugfile = NULL; // put some net info in a file during the game
#endif
2014-03-15 16:59:03 +00:00
#define MAXREBOUND 8
static doomdata_t reboundstore[MAXREBOUND];
static INT16 reboundsize[MAXREBOUND];
static INT32 rebound_head, rebound_tail;
/// \brief bandwith of netgame
INT32 net_bandwidth;
/// \brief max length per packet
INT16 hardware_MAXPACKETLENGTH;
boolean (*I_NetGet)(void) = NULL;
2014-03-15 16:59:03 +00:00
void (*I_NetSend)(void) = NULL;
boolean (*I_NetCanSend)(void) = NULL;
boolean (*I_NetCanGet)(void) = NULL;
void (*I_NetCloseSocket)(void) = NULL;
void (*I_NetFreeNodenum)(INT32 nodenum) = NULL;
SINT8 (*I_NetMakeNodewPort)(const char *address, const char* port) = NULL;
boolean (*I_NetOpenSocket)(void) = NULL;
boolean (*I_Ban) (INT32 node) = NULL;
void (*I_ClearBans)(void) = NULL;
const char *(*I_GetNodeAddress) (INT32 node) = NULL;
const char *(*I_GetBanAddress) (size_t ban) = NULL;
const char *(*I_GetBanMask) (size_t ban) = NULL;
boolean (*I_SetBanAddress) (const char *address, const char *mask) = NULL;
boolean *bannednode = NULL;
// network stats
static tic_t statstarttic;
INT32 getbytes = 0;
INT64 sendbytes = 0;
static INT32 retransmit = 0, duppacket = 0;
static INT32 sendackpacket = 0, getackpacket = 0;
INT32 ticruned = 0, ticmiss = 0;
// globals
INT32 getbps, sendbps;
float lostpercent, duppercent, gamelostpercent;
INT32 packetheaderlength;
boolean Net_GetNetStat(void)
{
const tic_t t = I_GetTime();
static INT64 oldsendbyte = 0;
if (statstarttic+STATLENGTH <= t)
{
const tic_t df = t-statstarttic;
const INT64 newsendbyte = sendbytes - oldsendbyte;
sendbps = (INT32)(newsendbyte*TICRATE)/df;
getbps = (getbytes*TICRATE)/df;
if (sendackpacket)
lostpercent = 100.0f*(float)retransmit/(float)sendackpacket;
else
lostpercent = 0.0f;
if (getackpacket)
duppercent = 100.0f*(float)duppacket/(float)getackpacket;
else
duppercent = 0.0f;
if (ticruned)
gamelostpercent = 100.0f*(float)ticmiss/(float)ticruned;
else
gamelostpercent = 0.0f;
ticmiss = ticruned = 0;
oldsendbyte = sendbytes;
getbytes = 0;
sendackpacket = getackpacket = duppacket = retransmit = 0;
statstarttic = t;
return 1;
}
return 0;
}
// -----------------------------------------------------------------
// Some structs and functions for acknowledgement of packets
// -----------------------------------------------------------------
2016-12-31 18:26:33 +00:00
#define MAXACKPACKETS 96 // Minimum number of nodes (wat)
2014-03-23 16:00:29 +00:00
#define MAXACKTOSEND 96
2016-12-31 18:26:33 +00:00
#define URGENTFREESLOTNUM 10
2014-03-23 16:00:29 +00:00
#define ACKTOSENDTIMEOUT (TICRATE/11)
2014-03-15 16:59:03 +00:00
#ifndef NONET
typedef struct
{
UINT8 acknum;
UINT8 nextacknum;
2016-12-31 18:26:33 +00:00
UINT8 destinationnode; // The node to send the ack to
tic_t senttime; // The time when the ack was sent
UINT16 length; // The packet size
UINT16 resentnum; // The number of times the ack has been resent
2014-03-15 16:59:03 +00:00
union {
SINT8 raw[MAXPACKETLENGTH];
doomdata_t data;
} pak;
} ackpak_t;
#endif
typedef enum
{
NF_CLOSE = 1, // Flag is set when connection is closing
NF_TIMEOUT = 2, // Flag is set when the node got a timeout
2014-03-15 16:59:03 +00:00
} node_flags_t;
#ifndef NONET
// Table of packets that were not acknowleged can be resent (the sender window)
2014-03-15 16:59:03 +00:00
static ackpak_t ackpak[MAXACKPACKETS];
#endif
typedef struct
{
// ack return to send (like sliding window protocol)
UINT8 firstacktosend;
// when no consecutive packets are received we keep in mind what packets
// we already received in a queue
UINT8 acktosend_head;
UINT8 acktosend_tail;
UINT8 acktosend[MAXACKTOSEND];
// automatically send keep alive packet when not enough trafic
tic_t lasttimeacktosend_sent;
// detect connection lost
tic_t lasttimepacketreceived;
// flow control: do not send too many packets with ack
UINT8 remotefirstack;
UINT8 nextacknum;
UINT8 flags;
} node_t;
static node_t nodes[MAXNETNODES];
#define NODETIMEOUT 14
2014-03-15 16:59:03 +00:00
#ifndef NONET
// return <0 if a < b (mod 256)
// 0 if a = n (mod 256)
// >0 if a > b (mod 256)
// mnemonic: to use it compare to 0: cmpack(a,b)<0 is "a < b" ...
FUNCMATH static INT32 cmpack(UINT8 a, UINT8 b)
{
register INT32 d = a - b;
if (d >= 127 || d < -128)
return -d;
return d;
}
2016-12-31 18:26:33 +00:00
/** Sets freeack to a free acknum and copies the netbuffer in the ackpak table
*
* \param freeack The address to store the free acknum at
* \param lowtimer ???
* \return True if a free acknum was found
*/
2014-03-15 16:59:03 +00:00
static boolean GetFreeAcknum(UINT8 *freeack, boolean lowtimer)
{
node_t *node = &nodes[doomcom->remotenode];
2016-12-31 18:26:33 +00:00
INT32 i, numfreeslot = 0;
2014-03-15 16:59:03 +00:00
if (cmpack((UINT8)((node->remotefirstack + MAXACKTOSEND) % 256), node->nextacknum) < 0)
{
DEBFILE(va("too fast %d %d\n",node->remotefirstack,node->nextacknum));
return false;
}
for (i = 0; i < MAXACKPACKETS; i++)
if (!ackpak[i].acknum)
{
2016-12-31 18:26:33 +00:00
// For low priority packets, make sure to let freeslots so urgent packets can be sent
if (netbuffer->packettype >= PT_CANFAIL)
{
numfreeslot++;
if (numfreeslot <= URGENTFREESLOTNUM)
continue;
}
2014-03-15 16:59:03 +00:00
ackpak[i].acknum = node->nextacknum;
ackpak[i].nextacknum = node->nextacknum;
node->nextacknum++;
if (!node->nextacknum)
node->nextacknum++;
ackpak[i].destinationnode = (UINT8)(node - nodes);
ackpak[i].length = doomcom->datalength;
if (lowtimer)
{
2016-12-31 18:26:33 +00:00
// Lowtime means can't be sent now so try it as soon as possible
2014-03-15 16:59:03 +00:00
ackpak[i].senttime = 0;
ackpak[i].resentnum = 1;
}
else
{
ackpak[i].senttime = I_GetTime();
ackpak[i].resentnum = 0;
}
M_Memcpy(ackpak[i].pak.raw, netbuffer, ackpak[i].length);
*freeack = ackpak[i].acknum;
2016-12-31 18:26:33 +00:00
sendackpacket++; // For stat
2014-03-15 16:59:03 +00:00
return true;
}
#ifdef PARANOIA
CONS_Debug(DBG_NETPLAY, "No more free ackpacket\n");
#endif
if (netbuffer->packettype < PT_CANFAIL)
I_Error("Connection lost\n");
return false;
}
/** Counts how many acks are free
*
* \param urgent True if the type of the packet meant to
* use an ack is lower than PT_CANFAIL
* If for some reason you don't want use it
* for any packet type in particular,
* just set to false
* \return The number of free acks
*
*/
INT32 Net_GetFreeAcks(boolean urgent)
{
INT32 i, numfreeslot = 0;
INT32 n = 0; // Number of free acks found
for (i = 0; i < MAXACKPACKETS; i++)
if (!ackpak[i].acknum)
{
// For low priority packets, make sure to let freeslots so urgent packets can be sent
if (!urgent)
{
numfreeslot++;
if (numfreeslot <= URGENTFREESLOTNUM)
continue;
}
n++;
}
return n;
}
2016-12-31 18:26:33 +00:00
// Get a ack to send in the queue of this node
2014-03-15 16:59:03 +00:00
static UINT8 GetAcktosend(INT32 node)
{
nodes[node].lasttimeacktosend_sent = I_GetTime();
return nodes[node].firstacktosend;
}
2016-12-31 18:26:33 +00:00
static void RemoveAck(INT32 i)
2014-03-15 16:59:03 +00:00
{
INT32 node = ackpak[i].destinationnode;
DEBFILE(va("Remove ack %d\n",ackpak[i].acknum));
ackpak[i].acknum = 0;
if (nodes[node].flags & NF_CLOSE)
2014-03-15 16:59:03 +00:00
Net_CloseConnection(node);
}
2016-12-31 18:26:33 +00:00
// We have got a packet, proceed the ack request and ack return
2014-03-15 16:59:03 +00:00
static boolean Processackpak(void)
{
INT32 i;
boolean goodpacket = true;
node_t *node = &nodes[doomcom->remotenode];
2016-12-31 18:26:33 +00:00
// Received an ack return, so remove the ack in the list
2014-03-15 16:59:03 +00:00
if (netbuffer->ackreturn && cmpack(node->remotefirstack, netbuffer->ackreturn) < 0)
{
node->remotefirstack = netbuffer->ackreturn;
2016-12-31 18:26:33 +00:00
// Search the ackbuffer and free it
2014-03-15 16:59:03 +00:00
for (i = 0; i < MAXACKPACKETS; i++)
if (ackpak[i].acknum && ackpak[i].destinationnode == node - nodes
&& cmpack(ackpak[i].acknum, netbuffer->ackreturn) <= 0)
{
2016-12-31 18:26:33 +00:00
RemoveAck(i);
2014-03-15 16:59:03 +00:00
}
}
2016-12-31 18:26:33 +00:00
// Received a packet with ack, queue it to send the ack back
2014-03-15 16:59:03 +00:00
if (netbuffer->ack)
{
UINT8 ack = netbuffer->ack;
getackpacket++;
if (cmpack(ack, node->firstacktosend) <= 0)
{
DEBFILE(va("Discard(1) ack %d (duplicated)\n", ack));
duppacket++;
2016-12-31 18:26:33 +00:00
goodpacket = false; // Discard packet (duplicate)
2014-03-15 16:59:03 +00:00
}
else
{
2016-12-31 18:26:33 +00:00
// Check if it is not already in the queue
2014-03-15 16:59:03 +00:00
for (i = node->acktosend_tail; i != node->acktosend_head; i = (i+1) % MAXACKTOSEND)
if (node->acktosend[i] == ack)
{
DEBFILE(va("Discard(2) ack %d (duplicated)\n", ack));
duppacket++;
2016-12-31 18:26:33 +00:00
goodpacket = false; // Discard packet (duplicate)
2014-03-15 16:59:03 +00:00
break;
}
if (goodpacket)
{
2016-12-31 18:26:33 +00:00
// Is a good packet so increment the acknowledge number,
// Then search for a "hole" in the queue
2014-03-15 16:59:03 +00:00
UINT8 nextfirstack = (UINT8)(node->firstacktosend + 1);
if (!nextfirstack)
nextfirstack = 1;
if (ack == nextfirstack)
{
UINT8 hm1; // head - 1
boolean change = true;
node->firstacktosend = nextfirstack++;
if (!nextfirstack)
nextfirstack = 1;
hm1 = (UINT8)((node->acktosend_head-1+MAXACKTOSEND) % MAXACKTOSEND);
while (change)
{
change = false;
for (i = node->acktosend_tail; i != node->acktosend_head;
i = (i+1) % MAXACKTOSEND)
{
if (cmpack(node->acktosend[i], nextfirstack) <= 0)
{
if (node->acktosend[i] == nextfirstack)
{
node->firstacktosend = nextfirstack++;
if (!nextfirstack)
nextfirstack = 1;
change = true;
}
if (i == node->acktosend_tail)
{
node->acktosend[node->acktosend_tail] = 0;
node->acktosend_tail = (UINT8)((i+1) % MAXACKTOSEND);
}
else if (i == hm1)
{
node->acktosend[hm1] = 0;
node->acktosend_head = hm1;
hm1 = (UINT8)((hm1-1+MAXACKTOSEND) % MAXACKTOSEND);
}
}
}
}
}
2016-12-31 18:26:33 +00:00
else // Out of order packet
2014-03-15 16:59:03 +00:00
{
2016-12-31 18:26:33 +00:00
// Don't increment firsacktosend, put it in asktosend queue
// Will be incremented when the nextfirstack comes (code above)
2014-03-15 16:59:03 +00:00
UINT8 newhead = (UINT8)((node->acktosend_head+1) % MAXACKTOSEND);
DEBFILE(va("out of order packet (%d expected)\n", nextfirstack));
if (newhead != node->acktosend_tail)
{
node->acktosend[node->acktosend_head] = ack;
node->acktosend_head = newhead;
}
2016-12-31 18:26:33 +00:00
else // Buffer full discard packet, sender will resend it
{ // We can admit the packet but we will not detect the duplication after :(
2014-03-15 16:59:03 +00:00
DEBFILE("no more freeackret\n");
goodpacket = false;
}
}
}
}
}
return goodpacket;
}
#endif
// send special packet with only ack on it
void Net_SendAcks(INT32 node)
{
#ifdef NONET
(void)node;
#else
netbuffer->packettype = PT_NOTHING;
M_Memcpy(netbuffer->u.textcmd, nodes[node].acktosend, MAXACKTOSEND);
HSendPacket(node, false, 0, MAXACKTOSEND);
#endif
}
#ifndef NONET
static void GotAcks(void)
{
INT32 i, j;
for (j = 0; j < MAXACKTOSEND; j++)
if (netbuffer->u.textcmd[j])
for (i = 0; i < MAXACKPACKETS; i++)
if (ackpak[i].acknum && ackpak[i].destinationnode == doomcom->remotenode)
{
if (ackpak[i].acknum == netbuffer->u.textcmd[j])
2016-12-31 18:26:33 +00:00
RemoveAck(i);
// nextacknum is first equal to acknum, then when receiving bigger ack
// there is big chance the packet is lost
// When resent, nextacknum = nodes[node].nextacknum
// will redo the same but with different value
else if (cmpack(ackpak[i].nextacknum, netbuffer->u.textcmd[j]) <= 0
&& ackpak[i].senttime > 0)
{
ackpak[i].senttime--; // hurry up
}
2014-03-15 16:59:03 +00:00
}
}
#endif
void Net_ConnectionTimeout(INT32 node)
2014-03-15 16:59:03 +00:00
{
// Don't timeout several times
if (nodes[node].flags & NF_TIMEOUT)
return;
nodes[node].flags |= NF_TIMEOUT;
2016-12-31 18:26:33 +00:00
// Send a very special packet to self (hack the reboundstore queue)
// Main code will handle it
2014-03-15 16:59:03 +00:00
reboundstore[rebound_head].packettype = PT_NODETIMEOUT;
reboundstore[rebound_head].ack = 0;
reboundstore[rebound_head].ackreturn = 0;
reboundstore[rebound_head].u.textcmd[0] = (UINT8)node;
reboundsize[rebound_head] = (INT16)(BASEPACKETSIZE + 1);
rebound_head = (rebound_head+1) % MAXREBOUND;
2016-12-31 18:26:33 +00:00
// Do not redo it quickly (if we do not close connection it is
2014-03-15 16:59:03 +00:00
// for a good reason!)
nodes[node].lasttimepacketreceived = I_GetTime();
}
2016-12-31 18:26:33 +00:00
// Resend the data if needed
2014-03-15 16:59:03 +00:00
void Net_AckTicker(void)
{
#ifndef NONET
INT32 i;
for (i = 0; i < MAXACKPACKETS; i++)
{
const INT32 nodei = ackpak[i].destinationnode;
node_t *node = &nodes[nodei];
if (ackpak[i].acknum && ackpak[i].senttime + NODETIMEOUT < I_GetTime())
{
if (ackpak[i].resentnum > 10 && (node->flags & NF_CLOSE))
2014-03-15 16:59:03 +00:00
{
DEBFILE(va("ack %d sent 10 times so connection is supposed lost: node %d\n",
i, nodei));
Net_CloseConnection(nodei | FORCECLOSE);
ackpak[i].acknum = 0;
continue;
}
DEBFILE(va("Resend ack %d, %u<%d at %u\n", ackpak[i].acknum, ackpak[i].senttime,
NODETIMEOUT, I_GetTime()));
M_Memcpy(netbuffer, ackpak[i].pak.raw, ackpak[i].length);
ackpak[i].senttime = I_GetTime();
ackpak[i].resentnum++;
ackpak[i].nextacknum = node->nextacknum;
2016-12-31 18:26:33 +00:00
retransmit++; // For stat
2014-03-15 16:59:03 +00:00
HSendPacket((INT32)(node - nodes), false, ackpak[i].acknum,
(size_t)(ackpak[i].length - BASEPACKETSIZE));
}
}
for (i = 1; i < MAXNETNODES; i++)
{
2016-12-31 18:26:33 +00:00
// This is something like node open flag
2014-03-15 16:59:03 +00:00
if (nodes[i].firstacktosend)
{
2016-12-31 18:26:33 +00:00
// We haven't sent a packet for a long time
// Acknowledge packet if needed
2014-03-15 16:59:03 +00:00
if (nodes[i].lasttimeacktosend_sent + ACKTOSENDTIMEOUT < I_GetTime())
Net_SendAcks(i);
if (!(nodes[i].flags & NF_CLOSE)
2014-03-15 16:59:03 +00:00
&& nodes[i].lasttimepacketreceived + connectiontimeout < I_GetTime())
{
Net_ConnectionTimeout(i);
}
}
}
#endif
}
2016-12-31 18:26:33 +00:00
// Remove last packet received ack before resending the ackreturn
2014-03-15 16:59:03 +00:00
// (the higher layer doesn't have room, or something else ....)
2016-12-31 18:26:33 +00:00
void Net_UnAcknowledgePacket(INT32 node)
2014-03-15 16:59:03 +00:00
{
#ifdef NONET
(void)node;
#else
INT32 hm1 = (nodes[node].acktosend_head-1+MAXACKTOSEND) % MAXACKTOSEND;
DEBFILE(va("UnAcknowledge node %d\n", node));
if (!node)
return;
if (nodes[node].acktosend[hm1] == netbuffer->ack)
{
nodes[node].acktosend[hm1] = 0;
nodes[node].acktosend_head = (UINT8)hm1;
}
else if (nodes[node].firstacktosend == netbuffer->ack)
{
nodes[node].firstacktosend--;
if (!nodes[node].firstacktosend)
nodes[node].firstacktosend = UINT8_MAX;
}
else
{
while (nodes[node].firstacktosend != netbuffer->ack)
{
nodes[node].acktosend_tail = (UINT8)
((nodes[node].acktosend_tail-1+MAXACKTOSEND) % MAXACKTOSEND);
nodes[node].acktosend[nodes[node].acktosend_tail] = nodes[node].firstacktosend;
nodes[node].firstacktosend--;
if (!nodes[node].firstacktosend)
nodes[node].firstacktosend = UINT8_MAX;
}
nodes[node].firstacktosend++;
if (!nodes[node].firstacktosend)
nodes[node].firstacktosend = 1;
}
#endif
}
2017-01-01 22:52:27 +00:00
#ifndef NONET
2016-12-31 18:26:33 +00:00
/** Checks if all acks have been received
*
* \return True if all acks have been received
*
*/
static boolean Net_AllAcksReceived(void)
2014-03-15 16:59:03 +00:00
{
INT32 i;
for (i = 0; i < MAXACKPACKETS; i++)
if (ackpak[i].acknum)
return false;
return true;
}
2017-01-01 22:52:27 +00:00
#endif
2014-03-15 16:59:03 +00:00
2016-12-31 18:26:33 +00:00
/** Waits for all ackreturns
*
* \param timeout Timeout in seconds
*
*/
2014-03-15 16:59:03 +00:00
void Net_WaitAllAckReceived(UINT32 timeout)
{
#ifdef NONET
(void)timeout;
#else
tic_t tictac = I_GetTime();
timeout = tictac + timeout*NEWTICRATE;
HGetPacket();
2016-12-31 18:26:33 +00:00
while (timeout > I_GetTime() && !Net_AllAcksReceived())
2014-03-15 16:59:03 +00:00
{
while (tictac == I_GetTime())
I_Sleep();
tictac = I_GetTime();
HGetPacket();
Net_AckTicker();
}
#endif
}
2016-12-31 18:26:33 +00:00
static void InitNode(node_t *node)
2014-03-15 16:59:03 +00:00
{
2016-12-31 18:26:33 +00:00
node->acktosend_head = node->acktosend_tail = 0;
node->firstacktosend = 0;
node->nextacknum = 1;
node->remotefirstack = 0;
node->flags = 0;
2014-03-15 16:59:03 +00:00
}
static void InitAck(void)
{
INT32 i;
#ifndef NONET
for (i = 0; i < MAXACKPACKETS; i++)
ackpak[i].acknum = 0;
#endif
for (i = 0; i < MAXNETNODES; i++)
2016-12-31 18:26:33 +00:00
InitNode(&nodes[i]);
2014-03-15 16:59:03 +00:00
}
2016-12-31 18:26:33 +00:00
/** Removes all acks of a given packet type
*
* \param packettype The packet type to forget
*
*/
2014-03-15 16:59:03 +00:00
void Net_AbortPacketType(UINT8 packettype)
{
#ifdef NONET
(void)packettype;
#else
INT32 i;
for (i = 0; i < MAXACKPACKETS; i++)
if (ackpak[i].acknum && (ackpak[i].pak.data.packettype == packettype
|| packettype == UINT8_MAX))
{
ackpak[i].acknum = 0;
}
#endif
}
// -----------------------------------------------------------------
// end of acknowledge function
// -----------------------------------------------------------------
// remove a node, clear all ack from this node and reset askret
void Net_CloseConnection(INT32 node)
{
#ifdef NONET
(void)node;
#else
INT32 i;
boolean forceclose = (node & FORCECLOSE) != 0;
if (node == -1)
{
DEBFILE(M_GetText("Net_CloseConnection: node -1 detected!\n"));
return; // nope, just ignore it
}
2014-03-15 16:59:03 +00:00
node &= ~FORCECLOSE;
if (!node)
return;
2017-05-09 19:02:42 +00:00
if (node < 0 || node >= MAXNETNODES) // prevent invalid nodes from crashing the game
{
DEBFILE(va(M_GetText("Net_CloseConnection: invalid node %d detected!\n"), node));
return;
}
nodes[node].flags |= NF_CLOSE;
2014-03-15 16:59:03 +00:00
// try to Send ack back (two army problem)
if (GetAcktosend(node))
{
Net_SendAcks(node);
Net_SendAcks(node);
}
// check if we are waiting for an ack from this node
for (i = 0; i < MAXACKPACKETS; i++)
if (ackpak[i].acknum && ackpak[i].destinationnode == node)
{
if (!forceclose)
return; // connection will be closed when ack is returned
else
ackpak[i].acknum = 0;
}
2016-12-31 18:26:33 +00:00
InitNode(&nodes[node]);
SV_AbortSendFiles(node);
2014-03-15 16:59:03 +00:00
I_NetFreeNodenum(node);
#endif
}
#ifndef NONET
//
// Checksum
//
static UINT32 NetbufferChecksum(void)
{
UINT32 c = 0x1234567;
const INT32 l = doomcom->datalength - 4;
const UINT8 *buf = (UINT8 *)netbuffer + 4;
INT32 i;
for (i = 0; i < l; i++, buf++)
c += (*buf) * (i+1);
return LONG(c);
}
#endif
#ifdef DEBUGFILE
static void fprintfstring(char *s, size_t len)
{
INT32 mode = 0;
size_t i;
for (i = 0; i < len; i++)
if (s[i] < 32)
{
if (!mode)
{
fprintf(debugfile, "[%d", (UINT8)s[i]);
mode = 1;
}
else
fprintf(debugfile, ",%d", (UINT8)s[i]);
}
else
{
if (mode)
{
fprintf(debugfile, "]");
mode = 0;
}
fprintf(debugfile, "%c", s[i]);
}
if (mode)
fprintf(debugfile, "]");
2016-12-31 18:26:33 +00:00
}
static void fprintfstringnewline(char *s, size_t len)
{
fprintfstring(s, len);
2014-03-15 16:59:03 +00:00
fprintf(debugfile, "\n");
}
2016-12-31 18:26:33 +00:00
/// \warning Keep this up-to-date if you add/remove/rename packet types
2014-03-15 16:59:03 +00:00
static const char *packettypename[NUMPACKETTYPE] =
{
"NOTHING",
"SERVERCFG",
"CLIENTCMD",
"CLIENTMIS",
"CLIENT2CMD",
"CLIENT2MIS",
"NODEKEEPALIVE",
"NODEKEEPALIVEMIS",
"SERVERTICS",
"SERVERREFUSE",
"SERVERSHUTDOWN",
"CLIENTQUIT",
"ASKINFO",
"SERVERINFO",
2016-12-31 18:26:33 +00:00
"PLAYERINFO",
2014-03-15 16:59:03 +00:00
"REQUESTFILE",
"ASKINFOVIAMS",
2016-12-31 18:26:33 +00:00
"RESYNCHEND",
"RESYNCHGET",
2014-03-15 16:59:03 +00:00
"FILEFRAGMENT",
"TEXTCMD",
"TEXTCMD2",
"CLIENTJOIN",
"NODETIMEOUT",
2016-12-31 18:26:33 +00:00
"RESYNCHING",
"PING"
2014-03-15 16:59:03 +00:00
};
static void DebugPrintpacket(const char *header)
{
fprintf(debugfile, "%-12s (node %d,ack %d,ackret %d,size %d) type(%d) : %s\n",
header, doomcom->remotenode, netbuffer->ack, netbuffer->ackreturn, doomcom->datalength,
netbuffer->packettype, packettypename[netbuffer->packettype]);
switch (netbuffer->packettype)
{
case PT_ASKINFO:
case PT_ASKINFOVIAMS:
2016-12-31 18:26:33 +00:00
fprintf(debugfile, " time %u\n", (tic_t)LONG(netbuffer->u.askinfo.time));
2014-03-15 16:59:03 +00:00
break;
case PT_CLIENTJOIN:
fprintf(debugfile, " number %d mode %d\n", netbuffer->u.clientcfg.localplayers,
netbuffer->u.clientcfg.mode);
break;
case PT_SERVERTICS:
2016-12-31 18:26:33 +00:00
{
servertics_pak *serverpak = &netbuffer->u.serverpak;
UINT8 *cmd = (UINT8 *)(&serverpak->cmds[serverpak->numslots * serverpak->numtics]);
size_t ntxtcmd = &((UINT8 *)netbuffer)[doomcom->datalength] - cmd;
2016-12-31 18:26:33 +00:00
2014-03-15 16:59:03 +00:00
fprintf(debugfile, " firsttic %u ply %d tics %d ntxtcmd %s\n ",
2016-12-31 18:26:33 +00:00
(UINT32)ExpandTics(serverpak->starttic), serverpak->numslots, serverpak->numtics, sizeu1(ntxtcmd));
/// \todo Display more readable information about net commands
fprintfstringnewline((char *)cmd, ntxtcmd);
/*fprintfstring((char *)cmd, 3);
2016-12-31 18:26:33 +00:00
if (ntxtcmd > 4)
{
fprintf(debugfile, "[%s]", netxcmdnames[*((cmd) + 3) - 1]);
2016-12-31 18:26:33 +00:00
fprintfstring(((char *)cmd) + 4, ntxtcmd - 4);
}
fprintf(debugfile, "\n");*/
2014-03-15 16:59:03 +00:00
break;
2016-12-31 18:26:33 +00:00
}
2014-03-15 16:59:03 +00:00
case PT_CLIENTCMD:
case PT_CLIENT2CMD:
case PT_CLIENTMIS:
case PT_CLIENT2MIS:
case PT_NODEKEEPALIVE:
case PT_NODEKEEPALIVEMIS:
fprintf(debugfile, " tic %4u resendfrom %u\n",
(UINT32)ExpandTics(netbuffer->u.clientpak.client_tic),
(UINT32)ExpandTics (netbuffer->u.clientpak.resendfrom));
break;
case PT_TEXTCMD:
case PT_TEXTCMD2:
fprintf(debugfile, " length %d\n ", netbuffer->u.textcmd[0]);
2016-12-31 18:26:33 +00:00
fprintf(debugfile, "[%s]", netxcmdnames[netbuffer->u.textcmd[1] - 1]);
fprintfstringnewline((char *)netbuffer->u.textcmd + 2, netbuffer->u.textcmd[0] - 1);
2014-03-15 16:59:03 +00:00
break;
case PT_SERVERCFG:
2014-08-04 03:49:33 +00:00
fprintf(debugfile, " playerslots %d clientnode %d serverplayer %d "
2014-03-15 16:59:03 +00:00
"gametic %u gamestate %d gametype %d modifiedgame %d\n",
netbuffer->u.servercfg.totalslotnum, netbuffer->u.servercfg.clientnode,
netbuffer->u.servercfg.serverplayer, (UINT32)LONG(netbuffer->u.servercfg.gametic),
netbuffer->u.servercfg.gamestate, netbuffer->u.servercfg.gametype,
netbuffer->u.servercfg.modifiedgame);
break;
case PT_SERVERINFO:
fprintf(debugfile, " '%s' player %d/%d, map %s, filenum %d, time %u \n",
netbuffer->u.serverinfo.servername, netbuffer->u.serverinfo.numberofplayer,
netbuffer->u.serverinfo.maxplayer, netbuffer->u.serverinfo.mapname,
netbuffer->u.serverinfo.fileneedednum,
(UINT32)LONG(netbuffer->u.serverinfo.time));
2016-12-31 18:26:33 +00:00
fprintfstringnewline((char *)netbuffer->u.serverinfo.fileneeded,
2014-03-15 16:59:03 +00:00
(UINT8)((UINT8 *)netbuffer + doomcom->datalength
- (UINT8 *)netbuffer->u.serverinfo.fileneeded));
break;
case PT_SERVERREFUSE:
fprintf(debugfile, " reason %s\n", netbuffer->u.serverrefuse.reason);
break;
case PT_FILEFRAGMENT:
fprintf(debugfile, " fileid %d datasize %d position %u\n",
netbuffer->u.filetxpak.fileid, (UINT16)SHORT(netbuffer->u.filetxpak.size),
(UINT32)LONG(netbuffer->u.filetxpak.position));
break;
case PT_REQUESTFILE:
default: // write as a raw packet
2016-12-31 18:26:33 +00:00
fprintfstringnewline((char *)netbuffer->u.textcmd,
2014-03-15 16:59:03 +00:00
(UINT8)((UINT8 *)netbuffer + doomcom->datalength - (UINT8 *)netbuffer->u.textcmd));
break;
}
}
#endif
2016-12-31 18:26:33 +00:00
#ifdef PACKETDROP
static INT32 packetdropquantity[NUMPACKETTYPE] = {0};
static INT32 packetdroprate = 0;
void Command_Drop(void)
{
INT32 packetquantity;
const char *packetname;
size_t i;
if (COM_Argc() < 2)
{
CONS_Printf("drop <packettype> [quantity]: drop packets\n"
"drop reset: cancel all packet drops\n");
2016-12-31 18:26:33 +00:00
return;
}
if (!(stricmp(COM_Argv(1), "reset") && stricmp(COM_Argv(1), "cancel") && stricmp(COM_Argv(1), "stop")))
{
memset(packetdropquantity, 0, sizeof(packetdropquantity));
return;
}
if (COM_Argc() >= 3)
{
packetquantity = atoi(COM_Argv(2));
if (packetquantity <= 0 && COM_Argv(2)[0] != '0')
{
CONS_Printf("Invalid quantity\n");
return;
}
}
else
packetquantity = -1;
packetname = COM_Argv(1);
if (!(stricmp(packetname, "all") && stricmp(packetname, "any")))
for (i = 0; i < NUMPACKETTYPE; i++)
packetdropquantity[i] = packetquantity;
else
{
for (i = 0; i < NUMPACKETTYPE; i++)
if (!stricmp(packetname, packettypename[i]))
{
packetdropquantity[i] = packetquantity;
return;
}
CONS_Printf("Unknown packet name\n");
}
}
void Command_Droprate(void)
{
INT32 droprate;
if (COM_Argc() < 2)
{
CONS_Printf("Packet drop rate: %d%%\n", packetdroprate);
return;
}
droprate = atoi(COM_Argv(1));
if ((droprate <= 0 && COM_Argv(1)[0] != '0') || droprate > 100)
{
CONS_Printf("Packet drop rate must be between 0 and 100!\n");
return;
}
packetdroprate = droprate;
}
2017-03-12 21:23:56 +00:00
#ifndef NONET
2016-12-31 18:26:33 +00:00
static boolean ShouldDropPacket(void)
{
return (packetdropquantity[netbuffer->packettype])
|| (packetdroprate != 0 && rand() < (RAND_MAX * (packetdroprate / 100.f))) || packetdroprate == 100;
}
#endif
2017-03-12 21:23:56 +00:00
#endif
2016-12-31 18:26:33 +00:00
2014-03-15 16:59:03 +00:00
//
// HSendPacket
//
boolean HSendPacket(INT32 node, boolean reliable, UINT8 acknum, size_t packetlength)
{
doomcom->datalength = (INT16)(packetlength + BASEPACKETSIZE);
2016-12-31 18:26:33 +00:00
if (node == 0) // Packet is to go back to us
2014-03-15 16:59:03 +00:00
{
if ((rebound_head+1) % MAXREBOUND == rebound_tail)
{
#ifdef PARANOIA
CONS_Debug(DBG_NETPLAY, "No more rebound buf\n");
#endif
return false;
}
netbuffer->ack = netbuffer->ackreturn = 0; // don't hold over values from last packet sent/received
2014-03-15 16:59:03 +00:00
M_Memcpy(&reboundstore[rebound_head], netbuffer,
doomcom->datalength);
reboundsize[rebound_head] = doomcom->datalength;
rebound_head = (rebound_head+1) % MAXREBOUND;
#ifdef DEBUGFILE
if (debugfile)
{
doomcom->remotenode = (INT16)node;
DebugPrintpacket("SENDLOCAL");
}
#endif
return true;
}
if (!netgame)
I_Error("Tried to transmit to another node");
#ifdef NONET
(void)node;
(void)reliable;
(void)acknum;
#else
2016-12-31 18:26:33 +00:00
// do this before GetFreeAcknum because this function backups
2014-03-15 16:59:03 +00:00
// the current packet
doomcom->remotenode = (INT16)node;
if (doomcom->datalength <= 0)
{
DEBFILE("HSendPacket: nothing to send\n");
#ifdef DEBUGFILE
if (debugfile)
DebugPrintpacket("TRISEND");
#endif
return false;
}
2016-12-31 18:26:33 +00:00
if (node < MAXNETNODES) // Can be a broadcast
2014-03-15 16:59:03 +00:00
netbuffer->ackreturn = GetAcktosend(node);
else
netbuffer->ackreturn = 0;
if (reliable)
{
if (I_NetCanSend && !I_NetCanSend())
{
if (netbuffer->packettype < PT_CANFAIL)
GetFreeAcknum(&netbuffer->ack, true);
DEBFILE("HSendPacket: Out of bandwidth\n");
return false;
}
else if (!GetFreeAcknum(&netbuffer->ack, false))
return false;
}
else
netbuffer->ack = acknum;
netbuffer->checksum = NetbufferChecksum();
2016-12-31 18:26:33 +00:00
sendbytes += packetheaderlength + doomcom->datalength; // For stat
2014-03-15 16:59:03 +00:00
2016-12-31 18:26:33 +00:00
#ifdef PACKETDROP
// Simulate internet :)
//if (rand() >= (INT32)(RAND_MAX * (PACKETLOSSRATE / 100.f)))
if (!ShouldDropPacket())
2014-03-15 16:59:03 +00:00
{
2016-12-31 18:26:33 +00:00
#endif
2014-03-15 16:59:03 +00:00
#ifdef DEBUGFILE
if (debugfile)
2016-12-31 18:26:33 +00:00
DebugPrintpacket("SENT");
2014-03-15 16:59:03 +00:00
#endif
I_NetSend();
2016-12-31 18:26:33 +00:00
#ifdef PACKETDROP
2014-03-15 16:59:03 +00:00
}
2016-12-31 18:26:33 +00:00
else
{
if (packetdropquantity[netbuffer->packettype] > 0)
packetdropquantity[netbuffer->packettype]--;
2014-03-15 16:59:03 +00:00
#ifdef DEBUGFILE
2016-12-31 18:26:33 +00:00
if (debugfile)
DebugPrintpacket("NOT SENT");
#endif
}
2014-03-15 16:59:03 +00:00
#endif
#endif // ndef NONET
return true;
}
//
// HGetPacket
// Returns false if no packet is waiting
// Check Datalength and checksum
//
boolean HGetPacket(void)
{
//boolean nodejustjoined;
2016-12-31 18:26:33 +00:00
// Get a packet from self
2014-03-15 16:59:03 +00:00
if (rebound_tail != rebound_head)
{
M_Memcpy(netbuffer, &reboundstore[rebound_tail], reboundsize[rebound_tail]);
doomcom->datalength = reboundsize[rebound_tail];
if (netbuffer->packettype == PT_NODETIMEOUT)
doomcom->remotenode = netbuffer->u.textcmd[0];
else
doomcom->remotenode = 0;
rebound_tail = (rebound_tail+1) % MAXREBOUND;
#ifdef DEBUGFILE
if (debugfile)
DebugPrintpacket("GETLOCAL");
#endif
return true;
}
if (!netgame)
return false;
#ifndef NONET
while(true)
{
//nodejustjoined = I_NetGet();
2014-03-15 16:59:03 +00:00
I_NetGet();
if (doomcom->remotenode == -1) // No packet received
2014-03-15 16:59:03 +00:00
return false;
2016-12-31 18:26:33 +00:00
getbytes += packetheaderlength + doomcom->datalength; // For stat
2014-03-15 16:59:03 +00:00
if (doomcom->remotenode >= MAXNETNODES)
{
2016-12-31 18:26:33 +00:00
DEBFILE(va("Received packet from node %d!\n", doomcom->remotenode));
2014-03-15 16:59:03 +00:00
continue;
}
nodes[doomcom->remotenode].lasttimepacketreceived = I_GetTime();
if (netbuffer->checksum != NetbufferChecksum())
{
DEBFILE("Bad packet checksum\n");
//Net_CloseConnection(nodejustjoined ? (doomcom->remotenode | FORCECLOSE) : doomcom->remotenode);
2014-03-15 16:59:03 +00:00
Net_CloseConnection(doomcom->remotenode);
continue;
}
#ifdef DEBUGFILE
if (debugfile)
DebugPrintpacket("GET");
#endif
/*// If a new node sends an unexpected packet, just ignore it
if (nodejustjoined && server
&& !(netbuffer->packettype == PT_ASKINFO
|| netbuffer->packettype == PT_SERVERINFO
|| netbuffer->packettype == PT_PLAYERINFO
|| netbuffer->packettype == PT_REQUESTFILE
|| netbuffer->packettype == PT_ASKINFOVIAMS
|| netbuffer->packettype == PT_CLIENTJOIN))
{
DEBFILE(va("New node sent an unexpected %s packet\n", packettypename[netbuffer->packettype]));
//CONS_Alert(CONS_NOTICE, "New node sent an unexpected %s packet\n", packettypename[netbuffer->packettype]);
Net_CloseConnection(doomcom->remotenode | FORCECLOSE);
continue;
}*/
// Proceed the ack and ackreturn field
2014-03-15 16:59:03 +00:00
if (!Processackpak())
continue; // discarded (duplicated)
// A packet with just ackreturn
2014-03-15 16:59:03 +00:00
if (netbuffer->packettype == PT_NOTHING)
{
GotAcks();
continue;
}
break;
}
#endif // ndef NONET
return true;
}
static boolean Internal_Get(void)
2014-03-15 16:59:03 +00:00
{
doomcom->remotenode = -1;
return false;
2014-03-15 16:59:03 +00:00
}
FUNCNORETURN static ATTRNORETURN void Internal_Send(void)
{
I_Error("Send without netgame\n");
}
static void Internal_FreeNodenum(INT32 nodenum)
{
(void)nodenum;
}
SINT8 I_NetMakeNode(const char *hostname)
{
SINT8 newnode = -1;
if (I_NetMakeNodewPort)
{
char *localhostname = strdup(hostname);
char *t = localhostname;
const char *port;
if (!localhostname)
return newnode;
// retrieve portnum from address!
strtok(localhostname, ":");
port = strtok(NULL, ":");
// remove the port in the hostname as we've it already
while ((*t != ':') && (*t != '\0'))
t++;
*t = '\0';
newnode = I_NetMakeNodewPort(localhostname, port);
free(localhostname);
}
return newnode;
}
void D_SetDoomcom(void)
{
if (doomcom) return;
doomcom = Z_Calloc(sizeof (doomcom_t), PU_STATIC, NULL);
doomcom->id = DOOMCOM_ID;
doomcom->numslots = doomcom->numnodes = 1;
doomcom->gametype = 0;
doomcom->consoleplayer = 0;
doomcom->extratics = 0;
}
//
// D_CheckNetGame
// Works out player numbers among the net participants
//
boolean D_CheckNetGame(void)
{
boolean ret = false;
InitAck();
rebound_tail = rebound_head = 0;
statstarttic = I_GetTime();
I_NetGet = Internal_Get;
I_NetSend = Internal_Send;
I_NetCanSend = NULL;
I_NetCloseSocket = NULL;
I_NetFreeNodenum = Internal_FreeNodenum;
I_NetMakeNodewPort = NULL;
hardware_MAXPACKETLENGTH = MAXPACKETLENGTH;
net_bandwidth = 30000;
// I_InitNetwork sets doomcom and netgame
// check and initialize the network driver
multiplayer = false;
// only dos version with external driver will return true
netgame = I_InitNetwork();
2014-03-15 16:59:03 +00:00
if (!netgame && !I_NetOpenSocket)
{
D_SetDoomcom();
netgame = I_InitTcpNetwork();
}
if (netgame)
ret = true;
if (client && netgame)
2014-03-15 16:59:03 +00:00
netgame = false;
server = true; // WTF? server always true???
// no! The deault mode is server. Client is set elsewhere
// when the client executes connect command.
doomcom->ticdup = 1;
if (M_CheckParm("-extratic"))
{
if (M_IsNextParm())
doomcom->extratics = (INT16)atoi(M_GetNextParm());
else
doomcom->extratics = 1;
CONS_Printf(M_GetText("Set extratics to %d\n"), doomcom->extratics);
}
if (M_CheckParm("-bandwidth"))
{
if (M_IsNextParm())
{
net_bandwidth = atoi(M_GetNextParm());
if (net_bandwidth < 1000)
net_bandwidth = 1000;
if (net_bandwidth > 100000)
hardware_MAXPACKETLENGTH = MAXPACKETLENGTH;
CONS_Printf(M_GetText("Network bandwidth set to %d\n"), net_bandwidth);
}
else
I_Error("usage: -bandwidth <byte_per_sec>");
}
software_MAXPACKETLENGTH = hardware_MAXPACKETLENGTH;
if (M_CheckParm("-packetsize"))
{
if (M_IsNextParm())
{
INT32 p = atoi(M_GetNextParm());
if (p < 75)
p = 75;
if (p > hardware_MAXPACKETLENGTH)
p = hardware_MAXPACKETLENGTH;
software_MAXPACKETLENGTH = (UINT16)p;
}
else
I_Error("usage: -packetsize <bytes_per_packet>");
}
if (netgame)
multiplayer = true;
if (doomcom->id != DOOMCOM_ID)
I_Error("Doomcom buffer invalid!");
if (doomcom->numnodes > MAXNETNODES)
I_Error("Too many nodes (%d), max:%d", doomcom->numnodes, MAXNETNODES);
netbuffer = (doomdata_t *)(void *)&doomcom->data;
#ifdef DEBUGFILE
if (M_CheckParm("-debugfile"))
{
char filename[21];
2014-03-15 16:59:03 +00:00
INT32 k = doomcom->consoleplayer - 1;
if (M_IsNextParm())
k = atoi(M_GetNextParm()) - 1;
while (!debugfile && k < MAXPLAYERS)
{
k++;
sprintf(filename, "debug%d.txt", k);
2018-12-13 18:23:09 +00:00
debugfile = fopen(va("%s" PATHSEP "%s", srb2home, filename), "w");
2014-03-15 16:59:03 +00:00
}
if (debugfile)
2018-12-13 18:23:09 +00:00
CONS_Printf(M_GetText("debug output to: %s\n"), va("%s" PATHSEP "%s", srb2home, filename));
2014-03-15 16:59:03 +00:00
else
2018-12-13 18:23:09 +00:00
CONS_Alert(CONS_WARNING, M_GetText("cannot debug output to file %s!\n"), va("%s" PATHSEP "%s", srb2home, filename));
2014-03-15 16:59:03 +00:00
}
#endif
D_ClientServerInit();
return ret;
}
struct pingcell
{
INT32 num;
INT32 ms;
};
static int pingcellcmp(const void *va, const void *vb)
{
const struct pingcell *a, *b;
a = va;
b = vb;
return ( a->ms - b->ms );
}
/*
New ping command formatted nicely to present ping in
ascending order. And with equally spaced columns.
The caller's ping is presented at the bottom too, for
convenience.
*/
2014-03-15 16:59:03 +00:00
void Command_Ping_f(void)
{
struct pingcell pingv[MAXPLAYERS];
INT32 pingc;
int name_width = 0;
int ms_width = 0;
int n;
INT32 i;
pingc = 0;
for (i = 1; i < MAXPLAYERS; ++i)
if (playeringame[i])
{
n = strlen(player_names[i]);
if (n > name_width)
name_width = n;
n = playerpingtable[i];
if (n > ms_width)
ms_width = n;
pingv[pingc].num = i;
pingv[pingc].ms = playerpingtable[i];
pingc++;
}
if (ms_width < 10) ms_width = 1;
else if (ms_width < 100) ms_width = 2;
else ms_width = 3;
qsort(pingv, pingc, sizeof (struct pingcell), &pingcellcmp);
for (i = 0; i < pingc; ++i)
{
CONS_Printf("%02d : %-*s %*d ms\n",
pingv[i].num,
name_width, player_names[pingv[i].num],
ms_width, pingv[i].ms);
}
if (!server && playeringame[consoleplayer])
2014-03-15 16:59:03 +00:00
{
CONS_Printf("\nYour ping is %d ms\n", playerpingtable[consoleplayer]);
2014-03-15 16:59:03 +00:00
}
}
void D_CloseConnection(void)
{
INT32 i;
if (netgame)
{
// wait the ackreturn with timout of 5 Sec
Net_WaitAllAckReceived(5);
// close all connection
for (i = 0; i < MAXNETNODES; i++)
Net_CloseConnection(i|FORCECLOSE);
InitAck();
if (I_NetCloseSocket)
I_NetCloseSocket();
I_NetGet = Internal_Get;
I_NetSend = Internal_Send;
I_NetCanSend = NULL;
I_NetCloseSocket = NULL;
I_NetFreeNodenum = Internal_FreeNodenum;
I_NetMakeNodewPort = NULL;
netgame = false;
addedtogame = false;
}
2017-01-02 19:02:49 +00:00
D_ResetTiccmds();
2014-03-15 16:59:03 +00:00
}