Port basics of roundqueue

This commit is contained in:
NepDisk 2026-03-25 22:52:54 -04:00
parent 266453f15a
commit 24373da780
18 changed files with 598 additions and 282 deletions

View file

@ -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!

View file

@ -1200,8 +1200,9 @@ void D_StartTitle(void)
modeattacking = ATTACKING_NONE;
marathonmode = static_cast<marathonmode_t>(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;

View file

@ -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 <graue@oceanbase.org>
*/
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();

View file

@ -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;

View file

@ -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"))
{

View file

@ -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

View file

@ -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);

View file

@ -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. */

View file

@ -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);

View file

@ -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;

View file

@ -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

View file

@ -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)

View file

@ -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;
}

View file

@ -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)
{

View file

@ -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();
}

View file

@ -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;

View file

@ -134,6 +134,7 @@ TYPEDEF (menudemo_t);
TYPEDEF (demoghost);
// g_game.h
TYPEDEF (roundentry_t);
TYPEDEF (mapsearchfreq_t);
// hu_stuff.h

View file

@ -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);