From 1f521d21da49b7afdbfffd0edea7b6881e279054 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Sat, 28 Mar 2026 01:02:05 -0400 Subject: [PATCH] Rewrite random map buffer Each map now just has a countdown for when they'll reappear (stored in mapheader), which gets decremented each time a new map is played. This means it's now compatible across gametype switches, is a lot less complex, and is easy to retrieve the value for a specific map without needing to iterate constantly. Lots of the old unused code surrounding this function was also removed. Lastly, added a PARANOIA check for callAgainSoon being mishandled. NEP: I've added back map hell and gametype switching here since it was not in RR --- src/d_netcmd.c | 8 +- src/doomstat.h | 2 + src/f_finale.c | 2 +- src/g_game.c | 275 +++++++++++++++++++++++----------------------- src/g_game.h | 5 +- src/lua_baselib.c | 2 +- 6 files changed, 147 insertions(+), 147 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index e7ecce2dd..cf75fa0e6 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -3442,11 +3442,11 @@ void D_SetupVote(void) hellpick = 1; if (i == 2) // sometimes a different gametype - m = G_RandMap(G_TOLFlag(secondgt), prevmap, ((secondgt != gametype) ? 2 : 0), 0, votebuffer); + m = G_RandMap(G_TOLFlag(secondgt), prevmap, false, 0, true, votebuffer); else if (i >= VOTEROWS) // unknown-random and formerly force-unknown MAP HELL - m = G_RandMap(G_TOLFlag(gt), prevmap, 0, hellpick, votebuffer); + m = G_RandMap(G_TOLFlag(gt), prevmap, false, hellpick, true, votebuffer); else - m = G_RandMap(G_TOLFlag(gt), prevmap, 0, 0, votebuffer); + m = G_RandMap(G_TOLFlag(gt), prevmap, false, 0, true, votebuffer); if (i < VOTEROWS) votebuffer[min(i, 2)] = m; // min() is a dumb workaround for gcc 4.4 array-bounds error WRITEUINT16(p, m); @@ -4101,7 +4101,7 @@ static void Command_RandomMap(void) oldmapnum = NEXTMAP_INVALID; } - newmapnum = G_RandMap(G_TOLFlag(newgametype), oldmapnum, 0, 0, NULL) + 1; + newmapnum = G_RandMap(G_TOLFlag(newgametype), oldmapnum, true, 0, false, NULL) + 1; D_MapChange(newmapnum, newgametype, newencoremode, newresetplayers, 0, false, false); } diff --git a/src/doomstat.h b/src/doomstat.h index 27a587454..6fa215316 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -382,6 +382,8 @@ struct mapheader_t cupheader_t *cup; ///< Cached cup + size_t justPlayed; ///< Prevent this map from showing up in votes if it was recently picked. + // Titlecard information char lvlttl[22]; ///< Level name without "Zone". (21 character limit instead of 32, 21 characters can display on screen max anyway) char subttl[33]; ///< Subtitle for level diff --git a/src/f_finale.c b/src/f_finale.c index 94e925c7f..75ee9dd55 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -1741,7 +1741,7 @@ void F_TitleScreenTicker(boolean run) // prevent console spam if failed demoIdleLeft = demoIdleTime; - mapnum = G_RandMap(TOL_RACE, UINT16_MAX, 2, 0, NULL); + mapnum = G_RandMap(TOL_RACE, UINT16_MAX, true, 0, false, NULL); if (mapnum == 0) // gotta have ONE { return; diff --git a/src/g_game.c b/src/g_game.c index fe3b0ba9e..c3a04fbc1 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -324,25 +324,6 @@ boolean legitimateexit; // Did this client actually finish the match? boolean comebackshowninfo; // Have you already seen the "ATTACK OR PROTECT" message? tic_t antibumptime; // Delay before players start bumping into one another. -typedef struct -{ - mapnum_t *mapbuffer; // Pointer to zone memory - INT32 lastnummapheaders; // Reset if nummapheaders != this - UINT8 counttogametype; // Time to gametype change event -} randmaps_t; -static randmaps_t randmaps = {NULL, 0, 0}; - -static void G_ResetRandMapBuffer(void) -{ - INT32 i; - Z_Free(randmaps.mapbuffer); - randmaps.lastnummapheaders = nummapheaders; - randmaps.mapbuffer = Z_Malloc(randmaps.lastnummapheaders * sizeof(mapnum_t), PU_STATIC, NULL); - for (i = 0; i < randmaps.lastnummapheaders; i++) - randmaps.mapbuffer[i] = NEXTMAP_INVALID; - //intentionally not resetting randmaps.counttogametype here -} - // Grading UINT32 timesBeaten; @@ -4467,10 +4448,6 @@ INT16 G_SometimesGetDifferentGametype(UINT8 prefgametype) && (gametypes[prefgametype]->rules & GTR_CIRCUIT)); UINT8 encoremodifier = 0; - // -- the below is only necessary if you want to use randmaps.mapbuffer here - //if (randmaps.lastnummapheaders != nummapheaders) - //G_ResetRandMapBuffer(); - if (encorepossible) { if (encorescramble != -1) @@ -4501,22 +4478,22 @@ INT16 G_SometimesGetDifferentGametype(UINT8 prefgametype) if (!cv_kartvoterulechanges.value) // never return (gametype|encoremodifier); - if (randmaps.counttogametype > 0 && (cv_kartvoterulechanges.value != 3)) + if (g_countToGametype > 0 && (cv_kartvoterulechanges.value != 3)) { - randmaps.counttogametype--; - return (gametype|encoremodifier); + g_countToGametype--; + return (gametype|encoremodifier); } switch (cv_kartvoterulechanges.value) // okay, we're having a gametype change! when's the next one, luv? { - case 1: // sometimes - randmaps.counttogametype = 5; // per "cup" - break; - default: - // fallthrough - happens when clearing buffer, but needs a reasonable countdown if cvar is modified - case 2: // frequent - randmaps.counttogametype = 2; // ...every 1/2th-ish cup? - break; + case 1: // sometimes + g_countToGametype = 5; // per "cup" + break; + default: + // fallthrough - happens when clearing buffer, but needs a reasonable countdown if cvar is modified + case 2: // frequent + g_countToGametype = 2; // ...every 1/2th-ish cup? + break; } // Only this response is prefgametype-based. @@ -4625,49 +4602,63 @@ static INT32 TOLMaps(UINT8 pgametype) * has those flags. * \author Graue */ -mapnum_t G_RandMap(UINT32 tolflags, mapnum_t pprevmap, UINT8 ignorebuffer, UINT8 maphell, mapnum_t *extbuffer) +static mapnum_t *g_allowedMaps = NULL; + +UINT8 g_countToGametype = 0; + +#ifdef PARANOIA +static INT32 g_randMapStack = 0; +#endif + +mapnum_t G_RandMap(UINT32 tolflags, mapnum_t pprevmap, boolean ignoreBuffers, UINT8 maphell, boolean callAgainSoon, mapnum_t *extBuffer) { - UINT32 numokmaps = 0; - mapnum_t ix, bufx; - mapnum_t *okmaps = NULL; - mapnum_t extbufsize = 0; + INT32 allowedMapsCount = 0; + INT32 extBufferCount = 0; + mapnum_t ret = 0; + INT32 i, j; boolean usehellmaps; // Only consider Hell maps in this pick - if (randmaps.lastnummapheaders != nummapheaders) - G_ResetRandMapBuffer(); +#ifdef PARANOIA + g_randMapStack++; +#endif - if (!okmaps) + if (g_allowedMaps == NULL) { - //CONS_Printf("(making okmaps)\n"); - okmaps = Z_Malloc(nummapheaders * sizeof(INT16), PU_STATIC, NULL); + g_allowedMaps = Z_Malloc(nummapheaders * sizeof(INT16), PU_STATIC, NULL); } - if (extbuffer != NULL) + if (extBuffer != NULL) { - bufx = 0; - while (extbuffer[bufx]) + for (i = 0; extBuffer[i] != 0; i++) { - extbufsize++; bufx++; + extBufferCount++; } } -tryagain: +tryAgain: usehellmaps = (maphell == 0 ? false : (maphell == 2 || M_RandomChance(FRACUNIT/100))); // 1% chance of Hell // Find all the maps that are ok and and put them in an array. - for (ix = 0; ix < nummapheaders; ix++) + for (i = 0; i < nummapheaders; i++) { - boolean isokmap = true; - - if (!mapheaderinfo[ix] || mapheaderinfo[ix]->lumpnum == LUMPERROR) + if (mapheaderinfo[i] == NULL || mapheaderinfo[i]->lumpnum == LUMPERROR) + { + // Doesn't exist? continue; + } - if ((mapheaderinfo[ix]->typeoflevel & tolflags) != tolflags - || ix == pprevmap - || (!dedicated && M_MapLocked(ix+1)) - || (usehellmaps != (mapheaderinfo[ix]->menuflags & LF2_HIDEINMENU))) // this is bad - continue; //isokmap = false; + if (i == pprevmap) + { + // We were just here. + continue; + } + + if ((mapheaderinfo[i]->typeoflevel & tolflags) == 0) + { + // Doesn't match our gametype. + continue; + } if (pprevmap == UINT16_MAX) // title demo hack { @@ -4676,17 +4667,17 @@ tryagain: virtlump_t *vLump; lumpnum_t l; - vRes = vres_GetMap(mapheaderinfo[ix]->lumpnum); + vRes = vres_GetMap(mapheaderinfo[i]->lumpnum); - for (int i = 0; i < 10; i++) + for (int k = 0; k < 10; k++) { - vLump = vres_Find(vRes, va("%s/GHOST_%u",mapheaderinfo[ix]->lumpname,i)); + vLump = vres_Find(vRes, va("%s/GHOST_%u",mapheaderinfo[i]->lumpname,k)); if (vLump != NULL) break; } - if (vLump == NULL && ((l = W_CheckNumForLongName(va("%sS01",mapheaderinfo[ix]->lumpname))) == LUMPERROR)) + if (vLump == NULL && ((l = W_CheckNumForLongName(va("%sS01",mapheaderinfo[i]->lumpname))) == LUMPERROR)) { vres_Free(vRes); continue; @@ -4695,118 +4686,124 @@ tryagain: vres_Free(vRes); } - if (!ignorebuffer) + if ((!usehellmaps && ((mapheaderinfo[i]->menuflags & LF2_HIDEINMENU) == LF2_HIDEINMENU)) + || (usehellmaps && usehellmaps != ((mapheaderinfo[i]->menuflags & LF2_HIDEINMENU) == LF2_HIDEINMENU))) { - if (extbufsize > 0) + // THIS IS BAD + continue; + } + + if (M_MapLocked(i + 1) == true) + { + // We haven't earned this one. + continue; + } + + if (ignoreBuffers == false) + { + if (mapheaderinfo[i]->justPlayed > 0) { - for (bufx = 0; bufx < extbufsize; bufx++) + // We just played this map, don't play it again. + continue; + } + + if (extBufferCount > 0) + { + // An optional additional buffer, + // to avoid duplicates on the voting screen. + for (j = 0; j < (maphell ? 3 : extBufferCount); j++) { - if (extbuffer[bufx] == NEXTMAP_INVALID) // Rest of buffer SHOULD be empty - break; - if (ix == extbuffer[bufx]) + if (extBuffer[j] < 0 || extBuffer[j] >= nummapheaders) { - isokmap = false; + // Rest of buffer SHOULD be empty. + break; + } + + if (i == extBuffer[j]) + { + // Map is in this other buffer, don't duplicate. break; } } - if (!isokmap) - continue; - } - - for (bufx = 0; bufx < (maphell ? 3 : randmaps.lastnummapheaders); bufx++) - { - if (randmaps.mapbuffer[bufx] == NEXTMAP_INVALID) // Rest of buffer SHOULD be empty - break; - if (ix == randmaps.mapbuffer[bufx]) + if (j < extBufferCount) { - isokmap = false; - break; + // Didn't make it out of this buffer, so don't add this map. + continue; } } - - if (!isokmap) - continue; } - okmaps[numokmaps++] = ix; + // Got past the gauntlet, so we can allow this one. + g_allowedMaps[ allowedMapsCount++ ] = i; } - if (numokmaps == 0) // If there's no matches... (Goodbye, incredibly silly function chains :V) + if (allowedMapsCount == 0) { - if (!ignorebuffer) + // No maps are available. + if (ignoreBuffers == false) { - if (randmaps.mapbuffer[3] == NEXTMAP_INVALID) // Is the buffer basically empty? - { - ignorebuffer = 1; // This will probably only help in situations where there's very few maps, but it's folly not to at least try it - //CONS_Printf("RANDMAP - ignoring buffer\n"); - goto tryagain; - } - - for (bufx = 3; bufx < randmaps.lastnummapheaders; bufx++) // Let's clear all but the three most recent maps... - randmaps.mapbuffer[bufx] = NEXTMAP_INVALID; - //CONS_Printf("RANDMAP - emptying randmapbuffer\n"); - goto tryagain; + // Try again with ignoring the buffer before giving up. + ignoreBuffers = true; + goto tryAgain; } - if (maphell) // Any wiggle room to loosen our restrictions here? + if (maphell) { - //CONS_Printf("RANDMAP -maphell decrement\n"); + // Any wiggle room to loosen our restrictions here? maphell--; - goto tryagain; + goto tryAgain; } - //CONS_Printf("RANDMAP - defaulting to map01\n"); - ix = 0; // Sorry, none match. You get MAP01. - if (ignorebuffer == 1) - { - //CONS_Printf("(emptying randmapbuffer entirely)\n"); - for (bufx = 0; bufx < randmaps.lastnummapheaders; bufx++) - randmaps.mapbuffer[bufx] = NEXTMAP_INVALID; // if we're having trouble finding a map we should probably clear it - } + // Nothing else actually worked. Welp! + // You just get whatever was added first. + ret = 0; } else { - //CONS_Printf("RANDMAP - %d maps available to grab\n", numokmaps); - ix = okmaps[M_RandomKey(numokmaps)]; + ret = g_allowedMaps[ M_RandomKey(allowedMapsCount) ]; } - //CONS_Printf("(freeing okmaps)\n"); - Z_Free(okmaps); - okmaps = NULL; + if (callAgainSoon == false) + { + Z_Free(g_allowedMaps); + g_allowedMaps = NULL; - return ix; +#ifdef PARANOIA + // Crash if callAgainSoon was mishandled. + I_Assert(g_randMapStack == 1); +#endif + } + +#ifdef PARANOIA + g_randMapStack--; +#endif + + return ret; } +#define VOTEROWSADDSONE ((cv_votemaxrows.value*3) + 1 + ((cv_votemaxrows.value > 1) ? (cv_votemaxrows.value - 1) : 0)) + + void G_AddMapToBuffer(mapnum_t map) { - mapnum_t bufx; - mapnum_t refreshnum = (TOLMaps(gametype))-3; - - //if (refreshnum < 0) - refreshnum = 3; - - if (nummapheaders != randmaps.lastnummapheaders) + if (mapheaderinfo[map]->justPlayed == 0) // Started playing a new map. { - G_ResetRandMapBuffer(); - } - else - { - for (bufx = randmaps.lastnummapheaders-1; bufx > 0; bufx--) - randmaps.mapbuffer[bufx] = randmaps.mapbuffer[bufx-1]; + // Decrement every maps' justPlayed value. + INT32 i; + for (i = 0; i < nummapheaders; i++) + { + if (mapheaderinfo[i]->justPlayed > 0) + { + mapheaderinfo[i]->justPlayed--; + } + } } - randmaps.mapbuffer[0] = map; - - // We're getting pretty full, so lets flush this for future usage. - if (randmaps.mapbuffer[refreshnum] != NEXTMAP_INVALID) - { - // Clear all but the five most recent maps. - for (bufx = 5; bufx < randmaps.lastnummapheaders; bufx++) - randmaps.mapbuffer[bufx] = NEXTMAP_INVALID; - //CONS_Printf("Random map buffer has been flushed.\n"); - } + // Set our map's justPlayed value. + mapheaderinfo[map]->justPlayed = TOLMaps(gametype) - VOTEROWSADDSONE; } +#undef VOTEROWSADDSONE // // G_UpdateVisited @@ -5277,7 +5274,7 @@ void G_GetNextMap(void) } /* FALLTHRU */ case 2: // Go to random map. - nextmap = G_RandMap(G_TOLFlag(gametype), prevmap, false, false, NULL); + nextmap = G_RandMap(G_TOLFlag(gametype), prevmap, false, 0, false, NULL); break; default: if (nextmap >= NEXTMAP_SPECIAL) // Loop back around @@ -6260,8 +6257,6 @@ void G_DeferedInitNew(boolean pencoremode, mapnum_t map, INT32 pickedchar, UINT8 G_FreeGhosts(); // TODO: do we actually need to do this? - G_ResetRandMapBuffer(); - if ((modeattacking == ATTACKING_ITEMBREAK) || (bossinfo.boss == true)) { dogametype = GT_BATTLE; diff --git a/src/g_game.h b/src/g_game.h index 078224625..b1b8bd3ea 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -383,9 +383,12 @@ FUNCMATH INT32 G_TicsToMilliseconds(tic_t tics); UINT32 G_TOLFlag(INT32 pgametype); mapnum_t G_GetFirstMapOfGametype(UINT8 pgametype); -mapnum_t G_RandMap(UINT32 tolflags, mapnum_t pprevmap, UINT8 ignorebuffer, UINT8 maphell, mapnum_t *extbuffer); void G_AddMapToBuffer(mapnum_t map); +extern UINT8 g_countToGametype; + +mapnum_t G_RandMap(UINT32 tolflags, mapnum_t pprevmap, boolean ignoreBuffers, UINT8 maphell, boolean callAgainSoon, mapnum_t *extBuffer); + typedef struct { INT32 player; diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 079777626..056aa42e8 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -3351,7 +3351,7 @@ static int lib_gBuildMapName(lua_State *L) if (lua_compatmode && map == 0) // v1 has undefined behaviour if you return 0, who knew. { - map = G_NativeMapToKart(G_RandMap(G_TOLFlag(gametype), gamemap-1, 0, 0, NULL) + 1); + map = G_NativeMapToKart(G_RandMap(G_TOLFlag(gametype), gamemap-1, true, 0, false, NULL) + 1); } //HUDSAFE