diff --git a/src/Sourcefile b/src/Sourcefile index c5e65c549..22db137b6 100644 --- a/src/Sourcefile +++ b/src/Sourcefile @@ -12,6 +12,7 @@ deh_lua.c deh_tables.c z_zone.c f_finale.c +f_dscredits.cpp f_wipe.c g_demo.c g_game.c diff --git a/src/console.c b/src/console.c index 1c1893faa..c523959e8 100644 --- a/src/console.c +++ b/src/console.c @@ -1630,7 +1630,7 @@ void CON_Drawer(void) if (con_curlines > 0) CON_DrawConsole(); else if (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_CUTSCENE || gamestate == GS_CREDITS || gamestate == GS_BLANCREDITS - || gamestate == GS_VOTING || gamestate == GS_EVALUATION || gamestate == GS_WAITINGPLAYERS) + || gamestate == GS_SECRETCREDITS || gamestate == GS_VOTING || gamestate == GS_EVALUATION || gamestate == GS_WAITINGPLAYERS) CON_DrawHudlines(); Unlock_state(); diff --git a/src/d_main.cpp b/src/d_main.cpp index 617627c89..58517d93c 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -36,6 +36,7 @@ #include "console.h" #include "d_net.h" #include "f_finale.h" +#include "f_dscredits.hpp" #include "g_game.h" #include "hu_stuff.h" #include "m_emotes.h" @@ -87,7 +88,7 @@ #define ASSET_HASH_TEXTURES_KART 0xb4211b2f32b6a291 #define ASSET_HASH_CHARS_KART 0x1e68a3e01aa5c68b #define ASSET_HASH_MAPS_KART 0x38558ed00da41ce9 -#define ASSET_HASH_MAIN_PK3 0x492465f56dce7a0c +#define ASSET_HASH_MAIN_PK3 0x5c3c5f3a797479ff #define ASSET_HASH_MAPPATCH_PK3 0x3a82d44f1514d7f1 #define ASSET_HASH_BONUSCHARS_KART 0x60e6f13d822a7461 #ifdef USE_PATCH_FILE @@ -582,6 +583,12 @@ static void D_Display(void) HU_Drawer(); break; + case GS_SECRETCREDITS: + F_SecretCreditsDrawer(); + HU_Erase(); + HU_Drawer(); + break; + case GS_WAITINGPLAYERS: // The clientconnect drawer is independent... if (netgame) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index e68d69521..67e391f1a 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -5740,7 +5740,7 @@ static void Command_ExitLevel_f(void) CONS_Printf(M_GetText("This only works in a netgame.\n")); else if (!(server || (IsPlayerAdmin(consoleplayer)))) CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n")); - else if (( gamestate != GS_LEVEL && gamestate != GS_CREDITS && gamestate != GS_BLANCREDITS ) || demo.playback) + else if (( gamestate != GS_LEVEL && gamestate != GS_CREDITS && gamestate != GS_BLANCREDITS && gamestate != GS_SECRETCREDITS ) || demo.playback) CONS_Printf(M_GetText("You must be in a level to use this.\n")); else SendNetXCmd(XD_EXITLEVEL, NULL, 0); diff --git a/src/deh_tables.c b/src/deh_tables.c index b1c5b1843..9b350cb81 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -717,6 +717,7 @@ struct menu_routine_s const MENU_ROUTINES[] = { { "ERASEDATA", &MR_EraseData }, { "CREDITS", &MR_Credits }, { "BLANCREDITS", &MR_BlanCredits }, + { "SECRETCREDITS", &MR_SecretCredits }, { "HANDLEADDONS", &MR_HandleAddons }, { "SELECTABLECLEARMENUS", &MR_SelectableClearMenus }, { "GOBACK", &MR_GoBack }, @@ -1418,6 +1419,7 @@ struct int_const_s const INT_CONST[] = { {"GS_DEDICATEDSERVER",GS_DEDICATEDSERVER}, {"GS_WAITINGPLAYERS",GS_WAITINGPLAYERS}, {"GS_BLANCREDITS",GS_BLANCREDITS}, + {"GS_SECRETCREDITS",GS_SECRETCREDITS}, // Game controls {"GC_NULL",gc_null}, diff --git a/src/doomtype.h b/src/doomtype.h index 9743ca838..8f0d14a75 100644 --- a/src/doomtype.h +++ b/src/doomtype.h @@ -346,6 +346,9 @@ typedef UINT32 lumpnum_t; // 16 : 16 unsigned long (wad num: lump num) typedef UINT32 tic_t; #define INFTICS UINT32_MAX +typedef UINT64 longtic_t; +#define INFLONGTICS UINT64_MAX + #include "endian.h" // This is needed to make sure the below macro acts correctly in big endian builds #ifdef SRB2_BIG_ENDIAN diff --git a/src/f_dscredits.cpp b/src/f_dscredits.cpp new file mode 100644 index 000000000..ce5ba5d32 --- /dev/null +++ b/src/f_dscredits.cpp @@ -0,0 +1,1517 @@ +// BLANKART +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. +// Copyright (C) 2025 by "Anonimus". +// Copyright (C) 2025 Blankart Team. +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file k_dscredits.cpp +/// \brief (Mostly) secret MKDS credits :) + +#include + +#include "f_dscredits.hpp" + +#include "doomdef.h" +#include "doomstat.h" +#include "d_main.h" +#include "d_netcmd.h" +#include "f_finale.h" +#include "g_game.h" +#include "hu_stuff.h" +#include "r_local.h" +#include "s_sound.h" +#include "i_time.h" +#include "i_video.h" +#include "v_video.h" +#include "w_wad.h" +#include "z_zone.h" +#include "i_system.h" +#include "i_threads.h" +#include "m_menu.h" +#include "dehacked.h" +#include "g_input.h" +#include "console.h" +#include "m_random.h" +#include "m_misc.h" // moviemode functionality +#include "y_inter.h" +#include "m_cond.h" +#include "p_local.h" +#include "p_setup.h" +#include "st_stuff.h" // hud hiding +#include "fastcmp.h" +#include "r_fps.h" // R_GetTimeFrac +#include "r_things.h" // numskins +#include "r_skins.h" + +#include "lua_hud.h" +#include "lua_hook.h" + +//static INT16 dscredits_idx = 0; + +static longtic_t elapsed = 0; +static longtic_t lastelapsed = 0; +static longtic_t elapsed_diff; + +static longtic_t kartcredits_start = 13335000; +static longtic_t kartcredits_end = 186384000; +static longtic_t kartcredits_length = (kartcredits_end - kartcredits_start); +static fixed_t kartcredits_dividend = kartcredits_length / FRACUNIT; +static fixed_t kartcredits_height; + +static longtic_t blancredits_start = 195977000; +static longtic_t blancredits_end = 294593000; +static longtic_t blancredits_length = (blancredits_end - blancredits_start); +static fixed_t blancredits_dividend = blancredits_length / FRACUNIT; +static fixed_t blancredits_height; + +static longtic_t finalescene; +static INT32 finalesecs, finalespeed; + +static boolean dscredits_running = false; + +// Title Screen +static patch_t *ttbanner; // SONIC ROBO BLAST 2 +static patch_t *ttkart; // *vroom* KART +static patch_t *ttcheckers; // *vroom* KART +static patch_t *ttkflash; // flash screen + +// Scenery +#define NUMDSBUILDINGS 6 +static patch_t *dsground; // Ground +static patch_t *dsgrass; // Grass +static patch_t *dssky; // Sky +static patch_t *tylergag; // Tyler +static patch_t *dsbuildings[NUMDSBUILDINGS]; + +std::chrono::high_resolution_clock timer; +std::chrono::high_resolution_clock::time_point start, curr; + +#define CREDSKINCOUNT (MAXPLAYERS + DSUNIQUEPLAYERS) + +#define MAXMISCOBJS (MAXDSOBJECTS - CREDSKINCOUNT) + +#define KARTMOVE (884736) + +// 183 pixels in 20.5 seconds +#define RIVALAPPROACH (509542) + +#define SPECIALAPPROACH (3396949) + +#define FINALESPD (1791317) + +#define MICROSECS(x) (x * 1000000) + +#define FINALESECDELAY (MICROSECS(1)) + +// "Overconfident" character's initial speed. Dynamically adapts to screen resolution. +#define TOODAMNFAST(w) ((w + 256) * FRACUNIT) + +#define MICROSEC_TICS (MICROSECS(1) / TICRATE) + +#define LASTSCENE (21) + +// Converts microseconds to fixed-point values. +#define UsecToFixed(x) (static_cast((static_cast(x / 1000) * FRACUNIT) / 1000)) + +static fixed_t scroll_slow = (5033164); // 192 pixels in 2.5 seconds +static fixed_t scroll_fast = (12582912); // 192 pixels in 1 second + +// Generic scrolling multiplier +// 0 = current, 1 = last, 2 = offset +static fixed_t scene_scroll[3]; + +static fixed_t tylerscroll; +static longtic_t hityler; + +static longtic_t building_delay; +static longtic_t player_delay; + +static UINT16 numbuildings; +static UINT16 numplayerobjs; + +static boolean spawnplayers; +static INT32 playersadded; +static INT32 player_iterate; + + +static UINT16 specialiterate; + +static inline INT64 LongFixedMul(fixed_t a, fixed_t b) +{ + // Need to cast to unsigned before shifting to avoid undefined behaviour + // for negative integers + return (INT64)(((UINT64)((INT64)a * b)) >> FRACBITS); +} + +// Handles interpolating microseconds into a scroll offset. +#define CreditsScrollFx(x,y) (static_cast(LongFixedMul(UsecToFixed(x), y))) +#define CreditsScroll(x,y) (CreditsScrollFx(x,y) >> FRACBITS) + +static UINT16 credits_skins[CREDSKINCOUNT]; +static UINT16 credits_colors[CREDSKINCOUNT]; + +static UINT16 player1_idx, rival_idx, special_idx, specialp, tylermemoriam; + +static const char* gamettl = "SRB2KART"; + +dscredits_t dscredits; + +extern "C" +{ + +// These timings are in microseconds (1 second is 1000000 microseconds) +longtic_t creditstimings[] = { + // ACT 1 (Kart credits) + 7335000, // Title screen text fades out (fadeout is 1.5 seconds) + 13335000, // Credits begin to scroll + 32005000, // Screen fades into sunset scene (fadein is 2 seconds) + + 77350000, // Characters begin to randomly appear + // (spawn rate should be betwen 2.5 and 5 seconds) + + 134528000, // Stop spawning characters for some time + + 160036000, // If they're not an RR character, spawn in Tails. + // If they ARE an RR character, spawn in one of 3 random rivals. + // They should drive up for around 20 seconds, before stopping. + + 180036000, // Stop the rival here. + + // ACT 2 (Blan credits) + 195977000, // Both players speed up! The HOPE-O-METER is at maximum! + 210489000, // Spawn a random "special" character. These stop in place for a bit. + // (Drive ahead for 6 seconds, then stop for 4, then drive off). + + 218154000, // Spawn another special character. This one acts the same as the previous. + + 226260000, // Spawn a third special character. This one's overconfident and ends up spinning out. + // * Drive ahead for 1 second (really fast!) + // * Pause offscreen on the left for 2.5 seconds + // * Spin around all the way back for 2.75 seconds + + 234260000, // Back to normalcy, mostly. Spawn a random character. + + 239260000, // The "overconfident" one returns, this time properly ready and + // moving at a decent pace. + // They lead the pack of randomly spawning racers. + // Back to normalcy (for real this time)! + + 254688000, // Stop spawning characters for some time + 267874000, // The big finish! All racers come in in a line... + + 296536000, // ...and they all drive off into the sunset. + + 298978000, // Fade the screen. + + 300226428, // SONIC ROBO BLAST 2 + 301374285, // *vroom* KART + + 303311000, // One more fadeout... + + 308203000, // (scene 21) This should return you to the title screen. + + INFLONGTICS // Dummy (Let's *not* crash the game...) +}; + +// Basically just trims the "Thanks for playing!" text +static const char *credits[] = { + "\1SRB2Kart", + "\1Credits", + "", + "\1Game Design", + "Sally \"TehRealSalt\" Cochenour", + "Jeffery \"Chromatian\" Scott", + "\"VelocitOni\"", + "", + "\1Lead Programming", + "Sally \"TehRealSalt\" Cochenour", + "Vivian \"toaster\" Grannell", + "Ronald \"Eidolon\" Kinard", + "James Robert Roman", + "Sean \"Sryder\" Ryder", + "Ehab \"wolfs\" Saeed", + "\"ZarroTsu\"", + "", + "\1Support Programming", + "\"Lach\"", + "\"Lat\'\"", + "AJ \"Tyron\" Martinez", + "\"Monster Iestyn\"", + "\"SteelT\"", + "", + "\1External Programming", + "Alam Ed Arias", + "\"alphaRexJames\"", + "\"Ashnal\"", + "\"filpAM\"", + "\"FlykeSpice\"", + "\"Hannu Hanhi\"", + "\"himie\"", + "\"JugadorXEI\"", + "\"Kimberly\"", + "\"Lighto97\"", + "\"Lonsfor\"", + "\"mazmazz\"", + "\"minenice\"", + "\"Shuffle\"", + "\"Snu\"", + "\"X.organic\"", + "", + "\1Lead Artists", + "Desmond \"Blade\" DesJardins", + "\"VelocitOni\"", + "", + "\1Support Artists", + "Sally \"TehRealSalt\" Cochenour", + "\"Chengi\"", + "\"Chrispy\"", + "Sherman \"CoatRack\" DesJardins", + "\"DrTapeworm\"", + "Jesse \"Jeck Jims\" Emerick", + "Wesley \"Charyb\" Gillebaard", + "\"Nev3r\"", + "Vivian \"toaster\" Grannell", + "James \"SeventhSentinel\" Hall", + "\"Lat\'\"", + "\"rairai104n\"", + "\"Tyrannosaur Chao\"", + "\"ZarroTsu\"", + "", + "\1External Artists", + "\"1-Up Mason\"", + "\"DirkTheHusky\"", + "\"LJSTAR\"", + "\"MotorRoach\"", + "\"Ritz\"", + "\"Rob\"", + "\"SmithyGNC\"", + "\"Snu\"", + "\"Spherallic\"", + "\"TelosTurntable\"", + "\"VAdaPEGA\"", + "\"Virt\"", + "\"Voltrix\"", + "", + "\1Sound Design", + "James \"SeventhSentinel\" Hall", + "Sonic Team", + "\"VAdaPEGA\"", + "\"VelocitOni\"", + "", + "\1Original Music", + "\"DrTapeworm\"", + "Wesley \"Charyb\" Gillebaard", + "James \"SeventhSentinel\" Hall", + "", + "\1Lead Level Design", + "\"Blitz-T\"", + "Sally \"TehRealSalt\" Cochenour", + "Desmond \"Blade\" DesJardins", + "Jeffery \"Chromatian\" Scott", + "\"Tyrannosaur Chao\"", + "", + "\1Support Level Design", + "\"Chaos Zero 64\"", + "\"D00D64\"", + "\"DrTapeworm\"", + "Paul \"Boinciel\" Clempson", + "Sherman \"CoatRack\" DesJardins", + "Vivian \"toaster\" Grannell", + "\"Gunla\"", + "James \"SeventhSentinel\" Hall", + "\"Lat\'\"", + "\"MK\"", + "\"Ninferno\"", + "Sean \"Sryder\" Ryder", + "\"Ryuspark\"", + "\"Simsmagic\"", + "Ivo Solarin", + "\"SP47\"", + "\"TG\"", + "\"Victor Rush Turbo\"", + "\"ZarroTsu\"", + "", + "\1Testing", + "RKH License holders", + "The KCS", + "\"CyberIF\"", + "\"Dani\"", + "Karol \"Fooruman\" D""\x1E""browski", // DÄ…browski, accents in srb2 :ytho: + "\"Virt\"", + "", + "\1Special Thanks", + "SEGA", + "Sonic Team", + "SRB2 & Sonic Team Jr. (www.srb2.org)", + "\"Chaos Zero 64\"", + "", + "\1Produced By", + "Kart Krew", + "", + "\1In Memory of", + "\2\"Tyler52\"", + NULL +}; + +tic_t dscreditsticks = 0; + +static INT32 F_FindFirstFreeObj() +{ + INT32 i = 0; + for (i = 0; i < MAXMISCOBJS; i++) + { + // Found one. + if (!dscredits.creditsobj[i].active) + { + break; + } + } + + return i; +} + +static void F_SpawnCreditsObject(INT32 id, INT32 x, INT32 y, fixed_t scale, patch_t *patch, INT64 stopat) +{ + dscredits.creditsobj[id].active = true; + + dscredits.creditsobj[id].position[0] = x << FRACBITS; + dscredits.creditsobj[id].position[1] = y << FRACBITS; + dscredits.creditsobj[id].scale = scale; + + dscredits.creditsobj[id].patch = patch; + + dscredits.creditsobj[id].movestop = stopat; +} + +static INT32 F_FindFirstFreePlayer() +{ + INT32 i = 0; + for (i = MAXMISCOBJS; i < MAXDSOBJECTS; i++) + { + // Found one. + if (!dscredits.creditsobj[i].active) + { + break; + } + } + + return i; +} + +static void F_SpawnCreditsPlayer(INT32 id, INT32 x, INT32 y, fixed_t scale, UINT16 skin, UINT16 color, INT64 stopat) +{ + dscredits.creditsobj[id].active = true; + + dscredits.creditsobj[id].position[0] = x << FRACBITS; + dscredits.creditsobj[id].position[1] = y << FRACBITS; + dscredits.creditsobj[id].scale = scale; + + dscredits.creditsobj[id].skin = skin; + dscredits.creditsobj[id].color = color; + + dscredits.creditsobj[id].movestop = stopat; + dscredits.creditsobj[id].movestart = stopat; +} + +static fixed_t F_FixedLerp(fixed_t v0, fixed_t v1, fixed_t t) +{ + return FixedMul(v1,t) + FixedMul(FRACUNIT - t, v0); +} + +void F_SecretCreditsTicker(void) +{ + UINT16 i; + INT32 credskins; + UINT16 pskin, pcol; + + if (dscreditsticks < 1) + { + dscredits.creditsscene = 0; + tylermemoriam = 0; + hityler = 0; + + building_delay = 0; + numbuildings = 0; + + player_delay = 0; + player_iterate = 0; + + specialiterate = 0; + playersadded = 0; + + finalescene = 0; + finalespeed = 0; + finalesecs = -1; + + spawnplayers = false; + dscredits_running = false; + } + + if (dscreditsticks == 1) + { + // Use the second tic to load in all necessary content. + + // Timer and scene counter. + elapsed = 0; + dscredits.creditsscene = 0; + + credskins = CREDSKINCOUNT - 1; + + // SRB2K logo + ttbanner = static_cast(W_CachePatchName("TTKBANNR", PU_PATCH_LOWPRIORITY)); + ttkart = static_cast(W_CachePatchName("TTKART", PU_PATCH_LOWPRIORITY)); + ttcheckers = static_cast(W_CachePatchName("TTCHECK", PU_PATCH_LOWPRIORITY)); + ttkflash = static_cast(W_CachePatchName("TTKFLASH", PU_PATCH_LOWPRIORITY)); + + // Scenery patches + dsground = static_cast(W_CachePatchName("EXCDGND", PU_PATCH_LOWPRIORITY)); + dsgrass = static_cast(W_CachePatchName("EXCDGRS", PU_PATCH_LOWPRIORITY)); + dssky = static_cast(W_CachePatchName("EXCDSKY", PU_PATCH_LOWPRIORITY)); + tylergag = static_cast(W_CachePatchName("TYLER52", PU_PATCH_LOWPRIORITY)); + + const char* patchNames[] = { + "EXCDBD1", "EXCDBD2", "EXCDBD3", "EXCDBD4", "EXCDBD5", "EXCDBD6" + }; + + for (i = 0; i < NUMDSBUILDINGS; i++) + { + dsbuildings[i] = static_cast(W_CachePatchName(patchNames[i], PU_PATCH_LOWPRIORITY)); + } + + // HUD object array + for (i = 0; i < MAXDSOBJECTS; i++) + { + dscredits.creditsobj[i].active = false; + dscredits.creditsobj[i].skin = MAXSKINS; + dscredits.creditsobj[i].scale = FRACUNIT; + } + + // Allocate skins for the local players. + for (i = 0; i < (splitscreen + 1); i++) + { + pskin = R_SkinAvailable(cv_skin[i].string); + + if (pskin >= MAXSKINS) + { + pskin = 0; + } + + if (!R_SkinUsable(-1, pskin)) + { + pskin = 0; + } + + if (i == 0) + { + // Player 1 always gets priority. + player1_idx = credskins; + } + + pcol = cv_playercolor[i].value; + credits_skins[credskins] = pskin; + credits_colors[credskins] = pcol; + credskins--; + } + + // Allocate the rival's skin, if possible. + UINT16 grabskins[MAXSKINS]; + UINT16 rand, rivalskin; + + grabskins[0] = R_SkinAvailable(skins[pskin].rivals[0]); + grabskins[1] = R_SkinAvailable(skins[pskin].rivals[1]); + grabskins[2] = R_SkinAvailable(skins[pskin].rivals[2]); + + // Pick a random initial point to find the rival. + rand = M_RandomKey(SKINRIVALS); + + // Start verifying and looking for the first valid rival. + for (i = 0; i < 5; i++) + { + rivalskin = grabskins[(i + rand) % SKINRIVALS]; + + if ((rivalskin >= MAXSKINS)||(!R_SkinUsable(-1, rivalskin))) + { + // Not valid (likely -1, or can't be used) + continue; + } + + // Got one, I think! + break; + } + + if (rivalskin >= MAXSKINS) + { + // Couldn't find a rival. Get Tails! + rivalskin = R_SkinAvailable("tails"); + + if ((rivalskin >= MAXSKINS)||(!R_SkinUsable(-1, rivalskin))) + { + // ...goddammit. + rivalskin = 0; + } + } + + // Add in the rival. + rival_idx = credskins; + credits_skins[credskins--] = rivalskin; + + UINT16 usableskins = 0; + UINT16 j; + + for (i = 0; i < numskins; i++) + { + if (!R_SkinUsable(-1, i)) + continue; + + for (j = 0; j < (splitscreen + 1); j++) + { + if (i == credits_skins[(CREDSKINCOUNT - 1) - j]) + { + // Already used by a player. + continue; + } + } + + if (i == rivalskin) + { + // Already used by the rival. + continue; + } + + grabskins[usableskins++] = i; + } + + if (!usableskins) + { + I_Error("F_SecretCreditsTicker: No valid skins to pick from!?"); + } + + // Pick a random initial point to load in the usable skins + rand = M_RandomKey(usableskins); + + // Pre-allocate the "special character" index. This can be random. + special_idx = credskins; + + while(credskins >= 0) + { + // Allocate the remaining skins from the skin list. + credits_skins[credskins] = grabskins[((rand + credskins) % usableskins)]; + credits_colors[credskins] = skins[credits_skins[credskins]].prefcolor; + credskins--; + + if (credskins < 0) + { + // Break if we're out of range! + break; + } + } + + // Finally, get the height of the credits scroll. Consistent timing is nice. :) + kartcredits_height = 0; + blancredits_height = 0; + + for (i = 0; credits[i]; i++) + { + switch(credits[i][0]) + { + case 0: + kartcredits_height += 50<height) >> 1) - 1, + FRACUNIT >> 1, + credits_skins[player1_idx], + credits_colors[player1_idx], + INFLONGTICS); + + start = timer.now(); + + S_ChangeMusicInternal("BLNCDS", false); + S_ShowMusicCredit(0, 5*TICRATE, 0); + } + + if (!dscredits_running) + { + return; + } + + scene_scroll[1] = scene_scroll[0]; + + curr = timer.now(); + const std::chrono::duration fp_ms = curr - start; + + // This is fucking stupid. + lastelapsed = elapsed; + elapsed = static_cast(fp_ms.count() * 1000000); + + if ((tylermemoriam) && (!hityler)) + { + hityler = elapsed; + } + + if (elapsed > creditstimings[dscredits.creditsscene]) + { + // Increment the scene count by 1. + dscredits.creditsscene++; + } + + fixed_t lerptime = std::max(0, + std::min(static_cast(FRACUNIT), + static_cast(elapsed - creditstimings[7]) / 8)); + + fixed_t scroll_mul = scroll_slow; + elapsed_diff = elapsed - lastelapsed; + + if (dscredits.creditsscene > 1) + { + scroll_mul = F_FixedLerp(scroll_slow, + scroll_fast, + lerptime); + + if ((elapsed > creditstimings[2]) && (dscredits.creditsscene < 16)) + { + scene_scroll[0] += CreditsScroll((elapsed_diff), scroll_mul); + } + else if (dscredits.creditsscene >= 16) + { + finalespeed = CreditsScrollFx((elapsed_diff), scroll_mul) * -1; + + if (specialiterate == 7) + { + for (i = MAXMISCOBJS; i < MAXMISCOBJS + MAXPLAYERS; i++) + { + if (!dscredits.creditsobj[i].active) + { + continue; + } + + dscredits.creditsobj[i].speed[0] = -scroll_mul; + } + specialiterate++; + + dscredits.creditsobj[rival_idx + MAXMISCOBJS].speed[0] = -scroll_mul; + } + + dscredits.creditsobj[player1_idx + MAXMISCOBJS].position[0] += finalespeed; + } + + scene_scroll[2] = (scene_scroll[0] - scene_scroll[1]); + + if (hityler) + { + tylerscroll = CreditsScroll((elapsed - hityler), scroll_mul); + } + } + + if (dscredits.creditsscene > 2) + { + INT16 idx; + + numbuildings = 0; + numplayerobjs = 0; + + building_delay = static_cast(std::max(static_cast(0), static_cast(building_delay) - static_cast(elapsed_diff))); + + if (!building_delay) + { + idx = F_FindFirstFreeObj(); + + patch_t *bld = dsbuildings[M_RandomKey(NUMDSBUILDINGS)]; + + F_SpawnCreditsObject(idx, + (-vidxdiff) - SHORT(bld->width), + (-vidydiff) + h - (SHORT(dsground->height) >> 1) - SHORT(bld->height), + FRACUNIT, + bld, + INFLONGTICS); + + // 5 or 9 seconds, random delay + building_delay = static_cast(M_RandomRange(5, 9)) * + F_FixedLerp(1000000,400000,lerptime); + } + + // Dynamically do this for consistent timing. + fixed_t vidwidthmul = 205 * w; + fixed_t approach = FixedMul(SPECIALAPPROACH, vidwidthmul); + + if (spawnplayers) + { + player_delay = static_cast(std::max(static_cast(0), static_cast(player_delay) - static_cast(elapsed_diff))); + + if (!player_delay) + { + idx = F_FindFirstFreePlayer(); + + F_SpawnCreditsPlayer(idx, + (-vidxdiff) + w + 128, + (-vidydiff) + h - (SHORT(dsground->height) >> 1) - 1, + FRACUNIT >> 1, + credits_skins[player_iterate], + credits_colors[player_iterate], + INFLONGTICS); + + dscredits.creditsobj[idx].speed[0] = -KARTMOVE; + + // 8 or 12 seconds, random delay + if (dscredits.creditsscene >= 12) + { + dscredits.creditsobj[idx].speed[0] = + (dscredits.creditsobj[idx].speed[0] * 9) / 2; + player_delay = static_cast(M_RandomRange(8, 12)) * 250000; + } + else + { + player_delay = static_cast(M_RandomRange(8, 12)) * 1000000; + } + + playersadded++; + player_iterate++; + player_iterate %= MAXPLAYERS; + } + } + + // Set up players for the second set of spawns. + if (dscredits.creditsscene == 12) + { + player_delay = static_cast(M_RandomRange(8, 12)) * 250000; + } + + if ((dscredits.creditsscene == 4) || (dscredits.creditsscene == 13)) + { + spawnplayers = true; + } + else + { + spawnplayers = false; + } + + // Rival handling. + if (dscredits.creditsscene == 6) + { + if (!specialiterate) + { + F_SpawnCreditsPlayer(rival_idx + MAXMISCOBJS, + (-vidxdiff) + w + 128, + (-vidydiff) + h - (SHORT(dsground->height) >> 1) - 1, + FRACUNIT >> 1, + credits_skins[rival_idx], + skins[credits_skins[rival_idx]].prefcolor, + INFLONGTICS); + + dscredits.creditsobj[rival_idx + MAXMISCOBJS].speed[0] = -RIVALAPPROACH; + dscredits.creditsobj[rival_idx + MAXMISCOBJS].movestop = (40 * 1000000); + specialiterate++; + } + } + + if (dscredits.creditsobj[rival_idx + MAXMISCOBJS].active) + { + if (dscredits.creditsobj[rival_idx + MAXMISCOBJS].movestop > 0) + { + dscredits.creditsobj[rival_idx + MAXMISCOBJS].movestop = std::max( + static_cast(0), + static_cast(dscredits.creditsobj[rival_idx + MAXMISCOBJS].movestop) - + static_cast(elapsed_diff)); + + if (!dscredits.creditsobj[rival_idx + MAXMISCOBJS].movestop) + { + dscredits.creditsobj[rival_idx + MAXMISCOBJS].speed[0] = 0; + dscredits.creditsobj[rival_idx + MAXMISCOBJS].movestop = -1; + } + } + + // Apply movements. + if (dscredits.creditsscene == 8) + { + // Scrolls with the FG + dscredits.creditsobj[rival_idx + MAXMISCOBJS].position[0] += (scene_scroll[2] << FRACBITS); + } + + // Moves by speed. + dscredits.creditsobj[rival_idx + MAXMISCOBJS].position[0] += CreditsScrollFx( + (elapsed_diff), dscredits.creditsobj[rival_idx + MAXMISCOBJS].speed[0]); + dscredits.creditsobj[rival_idx + MAXMISCOBJS].position[1] += CreditsScrollFx( + (elapsed_diff), dscredits.creditsobj[rival_idx + MAXMISCOBJS].speed[1]); + + // Clamp to "hide" the rival at points + fixed_t xpos = (dscredits.creditsobj[rival_idx + MAXMISCOBJS].position[0] >> FRACBITS); + fixed_t max_xpos = (-vidxdiff + w + 128); + fixed_t min_xpos = (-vidxdiff - 128); + + + if (xpos < min_xpos) + { + dscredits.creditsobj[rival_idx + MAXMISCOBJS].position[0] = (min_xpos << FRACBITS); + } + else if (xpos > max_xpos) + { + dscredits.creditsobj[rival_idx + MAXMISCOBJS].position[0] = (max_xpos << FRACBITS); + } + } + + // Special characters. + if (((dscredits.creditsscene == 9) && (specialiterate < 2)) || + ((dscredits.creditsscene == 10) && (specialiterate < 3))) + { + idx = F_FindFirstFreePlayer(); + F_SpawnCreditsPlayer(idx, + (-vidxdiff) + w + 128, + (-vidydiff) + h - (SHORT(dsground->height) >> 1) - 1, + FRACUNIT >> 1, + credits_skins[player_iterate], + credits_colors[player_iterate], + INFLONGTICS); + + dscredits.creditsobj[idx].speed[0] = -approach; + dscredits.creditsobj[idx].movestop = (6 * 1000000); + + dscredits.creditsobj[idx].movestart = (8 * 1000000); + + + dscredits.creditsobj[idx].movestartspd[0] = -(approach * 2); + specialiterate++; + + player_iterate++; + player_iterate %= MAXPLAYERS; + + playersadded++; + } + else if ((dscredits.creditsscene == 11) && (specialiterate < 4)) + { + idx = F_FindFirstFreePlayer(); + + F_SpawnCreditsPlayer(idx, + (-vidxdiff) + w + 128, + (-vidydiff) + h - (SHORT(dsground->height) >> 1) - 1, + FRACUNIT >> 1, + credits_skins[special_idx], + credits_colors[special_idx], + INFLONGTICS); + + dscredits.creditsobj[idx].speed[0] = -TOODAMNFAST(w); + dscredits.creditsobj[idx].movestop = (1150000); + + dscredits.creditsobj[idx].movestart = (3500000); + dscredits.creditsobj[idx].movestartspd[0] = scroll_mul; + + specialp = idx; + + specialiterate++; + } + + // Miscellaneous interactions. + if ((dscredits.creditsobj[specialp].movestop <= 0) && (specialiterate == 4)) + { + dscredits.creditsobj[specialp].spinout = true; + specialiterate++; + } + + if ((dscredits.creditsscene == 12) && (specialiterate == 5)) + { + dscredits.creditsobj[rival_idx + MAXMISCOBJS].speed[0] = -((KARTMOVE * 9) / 2); + specialiterate++; + } + + if ((dscredits.creditsscene == 13) && (specialiterate == 6)) + { + dscredits.creditsobj[specialp].spinout = false; + dscredits.creditsobj[specialp].speed[0] = -((KARTMOVE * 9) / 2); + specialiterate++; + } + + // Finale! + if (dscredits.creditsscene == 15) + { + if (!finalescene) + { + finalescene = elapsed; + } + + if ((((elapsed - finalescene) / FINALESECDELAY) != finalesecs)) + { + finalesecs = ((elapsed - finalescene) / FINALESECDELAY); + + if (playersadded > 0) + { + playersadded--; + player_iterate--; + + while (player_iterate < 0) + { + player_iterate += MAXPLAYERS; + } + + idx = F_FindFirstFreePlayer(); + + F_SpawnCreditsPlayer(idx, + (-vidxdiff) - 128, + (-vidydiff) + h - (SHORT(dsground->height) >> 1) - 1, + FRACUNIT >> 1, + credits_skins[player_iterate], + credits_colors[player_iterate], + INFLONGTICS); + + dscredits.creditsobj[idx].speed[0] = FixedMul(vidwidthmul,FINALESPD); + } + else + { + if (playersadded > -1) + { + playersadded--; + + dscredits.creditsobj[specialp].speed[0] = FixedMul(vidwidthmul,FINALESPD); + } + else if (playersadded > -2) + { + playersadded--; + + dscredits.creditsobj[rival_idx + MAXMISCOBJS].speed[0] = FixedMul(vidwidthmul,FINALESPD); + } + } + } + } + + // Handle scenery. + for (i = 0; i < MAXMISCOBJS; i++) + { + if (!dscredits.creditsobj[i].active) + { + continue; + } + + numbuildings++; + + // Scroll along the background. + dscredits.creditsobj[i].position[0] += ((scene_scroll[2] << FRACBITS) >> 1); + + if ((dscredits.creditsobj[i].position[0] >> FRACBITS) > ((-vidxdiff + w) + 48)) + { + // Offscreen, unload ourselves. + dscredits.creditsobj[i].active = false; + continue; + } + } + + // Handle players. Unique objects are handled on a case-by-case basis. + for (i = MAXMISCOBJS; i < MAXMISCOBJS + MAXPLAYERS; i++) + { + if (!dscredits.creditsobj[i].active) + { + continue; + } + + numplayerobjs++; + + if (dscredits.creditsobj[i].movestop > 0) + { + dscredits.creditsobj[i].movestop = std::max( + static_cast(0), + static_cast(dscredits.creditsobj[i].movestop) - + static_cast(elapsed_diff)); + + if (!dscredits.creditsobj[i].movestop) + { + dscredits.creditsobj[i].speed[0] = 0; + dscredits.creditsobj[i].movestop = -1; + } + } + + if (dscredits.creditsobj[i].movestart > 0) + { + dscredits.creditsobj[i].movestart = std::max( + static_cast(0), + static_cast(dscredits.creditsobj[i].movestart) - + static_cast(elapsed_diff)); + + if (!dscredits.creditsobj[i].movestart) + { + dscredits.creditsobj[i].speed[0] = dscredits.creditsobj[i].movestartspd[0]; + dscredits.creditsobj[i].movestart = -1; + } + } + + // Apply movements. + dscredits.creditsobj[i].position[0] += CreditsScrollFx( + (elapsed_diff), dscredits.creditsobj[i].speed[0]); + dscredits.creditsobj[i].position[1] += CreditsScrollFx( + (elapsed_diff), dscredits.creditsobj[i].speed[1]); + + if (dscredits.creditsscene < 15) // For the finale, no offscreen handling allowed! + { + if (((dscredits.creditsobj[i].position[0] >> FRACBITS) < (-vidxdiff - 128)) && + (i != specialp)) // The special player needs to be handled differently. + { + // Offscreen, unload ourselves. + dscredits.creditsobj[i].active = false; + continue; + } + } + + if (i == specialp) + { + // Handle offscreening VERY differently + fixed_t xpos = (dscredits.creditsobj[i].position[0] >> FRACBITS); + fixed_t max_xpos = (-vidxdiff + w + 128); + fixed_t min_xpos = (-vidxdiff - 128); + + if (xpos < min_xpos) + { + dscredits.creditsobj[i].position[0] = (min_xpos << FRACBITS); + } + else if ((xpos > max_xpos) && (dscredits.creditsscene < 15)) + { + dscredits.creditsobj[i].position[0] = (max_xpos << FRACBITS); + } + } + } + } + + // After ALL THAT, check if the credits are finished. + if (dscredits.creditsscene >= LASTSCENE) + { + F_StartGameEvaluation(); + } + + /*if (dscredits.creditsscene > 1) + { + // "Simulate" the drawing of the credits so that dedicated mode doesn't get stuck + fixed_t y = (80< vid.height) + break; + } + } + + // Do this here rather than in the drawer you doofus! (this is why dedicated mode broke at credits) + if (!credits[i] && y <= 120< 0) ? 1 : 0)) +#define absmod(x,y) ((abs(x) % y) * sign(x)) + +// Repeats horizontally until it reaches across the screen. +static void F_DrawBannerPatch(fixed_t x, fixed_t y, fixed_t scale, INT32 scrn, patch_t *patch, const UINT8 *colormap) +{ + fixed_t w = (vid.width / vid.dupx); + fixed_t vidxdiff = ((w - BASEVIDWIDTH) << FRACBITS) / 2; + fixed_t pw = (static_cast(SHORT(patch->width)) * scale); + + fixed_t xx = ((-vidxdiff + absmod(x * FRACUNIT, pw)) - (pw << 1)); + + while(xx < (w << FRACBITS)) + { + V_DrawFixedPatch(xx, y<numframes) // No frames ?? + return false; // Can't render! + + frame %= sprdef->numframes; + + sprframe = &sprdef->spriteframes[frame]; + sprpatch = static_cast(W_CachePatchNum(sprframe->lumppat[rotation], PU_CACHE)); + + if (sprframe->flip & (1<(dscredits.creditsobj[i].color), + GTC_MENUCACHE); + + /*sprintf(debug, + "xspd: %f\nstopat: %d\nspinout: %d\n", + FIXED_TO_FLOAT(CreditsScrollFx((elapsed_diff), dscredits.creditsobj[i].speed[0])), + dscredits.creditsobj[i].movestop, + static_cast(dscredits.creditsobj[i].spinout));*/ + + M_DrawCharacterSprite(dscredits.creditsobj[i].position[0] >> FRACBITS, + dscredits.creditsobj[i].position[1] >> FRACBITS, + dscredits.creditsobj[i].scale, + 4, + dscredits.creditsobj[i].skin, + dscredits.creditsobj[i].spinout ? SPR2_SPIN : SPR2_FSTN, + dscredits.creditsobj[i].spinout ? (((ticker * 5)/8) & 7) : 2, + ticker + i, + 0, + colormap); + + + /*V_DrawThinString((dscredits.creditsobj[i].position[0] >> FRACBITS) - 128, + (dscredits.creditsobj[i].position[1] >> FRACBITS) - 96, + V_ALLOWLOWERCASE, + debug);*/ + } + else if (dscredits.creditsobj[i].patch) + { + V_DrawFixedPatch((dscredits.creditsobj[i].position[0] >> FRACBITS) << FRACBITS, + (dscredits.creditsobj[i].position[1] >> FRACBITS) << FRACBITS, + dscredits.creditsobj[i].scale, + 0, dscredits.creditsobj[i].patch, NULL); + } + } +} + +static void F_DrawCreditsArea(fixed_t y_root, fixed_t height, fixed_t xdiff) +{ + fixed_t y, sky_y, ground_y; + + y = y_root; + + // Store the ground area + y -= static_cast(SHORT(dsground->height) >> 1); + ground_y = sky_y = y; + + sky_y -= static_cast(SHORT(dssky->height) >> 1); + + // Draw the sky + F_DrawBannerPatch(scene_scroll[0] >> 1, sky_y, FRACUNIT >> 1, 0, dssky, NULL); + + // Draw Tyler + if (tylermemoriam) + { + V_DrawPatch(-xdiff - SHORT(tylergag->width) + ((tylerscroll * 3) >> 1), ground_y - (height >> 1), V_TRANSLUCENT, tylergag); + } + + // Draw the game objects (buildings, players) + F_DrawCreditsObjects(); + + // Draw the ground and grass + F_DrawBannerPatch(scene_scroll[0], ground_y, FRACUNIT >> 1, 0, dsground, NULL); + + ground_y -= static_cast(SHORT(dsgrass->height) >> 1); + + F_DrawBannerPatch(scene_scroll[0], ground_y, FRACUNIT >> 1, 0, dsgrass, NULL); +} + +// Not really "fake" considering this is the actual title sequence. +static void F_FakeTitleScreen(tic_t count) +{ + INT32 w, h; + + w = (vid.width / vid.dupx); + h = (vid.height / vid.dupy); + + const INT32 vidxdiff = (w - BASEVIDWIDTH) / 2; + const INT32 vidydiff = (h - BASEVIDHEIGHT) / 2; + + if (count < 50) + { + V_DrawSmallScaledPatch(84, 36, 0, ttbanner); + + if (count >= 20) + V_DrawSmallScaledPatch(84, 87, 0, ttkart); + else if (count >= 10) + V_DrawSciencePatch((84<>1; + + if (transval) + { + V_DrawFill(-vidxdiff - 2, -vidydiff - 2, w + 2, h + 2, ((10 - transval) << V_ALPHASHIFT)|0); + } + + V_DrawSmallScaledPatch(84, 36, 0, ttbanner); + + V_DrawSmallScaledPatch(84, 87, 0, ttkart); + + if (!transval) + return; + + V_DrawSmallScaledPatch(84, 36, transval< 2) && (dscredits.creditsscene < 19)) + { + bgcolor = 198; + } + + V_DrawFill(-vidxdiff - 2, -vidydiff - 2, w + 2, h + 2, bgcolor); + + if (dscredits.creditsscene < 2) + { + V_DrawCenteredStringAtFixed(160<(10), std::max(static_cast(0), elapsed - creditstimings[0]) / 150000); // 1.5 seconds for fadeout + } + else if (dscredits.creditsscene == 3) + { + alpha = 10 - std::min(static_cast(10), std::max(static_cast(0), elapsed - creditstimings[2]) / 200000); // 2.0 seconds for fadein + } + else if (dscredits.creditsscene >= 17) + { + alpha = std::min(static_cast(10), std::max(static_cast(0), elapsed - creditstimings[16]) / 200000); // 2.0 seconds for fadeout + titlealpha = std::min(static_cast(10), std::max(static_cast(0), elapsed - creditstimings[16]) / 100000); // 1 second for banner fadein + } + + if (dscredits.creditsscene >= 20) + { + titlefade = std::min(static_cast(10), std::max(static_cast(0), elapsed - creditstimings[19]) / 400000); // 4.0 seconds for fadeout + } + + if ((dscredits.creditsscene > 2) && (dscredits.creditsscene < 19)) + { + F_DrawCreditsArea(static_cast(-vidydiff + h), static_cast(h), static_cast(vidxdiff)); + } + + if (alpha) + { + V_DrawFill(-vidxdiff - 2, -vidydiff - 2, w + 2, h + 2, ((10 - alpha) << V_ALPHASHIFT)|31); + } + + if ((titlealpha) && (dscredits.creditsscene < 18)) + { + V_DrawSmallScaledPatch(84, 36, ((10 - titlealpha) << V_ALPHASHIFT), ttbanner); + } + else if (dscredits.creditsscene >= 18) + { + tic_t fakefinalecount = static_cast(std::max(static_cast(0), + elapsed - creditstimings[17]) / + MICROSEC_TICS); + + F_FakeTitleScreen(fakefinalecount); + + if (titlefade) + { + V_DrawFill(-vidxdiff - 2, -vidydiff - 2, w + 2, h + 2, ((10 - titlefade) << V_ALPHASHIFT)|31); + } + } + + INT32 y, i; + // The credits text overlays everything. + if ((dscredits.creditsscene > 1) && (dscredits.creditsscene < 8)) + { + y = ((BASEVIDHEIGHT + vidydiff) << FRACBITS) - + FixedMul(kartcredits_height + (h << FRACBITS), std::min(std::max(kartcredits_start, elapsed) - kartcredits_start, kartcredits_end) / kartcredits_dividend); + + // Draw credits text on top + for (i = 0; credits[i]; i++) + { + switch(credits[i][0]) + { + case 0: + y += 50<>FRACBITS > -(10 + vidydiff)) + V_DrawString(8 - vidxdiff, y >> FRACBITS, V_ORANGEMAP, &credits[i][1]); + y += 16<>FRACBITS > -(10 + vidydiff)) + V_DrawRightAlignedThinString(248 - vidxdiff, y >> FRACBITS, V_ALLOWLOWERCASE, &credits[i][1]); + y += 20<>FRACBITS > -(10 + vidydiff)) + V_DrawRightAlignedThinString(248 - vidxdiff, y >> FRACBITS, V_ALLOWLOWERCASE, credits[i]); + y += 20<>FRACBITS) * vid.dupy) > vid.height) + break; + } + } + else if (dscredits.creditsscene > 7) + { + y = ((BASEVIDHEIGHT + vidydiff) << FRACBITS) - + FixedMul(blancredits_height + (h << FRACBITS), std::min(std::max(blancredits_start, elapsed) - blancredits_start, blancredits_end) / blancredits_dividend); + + // Draw credits text on top + for (i = 0; blancredits[i]; i++) + { + switch(blancredits[i][0]) + { + case 0: + y += 50<>FRACBITS > -(10 + vidydiff)) + V_DrawString(8 - vidxdiff, y >> FRACBITS, V_ORANGEMAP, &blancredits[i][1]); + y += 16<>FRACBITS > -(10 + vidydiff)) + V_DrawRightAlignedThinString(248 - vidxdiff, y >> FRACBITS, V_ALLOWLOWERCASE, blancredits[i]); + y += 20<>FRACBITS) * vid.dupy) > vid.height) + break; + } + } + + /*char debug[256]; + + sprintf(debug,"Tics: %d\n",static_cast(elapsed / MICROSEC_TICS)); + + V_DrawRightAlignedThinString(-vidxdiff + w, -vidydiff, V_ALLOWLOWERCASE, debug);*/ +} + +#undef CREDSKINCOUNT + +} diff --git a/src/f_dscredits.hpp b/src/f_dscredits.hpp new file mode 100644 index 000000000..562fbeccc --- /dev/null +++ b/src/f_dscredits.hpp @@ -0,0 +1,74 @@ +// BLANKART +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. +// Copyright (C) 2025 by "Anonimus". +// Copyright (C) 2025 Blankart Team. +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file k_dscredits.hpp +/// \brief (Mostly) secret MKDS credits :) + +#ifndef __F_DSCREDITS__ +#define __F_DSCREDITS__ + +#include "doomdef.h" +#include "m_fixed.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define MAXDSOBJECTS (MAXPLAYERS + (48 - MAXPLAYERS)) +#define DSOBJOFFSCREEN 48 + +#define FRAMETOSEC (FRACUNIT / TICRATE) + +#define CREDITSDELAY (TICRATE / 2) + +// "Special" players, such as us, our rival, and the various unique characters in Act 2. +#define DSUNIQUEPLAYERS 3 + +extern longtic_t creditstimings[]; + +typedef struct dscredits_s { + longtic_t ticker; + tic_t delaytoplayer; + tic_t delaytobg; + UINT16 creditsscene; // "Scene" number of credits, iterated by 1 for each timing. + struct dscreditsobj_s + { + boolean active; + boolean player; // Are we a player? + UINT16 skin; + UINT16 color; + patch_t *patch; // Patch. + INT32 position[2]; // Position + fixed_t scale; // Scale + INT32 speed[2]; // Speed + + INT64 movestop; // Stops our movement after + INT64 movestart; // Restarts our movement + INT32 movestartspd[2]; // Speed to apply on movestart + + UINT8 glance; // They look at one another as they drive + boolean spinout; // Spinning out? + } creditsobj[MAXDSOBJECTS + DSUNIQUEPLAYERS]; + +} dscredits_t; + +extern dscredits_t dscredits; + +extern tic_t dscreditsticks; + +void F_SecretCreditsTicker(void); +void F_SecretCreditsDrawer(void); + +#ifdef __cplusplus +} +#endif + +#endif // __F_DSCREDITS__ \ No newline at end of file diff --git a/src/f_finale.c b/src/f_finale.c index ab0ace66b..ea07d9cc5 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -875,7 +875,7 @@ boolean F_CreditResponder(event_t *event) // BLANCREDITS // ============ -static const char *blancredits[] = { +const char *blancredits[] = { "\1BlanKart", "\1Credits", "", @@ -1009,6 +1009,33 @@ void F_BlanStartCredits(void) timetonext = 2*TICRATE; } +void F_StartSecretCredits(void) +{ + G_SetGamestate(GS_SECRETCREDITS); + + // Just in case they're open ... somehow + M_ClearMenus(true); + + if (creditscutscene) + { + F_StartCustomCutscene(creditscutscene - 1, false, false); + return; + } + + gameaction = ga_nothing; + paused = false; + CON_ToggleOff(); + S_StopMusic(); + S_StopSounds(); + + //S_ChangeMusicInternal("BLNCDS", true); + //S_ShowMusicCredit(0, 5*TICRATE, 0); + + finalecount = 0; + animtimer = 0; + timetonext = 2*TICRATE; +} + static void F_DrawDiagCubes(void) { INT32 j; diff --git a/src/f_finale.h b/src/f_finale.h index 471b74568..7abbfe430 100644 --- a/src/f_finale.h +++ b/src/f_finale.h @@ -76,6 +76,7 @@ void F_StartTitleScreen(void); void F_CacheTitleScreen(void); void F_StartCredits(void); void F_BlanStartCredits(void); +void F_StartSecretCredits(void); extern INT32 finalecount; extern INT32 titlescrollxspeed; @@ -100,7 +101,7 @@ extern INT16 tty; extern INT16 ttloop; extern UINT16 tttics; extern boolean ttavailable[6]; - +extern const char *blancredits[]; typedef enum { diff --git a/src/g_game.c b/src/g_game.c index f7e6581f1..e1d972089 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -64,6 +64,7 @@ #include "acs/interface.h" #include "k_director.h" #include "g_party.h" +#include "f_dscredits.hpp" #ifdef HAVE_DISCORDRPC #include "discord.h" @@ -1626,6 +1627,24 @@ boolean G_Responder(event_t *ev) return true; } } + else if (gamestate == GS_SECRETCREDITS) + { + if (HU_Responder(ev)) + { + hu_keystrokes = true; + return true; // chat ate the event + } + + if (F_CreditResponder(ev)) + { + // Skip credits for everyone + if (! netgame) + F_StartGameEvaluation(); + else if (server || IsPlayerAdmin(consoleplayer)) + SendNetXCmd(XD_EXITLEVEL, NULL, 0); + return true; + } + } else if (gamestate == GS_INTERMISSION || gamestate == GS_VOTING || gamestate == GS_EVALUATION) if (HU_Responder(ev)) { @@ -2260,6 +2279,12 @@ void G_Ticker(boolean run) HU_Ticker(); break; + case GS_SECRETCREDITS: + if (run) + F_SecretCreditsTicker(); + HU_Ticker(); + break; + case GS_TITLESCREEN: if (titlemapinaction) P_Ticker(run); @@ -3292,7 +3317,7 @@ void G_FinishExitLevel(void) // Don't save demos immediately here! Let standings write first } - else if (gamestate == GS_CREDITS || gamestate == GS_BLANCREDITS) + else if (gamestate == GS_CREDITS || gamestate == GS_BLANCREDITS || gamestate == GS_SECRETCREDITS) { F_StartGameEvaluation(); } diff --git a/src/g_state.h b/src/g_state.h index 678ba0a5d..ba872cca7 100644 --- a/src/g_state.h +++ b/src/g_state.h @@ -46,6 +46,7 @@ typedef enum // New GS_BLANCREDITS, // BlanKart: Credits for BlanKart + GS_SECRETCREDITS, // BlanKart: Secret/full credits } gamestate_t; typedef enum diff --git a/src/hu_stuff.c b/src/hu_stuff.c index 80ffaa8c3..7f9819e5a 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -2118,7 +2118,7 @@ void HU_Drawer(void) || gamestate == GS_INTERMISSION || gamestate == GS_CUTSCENE || gamestate == GS_CREDITS || gamestate == GS_EVALUATION || gamestate == GS_VOTING || gamestate == GS_WAITINGPLAYERS - || gamestate == GS_BLANCREDITS + || gamestate == GS_BLANCREDITS || gamestate == GS_SECRETCREDITS ) // SRB2kart return; diff --git a/src/info/menus.h b/src/info/menus.h index 8750c1235..54d559f03 100644 --- a/src/info/menus.h +++ b/src/info/menus.h @@ -62,6 +62,7 @@ _(OP_DISCORD) #endif _(OP_CUSTOM) +_(OP_EXTRACREDITS) // Extras _(SR_MAIN) diff --git a/src/m_menu.c b/src/m_menu.c index 9a4211920..998665711 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -52,6 +52,7 @@ #include "p_local.h" #include "p_setup.h" #include "f_finale.h" +#include "f_dscredits.hpp" // BlanKart: Secret credits #ifdef HWRENDER #include "hardware/hw_main.h" @@ -1302,7 +1303,7 @@ boolean M_Responder(event_t *ev) if (dedicated || (demo.playback && demo.title) || gamestate == GS_INTRO || gamestate == GS_CUTSCENE || gamestate == GS_CREDITS || gamestate == GS_EVALUATION - || gamestate == GS_BLANCREDITS + || gamestate == GS_BLANCREDITS || gamestate == GS_SECRETCREDITS ) return false; @@ -4657,7 +4658,10 @@ INT32 MR_Options(INT32 choice) // no credits or data erasing in-game/during replays M_SetItemDisabled(MN_OP_MAIN, "KARTCREDITS", gamecheck); - M_SetItemDisabled(MN_OP_MAIN, "BLANCREDITS", gamecheck); + M_SetItemDisabled(MN_OP_MAIN, "EXTRAREDITS", gamecheck); + //M_SetItemDisabled(MN_OP_MAIN, "BLANCREDITS", gamecheck); + //M_SetItemDisabled(MN_OP_MAIN, "SECRETCREDS", gamecheck); + M_SetItemDisabled(MN_OP_DATA, "ERASE", gamecheck); M_SetItemSecret(MN_OP_GAME, "ENCORE", !M_SecretUnlocked(SECRET_ENCORE)); @@ -5177,6 +5181,18 @@ INT32 MR_BlanCredits(INT32 choice) return true; } +INT32 MR_SecretCredits(INT32 choice) +{ + // Reset ticks here before anything else. + dscreditsticks = 0; + + (void)choice; + cursaveslot = -2; + M_ClearMenus(true); + F_StartSecretCredits(); + return true; +} + // ================== // SINGLE PLAYER MENU // ================== diff --git a/src/m_menu.h b/src/m_menu.h index 6ff014c0b..eebc36c86 100644 --- a/src/m_menu.h +++ b/src/m_menu.h @@ -293,6 +293,7 @@ INT32 MR_AddonsOptions(INT32 choice); INT32 MR_EraseData(INT32 arg); INT32 MR_Credits(INT32 choice); INT32 MR_BlanCredits(INT32 choice); +INT32 MR_SecretCredits(INT32 choice); INT32 MR_HandleAddons(INT32 choice); INT32 MR_SelectableClearMenus(INT32 choice); INT32 MR_GoBack(INT32 choice);