Merge branch 'waiting-players-intermission' into 'next'

SRB2Kart netcode ports

See merge request STJr/SRB2!2019
This commit is contained in:
Sal 2023-07-24 18:03:45 +00:00
commit 3a52e6deb2
12 changed files with 294 additions and 59 deletions

View file

@ -1887,7 +1887,7 @@ void CON_Drawer(void)
CON_DrawConsole();
else if (gamestate == GS_LEVEL
|| gamestate == GS_INTERMISSION || gamestate == GS_ENDING || gamestate == GS_CUTSCENE
|| gamestate == GS_CREDITS || gamestate == GS_EVALUATION)
|| gamestate == GS_CREDITS || gamestate == GS_EVALUATION || gamestate == GS_WAITINGPLAYERS)
CON_DrawHudlines();
Unlock_state();

View file

@ -120,6 +120,8 @@ UINT8 hu_redownloadinggamestate = 0;
// true when a player is connecting or disconnecting so that the gameplay has stopped in its tracks
boolean hu_stopped = false;
consvar_t cv_dedicatedidletime = CVAR_INIT ("dedicatedidletime", "10", CV_SAVE, CV_Unsigned, NULL);
UINT8 adminpassmd5[16];
boolean adminpasswordset = false;
@ -2600,6 +2602,8 @@ static void CL_ConnectToServer(void)
}
while (!(cl_mode == CL_CONNECTED && (client || (server && nodewaited <= pnumnodes))));
if (netgame)
F_StartWaitingPlayers();
DEBFILE(va("Synchronisation Finished\n"));
displayplayer = consoleplayer;
@ -4518,6 +4522,7 @@ static void HandlePacketFromPlayer(SINT8 node)
netconsole = 0;
else
netconsole = nodetoplayer[node];
#ifdef PARANOIA
if (netconsole >= MAXPLAYERS)
I_Error("bad table nodetoplayer: node %d player %d", doomcom->remotenode, netconsole);
@ -4556,15 +4561,20 @@ static void HandlePacketFromPlayer(SINT8 node)
// Update the nettics
nettics[node] = realend;
// Don't do anything for packets of type NODEKEEPALIVE?
if (netconsole == -1 || netbuffer->packettype == PT_NODEKEEPALIVE
|| netbuffer->packettype == PT_NODEKEEPALIVEMIS)
// This should probably still timeout though, as the node should always have a player 1 number
if (netconsole == -1)
break;
// 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;
// Don't do anything for packets of type NODEKEEPALIVE?
// Sryder 2018/07/01: Update the freezetimeout still!
if (netbuffer->packettype == PT_NODEKEEPALIVE
|| netbuffer->packettype == PT_NODEKEEPALIVEMIS)
break;
// If we've alredy received a ticcmd for this tic, just submit it for the next one.
tic_t faketic = maketic;
if ((!!(netcmds[maketic % BACKUPTICS][netconsole].angleturn & TICCMD_RECEIVED))
@ -4628,6 +4638,21 @@ static void HandlePacketFromPlayer(SINT8 node)
}
}
break;
case PT_BASICKEEPALIVE:
if (client)
break;
// This should probably still timeout though, as the node should always have a player 1 number
if (netconsole == -1)
break;
// If a client sends this it should mean they are done receiving the savegame
sendingsavegame[node] = false;
// As long as clients send keep alives, the server can keep running, so reset the timeout
/// \todo Use a separate cvar for that kind of timeout?
freezetimeout[node] = I_GetTime() + connectiontimeout;
break;
case PT_TEXTCMD2: // splitscreen special
netconsole = nodetoplayer2[node];
/* FALLTHRU */
@ -5054,39 +5079,66 @@ static INT16 Consistancy(void)
return (INT16)(ret & 0xFFFF);
}
// confusing, but this DOESN'T send PT_NODEKEEPALIVE, it sends PT_BASICKEEPALIVE
// used during wipes to tell the server that a node is still connected
static void CL_SendClientKeepAlive(void)
{
netbuffer->packettype = PT_BASICKEEPALIVE;
HSendPacket(servernode, false, 0, 0);
}
static void SV_SendServerKeepAlive(void)
{
INT32 n;
for (n = 1; n < MAXNETNODES; n++)
{
if (nodeingame[n])
{
netbuffer->packettype = PT_BASICKEEPALIVE;
HSendPacket(n, false, 0, 0);
}
}
}
// send the client packet to the server
static void CL_SendClientCmd(void)
{
size_t packetsize = 0;
boolean mis = false;
netbuffer->packettype = PT_CLIENTCMD;
if (cl_packetmissed)
netbuffer->packettype++;
{
netbuffer->packettype = PT_CLIENTMIS;
mis = true;
}
netbuffer->u.clientpak.resendfrom = (UINT8)(neededtic & UINT8_MAX);
netbuffer->u.clientpak.client_tic = (UINT8)(gametic & UINT8_MAX);
if (gamestate == GS_WAITINGPLAYERS)
{
// Send PT_NODEKEEPALIVE packet
netbuffer->packettype += 4;
netbuffer->packettype = (mis ? PT_NODEKEEPALIVEMIS : PT_NODEKEEPALIVE);
packetsize = sizeof (clientcmd_pak) - sizeof (ticcmd_t) - sizeof (INT16);
HSendPacket(servernode, false, 0, packetsize);
}
else if (gamestate != GS_NULL && (addedtogame || dedicated))
{
packetsize = sizeof (clientcmd_pak);
G_MoveTiccmd(&netbuffer->u.clientpak.cmd, &localcmds, 1);
netbuffer->u.clientpak.consistancy = SHORT(consistancy[gametic%BACKUPTICS]);
// Send a special packet with 2 cmd for splitscreen
if (splitscreen || botingame)
{
netbuffer->packettype += 2;
G_MoveTiccmd(&netbuffer->u.client2pak.cmd2, &localcmds2, 1);
netbuffer->packettype = (mis ? PT_CLIENT2MIS : PT_CLIENT2CMD);
packetsize = sizeof (client2cmd_pak);
G_MoveTiccmd(&netbuffer->u.client2pak.cmd2, &localcmds2, 1);
}
else
packetsize = sizeof (clientcmd_pak);
HSendPacket(servernode, false, 0, packetsize);
}
@ -5097,7 +5149,7 @@ static void CL_SendClientCmd(void)
if (localtextcmd[0])
{
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 sent
if (HSendPacket(servernode, true, 0, localtextcmd[0]+1)) // Send can fail...
localtextcmd[0] = 0;
@ -5477,28 +5529,11 @@ static inline void PingUpdate(void)
pingmeasurecount = 1; //Reset count
}
void NetUpdate(void)
static tic_t gametime = 0;
static void UpdatePingTable(void)
{
static tic_t gametime = 0;
static tic_t resptime = 0;
tic_t nowtime;
INT32 i;
INT32 realtics;
nowtime = I_GetTime();
realtics = nowtime - gametime;
if (realtics <= 0) // nothing new to update
return;
if (realtics > 5)
{
if (server)
realtics = 1;
else
realtics = 5;
}
gametime = nowtime;
if (server)
{
@ -5510,6 +5545,150 @@ void NetUpdate(void)
realpingtable[i] += G_TicsToMilliseconds(GetLag(playernode[i]));
pingmeasurecount++;
}
}
// Handle timeouts to prevent definitive freezes from happenning
static void HandleNodeTimeouts(void)
{
INT32 i;
if (server)
{
for (i = 1; i < MAXNETNODES; i++)
if (nodeingame[i] && freezetimeout[i] < I_GetTime())
Net_ConnectionTimeout(i);
// In case the cvar value was lowered
if (joindelay)
joindelay = min(joindelay - 1, 3 * (tic_t)cv_joindelay.value * TICRATE);
}
}
// Keep the network alive while not advancing tics!
void NetKeepAlive(void)
{
tic_t nowtime;
INT32 realtics;
nowtime = I_GetTime();
realtics = nowtime - gametime;
// return if there's no time passed since the last call
if (realtics <= 0) // nothing new to update
return;
UpdatePingTable();
GetPackets();
#ifdef MASTERSERVER
MasterClient_Ticker();
#endif
if (client)
{
// send keep alive
CL_SendClientKeepAlive();
// No need to check for resynch because we aren't running any tics
}
else
{
SV_SendServerKeepAlive();
}
// No else because no tics are being run and we can't resynch during this
Net_AckTicker();
HandleNodeTimeouts();
FileSendTicker();
}
void NetUpdate(void)
{
static tic_t resptime = 0;
tic_t nowtime;
INT32 i;
INT32 realtics;
nowtime = I_GetTime();
realtics = nowtime - gametime;
if (realtics <= 0) // nothing new to update
return;
if (realtics > 5)
{
if (server)
realtics = 1;
else
realtics = 5;
}
if (server && dedicated && gamestate == GS_LEVEL)
{
const tic_t dedicatedidletime = cv_dedicatedidletime.value * TICRATE;
static tic_t dedicatedidletimeprev = 0;
static tic_t dedicatedidle = 0;
if (dedicatedidletime > 0)
{
for (i = 1; i < MAXNETNODES; ++i)
if (nodeingame[i])
{
if (dedicatedidle >= dedicatedidletime)
{
CONS_Printf("DEDICATED: Awakening from idle (Node %d detected...)\n", i);
dedicatedidle = 0;
}
break;
}
if (i == MAXNETNODES)
{
if (leveltime == 2)
{
// On next tick...
dedicatedidle = dedicatedidletime-1;
}
else if (dedicatedidle >= dedicatedidletime)
{
if (D_GetExistingTextcmd(gametic, 0) || D_GetExistingTextcmd(gametic+1, 0))
{
CONS_Printf("DEDICATED: Awakening from idle (Netxcmd detected...)\n");
dedicatedidle = 0;
}
else
{
realtics = 0;
}
}
else if ((dedicatedidle += realtics) >= dedicatedidletime)
{
const char *idlereason = "at round start";
if (leveltime > 3)
idlereason = va("for %d seconds", dedicatedidle/TICRATE);
CONS_Printf("DEDICATED: No nodes %s, idling...\n", idlereason);
realtics = 0;
dedicatedidle = dedicatedidletime;
}
}
}
else
{
if (dedicatedidletimeprev > 0 && dedicatedidle >= dedicatedidletimeprev)
{
CONS_Printf("DEDICATED: Awakening from idle (Idle disabled...)\n");
}
dedicatedidle = 0;
}
dedicatedidletimeprev = dedicatedidletime;
}
gametime = nowtime;
UpdatePingTable();
if (client)
maketic = neededtic;
@ -5541,25 +5720,26 @@ void NetUpdate(void)
}
else
{
if (!demoplayback)
if (!demoplayback && realtics > 0)
{
INT32 counts;
hu_redownloadinggamestate = false;
firstticstosend = gametic;
for (i = 0; i < MAXNETNODES; i++)
if (nodeingame[i] && nettics[i] < firstticstosend)
{
firstticstosend = nettics[i];
if (maketic + 1 >= nettics[i] + BACKUPTICS)
Net_ConnectionTimeout(i);
}
// Don't erase tics not acknowledged
counts = realtics;
firstticstosend = gametic;
for (i = 0; i < MAXNETNODES; i++)
{
if (!nodeingame[i])
continue;
if (nettics[i] < firstticstosend)
firstticstosend = nettics[i];
if (maketic + counts >= nettics[i] + (BACKUPTICS - TICRATE))
Net_ConnectionTimeout(i);
}
if (maketic + counts >= firstticstosend + BACKUPTICS)
counts = firstticstosend+BACKUPTICS-maketic-1;
@ -5576,20 +5756,10 @@ void NetUpdate(void)
}
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);
// In case the cvar value was lowered
if (joindelay)
joindelay = min(joindelay - 1, 3 * (tic_t)cv_joindelay.value * TICRATE);
}
HandleNodeTimeouts();
nowtime /= NEWTICRATERATIO;
if (nowtime > resptime)
{
resptime = nowtime;

View file

@ -77,6 +77,8 @@ typedef enum
PT_ASKLUAFILE, // Client telling the server they don't have the file
PT_HASLUAFILE, // Client telling the server they have the file
PT_BASICKEEPALIVE,// Keep the network alive during wipes, as tics aren't advanced and NetUpdate isn't called
// Add non-PT_CANFAIL packet types here to avoid breaking MS compatibility.
PT_CANFAIL, // This is kind of a priority. Anything bigger than CANFAIL
@ -398,6 +400,7 @@ extern tic_t servermaxping;
extern consvar_t cv_netticbuffer, cv_allownewplayer, cv_joinnextround, cv_maxplayers, cv_joindelay, cv_rejointimeout;
extern consvar_t cv_resynchattempts, cv_blamecfail;
extern consvar_t cv_maxsend, cv_noticedownload, cv_downloadspeed;
extern consvar_t cv_dedicatedidletime;
// Used in d_net, the only dependence
tic_t ExpandTics(INT32 low, INT32 node);
@ -412,6 +415,9 @@ void SendKick(UINT8 playernum, UINT8 msg);
// Create any new ticcmds and broadcast to other players.
void NetUpdate(void);
// Maintain connections to nodes without timing them all out.
void NetKeepAlive(void);
void SV_StartSinglePlayerServer(void);
boolean SV_SpawnServer(void);
void SV_StopServer(void);

View file

@ -458,6 +458,13 @@ static void D_Display(void)
case GS_WAITINGPLAYERS:
// The clientconnect drawer is independent...
if (netgame)
{
// I don't think HOM from nothing drawing is independent...
F_WaitingPlayersDrawer();
HU_Erase();
HU_Drawer();
}
case GS_DEDICATEDSERVER:
case GS_NULL:
break;

View file

@ -869,6 +869,9 @@ static void DebugPrintpacket(const char *header)
(UINT32)ExpandTics(netbuffer->u.clientpak.client_tic, doomcom->remotenode),
(UINT32)ExpandTics (netbuffer->u.clientpak.resendfrom, doomcom->remotenode));
break;
case PT_BASICKEEPALIVE:
fprintf(debugfile, " wipetime\n");
break;
case PT_TEXTCMD:
case PT_TEXTCMD2:
fprintf(debugfile, " length %d\n ", netbuffer->u.textcmd[0]);

