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.
This commit is contained in:
toaster 2023-04-08 20:42:27 +01:00
parent 06763da0e7
commit 19ef96351a
16 changed files with 375 additions and 233 deletions

View file

@ -6086,7 +6086,7 @@ boolean TryRunTics(tic_t realtics)
{ {
COM_BufTicker(); COM_BufTicker();
if (mapchangepending) 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(); NetUpdate();

View file

@ -1021,8 +1021,9 @@ void D_ClearState(void)
modeattacking = ATTACKING_NONE; modeattacking = ATTACKING_NONE;
marathonmode = 0; marathonmode = 0;
// Reset GP // Reset GP and roundqueue
memset(&grandprixinfo, 0, sizeof(struct grandprixinfo)); 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 // empty maptol so mario/etc sounds don't play in sound test when they shouldn't
maptol = 0; maptol = 0;
@ -1740,13 +1741,12 @@ void D_SRB2Main(void)
// Start up a "minor" grand prix session // Start up a "minor" grand prix session
memset(&grandprixinfo, 0, sizeof(struct grandprixinfo)); memset(&grandprixinfo, 0, sizeof(struct grandprixinfo));
memset(&roundqueue, 0, sizeof(struct roundqueue));
grandprixinfo.gamespeed = KARTSPEED_NORMAL; grandprixinfo.gamespeed = KARTSPEED_NORMAL;
grandprixinfo.encore = false;
grandprixinfo.masterbots = false; grandprixinfo.masterbots = false;
grandprixinfo.gp = true; grandprixinfo.gp = true;
grandprixinfo.roundnum = 0;
grandprixinfo.cup = NULL; grandprixinfo.cup = NULL;
grandprixinfo.wonround = false; grandprixinfo.wonround = false;
@ -1997,11 +1997,6 @@ void D_SRB2Main(void)
D_GameTypeChanged(j); D_GameTypeChanged(j);
} }
if (gametyperules & (GTR_BOSS|GTR_CATCHER))
grandprixinfo.eventmode = GPEVENT_SPECIAL;
else if (gametype != GT_RACE)
grandprixinfo.eventmode = GPEVENT_BONUS;
multiplayer = true; multiplayer = true;
} }

View file

