From 188d168ace27bf8b6258517df4442b00c1e2c220 Mon Sep 17 00:00:00 2001 From: Freaky Mutant Man Date: Mon, 20 Oct 2025 19:24:16 +0000 Subject: [PATCH] Add ability to queue all maps in a cup for netgames and local matches by pressing Action on cup select. --- src/g_game.h | 2 + src/k_menu.h | 4 + src/k_menudraw.c | 15 ++ src/menus/transient/cup-select.c | 150 +++++++++++++++++ src/menus/transient/level-select.c | 249 +++++++++++++++++++++-------- 5 files changed, 355 insertions(+), 65 deletions(-) diff --git a/src/g_game.h b/src/g_game.h index 7996a8e61..dd67d8eae 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -81,6 +81,8 @@ extern struct menuqueue UINT8 size; UINT8 sending; UINT8 anchor; + boolean clearing; + boolean cupqueue; roundentry_t entries[ROUNDQUEUE_MAX]; } menuqueue; diff --git a/src/k_menu.h b/src/k_menu.h index 353db4cbb..d52750abb 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -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; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index ca23b8cb0..7f0ea1c7c 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -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 %s %s", + (templevelsearch.cup && templevelsearch.cup != &dummy_lostandfound && !roundqueue.size) ? "" : "", + (roundqueue.size || menuqueue.size) ? "" : "", + worktext), 1, TINY_FONT, 0); + } if (templevelsearch.grandprix == false && templevelsearch.cup != NULL) { diff --git a/src/menus/transient/cup-select.c b/src/menus/transient/cup-select.c index 70bfdff81..a3b6b43f4 100644 --- a/src/menus/transient/cup-select.c +++ b/src/menus/transient/cup-select.c @@ -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(); } diff --git a/src/menus/transient/level-select.c b/src/menus/transient/level-select.c index 9b543f619..fee71b815 100644 --- a/src/menus/transient/level-select.c +++ b/src/menus/transient/level-select.c @@ -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 {