diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 72c2cbdc7..c9099d78c 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -6139,7 +6139,7 @@ boolean TryRunTics(tic_t realtics) { COM_BufTicker(); if (mapchangepending) - D_MapChange(-1, 0, encoremode, false, 2, false, fromlevelselect); // finish the map change + D_MapChange(-1, 0, encoremode, false, 2, false, forcespecialstage); // finish the map change } NetUpdate(); diff --git a/src/d_main.c b/src/d_main.c index 2821d7686..ee53759dc 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -1022,8 +1022,9 @@ void D_ClearState(void) modeattacking = ATTACKING_NONE; marathonmode = 0; - // Reset GP + // Reset GP and roundqueue memset(&grandprixinfo, 0, sizeof(struct grandprixinfo)); + memset(&roundqueue, 0, sizeof(struct roundqueue)); // empty maptol so mario/etc sounds don't play in sound test when they shouldn't maptol = 0; @@ -1743,13 +1744,12 @@ void D_SRB2Main(void) // Start up a "minor" grand prix session memset(&grandprixinfo, 0, sizeof(struct grandprixinfo)); + memset(&roundqueue, 0, sizeof(struct roundqueue)); grandprixinfo.gamespeed = KARTSPEED_NORMAL; - grandprixinfo.encore = false; grandprixinfo.masterbots = false; grandprixinfo.gp = true; - grandprixinfo.roundnum = 0; grandprixinfo.cup = NULL; grandprixinfo.wonround = false; @@ -2002,11 +2002,6 @@ void D_SRB2Main(void) D_GameTypeChanged(j); } - if (gametyperules & (GTR_BOSS|GTR_CATCHER)) - grandprixinfo.eventmode = GPEVENT_SPECIAL; - else if (gametype != GT_RACE) - grandprixinfo.eventmode = GPEVENT_BONUS; - multiplayer = true; } diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 7c4296a43..59fddaf99 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -100,6 +100,8 @@ static void Got_DiscordInfo(UINT8 **cp, INT32 playernum); static void Got_ScheduleTaskcmd(UINT8 **cp, INT32 playernum); static void Got_ScheduleClearcmd(UINT8 **cp, INT32 playernum); static void Got_Automatecmd(UINT8 **cp, INT32 playernum); +static void Got_RequestMapQueuecmd(UINT8 **cp, INT32 playernum); +static void Got_MapQueuecmd(UINT8 **cp, INT32 playernum); static void Got_Cheat(UINT8 **cp, INT32 playernum); static void PointLimit_OnChange(void); @@ -160,6 +162,7 @@ static void Command_StopMovie_f(void); static void Command_Map_f(void); static void Command_RandomMap(void); static void Command_RestartLevel(void); +static void Command_QueueMap_f(void); static void Command_ResetCamera_f(void); static void Command_View_f (void); @@ -564,6 +567,8 @@ INT16 numgametypes = GT_FIRSTFREESLOT; boolean forceresetplayers = false; boolean deferencoremode = false; +boolean forcespecialstage = false; + UINT8 splitscreen = 0; INT32 adminplayers[MAXPLAYERS]; @@ -625,6 +630,8 @@ const char *netxcmdnames[MAXNETXCMD - 1] = "SCHEDULETASK", // XD_SCHEDULETASK "SCHEDULECLEAR", // XD_SCHEDULECLEAR "AUTOMATE", // XD_AUTOMATE + "REQMAPQUEUE", // XD_REQMAPQUEUE + "MAPQUEUE", // XD_MAPQUEUE }; // ========================================================================= @@ -672,6 +679,8 @@ void D_RegisterServerCommands(void) RegisterNetXCmd(XD_SCHEDULETASK, Got_ScheduleTaskcmd); RegisterNetXCmd(XD_SCHEDULECLEAR, Got_ScheduleClearcmd); RegisterNetXCmd(XD_AUTOMATE, Got_Automatecmd); + RegisterNetXCmd(XD_REQMAPQUEUE, Got_RequestMapQueuecmd); + RegisterNetXCmd(XD_MAPQUEUE, Got_MapQueuecmd); RegisterNetXCmd(XD_CHEAT, Got_Cheat); @@ -694,6 +703,7 @@ void D_RegisterServerCommands(void) COM_AddCommand("map", Command_Map_f); COM_AddCommand("randommap", Command_RandomMap); COM_AddCommand("restartlevel", Command_RestartLevel); + COM_AddCommand("queuemap", Command_QueueMap_f); COM_AddCommand("exitgame", Command_ExitGame_f); COM_AddCommand("retry", Command_Retry_f); @@ -2502,42 +2512,27 @@ INT32 mapchangepending = 0; * 63 old events will get reexecuted, with ridiculous results. Just don't do * it (without setting delay to 1, which is the current solution). * - * \param mapnum Map number to change to. - * \param gametype Gametype to switch to. - * \param pencoremode Is this 'Encore Mode'? - * \param resetplayers 1 to reset player scores and lives and such, 0 not to. - * \param delay Determines how the function will be executed: 0 to do - * it all right now (must not be done from a menu), 1 to - * do step one and prepare step two, 2 to do step two. - * \param skipprecutscene To skip the precutscence or not? + * \param mapnum Map number to change to. + * \param gametype Gametype to switch to. + * \param pencoremode Is this 'Encore Mode'? + * \param presetplayers 1 to reset player scores and lives and such, 0 not to. + * \param delay Determines how the function will be executed: 0 to do + * it all right now (must not be done from a menu), 1 to + * do step one and prepare step two, 2 to do step two. + * \param skipprecutscene To skip the precutscence or not? + * \param pforcespecialstage For certain contexts, forces a special stage. * \sa D_GameTypeChanged, Command_Map_f * \author Graue */ -void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pencoremode, boolean resetplayers, INT32 delay, boolean skipprecutscene, boolean FLS) +void D_MapChange(UINT16 mapnum, INT32 newgametype, boolean pencoremode, boolean presetplayers, INT32 delay, boolean skipprecutscene, boolean pforcespecialstage) { - static char buf[2+MAX_WADPATH+1+4]; + static char buf[1+1+1+1+1+2+4]; static char *buf_p = buf; // The supplied data are assumed to be good. I_Assert(delay >= 0 && delay <= 2); - /* - if (mapnum != -1) - { - CV_SetValue(&cv_nextmap, mapnum); - } - */ - - CONS_Debug(DBG_GAMELOGIC, "Map change: mapnum=%d gametype=%d pencoremode=%d resetplayers=%d delay=%d skipprecutscene=%d\n", - mapnum, newgametype, pencoremode, resetplayers, delay, skipprecutscene); - - if ((netgame || multiplayer) && (grandprixinfo.gp != false)) - FLS = false; - - // Too lazy to change the input value for every instance of this function....... - if (grandprixinfo.gp == true) - { - pencoremode = grandprixinfo.encore; - } + CONS_Debug(DBG_GAMELOGIC, "Map change: mapnum=%d gametype=%d pencoremode=%d presetplayers=%d delay=%d skipprecutscene=%d pforcespecialstage = %d\n", + mapnum, newgametype, pencoremode, presetplayers, delay, skipprecutscene, pforcespecialstage); if (delay != 2) { @@ -2546,18 +2541,29 @@ 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); - // new gametype value - WRITEUINT8(buf_p, newgametype); + if (roundqueue.netcommunicate) + { + // roundqueue state + WRITEUINT8(buf_p, roundqueue.position); + WRITEUINT8(buf_p, roundqueue.size); + WRITEUINT8(buf_p, roundqueue.roundnum); + roundqueue.netcommunicate = false; + } - WRITEINT16(buf_p, mapnum); + // new gametype value + WRITEUINT16(buf_p, newgametype); + + WRITEUINT16(buf_p, mapnum); } if (delay == 1) @@ -2736,6 +2742,64 @@ ConcatCommandArgv (int start, int end) // // Largely rewritten by James. // + +static INT32 GetGametypeParm(size_t option_gametype) +{ + const char *gametypename; + INT32 newgametype; + + if (COM_Argc() < option_gametype + 2)/* no argument after? */ + { + CONS_Alert(CONS_ERROR, + "No gametype name follows parameter '%s'.\n", + COM_Argv(option_gametype)); + return -1; + } + + // new gametype value + // use current one by default + gametypename = COM_Argv(option_gametype + 1); + + newgametype = G_GetGametypeByName(gametypename); + + if (newgametype == -1) // reached end of the list with no match + { + /* Did they give us a gametype number? That's okay too! */ + if (isdigit(gametypename[0])) + { + INT16 d = atoi(gametypename); + if (d >= 0 && d < numgametypes) + newgametype = d; + else + { + CONS_Alert(CONS_ERROR, + "Gametype number %d is out of range. Use a number between" + " 0 and %d inclusive. ...Or just use the name. :v\n", + d, + numgametypes-1); + return -1; + } + } + else + { + CONS_Alert(CONS_ERROR, + "'%s' is not a valid gametype.\n", + gametypename); + return -1; + } + } + + if (Playing() && netgame && (gametypes[newgametype]->rules & GTR_FORBIDMP)) + { + CONS_Alert(CONS_ERROR, + "'%s' is not a net-compatible gametype.\n", + gametypename); + return -1; + } + + return newgametype; +} + static void Command_Map_f(void) { size_t first_option; @@ -2743,8 +2807,8 @@ static void Command_Map_f(void) size_t option_gametype; size_t option_encore; size_t option_skill; - const char *gametypename; boolean newresetplayers; + boolean newforcespecialstage; boolean usingcheats; boolean ischeating; @@ -2757,8 +2821,6 @@ static void Command_Map_f(void) INT32 newgametype = gametype; boolean newencoremode = (cv_kartencore.value == 1); - INT32 d; - if (client && !IsPlayerAdmin(consoleplayer)) { CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n")); @@ -2770,28 +2832,18 @@ static void Command_Map_f(void) option_encore = COM_CheckPartialParm("-e"); option_skill = COM_CheckPartialParm("-s"); newresetplayers = ! COM_CheckParm("-noresetplayers"); + newforcespecialstage = COM_CheckParm("-forcespecialstage"); usingcheats = CV_CheatsEnabled(); ischeating = (!(netgame || multiplayer)) || (!newresetplayers); - if (option_gametype) - { - if (COM_Argc() < option_gametype + 2)/* no argument after? */ - { - CONS_Alert(CONS_ERROR, - "No gametype name follows parameter '%s'.\n", - COM_Argv(option_gametype)); - return; - } - } - if (!( first_option = COM_FirstOption() )) first_option = COM_Argc(); if (first_option < 2) { /* I'm going over the fucking lines and I DON'T CAREEEEE */ - CONS_Printf("map [-gametype ] [-force]:\n"); + CONS_Printf("map [-gametype ] [-force]:\n"); CONS_Printf(M_GetText( "Warp to a map, by its name, two character code, with optional \"MAP\" prefix, or by its number (though why would you).\n" "All parameters are case-insensitive and may be abbreviated.\n")); @@ -2824,46 +2876,9 @@ static void Command_Map_f(void) // use current one by default if (option_gametype) { - gametypename = COM_Argv(option_gametype + 1); - - newgametype = G_GetGametypeByName(gametypename); - - if (newgametype == -1) // reached end of the list with no match + newgametype = GetGametypeParm(option_gametype); + if (newgametype == -1) { - /* Did they give us a gametype number? That's okay too! */ - if (isdigit(gametypename[0])) - { - d = atoi(gametypename); - if (d >= 0 && d < numgametypes) - newgametype = d; - else - { - CONS_Alert(CONS_ERROR, - "Gametype number %d is out of range. Use a number between" - " 0 and %d inclusive. ...Or just use the name. :v\n", - d, - numgametypes-1); - Z_Free(realmapname); - Z_Free(mapname); - return; - } - } - else - { - CONS_Alert(CONS_ERROR, - "'%s' is not a valid gametype.\n", - gametypename); - Z_Free(realmapname); - Z_Free(mapname); - return; - } - } - - if (Playing() && netgame && (gametypes[newgametype]->rules & GTR_FORBIDMP)) - { - CONS_Alert(CONS_ERROR, - "'%s' is not a net-compatible gametype.\n", - gametypename); Z_Free(realmapname); Z_Free(mapname); return; @@ -2907,7 +2922,7 @@ static void Command_Map_f(void) // don't use a gametype the map doesn't support if (cht_debug || option_force || cv_skipmapcheck.value) { - fromlevelselect = false; // The player wants us to trek on anyway. Do so. + // The player wants us to trek on anyway. Do so. } else { @@ -2922,12 +2937,6 @@ static void Command_Map_f(void) Z_Free(mapname); return; } - else - { - fromlevelselect = - ( netgame || multiplayer ) && - grandprixinfo.gp != false; - } } if (!(netgame || multiplayer)) @@ -2972,29 +2981,16 @@ static void Command_Map_f(void) } } - grandprixinfo.encore = newencoremode; - grandprixinfo.gp = true; - grandprixinfo.roundnum = 0; - grandprixinfo.cup = NULL; grandprixinfo.wonround = false; - grandprixinfo.initalize = true; - - grandprixinfo.eventmode = GPEVENT_NONE; - - if (gametypes[newgametype]->rules & (GTR_BOSS|GTR_CATCHER)) - { - grandprixinfo.eventmode = GPEVENT_SPECIAL; - } - else if (newgametype != GT_RACE) - { - grandprixinfo.eventmode = GPEVENT_BONUS; - } if (!Playing()) { UINT8 ssplayers = cv_splitplayers.value-1; + grandprixinfo.cup = NULL; + grandprixinfo.initalize = true; + multiplayer = true; restoreMenu = NULL; @@ -3011,9 +3007,10 @@ 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(mapname); } /** Receives a map command and changes the map. @@ -3026,10 +3023,10 @@ 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; - INT16 mapnumber; + INT32 presetplayer = 1; + UINT8 skipprecutscene, pforcespecialstage; + boolean pencoremode, hasroundqueuedata; + UINT16 mapnumber, lastgametype; forceresetplayers = deferencoremode = false; @@ -3041,17 +3038,51 @@ 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; + 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); + gametype = READUINT16(*cp); G_SetGametype(gametype); // I fear putting that macro as an argument if (gametype < 0 || gametype >= numgametypes) @@ -3059,14 +3090,46 @@ 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 (!(gametyperules & GTR_ENCORE)) pencoremode = false; - skipprecutscene = ((flags & (1<<2)) != 0); + mapnumber = READUINT16(*cp); - FLS = ((flags & (1<<3)) != 0); + // 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; - mapnumber = READINT16(*cp); + if (pforcespecialstage == true && gamedata->everseenspecial == false) + { + gamedata->everseenspecial = true; + // No need to do anything else here -- P_LoadLevel will get this for us! + //M_UpdateUnlockablesAndExtraEmblems(true, true); + //gamedata->deferredsave = true; + } + } + else if (gametype != GT_RACE) + { + grandprixinfo.eventmode = GPEVENT_BONUS; + } + else + { + grandprixinfo.eventmode = GPEVENT_NONE; + } + } if (netgame) P_ClearRandom(READUINT32(*cp)); @@ -3074,23 +3137,17 @@ 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 (demo.playback && !demo.timing) precache = false; - if (resetplayer && !FLS) - { - emeralds = 0; - memset(&luabanks, 0, sizeof(luabanks)); - } - demo.savemode = (cv_recordmultiplayerdemos.value == 2) ? DSM_WILLAUTOSAVE : DSM_NOTSAVING; demo.savebutton = 0; - G_InitNew(pencoremode, mapnumber, resetplayer, skipprecutscene, FLS); + G_InitNew(pencoremode, mapnumber, presetplayer, skipprecutscene); if (demo.playback && !demo.timing) precache = true; if (demo.timing) @@ -3124,56 +3181,9 @@ static void Command_RandomMap(void) if ((option_gametype = COM_CheckPartialParm("-g"))) { - const char *gametypename; - - if (COM_Argc() < option_gametype + 2)/* no argument after? */ - { - CONS_Alert(CONS_ERROR, - "No gametype name follows parameter '%s'.\n", - COM_Argv(option_gametype)); + newgametype = GetGametypeParm(option_gametype); + if (newgametype == -1) return; - } - - // new gametype value - // use current one by default - gametypename = COM_Argv(option_gametype + 1); - - newgametype = G_GetGametypeByName(gametypename); - - if (newgametype == -1) // reached end of the list with no match - { - /* Did they give us a gametype number? That's okay too! */ - if (isdigit(gametypename[0])) - { - INT16 d = atoi(gametypename); - if (d >= 0 && d < numgametypes) - newgametype = d; - else - { - CONS_Alert(CONS_ERROR, - "Gametype number %d is out of range. Use a number between" - " 0 and %d inclusive. ...Or just use the name. :v\n", - d, - numgametypes-1); - return; - } - } - else - { - CONS_Alert(CONS_ERROR, - "'%s' is not a valid gametype.\n", - gametypename); - return; - } - } - - if (Playing() && netgame && (gametypes[newgametype]->rules & GTR_FORBIDMP)) - { - CONS_Alert(CONS_ERROR, - "'%s' is not a net-compatible gametype.\n", - gametypename); - return; - } } // TODO: Handle singleplayer conditions. @@ -3230,6 +3240,247 @@ static void Command_RestartLevel(void) D_MapChange(gamemap, gametype, newencore, false, 0, false, false); } +static void Handle_MapQueueSend(UINT16 newmapnum, UINT16 newgametype, boolean newencoremode) +{ + static char buf[1+2+2]; + static char *buf_p = buf; + + UINT8 flags = 0; + + CONS_Debug(DBG_GAMELOGIC, "Map queue: mapnum=%d newgametype=%d newencoremode=%d\n", + newmapnum, newgametype, newencoremode); + + buf_p = buf; + + if (newencoremode) + flags |= 1; + + WRITEUINT8(buf_p, flags); + WRITEUINT16(buf_p, newgametype); + + if (client) + { + WRITEUINT16(buf_p, newmapnum); + SendNetXCmd(XD_REQMAPQUEUE, buf, buf_p - buf); + return; + } + + WRITEUINT8(buf_p, roundqueue.size); + SendNetXCmd(XD_MAPQUEUE, buf, buf_p - buf); + + G_MapIntoRoundQueue(newmapnum, newgametype, newencoremode, false); +} + +static void Command_QueueMap_f(void) +{ + size_t first_option; + size_t option_force; + size_t option_gametype; + size_t option_encore; + + boolean usingcheats; + boolean ischeating; + + INT32 newmapnum; + + char * mapname; + char *realmapname = NULL; + + INT32 newgametype = gametype; + boolean newencoremode = (cv_kartencore.value == 1); + + if (!Playing()) + { + CONS_Printf(M_GetText("Levels can only be queued in-game.\n")); + return; + } + + if (client && !IsPlayerAdmin(consoleplayer)) + { + CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n")); + return; + } + + if (roundqueue.size >= ROUNDQUEUE_MAX) + { + CONS_Printf(M_GetText("Round queue is currently full.\n")); + return; + } + + option_force = COM_CheckPartialParm("-f"); + option_gametype = COM_CheckPartialParm("-g"); + option_encore = COM_CheckPartialParm("-e"); + + usingcheats = CV_CheatsEnabled(); + ischeating = (!(netgame || multiplayer)); + + if (!( first_option = COM_FirstOption() )) + first_option = COM_Argc(); + + if (first_option < 2) + { + /* I'm going over the fucking lines and I DON'T CAREEEEE */ + CONS_Printf("queuemap [-gametype ] [-force]:\n"); + CONS_Printf(M_GetText( + "Queue up a map by its name, or by its number (though why would you).\n" + "All parameters are case-insensitive and may be abbreviated.\n")); + return; + } + + mapname = ConcatCommandArgv(1, first_option); + + newmapnum = G_FindMapByNameOrCode(mapname, &realmapname); + + if (newmapnum == 0) + { + CONS_Alert(CONS_ERROR, M_GetText("Could not find any map described as '%s'.\n"), mapname); + Z_Free(mapname); + return; + } + + if (!K_CanChangeRules(false) || (/*newmapnum != 1 &&*/ M_MapLocked(newmapnum))) + { + ischeating = true; + } + + if (ischeating && !usingcheats) + { + CONS_Printf(M_GetText("Cheats must be enabled.\n")); + return; + } + + // new gametype value + // use current one by default + if (option_gametype) + { + newgametype = GetGametypeParm(option_gametype); + if (newgametype == -1) + { + Z_Free(realmapname); + Z_Free(mapname); + return; + } + } + + // new encoremode value + if (option_encore) + { + newencoremode = !newencoremode; + + if (!M_SecretUnlocked(SECRET_ENCORE, false) && newencoremode == true && !usingcheats) + { + CONS_Alert(CONS_NOTICE, M_GetText("You haven't unlocked Encore Mode yet!\n")); + Z_Free(realmapname); + Z_Free(mapname); + return; + } + } + + // don't use a gametype the map doesn't support + if (cht_debug || option_force || cv_skipmapcheck.value) + { + // The player wants us to trek on anyway. Do so. + } + else + { + // G_TOLFlag handles both multiplayer gametype and ignores it for !multiplayer + if (!( + mapheaderinfo[newmapnum-1] && + mapheaderinfo[newmapnum-1]->typeoflevel & G_TOLFlag(newgametype) + )) + { + CONS_Alert(CONS_WARNING, M_GetText("%s (%s) doesn't support %s mode!\n(Use -force to override)\n"), realmapname, G_BuildMapName(newmapnum), gametypes[newgametype]->name); + Z_Free(realmapname); + Z_Free(mapname); + return; + } + } + + Handle_MapQueueSend(newmapnum-1, newgametype, newencoremode); + + Z_Free(realmapname); + Z_Free(mapname); +} + +static void Got_RequestMapQueuecmd(UINT8 **cp, INT32 playernum) +{ + UINT8 flags; + boolean setencore; + UINT16 mapnumber, setgametype; + + flags = READUINT8(*cp); + + setencore = ((flags & 1) != 0); + + setgametype = READUINT16(*cp); + + mapnumber = READUINT16(*cp); + + if (!IsPlayerAdmin(playernum)) + { + CONS_Alert(CONS_WARNING, M_GetText("Illegal request map queue command received from %s\n"), player_names[playernum]); + if (server && playernum != serverplayer) + SendKick(playernum, KICK_MSG_CON_FAIL); + return; + } + + if (roundqueue.size >= ROUNDQUEUE_MAX) + { + CONS_Alert(CONS_ERROR, "queuemap: Unable to add map beyond %u\n", roundqueue.size); + return; + } + + if (client) + return; + + Handle_MapQueueSend(mapnumber, setgametype, setencore); +} + +static void Got_MapQueuecmd(UINT8 **cp, INT32 playernum) +{ + UINT8 flags, queueposition; + boolean setencore; + UINT16 setgametype; + + flags = READUINT8(*cp); + + setencore = ((flags & 1) != 0); + + setgametype = READUINT16(*cp); + + queueposition = READUINT8(*cp); + + if (playernum != serverplayer) + { + CONS_Alert(CONS_WARNING, M_GetText("Illegal map queue command received from %s\n"), player_names[playernum]); + if (server) + SendKick(playernum, KICK_MSG_CON_FAIL); + return; + } + + if (queueposition >= ROUNDQUEUE_MAX) + { + CONS_Alert(CONS_ERROR, "queuemap: Unable to add map beyond %u\n", roundqueue.size); + return; + } + + if (server) + return; + + while (roundqueue.size <= queueposition) + { + memset(&roundqueue.entries[roundqueue.size], 0, sizeof(roundentry_t)); + roundqueue.size++; + } + + G_MapSlipIntoRoundQueue(queueposition, 0, setgametype, setencore, false); + + if (!IsPlayerAdmin(playernum)) + return; + + CONS_Printf("queuemap: A map was successfully added to the round queue (position %u)\n", queueposition); +} + static void Command_Pause(void) { UINT8 buf[2]; diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 0eee560d5..94883ce92 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -179,6 +179,8 @@ typedef enum XD_SCHEDULETASK, // 34 XD_SCHEDULECLEAR, // 35 XD_AUTOMATE, // 36 + XD_REQMAPQUEUE, // 37 + XD_MAPQUEUE, // 38 MAXNETXCMD } netxcmd_t; @@ -237,7 +239,7 @@ void D_SendPlayerConfig(UINT8 n); void Command_ExitGame_f(void); void Command_Retry_f(void); void D_GameTypeChanged(INT32 lastgametype); // not a real _OnChange function anymore -void D_MapChange(INT32 pmapnum, INT32 pgametype, boolean pencoremode, boolean presetplayers, INT32 pdelay, boolean pskipprecutscene, boolean pfromlevelselect); +void D_MapChange(UINT16 pmapnum, INT32 pgametype, boolean pencoremode, boolean presetplayers, INT32 pdelay, boolean pskipprecutscene, boolean pforcespecialstage); void D_SetupVote(void); void D_ModifyClientVote(UINT8 player, SINT8 voted); void D_PickVote(void); diff --git a/src/doomstat.h b/src/doomstat.h index a16e5baa8..72a3a8c71 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -182,8 +182,7 @@ extern boolean multiplayer; extern UINT8 splitscreen; extern int r_splitscreen; -extern boolean fromlevelselect; -extern boolean forceresetplayers, deferencoremode; +extern boolean forceresetplayers, deferencoremode, forcespecialstage; // ======================================== // Internal parameters for sound rendering. diff --git a/src/g_demo.c b/src/g_demo.c index 0d9ffa409..5cbe6ce38 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -3455,7 +3455,7 @@ void G_DoPlayDemo(char *defdemoname) P_SetRandSeed(i, randseed[i]); } - G_InitNew(demoflags & DF_ENCORE, gamemap, true, true, false); // Doesn't matter whether you reset or not here, given changes to resetplayer. + G_InitNew(demoflags & DF_ENCORE, gamemap, true, true); // Doesn't matter whether you reset or not here, given changes to resetplayer. for (i = 0; i < numslots; i++) { diff --git a/src/g_game.c b/src/g_game.c index 527ce39ad..5791fc7db 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4003,117 +4003,215 @@ static void G_HandleSaveLevel(void) } } +// 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 = (cup->numlevels+1)/(cup->numbonus+1); + UINT16 cupLevelNum; + + // Levels are added to the queue in the following pattern. + // For 5 Race rounds and 2 Bonus rounds, the most common case: + // race - race - BONUS - race - race - BONUS - race + // The system is flexible enough to permit other arrangements. + // However, we just want to keep the pacing even & consistent. + while (levelindex < cup->numlevels) + { + // Fill like two or three Race maps. + for (i = 0; i < bonusmodulo; i++) + { + cupLevelNum = cup->cachedlevels[levelindex]; + + if (cupLevelNum >= nummapheaders) + { + // For invalid Race maps, we keep the pacing by going to TEST RUN. + // It transparently lets the user know something is wrong. + cupLevelNum = 0; + } + + G_MapIntoRoundQueue( + cupLevelNum, + setgametype, + setencore, // *probably* correct + false + ); + + levelindex++; + if (levelindex >= cup->numlevels) + break; + } + + // Attempt to add an interstitial Bonus round. + if (levelindex < cup->numlevels + && bonusindex < cup->numbonus) + { + cupLevelNum = cup->cachedlevels[CUPCACHE_BONUS + bonusindex]; + + if (cupLevelNum < nummapheaders) + { + // In the case of Bonus rounds, we simply skip invalid maps. + G_MapIntoRoundQueue( + cupLevelNum, + G_GuessGametypeByTOL(mapheaderinfo[cupLevelNum]->typeoflevel), + setencore, // if this isn't correct, Got_Mapcmd will fix it + false + ); + } + + bonusindex++; + } + } + + // ...but there's one last trick up our sleeves. + // At the end of the Cup is a Rank-restricted treat. + // So we append it to the end of the roundqueue. + // (as long as it exists, of course!) + cupLevelNum = cup->cachedlevels[CUPCACHE_SPECIAL]; + if (cupLevelNum < nummapheaders) + { + G_MapIntoRoundQueue( + cupLevelNum, + G_GuessGametypeByTOL(mapheaderinfo[cupLevelNum]->typeoflevel), + setencore, // if this isn't correct, Got_Mapcmd will fix it + true // Rank-restricted! + ); + } + + if (roundqueue.size == 0) + { + I_Error("G_CupToRoundQueue: roundqueue size is 0 after population!?"); + } +} + static void G_GetNextMap(void) { INT32 i; + boolean setalready = false; + + if (!server) + { + // Server is authoriative, not you + return; + } + + deferencoremode = (cv_kartencore.value == 1); + forceresetplayers = forcespecialstage = false; // go to next level // nextmap is 0-based, unlike gamemap if (nextmapoverride != 0) { nextmap = (INT16)(nextmapoverride-1); + setalready = true; } - else if (grandprixinfo.gp == true) + else if (roundqueue.size > 0) { - if (grandprixinfo.roundnum == 0 || grandprixinfo.cup == NULL) // Single session + boolean permitrank = false; + if (grandprixinfo.gp == true + && grandprixinfo.gamespeed >= KARTSPEED_NORMAL) { - nextmap = prevmap; // Same map + // On A rank pace? Then you get a chance for S rank! + permitrank = (K_CalculateGPGrade(&grandprixinfo.rank) >= GRADE_A); + } + + while (roundqueue.position < roundqueue.size + && (roundqueue.entries[roundqueue.position].mapnum >= nummapheaders + || mapheaderinfo[roundqueue.entries[roundqueue.position].mapnum] == NULL + || (netgame && (gametypes[roundqueue.entries[roundqueue.position].gametype]->rules & GTR_FORBIDMP)) + || (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); + } + + // Is this special..? + forcespecialstage = roundqueue.entries[roundqueue.position].rankrestricted; + + // On entering roundqueue mode, kill the non-PWR between-round scores. + // This makes it viable as a future tournament mode base. + if (roundqueue.position == 0) + { + forceresetplayers = true; + } + + // Handle primary queue position update. + roundqueue.position++; + if (grandprixinfo.gp == false || gametype == roundqueue.entries[0].gametype) + { + roundqueue.roundnum++; + } + + setalready = true; } else { - INT32 lastgametype = gametype, newgametype = GT_RACE; - // 5 levels, 2 bonus stages: after rounds 2 and 4 (but flexible enough to accomodate other solutions) - UINT8 bonusmodulo = (grandprixinfo.cup->numlevels+1)/(grandprixinfo.cup->numbonus+1); - UINT8 bonusindex = (grandprixinfo.roundnum / bonusmodulo) - 1; + // Wipe the queue info. + memset(&roundqueue, 0, sizeof(struct roundqueue)); - // If we're in a GP event, don't immediately follow it up with another. - // I also suspect this will not work with online GP so I'm gonna prevent it right now. - // The server might have to communicate eventmode (alongside other GP data) in XD_MAP later. - if (netgame) - ; - else if (grandprixinfo.eventmode != GPEVENT_NONE) + if (grandprixinfo.gp == true) { - grandprixinfo.eventmode = GPEVENT_NONE; - - G_SetGametype(GT_RACE); - if (gametype != lastgametype) - D_GameTypeChanged(lastgametype); - } - // Special stage - else if (grandprixinfo.roundnum >= grandprixinfo.cup->numlevels) - { - gp_rank_e grade = K_CalculateGPGrade(&grandprixinfo.rank); - - if (grade >= GRADE_A && grandprixinfo.gamespeed >= KARTSPEED_NORMAL) // On A rank pace? Then you get a chance for S rank! - { - const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[CUPCACHE_SPECIAL]; - if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum]) - { - grandprixinfo.eventmode = GPEVENT_SPECIAL; - nextmap = cupLevelNum; - newgametype = G_GuessGametypeByTOL(mapheaderinfo[cupLevelNum]->typeoflevel); - - if (gamedata->everseenspecial == false) - { - gamedata->everseenspecial = true; - M_UpdateUnlockablesAndExtraEmblems(true, true); - G_SaveGameData(); - } - } - } - } - else if ((grandprixinfo.cup->numbonus > 0) - && (grandprixinfo.roundnum % bonusmodulo) == 0 - && bonusindex < MAXBONUSLIST) - { - // todo any other condition? - { - const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[CUPCACHE_BONUS + bonusindex]; - if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum]) - { - grandprixinfo.eventmode = GPEVENT_BONUS; - nextmap = cupLevelNum; - newgametype = G_GuessGametypeByTOL(mapheaderinfo[cupLevelNum]->typeoflevel); - } - } - } - - if (newgametype == -1) - { - // Don't permit invalid changes. - grandprixinfo.eventmode = GPEVENT_NONE; - newgametype = gametype; - } - - if (grandprixinfo.eventmode != GPEVENT_NONE) - { - G_SetGametype(newgametype); - if (gametype != lastgametype) - D_GameTypeChanged(lastgametype); - } - else if (grandprixinfo.roundnum >= grandprixinfo.cup->numlevels) // On final map - { - nextmap = NEXTMAP_CEREMONY; // ceremonymap + // In GP, we're now ready to go to the ceremony. + nextmap = NEXTMAP_CEREMONY; + setalready = true; } else { - // Proceed to next map - const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[grandprixinfo.roundnum]; - - if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum]) - { - nextmap = cupLevelNum; - } - else - { - nextmap = 0; // Prevent uninitialised use -- go to TEST RUN, it's very obvious - } - - grandprixinfo.roundnum++; + // On exiting roundqueue mode, kill the non-PWR between-round scores. + // This prevents future tournament winners from carrying their wins out. + forceresetplayers = true; } } + + // Make sure the next D_MapChange sends updated roundqueue state. + roundqueue.netcommunicate = true; } - else + else if (grandprixinfo.gp == true) + { + // Fast And Rapid Testing + // this codepath is exclusively accessible through console/command line + nextmap = prevmap; + setalready = true; + } + + if (setalready == false) { UINT32 tolflag = G_TOLFlag(gametype); register INT16 cm; @@ -4257,8 +4355,6 @@ static void G_GetNextMap(void) if (!spec) #endif //#if 0 lastmap = nextmap; - - deferencoremode = (cv_kartencore.value == 1); } // @@ -4432,8 +4528,6 @@ void G_NextLevel(void) return; } - forceresetplayers = false; - gameaction = ga_worlddone; } @@ -4448,7 +4542,7 @@ static void G_DoWorldDone(void) forceresetplayers, 0, false, - false); + forcespecialstage); } gameaction = ga_nothing; @@ -5421,14 +5515,12 @@ void G_DeferedInitNew(boolean pencoremode, INT32 map, INT32 pickedchar, UINT8 ss // This is the map command interpretation something like Command_Map_f // // called at: map cmd execution, doloadgame, doplaydemo -void G_InitNew(UINT8 pencoremode, INT32 map, boolean resetplayer, boolean skipprecutscene, boolean FLS) +void G_InitNew(UINT8 pencoremode, INT32 map, boolean resetplayer, boolean skipprecutscene) { const char * mapname = G_BuildMapName(map); INT32 i; - (void)FLS; - if (paused) { paused = false; @@ -5455,7 +5547,7 @@ void G_InitNew(UINT8 pencoremode, INT32 map, boolean resetplayer, boolean skippr players[i].roundscore = 0; - if (resetplayer && !(multiplayer && demo.playback)) // SRB2Kart + if (resetplayer && !demo.playback) // SRB2Kart { players[i].lives = 3; players[i].xtralife = 0; diff --git a/src/g_game.h b/src/g_game.h index 9fe875c09..c071a6b3e 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 + UINT16 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 INT32 gameovertics; extern UINT8 ammoremovaltics; extern tic_t timeinmap; // Ticker for time spent in level (used for levelcard display) @@ -117,7 +140,7 @@ void G_ChangePlayerReferences(mobj_t *oldmo, mobj_t *newmo); void G_DoReborn(INT32 playernum); void G_PlayerReborn(INT32 player, boolean betweenmaps); void G_InitNew(UINT8 pencoremode, INT32 map, boolean resetplayer, - boolean skipprecutscene, boolean FLS); + boolean skipprecutscene); char *G_BuildMapTitle(INT32 mapnum); struct searchdim diff --git a/src/hu_stuff.c b/src/hu_stuff.c index e5eb0aed9..5b028662d 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -2482,24 +2482,26 @@ static void HU_DrawRankings(void) V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, gametypes[gametype]->name); // Left hand side - if (grandprixinfo.gp == true) + if (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE) { const char *roundstr = NULL; V_DrawCenteredString(64, 8, 0, "ROUND"); switch (grandprixinfo.eventmode) { - case GPEVENT_BONUS: - roundstr = "BONUS"; - break; case GPEVENT_SPECIAL: roundstr = "SPECIAL"; break; default: - roundstr = va("%d", grandprixinfo.roundnum); + roundstr = "BONUS"; break; } V_DrawCenteredString(64, 16, hilicol, roundstr); } + else if (roundqueue.size > 0) + { + V_DrawCenteredString(64, 8, 0, "ROUND"); + V_DrawCenteredString(64, 16, hilicol, va("%d", roundqueue.roundnum)); + } else if ((gametyperules & GTR_TIMELIMIT) && timelimitintics > 0) { UINT32 timeval = (timelimitintics + starttime + 1 - leveltime); diff --git a/src/k_grandprix.c b/src/k_grandprix.c index 73cc0d27b..52c82d7a3 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -283,10 +283,10 @@ static INT16 K_RivalScore(player_t *bot) UINT8 lowestdifficulty = MAXBOTDIFFICULTY; UINT8 i; - if (grandprixinfo.cup != NULL) + if (grandprixinfo.cup != NULL && roundqueue.size > 0) { - roundnum = grandprixinfo.roundnum; - roundsleft = grandprixinfo.cup->numlevels - grandprixinfo.roundnum; + roundnum = roundqueue.roundnum; + roundsleft = grandprixinfo.cup->numlevels - roundnum; } for (i = 0; i < MAXPLAYERS; i++) @@ -523,8 +523,10 @@ void K_RetireBots(void) UINT8 i; if (grandprixinfo.gp == true - && ((grandprixinfo.cup != NULL && grandprixinfo.roundnum >= grandprixinfo.cup->numlevels) - || grandprixinfo.eventmode != GPEVENT_NONE)) + && (grandprixinfo.eventmode != GPEVENT_NONE + || (grandprixinfo.cup != NULL + && roundqueue.size > 0 + && roundqueue.roundnum >= grandprixinfo.cup->numlevels))) { // No replacement. return; @@ -566,7 +568,11 @@ void K_RetireBots(void) else { const UINT8 startingdifficulty = K_BotStartingDifficulty(grandprixinfo.gamespeed); - newDifficulty = startingdifficulty - 4 + grandprixinfo.roundnum; + newDifficulty = startingdifficulty - 4; + if (roundqueue.size > 0) + { + newDifficulty += roundqueue.roundnum; + } } if (newDifficulty > MAXBOTDIFFICULTY) @@ -697,7 +703,7 @@ void K_PlayerLoseLife(player_t *player) --------------------------------------------------*/ boolean K_CanChangeRules(boolean allowdemos) { - if (grandprixinfo.gp == true /*&& grandprixinfo.roundnum > 0*/) + if (grandprixinfo.gp == true) { // Don't cheat the rules of the GP! return false; diff --git a/src/k_grandprix.h b/src/k_grandprix.h index 891efc85c..ba19bdb61 100644 --- a/src/k_grandprix.h +++ b/src/k_grandprix.h @@ -31,7 +31,6 @@ typedef enum extern struct grandprixinfo { boolean gp; ///< If true, then we are in a Grand Prix. - UINT8 roundnum; ///< Round number. If 0, this is a single session from the warp command. cupheader_t *cup; ///< Which cup are we playing? UINT8 gamespeed; ///< Copy of gamespeed, just to make sure you can't cheat it with cvars boolean encore; ///< Ditto, but for encore mode diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 7c798915a..fd17477eb 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -32,7 +32,6 @@ I_mutex k_menu_mutex; #endif boolean menuactive = false; -boolean fromlevelselect = false; // current menudef menu_t *currentMenu = &MAIN_ProfilesDef; diff --git a/src/k_pwrlv.c b/src/k_pwrlv.c index 528c7afd0..394e91c50 100644 --- a/src/k_pwrlv.c +++ b/src/k_pwrlv.c @@ -38,23 +38,38 @@ consvar_t cv_debugencorevote = CVAR_INIT ("debugencorevote", "Off", CV_CHEAT|CV_ SINT8 K_UsingPowerLevels(void) { - SINT8 pt = PWRLV_DISABLED; - - if (!cv_kartusepwrlv.value || !(netgame || (demo.playback && demo.netgame)) || grandprixinfo.gp == 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; - } - else if (gametype == GT_BATTLE) - { - pt = PWRLV_BATTLE; + // Race PWR. + return PWRLV_RACE; } - return pt; + if (gametype == GT_BATTLE) + { + // Battle PWR. + return PWRLV_BATTLE; + } + + // We do not support PWR for custom gametypes at this moment in time. + return PWRLV_DISABLED; } void K_ClearClientPowerLevels(void) diff --git a/src/menus/transient/cup-select.c b/src/menus/transient/cup-select.c index f4f97ed29..a83d5f2c9 100644 --- a/src/menus/transient/cup-select.c +++ b/src/menus/transient/cup-select.c @@ -104,7 +104,6 @@ void M_CupSelectHandler(INT32 choice) if (cupgrid.grandprix == true) { - INT32 levelNum; UINT8 ssplayers = cv_splitplayers.value-1; S_StartSound(NULL, sfx_s3k63); @@ -130,13 +129,16 @@ void M_CupSelectHandler(INT32 choice) grandprixinfo.gamespeed = min(KARTSPEED_HARD, cv_dummygpdifficulty.value); grandprixinfo.masterbots = (cv_dummygpdifficulty.value == 3); - grandprixinfo.encore = (boolean)cv_dummygpencore.value; - - grandprixinfo.cup = newcup; grandprixinfo.gp = true; - grandprixinfo.roundnum = 1; grandprixinfo.initalize = true; + grandprixinfo.cup = newcup; + + // Populate the roundqueue + memset(&roundqueue, 0, sizeof(struct roundqueue)); + G_GPCupIntoRoundQueue(newcup, levellist.newgametype, (boolean)cv_dummygpencore.value); + roundqueue.position = roundqueue.roundnum = 1; + roundqueue.netcommunicate = true; // relevant for future Online GP paused = false; @@ -146,16 +148,14 @@ void M_CupSelectHandler(INT32 choice) SV_StartSinglePlayerServer(levellist.newgametype, levellist.netgame); } - levelNum = grandprixinfo.cup->cachedlevels[0]; - D_MapChange( - levelNum + 1, - GT_RACE, - grandprixinfo.encore, + roundqueue.entries[0].mapnum + 1, + roundqueue.entries[0].gametype, + roundqueue.entries[0].encore, true, 1, false, - false + roundqueue.entries[0].rankrestricted ); M_ClearMenus(true); diff --git a/src/p_saveg.c b/src/p_saveg.c index 0e335c210..2b054b82b 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -49,15 +49,16 @@ savedata_t savedata; // Block UINT32s to attempt to ensure that the correct data is // being sent and received -#define ARCHIVEBLOCK_MISC 0x7FEEDEED -#define ARCHIVEBLOCK_PLAYERS 0x7F448008 -#define ARCHIVEBLOCK_PARTIES 0x7F87AF0C -#define ARCHIVEBLOCK_WORLD 0x7F8C08C0 -#define ARCHIVEBLOCK_POBJS 0x7F928546 -#define ARCHIVEBLOCK_THINKERS 0x7F37037C -#define ARCHIVEBLOCK_SPECIALS 0x7F228378 -#define ARCHIVEBLOCK_WAYPOINTS 0x7F46498F -#define ARCHIVEBLOCK_RNG 0x7FAAB5BD +#define ARCHIVEBLOCK_MISC 0x7FEEDEED +#define ARCHIVEBLOCK_PLAYERS 0x7F448008 +#define ARCHIVEBLOCK_PARTIES 0x7F87AF0C +#define ARCHIVEBLOCK_ROUNDQUEUE 0x7F721331 +#define ARCHIVEBLOCK_WORLD 0x7F8C08C0 +#define ARCHIVEBLOCK_POBJS 0x7F928546 +#define ARCHIVEBLOCK_THINKERS 0x7F37037C +#define ARCHIVEBLOCK_SPECIALS 0x7F228378 +#define ARCHIVEBLOCK_WAYPOINTS 0x7F46498F +#define ARCHIVEBLOCK_RNG 0x7FAAB5BD // Note: This cannot be bigger // than an UINT16 @@ -942,6 +943,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 Round-queue"); + + 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 /// @@ -5419,6 +5462,7 @@ void P_SaveNetGame(savebuffer_t *save, boolean resending) P_NetArchivePlayers(save); P_NetArchiveParties(save); + P_NetArchiveRoundQueue(save); if (gamestate == GS_LEVEL) { @@ -5469,6 +5513,7 @@ boolean P_LoadNetGame(savebuffer_t *save, boolean reloading) P_NetUnArchivePlayers(save); P_NetUnArchiveParties(save); + P_NetUnArchiveRoundQueue(save); if (gamestate == GS_LEVEL) { diff --git a/src/st_stuff.c b/src/st_stuff.c index 5dde5c826..81cd693da 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -696,7 +696,7 @@ void ST_runTitleCard(void) { boolean run = !(paused || P_AutoPause()); INT32 auxticker; - boolean gp = (marathonmode || (grandprixinfo.gp && grandprixinfo.roundnum)); + boolean doroundicon = (marathonmode || (roundqueue.size > 0 && roundqueue.position > 0)); if (!G_IsTitleCardAvailable()) return; @@ -818,7 +818,7 @@ void ST_runTitleCard(void) roundnumy = eggy1; // split both halves of the egg, but only do that in grand prix! - if (gp && lt_ticker > TTANIMTHRESHOLD + TICRATE/2) + if (doroundicon && lt_ticker > TTANIMTHRESHOLD + TICRATE/2) { auxticker = (INT32)lt_ticker - (TTANIMTHRESHOLD + TICRATE/2); @@ -881,7 +881,7 @@ void ST_drawTitleCard(void) char *lvlttl = mapheaderinfo[gamemap-1]->lvlttl; char *zonttl = mapheaderinfo[gamemap-1]->zonttl; // SRB2kart UINT8 actnum = mapheaderinfo[gamemap-1]->actnum; - boolean gp = (marathonmode || (grandprixinfo.gp && grandprixinfo.roundnum)); + boolean doroundicon = (marathonmode || (roundqueue.size > 0 && roundqueue.position > 0)); INT32 acttimer; fixed_t actscale; @@ -1011,9 +1011,9 @@ void ST_drawTitleCard(void) // Draw ROUND bar, scroll it downwards. V_DrawFixedPatch(roundx*FRACUNIT, ((-32) + (lt_ticker%32))*FRACUNIT, FRACUNIT, V_SNAPTOTOP|V_SNAPTOLEFT, tcroundbar, NULL); // Draw ROUND text - if (gp) + if (doroundicon) V_DrawFixedPatch((roundx+10)*FRACUNIT, roundy*FRACUNIT, FRACUNIT, V_SNAPTOTOP|V_SNAPTOLEFT, - ((grandprixinfo.gp && grandprixinfo.eventmode) ? tcbonus : tcround), + ((grandprixinfo.gp && grandprixinfo.eventmode != GPEVENT_NONE) ? tcbonus : tcround), NULL); // round num background @@ -1032,25 +1032,29 @@ void ST_drawTitleCard(void) } // If possible, draw round number/icon - if (gp) + if (doroundicon) { patch_t *roundico = NULL; if (marathonmode) ; // TODO: Ruby - else switch (grandprixinfo.eventmode) + else if (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE) { - case GPEVENT_BONUS: - roundico = tcroundbonus; // TODO don't show capsule if we have other bonus types - break; - /*case GPEVENT_SPECIAL: - ; // TODO: Emerald/mount - break;*/ - case GPEVENT_NONE: - if (grandprixinfo.roundnum > 0 && grandprixinfo.roundnum < 11) // Check boundaries JUST IN CASE. - roundico = tcroundnum[grandprixinfo.roundnum-1]; - break; - default: - break; + switch (grandprixinfo.eventmode) + { + case GPEVENT_BONUS: + roundico = tcroundbonus; // TODO don't show capsule if we have other bonus types + break; + /*case GPEVENT_SPECIAL: + ; // TODO: Emerald/mount + break;*/ + default: + break; + } + } + else if (roundqueue.size > 0) + { + if (roundqueue.roundnum > 0 && roundqueue.roundnum < 11) // We DEFINITELY need to check boundaries. + roundico = tcroundnum[roundqueue.roundnum-1]; } if (roundico) diff --git a/src/typedef.h b/src/typedef.h index a8d2d669f..47d5fcf45 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -135,6 +135,7 @@ TYPEDEF (menudemo_t); TYPEDEF (demoghost); // g_game.h +TYPEDEF (roundentry_t); TYPEDEF (mapsearchfreq_t); // hu_stuff.h diff --git a/src/y_inter.c b/src/y_inter.c index 884466261..3636834e2 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -539,6 +539,9 @@ skiptallydrawer: if (demo.playback) string = va("Replay ends in %d", tickdown); + else if ((nextmapoverride != 0) + || (roundqueue.size > 0 && roundqueue.position < roundqueue.size)) + string = va("Next starts in %d", tickdown); else string = va("%s starts in %d", cv_advancemap.string, tickdown);