Overhaul the map command

Added support for map names, matched by substring and keywords too!
Added support for two digit MAP codes without the MAP part.
Added support for decimal map number. (But who cares.)

Gave a better description of command.
Supported abbreviated optional parameters.

And now REALLY detects incorrect parameters.
This commit is contained in:
James R 2019-02-22 20:18:33 -08:00
parent c221a89c67
commit 6358d839ee
4 changed files with 308 additions and 68 deletions

View File

@ -1719,25 +1719,54 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pultmode, boolean rese
}
}
/*
Easy macro; declare parm_*id* and define acceptableargc; put in the parameter
to match as a string as *name*. Set *argn* to the number of extra arguments
following the parameter. parm_*id* is filled with the index of the parameter
found and acceptableargc is incremented to match the macro parameters.
Returned is whether the parameter was found.
*/
#define CHECKPARM( id, name, argn ) \
( (( parm_ ## id = COM_CheckParm(name) )) &&\
( acceptableargc += 1 + argn ) )
//
// Warp to map code.
// Called either from map <mapname> console command, or idclev cheat.
//
// Largely rewritten by James.
//
static void Command_Map_f(void)
{
const char *mapname;
size_t i;
INT32 newmapnum;
size_t acceptableargc;
size_t parm_force;
size_t parm_gametype;
const char *arg_gametype;
/* debug? */
size_t parm_noresetplayers;
boolean newresetplayers;
boolean mustmodifygame;
boolean usemapcode = false;
INT32 newmapnum;
INT32 apromapnum = 0;
const char *mapname;
size_t mapnamelen;
char *realmapname = NULL;
char *apromapname = NULL;
/* Keyword matching */
char *query;
char *key;
UINT8 *freq;
UINT8 freqc;
INT32 newgametype = gametype;
// max length of command: map map03 -gametype coop -noresetplayers -force
// 1 2 3 4 5 6
// = 8 arg max
if (COM_Argc() < 2 || COM_Argc() > 8)
{
CONS_Printf(M_GetText("map <mapname> [-gametype <type> [-force]: warp to map\n"));
return;
}
INT32 i;
INT32 d;
char *p;
if (client && !IsPlayerAdmin(consoleplayer))
{
@ -1745,62 +1774,217 @@ static void Command_Map_f(void)
return;
}
// internal wad lump always: map command doesn't support external files as in doom legacy
if (W_CheckNumForName(COM_Argv(1)) == LUMPERROR)
acceptableargc = 2;/* map name */
(void)
(
CHECKPARM (force, "-force", 0) ||
CHECKPARM (force, "-f", 0)
);
(void)
(
CHECKPARM (gametype, "-gametype", 1) ||
CHECKPARM (gametype, "-g", 1) ||
CHECKPARM (gametype, "-gt", 1)
);
(void)CHECKPARM (noresetplayers, "-noresetplayers", 0);
newresetplayers = !parm_noresetplayers;
mustmodifygame =
!( netgame || multiplayer ) &&
(!modifiedgame || savemoddata );
if (mustmodifygame && !parm_force)
{
CONS_Alert(CONS_ERROR, M_GetText("Internal game level '%s' not found\n"), COM_Argv(1));
/* May want to be more descriptive? */
CONS_Printf(M_GetText("Sorry, level change disabled in single player.\n"));
return;
}
if (!(netgame || multiplayer) && (!modifiedgame || savemoddata))
{
if (COM_CheckParm("-force"))
G_SetGameModified(false);
else
{
CONS_Printf(M_GetText("Sorry, level change disabled in single player.\n"));
return;
}
}
newresetplayers = !COM_CheckParm("-noresetplayers");
if (!newresetplayers && !cv_debug)
{
CONS_Printf(M_GetText("DEVMODE must be enabled.\n"));
return;
}
mapname = COM_Argv(1);
if (strlen(mapname) != 5
|| (newmapnum = M_MapNumber(mapname[3], mapname[4])) == 0)
if (parm_gametype && !multiplayer)
{
CONS_Alert(CONS_ERROR, M_GetText("Invalid level name %s\n"), mapname);
CONS_Printf(M_GetText("You can't switch gametypes in single player!\n"));
return;
}
// Ultimate Mode only in SP via menu
if (netgame || multiplayer)
ultimatemode = false;
if (COM_Argc() != acceptableargc)
{
/* I'm going over the fucking lines and I DON'T CAREEEEE */
CONS_Printf("map <name / [MAP]code / number> [-gametype <type>] [-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.\n"
"* \"-force\" may be shortened to \"-f\".\n"
"* \"-gametype\" may be shortened to \"-g\" or \"-gt\".\n"));
return;
}
mapname = COM_Argv(1);
mapnamelen = strlen(mapname);
if (mapnamelen == 2)/* maybe two digit code */
{
if (( newmapnum = M_MapNumber(mapname[0], mapname[1]) ))
usemapcode = true;
}
else if (mapnamelen == 5 && strnicmp(mapname, "MAP", 3) == 0)
{
if (( newmapnum = M_MapNumber(mapname[3], mapname[4]) ) == 0)
{
CONS_Alert(CONS_ERROR, M_GetText("Invalid map code '%s'.\n"), mapname);
return;
}
usemapcode = true;
}
if (!usemapcode)
{
/* Now detect map number in base 10, which no one asked for. */
newmapnum = strtol(mapname, &p, 10);
if (*p == '\0')/* we got it */
{
if (newmapnum < 1 || newmapnum > NUMMAPS)
{
CONS_Alert(CONS_ERROR, M_GetText("Invalid map number %d.\n"), newmapnum);
return;
}
usemapcode = true;
}
else
{
query = ZZ_Alloc(strlen(mapname)+1);
freq = ZZ_Calloc(NUMMAPS * sizeof (UINT8));
for (i = 0, newmapnum = 1; i < NUMMAPS; ++i, ++newmapnum)
if (mapheaderinfo[i])
{
realmapname = G_BuildMapTitle(newmapnum);
/* Now that we found a perfect match no need to fucking guess. */
if (strnicmp(realmapname, mapname, mapnamelen) == 0)
{
Z_Free(apromapname);
break;
}
if (apromapnum == 0)
{
/* LEVEL 1--match keywords verbatim */
if (strcasestr(realmapname, mapname))
{
apromapnum = newmapnum;
apromapname = realmapname;
realmapname = 0;
}
else/* ...match individual keywords */
{
strcpy(query, mapname);
for (key = strtok(query, " ");
key;
key = strtok(0, " "))
{
if (strcasestr(realmapname, key))
{
freq[i]++;
}
}
}
}
Z_Free(realmapname);/* leftover old name */
}
if (newmapnum == NUMMAPS+1)/* no perfect match--try a substring */
{
newmapnum = apromapnum;
realmapname = apromapname;
}
if (newmapnum == 0)/* calculate most queries met! */
{
freqc = 0;
for (i = 0; i < NUMMAPS; ++i)
{
if (freq[i] > freqc)
{
freqc = freq[i];
newmapnum = i + 1;
}
}
if (newmapnum)
{
realmapname = G_BuildMapTitle(newmapnum);
}
}
Z_Free(freq);
Z_Free(query);
}
}
if (newmapnum == 0 || !mapheaderinfo[newmapnum-1])
{
CONS_Alert(CONS_ERROR, M_GetText("Could not find any map described as '%s'.\n"), mapname);
return;
}
if (usemapcode)
{
realmapname = G_BuildMapTitle(newmapnum);
}
if (mustmodifygame && parm_force)
{
G_SetGameModified(false);
}
arg_gametype = COM_Argv(parm_gametype + 1);
// new gametype value
// use current one by default
i = COM_CheckParm("-gametype");
if (i)
if (parm_gametype)
{
if (!multiplayer)
{
CONS_Printf(M_GetText("You can't switch gametypes in single player!\n"));
return;
}
newgametype = G_GetGametypeByName(COM_Argv(i+1));
newgametype = G_GetGametypeByName(arg_gametype);
if (newgametype == -1) // reached end of the list with no match
{
INT32 j = atoi(COM_Argv(i+1)); // assume they gave us a gametype number, which is okay too
if (j >= 0 && j < NUMGAMETYPES)
newgametype = (INT16)j;
d = atoi(arg_gametype);
// assume they gave us a gametype number, which is okay too
if (d >= 0 && d < NUMGAMETYPES)
newgametype = d;
}
}
// don't use a gametype the map doesn't support
if (cv_debug || parm_force || cv_skipmapcheck.value)
fromlevelselect = false; // The player wants us to trek on anyway. Do so.
// G_TOLFlag handles both multiplayer gametype and ignores it for !multiplayer
else
{
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),
(multiplayer ? gametype_cons_t[newgametype].strvalue : "Single Player"));
Z_Free(realmapname);
return;
}
else
{
fromlevelselect =
( netgame || multiplayer ) &&
newgametype == gametype &&
newgametype == GT_COOP;
}
}
@ -1811,31 +1995,13 @@ static void Command_Map_f(void)
if (!dedicated && M_MapLocked(newmapnum))
{
CONS_Alert(CONS_NOTICE, M_GetText("You need to unlock this level before you can warp to it!\n"));
Z_Free(realmapname);
return;
}
// don't use a gametype the map doesn't support
if (cv_debug || COM_CheckParm("-force") || cv_skipmapcheck.value)
fromlevelselect = false; // The player wants us to trek on anyway. Do so.
// G_TOLFlag handles both multiplayer gametype and ignores it for !multiplayer
// Alternatively, bail if the map header is completely missing anyway.
else if (!mapheaderinfo[newmapnum-1]
|| !(mapheaderinfo[newmapnum-1]->typeoflevel & G_TOLFlag(newgametype)))
{
char gametypestring[32] = "Single Player";
if (multiplayer)
{
if (newgametype >= 0 && newgametype < NUMGAMETYPES
&& Gametype_Names[newgametype])
strcpy(gametypestring, Gametype_Names[newgametype]);
}
CONS_Alert(CONS_WARNING, M_GetText("%s doesn't support %s mode!\n(Use -force to override)\n"), mapname, gametypestring);
return;
}
else
fromlevelselect = ((netgame || multiplayer) && ((gametype == newgametype) && (newgametype == GT_COOP)));
// Ultimate Mode only in SP via menu
if (netgame || multiplayer)
ultimatemode = false;
if (tutorialmode && tutorialgcs)
{
@ -1848,7 +2014,10 @@ static void Command_Map_f(void)
tutorialmode = false; // warping takes us out of tutorial mode
D_MapChange(newmapnum, newgametype, false, newresetplayers, 0, false, fromlevelselect);
Z_Free(realmapname);
}
#undef CHECKPARM
/** Receives a map command and changes the map.
*

View File

@ -495,6 +495,7 @@ extern boolean capslock;
// if we ever make our alloc stuff...
#define ZZ_Alloc(x) Z_Malloc(x, PU_STATIC, NULL)
#define ZZ_Calloc(x) Z_Calloc(x, PU_STATIC, NULL)
// i_system.c, replace getchar() once the keyboard has been appropriated
INT32 I_GetKey(void);

View File

@ -115,6 +115,9 @@ typedef long ssize_t;
#define strnicmp(x,y,n) strncasecmp(x,y,n)
#endif
char *strcasestr(const char *in, const char *what);
#define stristr strcasestr
#if defined (macintosh) //|| defined (__APPLE__) //skip all boolean/Boolean crap
#define true 1
#define false 0

View File

@ -1672,6 +1672,73 @@ void strcatbf(char *s1, const char *s2, const char *s3)
strcat(s1, tmp);
}
/** Locate a substring, case-insensitively.
* Know that I hate this style. -James
*
* \param s The string to search within.
* \param q The substring to find.
* \return a pointer to the located substring, or NULL if it could be found.
*/
char *strcasestr(const char *s, const char *q)
{
void **vpp;/* a hack! */
size_t qz;
const char *up;
const char *lp;
int uc;
int lc;
qz = strlen(q);
uc = toupper(*q);
lc = tolower(*q);
up = s;
lp = s;
do
{
if (uc > 0)
{
up = strchr(up, uc);
if (!up || ( lc == 0 && lp < up ))
uc = -1;
else
if (strnicmp(q, up, qz) == 0)
uc = 0;
else
up++;
}
if (lc > 0)
{
lp = strchr(lp, lc);
if (!lp || ( uc == 0 && up < lp ))
lc = -1;
else
if (strnicmp(q, lp, qz) == 0)
lc = 0;
else
lp++;
}
}
while (( uc > 0 ) || ( lc > 0 )) ;
if (uc == 0)
vpp = (void **)&up;
else
vpp = (void **)&lp;
/*
We can dereference a double void pointer and cast it to remove const.
This works because the original variable (the pointer) is writeable,
but its value is not.
*/
return (char *)*vpp;
}
/** Converts an ASCII Hex string into an integer. Thanks, Borland!
* <Inuyasha> I don't know if this belongs here specifically, but it sure
* doesn't belong in p_spec.c, that's for sure