Console command queuemap

- Full valid format: queuemap [name/num] -gametype [name] -encore -force
- Server is fully authoriative about the order of maps in the round-queue
    - Server sends XD_MAPQUEUE (which contains gametype, encore, and ordering)
    - Admin clients have to send XD_REQMAPQUEUE (which contains gametype, encore, and mapnum)
        - Servers spit out a processed XD_MAPQUEUE on reciept
    - Done this way just in case an XD_MAPQUEUE is not recieved and has to be resent, guarantees ordering
- Will create a UI for this post-launch, this is primarily for testing but may be useful for user-ran tournaments
This commit is contained in:
toaster 2023-04-10 23:41:58 +01:00 committed by NepDisk
parent 24373da780
commit 04697e94f3
3 changed files with 319 additions and 34 deletions

View file

@ -106,6 +106,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 Command_ScoreboardAdd(void);
static void Command_ScoreboardClear(void);
@ -190,6 +192,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);
@ -942,6 +945,8 @@ const char *netxcmdnames[MAXNETXCMD - 1] =
"SCHEDULECLEAR", // XD_SCHEDULECLEAR
"AUTOMATE", // XD_AUTOMATE
"CHEAT", // XD_CHEAT
"REQMAPQUEUE", // XD_REQMAPQUEUE
"MAPQUEUE", // XD_MAPQUEUE
};
// =========================================================================
@ -991,6 +996,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);
COM_AddCommand("scoreboard_addline", Command_ScoreboardAdd);
@ -1016,6 +1023,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);
@ -3562,6 +3570,64 @@ static char *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;
@ -3663,39 +3729,10 @@ 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 gametype.\n",
gametypename);
Z_Free(realmapname);
Z_Free(mapname);
return;
}
return;
}
}
else if (!Playing())
@ -4086,6 +4123,252 @@ static void Command_RestartLevel(void)
D_MapChange(gamemap, gametype, encoremode, false, 0, false, 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 <name / number> [-gametype <type>] [-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) && 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;
}
}
{
static char buf[1+1+2];
static char *buf_p = buf;
UINT8 flags = 0;
CONS_Debug(DBG_GAMELOGIC, "Map queue: mapnum=%d newgametype=%d newencoremode=%d\n",
newmapnum-1, newgametype, newencoremode);
buf_p = buf;
if (newencoremode)
flags |= 1;
WRITEUINT8(buf_p, flags);
WRITEUINT8(buf_p, newgametype);
if (client)
{
WRITEUINT16(buf_p, (newmapnum-1));
SendNetXCmd(XD_REQMAPQUEUE, buf, buf_p - buf);
}
else
{
WRITEUINT8(buf_p, roundqueue.size);
SendNetXCmd(XD_MAPQUEUE, buf, buf_p - buf);
G_MapIntoRoundQueue(newmapnum-1, newgametype, newencoremode, false);
}
}
Z_Free(realmapname);
Z_Free(mapname);
}
static void Got_RequestMapQueuecmd(UINT8 **cp, INT32 playernum)
{
UINT8 flags, setgametype;
boolean setencore;
UINT16 mapnumber;
flags = READUINT8(*cp);
setencore = ((flags & 1) != 0);
setgametype = READUINT8(*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;
{
static char buf[1+1+1];
static char *buf_p = buf;
buf_p = buf;
WRITEUINT8(buf_p, flags);
WRITEUINT8(buf_p, setgametype);
WRITEUINT8(buf_p, roundqueue.size);
SendNetXCmd(XD_MAPQUEUE, buf, buf_p - buf);
}
G_MapIntoRoundQueue(mapnumber, setgametype, setencore, false);
}
static void Got_MapQueuecmd(UINT8 **cp, INT32 playernum)
{
UINT8 flags, setgametype, queueposition;
boolean setencore;
flags = READUINT8(*cp);
setencore = ((flags & 1) != 0);
setgametype = READUINT8(*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);
}
static void Command_Pause(void)
{
UINT8 buf[2];

View file

@ -360,6 +360,8 @@ typedef enum
XD_SCHEDULECLEAR, // 36
XD_AUTOMATE, // 37
XD_CHEAT, // 38
XD_REQMAPQUEUE, // 39
XD_MAPQUEUE, // 40
MAXNETXCMD
} netxcmd_t;

View file

@ -778,8 +778,8 @@ void ST_drawTitleCard(void)
V_DrawLevelTitle(ttlnumxpos+12, bary+6, V_SNAPTOBOTTOM, actnum);
// TODO: a bit more elaborate?
if (grandprixinfo.gp)
V_DrawCenteredString(BASEVIDWIDTH/2 + sub, bary+24, V_SNAPTOBOTTOM|V_YELLOWMAP, va("ROUND %d", grandprixinfo.roundnum));
if (roundqueue.size > 0)
V_DrawCenteredString(BASEVIDWIDTH/2 + sub, bary+24, V_SNAPTOBOTTOM|V_YELLOWMAP, va("ROUND %d", roundqueue.roundnum));
luahook:
if (renderisnewtic)