RingRacers/src/menus/transient/cup-select.c
toaster 71e1179030 M_MenuToLevelPreamble: Seriously clean up the process of starting a course from the menu
A lot of messy, copypasted boilerplate has been bundled together into a single function.
Programmers can now fire up a Match Race, the most basic type of gameplay, from menu code in only four steps (other modes take a little more attention):
- **M_MenuToLevelPreamble(UINT8 - splitscreen players #, boolean - false to extend wipe/true for short wipe)**
- set restoremenu
- D_MapChange(...)
- M_ClearMenus(...)
Includes the following fixes:
- Encore no longer has over-long wipes when started from menu, only standard-length
- "Boss Intro" and Encore start-of-round sounds will always play, even if no Title Card is drawn
- No long wipe when restarting a Time Attack run
- Auto Encore and Auto Gamespeed are no longer accidentially forced if you've manually changed them to Off and Gear 2 before starting netgame
2025-06-01 00:05:45 +01:00

336 lines
7.3 KiB
C

// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour.
// Copyright (C) 2025 by Vivian "toastergrl" Grannell.
// Copyright (C) 2025 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 menus/transient/cup-select.c
/// \brief Cup Select
#include "../../i_time.h"
#include "../../k_menu.h"
#include "../../s_sound.h"
#include "../../f_finale.h" // F_WipeStartScreen
#include "../../v_video.h"
#include "../../k_grandprix.h"
#include "../../r_local.h" // SplitScreen_OnChange
#include "../../k_podium.h" // K_StartCeremony
#include "../../m_misc.h" // FIL_FileExists
#include "../../d_main.h" // D_ClearState
menuitem_t PLAY_CupSelect[] =
{
{IT_NOTHING | IT_KEYHANDLER, NULL, NULL, NULL, {.routine = M_CupSelectHandler}, 0, 0},
};
menu_t PLAY_CupSelectDef = {
sizeof(PLAY_CupSelect) / sizeof(menuitem_t),
&PLAY_RaceGamemodesDef,
0,
PLAY_CupSelect,
0, 0,
0, 0,
0,
NULL,
2, 5,
M_DrawCupSelect,
NULL,
M_CupSelectTick,
NULL,
NULL,
NULL
};
struct cupgrid_s cupgrid;
static void M_StartCup(UINT8 entry)
{
UINT8 ssplayers = cv_splitplayers.value-1;
if (ssplayers > 0)
{
// Splitscreen is not accomodated with this recovery feature.
entry = UINT8_MAX;
}
M_MenuToLevelPreamble(ssplayers, false);
if (entry == UINT8_MAX)
{
entry = 0;
memset(&grandprixinfo, 0, sizeof(struct grandprixinfo));
// read our dummy cvars
grandprixinfo.gamespeed = min(KARTSPEED_HARD, cv_dummygpdifficulty.value);
grandprixinfo.masterbots = (cv_dummygpdifficulty.value == 3);
grandprixinfo.gp = true;
grandprixinfo.initalize = true;
grandprixinfo.cup = levellist.levelsearch.cup;
// Populate the roundqueue
memset(&roundqueue, 0, sizeof(struct roundqueue));
G_GPCupIntoRoundQueue(levellist.levelsearch.cup, levellist.newgametype,
#if 0 // TODO: encore GP
(boolean)cv_dummygpencore.value
#else
false
#endif
);
roundqueue.position = roundqueue.roundnum = 1;
roundqueue.netcommunicate = true; // relevant for future Online GP
}
else
{
// Silently change player setup
{
CV_StealthSetValue(&cv_playercolor[0], savedata.skincolor);
// follower
if (savedata.followerskin < 0 || savedata.followerskin >= numfollowers)
CV_StealthSet(&cv_follower[0], "None");
else
CV_StealthSet(&cv_follower[0], followers[savedata.followerskin].name);
// finally, call the skin[x] console command.
// This will call SendNameAndColor which will synch everything we sent here and apply the changes!
CV_StealthSet(&cv_skin[0], skins[savedata.skin].name);
// ...actually, let's do this last - Skin_OnChange has some return-early occasions
// follower color
CV_SetValue(&cv_followercolor[0], savedata.followercolor);
}
// Skip Bonus rounds.
if (roundqueue.entries[entry].gametype != GT_RACE // roundqueue.entries[0].gametype
&& roundqueue.entries[entry].rankrestricted == false)
{
G_GetNextMap(); // updates position in the roundqueue
entry = roundqueue.position-1;
}
}
M_ClearMenus(true);
restoreMenu = &PLAY_CupSelectDef;
restorelevellist = levellist;
if (entry < roundqueue.size)
{
D_MapChange(
roundqueue.entries[entry].mapnum + 1,
roundqueue.entries[entry].gametype,
roundqueue.entries[entry].encore,
true,
1,
false,
roundqueue.entries[entry].rankrestricted
);
}
else if (entry == 0)
{
I_Error("M_StartCup: roundqueue is empty on startup!!");
}
else
{
if (K_StartCeremony() == false)
{
// Accomodate our buffoonery
D_ClearState();
M_StartControlPanel();
M_StartMessage(
"Grand Prix Backup",
"The session is concluded!\n"
"You exited a final Bonus Round,\n"
"and the Podium failed to load.\n",
NULL, MM_NOTHING, NULL, NULL
);
if (FIL_FileExists(gpbackup))
remove(gpbackup);
return;
}
}
}
static void M_GPBackup(INT32 choice)
{
if (choice == MA_YES)
{
G_LoadGame();
if (savedata.lives != 0)
{
M_StartCup(roundqueue.position-1);
}
return;
}
M_StartCup(UINT8_MAX);
}
void M_CupSelectHandler(INT32 choice)
{
const UINT8 pid = 0;
(void)choice;
if (menucmd[pid].dpad_lr > 0)
{
cupgrid.x++;
if (cupgrid.x >= CUPMENU_COLUMNS)
{
cupgrid.x = 0;
cupgrid.pageno++;
if (cupgrid.pageno >= cupgrid.numpages)
cupgrid.pageno = 0;
}
cupgrid.xslide.start = I_GetTime();
cupgrid.xslide.dist = 42;
S_StartSound(NULL, sfx_s3k5b);
M_SetMenuDelay(pid);
}
else if (menucmd[pid].dpad_lr < 0)
{
cupgrid.x--;
if (cupgrid.x < 0)
{
cupgrid.x = CUPMENU_COLUMNS-1;
if (cupgrid.pageno == 0)
cupgrid.pageno = cupgrid.numpages-1;
else
cupgrid.pageno--;
}
cupgrid.xslide.start = I_GetTime();
cupgrid.xslide.dist = -42;
S_StartSound(NULL, sfx_s3k5b);
M_SetMenuDelay(pid);
}
if (cupgrid.cache_secondrowlocked == true)
; // No up/down for you!
else if (menucmd[pid].dpad_ud > 0)
{
cupgrid.y++;
if (cupgrid.y >= CUPMENU_ROWS)
{
cupgrid.y = 0;
cupgrid.yslide.dist = 8;
}
else
cupgrid.yslide.dist = 44;
cupgrid.yslide.start = I_GetTime();
S_StartSound(NULL, sfx_s3k5b);
M_SetMenuDelay(pid);
}
else if (menucmd[pid].dpad_ud < 0)
{
cupgrid.y--;
if (cupgrid.y < 0)
{
cupgrid.y = CUPMENU_ROWS-1;
cupgrid.yslide.dist = -8;
}
else
cupgrid.yslide.dist = -44;
cupgrid.yslide.start = I_GetTime();
S_StartSound(NULL, sfx_s3k5b);
M_SetMenuDelay(pid);
}
if (M_MenuConfirmPressed(pid) /*|| M_MenuButtonPressed(pid, MBT_START)*/)
{
UINT16 count;
cupheader_t *newcup = cupgrid.builtgrid[CUPMENU_CURSORID];
cupheader_t *oldcup = levellist.levelsearch.cup;
M_SetMenuDelay(pid);
if (!newcup)
{
S_StartSound(NULL, sfx_s3kb2);
return;
}
levellist.levelsearch.cup = newcup;
count = M_CountLevelsToShowInList(&levellist.levelsearch);
if (count == 0
|| (
levellist.levelsearch.grandprix == true
&& newcup->cachedlevels[0] == NEXTMAP_INVALID
)
)
{
S_StartSound(NULL, sfx_s3kb2);
return;
}
if (levellist.levelsearch.grandprix == true)
{
if (newcup == cupsavedata.cup
&& FIL_FileExists(gpbackup))
{
M_StartMessage(
"Grand Prix Backup",
"A progress backup was found.\n"
"Do you want to resurrect your\n"
"last Grand Prix session?\n",
M_GPBackup,
MM_YESNO,
"Yes, let's try again",
"No, start from Round 1"
);
return;
}
M_StartCup(UINT8_MAX);
}
else if (count == 1 && levellist.levelsearch.timeattack == true)
{
currentMenu->transitionID = PLAY_TimeAttackDef.transitionID+1;
M_LevelSelected(0, true);
}
else
{
currentMenu->transitionID = PLAY_LevelSelectDef.transitionID;
// Keep cursor position if you select the same cup again, reset if it's a different cup
if (oldcup != newcup || levellist.cursor >= count)
{
levellist.cursor = 0;
}
levellist.mapcount = count;
M_LevelSelectScrollDest();
levellist.slide.start = 0;
M_SetupNextMenu(&PLAY_LevelSelectDef, false);
S_StartSound(NULL, sfx_s3k63);
}
}
else if (M_MenuBackPressed(pid))
{
M_SetMenuDelay(pid);
if (currentMenu->prevMenu)
M_SetupNextMenu(currentMenu->prevMenu, false);
else
M_ClearMenus(true);
}
}
void M_CupSelectTick(void)
{
cupgrid.previewanim++;
}