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
parent a1065d9036
commit 6bca2a7003
2 changed files with 256 additions and 0 deletions

View file

@ -102,6 +102,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);
@ -162,6 +164,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);
@ -632,6 +635,8 @@ const char *netxcmdnames[MAXNETXCMD - 1] =
"SCHEDULETASK", // XD_SCHEDULETASK
"SCHEDULECLEAR", // XD_SCHEDULECLEAR
"AUTOMATE", // XD_AUTOMATE
"REQMAPQUEUE", // XD_REQMAPQUEUE
"MAPQUEUE", // XD_MAPQUEUE
};
// =========================================================================
@ -681,6 +686,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);
@ -703,6 +710,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);
@ -3271,6 +3279,252 @@ static void Command_RestartLevel(void)
D_MapChange(gamemap, gametype, newencore, 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, 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;
}
}
{
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

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