Add demo rewinding

Preview is fairly basic, and only shows players
rewinding. Full state is restored via reloading
the replay and jumping to the timestamp once
the rewind is done.
This commit is contained in:
fickleheart 2019-04-06 14:51:00 -05:00
parent d9bcb43d0b
commit 6267986c72
8 changed files with 266 additions and 41 deletions

View file

@ -568,6 +568,9 @@ static void D_Display(void)
V_DrawScaledPatch(viewwindowx + (BASEVIDWIDTH - SHORT(patch->width))/2, py, 0, patch);
}
if (demo.rewinding)
V_DrawFadeScreen(TC_RAINBOW, (leveltime & 0x20) ? SKINCOLOR_PASTEL : SKINCOLOR_MOONSLAM);
// vid size change is now finished if it was on...
vid.recalc = 0;

View file

@ -5875,6 +5875,168 @@ void G_GhostTicker(void)
}
}
// Demo rewinding functions
typedef struct rewindinfo_s {
tic_t leveltime;
struct {
boolean ingame;
player_t player;
mobj_t mobj;
} playerinfo[MAXPLAYERS];
struct rewindinfo_s *prev;
} rewindinfo_t;
static tic_t currentrewindnum;
static rewindinfo_t *rewindhead = NULL; // Reverse chronological order
void G_InitDemoRewind(void)
{
while (rewindhead)
{
rewindinfo_t *p = rewindhead->prev;
Z_Free(rewindhead);
rewindhead = p;
}
currentrewindnum = 0;
}
void G_StoreRewindInfo(void)
{
static UINT8 timetolog = 8;
rewindinfo_t *info;
size_t i;
if (timetolog-- > 0)
return;
timetolog = 8;
info = Z_Calloc(sizeof(rewindinfo_t), PU_STATIC, NULL);
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator)
{
info->playerinfo[i].ingame = false;
continue;
}
info->playerinfo[i].ingame = true;
memcpy(&info->playerinfo[i].player, &players[i], sizeof(player_t));
if (players[i].mo)
memcpy(&info->playerinfo[i].mobj, players[i].mo, sizeof(mobj_t));
}
info->leveltime = leveltime;
info->prev = rewindhead;
rewindhead = info;
}
void G_PreviewRewind(tic_t previewtime)
{
size_t i, j;
fixed_t tweenvalue = 0;
rewindinfo_t *info = rewindhead, *next_info = rewindhead;
while (info->leveltime > previewtime && info->prev)
{
next_info = info;
info = info->prev;
}
if (info != next_info)
tweenvalue = FixedDiv(previewtime - info->leveltime, next_info->leveltime - info->leveltime);
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator)
{
if (info->playerinfo[i].player.mo)
{
//@TODO spawn temp object to act as a player display
}
continue;
}
if (!info->playerinfo[i].ingame || !info->playerinfo[i].player.mo)
{
if (players[i].mo)
players[i].mo->flags2 |= MF2_DONTDRAW;
continue;
}
if (!players[i].mo)
continue; //@TODO spawn temp object to act as a player display
players[i].mo->flags2 &= ~MF2_DONTDRAW;
P_UnsetThingPosition(players[i].mo);
#define TWEEN(pr) info->playerinfo[i].mobj.pr + FixedMul((INT32) (next_info->playerinfo[i].mobj.pr - info->playerinfo[i].mobj.pr), tweenvalue)
players[i].mo->x = TWEEN(x);
players[i].mo->y = TWEEN(y);
players[i].mo->z = TWEEN(z);
players[i].mo->angle = TWEEN(angle);
#undef TWEEN
P_SetThingPosition(players[i].mo);
players[i].frameangle = info->playerinfo[i].player.frameangle + FixedMul((INT32) (next_info->playerinfo[i].player.frameangle - info->playerinfo[i].player.frameangle), tweenvalue);
players[i].mo->sprite = info->playerinfo[i].mobj.sprite;
players[i].mo->frame = info->playerinfo[i].mobj.frame;
players[i].realtime = info->playerinfo[i].player.realtime;
for (j = 0; j < NUMKARTSTUFF; j++)
players[i].kartstuff[j] = info->playerinfo[i].player.kartstuff[j];
}
for (i = splitscreen+1; i > 0; i--)
P_ResetCamera(&players[(*G_GetDisplayplayerPtr(i))], P_GetCameraPtr(i));
}
void G_ConfirmRewind(tic_t rewindtime)
{
tic_t i;
boolean oldmenuactive = menuactive, oldsounddisabled = sound_disabled, olddigitaldisabled = digital_disabled;
INT32 olddp1 = displayplayer, olddp2 = secondarydisplayplayer, olddp3 = thirddisplayplayer, olddp4 = fourthdisplayplayer;
UINT8 oldss = splitscreen;
cv_renderview.value = 0;
menuactive = false; // Prevent loops
sound_disabled = /*digital_disabled =*/ true; // Prevent sound spam
demo.rewinding = true; // may not need later
G_DoPlayDemo(NULL); // Restart the current demo
for (i = 0; i < rewindtime && leveltime < rewindtime; i++)
{
//TryRunTics(1);
G_Ticker((i % NEWTICRATERATIO) == 0);
}
demo.rewinding = false;
menuactive = oldmenuactive; // Bring the menu back up
sound_disabled = oldsounddisabled; // Re-enable SFX
digital_disabled = olddigitaldisabled;
wipegamestate = gamestate; // No fading back in!
cv_renderview.value = 1;
splitscreen = oldss;
displayplayer = olddp1;
secondarydisplayplayer = olddp2;
thirddisplayplayer = olddp3;
fourthdisplayplayer = olddp4;
R_ExecuteSetViewSize();
G_ResetViews();
for (i = splitscreen+1; i > 0; i--)
P_ResetCamera(&players[(*G_GetDisplayplayerPtr(i))], P_GetCameraPtr(i));
}
void G_ReadMetalTic(mobj_t *metal)
{
UINT8 ziptic;
@ -6842,46 +7004,57 @@ void G_DoPlayDemo(char *defdemoname)
boolean spectator;
UINT8 slots[MAXPLAYERS], kartspeed[MAXPLAYERS], kartweight[MAXPLAYERS], numslots = 0;
G_InitDemoRewind();
skin[16] = '\0';
color[16] = '\0';
n = defdemoname+strlen(defdemoname);
while (*n != '/' && *n != '\\' && n != defdemoname)
n--;
if (n != defdemoname)
n++;
pdemoname = ZZ_Alloc(strlen(n)+1);
strcpy(pdemoname,n);
// Internal if no extension, external if one exists
if (FIL_CheckExtension(defdemoname))
// No demo name means we're restarting the current demo
if (defdemoname == NULL)
{
//FIL_DefaultExtension(defdemoname, ".lmp");
if (!FIL_ReadFile(defdemoname, &demobuffer))
demo_p = demobuffer;
pdemoname = ZZ_Alloc(1); // Easier than adding checks for this everywhere it's freed
}
else
{
n = defdemoname+strlen(defdemoname);
while (*n != '/' && *n != '\\' && n != defdemoname)
n--;
if (n != defdemoname)
n++;
pdemoname = ZZ_Alloc(strlen(n)+1);
strcpy(pdemoname,n);
// Internal if no extension, external if one exists
if (FIL_CheckExtension(defdemoname))
{
snprintf(msg, 1024, M_GetText("Failed to read file '%s'.\n"), defdemoname);
//FIL_DefaultExtension(defdemoname, ".lmp");
if (!FIL_ReadFile(defdemoname, &demobuffer))
{
snprintf(msg, 1024, M_GetText("Failed to read file '%s'.\n"), defdemoname);
CONS_Alert(CONS_ERROR, "%s", msg);
gameaction = ga_nothing;
M_StartMessage(msg, NULL, MM_NOTHING);
return;
}
demo_p = demobuffer;
}
// load demo resource from WAD
else if ((l = W_CheckNumForName(defdemoname)) == LUMPERROR)
{
snprintf(msg, 1024, M_GetText("Failed to read lump '%s'.\n"), defdemoname);
CONS_Alert(CONS_ERROR, "%s", msg);
gameaction = ga_nothing;
M_StartMessage(msg, NULL, MM_NOTHING);
return;
}
demo_p = demobuffer;
}
// load demo resource from WAD
else if ((l = W_CheckNumForName(defdemoname)) == LUMPERROR)
{
snprintf(msg, 1024, M_GetText("Failed to read lump '%s'.\n"), defdemoname);
CONS_Alert(CONS_ERROR, "%s", msg);
gameaction = ga_nothing;
M_StartMessage(msg, NULL, MM_NOTHING);
return;
}
else // it's an internal demo
{
demobuffer = demo_p = W_CacheLumpNum(l, PU_STATIC);
else // it's an internal demo
{
demobuffer = demo_p = W_CacheLumpNum(l, PU_STATIC);
#if defined(SKIPERRORS) && !defined(DEVELOP)
skiperrors = true; // SRB2Kart: Don't print warnings for staff ghosts, since they'll inevitably happen when we make bugfixes/changes...
skiperrors = true; // SRB2Kart: Don't print warnings for staff ghosts, since they'll inevitably happen when we make bugfixes/changes...
#endif
}
}
// read demo header

View file

@ -43,6 +43,7 @@ struct demovars_s {
char titlename[65];
boolean recording, playback, timing;
boolean title; // Title Screen demo can be cancelled by any key
boolean rewinding; // Rewind in progress
boolean loadfiles, ignorefiles; // Demo file loading options
boolean fromtitle; // SRB2Kart: Don't stop the music
@ -234,6 +235,12 @@ void G_WriteGhostTic(mobj_t *ghost, INT32 playernum);
void G_ConsAllGhostTics(void);
void G_ConsGhostTic(INT32 playernum);
void G_GhostTicker(void);
void G_InitDemoRewind(void);
void G_StoreRewindInfo(void);
void G_PreviewRewind(tic_t previewtime);
void G_ConfirmRewind(tic_t rewindtime);
void G_ReadMetalTic(mobj_t *metal);
void G_WriteMetalTic(mobj_t *metal);
void G_SaveMetal(UINT8 **buffer);

View file

@ -346,6 +346,7 @@ static void M_EnterReplayOptions(INT32 choice);
static void M_HutStartReplay(INT32 choice);
static void M_DrawPlaybackMenu(void);
static void M_PlaybackRewind(INT32 choice);
static void M_PlaybackPause(INT32 choice);
static void M_PlaybackFastForward(INT32 choice);
static void M_PlaybackAdvance(INT32 choice);
@ -569,11 +570,11 @@ static menuitem_t PlaybackMenu[] =
{
{IT_CALL | IT_STRING, "M_PHIDE", "Hide Menu", M_SelectableClearMenus, 0},
{IT_CALL | IT_STRING, "M_PREW", "Rewind", M_SelectableClearMenus, 24},
{IT_CALL | IT_STRING, "M_PPAUSE", "Pause", M_PlaybackPause, 40},
{IT_CALL | IT_STRING, "M_PRESUM", "Resume", M_PlaybackPause, 40},
{IT_CALL | IT_STRING, "M_PFFWD", "Fast-Foward", M_PlaybackFastForward, 56},
{IT_CALL | IT_STRING, "M_PFADV", "Advance Frame", M_PlaybackAdvance, 56},
{IT_CALL | IT_STRING, "M_PREW", "Rewind", M_PlaybackRewind, 24},
{IT_CALL | IT_STRING, "M_PPAUSE", "Pause", M_PlaybackPause, 40},
{IT_CALL | IT_STRING, "M_PRESUM", "Resume", M_PlaybackPause, 40},
{IT_CALL | IT_STRING, "M_PFFWD", "Fast-Foward", M_PlaybackFastForward, 56},
{IT_CALL | IT_STRING, "M_PFADV", "Advance Frame", M_PlaybackAdvance, 56},
{IT_ARROWS | IT_STRING, "M_PVIEWS", "View Count", M_PlaybackSetViews, 80},
{IT_ARROWS | IT_STRING, "M_PNVIEW", "Viewpoint", M_PlaybackAdjustView, 96},
@ -5713,7 +5714,7 @@ static void M_DrawPlaybackMenu(void)
UINT8 *activemap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GOLD, GTC_MENUCACHE);
// Toggle items
if (paused)
if (paused && !demo.rewinding)
{
PlaybackMenu[playback_pause].status = PlaybackMenu[playback_fastforward].status = IT_DISABLED;
PlaybackMenu[playback_resume].status = PlaybackMenu[playback_advanceframe].status = IT_CALL|IT_STRING;
@ -5768,13 +5769,32 @@ static void M_DrawPlaybackMenu(void)
}
}
static void M_PlaybackRewind(INT32 choice)
{
(void)choice;
if (!demo.rewinding)
demo.rewinding = paused = true;
else
G_ConfirmRewind(leveltime);
// temp
//G_ConfirmRewind(starttime + 90*TICRATE);
}
static void M_PlaybackPause(INT32 choice)
{
(void)choice;
paused = !paused;
if (paused)
if (demo.rewinding)
{
G_ConfirmRewind(leveltime);
paused = true;
S_PauseAudio();
}
else if (paused)
{
itemOn = playback_resume;
S_PauseAudio();
@ -5792,6 +5812,12 @@ static void M_PlaybackFastForward(INT32 choice)
{
(void)choice;
if (demo.rewinding)
{
G_ConfirmRewind(leveltime);
paused = false;
S_ResumeAudio();
}
CV_SetValue(&cv_playbackspeed, cv_playbackspeed.value == 1 ? 4 : 1);
}

View file

@ -2829,7 +2829,7 @@ boolean P_SetupLevel(boolean skipprecip)
// Encore mode fade to pink to white
// This is handled BEFORE sounds are stopped.
if (rendermode != render_none && encoremode && !prevencoremode)
if (rendermode != render_none && encoremode && !prevencoremode && !demo.rewinding)
{
tic_t locstarttime, endtime, nowtime;
@ -2881,7 +2881,7 @@ boolean P_SetupLevel(boolean skipprecip)
// Let's fade to white here
// But only if we didn't do the encore startup wipe
if (rendermode != render_none && !ranspecialwipe)
if (rendermode != render_none && !ranspecialwipe && !demo.rewinding)
{
F_WipeStartScreen();
V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, levelfadecol);

View file

@ -591,7 +591,15 @@ void P_Ticker(boolean run)
// Check for pause or menu up in single player
if (paused || P_AutoPause())
{
if (demo.rewinding && leveltime > 0)
{
leveltime = (leveltime-1) & ~3;
G_PreviewRewind(leveltime);
}
return;
}
postimgtype = postimgtype2 = postimgtype3 = postimgtype4 = postimg_none;
@ -768,6 +776,9 @@ void P_Ticker(boolean run)
P_MapEnd();
if (demo.playback)
G_StoreRewindInfo();
// Z_CheckMemCleanup();
}

View file

@ -1733,7 +1733,7 @@ void S_ShowMusicCredit(void)
{
musicdef_t *def = musicdefstart;
if (!cv_songcredits.value)
if (!cv_songcredits.value || demo.rewinding)
return;
if (!def) // No definitions
@ -1920,6 +1920,7 @@ void S_ChangeMusic(const char *mmusic, UINT16 mflags, boolean looping)
#endif
if (S_MusicDisabled()
|| demo.rewinding // Don't mess with music while rewinding!
|| demo.title) // SRB2Kart: Demos don't interrupt title screen music
return;
@ -1955,6 +1956,7 @@ void S_ChangeMusic(const char *mmusic, UINT16 mflags, boolean looping)
void S_StopMusic(void)
{
if (!I_SongPlaying()
|| demo.rewinding // Don't mess with music while rewinding!
|| demo.title) // SRB2Kart: Demos don't interrupt title screen music
return;

View file

@ -1234,9 +1234,12 @@ void V_DrawFadeScreen(UINT16 color, UINT8 strength)
#endif
{
const UINT8 *fadetable = ((color & 0xFF00) // Color is not palette index?
? ((UINT8 *)colormaps + strength*256) // Do COLORMAP fade.
: ((UINT8 *)transtables + ((9-strength)<<FF_TRANSSHIFT) + color*256)); // Else, do TRANSMAP** fade.
const UINT8 *fadetable =
(color > 0xFFF0) // Grab a specific colormap palette?
? R_GetTranslationColormap(color | 0xFFFF0000, strength, GTC_CACHE)
: ((color & 0xFF00) // Color is not palette index?
? ((UINT8 *)colormaps + strength*256) // Do COLORMAP fade.
: ((UINT8 *)transtables + ((9-strength)<<FF_TRANSSHIFT) + color*256)); // Else, do TRANSMAP** fade.
const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
UINT8 *buf = screens[0];