View file

@ -599,6 +599,7 @@ void D_RegisterServerCommands(void)
CV_RegisterVar(&cv_joinnextround);
CV_RegisterVar(&cv_showjoinaddress);
CV_RegisterVar(&cv_blamecfail);
CV_RegisterVar(&cv_dedicatedidletime);
#endif
COM_AddCommand("ping", Command_Ping_f, COM_LUA);
@ -2176,7 +2177,7 @@ static void Command_Pause(void)
if (cv_pause.value || server || (IsPlayerAdmin(consoleplayer)))
{
if (modeattacking || !(gamestate == GS_LEVEL || gamestate == GS_INTERMISSION) || (marathonmode && gamestate == GS_INTERMISSION))
if (modeattacking || !(gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_WAITINGPLAYERS) || (marathonmode && gamestate == GS_INTERMISSION))
{
CONS_Printf(M_GetText("You can't pause here.\n"));
return;

View file

@ -4669,3 +4669,36 @@ void F_TextPromptTicker(void)
animtimer--;
}
}
// ================
// WAITINGPLAYERS
// ================
void F_StartWaitingPlayers(void)
{
wipegamestate = GS_TITLESCREEN; // technically wiping from title screen
finalecount = 0;
}
void F_WaitingPlayersTicker(void)
{
if (paused)
return;
finalecount++;
// dumb hack, only start the music on the 1st tick so if you instantly go into the map you aren't hearing a tic of music
if (finalecount == 2)
S_ChangeMusicInternal("_CHSEL", true);
}
void F_WaitingPlayersDrawer(void)
{
const char *waittext1 = "You will join";
const char *waittext2 = "next level...";
V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
V_DrawCreditString((160 - (V_CreditStringWidth(waittext1)>>1))<<FRACBITS, 48<<FRACBITS, 0, waittext1);
V_DrawCreditString((160 - (V_CreditStringWidth(waittext2)>>1))<<FRACBITS, 64<<FRACBITS, 0, waittext2);
}

