mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2025-10-30 08:01:28 +00:00
- Remove CV_NOSHOWHELP, so the config file can load - Do not stealth set the cvar, so it is not dependent on order of operations of config loading and challenge loading
1185 lines
24 KiB
C++
1185 lines
24 KiB
C++
// DR. ROBOTNIK'S RING RACERS
|
|
//-----------------------------------------------------------------------------
|
|
// Copyright (C) by Sally "TehRealSalt" Cochenour
|
|
// Copyright (C) by Kart Krew
|
|
//
|
|
// 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_credits.cpp
|
|
/// \brief Credits sequence
|
|
|
|
#include "k_credits.h"
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
#include <fmt/format.h>
|
|
#include <nlohmann/json.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_joy.h"
|
|
#include "i_threads.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"
|
|
|
|
#include "lua_hud.h"
|
|
#include "lua_hook.h"
|
|
|
|
// SRB2Kart
|
|
#include "k_menu.h"
|
|
#include "k_grandprix.h"
|
|
#include "music.h"
|
|
#include "r_main.h"
|
|
#include "m_easing.h"
|
|
|
|
using nlohmann::json;
|
|
|
|
enum credits_slide_types_e
|
|
{
|
|
CRED_TYPE_SCROLL,
|
|
CRED_TYPE_SLIDE,
|
|
CRED_TYPE_TITLEDROP,
|
|
CRED_TYPE_TYLER52,
|
|
CRED_TYPE_KARTKREW,
|
|
CRED_TYPE_SONGS,
|
|
CRED_TYPE__MAX
|
|
};
|
|
|
|
struct credits_slide_s
|
|
{
|
|
credits_slide_types_e type;
|
|
std::string label;
|
|
std::vector<std::string> strings;
|
|
size_t strings_height;
|
|
boolean play_demo_afterwards;
|
|
};
|
|
|
|
static std::vector<struct credits_slide_s> g_credits_slides;
|
|
|
|
struct credits_star_s
|
|
{
|
|
fixed_t x, y;
|
|
fixed_t vel_x, vel_y;
|
|
INT32 frame;
|
|
};
|
|
|
|
static struct credits_s
|
|
{
|
|
size_t current_slide;
|
|
std::vector<UINT16> demo_maps;
|
|
boolean skip;
|
|
|
|
std::vector<struct credits_star_s> stars;
|
|
|
|
fixed_t transition;
|
|
fixed_t transition_prev;
|
|
boolean transition_reverse;
|
|
|
|
tic_t animation_timer;
|
|
|
|
std::vector<std::vector<std::string>> split_slide_strings;
|
|
size_t split_slide_id;
|
|
tic_t split_slide_delay;
|
|
|
|
UINT64 scroll_timer;
|
|
UINT64 scroll_timer_prev;
|
|
|
|
int tyler_fade;
|
|
|
|
tic_t finish_counter;
|
|
tic_t input_delay;
|
|
|
|
tic_t demo_exit;
|
|
|
|
boolean havent_ticked;
|
|
} g_credits;
|
|
|
|
constexpr const fixed_t kScrollFactor = FRACUNIT * 7 / 8;
|
|
constexpr const int kSkipSpeed = 8;
|
|
constexpr const int kScrollSkipSpeed = 4;
|
|
|
|
void F_LoadCreditsDefinitions(void)
|
|
{
|
|
// Load credits definitions from bios.pk3
|
|
if (g_credits_slides.empty() == false)
|
|
{
|
|
// TODO: Allow custom credits definition files.
|
|
// Either append the new data to the start or the end. Not sure which would be better.
|
|
return;
|
|
}
|
|
|
|
lumpnum_t credits_lump_id = W_GetNumForLongName("credits_def");
|
|
size_t credits_lump_len = W_LumpLength(credits_lump_id);
|
|
const char *credits_lump = static_cast<const char *>( W_CacheLumpNum(credits_lump_id, PU_CACHE) );
|
|
|
|
json credits_array = json::parse(credits_lump, credits_lump + credits_lump_len);
|
|
if (credits_array.is_array() == false)
|
|
{
|
|
I_Error("credits_def parse error: Not a JSON array");
|
|
return;
|
|
}
|
|
|
|
if (credits_array.size() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
for (json& slide_obj : credits_array)
|
|
{
|
|
struct credits_slide_s slide;
|
|
|
|
std::string type_str = slide_obj.value("type", "scroll");
|
|
|
|
if (type_str == "scroll")
|
|
{
|
|
slide.type = CRED_TYPE_SCROLL;
|
|
}
|
|
else if (type_str == "slide")
|
|
{
|
|
slide.type = CRED_TYPE_SLIDE;
|
|
}
|
|
else if (type_str == "titledrop")
|
|
{
|
|
slide.type = CRED_TYPE_TITLEDROP;
|
|
}
|
|
else if (type_str == "tyler52")
|
|
{
|
|
slide.type = CRED_TYPE_TYLER52;
|
|
}
|
|
else if (type_str == "kartkrew")
|
|
{
|
|
slide.type = CRED_TYPE_KARTKREW;
|
|
}
|
|
else if (type_str == "songs")
|
|
{
|
|
#if 0
|
|
slide.type = CRED_TYPE_SONGS;
|
|
#else
|
|
// TODO
|
|
continue;
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
throw std::runtime_error("unexpected type name '" + type_str + "'");
|
|
}
|
|
|
|
slide.label = slide_obj.value("label", "");
|
|
|
|
slide.strings_height = 0;
|
|
|
|
if (slide_obj.contains("strings"))
|
|
{
|
|
json strings_array = slide_obj.at("strings");
|
|
if (strings_array.is_array() == true)
|
|
{
|
|
for (size_t i = 0; i < strings_array.size(); i++)
|
|
{
|
|
slide.strings.push_back( strings_array.at(i) );
|
|
|
|
if (slide.type == CRED_TYPE_SCROLL)
|
|
{
|
|
if (slide.strings[i].empty())
|
|
{
|
|
slide.strings_height += 40;
|
|
}
|
|
else
|
|
{
|
|
if (slide.strings[i].at(0) == '*')
|
|
{
|
|
slide.strings_height += 30;
|
|
}
|
|
else
|
|
{
|
|
slide.strings_height += 12;
|
|
}
|
|
}
|
|
}
|
|
else if (slide.type == CRED_TYPE_SLIDE)
|
|
{
|
|
slide.strings_height += 30;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
slide.play_demo_afterwards = slide_obj.value("demo", false);
|
|
|
|
g_credits_slides.push_back( slide );
|
|
}
|
|
}
|
|
catch (const std::exception& ex)
|
|
{
|
|
I_Error("credits_def parse error: %s", ex.what());
|
|
}
|
|
}
|
|
|
|
void F_CreditsReset(void)
|
|
{
|
|
g_credits.stars.clear();
|
|
g_credits.split_slide_strings.clear();
|
|
g_credits.split_slide_id = 0;
|
|
g_credits.split_slide_delay = 0;
|
|
|
|
g_credits.transition = g_credits.transition_prev = 0;
|
|
g_credits.transition_reverse = false;
|
|
|
|
g_credits.scroll_timer = g_credits.scroll_timer_prev = 0;
|
|
g_credits.animation_timer = 0;
|
|
g_credits.tyler_fade = 0;
|
|
|
|
g_credits.finish_counter = 0;
|
|
g_credits.demo_exit = 0;
|
|
|
|
g_credits.havent_ticked = true; // fucking stupid bullshit
|
|
}
|
|
|
|
static void F_InitCreditsSlide(void)
|
|
{
|
|
struct credits_slide_s *slide = &g_credits_slides[ g_credits.current_slide ];
|
|
|
|
if (slide->type == CRED_TYPE_SLIDE)
|
|
{
|
|
// How many can be shown on one screen
|
|
constexpr const size_t kMaxSlideStrings = 4;
|
|
const size_t num_strings = slide->strings.size();
|
|
|
|
if (num_strings <= kMaxSlideStrings)
|
|
{
|
|
// Our job is easy. Simply copy what is already there.
|
|
g_credits.split_slide_strings.push_back( slide->strings );
|
|
}
|
|
else
|
|
{
|
|
// Try to divide it up relatively evenly into multiple sub-slides.
|
|
const size_t num_sub_screens = (num_strings - 1) / kMaxSlideStrings + 1;
|
|
size_t max_strings_per_screen = (num_strings - 1) / num_sub_screens + 1;
|
|
|
|
size_t str_id = 0;
|
|
std::vector<std::string> screen_strings;
|
|
|
|
if (max_strings_per_screen == kMaxSlideStrings
|
|
&& num_strings % kMaxSlideStrings == 1)
|
|
{
|
|
// 13 strings is unlucky and creates a page
|
|
// that only has one name on the end.
|
|
// This fixes it.
|
|
screen_strings.emplace_back( slide->strings[str_id] );
|
|
str_id++;
|
|
max_strings_per_screen--;
|
|
}
|
|
|
|
while (str_id < num_strings)
|
|
{
|
|
for (size_t i = 0; i < max_strings_per_screen; i++)
|
|
{
|
|
screen_strings.emplace_back( slide->strings[str_id] );
|
|
str_id++;
|
|
|
|
if (str_id >= num_strings)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
g_credits.split_slide_strings.push_back( screen_strings );
|
|
screen_strings.clear();
|
|
}
|
|
}
|
|
}
|
|
#if 0
|
|
else if (slide->type == CRED_TYPE_SONGS)
|
|
{
|
|
// Auto fill out with music credits
|
|
slide->strings.clear();
|
|
|
|
slide->strings.push_back("*MUSIC");
|
|
slide->strings_height += 30;
|
|
|
|
// I do not even remotely understand the sequence code even a little bit
|
|
// so SOMEONE ELSE can do it! I don't care!
|
|
// ---
|
|
// "I know how it work's" - toast 110124
|
|
musicdef_t *def = soundtest.sequence.next;
|
|
while (def)
|
|
{
|
|
if (def->title
|
|
#if 0 // Let's not make the credits variable-length
|
|
&& !S_SoundTestDefLocked(def)
|
|
#endif
|
|
)
|
|
{
|
|
slide->strings.push_back("#" + std::string(def->title));
|
|
slide->strings_height += 12;
|
|
|
|
if (def->author)
|
|
{
|
|
slide->strings.push_back("by " + std::string(def->author));
|
|
slide->strings_height += 12;
|
|
}
|
|
|
|
if (def->source)
|
|
{
|
|
slide->strings.push_back("from " + std::string(def->source));
|
|
slide->strings_height += 12;
|
|
}
|
|
|
|
if (def->composers)
|
|
{
|
|
slide->strings.push_back("originally by " + std::string(def->composers));
|
|
slide->strings_height += 12;
|
|
}
|
|
|
|
slide->strings.push_back(" ");
|
|
slide->strings_height += 12;
|
|
}
|
|
|
|
def = def->sequence.next;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Clear the console hud just to avoid anything getting in the way.
|
|
CON_ClearHUD();
|
|
}
|
|
|
|
void F_StartCredits(void)
|
|
{
|
|
G_SetGamestate(GS_CREDITS);
|
|
|
|
// Just in case they're open ... somehow
|
|
M_ClearMenus(true);
|
|
|
|
if (g_credits_cutscene)
|
|
{
|
|
F_StartCustomCutscene(g_credits_cutscene - 1, false, false);
|
|
return;
|
|
}
|
|
|
|
gameaction = ga_nothing;
|
|
paused = false;
|
|
|
|
CON_ToggleOff();
|
|
Music_StopAll();
|
|
S_StopSounds();
|
|
|
|
Music_Play("credits");
|
|
|
|
F_CreditsReset();
|
|
|
|
g_credits.demo_maps.clear();
|
|
g_credits.current_slide = 0;
|
|
|
|
g_credits.input_delay = TICRATE;
|
|
g_credits.skip = false;
|
|
|
|
F_InitCreditsSlide();
|
|
}
|
|
|
|
static void F_CreditsNextSlide(void)
|
|
{
|
|
F_CreditsReset();
|
|
|
|
g_credits.current_slide++;
|
|
if (g_credits.current_slide >= g_credits_slides.size())
|
|
{
|
|
// You watched all the credits? What a trooper!
|
|
gamedata->everfinishedcredits = true;
|
|
|
|
if (M_UpdateUnlockablesAndExtraEmblems(true, true))
|
|
{
|
|
G_SaveGameData();
|
|
}
|
|
|
|
F_StartGameEvaluation();
|
|
return;
|
|
}
|
|
|
|
F_InitCreditsSlide();
|
|
}
|
|
|
|
void F_ContinueCredits(void)
|
|
{
|
|
G_SetGamestate(GS_CREDITS);
|
|
F_CreditsReset();
|
|
|
|
// Returning from playing a demo.
|
|
// Go to the next slide.
|
|
F_CreditsNextSlide();
|
|
}
|
|
|
|
static UINT16 F_PickRandomCreditsDemoMap(void)
|
|
{
|
|
std::vector<UINT16> allowedMaps;
|
|
|
|
for (INT32 i = 0; i < basenummapheaders; i++) // Only take from the base game.
|
|
{
|
|
if (mapheaderinfo[i] == NULL || mapheaderinfo[i]->lumpnum == LUMPERROR)
|
|
{
|
|
// Doesn't exist?
|
|
continue;
|
|
}
|
|
|
|
if ((mapheaderinfo[i]->typeoflevel & TOL_RACE) == 0)
|
|
{
|
|
// We want Race gametype demos, since they will be
|
|
// the most suited to the "camera left behind" effect.
|
|
continue;
|
|
}
|
|
|
|
if (mapheaderinfo[i]->ghostCount == 0)
|
|
{
|
|
// It doesn't have any demos...
|
|
continue;
|
|
}
|
|
|
|
if ((mapheaderinfo[i]->menuflags & LF2_HIDEINMENU) == LF2_HIDEINMENU)
|
|
{
|
|
// Secret map.
|
|
continue;
|
|
}
|
|
|
|
if (M_MapLocked(i + 1) == true)
|
|
{
|
|
// We haven't earned this one.
|
|
continue;
|
|
}
|
|
|
|
if (std::find(g_credits.demo_maps.begin(), g_credits.demo_maps.end(), i) != g_credits.demo_maps.end())
|
|
{
|
|
// Already was added.
|
|
continue;
|
|
}
|
|
|
|
// Got past the gauntlet, so we can allow this one.
|
|
allowedMaps.push_back(i);
|
|
}
|
|
|
|
if (allowedMaps.size() > 0)
|
|
{
|
|
return allowedMaps[ M_RandomKey(allowedMaps.size()) ];
|
|
}
|
|
|
|
return UINT16_MAX;
|
|
}
|
|
|
|
static boolean F_CreditsPlayDemo(void)
|
|
{
|
|
const struct credits_slide_s *slide = &g_credits_slides[ g_credits.current_slide ];
|
|
staffbrief_t *brief;
|
|
|
|
if (slide->play_demo_afterwards == false)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (g_credits.skip == true)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
UINT16 map_id = F_PickRandomCreditsDemoMap();
|
|
if (map_id == UINT16_MAX)
|
|
{
|
|
return false;
|
|
}
|
|
g_credits.demo_maps.push_back(map_id);
|
|
|
|
UINT8 ghost_id = M_RandomKey( mapheaderinfo[map_id]->ghostCount );
|
|
brief = mapheaderinfo[map_id]->ghostBrief[ghost_id];
|
|
std::string demo_name = static_cast<const char *>(W_CheckNameForNumPwad(brief->wad, brief->lump));
|
|
|
|
demo.attract = DEMO_ATTRACT_CREDITS;
|
|
demo.ignorefiles = true;
|
|
demo.loadfiles = false;
|
|
|
|
G_DoPlayDemo(demo_name.c_str());
|
|
|
|
g_fast_forward = 30 * TICRATE;
|
|
g_credits.demo_exit = 0;
|
|
return true;
|
|
}
|
|
|
|
constexpr const unsigned int kDemoExitTicCount = 10 * TICRATE;
|
|
|
|
void F_TickCreditsDemoExit(void)
|
|
{
|
|
g_credits.demo_exit++;
|
|
|
|
if (!menuactive && M_MenuConfirmPressed(0))
|
|
{
|
|
g_credits.demo_exit = std::max(g_credits.demo_exit, kDemoExitTicCount - 64);
|
|
}
|
|
|
|
if (g_credits.demo_exit > kDemoExitTicCount)
|
|
{
|
|
G_CheckDemoStatus();
|
|
}
|
|
}
|
|
|
|
INT32 F_CreditsDemoExitFade(void)
|
|
{
|
|
return std::clamp<INT32>(
|
|
31 - ((kDemoExitTicCount - static_cast<INT32>(g_credits.demo_exit)) / 2),
|
|
-1, 31
|
|
);
|
|
}
|
|
|
|
static void F_CreditsSlideFinish(void)
|
|
{
|
|
if (F_CreditsPlayDemo() == true)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (g_credits.skip == true)
|
|
{
|
|
// FIXME: use shorter wipe, instead of no wipe
|
|
wipegamestate = GS_CREDITS;
|
|
}
|
|
else
|
|
{
|
|
wipegamestate = GS_NULL;
|
|
}
|
|
|
|
F_CreditsNextSlide();
|
|
}
|
|
|
|
static boolean F_TickCreditsScroll(void)
|
|
{
|
|
const struct credits_slide_s *slide = &g_credits_slides[ g_credits.current_slide ];
|
|
UINT32 scroll_max = FixedDiv(slide->strings_height + BASEVIDHEIGHT, kScrollFactor);
|
|
|
|
if (g_credits.skip == true)
|
|
{
|
|
g_credits.scroll_timer += kScrollSkipSpeed;
|
|
}
|
|
else
|
|
{
|
|
g_credits.scroll_timer++;
|
|
}
|
|
|
|
if (g_credits.scroll_timer > scroll_max)
|
|
{
|
|
g_credits.scroll_timer = scroll_max;
|
|
}
|
|
|
|
return (g_credits.scroll_timer >= scroll_max - (2 * TICRATE));
|
|
}
|
|
|
|
static void F_CreditsStarParticle(fixed_t x, fixed_t y)
|
|
{
|
|
struct credits_star_s star;
|
|
|
|
star.x = x;
|
|
star.y = y;
|
|
star.frame = 0;
|
|
|
|
star.vel_x = M_RandomRange(-2, 2) * FRACUNIT / 4;
|
|
|
|
g_credits.stars.push_back(star);
|
|
}
|
|
|
|
static boolean F_TickCreditsSlide(void)
|
|
{
|
|
const struct credits_slide_s *slide = &g_credits_slides[ g_credits.current_slide ];
|
|
|
|
if (g_credits.split_slide_delay > 0)
|
|
{
|
|
g_credits.split_slide_delay--;
|
|
return false;
|
|
}
|
|
|
|
if (g_credits.transition < FRACUNIT)
|
|
{
|
|
g_credits.transition = std::min(g_credits.transition + (FRACUNIT / TICRATE), FRACUNIT);
|
|
|
|
if (g_credits.split_slide_id < g_credits.split_slide_strings.size())
|
|
{
|
|
constexpr const fixed_t label_space = 30 * FRACUNIT;
|
|
const fixed_t strings_height = g_credits.split_slide_strings[ g_credits.split_slide_id ].size() * 30 * FRACUNIT;
|
|
|
|
fixed_t y = 0;
|
|
if (slide->label.empty() == false)
|
|
{
|
|
y += label_space;
|
|
}
|
|
y += ((BASEVIDHEIGHT * FRACUNIT) - y) / 2;
|
|
y -= strings_height / 2;
|
|
|
|
UINT8 side = 0;
|
|
UINT8 star_index = 0;
|
|
|
|
for (auto& str : g_credits.split_slide_strings[ g_credits.split_slide_id ])
|
|
{
|
|
const fixed_t str_width = V_StringScaledWidth(
|
|
FRACUNIT, FRACUNIT, FRACUNIT,
|
|
0, LSLOW_FONT,
|
|
str.c_str()
|
|
);
|
|
|
|
fixed_t x = 32 * FRACUNIT;
|
|
fixed_t slide_out = -BASEVIDWIDTH * FRACUNIT;
|
|
|
|
if (side == 1)
|
|
{
|
|
x = (BASEVIDWIDTH * FRACUNIT) - x - str_width;
|
|
slide_out = -slide_out;
|
|
}
|
|
else
|
|
{
|
|
x += str_width;
|
|
}
|
|
|
|
fixed_t ease = 0;
|
|
if (g_credits.transition_reverse)
|
|
{
|
|
ease = Easing_InOutSine(g_credits.transition, 0, -slide_out);
|
|
}
|
|
else
|
|
{
|
|
ease = Easing_InOutSine(g_credits.transition, slide_out, 0);
|
|
}
|
|
|
|
if ((g_credits.animation_timer + star_index) % 2 == 0)
|
|
{
|
|
F_CreditsStarParticle(
|
|
x + ease,
|
|
y + (16 * FRACUNIT)
|
|
);
|
|
}
|
|
|
|
y += 30 * FRACUNIT;
|
|
side = (side + 1) & 1;
|
|
star_index++;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
if (g_credits.split_slide_id < g_credits.split_slide_strings.size() - 1)
|
|
{
|
|
if (g_credits.transition_reverse)
|
|
{
|
|
g_credits.split_slide_id++;
|
|
}
|
|
else
|
|
{
|
|
g_credits.split_slide_delay = 2*TICRATE;
|
|
}
|
|
|
|
g_credits.transition = g_credits.transition_prev = 0;
|
|
g_credits.transition_reverse = !g_credits.transition_reverse;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static boolean F_TickCreditsTyler52(void)
|
|
{
|
|
if (g_credits.animation_timer > TICRATE && g_credits.animation_timer < (2*TICRATE) - 17)
|
|
{
|
|
g_credits.tyler_fade++;
|
|
}
|
|
else
|
|
{
|
|
g_credits.tyler_fade--;
|
|
}
|
|
|
|
g_credits.tyler_fade = std::clamp(g_credits.tyler_fade, 0, 8);
|
|
return true;
|
|
}
|
|
|
|
static void F_TickCreditsStars(void)
|
|
{
|
|
for (auto& star : g_credits.stars)
|
|
{
|
|
star.vel_y += FRACUNIT / 4;
|
|
|
|
if (g_credits.animation_timer % 2 == 0)
|
|
{
|
|
star.frame++;
|
|
}
|
|
}
|
|
|
|
g_credits.stars.erase(
|
|
std::remove_if(
|
|
g_credits.stars.begin(),
|
|
g_credits.stars.end(),
|
|
[](struct credits_star_s const &star) { return star.frame > 11; }
|
|
),
|
|
g_credits.stars.end()
|
|
);
|
|
}
|
|
|
|
static void F_HandleCreditsTick(void)
|
|
{
|
|
const struct credits_slide_s *slide = &g_credits_slides[ g_credits.current_slide ];
|
|
|
|
g_credits.animation_timer++;
|
|
F_TickCreditsStars();
|
|
|
|
boolean finalize_slide = true;
|
|
switch (slide->type)
|
|
{
|
|
case CRED_TYPE_SCROLL:
|
|
{
|
|
finalize_slide = F_TickCreditsScroll();
|
|
break;
|
|
}
|
|
case CRED_TYPE_SLIDE:
|
|
{
|
|
finalize_slide = F_TickCreditsSlide();
|
|
break;
|
|
}
|
|
case CRED_TYPE_TYLER52:
|
|
{
|
|
finalize_slide = F_TickCreditsTyler52();
|
|
break;
|
|
}
|
|
case CRED_TYPE_SONGS:
|
|
{
|
|
// TODO
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (g_credits.finish_counter > 0)
|
|
{
|
|
g_credits.finish_counter--;
|
|
|
|
if (g_credits.finish_counter == 0)
|
|
{
|
|
F_CreditsSlideFinish();
|
|
}
|
|
|
|
return;
|
|
}
|
|
else if (finalize_slide)
|
|
{
|
|
if (g_credits.current_slide >= g_credits_slides.size() - 1)
|
|
{
|
|
g_credits.finish_counter = 5 * TICRATE;
|
|
}
|
|
else
|
|
{
|
|
g_credits.finish_counter = 2 * TICRATE;
|
|
}
|
|
}
|
|
}
|
|
|
|
void F_CreditTicker(void)
|
|
{
|
|
g_credits.havent_ticked = false;
|
|
|
|
g_credits.transition_prev = g_credits.transition;
|
|
g_credits.scroll_timer_prev = g_credits.scroll_timer;
|
|
|
|
if (g_credits.input_delay > 0)
|
|
{
|
|
g_credits.input_delay--;
|
|
}
|
|
else
|
|
{
|
|
g_credits.skip = (!menuactive && M_MenuConfirmHeld(0));
|
|
}
|
|
|
|
if (g_credits.current_slide >= g_credits_slides.size() - 1)
|
|
{
|
|
// Don't skip the last slide
|
|
g_credits.skip = false;
|
|
}
|
|
|
|
if (g_credits.skip == true)
|
|
{
|
|
for (size_t i = 0; i < kSkipSpeed; i++)
|
|
{
|
|
F_HandleCreditsTick();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
F_HandleCreditsTick();
|
|
}
|
|
}
|
|
|
|
static void F_DrawCreditsScroll(void)
|
|
{
|
|
const struct credits_slide_s *slide = &g_credits_slides[ g_credits.current_slide ];
|
|
|
|
fixed_t y = (BASEVIDHEIGHT * FRACUNIT);
|
|
|
|
y -= Easing_Linear(
|
|
rendertimefrac,
|
|
g_credits.scroll_timer_prev * kScrollFactor,
|
|
g_credits.scroll_timer * kScrollFactor
|
|
);
|
|
|
|
for (auto& str : slide->strings)
|
|
{
|
|
if (str.empty())
|
|
{
|
|
y += 40 * FRACUNIT;
|
|
}
|
|
else
|
|
{
|
|
std::string new_str = str;
|
|
|
|
if (new_str.at(0) == '*')
|
|
{
|
|
if (y > -20 * FRACUNIT)
|
|
{
|
|
new_str.erase(0, 1);
|
|
|
|
const fixed_t str_width = V_StringScaledWidth(
|
|
FRACUNIT, FRACUNIT, FRACUNIT,
|
|
0, LSLOW_FONT,
|
|
new_str.c_str()
|
|
);
|
|
|
|
V_DrawStringScaled(
|
|
((BASEVIDWIDTH * FRACUNIT) - str_width) / 2, y,
|
|
FRACUNIT, FRACUNIT, FRACUNIT,
|
|
0, nullptr, LSLOW_FONT,
|
|
new_str.c_str()
|
|
);
|
|
}
|
|
|
|
y += 30 * FRACUNIT;
|
|
}
|
|
else
|
|
{
|
|
if (y > -10 * FRACUNIT)
|
|
{
|
|
if (new_str.at(0) == '#')
|
|
{
|
|
new_str.erase(0, 1);
|
|
|
|
const fixed_t str_width = V_StringScaledWidth(
|
|
FRACUNIT, FRACUNIT, FRACUNIT,
|
|
0, MENU_FONT,
|
|
new_str.c_str()
|
|
);
|
|
|
|
V_DrawStringScaled(
|
|
((BASEVIDWIDTH * FRACUNIT) - str_width) / 2, y,
|
|
FRACUNIT, FRACUNIT, FRACUNIT,
|
|
V_YELLOWMAP, nullptr, MENU_FONT,
|
|
new_str.c_str()
|
|
);
|
|
}
|
|
else
|
|
{
|
|
V_DrawStringScaled(
|
|
32 * FRACUNIT, y,
|
|
FRACUNIT, FRACUNIT, FRACUNIT,
|
|
0, nullptr, MENU_FONT,
|
|
new_str.c_str()
|
|
);
|
|
}
|
|
}
|
|
|
|
y += 12 * FRACUNIT;
|
|
}
|
|
}
|
|
|
|
if (((y / FRACUNIT) * vid.dupy) > vid.height)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (slide->label.empty() == false)
|
|
{
|
|
const fixed_t label_width = V_StringScaledWidth(
|
|
FRACUNIT, FRACUNIT, FRACUNIT,
|
|
0, LSHI_FONT,
|
|
slide->label.c_str()
|
|
);
|
|
V_DrawStringScaled(
|
|
((BASEVIDWIDTH * FRACUNIT) - label_width) / 2, 15 * FRACUNIT,
|
|
FRACUNIT, FRACUNIT, FRACUNIT,
|
|
0, nullptr, LSHI_FONT,
|
|
slide->label.c_str()
|
|
);
|
|
}
|
|
}
|
|
|
|
static void F_DrawCreditsSlide(void)
|
|
{
|
|
const struct credits_slide_s *slide = &g_credits_slides[ g_credits.current_slide ];
|
|
constexpr const fixed_t label_space = 30 * FRACUNIT;
|
|
|
|
const fixed_t transition = Easing_Linear(rendertimefrac, g_credits.transition_prev, g_credits.transition);
|
|
|
|
const fixed_t label_width = V_StringScaledWidth(
|
|
FRACUNIT, FRACUNIT, FRACUNIT,
|
|
0, LSHI_FONT,
|
|
slide->label.c_str()
|
|
);
|
|
V_DrawStringScaled(
|
|
((BASEVIDWIDTH * FRACUNIT) - label_width) / 2, label_space / 2,
|
|
FRACUNIT, FRACUNIT, FRACUNIT,
|
|
0, nullptr, LSHI_FONT,
|
|
slide->label.c_str()
|
|
);
|
|
|
|
if (g_credits.split_slide_id >= g_credits.split_slide_strings.size())
|
|
{
|
|
return;
|
|
}
|
|
|
|
const std::vector<std::string> *slide_strings = &g_credits.split_slide_strings[ g_credits.split_slide_id ];
|
|
const fixed_t strings_height = slide_strings->size() * 30 * FRACUNIT;
|
|
|
|
fixed_t y = 0;
|
|
if (slide->label.empty() == false)
|
|
{
|
|
y += label_space;
|
|
}
|
|
y += ((BASEVIDHEIGHT * FRACUNIT) - y) / 2;
|
|
y -= strings_height / 2;
|
|
|
|
UINT8 side = 0;
|
|
|
|
for (auto& str : g_credits.split_slide_strings[ g_credits.split_slide_id ])
|
|
{
|
|
const fixed_t str_width = V_StringScaledWidth(
|
|
FRACUNIT, FRACUNIT, FRACUNIT,
|
|
0, LSLOW_FONT,
|
|
str.c_str()
|
|
);
|
|
|
|
fixed_t x = 32 * FRACUNIT;
|
|
fixed_t slide_out = -BASEVIDWIDTH * FRACUNIT;
|
|
|
|
if (side == 1)
|
|
{
|
|
x = (BASEVIDWIDTH * FRACUNIT) - x - str_width;
|
|
slide_out = -slide_out;
|
|
}
|
|
|
|
fixed_t ease = 0;
|
|
if (g_credits.transition_reverse)
|
|
{
|
|
ease = Easing_InOutSine(transition, 0, -slide_out);
|
|
}
|
|
else
|
|
{
|
|
ease = Easing_InOutSine(transition, slide_out, 0);
|
|
}
|
|
|
|
V_DrawStringScaled(
|
|
x + ease, y,
|
|
FRACUNIT, FRACUNIT, FRACUNIT,
|
|
0, nullptr, LSLOW_FONT,
|
|
str.c_str()
|
|
);
|
|
|
|
y += 30 * FRACUNIT;
|
|
side = (side + 1) & 1;
|
|
}
|
|
}
|
|
|
|
static void F_DrawCreditsTitleDrop(void)
|
|
{
|
|
const struct credits_slide_s *slide = &g_credits_slides[ g_credits.current_slide ];
|
|
|
|
V_DrawFixedPatch(
|
|
0, -BASEVIDHEIGHT * (FRACUNIT / 2),
|
|
FRACUNIT, 0,
|
|
static_cast<patch_t *>(W_CachePatchName(
|
|
(M_UseAlternateTitleScreen() ? "KTSJUMPR1" : "KTSBUMPR1"),
|
|
PU_CACHE
|
|
)),
|
|
nullptr
|
|
);
|
|
|
|
const fixed_t label_width = V_StringScaledWidth(
|
|
FRACUNIT, FRACUNIT, FRACUNIT,
|
|
0, LSHI_FONT,
|
|
slide->label.c_str()
|
|
);
|
|
V_DrawStringScaled(
|
|
((BASEVIDWIDTH * FRACUNIT) - label_width) / 2, 120 * FRACUNIT,
|
|
FRACUNIT, FRACUNIT, FRACUNIT,
|
|
0, nullptr, LSHI_FONT,
|
|
slide->label.c_str()
|
|
);
|
|
}
|
|
|
|
static void F_DrawCreditsTyler52(void)
|
|
{
|
|
patch_t *tyler_patch = static_cast<patch_t *>(W_CachePatchName("TYLER52", PU_CACHE));
|
|
V_DrawStretchyFixedPatch(
|
|
0, 0,
|
|
(BASEVIDWIDTH * FRACUNIT) / tyler_patch->width,
|
|
(BASEVIDHEIGHT * FRACUNIT) / tyler_patch->height,
|
|
V_90TRANS,
|
|
tyler_patch,
|
|
nullptr
|
|
);
|
|
|
|
if (g_credits.tyler_fade < 8)
|
|
{
|
|
V_DrawFadeScreen(0xFF00, 31 - (g_credits.tyler_fade * 4));
|
|
}
|
|
|
|
std::string memory_str = "In memory of";
|
|
const fixed_t memory_width = V_StringScaledWidth(
|
|
FRACUNIT, FRACUNIT, FRACUNIT,
|
|
0, LSLOW_FONT,
|
|
memory_str.c_str()
|
|
);
|
|
V_DrawStringScaled(
|
|
((BASEVIDWIDTH * FRACUNIT) - memory_width) / 2, 60 * FRACUNIT,
|
|
FRACUNIT, FRACUNIT, FRACUNIT,
|
|
0, nullptr, LSLOW_FONT,
|
|
memory_str.c_str()
|
|
);
|
|
|
|
std::string tyler_str = "Tyler52";
|
|
const fixed_t tyler_width = V_StringScaledWidth(
|
|
FRACUNIT, FRACUNIT, FRACUNIT,
|
|
0, LSHI_FONT,
|
|
tyler_str.c_str()
|
|
);
|
|
V_DrawStringScaled(
|
|
((BASEVIDWIDTH * FRACUNIT) - tyler_width) / 2, 110 * FRACUNIT,
|
|
FRACUNIT, FRACUNIT, FRACUNIT,
|
|
0, nullptr, LSHI_FONT,
|
|
tyler_str.c_str()
|
|
);
|
|
}
|
|
|
|
static void F_DrawCreditsKartKrew(void)
|
|
{
|
|
const struct credits_slide_s *slide = &g_credits_slides[ g_credits.current_slide ];
|
|
|
|
const fixed_t label_width = V_StringScaledWidth(
|
|
FRACUNIT, FRACUNIT, FRACUNIT,
|
|
0, LSLOW_FONT,
|
|
slide->label.c_str()
|
|
);
|
|
V_DrawStringScaled(
|
|
((BASEVIDWIDTH * FRACUNIT) - label_width) / 2, 40 * FRACUNIT,
|
|
FRACUNIT, FRACUNIT, FRACUNIT,
|
|
0, nullptr, LSLOW_FONT,
|
|
slide->label.c_str()
|
|
);
|
|
|
|
V_DrawFixedPatch(
|
|
116 * FRACUNIT, 70 * FRACUNIT,
|
|
FRACUNIT / 2, 0,
|
|
static_cast<patch_t *>(W_CachePatchName(
|
|
"KKLOGO_C",
|
|
PU_CACHE
|
|
)),
|
|
nullptr
|
|
);
|
|
|
|
V_DrawFixedPatch(
|
|
116 * FRACUNIT, 70 * FRACUNIT,
|
|
FRACUNIT / 2, 0,
|
|
static_cast<patch_t *>(W_CachePatchName(
|
|
"KKTEXT_C",
|
|
PU_CACHE
|
|
)),
|
|
nullptr
|
|
);
|
|
}
|
|
|
|
void F_CreditDrawer(void)
|
|
{
|
|
const struct credits_slide_s *slide = &g_credits_slides[ g_credits.current_slide ];
|
|
V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
|
|
|
|
switch (slide->type)
|
|
{
|
|
case CRED_TYPE_SCROLL:
|
|
{
|
|
F_DrawCreditsScroll();
|
|
break;
|
|
}
|
|
case CRED_TYPE_SLIDE:
|
|
{
|
|
F_DrawCreditsSlide();
|
|
break;
|
|
}
|
|
case CRED_TYPE_TITLEDROP:
|
|
{
|
|
F_DrawCreditsTitleDrop();
|
|
break;
|
|
}
|
|
case CRED_TYPE_TYLER52:
|
|
{
|
|
F_DrawCreditsTyler52();
|
|
break;
|
|
}
|
|
case CRED_TYPE_KARTKREW:
|
|
{
|
|
F_DrawCreditsKartKrew();
|
|
break;
|
|
}
|
|
case CRED_TYPE_SONGS:
|
|
{
|
|
// TODO
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
UINT8 *colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_JAWZ, GTC_CACHE);
|
|
for (auto& star : g_credits.stars)
|
|
{
|
|
V_DrawFixedPatch(
|
|
star.x, star.y,
|
|
FRACUNIT / 2, V_ADD,
|
|
static_cast<patch_t *>(W_CachePatchName(
|
|
va("KINB%c0", 'A' + star.frame),
|
|
PU_CACHE
|
|
)),
|
|
colormap
|
|
);
|
|
|
|
star.x += FixedMul(star.vel_x, renderdeltatics);
|
|
star.y += FixedMul(star.vel_y, renderdeltatics);
|
|
}
|
|
}
|