diff --git a/src/deh_soc.c b/src/deh_soc.c index f23c71970..9f9224ae2 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -3021,6 +3021,28 @@ void readwipes(MYFILE *f) // SRB2KART // +static void invalidateacrosscups(UINT16 map) +{ + cupheader_t *cup = kartcupheaders; + UINT8 i; + + if (map >= nummapheaders) + return; + + while (cup) + { + for (i = 0; i < CUPCACHE_MAX; i++) + { + if (cup->cachedlevels[i] != map) + continue; + cup->cachedlevels[i] = NEXTMAP_INVALID; + } + cup = cup->next; + } + + mapheaderinfo[map]->cup = NULL; +} + void readcupheader(MYFILE *f, cupheader_t *cup) { char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL); @@ -3077,7 +3099,7 @@ void readcupheader(MYFILE *f, cupheader_t *cup) cup->levellist[cup->numlevels] = NULL; if (cup->cachedlevels[cup->numlevels] == NEXTMAP_INVALID) continue; - mapheaderinfo[cup->cachedlevels[cup->numlevels]]->cup = NULL; + invalidateacrosscups(cup->cachedlevels[cup->numlevels]); } tmp = strtok(word2,","); @@ -3100,6 +3122,9 @@ void readcupheader(MYFILE *f, cupheader_t *cup) cup->numbonus--; Z_Free(cup->levellist[CUPCACHE_BONUS + cup->numbonus]); cup->levellist[CUPCACHE_BONUS + cup->numbonus] = NULL; + if (cup->cachedlevels[CUPCACHE_BONUS + cup->numbonus] == NEXTMAP_INVALID) + continue; + invalidateacrosscups(cup->cachedlevels[CUPCACHE_BONUS + cup->numbonus]); } tmp = strtok(word2,","); @@ -3117,6 +3142,7 @@ void readcupheader(MYFILE *f, cupheader_t *cup) } else if (fastcmp(word, "SPECIALSTAGE")) { + invalidateacrosscups(cup->cachedlevels[CUPCACHE_SPECIAL]); Z_Free(cup->levellist[CUPCACHE_SPECIAL]); cup->levellist[CUPCACHE_SPECIAL] = Z_StrDup(word2); cup->cachedlevels[CUPCACHE_SPECIAL] = NEXTMAP_INVALID; diff --git a/src/doomstat.h b/src/doomstat.h index 320e29c13..ade6a4bb2 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -502,6 +502,8 @@ enum GameTypeRules GTR_LIVES = 1<<18, // Lives system, players are forced to spectate during Game Over. GTR_SPECIALBOTS = 1<<19, // Bot difficulty gets stronger between rounds, and the rival system is enabled. + GTR_NOCUPSELECT = 1<<20, // Your maps are not selected via cup. ...mutually exclusive with GTR_CAMPAIGN. + // free: to and including 1<<31 }; diff --git a/src/g_game.c b/src/g_game.c index ab4b6bc9c..819a6369e 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -3416,29 +3416,26 @@ UINT32 G_TOLFlag(INT32 pgametype) INT16 G_GetFirstMapOfGametype(UINT8 pgametype) { + UINT8 i = 0; INT16 mapnum = NEXTMAP_INVALID; + UINT32 tol = G_TOLFlag(pgametype); - if ((gametypedefaultrules[pgametype] & GTR_CAMPAIGN) && kartcupheaders) - { - mapnum = kartcupheaders->cachedlevels[0]; - } + levellist.cupmode = (!(gametypedefaultrules[pgametype] & GTR_NOCUPSELECT)); + levellist.timeattack = false; - if (mapnum >= nummapheaders) + if (levellist.cupmode) { - UINT32 tolflag = G_TOLFlag(pgametype); - for (mapnum = 0; mapnum < nummapheaders; mapnum++) + cupheader_t *cup = kartcupheaders; + while (cup && mapnum >= nummapheaders) { - if (!mapheaderinfo[mapnum]) - continue; - if (mapheaderinfo[mapnum]->lumpnum == LUMPERROR) - continue; - if (!(mapheaderinfo[mapnum]->typeoflevel & tolflag)) - continue; - if (mapheaderinfo[mapnum]->menuflags & LF2_HIDEINMENU) - continue; - break; + mapnum = M_GetFirstLevelInList(&i, tol, cup); + i = 0; } } + else + { + mapnum = M_GetFirstLevelInList(&i, tol, NULL); + } return mapnum; } @@ -3864,7 +3861,7 @@ static void G_GetNextMap(void) UINT32 tolflag = G_TOLFlag(gametype); register INT16 cm; - if (gametyperules & GTR_CAMPAIGN) + if (!(gametyperules & GTR_NOCUPSELECT)) { cupheader_t *cup = mapheaderinfo[gamemap-1]->cup; UINT8 gettingresult = 0; @@ -3891,6 +3888,12 @@ static void G_GetNextMap(void) || (!marathonmode && M_MapLocked(cm+1))) continue; + // If the map is in multiple cups, only consider the first one valid. + if (mapheaderinfo[cm]->cup != cup) + { + continue; + } + // Grab the first valid after the map you're on if (gettingresult) { diff --git a/src/k_menu.h b/src/k_menu.h index d8a966775..7623a19e7 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -679,8 +679,10 @@ void M_SetupRaceMenu(INT32 choice); extern struct cupgrid_s { SINT8 x, y; - SINT8 pageno; - UINT8 numpages; + size_t pageno; + cupheader_t **builtgrid; + size_t numpages; + size_t cappages; tic_t previewanim; boolean grandprix; // Setup grand prix server after picking boolean netgame; // Start the game in an actual server @@ -693,13 +695,16 @@ extern struct levellist_s { cupheader_t *selectedcup; INT16 choosemap; UINT8 newgametype; + UINT32 typeoflevel; + boolean cupmode; boolean timeattack; // Setup time attack menu after picking boolean netgame; // Start the game in an actual server } levellist; -boolean M_CanShowLevelInList(INT16 mapnum, UINT8 gt); -INT16 M_CountLevelsToShowInList(UINT8 gt); -INT16 M_GetFirstLevelInList(UINT8 gt); +boolean M_CanShowLevelInList(INT16 mapnum, UINT32 tol, cupheader_t *cup); +UINT16 M_CountLevelsToShowInList(UINT32 tol, cupheader_t *cup); +UINT16 M_GetFirstLevelInList(UINT8 *i, UINT32 tol, cupheader_t *cup); +UINT16 M_GetNextLevelInList(UINT16 map, UINT8 *i, UINT32 tol, cupheader_t *cup); void M_LevelSelectInit(INT32 choice); void M_CupSelectHandler(INT32 choice); diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 71f8b4242..729f99464 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -1902,31 +1902,52 @@ void M_DrawRaceDifficulty(void) static void M_DrawCupPreview(INT16 y, cupheader_t *cup) { - UINT8 i; - const INT16 pad = ((vid.width/vid.dupx) - BASEVIDWIDTH)/2; - INT16 x = -(cupgrid.previewanim % 82) - pad; + UINT8 i = 0; + INT16 maxlevels = M_CountLevelsToShowInList(levellist.typeoflevel, cup); + INT16 x = -(cupgrid.previewanim % 82); + INT16 add; + INT16 map, start = M_GetFirstLevelInList(&i, levellist.typeoflevel, cup); + UINT8 starti = i; V_DrawFill(0, y, BASEVIDWIDTH, 54, 31); if (cup && (cup->unlockrequired == -1 || unlockables[cup->unlockrequired].unlocked)) { - i = (cupgrid.previewanim / 82) % cup->numlevels; - while (x < BASEVIDWIDTH + pad) + add = (cupgrid.previewanim / 82) % maxlevels; + map = start; + while (add > 0) + { + map = M_GetNextLevelInList(map, &i, levellist.typeoflevel, cup); + + if (map >= nummapheaders) + { + break; + } + + add--; + } + while (x < BASEVIDWIDTH) { - INT32 cupLevelNum = cup->cachedlevels[i]; patch_t *PictureOfLevel = NULL; - if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum]) + if (map >= nummapheaders) { - PictureOfLevel = mapheaderinfo[cupLevelNum]->thumbnailPic; + map = start; + i = starti; + } + + if (map < nummapheaders && mapheaderinfo[map]) + { + PictureOfLevel = mapheaderinfo[map]->thumbnailPic; } if (!PictureOfLevel) PictureOfLevel = blanklvl; V_DrawSmallScaledPatch(x + 1, y+2, 0, PictureOfLevel); - i = (i+1) % cup->numlevels; x += 82; + + map = M_GetNextLevelInList(map, &i, levellist.typeoflevel, cup); } } else @@ -1970,32 +1991,18 @@ static void M_DrawCupTitle(INT16 y, cupheader_t *cup) void M_DrawCupSelect(void) { UINT8 i, j; - cupheader_t *cup = kartcupheaders; - - while (cup) - { - if (cup->id == CUPMENU_CURSORID) - break; - cup = cup->next; - } + cupheader_t *cup = cupgrid.builtgrid[CUPMENU_CURSORID]; for (i = 0; i < CUPMENU_COLUMNS; i++) { for (j = 0; j < CUPMENU_ROWS; j++) { - UINT8 id = (i + (j * CUPMENU_COLUMNS)) + (cupgrid.pageno * (CUPMENU_COLUMNS * CUPMENU_ROWS)); - cupheader_t *iconcup = kartcupheaders; + size_t id = (i + (j * CUPMENU_COLUMNS)) + (cupgrid.pageno * (CUPMENU_COLUMNS * CUPMENU_ROWS)); + cupheader_t *iconcup = cupgrid.builtgrid[id]; patch_t *patch = NULL; INT16 x, y; INT16 icony = 7; - while (iconcup) - { - if (iconcup->id == id) - break; - iconcup = iconcup->next; - } - if (!iconcup) break; @@ -2167,9 +2174,9 @@ static void M_DrawLevelSelectBlock(INT16 x, INT16 y, INT16 map, boolean redblink void M_DrawLevelSelect(void) { - INT16 i; - INT16 start = M_GetFirstLevelInList(levellist.newgametype); - INT16 map = start; + INT16 i = 0; + UINT8 j = 0; + INT16 map = M_GetFirstLevelInList(&j, levellist.typeoflevel, levellist.selectedcup); INT16 t = (64*menutransition.tics), tay = 0; INT16 y = 80 - (12 * levellist.y); boolean tatransition = ((menutransition.startmenu == &PLAY_TimeAttackDef || menutransition.endmenu == &PLAY_TimeAttackDef) && menutransition.tics); @@ -2180,13 +2187,10 @@ void M_DrawLevelSelect(void) tay = t/2; } - for (i = 0; i < M_CountLevelsToShowInList(levellist.newgametype); i++) + while (true) { INT16 lvlx = t, lvly = y; - while (!M_CanShowLevelInList(map, levellist.newgametype) && map < nummapheaders) - map++; - if (map >= nummapheaders) break; @@ -2202,7 +2206,8 @@ void M_DrawLevelSelect(void) ); y += 72; - map++; + i++; + map = M_GetNextLevelInList(map, &j, levellist.typeoflevel, levellist.selectedcup); } M_DrawCupTitle(tay, levellist.selectedcup); diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 384905f71..b15fdf8ad 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -3337,11 +3337,11 @@ void M_SetupDifficultySelect(INT32 choice) // M_CanShowLevelInList // // Determines whether to show a given map in the various level-select lists. -// Set gt = -1 to ignore gametype. // -boolean M_CanShowLevelInList(INT16 mapnum, UINT8 gt) +boolean M_CanShowLevelInList(INT16 mapnum, UINT32 tol, cupheader_t *cup) { - UINT32 tolflag = G_TOLFlag(gt); + if (mapnum >= nummapheaders) + return false; // Does the map exist? if (!mapheaderinfo[mapnum]) @@ -3359,7 +3359,7 @@ boolean M_CanShowLevelInList(INT16 mapnum, UINT8 gt) return false; // not unlocked // Check for TOL - if (!(mapheaderinfo[mapnum]->typeoflevel & tolflag)) + if (!(mapheaderinfo[mapnum]->typeoflevel & tol)) return false; // Should the map be hidden? @@ -3370,36 +3370,85 @@ boolean M_CanShowLevelInList(INT16 mapnum, UINT8 gt) if (levellist.timeattack && (mapheaderinfo[mapnum]->menuflags & LF2_NOTIMEATTACK)) return false; - if (gametypedefaultrules[gt] & GTR_CAMPAIGN && levellist.selectedcup) - { - if (mapheaderinfo[mapnum]->cup != levellist.selectedcup) - return false; - } + // Don't permit cup when no cup requested (also no dupes in time attack) + if (levellist.cupmode && (levellist.timeattack || !cup) && mapheaderinfo[mapnum]->cup != cup) + return false; // Survived our checks. return true; } -INT16 M_CountLevelsToShowInList(UINT8 gt) +UINT16 M_CountLevelsToShowInList(UINT32 tol, cupheader_t *cup) { - INT16 mapnum, count = 0; + INT16 i, count = 0; - for (mapnum = 0; mapnum < nummapheaders; mapnum++) - if (M_CanShowLevelInList(mapnum, gt)) + if (cup) + { + for (i = 0; i < CUPCACHE_MAX; i++) + { + if (!M_CanShowLevelInList(cup->cachedlevels[i], tol, cup)) + continue; + count++; + } + + return count; + } + + for (i = 0; i < nummapheaders; i++) + if (M_CanShowLevelInList(i, tol, NULL)) count++; return count; } -INT16 M_GetFirstLevelInList(UINT8 gt) +UINT16 M_GetFirstLevelInList(UINT8 *i, UINT32 tol, cupheader_t *cup) { - INT16 mapnum; + INT16 mapnum = NEXTMAP_INVALID; - for (mapnum = 0; mapnum < nummapheaders; mapnum++) - if (M_CanShowLevelInList(mapnum, gt)) - return mapnum; + if (cup) + { + *i = 0; + mapnum = NEXTMAP_INVALID; + for (; *i < CUPCACHE_MAX; (*i)++) + { + if (!M_CanShowLevelInList(cup->cachedlevels[*i], tol, cup)) + continue; + mapnum = cup->cachedlevels[*i]; + break; + } + } + else + { + for (mapnum = 0; mapnum < nummapheaders; mapnum++) + if (M_CanShowLevelInList(mapnum, tol, NULL)) + break; + } - return 0; + return mapnum; +} + +UINT16 M_GetNextLevelInList(UINT16 map, UINT8 *i, UINT32 tol, cupheader_t *cup) +{ + if (cup) + { + map = NEXTMAP_INVALID; + (*i)++; + for (; *i < CUPCACHE_MAX; (*i)++) + { + if (!M_CanShowLevelInList(cup->cachedlevels[*i], tol, cup)) + continue; + map = cup->cachedlevels[*i]; + break; + } + } + else + { + map++; + while (!M_CanShowLevelInList(map, tol, NULL) && map < nummapheaders) + map++; + } + + return map; } struct cupgrid_s cupgrid; @@ -3407,7 +3456,7 @@ struct levellist_s levellist; static void M_LevelSelectScrollDest(void) { - UINT16 m = M_CountLevelsToShowInList(levellist.newgametype)-1; + UINT16 m = M_CountLevelsToShowInList(levellist.typeoflevel, levellist.selectedcup)-1; levellist.dest = (6*levellist.cursor); @@ -3421,26 +3470,77 @@ static void M_LevelSelectScrollDest(void) // Builds the level list we'll be using from the gametype we're choosing and send us to the apropriate menu. static void M_LevelListFromGametype(INT16 gt) { - levellist.newgametype = gt; + static boolean first = true; + if (first || gt != levellist.newgametype) + { + levellist.newgametype = gt; + levellist.typeoflevel = G_TOLFlag(gt); + levellist.cupmode = (!(gametypedefaultrules[gt] & GTR_NOCUPSELECT)); + levellist.selectedcup = NULL; + first = false; + } + PLAY_CupSelectDef.prevMenu = currentMenu; // Obviously go to Cup Select in gametypes that have cups. // Use a really long level select in gametypes that don't use cups. - if (levellist.newgametype == GT_RACE) + if (levellist.cupmode) { cupheader_t *cup = kartcupheaders; - UINT8 highestid = 0; + size_t currentid = 0, highestunlockedid = 0; + const size_t unitlen = sizeof(cupheader_t*) * (CUPMENU_COLUMNS * CUPMENU_ROWS); // Make sure there's valid cups before going to this menu. if (cup == NULL) I_Error("Can you really call this a racing game, I didn't recieve any Cups on my pillow or anything"); + if (!cupgrid.builtgrid) + { + cupgrid.cappages = 2; + cupgrid.builtgrid = Z_Calloc( + cupgrid.cappages * unitlen, + PU_STATIC, + cupgrid.builtgrid); + + if (!cupgrid.builtgrid) + { + I_Error("M_LevelListFromGametype: Not enough memory to allocate builtgrid"); + } + } + memset(cupgrid.builtgrid, 0, cupgrid.cappages * unitlen); + while (cup) { + if (!M_CountLevelsToShowInList(levellist.typeoflevel, cup)) + { + // No valid maps, skip. + cup = cup->next; + continue; + } + + if ((currentid * sizeof(cupheader_t*)) >= cupgrid.cappages * unitlen) + { + // Double the size of the buffer, and clear the other stuff. + const size_t firstlen = cupgrid.cappages * unitlen; + cupgrid.builtgrid = Z_Realloc(cupgrid.builtgrid, + firstlen * 2, + PU_STATIC, NULL); + + if (!cupgrid.builtgrid) + { + I_Error("M_LevelListFromGametype: Not enough memory to reallocate builtgrid"); + } + + memset(cupgrid.builtgrid + firstlen, 0, firstlen); + cupgrid.cappages *= 2; + } + + cupgrid.builtgrid[currentid] = cup; + if (cup->unlockrequired == -1 || unlockables[cup->unlockrequired].unlocked) { - highestid = cup->id; + highestunlockedid = currentid; if (Playing() && mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->cup == cup) { cupgrid.x = cup->id % CUPMENU_COLUMNS; @@ -3448,10 +3548,16 @@ static void M_LevelListFromGametype(INT16 gt) cupgrid.pageno = cup->id / (CUPMENU_COLUMNS * CUPMENU_ROWS); } } + + currentid++; cup = cup->next; } - cupgrid.numpages = (highestid / (CUPMENU_COLUMNS * CUPMENU_ROWS)) + 1; + cupgrid.numpages = (highestunlockedid / (CUPMENU_COLUMNS * CUPMENU_ROWS)) + 1; + if (cupgrid.pageno >= cupgrid.numpages) + { + cupgrid.pageno = 0; + } PLAY_LevelSelectDef.prevMenu = &PLAY_CupSelectDef; M_SetupNextMenu(&PLAY_CupSelectDef, false); @@ -3504,25 +3610,15 @@ void M_LevelSelectInit(INT32 choice) return; } - levellist.newgametype = currentMenu->menuitems[itemOn].mvar2; - - M_LevelListFromGametype(levellist.newgametype); + M_LevelListFromGametype(currentMenu->menuitems[itemOn].mvar2); } void M_CupSelectHandler(INT32 choice) { - cupheader_t *newcup = kartcupheaders; const UINT8 pid = 0; (void)choice; - while (newcup) - { - if (newcup->id == CUPMENU_CURSORID) - break; - newcup = newcup->next; - } - if (menucmd[pid].dpad_lr > 0) { cupgrid.x++; @@ -3542,9 +3638,10 @@ void M_CupSelectHandler(INT32 choice) if (cupgrid.x < 0) { cupgrid.x = CUPMENU_COLUMNS-1; - cupgrid.pageno--; - if (cupgrid.pageno < 0) + if (cupgrid.pageno == 0) cupgrid.pageno = cupgrid.numpages-1; + else + cupgrid.pageno--; } S_StartSound(NULL, sfx_s3k5b); M_SetMenuDelay(pid); @@ -3569,6 +3666,8 @@ void M_CupSelectHandler(INT32 choice) if (M_MenuConfirmPressed(pid) /*|| M_MenuButtonPressed(pid, MBT_START)*/) { + cupheader_t *newcup = cupgrid.builtgrid[CUPMENU_CURSORID]; + M_SetMenuDelay(pid); if ((!newcup) @@ -3640,7 +3739,7 @@ void M_CupSelectHandler(INT32 choice) else { // Keep cursor position if you select the same cup again, reset if it's a different cup - if (!levellist.selectedcup || newcup->id != levellist.selectedcup->id) + if (levellist.selectedcup != newcup) { levellist.cursor = 0; levellist.selectedcup = newcup; @@ -3671,8 +3770,7 @@ void M_CupSelectTick(void) void M_LevelSelectHandler(INT32 choice) { - INT16 start = M_GetFirstLevelInList(levellist.newgametype); - INT16 maxlevels = M_CountLevelsToShowInList(levellist.newgametype); + INT16 maxlevels = M_CountLevelsToShowInList(levellist.typeoflevel, levellist.selectedcup); const UINT8 pid = 0; (void)choice; @@ -3703,20 +3801,20 @@ void M_LevelSelectHandler(INT32 choice) if (M_MenuConfirmPressed(pid) /*|| M_MenuButtonPressed(pid, MBT_START)*/) { - INT16 map = start; + UINT8 i = 0; + INT16 map = M_GetFirstLevelInList(&i, levellist.typeoflevel, levellist.selectedcup); INT16 add = levellist.cursor; M_SetMenuDelay(pid); while (add > 0) { - map++; - - while (!M_CanShowLevelInList(map, levellist.newgametype) && map < nummapheaders) - map++; + map = M_GetNextLevelInList(map, &i, levellist.typeoflevel, levellist.selectedcup); if (map >= nummapheaders) + { break; + } add--; } diff --git a/src/p_setup.c b/src/p_setup.c index 3be049311..38872851a 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -7795,13 +7795,9 @@ UINT8 P_InitMapData(boolean existingmapheaders) if (strcasecmp(cup->levellist[j], name) != 0) continue; - // Only panic about back-reference for non-bonus material. - if (j < MAXLEVELLIST) - { - if (mapheaderinfo[i]->cup) - I_Error("P_InitMapData: Map %s cannot appear in cups multiple times! (First in %s, now in %s)", name, mapheaderinfo[i]->cup->name, cup->name); + // Have a map recognise the first cup it's a part of. + if (!mapheaderinfo[i]->cup) mapheaderinfo[i]->cup = cup; - } cup->cachedlevels[j] = i; }