From 6358d839ee3ed7220a611ce412bcd7127978385b Mon Sep 17 00:00:00 2001 From: James R Date: Fri, 22 Feb 2019 20:18:33 -0800 Subject: [PATCH 01/11] 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. --- src/d_netcmd.c | 305 ++++++++++++++++++++++++++++++++++++++----------- src/doomdef.h | 1 + src/doomtype.h | 3 + src/m_misc.c | 67 +++++++++++ 4 files changed, 308 insertions(+), 68 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index b14f92b33..68ab3568f 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -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 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 [-gametype [-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 [-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.\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. * diff --git a/src/doomdef.h b/src/doomdef.h index 676c86e0d..b58d59259 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -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); diff --git a/src/doomtype.h b/src/doomtype.h index 7acdde966..bed9c09a0 100644 --- a/src/doomtype.h +++ b/src/doomtype.h @@ -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 diff --git a/src/m_misc.c b/src/m_misc.c index f7d5cf961..701fba2db 100644 --- a/src/m_misc.c +++ b/src/m_misc.c @@ -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! * I don't know if this belongs here specifically, but it sure * doesn't belong in p_spec.c, that's for sure From bb65e8d72fc724e75a8aeeed5f2c8fd64954feec Mon Sep 17 00:00:00 2001 From: James R Date: Fri, 22 Feb 2019 22:25:05 -0800 Subject: [PATCH 02/11] Add support for custom keywords For now, 32 characters separated by spaces. Also fixed a SIGSEGV from an empty level title. --- src/d_netcmd.c | 39 ++++++++++++++++++++++++--------------- src/dehacked.c | 5 +++++ src/doomstat.h | 1 + src/lua_maplib.c | 2 ++ src/p_setup.c | 1 + 5 files changed, 33 insertions(+), 15 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 68ab3568f..55833d0da 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -1719,6 +1719,25 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pultmode, boolean rese } } +/* +Return the number of times a series of keywords, delimited by spaces, matched. +*/ +static int measurekeywords(const char *s, const char *q) +{ + int r = 0; + char *qp; + for (qp = strtok(va("%s", q), " "); + qp; + qp = strtok(0, " ")) + { + if (strcasestr(s, qp)) + { + r++; + } + } + return r; +} + /* 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 @@ -1757,8 +1776,6 @@ static void Command_Map_f(void) char *apromapname = NULL; /* Keyword matching */ - char *query; - char *key; UINT8 *freq; UINT8 freqc; @@ -1860,13 +1877,13 @@ static void Command_Map_f(void) } 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); + if (!( realmapname = G_BuildMapTitle(newmapnum) )) + continue; /* Now that we found a perfect match no need to fucking guess. */ if (strnicmp(realmapname, mapname, mapnamelen) == 0) @@ -1886,16 +1903,9 @@ static void Command_Map_f(void) } else/* ...match individual keywords */ { - strcpy(query, mapname); - for (key = strtok(query, " "); - key; - key = strtok(0, " ")) - { - if (strcasestr(realmapname, key)) - { - freq[i]++; - } - } + freq[i] += measurekeywords(realmapname, mapname); + freq[i] += measurekeywords(mapheaderinfo[i]->keyword, + mapname); } } @@ -1926,7 +1936,6 @@ static void Command_Map_f(void) } Z_Free(freq); - Z_Free(query); } } diff --git a/src/dehacked.c b/src/dehacked.c index 34ee1f170..35274660d 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -1205,6 +1205,11 @@ static void readlevelheader(MYFILE *f, INT32 num) mapheaderinfo[num-1]->typeoflevel = tol; } } + else if (fastcmp(word, "KEYWORD")) + { + deh_strlcpy(mapheaderinfo[num-1]->keyword, word2, + sizeof(mapheaderinfo[num-1]->keyword), va("Level header %d: keyword", num)); + } else if (fastcmp(word, "MUSIC")) { if (fastcmp(word2, "NONE")) diff --git a/src/doomstat.h b/src/doomstat.h index 7d06f03e2..e01cf3227 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -286,6 +286,7 @@ typedef struct UINT8 actnum; ///< Act number or 0 for none. UINT16 typeoflevel; ///< Combination of typeoflevel flags. INT16 nextlevel; ///< Map number of next level, or 1100-1102 to end. + char keyword[33]; ///< Keywords separated by space to search for. 32 characters. char musname[7]; ///< Music track to play. "" for no music. UINT16 mustrack; ///< Subsong to play. Only really relevant for music modules and specific formats supported by GME. 0 to ignore. UINT32 muspos; ///< Music position to jump to. diff --git a/src/lua_maplib.c b/src/lua_maplib.c index 1da232efa..53ff29962 100644 --- a/src/lua_maplib.c +++ b/src/lua_maplib.c @@ -2014,6 +2014,8 @@ static int mapheaderinfo_get(lua_State *L) lua_pushinteger(L, header->typeoflevel); else if (fastcmp(field,"nextlevel")) lua_pushinteger(L, header->nextlevel); + else if (fastcmp(field,"keyword")) + lua_pushstring(L, header->keyword); else if (fastcmp(field,"musname")) lua_pushstring(L, header->musname); else if (fastcmp(field,"mustrack")) diff --git a/src/p_setup.c b/src/p_setup.c index cef176636..d98269065 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -211,6 +211,7 @@ static void P_ClearSingleMapHeaderInfo(INT16 i) mapheaderinfo[num]->typeoflevel = 0; mapheaderinfo[num]->nextlevel = (INT16)(i + 1); mapheaderinfo[num]->startrings = 0; + mapheaderinfo[num]->keyword[0] = '\0'; snprintf(mapheaderinfo[num]->musname, 7, "%sM", G_BuildMapName(i)); mapheaderinfo[num]->musname[6] = 0; mapheaderinfo[num]->mustrack = 0; From 70df5242a3d75817a157b13cb20108c225f2a64a Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 23 Feb 2019 21:25:53 -0800 Subject: [PATCH 03/11] Extend map name text matching code into a function --- src/d_netcmd.c | 85 +---------------------- src/g_game.c | 184 +++++++++++++++++++++++++++++++++++++++++++++++++ src/g_game.h | 21 ++++++ 3 files changed, 206 insertions(+), 84 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 55833d0da..246b509c2 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -1719,25 +1719,6 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pultmode, boolean rese } } -/* -Return the number of times a series of keywords, delimited by spaces, matched. -*/ -static int measurekeywords(const char *s, const char *q) -{ - int r = 0; - char *qp; - for (qp = strtok(va("%s", q), " "); - qp; - qp = strtok(0, " ")) - { - if (strcasestr(s, qp)) - { - r++; - } - } - return r; -} - /* 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 @@ -1768,16 +1749,10 @@ static void Command_Map_f(void) boolean usemapcode = false; INT32 newmapnum; - INT32 apromapnum = 0; const char *mapname; size_t mapnamelen; char *realmapname = NULL; - char *apromapname = NULL; - - /* Keyword matching */ - UINT8 *freq; - UINT8 freqc; INT32 newgametype = gametype; @@ -1877,65 +1852,7 @@ static void Command_Map_f(void) } else { - freq = ZZ_Calloc(NUMMAPS * sizeof (UINT8)); - - for (i = 0, newmapnum = 1; i < NUMMAPS; ++i, ++newmapnum) - if (mapheaderinfo[i]) - { - if (!( realmapname = G_BuildMapTitle(newmapnum) )) - continue; - - /* 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 */ - { - freq[i] += measurekeywords(realmapname, mapname); - freq[i] += measurekeywords(mapheaderinfo[i]->keyword, - mapname); - } - } - - 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); + newmapnum = G_FindMap(mapname, &realmapname, NULL, NULL); } } diff --git a/src/g_game.c b/src/g_game.c index e2f43e4f2..bc67ed792 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4040,6 +4040,190 @@ char *G_BuildMapTitle(INT32 mapnum) return title; } +static void measurekeywords(mapsearchfreq_t *fr, + struct searchdim **dimp, UINT8 *cuntp, + const char *s, const char *q, boolean wanttable) +{ + char *qp; + char *sp; + if (wanttable) + (*dimp) = Z_Realloc((*dimp), 255 * sizeof (struct searchdim), + PU_STATIC, NULL); + for (qp = strtok(va("%s", q), " "); + qp && fr->total < 255; + qp = strtok(0, " ")) + { + if (( sp = strcasestr(s, qp) )) + { + if (wanttable) + { + (*dimp)[(*cuntp)].pos = sp - s; + (*dimp)[(*cuntp)].siz = strlen(qp); + } + (*cuntp)++; + fr->total++; + } + } + if (wanttable) + (*dimp) = Z_Realloc((*dimp), (*cuntp) * sizeof (struct searchdim), + PU_STATIC, NULL); +} + +void writesimplefreq(mapsearchfreq_t *fr, INT32 *frc, + INT32 mapnum, UINT8 pos, UINT8 siz) +{ + fr[(*frc)].mapnum = mapnum; + fr[(*frc)].matchd = ZZ_Alloc(sizeof (struct searchdim)); + fr[(*frc)].matchd[0].pos = pos; + fr[(*frc)].matchd[0].siz = siz; + fr[(*frc)].matchc = 1; + fr[(*frc)].total = 1; + (*frc)++; +} + +INT32 G_FindMap(const char *mapname, char **foundmapnamep, + mapsearchfreq_t **freqp, INT32 *freqcp) +{ + INT32 newmapnum = 0; + INT32 mapnum; + INT32 apromapnum = 0; + + size_t mapnamelen; + char *realmapname = NULL; + char *newmapname = NULL; + char *apromapname = NULL; + char *aprop = NULL; + + mapsearchfreq_t *freq; + boolean wanttable; + INT32 freqc; + UINT8 frequ; + + INT32 i; + + mapnamelen = strlen(mapname); + + /* Count available maps; how ugly. */ + for (i = 0, freqc = 0; i < NUMMAPS; ++i) + { + if (mapheaderinfo[i]) + freqc++; + } + + freq = ZZ_Calloc(freqc * sizeof (mapsearchfreq_t)); + + wanttable = !!( freqp ); + + freqc = 0; + for (i = 0, mapnum = 1; i < NUMMAPS; ++i, ++mapnum) + if (mapheaderinfo[i]) + { + if (!( realmapname = G_BuildMapTitle(mapnum) )) + continue; + + aprop = realmapname; + + /* Now that we found a perfect match no need to fucking guess. */ + if (strnicmp(realmapname, mapname, mapnamelen) == 0) + { + if (wanttable) + { + writesimplefreq(freq, &freqc, mapnum, 0, mapnamelen); + } + if (newmapnum == 0) + { + newmapnum = mapnum; + newmapname = realmapname; + realmapname = 0; + Z_Free(apromapname); + if (!wanttable) + break; + } + } + else + if (apromapnum == 0 || wanttable) + { + /* LEVEL 1--match keywords verbatim */ + if (( aprop = strcasestr(realmapname, mapname) )) + { + if (wanttable) + { + writesimplefreq(freq, &freqc, + mapnum, aprop - realmapname, mapnamelen); + } + if (apromapnum == 0) + { + apromapnum = mapnum; + apromapname = realmapname; + realmapname = 0; + } + } + else/* ...match individual keywords */ + { + freq[freqc].mapnum = mapnum; + measurekeywords(&freq[freqc], + &freq[freqc].matchd, &freq[freqc].matchc, + realmapname, mapname, wanttable); + measurekeywords(&freq[freqc], + &freq[freqc].keywhd, &freq[freqc].keywhc, + mapheaderinfo[i]->keyword, mapname, wanttable); + if (freq[freqc].total) + freqc++; + } + } + + Z_Free(realmapname);/* leftover old name */ + } + + if (newmapnum == 0)/* no perfect match--try a substring */ + { + newmapnum = apromapnum; + newmapname = apromapname; + } + + if (newmapnum == 0)/* calculate most queries met! */ + { + frequ = 0; + for (i = 0; i < freqc; ++i) + { + if (freq[i].total > frequ) + { + frequ = freq[i].total; + newmapnum = freq[i].mapnum; + } + } + if (newmapnum) + { + newmapname = G_BuildMapTitle(newmapnum); + } + } + + if (freqp) + (*freqp) = freq; + else + Z_Free(freq); + + if (freqcp) + (*freqcp) = freqc; + + if (foundmapnamep) + (*foundmapnamep) = newmapname; + else + Z_Free(newmapname); + + return newmapnum; +} + +void G_FreeMapSearch(mapsearchfreq_t *freq, INT32 freqc) +{ + INT32 i; + for (i = 0; i < freqc; ++i) + { + Z_Free(freq[i].matchd); + } + Z_Free(freq); +} + // // DEMO RECORDING // diff --git a/src/g_game.h b/src/g_game.h index 198cbc396..3aaaf95d5 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -107,6 +107,27 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean skipprecutscene, boolean FLS); char *G_BuildMapTitle(INT32 mapnum); +struct searchdim +{ + UINT8 pos; + UINT8 siz; +}; + +typedef struct +{ + INT16 mapnum; + UINT8 matchc; + struct searchdim *matchd;/* offset that a pattern was matched */ + UINT8 keywhc; + struct searchdim *keywhd;/* ...in KEYWORD */ + UINT8 total;/* total hits */ +} +mapsearchfreq_t; + +INT32 G_FindMap(const char *query, char **foundmapnamep, + mapsearchfreq_t **freqp, INT32 *freqc); +void G_FreeMapSearch(mapsearchfreq_t *freq, INT32 freqc); + // XMOD spawning mapthing_t *G_FindCTFStart(INT32 playernum); mapthing_t *G_FindMatchStart(INT32 playernum); From 7bf819f5af888c2389310409bf8a953b5ee96164 Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 21 Apr 2019 23:18:40 -0700 Subject: [PATCH 04/11] Use a superior strcasestr --- src/m_misc.c | 67 ----------------------------- src/strcasestr.c | 110 +++++++++++++++++++++++++++++++++++++++++++++++ src/string.c | 3 ++ 3 files changed, 113 insertions(+), 67 deletions(-) create mode 100644 src/strcasestr.c diff --git a/src/m_misc.c b/src/m_misc.c index 701fba2db..f7d5cf961 100644 --- a/src/m_misc.c +++ b/src/m_misc.c @@ -1672,73 +1672,6 @@ 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! * I don't know if this belongs here specifically, but it sure * doesn't belong in p_spec.c, that's for sure diff --git a/src/strcasestr.c b/src/strcasestr.c new file mode 100644 index 000000000..2077dc3ff --- /dev/null +++ b/src/strcasestr.c @@ -0,0 +1,110 @@ +/* +strcasestr -- case insensitive substring searching function. +*/ +/* +Copyright 2019 James R. +All rights reserved. + +Redistribution and use in source forms, with or without modification, is +permitted provided that the following condition is met: + +1. Redistributions of source code must retain the above copyright notice, this + condition and the following disclaimer. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define SWAP( a, b ) \ +(\ + (a) ^= (b),\ + (b) ^= (a),\ + (a) ^= (b)\ +) + +static inline int +trycmp (char **pp, char *cp, + const char *q, size_t qn) +{ + char *p; + p = (*pp); + if (strncasecmp(p, q, qn) == 0) + return 0; + (*pp) = strchr(&p[1], (*cp)); + return 1; +} + +static inline void +swapp (char ***ppap, char ***ppbp, char **cpap, char **cpbp) +{ + SWAP(*(intptr_t *)ppap, *(intptr_t *)ppbp); + SWAP(*(intptr_t *)cpap, *(intptr_t *)cpbp); +} + +char * +strcasestr (const char *s, const char *q) +{ + size_t qn; + + char uc; + char lc; + + char *up; + char *lp; + + char **ppa; + char **ppb; + + char *cpa; + char *cpb; + + uc = toupper(*q); + lc = tolower(*q); + + up = strchr(s, uc); + lp = strchr(s, lc); + + if (!( (intptr_t)up|(intptr_t)lp )) + return 0; + + if (!lp || up < lp) + { + ppa = &up; + ppb = &lp; + + cpa = &uc; + cpb = &lc; + } + else + { + ppa = &lp; + ppb = &up; + + cpa = &lc; + cpb = &uc; + } + + qn = strlen(q); + + for (;;) + { + if (trycmp(ppa, cpa, q, qn) == 0) + return (*ppa); + + if (!( (intptr_t)up|(intptr_t)lp )) + break; + + if (!(*ppa) || ( (*ppb) && (*ppb) < (*ppa) )) + swapp(&ppa, &ppb, &cpa, &cpb); + } + + return 0; +} diff --git a/src/string.c b/src/string.c index 2a03e8729..c415e5245 100644 --- a/src/string.c +++ b/src/string.c @@ -2,6 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 2006 by Graue. // Copyright (C) 2006-2018 by Sonic Team Junior. +// Copyright (C) 2019 by James R. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -50,3 +51,5 @@ size_t strlcpy(char *dst, const char *src, size_t siz) } #endif + +#include "strcasestr.c" From 56ee233b080757d361c0a3896d0b8d3e805754d4 Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 21 Apr 2019 23:19:20 -0700 Subject: [PATCH 05/11] Fix compiler warnings --- src/g_game.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/g_game.c b/src/g_game.c index bc67ed792..9ebbc630c 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4069,7 +4069,7 @@ static void measurekeywords(mapsearchfreq_t *fr, PU_STATIC, NULL); } -void writesimplefreq(mapsearchfreq_t *fr, INT32 *frc, +static void writesimplefreq(mapsearchfreq_t *fr, INT32 *frc, INT32 mapnum, UINT8 pos, UINT8 siz) { fr[(*frc)].mapnum = mapnum; From f0e19d1597da558855377af818c5b66f7d7a1e32 Mon Sep 17 00:00:00 2001 From: James R Date: Thu, 10 Oct 2019 11:07:34 -0700 Subject: [PATCH 06/11] Fix minor unnecessary call --- src/d_netcmd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 246b509c2..4625b0dc5 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -1872,12 +1872,12 @@ static void Command_Map_f(void) G_SetGameModified(false); } - arg_gametype = COM_Argv(parm_gametype + 1); - // new gametype value // use current one by default if (parm_gametype) { + arg_gametype = COM_Argv(parm_gametype + 1); + newgametype = G_GetGametypeByName(arg_gametype); if (newgametype == -1) // reached end of the list with no match From a00dfcf4205f88cd6149daf73fec4831a7d7c2e5 Mon Sep 17 00:00:00 2001 From: James R Date: Fri, 25 Oct 2019 21:18:36 -0700 Subject: [PATCH 07/11] Oh right, the keywords... --- src/dehacked.c | 5 ----- src/doomstat.h | 1 - src/g_game.c | 3 --- src/lua_maplib.c | 2 -- src/p_setup.c | 1 - 5 files changed, 12 deletions(-) diff --git a/src/dehacked.c b/src/dehacked.c index 35274660d..34ee1f170 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -1205,11 +1205,6 @@ static void readlevelheader(MYFILE *f, INT32 num) mapheaderinfo[num-1]->typeoflevel = tol; } } - else if (fastcmp(word, "KEYWORD")) - { - deh_strlcpy(mapheaderinfo[num-1]->keyword, word2, - sizeof(mapheaderinfo[num-1]->keyword), va("Level header %d: keyword", num)); - } else if (fastcmp(word, "MUSIC")) { if (fastcmp(word2, "NONE")) diff --git a/src/doomstat.h b/src/doomstat.h index e01cf3227..7d06f03e2 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -286,7 +286,6 @@ typedef struct UINT8 actnum; ///< Act number or 0 for none. UINT16 typeoflevel; ///< Combination of typeoflevel flags. INT16 nextlevel; ///< Map number of next level, or 1100-1102 to end. - char keyword[33]; ///< Keywords separated by space to search for. 32 characters. char musname[7]; ///< Music track to play. "" for no music. UINT16 mustrack; ///< Subsong to play. Only really relevant for music modules and specific formats supported by GME. 0 to ignore. UINT32 muspos; ///< Music position to jump to. diff --git a/src/g_game.c b/src/g_game.c index 9ebbc630c..7a50c9065 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4164,9 +4164,6 @@ INT32 G_FindMap(const char *mapname, char **foundmapnamep, measurekeywords(&freq[freqc], &freq[freqc].matchd, &freq[freqc].matchc, realmapname, mapname, wanttable); - measurekeywords(&freq[freqc], - &freq[freqc].keywhd, &freq[freqc].keywhc, - mapheaderinfo[i]->keyword, mapname, wanttable); if (freq[freqc].total) freqc++; } diff --git a/src/lua_maplib.c b/src/lua_maplib.c index 53ff29962..1da232efa 100644 --- a/src/lua_maplib.c +++ b/src/lua_maplib.c @@ -2014,8 +2014,6 @@ static int mapheaderinfo_get(lua_State *L) lua_pushinteger(L, header->typeoflevel); else if (fastcmp(field,"nextlevel")) lua_pushinteger(L, header->nextlevel); - else if (fastcmp(field,"keyword")) - lua_pushstring(L, header->keyword); else if (fastcmp(field,"musname")) lua_pushstring(L, header->musname); else if (fastcmp(field,"mustrack")) diff --git a/src/p_setup.c b/src/p_setup.c index d98269065..cef176636 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -211,7 +211,6 @@ static void P_ClearSingleMapHeaderInfo(INT16 i) mapheaderinfo[num]->typeoflevel = 0; mapheaderinfo[num]->nextlevel = (INT16)(i + 1); mapheaderinfo[num]->startrings = 0; - mapheaderinfo[num]->keyword[0] = '\0'; snprintf(mapheaderinfo[num]->musname, 7, "%sM", G_BuildMapName(i)); mapheaderinfo[num]->musname[6] = 0; mapheaderinfo[num]->mustrack = 0; From fb9421893e555629f0fe42705f034e0b639c6905 Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 27 Oct 2019 14:29:13 -0700 Subject: [PATCH 08/11] :oh: --- src/d_netcmd.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 4625b0dc5..f33edaf47 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -1756,7 +1756,6 @@ static void Command_Map_f(void) INT32 newgametype = gametype; - INT32 i; INT32 d; char *p; From 5fd6561d464d53521343dd4abae6b08e42719885 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 12 Nov 2019 16:08:41 -0800 Subject: [PATCH 09/11] So you don't like macros? --- src/d_netcmd.c | 125 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 88 insertions(+), 37 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index f33edaf47..0e7c24eb6 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -1719,16 +1719,49 @@ 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 ) ) +enum +{ + MAP_COMMAND_FORCE_OPTION, + MAP_COMMAND_GAMETYPE_OPTION, + MAP_COMMAND_NORESETPLAYERS_OPTION, + + NUM_MAP_COMMAND_OPTIONS +}; + +static size_t CheckOptions( + int num_options, + size_t *user_options, + const char ***option_names, + int *option_num_arguments +) +{ + int arguments_used; + + int i; + const char **pp; + const char *name; + size_t n; + + arguments_used = 0; + + for (i = 0; i < num_options; ++i) + { + pp = option_names[i]; + name = *pp; + do + { + if (( n = COM_CheckParm(name) )) + { + user_options[i] = n; + arguments_used += 1 + option_num_arguments[i]; + } + } + while (( name = *++pp )) ; + } + + return arguments_used; +} + // // Warp to map code. // Called either from map console command, or idclev cheat. @@ -1737,12 +1770,42 @@ Returned is whether the parameter was found. // static void Command_Map_f(void) { - size_t acceptableargc; - size_t parm_force; - size_t parm_gametype; + const char *force_option_names[] = + { + "-force", + "-f", + NULL + }; + const char *gametype_option_names[] = + { + "-gametype", + "-g", + "-gt", + NULL + }; + const char *noresetplayers_option_names[] = + { + "-noresetplayers", + NULL + }; + const char **option_names[] = + { + force_option_names, + gametype_option_names, + noresetplayers_option_names, + }; + int option_num_arguments[] = + { + 0,/* -force */ + 1,/* -gametype */ + 0,/* -noresetplayers */ + }; + + size_t acceptableargc;/* (this includes the command name itself!) */ + + size_t user_options [NUM_MAP_COMMAND_OPTIONS] = {0}; + const char *arg_gametype; - /* debug? */ - size_t parm_noresetplayers; boolean newresetplayers; boolean mustmodifygame; @@ -1765,29 +1828,17 @@ static void Command_Map_f(void) return; } - acceptableargc = 2;/* map name */ + /* map name + options */ + acceptableargc = 2 + CheckOptions(NUM_MAP_COMMAND_OPTIONS, + user_options, option_names, option_num_arguments); - (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; + newresetplayers = !user_options[MAP_COMMAND_NORESETPLAYERS_OPTION]; mustmodifygame = !( netgame || multiplayer ) && (!modifiedgame || savemoddata ); - if (mustmodifygame && !parm_force) + if (mustmodifygame && !user_options[MAP_COMMAND_FORCE_OPTION]) { /* May want to be more descriptive? */ CONS_Printf(M_GetText("Sorry, level change disabled in single player.\n")); @@ -1800,7 +1851,7 @@ static void Command_Map_f(void) return; } - if (parm_gametype && !multiplayer) + if (user_options[MAP_COMMAND_GAMETYPE_OPTION] && !multiplayer) { CONS_Printf(M_GetText("You can't switch gametypes in single player!\n")); return; @@ -1866,16 +1917,16 @@ static void Command_Map_f(void) realmapname = G_BuildMapTitle(newmapnum); } - if (mustmodifygame && parm_force) + if (mustmodifygame && user_options[MAP_COMMAND_FORCE_OPTION]) { G_SetGameModified(false); } // new gametype value // use current one by default - if (parm_gametype) + if (user_options[MAP_COMMAND_GAMETYPE_OPTION]) { - arg_gametype = COM_Argv(parm_gametype + 1); + arg_gametype = COM_Argv(user_options[MAP_COMMAND_GAMETYPE_OPTION] + 1); newgametype = G_GetGametypeByName(arg_gametype); @@ -1889,7 +1940,7 @@ static void Command_Map_f(void) } // don't use a gametype the map doesn't support - if (cv_debug || parm_force || cv_skipmapcheck.value) + if (cv_debug || user_options[MAP_COMMAND_FORCE_OPTION] || 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 From 46fbed8b716f96a7e87ac4c68ca3ee98c8c6a25c Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 12 Nov 2019 17:42:54 -0800 Subject: [PATCH 10/11] Get map name from multiple arguments (This means you don't need quoting.) --- src/d_netcmd.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 0e7c24eb6..c27e920d2 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -1730,12 +1730,14 @@ enum static size_t CheckOptions( int num_options, + size_t *first_argumentp, size_t *user_options, const char ***option_names, int *option_num_arguments ) { - int arguments_used; + int arguments_used; + size_t first_argument; int i; const char **pp; @@ -1743,6 +1745,7 @@ static size_t CheckOptions( size_t n; arguments_used = 0; + first_argument = COM_Argc(); for (i = 0; i < num_options; ++i) { @@ -1754,14 +1757,53 @@ static size_t CheckOptions( { user_options[i] = n; arguments_used += 1 + option_num_arguments[i]; + if (n < first_argument) + first_argument = n; } } while (( name = *++pp )) ; } + (*first_argumentp) = first_argument; + return arguments_used; } +static char * +ConcatCommandArgv (int start, int end) +{ + char *final; + + size_t size; + + int i; + char *p; + + size = 0; + + for (i = start; i < end; ++i) + { + /* + one space after each argument, but terminating + character on final argument + */ + size += strlen(COM_Argv(i)) + 1; + } + + final = ZZ_Alloc(size); + p = final; + + --end;/* handle the final argument separately */ + for (i = start; i < end; ++i) + { + p += sprintf(p, "%s ", COM_Argv(i)); + } + /* at this point "end" is actually the last argument's position */ + strcpy(p, COM_Argv(end)); + + return final; +} + // // Warp to map code. // Called either from map console command, or idclev cheat. @@ -1802,6 +1844,7 @@ static void Command_Map_f(void) }; size_t acceptableargc;/* (this includes the command name itself!) */ + size_t first_argument; size_t user_options [NUM_MAP_COMMAND_OPTIONS] = {0}; @@ -1813,7 +1856,7 @@ static void Command_Map_f(void) INT32 newmapnum; - const char *mapname; + char * mapname; size_t mapnamelen; char *realmapname = NULL; @@ -1830,6 +1873,7 @@ static void Command_Map_f(void) /* map name + options */ acceptableargc = 2 + CheckOptions(NUM_MAP_COMMAND_OPTIONS, + &first_argument, user_options, option_names, option_num_arguments); newresetplayers = !user_options[MAP_COMMAND_NORESETPLAYERS_OPTION]; @@ -1857,7 +1901,7 @@ static void Command_Map_f(void) return; } - if (COM_Argc() != acceptableargc) + if (COM_Argc() < acceptableargc) { /* I'm going over the fucking lines and I DON'T CAREEEEE */ CONS_Printf("map [-gametype ] [-force]:\n"); @@ -1869,7 +1913,7 @@ static void Command_Map_f(void) return; } - mapname = COM_Argv(1); + mapname = ConcatCommandArgv(1, first_argument); mapnamelen = strlen(mapname); if (mapnamelen == 2)/* maybe two digit code */ @@ -1882,6 +1926,7 @@ static void Command_Map_f(void) if (( newmapnum = M_MapNumber(mapname[3], mapname[4]) ) == 0) { CONS_Alert(CONS_ERROR, M_GetText("Invalid map code '%s'.\n"), mapname); + Z_Free(mapname); return; } usemapcode = true; @@ -1896,6 +1941,7 @@ static void Command_Map_f(void) if (newmapnum < 1 || newmapnum > NUMMAPS) { CONS_Alert(CONS_ERROR, M_GetText("Invalid map number %d.\n"), newmapnum); + Z_Free(mapname); return; } usemapcode = true; @@ -1909,6 +1955,7 @@ static void Command_Map_f(void) if (newmapnum == 0 || !mapheaderinfo[newmapnum-1]) { CONS_Alert(CONS_ERROR, M_GetText("Could not find any map described as '%s'.\n"), mapname); + Z_Free(mapname); return; } @@ -1953,6 +2000,7 @@ static void Command_Map_f(void) 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); + Z_Free(mapname); return; } else @@ -1972,6 +2020,7 @@ static void Command_Map_f(void) { CONS_Alert(CONS_NOTICE, M_GetText("You need to unlock this level before you can warp to it!\n")); Z_Free(realmapname); + Z_Free(mapname); return; } From c9aad2d1862e92fc78d8dfbd5d1f2c5969d90d84 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 12 Nov 2019 18:29:42 -0800 Subject: [PATCH 11/11] Warn if the first argument is an option --- src/d_netcmd.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index c27e920d2..485b53719 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -1901,7 +1901,8 @@ static void Command_Map_f(void) return; } - if (COM_Argc() < acceptableargc) + /* If the first argument is an option, you fucked up. */ + if (COM_Argc() < acceptableargc || first_argument == 1) { /* I'm going over the fucking lines and I DON'T CAREEEEE */ CONS_Printf("map [-gametype ] [-force]:\n");