From 7b39fc4b7efaa257c3f5729547f914277c8c4ce1 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 19 Aug 2025 18:48:27 -0400 Subject: [PATCH] "Staffsync" command to test staff ghost playback --- src/d_main.cpp | 19 ++++++-- src/d_netcmd.c | 119 ++++++++++++++++++++++++++++++++++++++++++++++++ src/d_netcmd.h | 11 +++++ src/doomstat.h | 11 +++++ src/f_wipe.cpp | 3 ++ src/g_demo.cpp | 39 ++++++++++++++++ src/g_game.c | 14 +++++- src/k_hud.cpp | 14 ++++++ src/typedef.h | 1 + src/y_inter.cpp | 6 +++ 10 files changed, 233 insertions(+), 4 deletions(-) diff --git a/src/d_main.cpp b/src/d_main.cpp index ebfbdae69..0d076a6cc 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -1114,15 +1114,28 @@ void D_SRB2Loop(void) // // Wipes run an inner loop and artificially increase // the measured time. - if (!ranwipe && frameskip < 3 && deltatics > 1.0) + if (staffsync) { - frameskip++; + // SPECIAL: When checking staff demos for sync, + // draw as little as possible for speeeeeeed + if (frameskip < TICRATE*10) + frameskip++; + else + frameskip = 0; } else { - frameskip = 0; + if (!ranwipe && frameskip < 3 && deltatics > 1.0) + { + frameskip++; + } + else + { + frameskip = 0; + } } + if (world) { sincelastworld = 0.0; diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 7d080c03a..8be8e010f 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -193,6 +193,7 @@ static void Command_Eval(void); static void Command_WriteTextmap(void); static void Command_SnapshotMaps(void); +static void Command_Staffsync(void); #ifdef DEVELOP static void Command_FastForward(void); @@ -256,6 +257,8 @@ boolean forceresetplayers = false; boolean deferencoremode = false; boolean forcespecialstage = false; +boolean staffsync = false; + UINT8 splitscreen = 0; INT32 adminplayers[MAXPLAYERS]; @@ -451,6 +454,7 @@ void D_RegisterServerCommands(void) COM_AddCommand("snapshotmap", Command_SnapshotMaps); COM_AddCommand("snapshotmaps", Command_SnapshotMaps); // alias + COM_AddCommand("staffsync", Command_Staffsync); // for master server connection AddMServCommands(); @@ -6776,6 +6780,121 @@ static void Command_SnapshotMaps(void) CON_ToggleOff(); } +UINT32 staffsync_map = 0; +UINT32 staffsync_ghost = 0; +UINT32 staffsync_done = 0; +UINT32 staffsync_total = 0; +UINT32 staffsync_failed = 0; +staffsync_t staffsync_results[1024]; + +static void Command_Staffsync(void) +{ + if (Playing()) + { + CONS_Alert(CONS_ERROR, "This command cannot be used in-game. Return to the titlescreen first!\n"); + return; + } + + staffsync = true; + + if (demo.playback) + return; + + mapheader_t *mapheader; + staffbrief_t *staffbrief; + + demo.loadfiles = false; + demo.ignorefiles = true; + + mapheader = mapheaderinfo[staffsync_map]; + + // Start of the run, init progress and report vars + if (staffsync_map == 0 && staffsync_ghost == 0) + { + staffsync_done = 0; + staffsync_total = 0; + staffsync_failed = 0; + memset(staffsync_results, 0, sizeof(staffsync_results)); + for (INT32 i = 0; i < nummapheaders; i++) + { + if (mapheaderinfo[i] != NULL) + staffsync_total += mapheaderinfo[i]->ghostCount; + } + } + + // The current configuration corresponds to a valid staff ghost, play it + if (mapheader != NULL && mapheader->ghostCount > 0 && staffsync_ghost < mapheader->ghostCount) + { + demo.timing = true; + g_singletics = true; + framecount = 0; + demostarttime = I_GetTime(); + + staffbrief = mapheader->ghostBrief[staffsync_ghost]; + G_DoPlayDemoEx("", (staffbrief->wad << 16) | staffbrief->lump); + + staffsync_ghost++; + staffsync_done++; + } + + // We've exhausted map headers, report + if (mapheader == NULL) + { + + CONS_Printf("========================================\n"); + CONS_Printf("DESYNCING STAFF GHOSTS:\n"); + + UINT32 i = 0; + while (staffsync_results[i].reason != SYNC_NONE) + { + staffsync_t *result = &staffsync_results[i]; + + CONS_Printf("%s [%s] ", G_BuildMapName(result->map), result->name); + + switch (result->reason) + { + case SYNC_CHARACTER: + CONS_Printf("(character/stats)"); + break; + case SYNC_HEALTH: + CONS_Printf("(health)"); + break; + case SYNC_ITEM: + CONS_Printf("(item/bumpers)"); + break; + case SYNC_POSITION: + CONS_Printf("(position)"); + break; + case SYNC_RNG: + CONS_Printf("(RNG class %d)", result->extra); + break; + } + + CONS_Printf("\n"); + + i++; + } + + CONS_Printf("========================================\n"); + + staffsync_ghost = 0; + staffsync_map = 0; + staffsync = false; + return; + } + + // Out of ghosts for the current map, switch to the next map and re-exec + if (staffsync_ghost >= mapheader->ghostCount) + { + staffsync_ghost = 0; + staffsync_map++; + + Command_Staffsync(); + } + + return; +} + #ifdef DEVELOP static void Command_FastForward(void) { diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 7ac959ce2..c983bdeeb 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -255,6 +255,17 @@ void Automate_Clear(void); extern UINT32 livestudioaudience_timer; void LiveStudioAudience(void); +typedef enum +{ + SYNC_NONE, + SYNC_RNG, + SYNC_HEALTH, + SYNC_POSITION, + SYNC_ITEM, + SYNC_CHARACTER, + SYNC__MAX +} staffsync_reason_t; + void D_Cheat(INT32 playernum, INT32 cheat, ...); // used for the player setup menu diff --git a/src/doomstat.h b/src/doomstat.h index e585a5db3..322e59bad 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -238,6 +238,17 @@ extern UINT8 splitscreen; extern int r_splitscreen; extern boolean forceresetplayers, deferencoremode, forcespecialstage; +extern boolean staffsync; +extern UINT32 staffsync_map, staffsync_ghost, staffsync_done, staffsync_total, staffsync_failed; + +struct staffsync_t +{ + UINT32 map; + char name[MAXPLAYERNAME+1]; + UINT32 reason; + UINT32 extra; +}; +extern staffsync_t staffsync_results[1024]; // ======================================== // Internal parameters for sound rendering. diff --git a/src/f_wipe.cpp b/src/f_wipe.cpp index d583e13da..2b7d95d41 100644 --- a/src/f_wipe.cpp +++ b/src/f_wipe.cpp @@ -460,6 +460,9 @@ void F_RunWipe(UINT8 wipemode, UINT8 wipetype, boolean drawMenu, const char *col lumpnum_t clump = LUMPERROR; lighttable_t *fcolor = NULL; + if (staffsync) + return; + if (colormap != NULL) clump = W_GetNumForName(colormap); diff --git a/src/g_demo.cpp b/src/g_demo.cpp index ff3d28577..41ecd94d0 100644 --- a/src/g_demo.cpp +++ b/src/g_demo.cpp @@ -287,6 +287,21 @@ boolean G_ConsiderEndingDemoRead(void) return true; } +// Demo failed sync during a sync test! Log the failure to be reported later. +static void G_FailStaffSync(staffsync_reason_t reason, UINT32 extra) +{ + if (!staffsync) + return; + + if (staffsync_results[staffsync_failed].reason != 0) + return; + + staffsync_results[staffsync_failed].map = gamemap; + memcpy(&staffsync_results[staffsync_failed].name, player_names[consoleplayer], sizeof(player_names[consoleplayer])); + staffsync_results[staffsync_failed].reason = reason; + staffsync_results[staffsync_failed].extra = extra; +} + void G_ReadDemoExtraData(void) { INT32 p, extradata, i; @@ -449,7 +464,11 @@ void G_ReadDemoExtraData(void) P_SetRandSeed(static_cast(i), rng); if (demosynced) + { CONS_Alert(CONS_WARNING, "Demo playback has desynced (RNG class %d)!\n", i); + G_FailStaffSync(SYNC_RNG, i); + } + storesynced = false; } } @@ -1118,7 +1137,10 @@ void G_ConsGhostTic(INT32 playernum) if (th != &thlist[THINK_MOBJ] && mobj->health != health) // Wasn't damaged?! This is desync! Fix it! { if (demosynced) + { CONS_Alert(CONS_WARNING, M_GetText("Demo playback has desynced (health)!\n")); + G_FailStaffSync(SYNC_HEALTH, 0); + } demosynced = false; P_DamageMobj(mobj, players[0].mo, players[0].mo, 1, DMG_NORMAL); } @@ -1180,7 +1202,10 @@ void G_ConsGhostTic(INT32 playernum) if (ghostext[playernum].desyncframes >= 2) { if (demosynced) + { CONS_Alert(CONS_WARNING, "Demo playback has desynced (player %s)!\n", player_names[playernum]); + G_FailStaffSync(SYNC_POSITION, 0); + } demosynced = false; P_UnsetThingPosition(testmo); @@ -1203,7 +1228,11 @@ void G_ConsGhostTic(INT32 playernum) || testmo->health != ghostext[playernum].health) { if (demosynced) + { CONS_Alert(CONS_WARNING, M_GetText("Demo playback has desynced (item/bumpers)!\n")); + G_FailStaffSync(SYNC_HEALTH, 0); + } + demosynced = false; players[playernum].itemtype = ghostext[playernum].itemtype; @@ -1217,7 +1246,11 @@ void G_ConsGhostTic(INT32 playernum) demo.skinlist[ghostext[playernum].skinid].mapping != (UINT8)(((skin_t *)testmo->skin)->skinnum)) { if (demosynced) + { CONS_Alert(CONS_WARNING, M_GetText("Demo playback has desynced (Character/stats)!\n")); + G_FailStaffSync(SYNC_CHARACTER, 0); + } + demosynced = false; testmo->skin = skins[demo.skinlist[ghostext[playernum].skinid].mapping]; @@ -3904,6 +3937,12 @@ void G_StopDemo(void) demo.waitingfortally = false; g_singletics = false; + if (!demosynced && staffsync) + { + staffsync_failed++; + CONS_Printf("STAFF GHOST DESYNC on %s (%s)\n", G_BuildMapName(gamemap), player_names[consoleplayer]); + } + { UINT8 i; for (i = 0; i < MAXSPLITSCREENPLAYERS; ++i) diff --git a/src/g_game.c b/src/g_game.c index 8a261caf6..d4014de4e 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1882,7 +1882,14 @@ void G_Ticker(boolean run) P_MapStart(); - if (gamestate == GS_LEVEL && G_GetRetryFlag()) + extern boolean demosynced; + if (demo.playback && staffsync && !demosynced) + { + G_ClearRetryFlag(); + G_StopDemo(); + Command_ExitGame_f(); + } + else if (gamestate == GS_LEVEL && G_GetRetryFlag()) { if (demo.playback == true) { @@ -2042,6 +2049,8 @@ void G_Ticker(boolean run) break; case GS_MENU: + if (staffsync) + COM_BufInsertText("staffsync"); break; case GS_INTRO: @@ -2075,6 +2084,9 @@ void G_Ticker(boolean run) P_Ticker(run); F_TitleScreenTicker(run); + + if (staffsync) + COM_BufInsertText("staffsync"); break; case GS_CEREMONY: diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 281589de5..5c1bef202 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -7670,6 +7670,20 @@ void K_drawKartHUD(void) return; } + if (staffsync) + { + V_DrawFadeScreen(31, 8); + V_DrawCenteredGamemodeString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2 - 30, 0, 0, "Staff Ghost Sync Test"); + UINT16 barw = BASEVIDWIDTH/2; + UINT16 barh = 8; + UINT16 bary = 10; + V_DrawFill(BASEVIDWIDTH/2 - barw/2, BASEVIDHEIGHT/2 - barh/2 + bary, barw, barh, 199); + V_DrawFill(BASEVIDWIDTH/2 - barw/2 + 1, BASEVIDHEIGHT/2 - barh/2 + 1 + bary, barw * staffsync_done / staffsync_total - 2, barh - 2, 176); + V_DrawCenteredThinString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2 + 20, 0, va("Testing ghost %d of %d (%d failed). Choppy visuals are normal!\n", staffsync_done, staffsync_total, staffsync_failed)); + V_DrawCenteredThinString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2 + 30, 0, "See \"latest-log.txt\" for information on failing ghosts.\n"); + return; + } + // Draw full screen stuff that turns off the rest of the HUD if (R_GetViewNumber() == 0) { diff --git a/src/typedef.h b/src/typedef.h index 3dfa9b8c0..d2eea64c4 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -41,6 +41,7 @@ TYPEDEF (xcommand_t); TYPEDEF (changeteam_packet_t); TYPEDEF (changeteam_value_t); TYPEDEF (scheduleTask_t); +TYPEDEF (staffsync_t); // discord.h TYPEDEF (discordRequest_t); diff --git a/src/y_inter.cpp b/src/y_inter.cpp index a096b71f9..883876f8e 100644 --- a/src/y_inter.cpp +++ b/src/y_inter.cpp @@ -2656,6 +2656,12 @@ void Y_StartIntermission(void) return; } + if (staffsync) + { + Y_EndIntermission(); + return; + } + G_SetGamestate(GS_INTERMISSION); if (demo.playback)