diff --git a/src/d_main.c b/src/d_main.c index a86a4f97..d1a820c8 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -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; diff --git a/src/g_game.c b/src/g_game.c index df1c8235..d3f8d798 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -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 diff --git a/src/g_game.h b/src/g_game.h index 42623dfa..19060809 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -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); diff --git a/src/m_menu.c b/src/m_menu.c index a9c91724..0213847d 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -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); } diff --git a/src/p_setup.c b/src/p_setup.c index f73aef99..8c1c9ae7 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -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); diff --git a/src/p_tick.c b/src/p_tick.c index 177f3b65..9b4f7a21 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -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(); } diff --git a/src/s_sound.c b/src/s_sound.c index 9653d8ff..1585753f 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -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; diff --git a/src/v_video.c b/src/v_video.c index 29acc12b..dfad4c3a 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -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)< 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)<