diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 320d63200..8b41ce616 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -6171,7 +6171,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 // fun fact: this used to be located in NetUpdate! // as it turns out, reloading the level in rendering code is a very bad idea! diff --git a/src/d_main.cpp b/src/d_main.cpp index 99c8d8672..0cd6991fb 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -1200,8 +1200,9 @@ void D_StartTitle(void) modeattacking = ATTACKING_NONE; marathonmode = static_cast(0); - // Reset GP + // Reset GP and roundqueue memset(&grandprixinfo, 0, sizeof(struct grandprixinfo)); + memset(&roundqueue, 0, sizeof(struct roundqueue)); // Reset Server mods numcustomservermods = 0; @@ -1901,14 +1902,13 @@ 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_HARD; - grandprixinfo.encore = false; grandprixinfo.masterbots = false; grandprixinfo.lunaticmode = false; grandprixinfo.gp = true; - grandprixinfo.roundnum = 0; grandprixinfo.cup = NULL; grandprixinfo.wonround = false; diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 075291a65..a0dfea2b1 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -874,6 +874,7 @@ INT16 numgametypes = GT_FIRSTFREESLOT; boolean forceresetplayers = false; boolean deferencoremode = false; +boolean forcespecialstage = false; UINT8 splitscreen = 0; boolean circuitmap = false; INT32 adminplayers[MAXPLAYERS] = {}; @@ -893,7 +894,9 @@ const char *automate_names[AEV__MAX] = { "RoundStart", // AEV_ROUNDSTART "IntermissionStart", // AEV_INTERMISSIONSTART - "VoteStart" // AEV_VOTESTART + "VoteStart", // AEV_VOTESTART + "QueueStart", // AEV_QUEUESTART + "QueueEnd", // AEV_QUEUEEND }; /// \warning Keep this up-to-date if you add/remove/rename net text commands @@ -3317,21 +3320,21 @@ INT32 mapchangepending = 0; * ESC to abort", which calls I_GetKey(), which adds an event. In other words, * 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); @@ -3341,25 +3344,8 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pencoremode, boolean r 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 (bossinfo.boss == true) - { - pencoremode = bossinfo.encore; - } - else if (specialStage.active == true) - { - pencoremode = specialStage.encore; - } - else 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) { @@ -3368,14 +3354,25 @@ 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; + if (roundqueue.netcommunicate) + flags |= 1<<4; WRITEUINT8(buf_p, flags); + if (roundqueue.netcommunicate) + { + // roundqueue state + WRITEUINT8(buf_p, roundqueue.position); + WRITEUINT8(buf_p, roundqueue.size); + WRITEUINT8(buf_p, roundqueue.roundnum); + roundqueue.netcommunicate = false; + } + // new gametype value WRITEUINT8(buf_p, newgametype); @@ -3574,6 +3571,7 @@ static void Command_Map_f(void) size_t option_skill; const char *gametypename; boolean newresetplayers; + boolean newforcespecialstage; boolean mustmodifygame; @@ -3598,6 +3596,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"); mustmodifygame = !(netgame || multiplayer) && !majormods; @@ -3727,7 +3726,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. } // G_TOLFlag handles both multiplayer gametype and ignores it for !multiplayer else @@ -3751,12 +3750,6 @@ static void Command_Map_f(void) Z_Free(mapname); return; } - else - { - fromlevelselect = - ( netgame || multiplayer ) && - grandprixinfo.gp != false; - } } if (!(netgame || multiplayer)) @@ -3845,36 +3838,41 @@ static void Command_Map_f(void) } } - grandprixinfo.encore = newencoremode; - grandprixinfo.gp = true; - grandprixinfo.roundnum = 0; - grandprixinfo.cup = NULL; grandprixinfo.wonround = false; bossinfo.boss = false; specialStage.active = false; - grandprixinfo.initalize = true; + if (!Playing()) + { + UINT8 ssplayers = cv_splitplayers.value-1; + + grandprixinfo.cup = NULL; + grandprixinfo.initalize = true; + + multiplayer = true; + + CopyCaretColors(connectedservername, cv_servername.string, MAXSERVERNAME); + CopyCaretColors(connectedservercontact, cv_server_contact.string, MAXSERVERCONTACT); + strncpy(connectedserverdescription, DEFAULTDESCSTRING, MAXSERVERDESCRIPTION); + + if (cv_maxplayers.value < ssplayers+1) + CV_SetValue(&cv_maxplayers, ssplayers+1); + + if (splitscreen != ssplayers) + { + splitscreen = ssplayers; + SplitScreen_OnChange(); + } + } } } } - // Prevent warping to locked levels - // ... unless you're in a dedicated server. Yes, technically this means you can view any level by - // running a dedicated server and joining it yourself, but that's better than making dedicated server's - // lives hell. - if (!dedicated && M_MapLocked(newmapnum)) - { - CONS_Alert(CONS_NOTICE, M_GetText("You need to unlock this level before you can warp to it!\n")); - Z_Free(realmapname); - Z_Free(mapname); - return; - } - tutorialmode = false; // warping takes us out of tutorial mode - D_MapChange(newmapnum, newgametype, newencoremode, newresetplayers, 0, false, fromlevelselect); + D_MapChange(newmapnum, newgametype, newencoremode, newresetplayers, 0, false, newforcespecialstage); Z_Free(realmapname); } @@ -3889,9 +3887,9 @@ static void Command_Map_f(void) static void Got_Mapcmd(UINT8 **cp, INT32 playernum) { UINT8 flags; - INT32 resetplayer = 1, lastgametype; - UINT8 skipprecutscene, FLS; - boolean pencoremode; + INT32 presetplayer = 1, lastgametype; + UINT8 skipprecutscene, pforcespecialstage; + boolean pencoremode, hasroundqueuedata; INT16 mapnumber; forceresetplayers = deferencoremode = false; @@ -3904,14 +3902,53 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum) return; } - if (chmappending) - chmappending--; - flags = READUINT8(*cp); pencoremode = ((flags & 1) != 0); - resetplayer = ((flags & (1<<1)) == 0); + presetplayer = ((flags & (1<<1)) != 0); + + skipprecutscene = ((flags & (1<<2)) != 0); + + pforcespecialstage = ((flags & (1<<3)) != 0); + + hasroundqueuedata = ((flags & (1<<4)) != 0); + + if (hasroundqueuedata) + { + UINT8 position = READUINT8(*cp); + UINT8 size = READUINT8(*cp); + UINT8 roundnum = READUINT8(*cp); + + if (playernum != serverplayer // Clients, even admin clients, don't have full roundqueue data + || position > size // Sanity check A (intentionally not a >= comparison) + || size > ROUNDQUEUE_MAX) // Sanity Check B (ditto) + { + CONS_Alert(CONS_WARNING, M_GetText("Illegal round-queue data received from %s\n"), player_names[playernum]); + if (server && playernum != serverplayer) + SendKick(playernum, KICK_MSG_CON_FAIL); + return; + } + + roundqueue.position = position; + if (size < roundqueue.size) + { + // Bafooliganism afoot - set to zero if the size is zero! ~toast 150523 + roundqueue.size = size; + } + else while (roundqueue.size < size) + { + // We wipe rather than provide full data to prevent bloating the packet, + // and because only this data is necessary for sync. ~toast 100423 + memset(&roundqueue.entries[roundqueue.size], 0, sizeof(roundentry_t)); + roundqueue.size++; + } + roundqueue.roundnum = roundnum; // no sanity checking required, server is authoriative + } + + // No more kicks below this line, we can now start modifying state beyond this function. + if (chmappending) + chmappending--; lastgametype = gametype; gametype = READUINT8(*cp); @@ -3922,20 +3959,32 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum) else if (gametype != lastgametype) D_GameTypeChanged(lastgametype); // emulate consvar_t behavior for gametype + if (hasroundqueuedata && roundqueue.position > 0 && roundqueue.size > 0) + { + // ...we can evaluate CURRENT specifics for roundqueue data, though. + roundqueue.entries[roundqueue.position-1].gametype = gametype; + roundqueue.entries[roundqueue.position-1].encore = pencoremode; + } + if (!(gametypes[gametype]->rules & GTR_ENCORE) && !bossinfo.boss) pencoremode = false; - skipprecutscene = ((flags & (1<<2)) != 0); - - FLS = ((flags & (1<<3)) != 0); - mapnumber = READINT16(*cp); + // Handle some Grand Prix state. if (grandprixinfo.gp) { - if (gametype != GT_RACE) + boolean caughtretry = (gametype == lastgametype + && mapnumber == gamemap); + if (pforcespecialstage // Forced. + || (caughtretry && grandprixinfo.eventmode == GPEVENT_SPECIAL) // Catch retries of forced. + /*|| (roundqueue.size == 0 && (gametyperules & (GTR_BOSS|GTR_CATCHER)))*/) // Force convention for the (queue)map command. { - grandprixinfo.eventmode = GPEVENT_CHALLENGE; + grandprixinfo.eventmode = GPEVENT_SPECIAL; + } + else if (gametype != GT_RACE) + { + grandprixinfo.eventmode = GPEVENT_BONUS; } else { @@ -3949,16 +3998,10 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum) 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 (resetplayer && !FLS) - { - emeralds = 0; - memset(&luabanks, 0, sizeof(luabanks)); - } - demo.savemode = (cv_recordmultiplayerdemos.value == 2) ? DSM_WILLAUTOSAVE : DSM_NOTSAVING; demo.savebutton = 0; @@ -3966,7 +4009,7 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum) if (demo.recording && !modeattacking) G_CheckDemoStatus(); - G_InitNew(pencoremode, mapnumber, resetplayer, skipprecutscene, FLS); + G_InitNew(pencoremode, mapnumber, presetplayer, skipprecutscene); if (demo.timing) G_DoneLevelLoad(); diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 649906854..a5a041e25 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -402,7 +402,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); @@ -438,6 +438,8 @@ typedef enum AEV_ROUNDSTART, AEV_INTERMISSIONSTART, AEV_VOTESTART, + AEV_QUEUESTART, + AEV_QUEUEEND, AEV__MAX } automateEvents_t; diff --git a/src/deh_soc.c b/src/deh_soc.c index 967afe5ac..4eaa51263 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -3722,6 +3722,41 @@ void readmaincfg(MYFILE *f) Z_Free(s); } +// +// 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; +} + +static char *MapNameOrRemoval(char *name) +{ + if (name[0] == '\0' + || (name[0] == '/' && name[1] == '\0')) + return NULL; + + return Z_StrDup(name); +} + void readwipes(MYFILE *f) { char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL); @@ -3931,7 +3966,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,","); @@ -3949,9 +3984,32 @@ void readcupheader(MYFILE *f, cupheader_t *cup) } else if (fastcmp(word, "BONUSGAME")) { - Z_Free(cup->levellist[CUPCACHE_BONUS]); - cup->levellist[CUPCACHE_BONUS] = Z_StrDup(word2); - cup->cachedlevels[CUPCACHE_BONUS] = NEXTMAP_INVALID; + while (cup->numbonus > 0) + { + 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,","); + do { + if (cup->numbonus >= MAXBONUSLIST) + { + deh_warning("%s Cup: reached max bonus list (%d)\n", cup->name, MAXBONUSLIST); + break; + } + + cup->levellist[CUPCACHE_BONUS + cup->numbonus] = MapNameOrRemoval(tmp); + cup->cachedlevels[CUPCACHE_BONUS + cup->numbonus] = NEXTMAP_INVALID; + + if (cup->levellist[CUPCACHE_BONUS + cup->numbonus] == NULL) + break; + + cup->numbonus++; + } while((tmp = strtok(NULL,",")) != NULL); } else if (fastcmp(word, "SPECIALSTAGE")) { diff --git a/src/doomstat.h b/src/doomstat.h index 806bd7a7a..6413d612a 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -152,8 +152,7 @@ extern UINT8 splitscreen; extern int r_splitscreen; extern boolean circuitmap; // Does this level have 'circuit mode'? -extern boolean fromlevelselect; -extern boolean forceresetplayers, deferencoremode; +extern boolean forceresetplayers, deferencoremode, forcespecialstage; // ======================================== // Internal parameters for sound rendering. @@ -330,8 +329,10 @@ struct mapheader_lighting_t // Keep in mind that it may encourage people making overly long cups just because they "can", and would be a waste of memory. #define MAXLEVELLIST 5 #define CUPCACHE_BONUS MAXLEVELLIST -#define CUPCACHE_SPECIAL MAXLEVELLIST+1 -#define CUPCACHE_MAX CUPCACHE_SPECIAL+1 +#define MAXBONUSLIST 2 +#define CUPCACHE_SPECIAL (CUPCACHE_BONUS+MAXBONUSLIST) +#define CUPCACHE_PODIUM (CUPCACHE_SPECIAL+1) +#define CUPCACHE_MAX (CUPCACHE_PODIUM+1) #define MAXCUPNAME 16 // includes \0, for cleaner savedata @@ -348,6 +349,7 @@ struct cupheader_t char *levellist[CUPCACHE_MAX]; ///< List of levels that belong to this cup INT16 cachedlevels[CUPCACHE_MAX]; ///< IDs in levellist, bonusgame, and specialstage UINT8 numlevels; ///< Number of levels defined in levellist + UINT8 numbonus; ///< Number of bonus stages defined UINT8 emeraldnum; ///< ID of Emerald to use for special stage (1-7 for Chaos Emeralds, 8-14 for Super Emeralds, 0 for no emerald) SINT8 unlockrequired; ///< An unlockable is required to select this cup. -1 for no unlocking required. cupheader_t *next; ///< Next cup in linked list diff --git a/src/g_demo.c b/src/g_demo.c index 2864c223f..77f4f8ac0 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -4001,7 +4001,7 @@ void G_DoPlayDemo(char *defdemoname) R_ExecuteSetViewSize(); P_SetRandSeed(header.randseed); - 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. // Setup server name, contact and description. CopyCaretColors(connectedservername, header.servername, MAXSERVERNAME); diff --git a/src/g_game.c b/src/g_game.c index a791a7607..142db29d9 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2135,6 +2135,11 @@ void G_DoLoadLevel(boolean resetplayer) if (doAutomate == true) { + if (roundqueue.size > 0 && roundqueue.position == 1) + { + Automate_Run(AEV_QUEUESTART); + } + Automate_Run(AEV_ROUNDSTART); } } @@ -4197,6 +4202,25 @@ INT32 G_GetGametypeByName(const char *gametypestr) return -1; // unknown gametype } +// +// G_GuessGametypeByTOL +// +// Returns the first valid number for the given typeoflevel, or -1 if not valid. +// +INT32 G_GuessGametypeByTOL(UINT32 tol) +{ + INT32 i = 0; + + while (gametypes[i] != NULL) + { + if (tol & gametypes[i]->tol) + return i; + i++; + } + + return -1; // unknown gametype +} + // // G_SetGametype // @@ -4857,10 +4881,126 @@ static void G_HandleSaveLevel(void) G_SaveGame((UINT32)cursaveslot, lastmap+1); // not nextmap+1 to route around special stages } -static INT16 G_GetNextMap(boolean advancemap) +// Next map apparatus +struct roundqueue roundqueue; + +void G_MapSlipIntoRoundQueue(UINT8 position, UINT16 map, UINT8 setgametype, boolean setencore, boolean rankrestricted) +{ + I_Assert(position < ROUNDQUEUE_MAX); + + roundqueue.entries[position].mapnum = map; + roundqueue.entries[position].gametype = setgametype; + roundqueue.entries[position].encore = setencore; + roundqueue.entries[position].rankrestricted = rankrestricted; +} + +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; + } + + G_MapSlipIntoRoundQueue(roundqueue.size, map, setgametype, setencore, rankrestricted); + + roundqueue.size++; +} + +void G_GPCupIntoRoundQueue(cupheader_t *cup, UINT8 setgametype, boolean setencore) +{ + UINT8 i, levelindex = 0, bonusindex = 0; + UINT8 bonusmodulo = max(1, (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!?"); + } +} + +void G_GetNextMap(void) { boolean spec = G_IsSpecialStage(prevmap+1); INT32 i; + boolean setalready = false; + + if (!server) + { + // Server is authoriative, not you + return; + } + + deferencoremode = (cv_kartencore.value == 1); + forceresetplayers = forcespecialstage = false; + INT16 newmap, curmap = gamestate == GS_LEVEL ? gamemap-1 : prevmap; // go to next level @@ -4868,131 +5008,97 @@ static INT16 G_GetNextMap(boolean advancemap) if (nextmapoverride != 0) { newmap = (INT16)(nextmapoverride-1); + setalready = true; } - else if (grandprixinfo.gp == true) + else if (roundqueue.size > 0) { - // G: oh dear, this whole GP block has loads of side effects... - // for now, just repeat the same map for "map +" - // you're not supposed to use that in GP anyways - if (!advancemap || // ...right? - grandprixinfo.roundnum == 0 || grandprixinfo.cup == NULL) // Single session + boolean permitrank = false; + /*if (grandprixinfo.gp == true + && grandprixinfo.gamespeed >= KARTSPEED_NORMAL) { - newmap = curmap; // Same map + // On A rank pace? Then you get a chance for S rank! + permitrank = (K_CalculateGPPercent(&grandprixinfo.rank) >= K_SealedStarEntryRequirement(&grandprixinfo.rank)); + + // If you're on Master, a win floats you to rank-restricted levels for free. + // (This is a different class of challenge!) + if (grandprixinfo.masterbots && grandprixinfo.rank.position <= 1) + permitrank = true; + }*/ + + 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; + // 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 || grandprixinfo.eventmode != GPEVENT_NONE) + if (grandprixinfo.gp == true) { - grandprixinfo.eventmode = GPEVENT_NONE; - - G_SetGametype(GT_RACE); - if (gametype != lastgametype) - D_GameTypeChanged(lastgametype); - - specialStage.active = false; - bossinfo.boss = false; - } - // Special stage - else if (grandprixinfo.roundnum >= grandprixinfo.cup->numlevels) - { - INT16 totaltotalring = 0; - - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i]) - continue; - if (players[i].spectator) - continue; - if (players[i].bot) - continue; - totaltotalring += players[i].totalring; - } - - if (totaltotalring >= 50) - { - const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[CUPCACHE_SPECIAL]; - if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum] - && mapheaderinfo[cupLevelNum]->typeoflevel & (TOL_SPECIAL|TOL_BOSS|TOL_BATTLE)) - { - grandprixinfo.eventmode = GPEVENT_SPECIAL; - newmap = cupLevelNum; - } - } - } - else if (grandprixinfo.roundnum == (grandprixinfo.cup->numlevels+1)/2) // 3 for a 5-map cup - { - // todo any other condition? - { - const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[CUPCACHE_BONUS]; - if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum] - && mapheaderinfo[cupLevelNum]->typeoflevel & (TOL_BOSS|TOL_BATTLE)) - { - grandprixinfo.eventmode = GPEVENT_CHALLENGE; - newmap = cupLevelNum; - } - } - } - - if (grandprixinfo.eventmode != GPEVENT_NONE) - { - // nextmap is set above - const INT32 newtol = mapheaderinfo[newmap]->typeoflevel; - - if (newtol & TOL_SPECIAL) - { - specialStage.active = true; - specialStage.encore = grandprixinfo.encore; - } - else //(if newtol & (TOL_BATTLE|TOL_BOSS)) -- safe to assume?? - { - G_SetGametype(GT_BATTLE); - if (gametype != lastgametype) - D_GameTypeChanged(lastgametype); - if (newtol & TOL_BOSS) - { - K_ResetBossInfo(); - bossinfo.boss = true; - bossinfo.encore = grandprixinfo.encore; - } - } - } - else if (grandprixinfo.roundnum >= grandprixinfo.cup->numlevels) // On final map - { - newmap = 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]) - { - newmap = cupLevelNum; - } - else - { - newmap = curmap; // Prevent uninitialised use - } - - 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; } } + + // Make sure the next D_MapChange sends updated roundqueue state. + roundqueue.netcommunicate = true; } - else if (bossinfo.boss == true) + else if (grandprixinfo.gp == true) { - newmap = NEXTMAP_TITLE; // temporary + // Fast And Rapid Testing + // this codepath is exclusively accessible through console/command line + nextmap = prevmap; + setalready = true; } - else + + if (setalready == false) { UINT32 tolflag = G_TOLFlag(gametype); register INT16 cm; - if (grandprixinfo.gp == true) + if (true/*!(gametyperules & GTR_NOCUPSELECT)*/) { cupheader_t *cup = mapheaderinfo[gamemap-1]->cup; UINT8 gettingresult = 0; @@ -5000,12 +5106,12 @@ static INT16 G_GetNextMap(boolean advancemap) while (cup) { // Not unlocked? Grab the next result afterwards - if (!marathonmode && cup->unlockrequired != -1 && !unlockables[cup->unlockrequired].unlocked) + /*if (!marathonmode && M_CupLocked(cup)) { cup = cup->next; gettingresult = 1; continue; - } + }*/ for (i = 0; i < cup->numlevels; i++) { @@ -5019,16 +5125,22 @@ static INT16 G_GetNextMap(boolean advancemap) || (!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) { - newmap = cm; + nextmap = cm; gettingresult = 2; break; } // Not the map you're on? - if (cm != curmap) + if (cm != prevmap) { continue; } @@ -5050,16 +5162,16 @@ static INT16 G_GetNextMap(boolean advancemap) // Didn't get a nextmap before reaching the end? if (gettingresult != 2) { - newmap = NEXTMAP_CEREMONY; // ceremonymap + nextmap = NEXTMAP_CEREMONY; // ceremonymap } } else { - cm = curmap; + cm = prevmap; if (++cm >= nummapheaders) cm = 0; - while (cm != curmap) + while (cm != prevmap) { if (!mapheaderinfo[cm] || mapheaderinfo[cm]->lumpnum == LUMPERROR @@ -5075,40 +5187,42 @@ static INT16 G_GetNextMap(boolean advancemap) break; } - newmap = cm; + nextmap = cm; } - if (advancemap && K_CanChangeRules(true)) + if (K_CanChangeRules(true)) { - switch (cv_advancemap.value) + if (!netgame) // Match Race. + nextmap = NEXTMAP_TITLE; + else switch (cv_advancemap.value) { case 0: // Stay on same map. - newmap = curmap; + nextmap = prevmap; break; case 3: // Voting screen. + { + for (i = 0; i < MAXPLAYERS; i++) { - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i]) - continue; - if (players[i].spectator) - continue; - break; - } - if (i != MAXPLAYERS) - { - newmap = NEXTMAP_VOTING; - break; - } + if (!playeringame[i]) + continue; + if (players[i].spectator) + continue; + break; } - /* FALLTHRU */ + if (i != MAXPLAYERS) + { + nextmap = NEXTMAP_VOTING; + break; + } + } + /* FALLTHRU */ case 2: // Go to random map. - newmap = G_RandMap(G_TOLFlag(gametype), curmap, 0, 0, NULL); + nextmap = G_RandMap(G_TOLFlag(gametype), prevmap, false, false, NULL); break; default: - if (newmap >= NEXTMAP_SPECIAL) // Loop back around + if (nextmap >= NEXTMAP_SPECIAL) // Loop back around { - newmap = G_GetFirstMapOfGametype(gametype); + nextmap = G_GetFirstMapOfGametype(gametype); } break; } @@ -5119,10 +5233,10 @@ static INT16 G_GetNextMap(boolean advancemap) if (newmap == NEXTMAP_INVALID || (newmap < NEXTMAP_SPECIAL && (newmap >= nummapheaders || !mapheaderinfo[newmap] || mapheaderinfo[newmap]->lumpnum == LUMPERROR))) I_Error("G_GetNextMap: Internal map ID %d not found (nummapheaders = %d)\n", newmap, nummapheaders); +#if 0 // This is a surprise tool that will help us later. if (!spec) +#endif //#if 0 lastmap = newmap; - - return newmap; } // @@ -5244,7 +5358,7 @@ void G_AfterIntermission(void) if (gamestate != GS_VOTING) { - nextmap = G_GetNextMap(true); + G_GetNextMap(); G_HandleSaveLevel(); } @@ -5270,10 +5384,6 @@ void G_NextLevel(void) return; } - forceresetplayers = false; - if (cv_kartencore.value == 1) - deferencoremode = true; - gameaction = ga_worlddone; } @@ -5288,7 +5398,7 @@ static void G_DoWorldDone(void) forceresetplayers, 0, false, - false); + forcespecialstage); } gameaction = ga_nothing; @@ -6125,14 +6235,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; - Y_CleanupScreenBuffer(); if (paused) @@ -6160,7 +6268,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; @@ -6465,7 +6573,11 @@ INT32 G_FindMapByNameOrCode(const char *mapname, char **realmapnamep) if (mapname[0] == '*') // current map return gamemap; else if (mapname[0] == '+') // next map - return G_GetNextMap(false)+1; + { + G_GetNextMap(); + return nextmap; + } + } /* Now detect map number in base 10, which no one asked for. */ diff --git a/src/g_game.h b/src/g_game.h index 0c137714b..ab6d73653 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -53,6 +53,29 @@ 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 + boolean netcommunicate; // As server, should we net-communicate this in XD_MAP? + roundentry_t entries[ROUNDQUEUE_MAX]; // Entries in the round queue +} roundqueue; + +void G_MapSlipIntoRoundQueue(UINT8 position, UINT16 map, UINT8 setgametype, boolean setencore, boolean rankrestricted); +void G_MapIntoRoundQueue(UINT16 map, UINT8 setgametype, boolean setencore, boolean rankrestricted); +void G_GPCupIntoRoundQueue(cupheader_t *cup, UINT8 setgametype, boolean setencore); + extern INT16 kartmap2native[NEXTMAP_SPECIAL], nativemap2kart[NEXTMAP_SPECIAL]; extern INT16 nextexnum; @@ -166,7 +189,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 @@ -221,10 +244,11 @@ void G_SaveGameOver(UINT32 slot, boolean modifylives); void G_SetGametype(INT16 gametype); char *G_PrepareGametypeConstant(const char *newgtconst); -void G_UpdateGametypeSelections(void); -void G_SetGametypeColor(INT16 gtype,INT32 color); void G_AddTOL(UINT32 newtol, const char *tolname); +void G_UpdateGametypeSelections(void); INT32 G_GetGametypeByName(const char *gametypestr); +INT32 G_GuessGametypeByTOL(UINT32 tol); + boolean G_IsSpecialStage(INT32 mapnum); boolean G_GametypeUsesLives(void); boolean G_GametypeHasTeams(void); @@ -235,6 +259,7 @@ UINT8 G_GetGametypeColor(INT16 gt); void G_BeginLevelExit(void); void G_FinishExitLevel(void); void G_NextLevel(void); +void G_GetNextMap(void); void G_Continue(void); void G_UseContinue(void); void G_AfterIntermission(void); diff --git a/src/k_grandprix.c b/src/k_grandprix.c index a588ef45c..74e220d78 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -294,10 +294,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 < MAXGPPLAYERS; i++) @@ -539,8 +539,10 @@ void K_RetireBots(void) UINT16 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; @@ -580,6 +582,10 @@ void K_RetireBots(void) { const UINT8 startingdifficulty = K_BotStartingDifficulty(grandprixinfo.gamespeed); newDifficulty = startingdifficulty - 4; + if (roundqueue.size > 0) + { + newDifficulty += roundqueue.roundnum; + } } } else @@ -738,7 +744,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 5dba53aec..267d3de03 100644 --- a/src/k_grandprix.h +++ b/src/k_grandprix.h @@ -20,14 +20,16 @@ extern "C" { #endif -#define GPEVENT_NONE 0 -#define GPEVENT_CHALLENGE 1 -#define GPEVENT_SPECIAL 2 +typedef enum +{ + GPEVENT_NONE = 0, + GPEVENT_BONUS, + GPEVENT_SPECIAL, +} gpEvent_e; 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_pwrlv.c b/src/k_pwrlv.c index 1aa1875bf..541216450 100644 --- a/src/k_pwrlv.c +++ b/src/k_pwrlv.c @@ -39,23 +39,35 @@ SINT8 encorescramble = -1; SINT8 K_UsingPowerLevels(void) { - SINT8 pt = PWRLV_DISABLED; - - if (!cv_kartusepwrlv.value || !(netgame || (demo.playback && demo.netgame)) || grandprixinfo.gp == true || bossinfo.boss == true) + if (!cv_kartusepwrlv.value) { + // Explicitly forbidden. + return PWRLV_DISABLED; + } + + if (!(netgame || (demo.playback && demo.netgame))) + { + // Servers only. + return PWRLV_DISABLED; + } + + if (roundqueue.size > 0 && roundqueue.position > 0) + { + // When explicit progression is in place, we're going by different rules. return PWRLV_DISABLED; } if (gametype == GT_RACE) { - pt = PWRLV_RACE; + return PWRLV_RACE; } else if (gametype == GT_BATTLE) { - pt = PWRLV_BATTLE; + return PWRLV_BATTLE; } - return pt; + // We do not support PWR for custom gametypes at this moment in time. + return PWRLV_DISABLED; } void K_ClearClientPowerLevels(void) diff --git a/src/m_menu.c b/src/m_menu.c index 846644ee0..865b25002 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -145,8 +145,6 @@ M_waiting_mode_t m_waiting_mode = M_NOT_WAITING; const char *quitmsg[NUM_QUITMESSAGES]; -boolean fromlevelselect = false; - static char menu_text_input_buf[MAXSTRINGLENGTH]; static textinput_t menuinput; @@ -6525,8 +6523,6 @@ INT32 MR_StartGrandPrix(INT32 choice) break; } - grandprixinfo.encore = (boolean)(cv_dummygpencore.value); - for (gpcup = kartcupheaders; gpcup != NULL; gpcup = gpcup->next) { if (gpcup->id == cv_nextcup.value-1) @@ -6534,23 +6530,28 @@ INT32 MR_StartGrandPrix(INT32 choice) } I_Assert(gpcup != NULL); - grandprixinfo.cup = gpcup; grandprixinfo.gp = true; - grandprixinfo.roundnum = 1; grandprixinfo.wonround = false; - grandprixinfo.initalize = true; + grandprixinfo.cup = gpcup; - levelNum = G_MapNumber(grandprixinfo.cup->levellist[0]); + // Populate the roundqueue + memset(&roundqueue, 0, sizeof(struct roundqueue)); + G_GPCupIntoRoundQueue(gpcup, GT_RACE/*levellist.newgametype*/, (boolean)cv_dummygpencore.value); + roundqueue.position = roundqueue.roundnum = 1; + roundqueue.netcommunicate = true; // relevant for future Online GP - G_DeferedInitNew( + D_MapChange( + roundqueue.entries[0].mapnum + 1, + roundqueue.entries[0].gametype, + roundqueue.entries[0].encore, + true, + 1, false, - levelNum + 1, - cv_chooseskin.value, - (UINT8)(cv_splitplayers.value - 1), - false + roundqueue.entries[0].rankrestricted ); + return true; } diff --git a/src/p_saveg.c b/src/p_saveg.c index 6e39c7323..1ad790058 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -420,14 +420,15 @@ static void P_RelinkWaypoint(savebuffer_t *save, waypoint_t **ptr) // 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_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 // Specialized netsynch markers #define PLYRSYNC_ITEMLIST (1) @@ -920,6 +921,48 @@ static void P_NetSyncParties(savebuffer_t *save) TracyCZoneEnd(__zone); } +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 RoundQueue"); + + 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 /// @@ -4562,6 +4605,7 @@ void P_SaveNetGame(savebuffer_t *save, boolean resending) P_NetSyncPlayers(save); P_NetSyncParties(save); + P_NetArchiveRoundQueue(save); if (gamestate == GS_LEVEL) { @@ -4614,6 +4658,7 @@ boolean P_LoadNetGame(savebuffer_t *save, boolean reloading) return false; P_NetSyncPlayers(save); P_NetSyncParties(save); + P_NetUnArchiveRoundQueue(save); if (gamestate == GS_LEVEL) { diff --git a/src/p_tick.c b/src/p_tick.c index 7ce59f111..ed2c17128 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -867,7 +867,8 @@ void P_Ticker(boolean run) } if (leveltime < startingtime) // SRB2Kart - S_ChangeMusicInternal(((grandprixinfo.roundnum > 0) ? "gpstrt" : (encoremode ? "estart" : "kstart")), false); // yes this will be spammed otherwise encore and some stuff WILL overwrite it + S_ChangeMusicInternal(((grandprixinfo.gp == true + ) ? "gpstrt" : (encoremode ? "estart" : "kstart")), false); // yes this will be spammed otherwise encore and some stuff WILL overwrite it else if (leveltime == startingtime) // The GO! sound stops the level start ambience S_StopMusic(); } diff --git a/src/s_sound.c b/src/s_sound.c index 73907e14a..68d54bc13 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -2679,7 +2679,8 @@ void S_InitLevelMusic(boolean fromnetsave) S_StopMusic(); // Starting ambience should always be restarted, if playing. - S_ChangeMusicEx(((grandprixinfo.roundnum > 0) ? "gpstrt" : (encoremode ? "estart" : "kstart")), 0, false, mapmusposition, 0, 0); + S_ChangeMusicEx(((grandprixinfo.gp == true + ) ? "gpstrt" : (encoremode ? "estart" : "kstart")), 0, false, mapmusposition, 0, 0); S_ResetMusicStack(); music_stack_noposition = false; diff --git a/src/typedef.h b/src/typedef.h index 72b26bc8b..80d1d76b7 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -134,6 +134,7 @@ TYPEDEF (menudemo_t); TYPEDEF (demoghost); // g_game.h +TYPEDEF (roundentry_t); TYPEDEF (mapsearchfreq_t); // hu_stuff.h diff --git a/src/y_inter.c b/src/y_inter.c index f2a9e66a7..be1d7fe17 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -1201,6 +1201,11 @@ void Y_StartIntermission(void) K_CashInPowerLevels(); } + if (roundqueue.size > 0 && roundqueue.position == roundqueue.size) + { + Automate_Run(AEV_QUEUEEND); + } + bgtile = W_CachePatchName("SRB2BACK", PU_STATIC); useinterpic = false; Automate_Run(AEV_INTERMISSIONSTART);