Add ability to queue all maps in a cup for netgames and local matches by pressing Action on cup select.

This commit is contained in:
Freaky Mutant Man 2025-10-20 19:24:16 +00:00 committed by Eidolon
parent 6fcfd452aa
commit 188d168ace
5 changed files with 355 additions and 65 deletions

View file

@ -81,6 +81,8 @@ extern struct menuqueue
UINT8 size;
UINT8 sending;
UINT8 anchor;
boolean clearing;
boolean cupqueue;
roundentry_t entries[ROUNDQUEUE_MAX];
} menuqueue;

View file

@ -961,6 +961,10 @@ void M_MenuToLevelPreamble(UINT8 ssplayers, boolean nowipe);
void M_LevelSelected(INT16 add, boolean menuupdate);
boolean M_LevelSelectCupSwitch(boolean next, boolean skipones);
void M_LevelConfirmHandler(void);
void M_ClearQueueHandler(void);
void M_CupQueueHandler(cupheader_t *cup);
// dummy consvars for GP & match race setup
extern consvar_t cv_dummygpdifficulty;
extern consvar_t cv_dummykartspeed;

View file

@ -3340,6 +3340,21 @@ void M_DrawCupSelect(void)
M_DrawCupPreview(y, &templevelsearch);
M_DrawCupTitle(120 - ty, &templevelsearch);
const char *worktext = "Undo";
if (menuqueue.size)
worktext = "Undo";
else if (roundqueue.size)
worktext = "Clear Queue";
if (levellist.canqueue)
{
K_DrawGameControl(BASEVIDWIDTH/2, 6-ty, 0, va("%s Queue Cup<white> %s %s",
(templevelsearch.cup && templevelsearch.cup != &dummy_lostandfound && !roundqueue.size) ? "<z_animated>" : "<z_pressed><gray>",
(roundqueue.size || menuqueue.size) ? "<c_animated>" : "<c_pressed><gray>",
worktext), 1, TINY_FONT, 0);
}
if (templevelsearch.grandprix == false && templevelsearch.cup != NULL)
{

View file

@ -287,6 +287,97 @@ static void M_GPBackup(INT32 choice)
M_StartCup(UINT8_MAX);
}
static boolean M_IsCupQueueable(cupheader_t *cup)
{
levelsearch_t templevelsearch = levellist.levelsearch; // copy levellist so we don't mess with stuff I think
UINT16 ShownCount = 0;
UINT16 CupCount = 0;
UINT32 CheckGametype[2] = {TOL_RACE,TOL_BATTLE};
templevelsearch.cup = cup;
UINT8 e, i = 0;
for (e = 0; e < 2; e++)
{
templevelsearch.typeoflevel = CheckGametype[e];
ShownCount += M_CountLevelsToShowInList(&templevelsearch);
}
//CONS_Printf(M_GetText("ShownCount: %d\n"), ShownCount);
UINT16 checkmap = NEXTMAP_INVALID;
for (i = 0; i < CUPCACHE_SPECIAL; i++)
{
checkmap = templevelsearch.cup->cachedlevels[i];
if (checkmap == NEXTMAP_INVALID)
{
continue;
}
CupCount++;
}
//CONS_Printf(M_GetText("CupCount: %d\n"), CupCount);
if (ShownCount >= CupCount) // greater than is used to ensure multi-gametype maps don't accidentally cause this to return false.
return true;
return false;
}
static void M_CupStartResponse(INT32 ch)
{
if (ch != MA_YES)
return;
if (!(server || (IsPlayerAdmin(consoleplayer))))
return;
M_LevelConfirmHandler();
}
static void M_CupQueueResponse(INT32 ch)
{
if (ch != MA_YES)
return;
if (!(server || (IsPlayerAdmin(consoleplayer))))
return;
cupheader_t *queuedcup = cupgrid.builtgrid[CUPMENU_CURSORID];
M_CupQueueHandler(queuedcup);
S_StartSound(NULL, sfx_gshe2);
while ((menuqueue.size + roundqueue.size) > ROUNDQUEUE_MAX)
menuqueue.size--;
if (!netgame)
{
M_StartMessage("Cup Queue",
va(M_GetText(
"You just queued %s CUP.\n"
"\n"
"Do you want to start the\n"
"cup immediately?\n"
), queuedcup->realname
), &M_CupStartResponse, MM_YESNO,
"Here we go!",
"On second thought..."
);
}
else
{
M_StartMessage("Cup Queue",
va(M_GetText(
"You just queued %s CUP.\n"
"\n"
"Do you want to queue it\n"
"for everyone?\n"
), queuedcup->realname
), &M_CupStartResponse, MM_YESNO,
"Queue em up!",
"Not yet"
);
}
}
void M_CupSelectHandler(INT32 choice)
{
const UINT8 pid = 0;
@ -429,6 +520,63 @@ void M_CupSelectHandler(INT32 choice)
S_StartSound(NULL, sfx_s3k63);
}
}
// Queue a cup for match race and netgames. See levelselect.c for most of how this actually works.
else if (levellist.canqueue && M_MenuButtonPressed(pid, MBT_Z))
{
M_SetMenuDelay(pid);
if (cupgrid.builtgrid[CUPMENU_CURSORID] == &dummy_lostandfound)
S_StartSound(NULL, sfx_gshe7);
else if (!M_IsCupQueueable(cupgrid.builtgrid[CUPMENU_CURSORID]))
{
S_StartSound(NULL, sfx_s3kb2);
M_StartMessage("Back to the Grand Prix!", "Can't queue a cup you haven't fully unlocked!", NULL, MM_NOTHING, NULL, NULL);
}
// Better to avoid any headaches here - pass the buck to the Extra button.
else if (roundqueue.size)
{
S_StartSound(NULL, sfx_s3kb2);
M_StartMessage("Queue is not empty!", "Clear the queue before trying to queue a cup!", NULL, MM_NOTHING, NULL, NULL);
return;
}
else
{
// We're not queueing Battle maps if we're in single-player Match Race.
if (!levellist.netgame && (cv_splitplayers.value == 1) && !netgame)
{
M_StartMessage("Cup Queue",
va(M_GetText(
"This will queue all Race courses in this cup.\n"
"\n"
"Any rounds already in the queue will be cleared out.\n"
"\n"
"Do you want to queue the cup?\n"
)), &M_CupQueueResponse, MM_YESNO,
"Let's do it!",
"Nah.");
}
else
{
M_StartMessage("Cup Queue",
va(M_GetText(
"This will queue the entire cup, including both Race and Battle courses.\n"
"\n"
"Any rounds already in the queue will be cleared out.\n"
"\n"
"Do you want to queue the cup?\n"
)), &M_CupQueueResponse, MM_YESNO,
"Let's do it!",
"Nah.");
}
}
}
else if (levellist.canqueue && M_MenuExtraPressed(pid))
{
M_ClearQueueHandler();
}
else if (M_MenuBackPressed(pid))
{
M_SetMenuDelay(pid);
@ -443,4 +591,6 @@ void M_CupSelectHandler(INT32 choice)
void M_CupSelectTick(void)
{
cupgrid.previewanim++;
// Shoving this here for cup queue purposes.
M_LevelSelectTick();
}

View file

@ -832,8 +832,10 @@ void M_LevelSelected(INT16 add, boolean menuupdate)
static void M_MenuQueueStopSend(INT32 ch)
{
(void)ch;
memset(&menuqueue, 0, sizeof(struct menuqueue));
menuqueue.clearing = false;
}
static void M_MenuQueueSelectedLocal(void)
@ -905,6 +907,92 @@ static void M_MenuQueueSelectedLocal(void)
}
}
// Copy-pasted and edited from G_GPCupIntoRoundQueue
void M_CupQueueHandler(cupheader_t *cup)
{
UINT8 i, levelindex = 0, bonusindex = 0;
UINT8 bonusmodulo = max(1, (cup->numlevels+1)/(cup->numbonus+1));
UINT16 cupLevelNum;
INT32 gtcheck;
// We shouldn't get to this point while there's rounds queued, but if we do, get outta there.
if (roundqueue.size)
{
return;
}
menuqueue.size = 0;
// Levels are added to the queue in the following pattern.
// For 5 Race rounds and 2 Bonus rounds, the most common case:
// race - race - BONUS - race - race - BONUS - race
// The system is flexible enough to permit other arrangements.
// However, we just want to keep the pacing even & consistent.
while (levelindex < cup->numlevels)
{
memset(menuqueue.entries+menuqueue.size, 0, sizeof(roundentry_t));
// Fill like two or three Race maps.
for (i = 0; i < bonusmodulo; i++)
{
cupLevelNum = cup->cachedlevels[levelindex];
if (cupLevelNum >= nummapheaders)
{
// Just skip the map if it's invalid.
continue;
}
if ((mapheaderinfo[cupLevelNum]->typeoflevel & TOL_RACE) == TOL_RACE)
{
gtcheck = GT_RACE;
}
else
{
gtcheck = mapheaderinfo[cupLevelNum]->typeoflevel;
}
menuqueue.entries[menuqueue.size].mapnum = cupLevelNum;
menuqueue.entries[menuqueue.size].gametype = gtcheck;
menuqueue.entries[menuqueue.size].encore = (cv_kartencore.value == 1);
menuqueue.size++;
levelindex++;
if (levelindex >= cup->numlevels)
break;
}
// Attempt to add an interstitial Battle round.
// If we're in singleplayer Match Race, just skip this.
if ((levelindex < cup->numlevels
&& bonusindex < cup->numbonus) && (levellist.netgame || (cv_splitplayers.value > 1) || netgame))
{
cupLevelNum = cup->cachedlevels[CUPCACHE_BONUS + bonusindex];
if (cupLevelNum < nummapheaders)
{
if ((mapheaderinfo[cupLevelNum]->typeoflevel & TOL_BATTLE) == TOL_BATTLE)
{
gtcheck = GT_BATTLE;
}
else
{
gtcheck = mapheaderinfo[cupLevelNum]->typeoflevel;
}
// In the case of Bonus rounds, we simply skip invalid maps.
menuqueue.entries[menuqueue.size].mapnum = cupLevelNum;
menuqueue.entries[menuqueue.size].gametype = gtcheck;
menuqueue.entries[menuqueue.size].encore = (cv_kartencore.value == 1);
menuqueue.size++;
}
bonusindex++;
}
}
}
boolean M_LevelSelectCupSwitch(boolean next, boolean skipones)
{
levelsearch_t templevelsearch = levellist.levelsearch;
@ -1002,6 +1090,40 @@ static void M_MenuQueueResponse(INT32 ch)
SendNetXCmd(XD_EXITLEVEL, NULL, 0);
}
// Ripped out of LevelSelectHandler for use in cup queueing from cupselect.c
void M_LevelConfirmHandler(void)
{
// Starting immediately OR importing queue
while ((menuqueue.size + roundqueue.size) > ROUNDQUEUE_MAX)
menuqueue.size--;
if (!levellist.canqueue || !menuqueue.size)
{
M_LevelSelected(levellist.cursor, true);
}
else if (netgame)
{
menuqueue.anchor = roundqueue.size;
menuqueue.sending = 1;
M_StartMessage("Queueing Rounds",
va(M_GetText(
"Attempting to send %d Round%s...\n"
"\n"
"If this is taking longer than you\n"
"expect, exit out of this message.\n"
), menuqueue.size, (menuqueue.size == 1 ? "" : "s")
), &M_MenuQueueStopSend, MM_NOTHING,
NULL,
"This is taking too long..."
);
}
else
{
M_MenuQueueSelectedLocal();
}
}
static void M_ClearQueueResponse(INT32 ch)
{
@ -1012,18 +1134,56 @@ static void M_ClearQueueResponse(INT32 ch)
return;
S_StartSound(NULL, sfx_slip);
if (netgame)
if (!netgame)
memset(&roundqueue, 0, sizeof(struct roundqueue));
if (netgame && (roundqueue.size != 0))
{
if (roundqueue.size)
{
Handle_MapQueueSend(0, ROUNDQUEUE_CMD_CLEAR, false);
}
return;
menuqueue.clearing = true;
Handle_MapQueueSend(0, ROUNDQUEUE_CMD_CLEAR, false);
M_StartMessage("Clearing Rounds",
va(M_GetText(
"Attempting to clear %d Round%s...\n"
"\n"
"If this is taking longer than you\n"
"expect, exit out of this message.\n"
), roundqueue.size, (roundqueue.size == 1 ? "" : "s")
), &M_MenuQueueStopSend, MM_NOTHING,
NULL,
"This is taking too long..."
);
}
memset(&roundqueue, 0, sizeof(struct roundqueue));
}
// Ripped out of LevelSelectHandler for use in queue clearing from cupselect.c
void M_ClearQueueHandler(void)
{
while ((menuqueue.size + roundqueue.size) > ROUNDQUEUE_MAX)
menuqueue.size--;
if (menuqueue.size)
{
S_StartSound(NULL, sfx_shldls);
menuqueue.size--;
}
else if (roundqueue.size)
{
M_StartMessage("Queue Clearing",
va(M_GetText(
"There %s %d Round%s of play queued.\n"
"\n"
"Do you want to empty the queue?\n"
),
(roundqueue.size == 1 ? "is" : "are"),
roundqueue.size,
(roundqueue.size == 1 ? "" : "s")
), &M_ClearQueueResponse, MM_YESNO,
"Time to start fresh",
"Not right now"
);
}
}
void M_LevelSelectHandler(INT32 choice)
{
const UINT8 pid = 0;
@ -1074,38 +1234,9 @@ void M_LevelSelectHandler(INT32 choice)
if (M_MenuConfirmPressed(pid))
{
// Starting immediately OR importing queue
M_SetMenuDelay(pid);
while ((menuqueue.size + roundqueue.size) > ROUNDQUEUE_MAX)
menuqueue.size--;
if (!levellist.canqueue || !menuqueue.size)
{
M_LevelSelected(levellist.cursor, true);
}
else if (netgame)
{
menuqueue.anchor = roundqueue.size;
menuqueue.sending = 1;
M_StartMessage("Queueing Rounds",
va(M_GetText(
"Attempting to send %d Round%s...\n"
"\n"
"If this is taking longer than you\n"
"expect, exit out of this message.\n"
), menuqueue.size, (menuqueue.size == 1 ? "" : "s")
), &M_MenuQueueStopSend, MM_NOTHING,
NULL,
"This is taking too long..."
);
}
else
{
M_MenuQueueSelectedLocal();
}
M_LevelConfirmHandler();
}
else if (levellist.canqueue && M_MenuButtonPressed(pid, MBT_Z))
{
@ -1138,30 +1269,7 @@ void M_LevelSelectHandler(INT32 choice)
}
else if (levellist.canqueue && M_MenuExtraPressed(pid))
{
while ((menuqueue.size + roundqueue.size) > ROUNDQUEUE_MAX)
menuqueue.size--;
if (menuqueue.size)
{
S_StartSound(NULL, sfx_shldls);
menuqueue.size--;
}
else if (roundqueue.size)
{
M_StartMessage("Queue Clearing",
va(M_GetText(
"There %s %d Round%s of play queued.\n"
"\n"
"Do you want to empty the queue?\n"
),
(roundqueue.size == 1 ? "is" : "are"),
roundqueue.size,
(roundqueue.size == 1 ? "" : "s")
), &M_ClearQueueResponse, MM_YESNO,
"Time to start fresh",
"Not right now"
);
}
M_ClearQueueHandler();
}
else if (M_MenuBackPressed(pid))
{
@ -1176,9 +1284,20 @@ void M_LevelSelectHandler(INT32 choice)
void M_LevelSelectTick(void)
{
if (menuqueue.clearing)
{
if (roundqueue.size != 0)
return;
menuqueue.clearing = false;
if (!menuqueue.cupqueue)
M_StopMessage(MA_NONE);
else
menuqueue.cupqueue = false;
}
if (!menuqueue.sending)
return;
if ((menuqueue.sending <= menuqueue.size) // Sending
&& (roundqueue.size >= menuqueue.anchor)) // Didn't get it wiped
{