-Added a timeout for game state downloading to prevent definitive join freezes in some cases. The timeout has a minimum value of "jointimeout" and gets higher as the game state grows in size

-If the server tries to kick a joiner who is downloading the game state, they will get a timeout instead, because a regular kick would only happen once the game state has been downloaded
-Added a timeout for player ticcmd packets, again to prevent freezes to happen in some cases
-File/game state downloading is now faster, the speed is controlled by the "downloadspeed" cvar, in packets per tic
-The reason is now properly shown when the server refuses connection
-Changed the default values of "nettimeout" to 10 seconds (previously 15) and "maxsend" to 4 MB (previously 1)
-Added a "noticedownload" cvar that displays a message in the server console when someone is downloading a file
This commit is contained in:
Louis-Antoine 2017-01-13 20:53:52 +01:00
parent 04747ff79e
commit e9cb6d0331
11 changed files with 446 additions and 153 deletions

View file

@ -72,14 +72,21 @@
#define MAX_REASONLENGTH 30 #define MAX_REASONLENGTH 30
boolean server = true; // true or false but !server == client boolean server = true; // true or false but !server == client
#define client (!server)
boolean nodownload = false; boolean nodownload = false;
static boolean serverrunning = false; static boolean serverrunning = false;
INT32 serverplayer = 0; INT32 serverplayer = 0;
char motd[254], server_context[8]; // Message of the Day, Unique Context (even without Mumble support) char motd[254], server_context[8]; // Message of the Day, Unique Context (even without Mumble support)
// server specific vars // Server specific vars
UINT8 playernode[MAXPLAYERS]; UINT8 playernode[MAXPLAYERS];
// Minimum timeout for sending the savegame
// The actual timeout will be longer depending on the savegame length
tic_t jointimeout = (10*TICRATE);
static boolean sendingsavegame[MAXNETNODES]; // Are we sending the savegame?
static tic_t freezetimeout[MAXNETNODES]; // Until when can this node freeze the server before getting a timeout?
#ifdef NEWPING #ifdef NEWPING
UINT16 pingmeasurecount = 1; UINT16 pingmeasurecount = 1;
UINT32 realpingtable[MAXPLAYERS]; //the base table of ping where an average will be sent to everyone. UINT32 realpingtable[MAXPLAYERS]; //the base table of ping where an average will be sent to everyone.
@ -108,7 +115,7 @@ static UINT8 resynch_local_inprogress = false; // WE are desynched and getting p
static UINT8 player_joining = false; static UINT8 player_joining = false;
UINT8 hu_resynching = 0; UINT8 hu_resynching = 0;
// client specific // Client specific
static ticcmd_t localcmds; static ticcmd_t localcmds;
static ticcmd_t localcmds2; static ticcmd_t localcmds2;
static boolean cl_packetmissed; static boolean cl_packetmissed;
@ -404,7 +411,7 @@ static void ExtraDataTicker(void)
// If you are a client, you can safely forget the net commands for this tic // If you are a client, you can safely forget the net commands for this tic
// If you are the server, you need to remember them until every client has been aknowledged, // If you are the server, you need to remember them until every client has been aknowledged,
// because if you need to resend a PT_SERVERTICS packet, you need to put the commands in it // because if you need to resend a PT_SERVERTICS packet, you need to put the commands in it
if (!server) if (client)
D_FreeTextcmd(gametic); D_FreeTextcmd(gametic);
} }
@ -1038,6 +1045,9 @@ static void SV_AcknowledgeResynchAck(INT32 node, UINT8 rsg)
resynch_status[node] &= ~(1<<rsg); resynch_status[node] &= ~(1<<rsg);
--resynch_score[node]; // unpenalize --resynch_score[node]; // unpenalize
} }
// Don't let resynch cause a timeout
freezetimeout[node] = I_GetTime() + connectiontimeout;
} }
// ----------------------------------------------------------------- // -----------------------------------------------------------------
// end resynch // end resynch
@ -1131,12 +1141,17 @@ static inline void CL_DrawConnectionStatus(void)
{ {
#ifdef JOININGAME #ifdef JOININGAME
case CL_DOWNLOADSAVEGAME: case CL_DOWNLOADSAVEGAME:
if (lastfilenum != -1)
{
cltext = M_GetText("Downloading game state..."); cltext = M_GetText("Downloading game state...");
Net_GetNetStat(); Net_GetNetStat();
V_DrawString(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE, V_DrawString(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE,
va(" %4uK",fileneeded[lastfilenum].currentsize>>10)); va(" %4uK",fileneeded[lastfilenum].currentsize>>10));
V_DrawRightAlignedString(BASEVIDWIDTH/2+128, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE, V_DrawRightAlignedString(BASEVIDWIDTH/2+128, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE,
va("%3.1fK/s ", ((double)getbps)/1024)); va("%3.1fK/s ", ((double)getbps)/1024));
}
else
cltext = M_GetText("Waiting to download game state...");
break; break;
#endif #endif
case CL_ASKJOIN: case CL_ASKJOIN:
@ -1150,6 +1165,8 @@ static inline void CL_DrawConnectionStatus(void)
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24-32, V_YELLOWMAP, cltext); V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24-32, V_YELLOWMAP, cltext);
} }
else else
{
if (lastfilenum != -1)
{ {
INT32 dldlength; INT32 dldlength;
static char tempname[32]; static char tempname[32];
@ -1171,6 +1188,10 @@ static inline void CL_DrawConnectionStatus(void)
V_DrawRightAlignedString(BASEVIDWIDTH/2+128, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE, V_DrawRightAlignedString(BASEVIDWIDTH/2+128, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE,
va("%3.1fK/s ", ((double)getbps)/1024)); va("%3.1fK/s ", ((double)getbps)/1024));
} }
else
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24-32, V_YELLOWMAP,
M_GetText("Waiting to download files..."));
}
} }
#endif #endif
@ -1458,6 +1479,10 @@ static void SV_SendSaveGame(INT32 node)
SV_SendRam(node, buffertosend, length, SF_RAM, 0); SV_SendRam(node, buffertosend, length, SF_RAM, 0);
save_p = NULL; save_p = NULL;
// Remember when we started sending the savegame so we can handle timeouts
sendingsavegame[node] = true;
freezetimeout[node] = I_GetTime() + jointimeout + length / 1024; // 1 extra tic for each kilobyte
} }
#ifdef DUMPCONSISTENCY #ifdef DUMPCONSISTENCY
@ -1750,7 +1775,7 @@ static boolean CL_ServerConnectionSearchTicker(boolean viams, tic_t *asksent)
return false; return false;
} }
if (!server) if (client)
{ {
D_ParseFileneeded(serverlist[i].info.fileneedednum, D_ParseFileneeded(serverlist[i].info.fileneedednum,
serverlist[i].info.fileneeded); serverlist[i].info.fileneeded);
@ -1924,7 +1949,7 @@ static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, tic
*oldtic = I_GetTime(); *oldtic = I_GetTime();
#ifdef CLIENT_LOADINGSCREEN #ifdef CLIENT_LOADINGSCREEN
if (!server && cl_mode != CL_CONNECTED && cl_mode != CL_ABORTED) if (client && cl_mode != CL_CONNECTED && cl_mode != CL_ABORTED)
{ {
F_TitleScreenTicker(true); F_TitleScreenTicker(true);
F_TitleScreenDrawer(); F_TitleScreenDrawer();
@ -1966,7 +1991,7 @@ static void CL_ConnectToServer(boolean viams)
cl_mode = CL_SEARCHING; cl_mode = CL_SEARCHING;
#ifdef CLIENT_LOADINGSCREEN #ifdef CLIENT_LOADINGSCREEN
lastfilenum = 0; lastfilenum = -1;
#endif #endif
#ifdef JOININGAME #ifdef JOININGAME
@ -2037,7 +2062,7 @@ static void CL_ConnectToServer(boolean viams)
pnumnodes++; pnumnodes++;
} }
} }
while (!(cl_mode == CL_CONNECTED && (!server || (server && nodewaited <= pnumnodes)))); while (!(cl_mode == CL_CONNECTED && (client || (server && nodewaited <= pnumnodes))));
DEBFILE(va("Synchronisation Finished\n")); DEBFILE(va("Synchronisation Finished\n"));
@ -2574,6 +2599,14 @@ static void Command_Kick(void)
WRITESINT8(p, pn); WRITESINT8(p, pn);
if (pn == -1 || pn == 0) if (pn == -1 || pn == 0)
return; return;
// Special case if we are trying to kick a player who is downloading the game state:
// trigger a timeout instead of kicking them, because a kick would only
// take effect after they have finished downloading
if (sendingsavegame[playernode[pn]])
{
Net_ConnectionTimeout(playernode[pn]);
return;
}
if (COM_Argc() == 2) if (COM_Argc() == 2)
{ {
WRITEUINT8(p, KICK_MSG_GO_AWAY); WRITEUINT8(p, KICK_MSG_GO_AWAY);
@ -2786,7 +2819,12 @@ consvar_t cv_blamecfail = {"blamecfail", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL
// max file size to send to a player (in kilobytes) // max file size to send to a player (in kilobytes)
static CV_PossibleValue_t maxsend_cons_t[] = {{0, "MIN"}, {51200, "MAX"}, {0, NULL}}; static CV_PossibleValue_t maxsend_cons_t[] = {{0, "MIN"}, {51200, "MAX"}, {0, NULL}};
consvar_t cv_maxsend = {"maxsend", "1024", CV_SAVE, maxsend_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_maxsend = {"maxsend", "4096", CV_SAVE, maxsend_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
consvar_t cv_noticedownload = {"noticedownload", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
// Speed of file downloading (in packets per tic)
static CV_PossibleValue_t downloadspeed_cons_t[] = {{0, "MIN"}, {32, "MAX"}, {0, NULL}};
consvar_t cv_downloadspeed = {"downloadspeed", "16", CV_SAVE, downloadspeed_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
static void Got_AddPlayer(UINT8 **p, INT32 playernum); static void Got_AddPlayer(UINT8 **p, INT32 playernum);
@ -2809,6 +2847,9 @@ void D_ClientServerInit(void)
COM_AddCommand("drop", Command_Drop); COM_AddCommand("drop", Command_Drop);
COM_AddCommand("droprate", Command_Droprate); COM_AddCommand("droprate", Command_Droprate);
#endif #endif
#ifdef DEBUGMODE
COM_AddCommand("numnodes", Command_Numnodes);
#endif
#endif #endif
RegisterNetXCmd(XD_KICK, Got_KickCmd); RegisterNetXCmd(XD_KICK, Got_KickCmd);
@ -2844,6 +2885,7 @@ static void ResetNode(INT32 node)
supposedtics[node] = gametic; supposedtics[node] = gametic;
nodewaiting[node] = 0; nodewaiting[node] = 0;
playerpernode[node] = 0; playerpernode[node] = 0;
sendingsavegame[node] = false;
} }
void SV_ResetServer(void) void SV_ResetServer(void)
@ -3136,7 +3178,7 @@ void CL_RemoveSplitscreenPlayer(void)
// is there a game running // is there a game running
boolean Playing(void) boolean Playing(void)
{ {
return (server && serverrunning) || (!server && cl_mode == CL_CONNECTED); return (server && serverrunning) || (client && cl_mode == CL_CONNECTED);
} }
boolean SV_SpawnServer(void) boolean SV_SpawnServer(void)
@ -3399,12 +3441,19 @@ static void HandlePacketFromAwayNode(SINT8 node)
} }
if (cl_mode == CL_WAITJOINRESPONSE) if (cl_mode == CL_WAITJOINRESPONSE)
{ {
// Save the reason so it can be displayed after quitting the netgame
char *reason = strdup(netbuffer->u.serverrefuse.reason);
if (!reason)
I_Error("Out of memory!\n");
D_QuitNetGame(); D_QuitNetGame();
CL_Reset(); CL_Reset();
D_StartTitle(); D_StartTitle();
M_StartMessage(va(M_GetText("Server refuses connection\n\nReason:\n%s"), M_StartMessage(va(M_GetText("Server refuses connection\n\nReason:\n%s"),
netbuffer->u.serverrefuse.reason), NULL, MM_NOTHING); reason), NULL, MM_NOTHING);
free(reason);
// Will be reset by caller. Signals refusal. // Will be reset by caller. Signals refusal.
cl_mode = CL_ABORTED; cl_mode = CL_ABORTED;
@ -3425,7 +3474,7 @@ static void HandlePacketFromAwayNode(SINT8 node)
if (cl_mode != CL_WAITJOINRESPONSE) if (cl_mode != CL_WAITJOINRESPONSE)
break; break;
if (!server) if (client)
{ {
maketic = gametic = neededtic = (tic_t)LONG(netbuffer->u.servercfg.gametic); maketic = gametic = neededtic = (tic_t)LONG(netbuffer->u.servercfg.gametic);
gametype = netbuffer->u.servercfg.gametype; gametype = netbuffer->u.servercfg.gametype;
@ -3553,7 +3602,7 @@ FILESTAMP
case PT_CLIENT2MIS: case PT_CLIENT2MIS:
case PT_NODEKEEPALIVE: case PT_NODEKEEPALIVE:
case PT_NODEKEEPALIVEMIS: case PT_NODEKEEPALIVEMIS:
if (!server) if (client)
break; break;
// Ignore tics from those not synched // Ignore tics from those not synched
@ -3586,6 +3635,13 @@ FILESTAMP
|| netbuffer->packettype == PT_NODEKEEPALIVEMIS) || netbuffer->packettype == PT_NODEKEEPALIVEMIS)
break; break;
// If a client sends a ticcmd it should mean they are done receiving the savegame
sendingsavegame[node] = false;
// As long as clients send valid ticcmds, the server can keep running, so reset the timeout
/// \todo Use a separate cvar for that kind of timeout?
freezetimeout[node] = I_GetTime() + connectiontimeout;
// Copy ticcmd // Copy ticcmd
G_MoveTiccmd(&netcmds[maketic%BACKUPTICS][netconsole], &netbuffer->u.clientpak.cmd, 1); G_MoveTiccmd(&netcmds[maketic%BACKUPTICS][netconsole], &netbuffer->u.clientpak.cmd, 1);
@ -3652,7 +3708,7 @@ FILESTAMP
case PT_TEXTCMD2: // splitscreen special case PT_TEXTCMD2: // splitscreen special
netconsole = nodetoplayer2[node]; netconsole = nodetoplayer2[node];
case PT_TEXTCMD: case PT_TEXTCMD:
if (!server) if (client)
break; break;
if (netconsole < 0 || netconsole >= MAXPLAYERS) if (netconsole < 0 || netconsole >= MAXPLAYERS)
@ -3696,7 +3752,7 @@ FILESTAMP
break; break;
case PT_NODETIMEOUT: case PT_NODETIMEOUT:
case PT_CLIENTQUIT: case PT_CLIENTQUIT:
if (!server) if (client)
break; break;
// nodeingame will be put false in the execution of kick command // nodeingame will be put false in the execution of kick command
@ -3728,7 +3784,7 @@ FILESTAMP
// Only accept PT_RESYNCHEND from the server. // Only accept PT_RESYNCHEND from the server.
if (node != servernode) if (node != servernode)
{ {
CONS_Alert(CONS_WARNING, M_GetText("%s recieved from non-host %d\n"), "PT_RESYNCHEND", node); CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_RESYNCHEND", node);
if (server) if (server)
{ {
@ -3806,13 +3862,20 @@ FILESTAMP
neededtic = realend; neededtic = realend;
} }
else else
{
DEBFILE(va("frame not in bound: %u\n", neededtic)); DEBFILE(va("frame not in bound: %u\n", neededtic));
/*if (realend < neededtic - 2 * TICRATE || neededtic + 2 * TICRATE < realstart)
I_Error("Received an out of order PT_SERVERTICS packet!\n"
"Got tics %d-%d, needed tic %d\n\n"
"Please report this crash on the Master Board,\n"
"IRC or Discord so it can be fixed.\n", (INT32)realstart, (INT32)realend, (INT32)neededtic);*/
}
break; break;
case PT_RESYNCHING: case PT_RESYNCHING:
// Only accept PT_RESYNCHING from the server. // Only accept PT_RESYNCHING from the server.
if (node != servernode) if (node != servernode)
{ {
CONS_Alert(CONS_WARNING, M_GetText("%s recieved from non-host %d\n"), "PT_RESYNCHING", node); CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_RESYNCHING", node);
if (server) if (server)
{ {
@ -3832,7 +3895,7 @@ FILESTAMP
// Only accept PT_PING from the server. // Only accept PT_PING from the server.
if (node != servernode) if (node != servernode)
{ {
CONS_Alert(CONS_WARNING, M_GetText("%s recieved from non-host %d\n"), "PT_PING", node); CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_PING", node);
if (server) if (server)
{ {
@ -3846,7 +3909,7 @@ FILESTAMP
} }
//Update client ping table from the server. //Update client ping table from the server.
if (!server) if (client)
{ {
INT32 i; INT32 i;
for (i = 0; i < MAXNETNODES; i++) for (i = 0; i < MAXNETNODES; i++)
@ -3859,7 +3922,7 @@ FILESTAMP
case PT_SERVERCFG: case PT_SERVERCFG:
break; break;
case PT_FILEFRAGMENT: case PT_FILEFRAGMENT:
if (!server) if (client)
Got_Filetxpak(); Got_Filetxpak();
break; break;
default: default:
@ -3889,18 +3952,19 @@ FILESTAMP
HandleConnect(node); HandleConnect(node);
continue; continue;
} }
if (netbuffer->packettype == PT_SERVERSHUTDOWN && node == servernode if (node == servernode && client && cl_mode != CL_SEARCHING)
&& !server && cl_mode != CL_SEARCHING) {
if (netbuffer->packettype == PT_SERVERSHUTDOWN)
{ {
HandleShutdown(node); HandleShutdown(node);
continue; continue;
} }
if (netbuffer->packettype == PT_NODETIMEOUT && node == servernode if (netbuffer->packettype == PT_NODETIMEOUT)
&& !server && cl_mode != CL_SEARCHING)
{ {
HandleTimeout(node); HandleTimeout(node);
continue; continue;
} }
}
#ifndef NONET #ifndef NONET
if (netbuffer->packettype == PT_SERVERINFO) if (netbuffer->packettype == PT_SERVERINFO)
@ -3916,7 +3980,7 @@ FILESTAMP
// Packet received from someone already playing // Packet received from someone already playing
if (nodeingame[node]) if (nodeingame[node])
HandlePacketFromPlayer(node); HandlePacketFromPlayer(node);
// Packet received from someone trying to join // Packet received from someone not playing
else else
HandlePacketFromAwayNode(node); HandlePacketFromAwayNode(node);
} }
@ -4047,7 +4111,7 @@ static void CL_SendClientCmd(void)
if (gamestate == GS_WAITINGPLAYERS) if (gamestate == GS_WAITINGPLAYERS)
{ {
// send NODEKEEPALIVE packet // Send PT_NODEKEEPALIVE packet
netbuffer->packettype += 4; netbuffer->packettype += 4;
packetsize = sizeof (clientcmd_pak) - sizeof (ticcmd_t) - sizeof (INT16); packetsize = sizeof (clientcmd_pak) - sizeof (ticcmd_t) - sizeof (INT16);
HSendPacket(servernode, false, 0, packetsize); HSendPacket(servernode, false, 0, packetsize);
@ -4057,7 +4121,7 @@ static void CL_SendClientCmd(void)
G_MoveTiccmd(&netbuffer->u.clientpak.cmd, &localcmds, 1); G_MoveTiccmd(&netbuffer->u.clientpak.cmd, &localcmds, 1);
netbuffer->u.clientpak.consistancy = SHORT(consistancy[gametic%BACKUPTICS]); netbuffer->u.clientpak.consistancy = SHORT(consistancy[gametic%BACKUPTICS]);
// send a special packet with 2 cmd for splitscreen // Send a special packet with 2 cmd for splitscreen
if (splitscreen || botingame) if (splitscreen || botingame)
{ {
netbuffer->packettype += 2; netbuffer->packettype += 2;
@ -4072,23 +4136,23 @@ static void CL_SendClientCmd(void)
if (cl_mode == CL_CONNECTED || dedicated) if (cl_mode == CL_CONNECTED || dedicated)
{ {
// send extra data if needed // Send extra data if needed
if (localtextcmd[0]) if (localtextcmd[0])
{ {
netbuffer->packettype = PT_TEXTCMD; netbuffer->packettype = PT_TEXTCMD;
M_Memcpy(netbuffer->u.textcmd,localtextcmd, localtextcmd[0]+1); M_Memcpy(netbuffer->u.textcmd,localtextcmd, localtextcmd[0]+1);
// all extra data have been sended // All extra data have been sent
if (HSendPacket(servernode, true, 0, localtextcmd[0]+1)) // send can fail... if (HSendPacket(servernode, true, 0, localtextcmd[0]+1)) // Send can fail...
localtextcmd[0] = 0; localtextcmd[0] = 0;
} }
// send extra data if needed for player 2 (splitscreen) // Send extra data if needed for player 2 (splitscreen)
if (localtextcmd2[0]) if (localtextcmd2[0])
{ {
netbuffer->packettype = PT_TEXTCMD2; netbuffer->packettype = PT_TEXTCMD2;
M_Memcpy(netbuffer->u.textcmd, localtextcmd2, localtextcmd2[0]+1); M_Memcpy(netbuffer->u.textcmd, localtextcmd2, localtextcmd2[0]+1);
// all extra data have been sended // All extra data have been sent
if (HSendPacket(servernode, true, 0, localtextcmd2[0]+1)) // send can fail... if (HSendPacket(servernode, true, 0, localtextcmd2[0]+1)) // Send can fail...
localtextcmd2[0] = 0; localtextcmd2[0] = 0;
} }
} }
@ -4352,7 +4416,7 @@ static inline void PingUpdate(void)
//check for ping limit breakage. //check for ping limit breakage.
if (cv_maxping.value) if (cv_maxping.value)
{ {
for (i = 1; i < MAXNETNODES; i++) for (i = 1; i < MAXPLAYERS; i++)
{ {
if (playeringame[i] && (realpingtable[i] / pingmeasurecount > (unsigned)cv_maxping.value)) if (playeringame[i] && (realpingtable[i] / pingmeasurecount > (unsigned)cv_maxping.value))
{ {
@ -4366,7 +4430,7 @@ static inline void PingUpdate(void)
//in that case, it is probably the server's fault. //in that case, it is probably the server's fault.
if (numlaggers < D_NumPlayers() - 1) if (numlaggers < D_NumPlayers() - 1)
{ {
for (i = 1; i < MAXNETNODES; i++) for (i = 1; i < MAXPLAYERS; i++)
{ {
if (playeringame[i] && laggers[i]) if (playeringame[i] && laggers[i])
{ {
@ -4381,7 +4445,7 @@ static inline void PingUpdate(void)
} }
//make the ping packet and clear server data for next one //make the ping packet and clear server data for next one
for (i = 0; i < MAXNETNODES; i++) for (i = 0; i < MAXPLAYERS; i++)
{ {
netbuffer->u.pingtable[i] = realpingtable[i] / pingmeasurecount; netbuffer->u.pingtable[i] = realpingtable[i] / pingmeasurecount;
//server takes a snapshot of the real ping for display. //server takes a snapshot of the real ping for display.
@ -4391,7 +4455,7 @@ static inline void PingUpdate(void)
} }
//send out our ping packets //send out our ping packets
for (i = 0; i < MAXNETNODES; i++) for (i = 0; i < MAXPLAYERS; i++)
if (playeringame[i]) if (playeringame[i])
HSendPacket(i, true, 0, sizeof(INT32) * MAXPLAYERS); HSendPacket(i, true, 0, sizeof(INT32) * MAXPLAYERS);
@ -4440,7 +4504,7 @@ void NetUpdate(void)
} }
#endif #endif
if (!server) if (client)
maketic = neededtic; maketic = neededtic;
Local_Maketic(realtics); // make local tic, and call menu? Local_Maketic(realtics); // make local tic, and call menu?
@ -4455,7 +4519,7 @@ FILESTAMP
MasterClient_Ticker(); // Acking the Master Server MasterClient_Ticker(); // Acking the Master Server
if (!server) if (client)
{ {
if (!resynch_local_inprogress) if (!resynch_local_inprogress)
CL_SendClientCmd(); // Send tic cmd CL_SendClientCmd(); // Send tic cmd
@ -4505,6 +4569,11 @@ FILESTAMP
} }
} }
Net_AckTicker(); Net_AckTicker();
// Handle timeouts to prevent definitive freezes from happenning
if (server)
for (i = 1; i < MAXNETNODES; i++)
if (nodeingame[i] && freezetimeout[i] < I_GetTime())
Net_ConnectionTimeout(i);
nowtime /= NEWTICRATERATIO; nowtime /= NEWTICRATERATIO;
if (nowtime > resptime) if (nowtime > resptime)
{ {

View file

@ -80,6 +80,10 @@ typedef enum
void Command_Drop(void); void Command_Drop(void);
void Command_Droprate(void); void Command_Droprate(void);
#endif #endif
#ifdef DEBUGMODE
void Command_Numnodes(void);
#endif
boolean NodeClosing(INT32 node);
#if defined(_MSC_VER) #if defined(_MSC_VER)
#pragma pack(1) #pragma pack(1)
@ -442,6 +446,7 @@ extern consvar_t cv_playbackspeed;
#define KICK_MSG_CUSTOM_BAN 8 #define KICK_MSG_CUSTOM_BAN 8
extern boolean server; extern boolean server;
#define client (!server)
extern boolean dedicated; // For dedicated server extern boolean dedicated; // For dedicated server
extern UINT16 software_MAXPACKETLENGTH; extern UINT16 software_MAXPACKETLENGTH;
extern boolean acceptnewnode; extern boolean acceptnewnode;
@ -449,13 +454,14 @@ extern SINT8 servernode;
void Command_Ping_f(void); void Command_Ping_f(void);
extern tic_t connectiontimeout; extern tic_t connectiontimeout;
extern tic_t jointimeout;
#ifdef NEWPING #ifdef NEWPING
extern UINT16 pingmeasurecount; extern UINT16 pingmeasurecount;
extern UINT32 realpingtable[MAXPLAYERS]; extern UINT32 realpingtable[MAXPLAYERS];
extern UINT32 playerpingtable[MAXPLAYERS]; extern UINT32 playerpingtable[MAXPLAYERS];
#endif #endif
extern consvar_t cv_joinnextround, cv_allownewplayer, cv_maxplayers, cv_resynchattempts, cv_blamecfail, cv_maxsend; extern consvar_t cv_joinnextround, cv_allownewplayer, cv_maxplayers, cv_resynchattempts, cv_blamecfail, cv_maxsend, cv_noticedownload, cv_downloadspeed;
// Used in d_net, the only dependence // Used in d_net, the only dependence
tic_t ExpandTics(INT32 low); tic_t ExpandTics(INT32 low);

View file

@ -42,7 +42,7 @@
// Normally maketic >= gametic > 0 // Normally maketic >= gametic > 0
#define FORCECLOSE 0x8000 #define FORCECLOSE 0x8000
tic_t connectiontimeout = (15*TICRATE); tic_t connectiontimeout = (10*TICRATE);
/// \brief network packet /// \brief network packet
doomcom_t *doomcom = NULL; doomcom_t *doomcom = NULL;
@ -62,7 +62,7 @@ INT32 net_bandwidth;
/// \brief max length per packet /// \brief max length per packet
INT16 hardware_MAXPACKETLENGTH; INT16 hardware_MAXPACKETLENGTH;
void (*I_NetGet)(void) = NULL; boolean (*I_NetGet)(void) = NULL;
void (*I_NetSend)(void) = NULL; void (*I_NetSend)(void) = NULL;
boolean (*I_NetCanSend)(void) = NULL; boolean (*I_NetCanSend)(void) = NULL;
boolean (*I_NetCanGet)(void) = NULL; boolean (*I_NetCanGet)(void) = NULL;
@ -142,7 +142,7 @@ typedef struct
UINT8 destinationnode; // The node to send the ack to UINT8 destinationnode; // The node to send the ack to
tic_t senttime; // The time when the ack was sent tic_t senttime; // The time when the ack was sent
UINT16 length; // The packet size UINT16 length; // The packet size
UINT16 resentnum; // The number of UINT16 resentnum; // The number of times the ack has been resent
union { union {
SINT8 raw[MAXPACKETLENGTH]; SINT8 raw[MAXPACKETLENGTH];
doomdata_t data; doomdata_t data;
@ -152,11 +152,12 @@ typedef struct
typedef enum typedef enum
{ {
CLOSE = 1, // flag is set when connection is closing NF_CLOSE = 1, // Flag is set when connection is closing
NF_TIMEOUT = 2, // Flag is set when the node got a timeout
} node_flags_t; } node_flags_t;
#ifndef NONET #ifndef NONET
// table of packet that was not acknowleged can be resend (the sender window) // Table of packets that were not acknowleged can be resent (the sender window)
static ackpak_t ackpak[MAXACKPACKETS]; static ackpak_t ackpak[MAXACKPACKETS];
#endif #endif
@ -274,6 +275,38 @@ static boolean GetFreeAcknum(UINT8 *freeack, boolean lowtimer)
return false; 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;
}
// Get a ack to send in the queue of this node // Get a ack to send in the queue of this node
static UINT8 GetAcktosend(INT32 node) static UINT8 GetAcktosend(INT32 node)
{ {
@ -298,7 +331,7 @@ static void RemoveAck(INT32 i)
DEBFILE(va("Remove ack %d\n",ackpak[i].acknum)); DEBFILE(va("Remove ack %d\n",ackpak[i].acknum));
#endif #endif
ackpak[i].acknum = 0; ackpak[i].acknum = 0;
if (nodes[node].flags & CLOSE) if (nodes[node].flags & NF_CLOSE)
Net_CloseConnection(node); Net_CloseConnection(node);
} }
@ -452,8 +485,13 @@ static void GotAcks(void)
} }
#endif #endif
static inline void Net_ConnectionTimeout(INT32 node) void Net_ConnectionTimeout(INT32 node)
{ {
// Don't timeout several times
if (nodes[node].flags & NF_TIMEOUT)
return;
nodes[node].flags |= NF_TIMEOUT;
// Send a very special packet to self (hack the reboundstore queue) // Send a very special packet to self (hack the reboundstore queue)
// Main code will handle it // Main code will handle it
reboundstore[rebound_head].packettype = PT_NODETIMEOUT; reboundstore[rebound_head].packettype = PT_NODETIMEOUT;
@ -484,7 +522,7 @@ void Net_AckTicker(void)
if (ackpak[i].acknum && ackpak[i].senttime + node->timeout < I_GetTime()) if (ackpak[i].acknum && ackpak[i].senttime + node->timeout < I_GetTime())
#endif #endif
{ {
if (ackpak[i].resentnum > 10 && (node->flags & CLOSE)) if (ackpak[i].resentnum > 10 && (node->flags & NF_CLOSE))
{ {
DEBFILE(va("ack %d sent 10 times so connection is supposed lost: node %d\n", DEBFILE(va("ack %d sent 10 times so connection is supposed lost: node %d\n",
i, nodei)); i, nodei));
@ -520,7 +558,7 @@ void Net_AckTicker(void)
if (nodes[i].lasttimeacktosend_sent + ACKTOSENDTIMEOUT < I_GetTime()) if (nodes[i].lasttimeacktosend_sent + ACKTOSENDTIMEOUT < I_GetTime())
Net_SendAcks(i); Net_SendAcks(i);
if (!(nodes[i].flags & CLOSE) if (!(nodes[i].flags & NF_CLOSE)
&& nodes[i].lasttimepacketreceived + connectiontimeout < I_GetTime()) && nodes[i].lasttimepacketreceived + connectiontimeout < I_GetTime())
{ {
Net_ConnectionTimeout(i); Net_ConnectionTimeout(i);
@ -678,7 +716,7 @@ void Net_CloseConnection(INT32 node)
if (!node) if (!node)
return; return;
nodes[node].flags |= CLOSE; nodes[node].flags |= NF_CLOSE;
// try to Send ack back (two army problem) // try to Send ack back (two army problem)
if (GetAcktosend(node)) if (GetAcktosend(node))
@ -813,18 +851,20 @@ static void DebugPrintpacket(const char *header)
case PT_SERVERTICS: case PT_SERVERTICS:
{ {
servertics_pak *serverpak = &netbuffer->u.serverpak; servertics_pak *serverpak = &netbuffer->u.serverpak;
ticcmd_t *cmd = &serverpak->cmds[serverpak->numslots * serverpak->numtics]; UINT8 *cmd = (UINT8 *)(&serverpak->cmds[serverpak->numslots * serverpak->numtics]);
size_t ntxtcmd = &((UINT8 *)netbuffer)[doomcom->datalength] - (UINT8 *)cmd; size_t ntxtcmd = &((UINT8 *)netbuffer)[doomcom->datalength] - cmd;
fprintf(debugfile, " firsttic %u ply %d tics %d ntxtcmd %s\n ", fprintf(debugfile, " firsttic %u ply %d tics %d ntxtcmd %s\n ",
(UINT32)ExpandTics(serverpak->starttic), serverpak->numslots, serverpak->numtics, sizeu1(ntxtcmd)); (UINT32)ExpandTics(serverpak->starttic), serverpak->numslots, serverpak->numtics, sizeu1(ntxtcmd));
fprintfstring((char *)cmd, 3); /// \todo Display more readable information about net commands
fprintfstringnewline((char *)cmd, ntxtcmd);
/*fprintfstring((char *)cmd, 3);
if (ntxtcmd > 4) if (ntxtcmd > 4)
{ {
fprintf(debugfile, "[%s]", netxcmdnames[*(((UINT8 *)cmd) + 3) - 1]); fprintf(debugfile, "[%s]", netxcmdnames[*((cmd) + 3) - 1]);
fprintfstring(((char *)cmd) + 4, ntxtcmd - 4); fprintfstring(((char *)cmd) + 4, ntxtcmd - 4);
} }
fprintf(debugfile, "\n"); fprintf(debugfile, "\n");*/
break; break;
} }
case PT_CLIENTCMD: case PT_CLIENTCMD:
@ -891,7 +931,7 @@ void Command_Drop(void)
if (COM_Argc() < 2) if (COM_Argc() < 2)
{ {
CONS_Printf("drop <packettype> [quantity]: drop packets\n" CONS_Printf("drop <packettype> [quantity]: drop packets\n"
"drop reset: cancel all packet drops"); "drop reset: cancel all packet drops\n");
return; return;
} }
@ -958,6 +998,11 @@ static boolean ShouldDropPacket(void)
} }
#endif #endif
boolean NodeClosing(INT32 node)
{
return ((nodes[node].flags) & NF_CLOSE) != 0;
}
// //
// HSendPacket // HSendPacket
// //
@ -1067,6 +1112,8 @@ boolean HSendPacket(INT32 node, boolean reliable, UINT8 acknum, size_t packetlen
// //
boolean HGetPacket(void) boolean HGetPacket(void)
{ {
//boolean nodejustjoined;
// Get a packet from self // Get a packet from self
if (rebound_tail != rebound_head) if (rebound_tail != rebound_head)
{ {
@ -1092,9 +1139,10 @@ boolean HGetPacket(void)
while(true) while(true)
{ {
//nodejustjoined = I_NetGet();
I_NetGet(); I_NetGet();
if (doomcom->remotenode == -1) if (doomcom->remotenode == -1) // No packet received
return false; return false;
getbytes += packetheaderlength + doomcom->datalength; // For stat getbytes += packetheaderlength + doomcom->datalength; // For stat
@ -1110,6 +1158,7 @@ boolean HGetPacket(void)
if (netbuffer->checksum != NetbufferChecksum()) if (netbuffer->checksum != NetbufferChecksum())
{ {
DEBFILE("Bad packet checksum\n"); DEBFILE("Bad packet checksum\n");
//Net_CloseConnection(nodejustjoined ? (doomcom->remotenode | FORCECLOSE) : doomcom->remotenode);
Net_CloseConnection(doomcom->remotenode); Net_CloseConnection(doomcom->remotenode);
continue; continue;
} }
@ -1119,11 +1168,26 @@ boolean HGetPacket(void)
DebugPrintpacket("GET"); DebugPrintpacket("GET");
#endif #endif
// proceed the ack and ackreturn field /*// 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
if (!Processackpak()) if (!Processackpak())
continue; // discarded (duplicated) continue; // discarded (duplicated)
// a packet with just ackreturn // A packet with just ackreturn
if (netbuffer->packettype == PT_NOTHING) if (netbuffer->packettype == PT_NOTHING)
{ {
GotAcks(); GotAcks();
@ -1136,9 +1200,10 @@ boolean HGetPacket(void)
return true; return true;
} }
static void Internal_Get(void) static boolean Internal_Get(void)
{ {
doomcom->remotenode = -1; doomcom->remotenode = -1;
return false;
} }
FUNCNORETURN static ATTRNORETURN void Internal_Send(void) FUNCNORETURN static ATTRNORETURN void Internal_Send(void)
@ -1223,7 +1288,7 @@ boolean D_CheckNetGame(void)
if (netgame) if (netgame)
ret = true; ret = true;
if (!server && netgame) if (client && netgame)
netgame = false; netgame = false;
server = true; // WTF? server always true??? server = true; // WTF? server always true???
// no! The deault mode is server. Client is set elsewhere // no! The deault mode is server. Client is set elsewhere

View file

@ -39,6 +39,7 @@ extern SINT8 nodetoplayer2[MAXNETNODES]; // Say the numplayer for this node if a
extern UINT8 playerpernode[MAXNETNODES]; // Used specially for splitscreen extern UINT8 playerpernode[MAXNETNODES]; // Used specially for splitscreen
extern boolean nodeingame[MAXNETNODES]; // Set false as nodes leave game extern boolean nodeingame[MAXNETNODES]; // Set false as nodes leave game
INT32 Net_GetFreeAcks(boolean urgent);
void Net_AckTicker(void); void Net_AckTicker(void);
// If reliable return true if packet sent, 0 else // If reliable return true if packet sent, 0 else
@ -53,6 +54,7 @@ boolean D_CheckNetGame(void);
void D_CloseConnection(void); void D_CloseConnection(void);
void Net_UnAcknowledgePacket(INT32 node); void Net_UnAcknowledgePacket(INT32 node);
void Net_CloseConnection(INT32 node); void Net_CloseConnection(INT32 node);
void Net_ConnectionTimeout(INT32 node);
void Net_AbortPacketType(UINT8 packettype); void Net_AbortPacketType(UINT8 packettype);
void Net_SendAcks(INT32 node); void Net_SendAcks(INT32 node);
void Net_WaitAllAckReceived(UINT32 timeout); void Net_WaitAllAckReceived(UINT32 timeout);

View file

@ -82,6 +82,7 @@ static void AutoBalance_OnChange(void);
static void TeamScramble_OnChange(void); static void TeamScramble_OnChange(void);
static void NetTimeout_OnChange(void); static void NetTimeout_OnChange(void);
static void JoinTimeout_OnChange(void);
static void Ringslinger_OnChange(void); static void Ringslinger_OnChange(void);
static void Gravity_OnChange(void); static void Gravity_OnChange(void);
@ -340,7 +341,9 @@ consvar_t cv_killingdead = {"killingdead", "Off", CV_NETVAR, CV_OnOff, NULL, 0,
consvar_t cv_netstat = {"netstat", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; // show bandwidth statistics consvar_t cv_netstat = {"netstat", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; // show bandwidth statistics
static CV_PossibleValue_t nettimeout_cons_t[] = {{TICRATE/7, "MIN"}, {60*TICRATE, "MAX"}, {0, NULL}}; static CV_PossibleValue_t nettimeout_cons_t[] = {{TICRATE/7, "MIN"}, {60*TICRATE, "MAX"}, {0, NULL}};
consvar_t cv_nettimeout = {"nettimeout", "525", CV_CALL|CV_SAVE, nettimeout_cons_t, NetTimeout_OnChange, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_nettimeout = {"nettimeout", "350", CV_CALL|CV_SAVE, nettimeout_cons_t, NetTimeout_OnChange, 0, NULL, NULL, 0, 0, NULL};
static CV_PossibleValue_t jointimeout_cons_t[] = {{5*TICRATE, "MIN"}, {60*TICRATE, "MAX"}, {0, NULL}};
consvar_t cv_jointimeout = {"jointimeout", "350", CV_CALL|CV_SAVE, jointimeout_cons_t, JoinTimeout_OnChange, 0, NULL, NULL, 0, 0, NULL};
#ifdef NEWPING #ifdef NEWPING
consvar_t cv_maxping = {"maxping", "0", CV_SAVE, CV_Unsigned, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_maxping = {"maxping", "0", CV_SAVE, CV_Unsigned, NULL, 0, NULL, NULL, 0, 0, NULL};
#endif #endif
@ -546,9 +549,12 @@ void D_RegisterServerCommands(void)
// d_clisrv // d_clisrv
CV_RegisterVar(&cv_maxplayers); CV_RegisterVar(&cv_maxplayers);
CV_RegisterVar(&cv_maxsend); CV_RegisterVar(&cv_maxsend);
CV_RegisterVar(&cv_noticedownload);
CV_RegisterVar(&cv_downloadspeed);
COM_AddCommand("ping", Command_Ping_f); COM_AddCommand("ping", Command_Ping_f);
CV_RegisterVar(&cv_nettimeout); CV_RegisterVar(&cv_nettimeout);
CV_RegisterVar(&cv_jointimeout);
CV_RegisterVar(&cv_skipmapcheck); CV_RegisterVar(&cv_skipmapcheck);
CV_RegisterVar(&cv_sleep); CV_RegisterVar(&cv_sleep);
@ -1005,7 +1011,7 @@ UINT8 CanChangeSkin(INT32 playernum)
return true; return true;
// Force skin in effect. // Force skin in effect.
if (!server && (cv_forceskin.value != -1) && !(adminplayer == playernum && serverplayer == -1)) if (client && (cv_forceskin.value != -1) && !(adminplayer == playernum && serverplayer == -1))
return false; return false;
// Can change skin in intermission and whatnot. // Can change skin in intermission and whatnot.
@ -1616,7 +1622,7 @@ static void Command_Map_f(void)
return; return;
} }
if (!server && !(adminplayer == consoleplayer)) if (client && !(adminplayer == consoleplayer))
{ {
CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n")); CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
return; return;
@ -1943,7 +1949,7 @@ static void Got_Suicide(UINT8 **cp, INT32 playernum)
// You can't suicide someone else. Nice try, there. // You can't suicide someone else. Nice try, there.
if (suicideplayer != playernum || (!G_PlatformGametype())) if (suicideplayer != playernum || (!G_PlatformGametype()))
{ {
CONS_Alert(CONS_WARNING, M_GetText("Illegal suicide command recieved from %s\n"), player_names[playernum]); CONS_Alert(CONS_WARNING, M_GetText("Illegal suicide command received from %s\n"), player_names[playernum]);
if (server) if (server)
{ {
XBOXSTATIC UINT8 buf[2]; XBOXSTATIC UINT8 buf[2];
@ -2658,7 +2664,7 @@ static void Command_Changepassword_f(void)
// If we have no MD5 support then completely disable XD_LOGIN responses for security. // If we have no MD5 support then completely disable XD_LOGIN responses for security.
CONS_Alert(CONS_NOTICE, "Remote administration commands are not supported in this build.\n"); CONS_Alert(CONS_NOTICE, "Remote administration commands are not supported in this build.\n");
#else #else
if (!server) // cannot change remotely if (client) // cannot change remotely
{ {
CONS_Printf(M_GetText("Only the server can use this.\n")); CONS_Printf(M_GetText("Only the server can use this.\n"));
return; return;
@ -2717,7 +2723,7 @@ static void Got_Login(UINT8 **cp, INT32 playernum)
READMEM(*cp, sentmd5, 16); READMEM(*cp, sentmd5, 16);
if (!server) if (client)
return; return;
// Do the final pass to compare with the sent md5 // Do the final pass to compare with the sent md5
@ -2739,7 +2745,7 @@ static void Command_Verify_f(void)
char *temp; char *temp;
INT32 playernum; INT32 playernum;
if (!server) if (client)
{ {
CONS_Printf(M_GetText("Only the server can use this.\n")); CONS_Printf(M_GetText("Only the server can use this.\n"));
return; return;
@ -2823,7 +2829,7 @@ static void Command_MotD_f(void)
return; return;
} }
if ((netgame || multiplayer) && !server) if ((netgame || multiplayer) && client)
SendNetXCmd(XD_SETMOTD, mymotd, sizeof(motd)); SendNetXCmd(XD_SETMOTD, mymotd, sizeof(motd));
else else
{ {
@ -3080,7 +3086,7 @@ static void Got_RequestAddfilecmd(UINT8 **cp, INT32 playernum)
READMEM(*cp, md5sum, 16); READMEM(*cp, md5sum, 16);
// Only the server processes this message. // Only the server processes this message.
if (!server) if (client)
return; return;
// Disallow non-printing characters and semicolons. // Disallow non-printing characters and semicolons.
@ -3347,6 +3353,11 @@ static void NetTimeout_OnChange(void)
connectiontimeout = (tic_t)cv_nettimeout.value; connectiontimeout = (tic_t)cv_nettimeout.value;
} }
static void JoinTimeout_OnChange(void)
{
jointimeout = (tic_t)cv_jointimeout.value;
}
UINT32 timelimitintics = 0; UINT32 timelimitintics = 0;
/** Deals with a timelimit change by printing the change to the console. /** Deals with a timelimit change by printing the change to the console.

View file

@ -97,7 +97,7 @@ char downloaddir[256] = "DOWNLOAD";
#ifdef CLIENT_LOADINGSCREEN #ifdef CLIENT_LOADINGSCREEN
// for cl loading screen // for cl loading screen
INT32 lastfilenum = 0; INT32 lastfilenum = -1;
#endif #endif
/** Fills a serverinfo packet with information about wad files loaded. /** Fills a serverinfo packet with information about wad files loaded.
@ -487,6 +487,9 @@ static void SV_SendFile(INT32 node, const char *filename, UINT8 fileid)
INT32 i; INT32 i;
char wadfilename[MAX_WADPATH]; char wadfilename[MAX_WADPATH];
if (cv_noticedownload.value)
CONS_Printf("Sending file \"%s\" to node %d\n", filename, node);
// Find the last file in the list and set a pointer to its "next" field // Find the last file in the list and set a pointer to its "next" field
q = &transfer[node].txlist; q = &transfer[node].txlist;
while (*q) while (*q)
@ -609,6 +612,8 @@ static void SV_EndFileSend(INT32 node)
switch (p->ram) switch (p->ram)
{ {
case SF_FILE: // It's a file, close it and free its filename case SF_FILE: // It's a file, close it and free its filename
if (cv_noticedownload.value)
CONS_Printf("Ending file transfer for node %d\n", node);
if (transfer[node].currentfile) if (transfer[node].currentfile)
fclose(transfer[node].currentfile); fclose(transfer[node].currentfile);
free(p->id.filename); free(p->id.filename);
@ -636,6 +641,9 @@ static void SV_EndFileSend(INT32 node)
/** Handles file transmission /** Handles file transmission
* *
* \todo Use an acknowledging method more adapted to file transmission
* The current download speed suffers from lack of ack packets,
* especially when the one downloading has high latency
* *
*/ */
void SV_FileSendTicker(void) void SV_FileSendTicker(void)
@ -644,17 +652,34 @@ void SV_FileSendTicker(void)
filetx_pak *p; filetx_pak *p;
size_t size; size_t size;
filetx_t *f; filetx_t *f;
INT32 packetsent = PACKETPERTIC, ram, i; INT32 packetsent, ram, i, j;
INT32 maxpacketsent;
if (!filestosend) if (!filestosend) // No file to send
return; return;
if (cv_downloadspeed.value) // New (and experimental) behavior
{
packetsent = cv_downloadspeed.value;
// Don't send more packets than we have free acks
maxpacketsent = Net_GetFreeAcks(false) - 5; // Let 5 extra acks just in case
if (packetsent > maxpacketsent && maxpacketsent > 0) // Send at least one packet
packetsent = maxpacketsent;
}
else // Old behavior
{
packetsent = PACKETPERTIC;
if (!packetsent) if (!packetsent)
packetsent++; packetsent = 1;
}
netbuffer->packettype = PT_FILEFRAGMENT;
// (((sendbytes-nowsentbyte)*TICRATE)/(I_GetTime()-starttime)<(UINT32)net_bandwidth) // (((sendbytes-nowsentbyte)*TICRATE)/(I_GetTime()-starttime)<(UINT32)net_bandwidth)
while (packetsent-- && filestosend != 0) while (packetsent-- && filestosend != 0)
{ {
for (i = currentnode, ram = 0; ram < MAXNETNODES; for (i = currentnode, j = 0; j < MAXNETNODES;
i = (i+1) % MAXNETNODES, ram++) i = (i+1) % MAXNETNODES, j++)
{ {
if (transfer[i].txlist) if (transfer[i].txlist)
goto found; goto found;
@ -713,7 +738,6 @@ void SV_FileSendTicker(void)
p->position |= LONG(0x80000000); p->position |= LONG(0x80000000);
p->fileid = f->fileid; p->fileid = f->fileid;
p->size = SHORT((UINT16)size); p->size = SHORT((UINT16)size);
netbuffer->packettype = PT_FILEFRAGMENT;
// Send the packet // Send the packet
if (HSendPacket(i, true, 0, FILETXHEADER + size)) // Reliable SEND if (HSendPacket(i, true, 0, FILETXHEADER + size)) // Reliable SEND
@ -735,27 +759,40 @@ void SV_FileSendTicker(void)
void Got_Filetxpak(void) void Got_Filetxpak(void)
{ {
INT32 filenum = netbuffer->u.filetxpak.fileid; INT32 filenum = netbuffer->u.filetxpak.fileid;
fileneeded_t *file = &fileneeded[filenum];
char *filename = file->filename;
static INT32 filetime = 0; static INT32 filetime = 0;
if (!(strcmp(filename, "srb2.srb")
&& strcmp(filename, "srb2.wad")
&& strcmp(filename, "zones.dta")
&& strcmp(filename, "player.dta")
&& strcmp(filename, "rings.dta")
&& strcmp(filename, "patch.dta")
&& strcmp(filename, "music.dta")
))
I_Error("Tried to download \"%s\"", filename);
if (filenum >= fileneedednum) if (filenum >= fileneedednum)
{ {
DEBFILE(va("fileframent not needed %d>%d\n", filenum, fileneedednum)); DEBFILE(va("fileframent not needed %d>%d\n", filenum, fileneedednum));
//I_Error("Received an unneeded file fragment (file id received: %d, file id needed: %d)\n", filenum, fileneedednum);
return; return;
} }
if (fileneeded[filenum].status == FS_REQUESTED) if (file->status == FS_REQUESTED)
{ {
if (fileneeded[filenum].file) if (file->file)
I_Error("Got_Filetxpak: already open file\n"); I_Error("Got_Filetxpak: already open file\n");
fileneeded[filenum].file = fopen(fileneeded[filenum].filename, "wb"); file->file = fopen(filename, "wb");
if (!fileneeded[filenum].file) if (!file->file)
I_Error("Can't create file %s: %s", fileneeded[filenum].filename, strerror(errno)); I_Error("Can't create file %s: %s", filename, strerror(errno));
CONS_Printf("\r%s...\n",fileneeded[filenum].filename); CONS_Printf("\r%s...\n",filename);
fileneeded[filenum].currentsize = 0; file->currentsize = 0;
fileneeded[filenum].status = FS_DOWNLOADING; file->status = FS_DOWNLOADING;
} }
if (fileneeded[filenum].status == FS_DOWNLOADING) if (file->status == FS_DOWNLOADING)
{ {
UINT32 pos = LONG(netbuffer->u.filetxpak.position); UINT32 pos = LONG(netbuffer->u.filetxpak.position);
UINT16 size = SHORT(netbuffer->u.filetxpak.size); UINT16 size = SHORT(netbuffer->u.filetxpak.size);
@ -764,27 +801,47 @@ void Got_Filetxpak(void)
if (pos & 0x80000000) if (pos & 0x80000000)
{ {
pos &= ~0x80000000; pos &= ~0x80000000;
fileneeded[filenum].totalsize = pos + size; file->totalsize = pos + size;
} }
// We can receive packet in the wrong order, anyway all os support gaped file // We can receive packet in the wrong order, anyway all os support gaped file
fseek(fileneeded[filenum].file, pos, SEEK_SET); fseek(file->file, pos, SEEK_SET);
if (fwrite(netbuffer->u.filetxpak.data,size,1,fileneeded[filenum].file) != 1) if (fwrite(netbuffer->u.filetxpak.data,size,1,file->file) != 1)
I_Error("Can't write to %s: %s\n",fileneeded[filenum].filename, strerror(ferror(fileneeded[filenum].file))); I_Error("Can't write to %s: %s\n",filename, strerror(ferror(file->file)));
fileneeded[filenum].currentsize += size; file->currentsize += size;
// Finished? // Finished?
if (fileneeded[filenum].currentsize == fileneeded[filenum].totalsize) if (file->currentsize == file->totalsize)
{ {
fclose(fileneeded[filenum].file); fclose(file->file);
fileneeded[filenum].file = NULL; file->file = NULL;
fileneeded[filenum].status = FS_FOUND; file->status = FS_FOUND;
CONS_Printf(M_GetText("Downloading %s...(done)\n"), CONS_Printf(M_GetText("Downloading %s...(done)\n"),
fileneeded[filenum].filename); filename);
} }
} }
else else
I_Error("Received a file not requested\n"); {
const char *s;
switch(file->status)
{
case FS_NOTFOUND:
s = "FS_NOTFOUND";
break;
case FS_FOUND:
s = "FS_FOUND";
break;
case FS_OPEN:
s = "FS_OPEN";
break;
case FS_MD5SUMBAD:
s = "FS_MD5SUMBAD";
break;
default:
s = "unknown";
break;
}
I_Error("Received a file not requested (file id: %d, file status: %s)\n", filenum, s);
}
// Send ack back quickly // Send ack back quickly
if (++filetime == 3) if (++filetime == 3)
{ {
@ -797,6 +854,17 @@ void Got_Filetxpak(void)
#endif #endif
} }
/** \brief Checks if a node is downloading a file
*
* \param node The node to check for
* \return True if the node is downloading a file
*
*/
boolean SV_SendingFile(INT32 node)
{
return transfer[node].txlist != NULL;
}
/** Cancels all file requests for a node /** Cancels all file requests for a node
* *
* \param node The destination * \param node The destination

View file

@ -65,6 +65,7 @@ void SV_SendRam(INT32 node, void *data, size_t size, freemethod_t freemethod,
void SV_FileSendTicker(void); void SV_FileSendTicker(void);
void Got_Filetxpak(void); void Got_Filetxpak(void);
boolean SV_SendingFile(INT32 node);
boolean CL_CheckDownloadable(void); boolean CL_CheckDownloadable(void);
boolean CL_SendRequestFile(void); boolean CL_SendRequestFile(void);

View file

@ -470,7 +470,7 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
boolean action = false; boolean action = false;
char *ptr; char *ptr;
CONS_Debug(DBG_NETPLAY,"Recieved SAY cmd from Player %d (%s)\n", playernum+1, player_names[playernum]); CONS_Debug(DBG_NETPLAY,"Received SAY cmd from Player %d (%s)\n", playernum+1, player_names[playernum]);
target = READSINT8(*p); target = READSINT8(*p);
flags = READUINT8(*p); flags = READUINT8(*p);
@ -1101,7 +1101,19 @@ void HU_Drawer(void)
// draw desynch text // draw desynch text
if (hu_resynching) if (hu_resynching)
V_DrawCenteredString(BASEVIDWIDTH/2, 180, V_YELLOWMAP, "Resynching..."); {
static UINT32 resynch_ticker = 0;
char resynch_text[14];
INT32 i;
// Animate the dots
resynch_ticker++;
strcpy(resynch_text, "Resynching");
for (i = 0; i < (resynch_ticker / 16) % 4; i++)
strcat(resynch_text, ".");
V_DrawCenteredString(BASEVIDWIDTH/2, 180, V_YELLOWMAP | V_ALLOWLOWERCASE, resynch_text);
}
} }
//====================================================================== //======================================================================

View file

@ -85,7 +85,7 @@ extern doomcom_t *doomcom;
/** \brief return packet in doomcom struct /** \brief return packet in doomcom struct
*/ */
extern void (*I_NetGet)(void); extern boolean (*I_NetGet)(void);
/** \brief ask to driver if there is data waiting /** \brief ask to driver if there is data waiting
*/ */

View file

@ -179,6 +179,7 @@ static UINT8 UPNP_support = TRUE;
#include "i_system.h" #include "i_system.h"
#include "i_net.h" #include "i_net.h"
#include "d_net.h" #include "d_net.h"
#include "d_netfil.h"
#include "i_tcp.h" #include "i_tcp.h"
#include "m_argv.h" #include "m_argv.h"
@ -482,21 +483,12 @@ static boolean SOCK_cmpaddr(mysockaddr_t *a, mysockaddr_t *b, UINT8 mask)
return false; return false;
} }
static SINT8 getfreenode(void)
{
SINT8 j;
for (j = 0; j < MAXNETNODES; j++)
if (!nodeconnected[j])
{
nodeconnected[j] = true;
return j;
}
return -1;
}
// This is a hack. For some reason, nodes aren't being freed properly. // This is a hack. For some reason, nodes aren't being freed properly.
// This goes through and cleans up what nodes were supposed to be freed. // This goes through and cleans up what nodes were supposed to be freed.
/** \warning This function causes the file downloading to stop if someone joins.
* How? Because it removes nodes that are connected but not in game,
* which is exactly what clients downloading a file are.
*/
static void cleanupnodes(void) static void cleanupnodes(void)
{ {
SINT8 j; SINT8 j;
@ -506,13 +498,81 @@ static void cleanupnodes(void)
// Why can't I start at zero? // Why can't I start at zero?
for (j = 1; j < MAXNETNODES; j++) for (j = 1; j < MAXNETNODES; j++)
//if (!(nodeingame[j] || SV_SendingFile(j)))
if (!nodeingame[j]) if (!nodeingame[j])
nodeconnected[j] = false; nodeconnected[j] = false;
} }
static SINT8 getfreenode(void)
{
SINT8 j;
cleanupnodes();
for (j = 0; j < MAXNETNODES; j++)
if (!nodeconnected[j])
{
nodeconnected[j] = true;
return j;
}
/** \warning No free node? Just in case a node might not have been freed properly,
* look if there are connected nodes that aren't in game, and forget them.
* It's dirty, and might result in a poor guy having to restart
* downloading a needed wad, but it's better than not letting anyone join...
*/
/*I_Error("No more free nodes!!1!11!11!!1111\n");
for (j = 1; j < MAXNETNODES; j++)
if (!nodeingame[j])
return j;*/
return -1;
}
#ifdef DEBUGMODE
void Command_Numnodes(void)
{
INT32 connected = 0;
INT32 ingame = 0;
INT32 i;
for (i = 1; i < MAXNETNODES; i++)
{
if (!(nodeconnected[i] || nodeingame[i]))
continue;
if (nodeconnected[i])
connected++;
if (nodeingame[i])
ingame++;
CONS_Printf("%2d - ", i);
if (nodetoplayer[i] != -1)
CONS_Printf("player %.2d", nodetoplayer[i]);
else
CONS_Printf(" ");
if (nodeconnected[i])
CONS_Printf(" - connected");
else
CONS_Printf(" - ");
if (nodeingame[i])
CONS_Printf(" - ingame");
else
CONS_Printf(" - ");
CONS_Printf(" - %s\n", I_GetNodeAddress(i));
}
CONS_Printf("\n"
"Connected: %d\n"
"Ingame: %d\n",
connected, ingame);
}
#endif
#endif #endif
#ifndef NONET #ifndef NONET
static void SOCK_Get(void) // Returns true if a packet was received from a new node, false in all other cases
static boolean SOCK_Get(void)
{ {
size_t i, n; size_t i, n;
int j; int j;
@ -535,13 +595,12 @@ static void SOCK_Get(void)
doomcom->remotenode = (INT16)j; // good packet from a game player doomcom->remotenode = (INT16)j; // good packet from a game player
doomcom->datalength = (INT16)c; doomcom->datalength = (INT16)c;
nodesocket[j] = mysockets[n]; nodesocket[j] = mysockets[n];
return; return false;
} }
} }
// not found // not found
// find a free slot // find a free slot
cleanupnodes();
j = getfreenode(); j = getfreenode();
if (j > 0) if (j > 0)
{ {
@ -564,14 +623,15 @@ static void SOCK_Get(void)
} }
if (i == numbans) if (i == numbans)
SOCK_bannednode[j] = false; SOCK_bannednode[j] = false;
return; return true;
} }
else else
DEBFILE("New node detected: No more free slots\n"); DEBFILE("New node detected: No more free slots\n");
}
}
}
}
doomcom->remotenode = -1; // no packet doomcom->remotenode = -1; // no packet
return false;
} }
#endif #endif
@ -1256,7 +1316,6 @@ static SINT8 SOCK_NetMakeNodewPort(const char *address, const char *port)
gaie = I_getaddrinfo(address, port, &hints, &ai); gaie = I_getaddrinfo(address, port, &hints, &ai);
if (gaie == 0) if (gaie == 0)
{ {
cleanupnodes();
newnode = getfreenode(); newnode = getfreenode();
} }
if (newnode == -1) if (newnode == -1)

View file

@ -1684,7 +1684,7 @@ void P_CheckTimeLimit(void)
return; return;
//Tagmode round end but only on the tic before the //Tagmode round end but only on the tic before the
//XD_EXITLEVEL packet is recieved by all players. //XD_EXITLEVEL packet is received by all players.
if (G_TagGametype()) if (G_TagGametype())
{ {
if (leveltime == (timelimitintics + 1)) if (leveltime == (timelimitintics + 1))
@ -1695,7 +1695,7 @@ void P_CheckTimeLimit(void)
|| (players[i].pflags & PF_TAGGED) || (players[i].pflags & PF_TAGIT)) || (players[i].pflags & PF_TAGGED) || (players[i].pflags & PF_TAGIT))
continue; continue;
CONS_Printf(M_GetText("%s recieved double points for surviving the round.\n"), player_names[i]); CONS_Printf(M_GetText("%s received double points for surviving the round.\n"), player_names[i]);
P_AddPlayerScore(&players[i], players[i].score); P_AddPlayerScore(&players[i], players[i].score);
} }
} }