@ -567,6 +567,8 @@ INT16 numgametypes = GT_FIRSTFREESLOT;
boolean forceresetplayers = false; boolean forceresetplayers = false;
boolean deferencoremode = false; boolean deferencoremode = false;
boolean forcespecialstage = false;
UINT8 splitscreen = 0; UINT8 splitscreen = 0;
INT32 adminplayers[MAXPLAYERS]; INT32 adminplayers[MAXPLAYERS];
@ -2541,42 +2543,27 @@ INT32 mapchangepending = 0;
* 63 old events will get reexecuted, with ridiculous results. Just don't do * 63 old events will get reexecuted, with ridiculous results. Just don't do
* it (without setting delay to 1, which is the current solution). * it (without setting delay to 1, which is the current solution).
* *
* \param mapnum Map number to change to. * \param mapnum Map number to change to.
* \param gametype Gametype to switch to. * \param gametype Gametype to switch to.
* \param pencoremode Is this 'Encore Mode'? * \param pencoremode Is this 'Encore Mode'?
* \param resetplayers 1 to reset player scores and lives and such, 0 not to. * \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 * \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 * 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. * do step one and prepare step two, 2 to do step two.
* \param skipprecutscene To skip the precutscence or not? * \param skipprecutscene To skip the precutscence or not?
* \param pforcespecialstage For certain contexts, forces a special stage.
* \sa D_GameTypeChanged, Command_Map_f * \sa D_GameTypeChanged, Command_Map_f
* \author Graue <graue@oceanbase.org> * \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; static char *buf_p = buf;
// The supplied data are assumed to be good. // The supplied data are assumed to be good.
I_Assert(delay >= 0 && delay <= 2); I_Assert(delay >= 0 && delay <= 2);
/* CONS_Debug(DBG_GAMELOGIC, "Map change: mapnum=%d gametype=%d pencoremode=%d presetplayers=%d delay=%d skipprecutscene=%d pforcespecialstage = %d\n",
if (mapnum != -1) mapnum, newgametype, pencoremode, presetplayers, delay, skipprecutscene, pforcespecialstage);
{
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;
}
if (delay != 2) if (delay != 2)
{ {
@ -2585,14 +2572,19 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pencoremode, boolean r
buf_p = buf; buf_p = buf;
if (pencoremode) if (pencoremode)
flags |= 1; flags |= 1;
if (!resetplayers) if (presetplayers)
flags |= 1<<1; flags |= 1<<1;
if (skipprecutscene) if (skipprecutscene)
flags |= 1<<2; flags |= 1<<2;
if (FLS) if (pforcespecialstage)
flags |= 1<<3; flags |= 1<<3;
WRITEUINT8(buf_p, flags); WRITEUINT8(buf_p, flags);
// roundqueue state
WRITEUINT8(buf_p, roundqueue.position);
WRITEUINT8(buf_p, roundqueue.size);
WRITEUINT8(buf_p, roundqueue.roundnum);
// new gametype value // new gametype value
WRITEUINT8(buf_p, newgametype); WRITEUINT8(buf_p, newgametype);
@ -2784,6 +2776,7 @@ static void Command_Map_f(void)
size_t option_skill; size_t option_skill;
const char *gametypename; const char *gametypename;
boolean newresetplayers; boolean newresetplayers;
boolean newforcespecialstage;
boolean usingcheats; boolean usingcheats;
boolean ischeating; boolean ischeating;
@ -2809,6 +2802,7 @@ static void Command_Map_f(void)
option_encore = COM_CheckPartialParm("-e"); option_encore = COM_CheckPartialParm("-e");
option_skill = COM_CheckPartialParm("-s"); option_skill = COM_CheckPartialParm("-s");
newresetplayers = ! COM_CheckParm("-noresetplayers"); newresetplayers = ! COM_CheckParm("-noresetplayers");
newforcespecialstage = COM_CheckParm("-forcespecialstage");
usingcheats = CV_CheatsEnabled(); usingcheats = CV_CheatsEnabled();
ischeating = (!(netgame || multiplayer)) || (!newresetplayers); ischeating = (!(netgame || multiplayer)) || (!newresetplayers);
@ -2946,7 +2940,7 @@ static void Command_Map_f(void)
// don't use a gametype the map doesn't support // don't use a gametype the map doesn't support
if (cht_debug || option_force || cv_skipmapcheck.value) 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 else
{ {
@ -2961,12 +2955,6 @@ static void Command_Map_f(void)
Z_Free(mapname); Z_Free(mapname);
return; return;
} }
else
{
fromlevelselect =
( netgame || multiplayer ) &&
grandprixinfo.gp != false;
}
} }
if (!(netgame || multiplayer)) if (!(netgame || multiplayer))
@ -3011,29 +2999,16 @@ static void Command_Map_f(void)
} }
} }
grandprixinfo.encore = newencoremode;
grandprixinfo.gp = true; grandprixinfo.gp = true;
grandprixinfo.roundnum = 0;
grandprixinfo.cup = NULL;
grandprixinfo.wonround = false; 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()) if (!Playing())
{ {
UINT8 ssplayers = cv_splitplayers.value-1; UINT8 ssplayers = cv_splitplayers.value-1;
grandprixinfo.cup = NULL;
grandprixinfo.initalize = true;
multiplayer = true; multiplayer = true;
restoreMenu = NULL; 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); Z_Free(realmapname);
} }
@ -3065,8 +3040,8 @@ static void Command_Map_f(void)
static void Got_Mapcmd(UINT8 **cp, INT32 playernum) static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
{ {
UINT8 flags; UINT8 flags;
INT32 resetplayer = 1, lastgametype; INT32 presetplayer = 1, lastgametype;
UINT8 skipprecutscene, FLS; UINT8 skipprecutscene, pforcespecialstage;
boolean pencoremode; boolean pencoremode;
INT16 mapnumber; INT16 mapnumber;
@ -3087,7 +3062,15 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
pencoremode = ((flags & 1) != 0); 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; lastgametype = gametype;
gametype = READUINT8(*cp); gametype = READUINT8(*cp);
@ -3101,35 +3084,46 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
if (!(gametyperules & GTR_ENCORE)) if (!(gametyperules & GTR_ENCORE))
pencoremode = false; pencoremode = false;
skipprecutscene = ((flags & (1<<2)) != 0);
FLS = ((flags & (1<<3)) != 0);
mapnumber = READINT16(*cp); 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) if (netgame)
P_ClearRandom(READUINT32(*cp)); P_ClearRandom(READUINT32(*cp));
if (!skipprecutscene) if (!skipprecutscene)
{ {
DEBFILE(va("Warping to %s [resetplayer=%d lastgametype=%d gametype=%d cpnd=%d]\n", 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")); CON_LogMessage(M_GetText("Speeding off to level...\n"));
} }
if (demo.playback && !demo.timing) if (demo.playback && !demo.timing)
precache = false; precache = false;
if (resetplayer && !FLS)
{
emeralds = 0;
memset(&luabanks, 0, sizeof(luabanks));
}
demo.savemode = (cv_recordmultiplayerdemos.value == 2) ? DSM_WILLAUTOSAVE : DSM_NOTSAVING; demo.savemode = (cv_recordmultiplayerdemos.value == 2) ? DSM_WILLAUTOSAVE : DSM_NOTSAVING;
demo.savebutton = 0; demo.savebutton = 0;
G_InitNew(pencoremode, mapnumber, resetplayer, skipprecutscene, FLS); G_InitNew(pencoremode, mapnumber, presetplayer, skipprecutscene);
if (demo.playback && !demo.timing) if (demo.playback && !demo.timing)
precache = true; precache = true;
if (demo.timing) if (demo.timing)

View file

@ -239,7 +239,7 @@ void D_SendPlayerConfig(UINT8 n);
void Command_ExitGame_f(void); void Command_ExitGame_f(void);
void Command_Retry_f(void); void Command_Retry_f(void);
void D_GameTypeChanged(INT32 lastgametype); // not a real _OnChange function anymore 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_SetupVote(void);
void D_ModifyClientVote(UINT8 player, SINT8 voted); void D_ModifyClientVote(UINT8 player, SINT8 voted);
void D_PickVote(void); void D_PickVote(void);

View file

@ -182,8 +182,7 @@ extern boolean multiplayer;
extern UINT8 splitscreen; extern UINT8 splitscreen;
extern int r_splitscreen; extern int r_splitscreen;
extern boolean fromlevelselect; extern boolean forceresetplayers, deferencoremode, forcespecialstage;
extern boolean forceresetplayers, deferencoremode;
// ======================================== // ========================================
// Internal parameters for sound rendering. // Internal parameters for sound rendering.

View file

@ -3463,7 +3463,7 @@ void G_DoPlayDemo(char *defdemoname)
P_SetRandSeed(i, randseed[i]); 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++) for (i = 0; i < numslots; i++)
{ {

View file

@ -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) static void G_GetNextMap(void)
{ {
INT32 i; 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 // go to next level
// nextmap is 0-based, unlike gamemap // nextmap is 0-based, unlike gamemap
if (nextmapoverride != 0) if (nextmapoverride != 0)
{ {
nextmap = (INT16)(nextmapoverride-1); 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 else
{ {
INT32 lastgametype = gametype, newgametype = GT_RACE; // Wipe the queue info.
// 5 levels, 2 bonus stages: after rounds 2 and 4 (but flexible enough to accomodate other solutions) memset(&roundqueue, 0, sizeof(struct roundqueue));
UINT8 bonusmodulo = (grandprixinfo.cup->numlevels+1)/(grandprixinfo.cup->numbonus+1);
UINT8 bonusindex = (grandprixinfo.roundnum / bonusmodulo) - 1;
// If we're in a GP event, don't immediately follow it up with another. if (grandprixinfo.gp == true)
// 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)
{ {
grandprixinfo.eventmode = GPEVENT_NONE; // In GP, we're now ready to go to the ceremony.
nextmap = NEXTMAP_CEREMONY;
G_SetGametype(GT_RACE); setalready = true;
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
} }
else else
{ {
// Proceed to next map // On exiting roundqueue mode, kill the non-PWR between-round scores.
const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[grandprixinfo.roundnum]; // This prevents future tournament winners from carrying their wins out.
forceresetplayers = true;
if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum])
{
nextmap = cupLevelNum;
}
else
{
nextmap = 0; // Prevent uninitialised use -- go to TEST RUN, it's very obvious
}
grandprixinfo.roundnum++;
} }
} }
} }
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); UINT32 tolflag = G_TOLFlag(gametype);
register INT16 cm; register INT16 cm;
@ -4242,8 +4326,6 @@ static void G_GetNextMap(void)
if (!spec) if (!spec)
#endif //#if 0 #endif //#if 0
lastmap = nextmap; lastmap = nextmap;
deferencoremode = (cv_kartencore.value == 1);
} }
// //
@ -4417,8 +4499,6 @@ void G_NextLevel(void)
return; return;
} }
forceresetplayers = false;
gameaction = ga_worlddone; gameaction = ga_worlddone;
} }
@ -4433,7 +4513,7 @@ static void G_DoWorldDone(void)
forceresetplayers, forceresetplayers,
0, 0,
false, false,
false); forcespecialstage);
} }
gameaction = ga_nothing; 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 // This is the map command interpretation something like Command_Map_f
// //
// called at: map cmd execution, doloadgame, doplaydemo // 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); const char * mapname = G_BuildMapName(map);
INT32 i; INT32 i;
(void)FLS;
if (paused) if (paused)
{ {
paused = false; paused = false;
@ -5440,7 +5518,7 @@ void G_InitNew(UINT8 pencoremode, INT32 map, boolean resetplayer, boolean skippr
players[i].roundscore = 0; players[i].roundscore = 0;
if (resetplayer && !(multiplayer && demo.playback)) // SRB2Kart if (resetplayer && !demo.playback) // SRB2Kart
{ {
players[i].lives = 3; players[i].lives = 3;
players[i].xtralife = 0; players[i].xtralife = 0;

View file

@ -53,6 +53,27 @@ typedef enum
NEXTMAP_SPECIAL = NEXTMAP_INVALID NEXTMAP_SPECIAL = NEXTMAP_INVALID
} nextmapspecial_t; } 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 INT32 gameovertics;
extern UINT8 ammoremovaltics; extern UINT8 ammoremovaltics;
extern tic_t timeinmap; // Ticker for time spent in level (used for levelcard display) 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_DoReborn(INT32 playernum);
void G_PlayerReborn(INT32 player, boolean betweenmaps); void G_PlayerReborn(INT32 player, boolean betweenmaps);
void G_InitNew(UINT8 pencoremode, INT32 map, boolean resetplayer, void G_InitNew(UINT8 pencoremode, INT32 map, boolean resetplayer,
boolean skipprecutscene, boolean FLS); boolean skipprecutscene);
char *G_BuildMapTitle(INT32 mapnum); char *G_BuildMapTitle(INT32 mapnum);
struct searchdim struct searchdim

View file

@ -2482,24 +2482,26 @@ static void HU_DrawRankings(void)
V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, gametypes[gametype]->name); V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, gametypes[gametype]->name);
// Left hand side // Left hand side
if (grandprixinfo.gp == true) if (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE)
{ {
const char *roundstr = NULL; const char *roundstr = NULL;
V_DrawCenteredString(64, 8, 0, "ROUND"); V_DrawCenteredString(64, 8, 0, "ROUND");
switch (grandprixinfo.eventmode) switch (grandprixinfo.eventmode)
{ {
case GPEVENT_BONUS:
roundstr = "BONUS";
break;
case GPEVENT_SPECIAL: case GPEVENT_SPECIAL:
roundstr = "SPECIAL"; roundstr = "SPECIAL";
break; break;
default: default:
roundstr = va("%d", grandprixinfo.roundnum); roundstr = "BONUS";
break; break;
} }
V_DrawCenteredString(64, 16, hilicol, roundstr); 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) else if ((gametyperules & GTR_TIMELIMIT) && timelimitintics > 0)
{ {
UINT32 timeval = (timelimitintics + starttime + 1 - leveltime); UINT32 timeval = (timelimitintics + starttime + 1 - leveltime);

View file

@ -283,10 +283,10 @@ static INT16 K_RivalScore(player_t *bot)
UINT8 lowestdifficulty = MAXBOTDIFFICULTY; UINT8 lowestdifficulty = MAXBOTDIFFICULTY;
UINT8 i; UINT8 i;
if (grandprixinfo.cup != NULL) if (grandprixinfo.cup != NULL && roundqueue.size > 0)
{ {
roundnum = grandprixinfo.roundnum; roundnum = roundqueue.roundnum;
roundsleft = grandprixinfo.cup->numlevels - grandprixinfo.roundnum; roundsleft = grandprixinfo.cup->numlevels - roundnum;
} }
for (i = 0; i < MAXPLAYERS; i++) for (i = 0; i < MAXPLAYERS; i++)
@ -523,8 +523,10 @@ void K_RetireBots(void)
UINT8 i; UINT8 i;
if (grandprixinfo.gp == true 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. // No replacement.
return; return;
@ -566,7 +568,11 @@ void K_RetireBots(void)
else else
{ {
const UINT8 startingdifficulty = K_BotStartingDifficulty(grandprixinfo.gamespeed); 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) if (newDifficulty > MAXBOTDIFFICULTY)
@ -697,7 +703,7 @@ void K_PlayerLoseLife(player_t *player)
--------------------------------------------------*/ --------------------------------------------------*/
boolean K_CanChangeRules(boolean allowdemos) boolean K_CanChangeRules(boolean allowdemos)
{ {
if (grandprixinfo.gp == true /*&& grandprixinfo.roundnum > 0*/) if (grandprixinfo.gp == true)
{ {
// Don't cheat the rules of the GP! // Don't cheat the rules of the GP!
return false; return false;

View file

@ -31,7 +31,6 @@ typedef enum
extern struct grandprixinfo extern struct grandprixinfo
{ {
boolean gp; ///< If true, then we are in a Grand Prix. 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? cupheader_t *cup; ///< Which cup are we playing?
UINT8 gamespeed; ///< Copy of gamespeed, just to make sure you can't cheat it with cvars UINT8 gamespeed; ///< Copy of gamespeed, just to make sure you can't cheat it with cvars
boolean encore; ///< Ditto, but for encore mode boolean encore; ///< Ditto, but for encore mode

View file

@ -32,7 +32,6 @@ I_mutex k_menu_mutex;
#endif #endif
boolean menuactive = false; boolean menuactive = false;
boolean fromlevelselect = false;
// current menudef // current menudef
menu_t *currentMenu = &MAIN_ProfilesDef; menu_t *currentMenu = &MAIN_ProfilesDef;

View file

@ -104,7 +104,6 @@ void M_CupSelectHandler(INT32 choice)
if (cupgrid.grandprix == true) if (cupgrid.grandprix == true)
{ {
INT32 levelNum;
UINT8 ssplayers = cv_splitplayers.value-1; UINT8 ssplayers = cv_splitplayers.value-1;
S_StartSound(NULL, sfx_s3k63); S_StartSound(NULL, sfx_s3k63);
@ -130,13 +129,15 @@ void M_CupSelectHandler(INT32 choice)
grandprixinfo.gamespeed = min(KARTSPEED_HARD, cv_dummygpdifficulty.value); grandprixinfo.gamespeed = min(KARTSPEED_HARD, cv_dummygpdifficulty.value);
grandprixinfo.masterbots = (cv_dummygpdifficulty.value == 3); grandprixinfo.masterbots = (cv_dummygpdifficulty.value == 3);
grandprixinfo.encore = (boolean)cv_dummygpencore.value;
grandprixinfo.cup = newcup;
grandprixinfo.gp = true; grandprixinfo.gp = true;
grandprixinfo.roundnum = 1;
grandprixinfo.initalize = true; 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; paused = false;
@ -146,16 +147,14 @@ void M_CupSelectHandler(INT32 choice)
SV_StartSinglePlayerServer(levellist.newgametype, levellist.netgame); SV_StartSinglePlayerServer(levellist.newgametype, levellist.netgame);
} }
levelNum = grandprixinfo.cup->cachedlevels[0];
D_MapChange( D_MapChange(
levelNum + 1, roundqueue.entries[0].mapnum + 1,
GT_RACE, roundqueue.entries[0].gametype,
grandprixinfo.encore, roundqueue.entries[0].encore,
true, true,
1, 1,
false, false,
false roundqueue.entries[0].rankrestricted
); );
M_ClearMenus(true); M_ClearMenus(true);

View file

@ -49,15 +49,16 @@ savedata_t savedata;
// Block UINT32s to attempt to ensure that the correct data is // Block UINT32s to attempt to ensure that the correct data is
// being sent and received // being sent and received
#define ARCHIVEBLOCK_MISC 0x7FEEDEED #define ARCHIVEBLOCK_MISC 0x7FEEDEED
#define ARCHIVEBLOCK_PLAYERS 0x7F448008 #define ARCHIVEBLOCK_PLAYERS 0x7F448008
#define ARCHIVEBLOCK_PARTIES 0x7F87AF0C #define ARCHIVEBLOCK_PARTIES 0x7F87AF0C
#define ARCHIVEBLOCK_WORLD 0x7F8C08C0 #define ARCHIVEBLOCK_ROUNDQUEUE 0x7F721331
#define ARCHIVEBLOCK_POBJS 0x7F928546 #define ARCHIVEBLOCK_WORLD 0x7F8C08C0
#define ARCHIVEBLOCK_THINKERS 0x7F37037C #define ARCHIVEBLOCK_POBJS 0x7F928546
#define ARCHIVEBLOCK_SPECIALS 0x7F228378 #define ARCHIVEBLOCK_THINKERS 0x7F37037C
#define ARCHIVEBLOCK_WAYPOINTS 0x7F46498F #define ARCHIVEBLOCK_SPECIALS 0x7F228378
#define ARCHIVEBLOCK_RNG 0x7FAAB5BD #define ARCHIVEBLOCK_WAYPOINTS 0x7F46498F
#define ARCHIVEBLOCK_RNG 0x7FAAB5BD
// Note: This cannot be bigger // Note: This cannot be bigger
// than an UINT16 // 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 /// Colormaps
/// ///
@ -5400,6 +5443,7 @@ void P_SaveNetGame(savebuffer_t *save, boolean resending)
P_NetArchivePlayers(save); P_NetArchivePlayers(save);
P_NetArchiveParties(save); P_NetArchiveParties(save);
P_NetArchiveRoundQueue(save);
if (gamestate == GS_LEVEL) if (gamestate == GS_LEVEL)
{ {
@ -5450,6 +5494,7 @@ boolean P_LoadNetGame(savebuffer_t *save, boolean reloading)
P_NetUnArchivePlayers(save); P_NetUnArchivePlayers(save);
P_NetUnArchiveParties(save); P_NetUnArchiveParties(save);
P_NetUnArchiveRoundQueue(save);
if (gamestate == GS_LEVEL) if (gamestate == GS_LEVEL)
{ {

View file

@ -696,7 +696,7 @@ void ST_runTitleCard(void)
{ {
boolean run = !(paused || P_AutoPause()); boolean run = !(paused || P_AutoPause());
INT32 auxticker; INT32 auxticker;
boolean gp = (marathonmode || (grandprixinfo.gp && grandprixinfo.roundnum)); boolean doroundicon = (marathonmode || roundqueue.size > 0);
if (!G_IsTitleCardAvailable()) if (!G_IsTitleCardAvailable())
return; return;
@ -818,7 +818,7 @@ void ST_runTitleCard(void)
roundnumy = eggy1; roundnumy = eggy1;
// split both halves of the egg, but only do that in grand prix! // 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); auxticker = (INT32)lt_ticker - (TTANIMTHRESHOLD + TICRATE/2);
@ -881,7 +881,7 @@ void ST_drawTitleCard(void)
char *lvlttl = mapheaderinfo[gamemap-1]->lvlttl; char *lvlttl = mapheaderinfo[gamemap-1]->lvlttl;
char *zonttl = mapheaderinfo[gamemap-1]->zonttl; // SRB2kart char *zonttl = mapheaderinfo[gamemap-1]->zonttl; // SRB2kart
UINT8 actnum = mapheaderinfo[gamemap-1]->actnum; UINT8 actnum = mapheaderinfo[gamemap-1]->actnum;
boolean gp = (marathonmode || (grandprixinfo.gp && grandprixinfo.roundnum)); boolean doroundicon = (marathonmode || roundqueue.size > 0);
INT32 acttimer; INT32 acttimer;
fixed_t actscale; fixed_t actscale;
@ -1011,9 +1011,9 @@ void ST_drawTitleCard(void)
// Draw ROUND bar, scroll it downwards. // Draw ROUND bar, scroll it downwards.
V_DrawFixedPatch(roundx*FRACUNIT, ((-32) + (lt_ticker%32))*FRACUNIT, FRACUNIT, V_SNAPTOTOP|V_SNAPTOLEFT, tcroundbar, NULL); V_DrawFixedPatch(roundx*FRACUNIT, ((-32) + (lt_ticker%32))*FRACUNIT, FRACUNIT, V_SNAPTOTOP|V_SNAPTOLEFT, tcroundbar, NULL);
// Draw ROUND text // Draw ROUND text
if (gp) if (doroundicon)
V_DrawFixedPatch((roundx+10)*FRACUNIT, roundy*FRACUNIT, FRACUNIT, V_SNAPTOTOP|V_SNAPTOLEFT, 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); NULL);
// round num background // round num background
@ -1032,25 +1032,29 @@ void ST_drawTitleCard(void)
} }
// If possible, draw round number/icon // If possible, draw round number/icon
if (gp) if (doroundicon)
{ {
patch_t *roundico = NULL; patch_t *roundico = NULL;
if (marathonmode) if (marathonmode)
; // TODO: Ruby ; // TODO: Ruby
else switch (grandprixinfo.eventmode) else if (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE)
{ {
case GPEVENT_BONUS: switch (grandprixinfo.eventmode)
roundico = tcroundbonus; // TODO don't show capsule if we have other bonus types {
break; case GPEVENT_BONUS:
/*case GPEVENT_SPECIAL: roundico = tcroundbonus; // TODO don't show capsule if we have other bonus types
; // TODO: Emerald/mount break;
break;*/ /*case GPEVENT_SPECIAL:
case GPEVENT_NONE: ; // TODO: Emerald/mount
if (grandprixinfo.roundnum > 0 && grandprixinfo.roundnum < 11) // Check boundaries JUST IN CASE. break;*/
roundico = tcroundnum[grandprixinfo.roundnum-1]; default:
break; 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) if (roundico)

View file

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