mirror of
https://git.do.srb2.org/KartKrew/Kart-Public.git
synced 2024-12-26 20:31:30 +00:00
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:
parent
d9bcb43d0b
commit
6267986c72
8 changed files with 266 additions and 41 deletions
|
@ -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;
|
||||
|
||||
|
|
229
src/g_game.c
229
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
|
||||
|
|
|
@ -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);
|
||||
|
|
40
src/m_menu.c
40
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
11
src/p_tick.c
11
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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
||||
|
|
Loading…
Reference in a new issue