From 19ef96351a99d61adcb89c73099baf33bf56d00d Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 8 Apr 2023 20:42:27 +0100 Subject: [PATCH] roundqueue A general purpose system that permits cacheing of GP progression in one place, but which permits future expansion and brings Online GP a little closer to reality. - Stores a bunch of levels, gametypes, encore state, and restricted-by-rank-ness in sequence. - Initialised on GP cup select. - FUTURE WORK: Open to being initialised by other methods - Digests its way through that sequence as maps are completed. - Stores round number instead of `grandprixinfo`. - Map commands as sent over the wire have been adjusted. - Sends round number and size of/position in roundqueue. - Now figures out GP Event Type from gametype. - Can be swung in the direction of a Special Stage with a hint flag. - This hint flag replaces "fromlevelselect", which was functionally vestigal. --- src/d_clisrv.c | 2 +- src/d_main.c | 11 +- src/d_netcmd.c | 132 +++++++-------- src/d_netcmd.h | 2 +- src/doomstat.h | 3 +- src/g_demo.c | 2 +- src/g_game.c | 272 ++++++++++++++++++++----------- src/g_game.h | 23 ++- src/hu_stuff.c | 12 +- src/k_grandprix.c | 20 ++- src/k_grandprix.h | 1 - src/k_menufunc.c | 1 - src/menus/transient/cup-select.c | 21 ++- src/p_saveg.c | 63 ++++++- src/st_stuff.c | 42 ++--- src/typedef.h | 1 + 16 files changed, 375 insertions(+), 233 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 37bb6323b..5b3e4093d 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -6086,7 +6086,7 @@ boolean TryRunTics(tic_t realtics) { COM_BufTicker(); if (mapchangepending) - D_MapChange(-1, 0, encoremode, false, 2, false, fromlevelselect); // finish the map change + D_MapChange(-1, 0, encoremode, false, 2, false, forcespecialstage); // finish the map change } NetUpdate(); diff --git a/src/d_main.c b/src/d_main.c index c96ff0dc9..7cd1ad2c9 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -1021,8 +1021,9 @@ void D_ClearState(void) modeattacking = ATTACKING_NONE; marathonmode = 0; - // Reset GP + // Reset GP and roundqueue memset(&grandprixinfo, 0, sizeof(struct grandprixinfo)); + memset(&roundqueue, 0, sizeof(struct roundqueue)); // empty maptol so mario/etc sounds don't play in sound test when they shouldn't maptol = 0; @@ -1740,13 +1741,12 @@ void D_SRB2Main(void) // Start up a "minor" grand prix session memset(&grandprixinfo, 0, sizeof(struct grandprixinfo)); + memset(&roundqueue, 0, sizeof(struct roundqueue)); grandprixinfo.gamespeed = KARTSPEED_NORMAL; - grandprixinfo.encore = false; grandprixinfo.masterbots = false; grandprixinfo.gp = true; - grandprixinfo.roundnum = 0; grandprixinfo.cup = NULL; grandprixinfo.wonround = false; @@ -1997,11 +1997,6 @@ void D_SRB2Main(void) D_GameTypeChanged(j); } - if (gametyperules & (GTR_BOSS|GTR_CATCHER)) - grandprixinfo.eventmode = GPEVENT_SPECIAL; - else if (gametype != GT_RACE) - grandprixinfo.eventmode = GPEVENT_BONUS; - multiplayer = true; } diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 27cdc9042..9493efefe 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -567,6 +567,8 @@ INT16 numgametypes = GT_FIRSTFREESLOT; boolean forceresetplayers = false; boolean deferencoremode = false; +boolean forcespecialstage = false; + UINT8 splitscreen = 0; INT32 adminplayers[MAXPLAYERS]; @@ -2541,42 +2543,27 @@ INT32 mapchangepending = 0; * 63 old events will get reexecuted, with ridiculous results. Just don't do * it (without setting delay to 1, which is the current solution). * - * \param mapnum Map number to change to. - * \param gametype Gametype to switch to. - * \param pencoremode Is this 'Encore Mode'? - * \param resetplayers 1 to reset player scores and lives and such, 0 not to. - * \param delay Determines how the function will be executed: 0 to do - * it all right now (must not be done from a menu), 1 to - * do step one and prepare step two, 2 to do step two. - * \param skipprecutscene To skip the precutscence or not? + * \param mapnum Map number to change to. + * \param gametype Gametype to switch to. + * \param pencoremode Is this 'Encore Mode'? + * \param presetplayers 1 to reset player scores and lives and such, 0 not to. + * \param delay Determines how the function will be executed: 0 to do + * it all right now (must not be done from a menu), 1 to + * do step one and prepare step two, 2 to do step two. + * \param skipprecutscene To skip the precutscence or not? + * \param pforcespecialstage For certain contexts, forces a special stage. * \sa D_GameTypeChanged, Command_Map_f * \author Graue */ -void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pencoremode, boolean resetplayers, INT32 delay, boolean skipprecutscene, boolean FLS) +void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pencoremode, boolean presetplayers, INT32 delay, boolean skipprecutscene, boolean pforcespecialstage) { - static char buf[2+MAX_WADPATH+1+4]; + static char buf[1+1+1+1+1+2+4]; static char *buf_p = buf; // The supplied data are assumed to be good. I_Assert(delay >= 0 && delay <= 2); - /* - if (mapnum != -1) - { - CV_SetValue(&cv_nextmap, mapnum); - } - */ - - CONS_Debug(DBG_GAMELOGIC, "Map change: mapnum=%d gametype=%d pencoremode=%d resetplayers=%d delay=%d skipprecutscene=%d\n", - mapnum, newgametype, pencoremode, resetplayers, delay, skipprecutscene); - - if ((netgame || multiplayer) && (grandprixinfo.gp != false)) - FLS = false; - - // Too lazy to change the input value for every instance of this function....... - if (grandprixinfo.gp == true) - { - pencoremode = grandprixinfo.encore; - } + CONS_Debug(DBG_GAMELOGIC, "Map change: mapnum=%d gametype=%d pencoremode=%d presetplayers=%d delay=%d skipprecutscene=%d pforcespecialstage = %d\n", + mapnum, newgametype, pencoremode, presetplayers, delay, skipprecutscene, pforcespecialstage); if (delay != 2) { @@ -2585,14 +2572,19 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pencoremode, boolean r buf_p = buf; if (pencoremode) flags |= 1; - if (!resetplayers) + if (presetplayers) flags |= 1<<1; if (skipprecutscene) flags |= 1<<2; - if (FLS) + if (pforcespecialstage) flags |= 1<<3; WRITEUINT8(buf_p, flags); + // roundqueue state + WRITEUINT8(buf_p, roundqueue.position); + WRITEUINT8(buf_p, roundqueue.size); + WRITEUINT8(buf_p, roundqueue.roundnum); + // new gametype value WRITEUINT8(buf_p, newgametype); @@ -2784,6 +2776,7 @@ static void Command_Map_f(void) size_t option_skill; const char *gametypename; boolean newresetplayers; + boolean newforcespecialstage; boolean usingcheats; boolean ischeating; @@ -2809,6 +2802,7 @@ static void Command_Map_f(void) option_encore = COM_CheckPartialParm("-e"); option_skill = COM_CheckPartialParm("-s"); newresetplayers = ! COM_CheckParm("-noresetplayers"); + newforcespecialstage = COM_CheckParm("-forcespecialstage"); usingcheats = CV_CheatsEnabled(); ischeating = (!(netgame || multiplayer)) || (!newresetplayers); @@ -2946,7 +2940,7 @@ static void Command_Map_f(void) // don't use a gametype the map doesn't support if (cht_debug || option_force || cv_skipmapcheck.value) { - fromlevelselect = false; // The player wants us to trek on anyway. Do so. + // The player wants us to trek on anyway. Do so. } else { @@ -2961,12 +2955,6 @@ static void Command_Map_f(void) Z_Free(mapname); return; } - else - { - fromlevelselect = - ( netgame || multiplayer ) && - grandprixinfo.gp != false; - } } if (!(netgame || multiplayer)) @@ -3011,29 +2999,16 @@ static void Command_Map_f(void) } } - grandprixinfo.encore = newencoremode; - grandprixinfo.gp = true; - grandprixinfo.roundnum = 0; - grandprixinfo.cup = NULL; grandprixinfo.wonround = false; - grandprixinfo.initalize = true; - - grandprixinfo.eventmode = GPEVENT_NONE; - - if (gametypes[newgametype]->rules & (GTR_BOSS|GTR_CATCHER)) - { - grandprixinfo.eventmode = GPEVENT_SPECIAL; - } - else if (newgametype != GT_RACE) - { - grandprixinfo.eventmode = GPEVENT_BONUS; - } if (!Playing()) { UINT8 ssplayers = cv_splitplayers.value-1; + grandprixinfo.cup = NULL; + grandprixinfo.initalize = true; + multiplayer = true; restoreMenu = NULL; @@ -3050,7 +3025,7 @@ static void Command_Map_f(void) } } - D_MapChange(newmapnum, newgametype, newencoremode, newresetplayers, 0, false, fromlevelselect); + D_MapChange(newmapnum, newgametype, newencoremode, newresetplayers, 0, false, newforcespecialstage); Z_Free(realmapname); } @@ -3065,8 +3040,8 @@ static void Command_Map_f(void) static void Got_Mapcmd(UINT8 **cp, INT32 playernum) { UINT8 flags; - INT32 resetplayer = 1, lastgametype; - UINT8 skipprecutscene, FLS; + INT32 presetplayer = 1, lastgametype; + UINT8 skipprecutscene, pforcespecialstage; boolean pencoremode; INT16 mapnumber; @@ -3087,7 +3062,15 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum) pencoremode = ((flags & 1) != 0); - resetplayer = ((flags & (1<<1)) == 0); + presetplayer = ((flags & (1<<1)) != 0); + + skipprecutscene = ((flags & (1<<2)) != 0); + + pforcespecialstage = ((flags & (1<<3)) != 0); + + roundqueue.position = READUINT8(*cp); + roundqueue.size = READUINT8(*cp); + roundqueue.roundnum = READUINT8(*cp); lastgametype = gametype; gametype = READUINT8(*cp); @@ -3101,35 +3084,46 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum) if (!(gametyperules & GTR_ENCORE)) pencoremode = false; - skipprecutscene = ((flags & (1<<2)) != 0); - - FLS = ((flags & (1<<3)) != 0); - mapnumber = READINT16(*cp); + // Handle some Grand Prix state. + if (grandprixinfo.gp) + { + boolean caughtretry = (gametype == lastgametype + && mapnumber == gamemap); + if (pforcespecialstage // Forced. + || (caughtretry && grandprixinfo.eventmode == GPEVENT_SPECIAL) // Catch retries of forced. + || (gametyperules & (GTR_BOSS|GTR_CATCHER))) // Conventional rules. + { + grandprixinfo.eventmode = GPEVENT_SPECIAL; + } + else if (gametype != GT_RACE) + { + grandprixinfo.eventmode = GPEVENT_BONUS; + } + else + { + grandprixinfo.eventmode = GPEVENT_NONE; + } + } + if (netgame) P_ClearRandom(READUINT32(*cp)); if (!skipprecutscene) { DEBFILE(va("Warping to %s [resetplayer=%d lastgametype=%d gametype=%d cpnd=%d]\n", - G_BuildMapName(mapnumber), resetplayer, lastgametype, gametype, chmappending)); + G_BuildMapName(mapnumber), presetplayer, lastgametype, gametype, chmappending)); CON_LogMessage(M_GetText("Speeding off to level...\n")); } if (demo.playback && !demo.timing) precache = false; - if (resetplayer && !FLS) - { - emeralds = 0; - memset(&luabanks, 0, sizeof(luabanks)); - } - demo.savemode = (cv_recordmultiplayerdemos.value == 2) ? DSM_WILLAUTOSAVE : DSM_NOTSAVING; demo.savebutton = 0; - G_InitNew(pencoremode, mapnumber, resetplayer, skipprecutscene, FLS); + G_InitNew(pencoremode, mapnumber, presetplayer, skipprecutscene); if (demo.playback && !demo.timing) precache = true; if (demo.timing) diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 8cab1e10f..908c6aec7 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -239,7 +239,7 @@ void D_SendPlayerConfig(UINT8 n); void Command_ExitGame_f(void); void Command_Retry_f(void); void D_GameTypeChanged(INT32 lastgametype); // not a real _OnChange function anymore -void D_MapChange(INT32 pmapnum, INT32 pgametype, boolean pencoremode, boolean presetplayers, INT32 pdelay, boolean pskipprecutscene, boolean pfromlevelselect); +void D_MapChange(INT32 pmapnum, INT32 pgametype, boolean pencoremode, boolean presetplayers, INT32 pdelay, boolean pskipprecutscene, boolean pforcespecialstage); void D_SetupVote(void); void D_ModifyClientVote(UINT8 player, SINT8 voted); void D_PickVote(void); diff --git a/src/doomstat.h b/src/doomstat.h index a16e5baa8..72a3a8c71 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -182,8 +182,7 @@ extern boolean multiplayer; extern UINT8 splitscreen; extern int r_splitscreen; -extern boolean fromlevelselect; -extern boolean forceresetplayers, deferencoremode; +extern boolean forceresetplayers, deferencoremode, forcespecialstage; // ======================================== // Internal parameters for sound rendering. diff --git a/src/g_demo.c b/src/g_demo.c index da617188a..f5e9bcfdd 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -3463,7 +3463,7 @@ void G_DoPlayDemo(char *defdemoname) P_SetRandSeed(i, randseed[i]); } - G_InitNew(demoflags & DF_ENCORE, gamemap, true, true, false); // Doesn't matter whether you reset or not here, given changes to resetplayer. + G_InitNew(demoflags & DF_ENCORE, gamemap, true, true); // Doesn't matter whether you reset or not here, given changes to resetplayer. for (i = 0; i < numslots; i++) { diff --git a/src/g_game.c b/src/g_game.c index f5173e642..16ef27cbe 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -3988,117 +3988,201 @@ static void G_HandleSaveLevel(void) } } +// Next map apparatus +struct roundqueue roundqueue; + +void G_MapIntoRoundQueue(UINT16 map, UINT8 setgametype, boolean setencore, boolean rankrestricted) +{ + if (roundqueue.size >= ROUNDQUEUE_MAX) + { + CONS_Alert(CONS_ERROR, "G_MapIntoRoundQueue: Unable to add map beyond %u\n", roundqueue.size); + return; + } + + roundqueue.entries[roundqueue.size].mapnum = map; + roundqueue.entries[roundqueue.size].gametype = setgametype; + roundqueue.entries[roundqueue.size].encore = setencore; + roundqueue.entries[roundqueue.size].rankrestricted = rankrestricted; + + roundqueue.size++; +} + +void G_GPCupIntoRoundQueue(cupheader_t *cup, UINT8 setgametype, boolean setencore) +{ + UINT8 i, levelindex = 0, bonusindex = 0; + UINT8 bonusmodulo = (cup->numlevels+1)/(cup->numbonus+1); + UINT16 cupLevelNum; + + // 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) + { + // Fill like two or three Race maps. + for (i = 0; i < bonusmodulo; i++) + { + cupLevelNum = cup->cachedlevels[levelindex]; + + if (cupLevelNum >= nummapheaders) + { + // For invalid Race maps, we keep the pacing by going to TEST RUN. + // It transparently lets the user know something is wrong. + cupLevelNum = 0; + } + + G_MapIntoRoundQueue( + cupLevelNum, + setgametype, + setencore, // *probably* correct + false + ); + + levelindex++; + if (levelindex >= cup->numlevels) + break; + } + + // Attempt to add an interstitial Bonus round. + if (levelindex < cup->numlevels + && bonusindex < cup->numbonus) + { + cupLevelNum = cup->cachedlevels[CUPCACHE_BONUS + bonusindex]; + + if (cupLevelNum < nummapheaders) + { + // In the case of Bonus rounds, we simply skip invalid maps. + G_MapIntoRoundQueue( + cupLevelNum, + G_GuessGametypeByTOL(mapheaderinfo[cupLevelNum]->typeoflevel), + setencore, // if this isn't correct, Got_Mapcmd will fix it + false + ); + } + + bonusindex++; + } + } + + // ...but there's one last trick up our sleeves. + // At the end of the Cup is a Rank-restricted treat. + // So we append it to the end of the roundqueue. + // (as long as it exists, of course!) + cupLevelNum = cup->cachedlevels[CUPCACHE_SPECIAL]; + if (cupLevelNum < nummapheaders) + { + G_MapIntoRoundQueue( + cupLevelNum, + G_GuessGametypeByTOL(mapheaderinfo[cupLevelNum]->typeoflevel), + setencore, // if this isn't correct, Got_Mapcmd will fix it + true // Rank-restricted! + ); + } + + if (roundqueue.size == 0) + { + I_Error("G_CupToRoundQueue: roundqueue size is 0 after population!?"); + } +} + static void G_GetNextMap(void) { INT32 i; + boolean setalready = false; + + if (!server) + { + // Server is authoriative, not you + return; + } + + deferencoremode = (cv_kartencore.value == 1); + forceresetplayers = forcespecialstage = false; // go to next level // nextmap is 0-based, unlike gamemap if (nextmapoverride != 0) { nextmap = (INT16)(nextmapoverride-1); + setalready = true; } - else if (grandprixinfo.gp == true) + else if (roundqueue.size > 0) { - if (grandprixinfo.roundnum == 0 || grandprixinfo.cup == NULL) // Single session + boolean permitrank = false; + if (grandprixinfo.gp == true + && grandprixinfo.gamespeed >= KARTSPEED_NORMAL) { - nextmap = prevmap; // Same map + // On A rank pace? Then you get a chance for S rank! + permitrank = (K_CalculateGPGrade(&grandprixinfo.rank) >= GRADE_A); + } + + while (roundqueue.position < roundqueue.size + && (roundqueue.entries[roundqueue.position].mapnum >= nummapheaders + || (permitrank == false && roundqueue.entries[roundqueue.position].rankrestricted == true))) + { + // Skip all restricted queue entries. + roundqueue.position++; + } + + if (roundqueue.position < roundqueue.size) + { + // The next entry in the queue is valid; set it as nextmap! + nextmap = roundqueue.entries[roundqueue.position].mapnum; + deferencoremode = roundqueue.entries[roundqueue.position].encore; + + // And we handle gametype changes, too. + if (roundqueue.entries[roundqueue.position].gametype != gametype) + { + INT32 lastgametype = gametype; + G_SetGametype(roundqueue.entries[roundqueue.position].gametype); + D_GameTypeChanged(lastgametype); + } + + // On entering roundqueue mode, kill the non-PWR between-round scores. + // This makes it viable as a future tournament mode base. + if (roundqueue.position == 0) + { + forceresetplayers = true; + } + + // Handle primary queue position update. + roundqueue.position++; + if (grandprixinfo.gp == false || gametype == roundqueue.entries[0].gametype) + { + roundqueue.roundnum++; + } + + setalready = true; } else { - INT32 lastgametype = gametype, newgametype = GT_RACE; - // 5 levels, 2 bonus stages: after rounds 2 and 4 (but flexible enough to accomodate other solutions) - UINT8 bonusmodulo = (grandprixinfo.cup->numlevels+1)/(grandprixinfo.cup->numbonus+1); - UINT8 bonusindex = (grandprixinfo.roundnum / bonusmodulo) - 1; + // Wipe the queue info. + memset(&roundqueue, 0, sizeof(struct roundqueue)); - // If we're in a GP event, don't immediately follow it up with another. - // I also suspect this will not work with online GP so I'm gonna prevent it right now. - // The server might have to communicate eventmode (alongside other GP data) in XD_MAP later. - if (netgame) - ; - else if (grandprixinfo.eventmode != GPEVENT_NONE) + if (grandprixinfo.gp == true) { - grandprixinfo.eventmode = GPEVENT_NONE; - - G_SetGametype(GT_RACE); - if (gametype != lastgametype) - D_GameTypeChanged(lastgametype); - } - // Special stage - else if (grandprixinfo.roundnum >= grandprixinfo.cup->numlevels) - { - gp_rank_e grade = K_CalculateGPGrade(&grandprixinfo.rank); - - if (grade >= GRADE_A && grandprixinfo.gamespeed >= KARTSPEED_NORMAL) // On A rank pace? Then you get a chance for S rank! - { - const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[CUPCACHE_SPECIAL]; - if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum]) - { - grandprixinfo.eventmode = GPEVENT_SPECIAL; - nextmap = cupLevelNum; - newgametype = G_GuessGametypeByTOL(mapheaderinfo[cupLevelNum]->typeoflevel); - - if (gamedata->everseenspecial == false) - { - gamedata->everseenspecial = true; - M_UpdateUnlockablesAndExtraEmblems(true, true); - G_SaveGameData(); - } - } - } - } - else if ((grandprixinfo.cup->numbonus > 0) - && (grandprixinfo.roundnum % bonusmodulo) == 0 - && bonusindex < MAXBONUSLIST) - { - // todo any other condition? - { - const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[CUPCACHE_BONUS + bonusindex]; - if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum]) - { - grandprixinfo.eventmode = GPEVENT_BONUS; - nextmap = cupLevelNum; - newgametype = G_GuessGametypeByTOL(mapheaderinfo[cupLevelNum]->typeoflevel); - } - } - } - - if (newgametype == -1) - { - // Don't permit invalid changes. - grandprixinfo.eventmode = GPEVENT_NONE; - newgametype = gametype; - } - - if (grandprixinfo.eventmode != GPEVENT_NONE) - { - G_SetGametype(newgametype); - if (gametype != lastgametype) - D_GameTypeChanged(lastgametype); - } - else if (grandprixinfo.roundnum >= grandprixinfo.cup->numlevels) // On final map - { - nextmap = NEXTMAP_CEREMONY; // ceremonymap + // In GP, we're now ready to go to the ceremony. + nextmap = NEXTMAP_CEREMONY; + setalready = true; } else { - // Proceed to next map - const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[grandprixinfo.roundnum]; - - if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum]) - { - nextmap = cupLevelNum; - } - else - { - nextmap = 0; // Prevent uninitialised use -- go to TEST RUN, it's very obvious - } - - grandprixinfo.roundnum++; + // On exiting roundqueue mode, kill the non-PWR between-round scores. + // This prevents future tournament winners from carrying their wins out. + forceresetplayers = true; } } } - else + else if (grandprixinfo.gp == true) + { + // Fast And Rapid Testing + // this codepath is exclusively accessible through console/command line + nextmap = prevmap; + setalready = true; + } + + if (setalready == false) { UINT32 tolflag = G_TOLFlag(gametype); register INT16 cm; @@ -4242,8 +4326,6 @@ static void G_GetNextMap(void) if (!spec) #endif //#if 0 lastmap = nextmap; - - deferencoremode = (cv_kartencore.value == 1); } // @@ -4417,8 +4499,6 @@ void G_NextLevel(void) return; } - forceresetplayers = false; - gameaction = ga_worlddone; } @@ -4433,7 +4513,7 @@ static void G_DoWorldDone(void) forceresetplayers, 0, false, - false); + forcespecialstage); } gameaction = ga_nothing; @@ -5406,14 +5486,12 @@ void G_DeferedInitNew(boolean pencoremode, INT32 map, INT32 pickedchar, UINT8 ss // This is the map command interpretation something like Command_Map_f // // called at: map cmd execution, doloadgame, doplaydemo -void G_InitNew(UINT8 pencoremode, INT32 map, boolean resetplayer, boolean skipprecutscene, boolean FLS) +void G_InitNew(UINT8 pencoremode, INT32 map, boolean resetplayer, boolean skipprecutscene) { const char * mapname = G_BuildMapName(map); INT32 i; - (void)FLS; - if (paused) { paused = false; @@ -5440,7 +5518,7 @@ void G_InitNew(UINT8 pencoremode, INT32 map, boolean resetplayer, boolean skippr players[i].roundscore = 0; - if (resetplayer && !(multiplayer && demo.playback)) // SRB2Kart + if (resetplayer && !demo.playback) // SRB2Kart { players[i].lives = 3; players[i].xtralife = 0; diff --git a/src/g_game.h b/src/g_game.h index 9fe875c09..da957937a 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -53,6 +53,27 @@ typedef enum NEXTMAP_SPECIAL = NEXTMAP_INVALID } nextmapspecial_t; +#define ROUNDQUEUE_MAX 10 // sane max? maybe make dynamically allocated later + +struct roundentry_t +{ + UINT16 mapnum; // Map number at this position + UINT8 gametype; // Gametype we want to play this in + boolean encore; // Whether this will be flipped + boolean rankrestricted; // For grand prix progression +}; + +extern struct roundqueue +{ + UINT8 roundnum; // Visible number on HUD + UINT8 position; // Head position in the round queue + UINT8 size; // Number of entries in the round queue + roundentry_t entries[ROUNDQUEUE_MAX]; // Entries in the round queue +} roundqueue; + +void G_MapIntoRoundQueue(UINT16 map, UINT8 setgametype, boolean setencore, boolean rankrestricted); +void G_GPCupIntoRoundQueue(cupheader_t *cup, UINT8 setgametype, boolean setencore); + extern INT32 gameovertics; extern UINT8 ammoremovaltics; extern tic_t timeinmap; // Ticker for time spent in level (used for levelcard display) @@ -117,7 +138,7 @@ void G_ChangePlayerReferences(mobj_t *oldmo, mobj_t *newmo); void G_DoReborn(INT32 playernum); void G_PlayerReborn(INT32 player, boolean betweenmaps); void G_InitNew(UINT8 pencoremode, INT32 map, boolean resetplayer, - boolean skipprecutscene, boolean FLS); + boolean skipprecutscene); char *G_BuildMapTitle(INT32 mapnum); struct searchdim diff --git a/src/hu_stuff.c b/src/hu_stuff.c index e5eb0aed9..5b028662d 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -2482,24 +2482,26 @@ static void HU_DrawRankings(void) V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, gametypes[gametype]->name); // Left hand side - if (grandprixinfo.gp == true) + if (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE) { const char *roundstr = NULL; V_DrawCenteredString(64, 8, 0, "ROUND"); switch (grandprixinfo.eventmode) { - case GPEVENT_BONUS: - roundstr = "BONUS"; - break; case GPEVENT_SPECIAL: roundstr = "SPECIAL"; break; default: - roundstr = va("%d", grandprixinfo.roundnum); + roundstr = "BONUS"; break; } V_DrawCenteredString(64, 16, hilicol, roundstr); } + else if (roundqueue.size > 0) + { + V_DrawCenteredString(64, 8, 0, "ROUND"); + V_DrawCenteredString(64, 16, hilicol, va("%d", roundqueue.roundnum)); + } else if ((gametyperules & GTR_TIMELIMIT) && timelimitintics > 0) { UINT32 timeval = (timelimitintics + starttime + 1 - leveltime); diff --git a/src/k_grandprix.c b/src/k_grandprix.c index 73cc0d27b..52c82d7a3 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -283,10 +283,10 @@ static INT16 K_RivalScore(player_t *bot) UINT8 lowestdifficulty = MAXBOTDIFFICULTY; UINT8 i; - if (grandprixinfo.cup != NULL) + if (grandprixinfo.cup != NULL && roundqueue.size > 0) { - roundnum = grandprixinfo.roundnum; - roundsleft = grandprixinfo.cup->numlevels - grandprixinfo.roundnum; + roundnum = roundqueue.roundnum; + roundsleft = grandprixinfo.cup->numlevels - roundnum; } for (i = 0; i < MAXPLAYERS; i++) @@ -523,8 +523,10 @@ void K_RetireBots(void) UINT8 i; if (grandprixinfo.gp == true - && ((grandprixinfo.cup != NULL && grandprixinfo.roundnum >= grandprixinfo.cup->numlevels) - || grandprixinfo.eventmode != GPEVENT_NONE)) + && (grandprixinfo.eventmode != GPEVENT_NONE + || (grandprixinfo.cup != NULL + && roundqueue.size > 0 + && roundqueue.roundnum >= grandprixinfo.cup->numlevels))) { // No replacement. return; @@ -566,7 +568,11 @@ void K_RetireBots(void) else { const UINT8 startingdifficulty = K_BotStartingDifficulty(grandprixinfo.gamespeed); - newDifficulty = startingdifficulty - 4 + grandprixinfo.roundnum; + newDifficulty = startingdifficulty - 4; + if (roundqueue.size > 0) + { + newDifficulty += roundqueue.roundnum; + } } if (newDifficulty > MAXBOTDIFFICULTY) @@ -697,7 +703,7 @@ void K_PlayerLoseLife(player_t *player) --------------------------------------------------*/ boolean K_CanChangeRules(boolean allowdemos) { - if (grandprixinfo.gp == true /*&& grandprixinfo.roundnum > 0*/) + if (grandprixinfo.gp == true) { // Don't cheat the rules of the GP! return false; diff --git a/src/k_grandprix.h b/src/k_grandprix.h index 891efc85c..ba19bdb61 100644 --- a/src/k_grandprix.h +++ b/src/k_grandprix.h @@ -31,7 +31,6 @@ typedef enum extern struct grandprixinfo { boolean gp; ///< If true, then we are in a Grand Prix. - UINT8 roundnum; ///< Round number. If 0, this is a single session from the warp command. cupheader_t *cup; ///< Which cup are we playing? UINT8 gamespeed; ///< Copy of gamespeed, just to make sure you can't cheat it with cvars boolean encore; ///< Ditto, but for encore mode diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 7c798915a..fd17477eb 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -32,7 +32,6 @@ I_mutex k_menu_mutex; #endif boolean menuactive = false; -boolean fromlevelselect = false; // current menudef menu_t *currentMenu = &MAIN_ProfilesDef; diff --git a/src/menus/transient/cup-select.c b/src/menus/transient/cup-select.c index f4f97ed29..8c2ed8fe1 100644 --- a/src/menus/transient/cup-select.c +++ b/src/menus/transient/cup-select.c @@ -104,7 +104,6 @@ void M_CupSelectHandler(INT32 choice) if (cupgrid.grandprix == true) { - INT32 levelNum; UINT8 ssplayers = cv_splitplayers.value-1; S_StartSound(NULL, sfx_s3k63); @@ -130,13 +129,15 @@ void M_CupSelectHandler(INT32 choice) grandprixinfo.gamespeed = min(KARTSPEED_HARD, cv_dummygpdifficulty.value); grandprixinfo.masterbots = (cv_dummygpdifficulty.value == 3); - grandprixinfo.encore = (boolean)cv_dummygpencore.value; - - grandprixinfo.cup = newcup; grandprixinfo.gp = true; - grandprixinfo.roundnum = 1; grandprixinfo.initalize = true; + grandprixinfo.cup = newcup; + + // Populate the roundqueue + memset(&roundqueue, 0, sizeof(struct roundqueue)); + G_GPCupIntoRoundQueue(newcup, levellist.newgametype, (boolean)cv_dummygpencore.value); + roundqueue.position = roundqueue.roundnum = 1; paused = false; @@ -146,16 +147,14 @@ void M_CupSelectHandler(INT32 choice) SV_StartSinglePlayerServer(levellist.newgametype, levellist.netgame); } - levelNum = grandprixinfo.cup->cachedlevels[0]; - D_MapChange( - levelNum + 1, - GT_RACE, - grandprixinfo.encore, + roundqueue.entries[0].mapnum + 1, + roundqueue.entries[0].gametype, + roundqueue.entries[0].encore, true, 1, false, - false + roundqueue.entries[0].rankrestricted ); M_ClearMenus(true); diff --git a/src/p_saveg.c b/src/p_saveg.c index d66ea3f20..536d241dc 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -49,15 +49,16 @@ savedata_t savedata; // Block UINT32s to attempt to ensure that the correct data is // being sent and received -#define ARCHIVEBLOCK_MISC 0x7FEEDEED -#define ARCHIVEBLOCK_PLAYERS 0x7F448008 -#define ARCHIVEBLOCK_PARTIES 0x7F87AF0C -#define ARCHIVEBLOCK_WORLD 0x7F8C08C0 -#define ARCHIVEBLOCK_POBJS 0x7F928546 -#define ARCHIVEBLOCK_THINKERS 0x7F37037C -#define ARCHIVEBLOCK_SPECIALS 0x7F228378 -#define ARCHIVEBLOCK_WAYPOINTS 0x7F46498F -#define ARCHIVEBLOCK_RNG 0x7FAAB5BD +#define ARCHIVEBLOCK_MISC 0x7FEEDEED +#define ARCHIVEBLOCK_PLAYERS 0x7F448008 +#define ARCHIVEBLOCK_PARTIES 0x7F87AF0C +#define ARCHIVEBLOCK_ROUNDQUEUE 0x7F721331 +#define ARCHIVEBLOCK_WORLD 0x7F8C08C0 +#define ARCHIVEBLOCK_POBJS 0x7F928546 +#define ARCHIVEBLOCK_THINKERS 0x7F37037C +#define ARCHIVEBLOCK_SPECIALS 0x7F228378 +#define ARCHIVEBLOCK_WAYPOINTS 0x7F46498F +#define ARCHIVEBLOCK_RNG 0x7FAAB5BD // Note: This cannot be bigger // than an UINT16 @@ -932,6 +933,48 @@ static void P_NetUnArchiveParties(savebuffer_t *save) } } +static void P_NetArchiveRoundQueue(savebuffer_t *save) +{ + UINT8 i; + WRITEUINT32(save->p, ARCHIVEBLOCK_ROUNDQUEUE); + + WRITEUINT8(save->p, roundqueue.position); + WRITEUINT8(save->p, roundqueue.size); + WRITEUINT8(save->p, roundqueue.roundnum); + + for (i = 0; i < roundqueue.size; i++) + { + //WRITEUINT16(save->p, roundqueue.entries[i].mapnum); + /* NOPE! Clients do not need to know what is in the roundqueue. + This disincentivises cheaty clients in future tournament environments. + ~toast 080423 */ + WRITEUINT8(save->p, roundqueue.entries[i].gametype); + WRITEUINT8(save->p, (UINT8)roundqueue.entries[i].encore); + WRITEUINT8(save->p, (UINT8)roundqueue.entries[i].rankrestricted); + } +} + +static void P_NetUnArchiveRoundQueue(savebuffer_t *save) +{ + UINT8 i; + if (READUINT32(save->p) != ARCHIVEBLOCK_ROUNDQUEUE) + I_Error("Bad $$$.sav at archive block Parties"); + + memset(&roundqueue, 0, sizeof(struct roundqueue)); + + roundqueue.position = READUINT8(save->p); + roundqueue.size = READUINT8(save->p); + roundqueue.roundnum = READUINT8(save->p); + + for (i = 0; i < roundqueue.size; i++) + { + roundqueue.entries[i].mapnum = 0; // TEST RUN -- dummy, has to be < nummapheaders + roundqueue.entries[i].gametype = READUINT8(save->p); + roundqueue.entries[i].encore = (boolean)READUINT8(save->p); + roundqueue.entries[i].rankrestricted = (boolean)READUINT8(save->p); + } +} + /// /// Colormaps /// @@ -5400,6 +5443,7 @@ void P_SaveNetGame(savebuffer_t *save, boolean resending) P_NetArchivePlayers(save); P_NetArchiveParties(save); + P_NetArchiveRoundQueue(save); if (gamestate == GS_LEVEL) { @@ -5450,6 +5494,7 @@ boolean P_LoadNetGame(savebuffer_t *save, boolean reloading) P_NetUnArchivePlayers(save); P_NetUnArchiveParties(save); + P_NetUnArchiveRoundQueue(save); if (gamestate == GS_LEVEL) { diff --git a/src/st_stuff.c b/src/st_stuff.c index 5dde5c826..9604d66a8 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -696,7 +696,7 @@ void ST_runTitleCard(void) { boolean run = !(paused || P_AutoPause()); INT32 auxticker; - boolean gp = (marathonmode || (grandprixinfo.gp && grandprixinfo.roundnum)); + boolean doroundicon = (marathonmode || roundqueue.size > 0); if (!G_IsTitleCardAvailable()) return; @@ -818,7 +818,7 @@ void ST_runTitleCard(void) roundnumy = eggy1; // split both halves of the egg, but only do that in grand prix! - if (gp && lt_ticker > TTANIMTHRESHOLD + TICRATE/2) + if (doroundicon && lt_ticker > TTANIMTHRESHOLD + TICRATE/2) { auxticker = (INT32)lt_ticker - (TTANIMTHRESHOLD + TICRATE/2); @@ -881,7 +881,7 @@ void ST_drawTitleCard(void) char *lvlttl = mapheaderinfo[gamemap-1]->lvlttl; char *zonttl = mapheaderinfo[gamemap-1]->zonttl; // SRB2kart UINT8 actnum = mapheaderinfo[gamemap-1]->actnum; - boolean gp = (marathonmode || (grandprixinfo.gp && grandprixinfo.roundnum)); + boolean doroundicon = (marathonmode || roundqueue.size > 0); INT32 acttimer; fixed_t actscale; @@ -1011,9 +1011,9 @@ void ST_drawTitleCard(void) // Draw ROUND bar, scroll it downwards. V_DrawFixedPatch(roundx*FRACUNIT, ((-32) + (lt_ticker%32))*FRACUNIT, FRACUNIT, V_SNAPTOTOP|V_SNAPTOLEFT, tcroundbar, NULL); // Draw ROUND text - if (gp) + if (doroundicon) V_DrawFixedPatch((roundx+10)*FRACUNIT, roundy*FRACUNIT, FRACUNIT, V_SNAPTOTOP|V_SNAPTOLEFT, - ((grandprixinfo.gp && grandprixinfo.eventmode) ? tcbonus : tcround), + ((grandprixinfo.gp && grandprixinfo.eventmode != GPEVENT_NONE) ? tcbonus : tcround), NULL); // round num background @@ -1032,25 +1032,29 @@ void ST_drawTitleCard(void) } // If possible, draw round number/icon - if (gp) + if (doroundicon) { patch_t *roundico = NULL; if (marathonmode) ; // TODO: Ruby - else switch (grandprixinfo.eventmode) + else if (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE) { - case GPEVENT_BONUS: - roundico = tcroundbonus; // TODO don't show capsule if we have other bonus types - break; - /*case GPEVENT_SPECIAL: - ; // TODO: Emerald/mount - break;*/ - case GPEVENT_NONE: - if (grandprixinfo.roundnum > 0 && grandprixinfo.roundnum < 11) // Check boundaries JUST IN CASE. - roundico = tcroundnum[grandprixinfo.roundnum-1]; - break; - default: - break; + switch (grandprixinfo.eventmode) + { + case GPEVENT_BONUS: + roundico = tcroundbonus; // TODO don't show capsule if we have other bonus types + break; + /*case GPEVENT_SPECIAL: + ; // TODO: Emerald/mount + break;*/ + default: + break; + } + } + else if (roundqueue.size > 0) + { + if (roundqueue.roundnum > 0 && roundqueue.roundnum < 11) // We DEFINITELY need to check boundaries. + roundico = tcroundnum[roundqueue.roundnum-1]; } if (roundico) diff --git a/src/typedef.h b/src/typedef.h index 2683bff70..416fd36f9 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -135,6 +135,7 @@ TYPEDEF (menudemo_t); TYPEDEF (demoghost); // g_game.h +TYPEDEF (roundentry_t); TYPEDEF (mapsearchfreq_t); // hu_stuff.h