diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 1beef9bd4..e28eb8447 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -2200,7 +2200,7 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic else { I_Sleep(cv_sleep.value); - I_UpdateTime(cv_timescale.value); + I_UpdateTime(); } return true; diff --git a/src/d_main.cpp b/src/d_main.cpp index 473cd1fa3..f6e56845b 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -150,6 +150,7 @@ boolean g_singletics = false; // timedemo boolean lastdraw = false; tic_t g_fast_forward = 0; +tic_t g_fast_forward_clock_stop = INFTICS; postimg_t postimgtype[MAXSPLITSCREENPLAYERS]; INT32 postimgparam[MAXSPLITSCREENPLAYERS]; @@ -649,6 +650,13 @@ static bool D_Display(void) V_DrawCustomFadeScreen("FADEMAP0", val); } } + else if (demo.attract == DEMO_ATTRACT_TITLE) + { + if (INT32 fade = F_AttractDemoExitFade()) + { + V_DrawCustomFadeScreen("FADEMAP0", fade); + } + } VID_DisplaySoftwareScreen(); } @@ -829,7 +837,7 @@ void D_SRB2Loop(void) // Pushing of + parameters is now done back in D_SRB2Main, not here. - I_UpdateTime(cv_timescale.value); + I_UpdateTime(); oldentertics = I_GetTime(); // end of loading screen: CONS_Printf() will no more call FinishUpdate() @@ -877,7 +885,7 @@ void D_SRB2Loop(void) bool ranwipe = false; - I_UpdateTime(cv_timescale.value); + I_UpdateTime(); if (lastwipetic) { @@ -1131,12 +1139,19 @@ static boolean g_deferredtitle = false; // void D_StartTitle(void) { + bool fromAttract = (demo.attract == DEMO_ATTRACT_TITLE); + demo.attract = DEMO_ATTRACT_OFF; + Music_StopAll(); D_ClearState(); F_StartTitleScreen(); M_ClearMenus(false); g_deferredtitle = false; + + if (fromAttract) + S_ShowMusicCredit(); // Show music credit when returning to the title screen + } void D_SetDeferredStartTitle(boolean deferred) diff --git a/src/d_net.c b/src/d_net.c index 22fd1ce5c..b68385f93 100644 --- a/src/d_net.c +++ b/src/d_net.c @@ -600,7 +600,7 @@ void Net_WaitAllAckReceived(UINT32 timeout) while (tictac == I_GetTime()) { I_Sleep(cv_sleep.value); - I_UpdateTime(cv_timescale.value); + I_UpdateTime(); } tictac = I_GetTime(); HGetPacket(); diff --git a/src/d_netcmd.c b/src/d_netcmd.c index f5d5fd5b8..48c1c30c8 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -71,6 +71,7 @@ #include "k_roulette.h" #include "k_bans.h" #include "k_director.h" +#include "k_credits.h" #ifdef SRB2_CONFIG_ENABLE_WEBM_MOVIES #include "m_avrecorder.h" @@ -6374,14 +6375,20 @@ void Command_ExitGame_f(void) if (!modeattacking) { - if (restoreMenu == NULL) + // YES, this is where demo.attract gets cleared! + if (demo.attract == DEMO_ATTRACT_CREDITS) { - D_StartTitle(); + F_ContinueCredits(); // <-- clears demo.attract + } + else if (restoreMenu == NULL) // this is true for attract demos too! + { + D_StartTitle(); // <-- clears demo.attract } else { D_ClearState(); M_StartControlPanel(); + demo.attract = DEMO_ATTRACT_OFF; // shouldn't ever happen, but let's keep the code symmetrical } } } diff --git a/src/doomstat.h b/src/doomstat.h index 255600a47..c09e7d96f 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -927,6 +927,7 @@ extern INT16 wipetypepost; // debug flag to cancel adaptiveness extern boolean g_singletics; extern tic_t g_fast_forward; +extern tic_t g_fast_forward_clock_stop; #define singletics (g_singletics == true || g_fast_forward > 0) diff --git a/src/f_finale.c b/src/f_finale.c index 0b593e2e7..e3e7bbe08 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -65,6 +65,10 @@ static tic_t stoptimer; static boolean keypressed = false; +static tic_t attractcountdown; // Countdown until attract demo ends +static boolean attractcredit; // Show music credit once attract demo begins +boolean g_attractnowipe; // Do not wipe on return to title screen + static INT32 menuanimtimer; // Title screen: background animation timing altview_t titlemapcam = {0}; @@ -1747,7 +1751,7 @@ void F_TitleScreenTicker(boolean run) UINT16 mapnum; UINT8 numstaff; static boolean use_netreplay = false; - staffbrief_t *brief; + staffbrief_t *brief = NULL; if ((use_netreplay = !use_netreplay)) { @@ -1785,6 +1789,40 @@ loadreplay: demo.attract = DEMO_ATTRACT_TITLE; demo.ignorefiles = true; demo.loadfiles = false; + + attractcountdown = INFTICS; + + if (brief) + { + // "Random" table of times to skip forward in the demo. + // I didn't want to use real random functions because I didn't like the distribution. + tic_t table[] = { + 0, + 15*TICRATE, + brief->lap / 2, // references to brief->lap will skip to the end of Prison replays + 0, + 40*TICRATE, + brief->lap, + 0, + 0, + brief->time, + }; + UINT8 numintable = sizeof table / sizeof *table; + + static UINT8 index = UINT8_MAX; + if (index == UINT8_MAX) + index = M_RandomKey(numintable); + else + index = (index + 1) % numintable; + + attractcountdown = min(30*TICRATE, brief->time); + g_fast_forward = min(table[index], brief->time - attractcountdown); + // Slow computers, don't wait all day + g_fast_forward_clock_stop = I_GetTime() + 2*TICRATE; + // Show title screen music credit at beginning of demo + attractcredit = true; + } + G_DoPlayDemoEx(dname, dlump); } } @@ -1792,6 +1830,30 @@ loadreplay: void F_AttractDemoTicker(void) { keypressed = false; + + if (attractcountdown > 0 && !g_fast_forward) + { + if (attractcredit) + { + S_ShowMusicCredit(); + attractcredit = false; + } + + if (attractcountdown > 0 && !--attractcountdown) + { + // Fade will be handled without a wipe (see F_AttractDemoExitFade) + g_attractnowipe = true; + G_CheckDemoStatus(); + } + } +} + +INT32 F_AttractDemoExitFade(void) +{ + if (attractcountdown > 15) + return 0; + + return 31 - (attractcountdown * 2); } // ================ diff --git a/src/f_finale.h b/src/f_finale.h index 5b72790b7..6caf0bd4b 100644 --- a/src/f_finale.h +++ b/src/f_finale.h @@ -64,6 +64,8 @@ void F_EndTextPrompt(boolean forceexec, boolean noexec); boolean F_GetPromptHideHudAll(void); boolean F_GetPromptHideHud(fixed_t y); +INT32 F_AttractDemoExitFade(void); + void F_StartGameEnd(void); void F_StartIntro(void); void F_StartTitleScreen(void); @@ -128,6 +130,8 @@ extern boolean WipeStageTitle; extern INT32 lastwipetic; +extern boolean g_attractnowipe; + // Don't know where else to place this constant // But this file seems appropriate #define PRELEVELTIME TICRATE // frames in tics diff --git a/src/f_wipe.cpp b/src/f_wipe.cpp index 6c848cda1..1d88396e0 100644 --- a/src/f_wipe.cpp +++ b/src/f_wipe.cpp @@ -507,7 +507,7 @@ void F_RunWipe(UINT8 wipemode, UINT8 wipetype, boolean drawMenu, const char *col while (!((nowtime = I_GetTime()) - lastwipetic)) { I_Sleep(cv_sleep.value); - I_UpdateTime(cv_timescale.value); + I_UpdateTime(); } lastwipetic = nowtime; diff --git a/src/g_game.c b/src/g_game.c index 10edfc6af..e92d790c9 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1285,7 +1285,7 @@ void G_PreLevelTitleCard(void) while (!((nowtime = I_GetTime()) - lasttime)) { I_Sleep(cv_sleep.value); - I_UpdateTime(cv_timescale.value); + I_UpdateTime(); } lasttime = nowtime; } @@ -1348,7 +1348,6 @@ boolean G_Responder(event_t *ev) { // stop the title demo G_CheckDemoStatus(); - demo.attract = DEMO_ATTRACT_OFF; return true; } } @@ -2031,7 +2030,19 @@ void G_Ticker(boolean run) if (g_fast_forward > 0) { + if (I_GetTime() > g_fast_forward_clock_stop) + { + // If too much real time has passed, end the fast-forward early. + g_fast_forward = 1; + } + g_fast_forward--; + + if (g_fast_forward == 0) + { + // Next fast-forward is unlimited. + g_fast_forward_clock_stop = INFTICS; + } } } } diff --git a/src/i_time.c b/src/i_time.c index ff6454a8d..6df6d5581 100644 --- a/src/i_time.c +++ b/src/i_time.c @@ -18,6 +18,8 @@ #include "command.h" #include "doomtype.h" #include "d_netcmd.h" +#include "f_finale.h" +#include "g_demo.h" #include "m_fixed.h" #include "i_system.h" @@ -57,14 +59,25 @@ void I_InitializeTime(void) I_StartupTimer(); } -void I_UpdateTime(fixed_t timescale) +fixed_t I_GetTimeScale(void) +{ + if (demo.playback && demo.attract == DEMO_ATTRACT_TITLE && F_AttractDemoExitFade()) + { + // Slow down at the end of attract demos + return FRACUNIT/2; + } + + return cv_timescale.value; +} + +void I_UpdateTime(void) { double ticratescaled; double elapsedseconds; tic_t realtics; // get real tics - ticratescaled = (double)TICRATE * FIXED_TO_FLOAT(timescale); + ticratescaled = (double)TICRATE * FIXED_TO_FLOAT(I_GetTimeScale()); enterprecise = I_GetPreciseTime(); elapsedseconds = (double)(enterprecise - oldenterprecise) / I_GetPrecisePrecision(); diff --git a/src/i_time.h b/src/i_time.h index 8d90c3d09..318b1dda1 100644 --- a/src/i_time.h +++ b/src/i_time.h @@ -38,7 +38,8 @@ tic_t I_GetTime(void); */ void I_InitializeTime(void); -void I_UpdateTime(fixed_t timescale); +void I_UpdateTime(void); +fixed_t I_GetTimeScale(void); /** \brief Block for at minimum the duration specified. This function makes a best effort not to oversleep, and will spinloop if sleeping would diff --git a/src/k_credits.cpp b/src/k_credits.cpp index 83219e60d..039dbf21d 100644 --- a/src/k_credits.cpp +++ b/src/k_credits.cpp @@ -430,6 +430,7 @@ void F_ContinueCredits(void) { G_SetGamestate(GS_CREDITS); F_CreditsReset(); + demo.attract = DEMO_ATTRACT_OFF; // Returning from playing a demo. // Go to the next slide. @@ -524,6 +525,8 @@ static boolean F_CreditsPlayDemo(void) G_DoPlayDemoEx("", (brief->wad << 16) | brief->lump); g_fast_forward = 30 * TICRATE; + // Slow computers, don't wait all day + g_fast_forward_clock_stop = I_GetTime() + 2 * TICRATE; g_credits.demo_exit = 0; return true; } diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 0be59c7be..9cf7463bb 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -52,6 +52,7 @@ #include "k_hitlag.h" #include "g_input.h" #include "k_dialogue.h" +#include "f_finale.h" //{ Patch Definitions static patch_t *kp_nodraw; @@ -6097,6 +6098,33 @@ void K_drawKartHUD(void) { INT32 x = BASEVIDWIDTH - 8, y = BASEVIDHEIGHT-8, snapflags = V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SLIDEIN; patch_t *pat = static_cast(W_CachePatchName((M_UseAlternateTitleScreen() ? "MTSJUMPR1" : "MTSBUMPR1"), PU_CACHE)); + const UINT8 *colormap = nullptr; + + if (INT32 fade = F_AttractDemoExitFade()) + { + // TODO: Twodee cannot handle + // V_DrawCustomFadeScreen. + // However, since the screen fade just + // uses a colormap, the same colormap can + // be applied on a per-patch basis. + // I'm only bothering to apply this + // colormap to the attract mode sticker, + // since it's the lone HUD element. + if (lighttable_t *clm = V_LoadCustomFadeMap("FADEMAP0")) + { + // This must be statically allocated for Twodee + static UINT8 *colormap_storage; + const UINT8 *fadetable = V_OffsetIntoFadeMap(clm, fade); + + if (!colormap_storage) + Z_MallocAlign(256, PU_STATIC, &colormap_storage, 8); + + memcpy(colormap_storage, fadetable, 256); + colormap = colormap_storage; + + Z_Free(clm); + } + } if (r_splitscreen == 3) { @@ -6105,7 +6133,7 @@ void K_drawKartHUD(void) snapflags = 0; } - V_DrawScaledPatch(x-(SHORT(pat->width)), y-(SHORT(pat->height)), snapflags, pat); + V_DrawMappedPatch(x-(SHORT(pat->width)), y-(SHORT(pat->height)), snapflags, pat, colormap); } } else diff --git a/src/k_tally.cpp b/src/k_tally.cpp index 1c065732d..3cade54c0 100644 --- a/src/k_tally.cpp +++ b/src/k_tally.cpp @@ -1417,7 +1417,9 @@ void K_TickPlayerTally(player_t *player) && (!netgame && !demo.playback) && player->tally.state != TALLY_ST_DONE; - if (fastForwardInput && allowFastForward) + if ((fastForwardInput && allowFastForward) || + // Skip tally in atract demos + (demo.playback && demo.attract)) { do player->tally.Tick(); diff --git a/src/menus/main-1.c b/src/menus/main-1.c index 12f547000..9639e5ad5 100644 --- a/src/menus/main-1.c +++ b/src/menus/main-1.c @@ -134,7 +134,7 @@ void M_QuitResponse(INT32 ch) V_DrawSmallScaledPatch(0, 0, 0, W_CachePatchName("GAMEQUIT", PU_CACHE)); // Demo 3 Quit Screen Tails 06-16-2001 I_FinishUpdate(); // Update the screen with the image Tails 06-19-2001 I_Sleep(cv_sleep.value); - I_UpdateTime(cv_timescale.value); + I_UpdateTime(); } } I_Quit(); diff --git a/src/menus/transient/pause-replay.c b/src/menus/transient/pause-replay.c index 04d3ba545..6b2b87815 100644 --- a/src/menus/transient/pause-replay.c +++ b/src/menus/transient/pause-replay.c @@ -94,8 +94,12 @@ void M_EndModeAttackRun(void) // - Now we need to clear the rest of the gamestate ourself! } - // Playback: modeattacking is always false, so calling this returns to the menu. - // Recording: modeattacking is still true and this function call preserves that. + // Playback: + // - modeattacking is always false, so calling this returns to the menu. + // - Because modeattacking is false, also clears demo.attract. + // + // Recording: + // - modeattacking is still true and this function call preserves that. Command_ExitGame_f(); if (!modeattacking) @@ -123,19 +127,8 @@ void M_EndModeAttackRun(void) modeattacking = ATTACKING_NONE; // Return to the menu. - if (demo.attract == DEMO_ATTRACT_TITLE) - { - D_SetDeferredStartTitle(true); - } - else if (demo.attract == DEMO_ATTRACT_CREDITS) - { - F_ContinueCredits(); - } - else - { - D_ClearState(); - M_StartControlPanel(); - } + D_ClearState(); + M_StartControlPanel(); } // Replay Playback Menu diff --git a/src/p_setup.cpp b/src/p_setup.cpp index 812815984..1cc4c9cd3 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -8342,8 +8342,10 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) P_InitLevelSettings(); - if (demo.attract != DEMO_ATTRACT_TITLE) + if (demo.attract != DEMO_ATTRACT_TITLE && gamestate != GS_TITLESCREEN) { + // Stop titlescreen music from overriding level music. + // Except on the title screen, where an attract demo or title map may be used. Music_Stop("title"); } @@ -8391,7 +8393,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) while (!((nowtime = I_GetTime()) - lastwipetic)) \ { \ I_Sleep(cv_sleep.value); \ - I_UpdateTime(cv_timescale.value); \ + I_UpdateTime(); \ } \ lastwipetic = nowtime; \ if (moviemode && rendermode == render_opengl) \ @@ -8530,15 +8532,24 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) wipetype = wipe_encore_towhite; } - if (rendermode != render_none) + if (g_attractnowipe) { - F_WipeStartScreen(); - - V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, levelfadecol); - F_WipeEndScreen(); + // Attract demos do a custom fade on exit, so + // don't run a wipe here. + g_attractnowipe = false; } + else + { + if (rendermode != render_none) + { + F_WipeStartScreen(); - F_RunWipe(wipetype, wipedefs[wipetype], false, ((levelfadecol == 0) ? "FADEMAP1" : "FADEMAP0"), false, false); + V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, levelfadecol); + F_WipeEndScreen(); + } + + F_RunWipe(wipetype, wipedefs[wipetype], false, ((levelfadecol == 0) ? "FADEMAP1" : "FADEMAP0"), false, false); + } } /* @@ -8856,11 +8867,6 @@ void P_PostLoadLevel(void) G_BeginRecording(); // I AM NOW READY TO RECORD. demo.deferstart = true; - if (demo.attract == DEMO_ATTRACT_TITLE) - { - S_ShowMusicCredit(); - } - nextmapoverride = 0; skipstats = 0; diff --git a/src/r_fps.c b/src/r_fps.c index 1418b2a22..68b100b28 100644 --- a/src/r_fps.c +++ b/src/r_fps.c @@ -50,7 +50,7 @@ UINT32 R_GetFramerateCap(void) boolean R_UsingFrameInterpolation(void) { - return (R_GetFramerateCap() != TICRATE || cv_timescale.value < FRACUNIT); + return (R_GetFramerateCap() != TICRATE || I_GetTimeScale() < FRACUNIT); } static viewvars_t pview_old[MAXSPLITSCREENPLAYERS]; diff --git a/src/v_video.cpp b/src/v_video.cpp index 537c5d055..0dfd71e8e 100644 --- a/src/v_video.cpp +++ b/src/v_video.cpp @@ -1566,6 +1566,31 @@ void V_DrawFadeScreen(UINT16 color, UINT8 strength) .done(); } +lighttable_t *V_LoadCustomFadeMap(const char *lump) +{ + lumpnum_t lumpnum = LUMPERROR; + lighttable_t *clm = NULL; + + if (lump != NULL) + lumpnum = W_GetNumForName(lump); + else + return NULL; + + if (lumpnum != LUMPERROR) + { + clm = static_cast(Z_MallocAlign(COLORMAP_SIZE, PU_STATIC, NULL, 8)); + W_ReadLump(lumpnum, clm); + return clm; + } + + return NULL; +} + +const UINT8 *V_OffsetIntoFadeMap(const lighttable_t *clm, UINT8 strength) +{ + return ((const UINT8 *)clm + strength*256); +} + // // Fade the screen buffer, using a custom COLORMAP lump. // Split from V_DrawFadeScreen, because that function has @@ -1583,33 +1608,21 @@ void V_DrawCustomFadeScreen(const char *lump, UINT8 strength) // TODO: fix this for Twodee { - lumpnum_t lumpnum = LUMPERROR; - lighttable_t *clm = NULL; + lighttable_t *clm = V_LoadCustomFadeMap(lump); - if (lump != NULL) - lumpnum = W_GetNumForName(lump); - else - return; - - if (lumpnum != LUMPERROR) + if (clm != NULL) { - clm = static_cast(Z_MallocAlign(COLORMAP_SIZE, PU_STATIC, NULL, 8)); - W_ReadLump(lumpnum, clm); + const UINT8 *fadetable = V_OffsetIntoFadeMap(clm, strength); + const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height; + UINT8 *buf = screens[0]; - if (clm != NULL) - { - const UINT8 *fadetable = ((UINT8 *)clm + strength*256); - const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height; - UINT8 *buf = screens[0]; + // heavily simplified -- we don't need to know x or y + // position when we're doing a full screen fade + for (; buf < deststop; ++buf) + *buf = fadetable[*buf]; - // heavily simplified -- we don't need to know x or y - // position when we're doing a full screen fade - for (; buf < deststop; ++buf) - *buf = fadetable[*buf]; - - Z_Free(clm); - clm = NULL; - } + Z_Free(clm); + clm = NULL; } } } diff --git a/src/v_video.h b/src/v_video.h index 34f4c40ff..1597c86a8 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -236,6 +236,8 @@ void V_DrawFadeScreen(UINT16 color, UINT8 strength); // available to lua over my dead body, which will probably happen in this heat void V_DrawFadeFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c, UINT16 color, UINT8 strength); +lighttable_t *V_LoadCustomFadeMap(const char *lump); +const UINT8 *V_OffsetIntoFadeMap(const lighttable_t *clm, UINT8 strength); void V_DrawCustomFadeScreen(const char *lump, UINT8 strength); void V_DrawFadeConsBack(INT32 plines); diff --git a/src/y_inter.cpp b/src/y_inter.cpp index 7618eaa16..c6086dbf8 100644 --- a/src/y_inter.cpp +++ b/src/y_inter.cpp @@ -1951,7 +1951,9 @@ void Y_DetermineIntermissionType(void) // or for explicit requested skip (outside of modeattacking) || (modeattacking == ATTACKING_NONE && skipstats != 0) // or tutorial skip material - || (nextmapoverride == NEXTMAP_TUTORIALCHALLENGE+1 || tutorialchallenge != TUTORIALSKIP_NONE)) + || (nextmapoverride == NEXTMAP_TUTORIALCHALLENGE+1 || tutorialchallenge != TUTORIALSKIP_NONE) + // or title screen attract demos + || (demo.playback && demo.attract == DEMO_ATTRACT_TITLE)) { intertype = int_none; return;