View file

@ -74,6 +74,10 @@ void F_StartContinue(void);
void F_ContinueTicker(void);
void F_ContinueDrawer(void);
void F_StartWaitingPlayers(void);
void F_WaitingPlayersTicker(void);
void F_WaitingPlayersDrawer(void);
extern INT32 finalecount;
extern INT32 titlescrollxspeed;
extern INT32 titlescrollyspeed;

View file

@ -614,6 +614,8 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu)
if (moviemode)
M_SaveFrame();
NetKeepAlive(); // Update the network so we don't cause timeouts
}
WipeInAction = false;

View file

@ -1915,6 +1915,7 @@ void G_PreLevelTitleCard(void)
ST_runTitleCard();
ST_preLevelTitleCardDrawer();
I_FinishUpdate(); // page flip or blit buffer
NetKeepAlive(); // Prevent timeouts
if (moviemode)
M_SaveFrame();
@ -2437,14 +2438,17 @@ void G_Ticker(boolean run)
case GS_TITLESCREEN:
if (titlemapinaction)
P_Ticker(run);
// then intentionally fall through
/* FALLTHRU */
case GS_WAITINGPLAYERS:
if (run)
F_MenuPresTicker();
F_TitleScreenTicker(run);
break;
case GS_WAITINGPLAYERS:
if (netgame)
F_WaitingPlayersTicker();
HU_Ticker();
break;
case GS_DEDICATEDSERVER:
case GS_NULL:
break; // do nothing

View file

@ -4279,7 +4279,11 @@ static void P_NetArchiveMisc(boolean resending)
if (resending)
WRITEUINT32(save_p, gametic);
WRITEINT16(save_p, gamemap);
WRITEINT16(save_p, gamestate);
if (gamestate != GS_LEVEL)
WRITEINT16(save_p, GS_WAITINGPLAYERS); // nice hack to put people back into waitingplayers
else
WRITEINT16(save_p, gamestate);
WRITEINT16(save_p, gametype);
{

View file

@ -7490,6 +7490,7 @@ static void P_RunSpecialStageWipe(void)
lastwipetic = nowtime;
if (moviemode) // make sure we save frames for the white hold too
M_SaveFrame();
NetKeepAlive(); // Prevent timeout
}
}