Add "resendgamestate" command

This commit is contained in:
Louis-Antoine 2020-01-31 15:57:04 +01:00
parent 7c22d0957e
commit 5eae1ca914
9 changed files with 153 additions and 29 deletions

View file

@ -121,6 +121,7 @@ static ticcmd_t localcmds2;
static boolean cl_packetmissed; static boolean cl_packetmissed;
// here it is for the secondary local player (splitscreen) // here it is for the secondary local player (splitscreen)
static UINT8 mynode; // my address pointofview server static UINT8 mynode; // my address pointofview server
static boolean cl_redownloadinggamestate = false;
static UINT8 localtextcmd[MAXTEXTCMD]; static UINT8 localtextcmd[MAXTEXTCMD];
static UINT8 localtextcmd2[MAXTEXTCMD]; // splitscreen static UINT8 localtextcmd2[MAXTEXTCMD]; // splitscreen
@ -1429,7 +1430,7 @@ static boolean SV_SendServerConfig(INT32 node)
#ifdef JOININGAME #ifdef JOININGAME
#define SAVEGAMESIZE (768*1024) #define SAVEGAMESIZE (768*1024)
static void SV_SendSaveGame(INT32 node) static void SV_SendSaveGame(INT32 node, boolean resending)
{ {
size_t length, compressedlen; size_t length, compressedlen;
UINT8 *savebuffer; UINT8 *savebuffer;
@ -1447,7 +1448,7 @@ static void SV_SendSaveGame(INT32 node)
// Leave room for the uncompressed length. // Leave room for the uncompressed length.
save_p = savebuffer + sizeof(UINT32); save_p = savebuffer + sizeof(UINT32);
P_SaveNetGame(); P_SaveNetGame(resending);
length = save_p - savebuffer; length = save_p - savebuffer;
if (length > SAVEGAMESIZE) if (length > SAVEGAMESIZE)
@ -1520,7 +1521,7 @@ static void SV_SavedGame(void)
return; return;
} }
P_SaveNetGame(); P_SaveNetGame(false);
length = save_p - savebuffer; length = save_p - savebuffer;
if (length > SAVEGAMESIZE) if (length > SAVEGAMESIZE)
@ -1543,7 +1544,7 @@ static void SV_SavedGame(void)
#define TMPSAVENAME "$$$.sav" #define TMPSAVENAME "$$$.sav"
static void CL_LoadReceivedSavegame(void) static void CL_LoadReceivedSavegame(boolean reloading)
{ {
UINT8 *savebuffer = NULL; UINT8 *savebuffer = NULL;
size_t length, decompressedlen; size_t length, decompressedlen;
@ -1579,7 +1580,7 @@ static void CL_LoadReceivedSavegame(void)
automapactive = false; automapactive = false;
// load a base level // load a base level
if (P_LoadNetGame()) if (P_LoadNetGame(reloading))
{ {
const INT32 actnum = mapheaderinfo[gamemap-1]->actnum; const INT32 actnum = mapheaderinfo[gamemap-1]->actnum;
CONS_Printf(M_GetText("Map is now \"%s"), G_BuildMapName(gamemap)); CONS_Printf(M_GetText("Map is now \"%s"), G_BuildMapName(gamemap));
@ -1611,6 +1612,32 @@ static void CL_LoadReceivedSavegame(void)
consistancy[gametic%BACKUPTICS] = Consistancy(); consistancy[gametic%BACKUPTICS] = Consistancy();
CON_ToggleOff(); CON_ToggleOff();
} }
static void CL_ReloadReceivedSavegame(void)
{
INT32 i;
for (i = 0; i < MAXPLAYERS; i++)
{
#ifdef HAVE_BLUA
LUA_InvalidatePlayer(&players[i]);
#endif
sprintf(player_names[i], "Player %d", i + 1);
}
CL_LoadReceivedSavegame(true);
if (neededtic < gametic)
neededtic = gametic;
maketic = neededtic;
camera.subsector = R_PointInSubsector(camera.x, camera.y);
camera2.subsector = R_PointInSubsector(camera2.x, camera2.y);
cl_redownloadinggamestate = false;
CONS_Printf(M_GetText("Game state reloaded\n"));
}
#endif #endif
#ifndef NONET #ifndef NONET
@ -1950,7 +1977,7 @@ static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, tic
if (fileneeded[0].status == FS_FOUND) if (fileneeded[0].status == FS_FOUND)
{ {
// Gamestate is now handled within CL_LoadReceivedSavegame() // Gamestate is now handled within CL_LoadReceivedSavegame()
CL_LoadReceivedSavegame(); CL_LoadReceivedSavegame(false);
cl_mode = CL_CONNECTED; cl_mode = CL_CONNECTED;
} // don't break case continue to CL_CONNECTED } // don't break case continue to CL_CONNECTED
else else
@ -2974,6 +3001,32 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
CL_RemovePlayer(pnum, kickreason); CL_RemovePlayer(pnum, kickreason);
} }
static void Command_ResendGamestate(void)
{
if (COM_Argc() == 1)
{
CONS_Printf(M_GetText("resendgamestate <playername/playernum>: resend the game state to a player\n"));
return;
}
else if (client)
{
CONS_Printf(M_GetText("Only the server can use this.\n"));
return;
}
const SINT8 playernum = nametonum(COM_Argv(1));
if (playernum == -1 || playernum == 0)
return;
// Send a PT_WILLRESENDGAMESTATE packet to the client so they know what's going on
netbuffer->packettype = PT_WILLRESENDGAMESTATE;
if (!HSendPacket(playernode[playernum], true, 0, 0))
{
CONS_Alert(CONS_ERROR, M_GetText("A problem occured, please try again.\n"));
return;
}
}
consvar_t cv_allownewplayer = {"allowjoin", "On", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL }; consvar_t cv_allownewplayer = {"allowjoin", "On", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL };
consvar_t cv_joinnextround = {"joinnextround", "Off", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; /// \todo not done consvar_t cv_joinnextround = {"joinnextround", "Off", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; /// \todo not done
static CV_PossibleValue_t maxplayers_cons_t[] = {{2, "MIN"}, {32, "MAX"}, {0, NULL}}; static CV_PossibleValue_t maxplayers_cons_t[] = {{2, "MIN"}, {32, "MAX"}, {0, NULL}};
@ -3012,6 +3065,7 @@ void D_ClientServerInit(void)
COM_AddCommand("reloadbans", Command_ReloadBan); COM_AddCommand("reloadbans", Command_ReloadBan);
COM_AddCommand("connect", Command_connect); COM_AddCommand("connect", Command_connect);
COM_AddCommand("nodes", Command_Nodes); COM_AddCommand("nodes", Command_Nodes);
COM_AddCommand("resendgamestate", Command_ResendGamestate);
#ifdef PACKETDROP #ifdef PACKETDROP
COM_AddCommand("drop", Command_Drop); COM_AddCommand("drop", Command_Drop);
COM_AddCommand("droprate", Command_Droprate); COM_AddCommand("droprate", Command_Droprate);
@ -3082,6 +3136,7 @@ void SV_ResetServer(void)
mynode = 0; mynode = 0;
cl_packetmissed = false; cl_packetmissed = false;
cl_redownloadinggamestate = false;
if (dedicated) if (dedicated)
{ {
@ -3598,7 +3653,7 @@ static void HandleConnect(SINT8 node)
{ {
if ((gamestate == GS_LEVEL || gamestate == GS_INTERMISSION) && newnode) if ((gamestate == GS_LEVEL || gamestate == GS_INTERMISSION) && newnode)
{ {
SV_SendSaveGame(node); // send a complete game state SV_SendSaveGame(node, false); // send a complete game state
DEBFILE("send savegame\n"); DEBFILE("send savegame\n");
} }
SV_AddWaitingPlayers(names[0], names[1]); SV_AddWaitingPlayers(names[0], names[1]);
@ -3665,6 +3720,42 @@ static void HandleServerInfo(SINT8 node)
} }
#endif #endif
static void PT_WillResendGamestate(void)
{
char tmpsave[256];
if (server || cl_redownloadinggamestate)
return;
// Send back a PT_CANRESENDGAMESTATE packet to the server
// so they know they can start sending the game state
netbuffer->packettype = PT_CANRESENDGAMESTATE;
if (!HSendPacket(servernode, true, 0, 0))
return;
CONS_Printf(M_GetText("Reloading game state...\n"));
sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home);
// Don't get a corrupt savegame error because tmpsave already exists
if (FIL_FileExists(tmpsave) && unlink(tmpsave) == -1)
I_Error("Can't delete %s\n", tmpsave);
CL_PrepareDownloadSaveGame(tmpsave);
cl_redownloadinggamestate = true;
}
static void PT_CanResendGamestate(SINT8 node)
{
if (client || sendingsavegame[node])
return;
CONS_Printf(M_GetText("Resending game state to %s...\n"), player_names[nodetoplayer[node]]);
SV_SendSaveGame(node, true); // Resend a complete game state
}
/** Handles a packet received from a node that isn't in game /** Handles a packet received from a node that isn't in game
* *
* \param node The packet sender * \param node The packet sender
@ -4107,6 +4198,9 @@ static void HandlePacketFromPlayer(SINT8 node)
Net_CloseConnection(node); Net_CloseConnection(node);
nodeingame[node] = false; nodeingame[node] = false;
break; break;
case PT_CANRESENDGAMESTATE:
PT_CanResendGamestate(node);
break;
// -------------------------------------------- CLIENT RECEIVE ---------- // -------------------------------------------- CLIENT RECEIVE ----------
case PT_RESYNCHEND: case PT_RESYNCHEND:
// Only accept PT_RESYNCHEND from the server. // Only accept PT_RESYNCHEND from the server.
@ -4136,6 +4230,9 @@ static void HandlePacketFromPlayer(SINT8 node)
break; break;
} }
if (cl_redownloadinggamestate)
break;
realstart = ExpandTics(netbuffer->u.serverpak.starttic); realstart = ExpandTics(netbuffer->u.serverpak.starttic);
realend = realstart + netbuffer->u.serverpak.numtics; realend = realstart + netbuffer->u.serverpak.numtics;
@ -4234,6 +4331,9 @@ static void HandlePacketFromPlayer(SINT8 node)
if (client) if (client)
Got_Filetxpak(); Got_Filetxpak();
break; break;
case PT_WILLRESENDGAMESTATE:
PT_WillResendGamestate();
break;
default: default:
DEBFILE(va("UNKNOWN PACKET TYPE RECEIVED %d from host %d\n", DEBFILE(va("UNKNOWN PACKET TYPE RECEIVED %d from host %d\n",
netbuffer->packettype, node)); netbuffer->packettype, node));
@ -4876,7 +4976,11 @@ void NetUpdate(void)
if (client) if (client)
{ {
if (!resynch_local_inprogress) // If the client just finished redownloading the game state, load it
if (cl_redownloadinggamestate && fileneeded[0].status == FS_FOUND)
CL_ReloadReceivedSavegame();
if (!(resynch_local_inprogress || cl_redownloadinggamestate))
CL_SendClientCmd(); // Send tic cmd CL_SendClientCmd(); // Send tic cmd
hu_resynching = resynch_local_inprogress; hu_resynching = resynch_local_inprogress;
} }

View file

@ -67,6 +67,9 @@ typedef enum
PT_RESYNCHEND, // Player is now resynched and is being requested to remake the gametic PT_RESYNCHEND, // Player is now resynched and is being requested to remake the gametic
PT_RESYNCHGET, // Player got resynch packet PT_RESYNCHGET, // Player got resynch packet
PT_WILLRESENDGAMESTATE, // Hey Client, I am about to resend you the gamestate!
PT_CANRESENDGAMESTATE, // Okay Server, I'm ready to receive it, you can go ahead.
// Add non-PT_CANFAIL packet types here to avoid breaking MS compatibility. // Add non-PT_CANFAIL packet types here to avoid breaking MS compatibility.
PT_CANFAIL, // This is kind of a priority. Anything bigger than CANFAIL PT_CANFAIL, // This is kind of a priority. Anything bigger than CANFAIL

View file

@ -1514,7 +1514,7 @@ void D_SRB2Main(void)
{ {
levelstarttic = gametic; levelstarttic = gametic;
G_SetGamestate(GS_LEVEL); G_SetGamestate(GS_LEVEL);
if (!P_LoadLevel(false)) if (!P_LoadLevel(false, false))
I_Quit(); // fail so reset game stuff I_Quit(); // fail so reset game stuff
} }
} }

View file

@ -799,6 +799,9 @@ static const char *packettypename[NUMPACKETTYPE] =
"RESYNCHEND", "RESYNCHEND",
"RESYNCHGET", "RESYNCHGET",
"WILLRESENDGAMESTATE",
"CANRESENDGAMESTATE",
"FILEFRAGMENT", "FILEFRAGMENT",
"TEXTCMD", "TEXTCMD",
"TEXTCMD2", "TEXTCMD2",

View file

@ -1839,7 +1839,7 @@ void G_DoLoadLevel(boolean resetplayer)
} }
// Setup the level. // Setup the level.
if (!P_LoadLevel(false)) // this never returns false? if (!P_LoadLevel(false, false)) // this never returns false?
{ {
// fail so reset game stuff // fail so reset game stuff
Command_ExitGame_f(); Command_ExitGame_f();

View file

@ -3985,12 +3985,14 @@ static inline void P_UnArchiveSPGame(INT16 mapoverride)
playeringame[consoleplayer] = true; playeringame[consoleplayer] = true;
} }
static void P_NetArchiveMisc(void) static void P_NetArchiveMisc(boolean resending)
{ {
INT32 i; INT32 i;
WRITEUINT32(save_p, ARCHIVEBLOCK_MISC); WRITEUINT32(save_p, ARCHIVEBLOCK_MISC);
if (resending)
WRITEUINT32(save_p, gametic);
WRITEINT16(save_p, gamemap); WRITEINT16(save_p, gamemap);
WRITEINT16(save_p, gamestate); WRITEINT16(save_p, gamestate);
WRITEINT16(save_p, gametype); WRITEINT16(save_p, gametype);
@ -4056,13 +4058,16 @@ static void P_NetArchiveMisc(void)
WRITEUINT8(save_p, 0x2e); WRITEUINT8(save_p, 0x2e);
} }
static inline boolean P_NetUnArchiveMisc(void) static inline boolean P_NetUnArchiveMisc(boolean reloading)
{ {
INT32 i; INT32 i;
if (READUINT32(save_p) != ARCHIVEBLOCK_MISC) if (READUINT32(save_p) != ARCHIVEBLOCK_MISC)
I_Error("Bad $$$.sav at archive block Misc"); I_Error("Bad $$$.sav at archive block Misc");
if (reloading)
gametic = READUINT32(save_p);
gamemap = READINT16(save_p); gamemap = READINT16(save_p);
// gamemap changed; we assume that its map header is always valid, // gamemap changed; we assume that its map header is always valid,
@ -4091,7 +4096,7 @@ static inline boolean P_NetUnArchiveMisc(void)
tokenlist = READUINT32(save_p); tokenlist = READUINT32(save_p);
if (!P_LoadLevel(true)) if (!P_LoadLevel(true, reloading))
return false; return false;
// get the time // get the time
@ -4192,14 +4197,14 @@ void P_SaveGame(void)
P_ArchiveLuabanksAndConsistency(); P_ArchiveLuabanksAndConsistency();
} }
void P_SaveNetGame(void) void P_SaveNetGame(boolean resending)
{ {
thinker_t *th; thinker_t *th;
mobj_t *mobj; mobj_t *mobj;
INT32 i = 1; // don't start from 0, it'd be confused with a blank pointer otherwise INT32 i = 1; // don't start from 0, it'd be confused with a blank pointer otherwise
CV_SaveNetVars(&save_p); CV_SaveNetVars(&save_p);
P_NetArchiveMisc(); P_NetArchiveMisc(resending);
// Assign the mobjnumber for pointer tracking // Assign the mobjnumber for pointer tracking
for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
@ -4250,10 +4255,10 @@ boolean P_LoadGame(INT16 mapoverride)
return true; return true;
} }
boolean P_LoadNetGame(void) boolean P_LoadNetGame(boolean reloading)
{ {
CV_LoadNetVars(&save_p); CV_LoadNetVars(&save_p);
if (!P_NetUnArchiveMisc()) if (!P_NetUnArchiveMisc(reloading))
return false; return false;
P_NetUnArchivePlayers(); P_NetUnArchivePlayers();
if (gamestate == GS_LEVEL) if (gamestate == GS_LEVEL)

View file

@ -22,9 +22,9 @@
// These are the load / save game routines. // These are the load / save game routines.
void P_SaveGame(void); void P_SaveGame(void);
void P_SaveNetGame(void); void P_SaveNetGame(boolean resending);
boolean P_LoadGame(INT16 mapoverride); boolean P_LoadGame(INT16 mapoverride);
boolean P_LoadNetGame(void); boolean P_LoadNetGame(boolean reloading);
mobj_t *P_FindNewPosition(UINT32 oldposition); mobj_t *P_FindNewPosition(UINT32 oldposition);

View file

@ -2777,8 +2777,6 @@ static void P_InitLevelSettings(void)
leveltime = 0; leveltime = 0;
localaiming = 0;
localaiming2 = 0;
modulothing = 0; modulothing = 0;
// special stage tokens, emeralds, and ring total // special stage tokens, emeralds, and ring total
@ -2893,6 +2891,9 @@ void P_RespawnThings(void)
P_InitLevelSettings(); P_InitLevelSettings();
localaiming = 0;
localaiming2 = 0;
P_SpawnMapThings(true); P_SpawnMapThings(true);
// restore skybox viewpoint/centerpoint if necessary, set them to defaults if we can't do that // restore skybox viewpoint/centerpoint if necessary, set them to defaults if we can't do that
@ -3387,7 +3388,7 @@ static void P_InitGametype(void)
* \param fromnetsave If true, skip some stuff because we're loading a netgame snapshot. * \param fromnetsave If true, skip some stuff because we're loading a netgame snapshot.
* \todo Clean up, refactor, split up; get rid of the bloat. * \todo Clean up, refactor, split up; get rid of the bloat.
*/ */
boolean P_LoadLevel(boolean fromnetsave) boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
{ {
// use gamemap to get map number. // use gamemap to get map number.
// 99% of the things already did, so. // 99% of the things already did, so.
@ -3457,6 +3458,9 @@ boolean P_LoadLevel(boolean fromnetsave)
players[consoleplayer].viewz = 1; players[consoleplayer].viewz = 1;
// Cancel all d_main.c fadeouts (keep fade in though). // Cancel all d_main.c fadeouts (keep fade in though).
if (reloadinggamestate)
wipegamestate = gamestate; // Don't fade if reloading the gamestate
else
wipegamestate = FORCEWIPEOFF; wipegamestate = FORCEWIPEOFF;
wipestyleflags = 0; wipestyleflags = 0;
@ -3491,7 +3495,7 @@ boolean P_LoadLevel(boolean fromnetsave)
// Let's fade to black here // Let's fade to black here
// But only if we didn't do the special stage wipe // But only if we didn't do the special stage wipe
if (rendermode != render_none && !ranspecialwipe) if (rendermode != render_none && !(ranspecialwipe || reloadinggamestate))
P_RunLevelWipe(); P_RunLevelWipe();
if (!titlemapinaction) if (!titlemapinaction)
@ -3622,7 +3626,12 @@ boolean P_LoadLevel(boolean fromnetsave)
if (!fromnetsave) if (!fromnetsave)
P_InitGametype(); P_InitGametype();
if (!reloadinggamestate)
{
P_InitCamera(); P_InitCamera();
localaiming = 0;
localaiming2 = 0;
}
// clear special respawning que // clear special respawning que
iquehead = iquetail = 0; iquehead = iquetail = 0;
@ -3633,7 +3642,7 @@ boolean P_LoadLevel(boolean fromnetsave)
P_MapEnd(); P_MapEnd();
// Remove the loading shit from the screen // Remove the loading shit from the screen
if (rendermode != render_none && !titlemapinaction) if (rendermode != render_none && !(titlemapinaction || reloadinggamestate))
F_WipeColorFill(levelfadecol); F_WipeColorFill(levelfadecol);
if (precache || dedicated) if (precache || dedicated)
@ -3671,8 +3680,8 @@ boolean P_LoadLevel(boolean fromnetsave)
#endif #endif
} }
// No render mode, stop here. // No render mode or reloading gamestate, stop here.
if (rendermode == render_none) if (rendermode == render_none || reloadinggamestate)
return true; return true;
// Title card! // Title card!

View file

@ -97,7 +97,7 @@ void P_SetupLevelSky(INT32 skynum, boolean global);
void P_ScanThings(INT16 mapnum, INT16 wadnum, INT16 lumpnum); void P_ScanThings(INT16 mapnum, INT16 wadnum, INT16 lumpnum);
#endif #endif
void P_RespawnThings(void); void P_RespawnThings(void);
boolean P_LoadLevel(boolean fromnetsave); boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate);
#ifdef HWRENDER #ifdef HWRENDER
void HWR_SetupLevel(void); void HWR_SetupLevel(void);
#endif #endif