From 0a9ba013fece245d0ff660e4b91f40da4fea5ee4 Mon Sep 17 00:00:00 2001 From: James R Date: Fri, 22 Feb 2019 20:18:33 -0800 Subject: [PATCH 01/28] 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 | 266 +++++++++++++++++++++++++++++++++++++++---------- src/doomdef.h | 1 + src/doomtype.h | 3 + src/m_misc.c | 67 +++++++++++++ 4 files changed, 285 insertions(+), 52 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 438cdcd5..5ed78165 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2180,25 +2180,54 @@ void D_PickVote(void) SendNetXCmd(XD_PICKVOTE, &buf, 2); } +/* +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 j, 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)) { @@ -2206,91 +2235,220 @@ 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 || majormods ); + + 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) && !majormods) - { - if (COM_CheckParm("-force")) - { - G_SetGameModified(false, true); - } - 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; } - // new gametype value - // use current one by default - i = COM_CheckParm("-gametype"); - if (i) + if (COM_Argc() != acceptableargc) { - if (!multiplayer) + /* 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_Printf(M_GetText("You can't switch gametypes in single player!\n")); + CONS_Alert(CONS_ERROR, M_GetText("Invalid map code '%s'.\n"), mapname); return; } + usemapcode = true; + } - for (j = 0; gametype_cons_t[j].strvalue; j++) - if (!strcasecmp(gametype_cons_t[j].strvalue, COM_Argv(i+1))) + 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, true); + } + + arg_gametype = COM_Argv(parm_gametype + 1); + + // new gametype value + // use current one by default + if (parm_gametype) + { + for (i = 0; gametype_cons_t[i].strvalue; i++) + if (!strcasecmp(gametype_cons_t[i].strvalue, arg_gametype)) { // Don't do any variable setting here. Wait until you get your // map packet first to avoid sending the same info twice! - newgametype = gametype_cons_t[j].value; + newgametype = gametype_cons_t[i].value; break; } - if (!gametype_cons_t[j].strvalue) // reached end of the list with no match + if (!gametype_cons_t[i].strvalue) // reached end of the list with no match { + d = atoi(arg_gametype); // assume they gave us a gametype number, which is okay too - for (j = 0; gametype_cons_t[j].strvalue != NULL; j++) + for (i = 0; gametype_cons_t[i].strvalue != NULL; i++) { - if (atoi(COM_Argv(i+1)) == gametype_cons_t[j].value) + if (d == gametype_cons_t[i].value) { - newgametype = gametype_cons_t[j].value; + newgametype = gametype_cons_t[i].value; break; } } } } - if (!(i = COM_CheckParm("-force")) && newgametype == gametype) // SRB2Kart + if (!parm_force && newgametype == gametype) // SRB2Kart newresetplayers = false; // if not forcing and gametypes is the same // don't use a gametype the map doesn't support - if (cv_debug || i || cv_skipmapcheck.value) + if (cv_debug || parm_force || cv_skipmapcheck.value) ; // 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))) + if (!(mapheaderinfo[newmapnum-1]->typeoflevel & G_TOLFlag(newgametype))) { - CONS_Alert(CONS_WARNING, M_GetText("%s doesn't support %s mode!\n(Use -force to override)\n"), mapname, + CONS_Alert(CONS_WARNING, M_GetText("Course %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; } } @@ -2302,12 +2460,16 @@ 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; } fromlevelselect = false; D_MapChange(newmapnum, newgametype, (boolean)cv_kartencore.value, newresetplayers, 0, false, false); + + Z_Free(realmapname); } +#undef CHECKPARM /** Receives a map command and changes the map. * diff --git a/src/doomdef.h b/src/doomdef.h index ab863c6f..c1af6e59 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -460,6 +460,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 5d643484..89032ae1 100644 --- a/src/doomtype.h +++ b/src/doomtype.h @@ -139,6 +139,9 @@ typedef long ssize_t; #define strlwr _strlwr #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 c95aa392..d28577c0 100644 --- a/src/m_misc.c +++ b/src/m_misc.c @@ -1607,6 +1607,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 3cdfb6327a713ac4d58576dba6a554481d8e34ca Mon Sep 17 00:00:00 2001 From: James R Date: Fri, 22 Feb 2019 22:25:05 -0800 Subject: [PATCH 02/28] 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 | 2 ++ 5 files changed, 34 insertions(+), 15 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 5ed78165..d81e9d6f 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2180,6 +2180,25 @@ void D_PickVote(void) SendNetXCmd(XD_PICKVOTE, &buf, 2); } +/* +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 @@ -2218,8 +2237,6 @@ static void Command_Map_f(void) char *apromapname = NULL; /* Keyword matching */ - char *query; - char *key; UINT8 *freq; UINT8 freqc; @@ -2319,13 +2336,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) @@ -2345,16 +2362,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); } } @@ -2385,7 +2395,6 @@ static void Command_Map_f(void) } Z_Free(freq); - Z_Free(query); } } diff --git a/src/dehacked.c b/src/dehacked.c index 11aed24d..7dfedde5 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -1191,6 +1191,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 9ae2726d..43ebaf08 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -226,6 +226,7 @@ typedef struct char actnum[3]; ///< SRB2Kart: Now a 2 character long string. 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. char forcecharacter[17]; ///< (SKINNAMESIZE+1) Skin to switch to or "" to disable. diff --git a/src/lua_maplib.c b/src/lua_maplib.c index 0bb9a99d..d7a4fdaf 100644 --- a/src/lua_maplib.c +++ b/src/lua_maplib.c @@ -1473,6 +1473,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 58e13c2e..0187788d 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -188,6 +188,8 @@ static void P_ClearSingleMapHeaderInfo(INT16 i) mapheaderinfo[num]->typeoflevel = 0; DEH_WriteUndoline("NEXTLEVEL", va("%d", mapheaderinfo[num]->nextlevel), UNDO_NONE); mapheaderinfo[num]->nextlevel = (INT16)(i + 1); + DEH_WriteUndoline("KEYWORD", mapheaderinfo[num]->keyword, UNDO_NONE); + mapheaderinfo[num]->keyword[0] = '\0'; DEH_WriteUndoline("MUSIC", mapheaderinfo[num]->musname, UNDO_NONE); snprintf(mapheaderinfo[num]->musname, 7, "%sM", G_BuildMapName(i)); mapheaderinfo[num]->musname[6] = 0; From 7e389f40626445e86994c114302d944a663e3348 Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 23 Feb 2019 21:25:53 -0800 Subject: [PATCH 03/28] 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 d81e9d6f..7f22b2fe 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2180,25 +2180,6 @@ void D_PickVote(void) SendNetXCmd(XD_PICKVOTE, &buf, 2); } -/* -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 @@ -2229,16 +2210,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; @@ -2336,65 +2311,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 f0d221ff..dbd1f87e 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4486,6 +4486,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 fc7a4a4f..eace3fcd 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -115,6 +115,27 @@ void G_InitNew(UINT8 pencoremode, const char *mapname, boolean resetplayer, boolean skipprecutscene); 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 b4e900be1f91c1fff2738a2fae1ed9ac23dd7acc Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 21 Apr 2019 23:18:40 -0700 Subject: [PATCH 04/28] 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 d28577c0..c95aa392 100644 --- a/src/m_misc.c +++ b/src/m_misc.c @@ -1607,73 +1607,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 00000000..2077dc3f --- /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 2a03e872..c415e524 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 e79ec14dc3bbfa7b1d7817d472e42d07aca63126 Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 21 Apr 2019 23:19:20 -0700 Subject: [PATCH 05/28] 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 d89e557f..69781da2 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4524,7 +4524,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 e1898336977c282a5e0443c01b42ba1114ba015e Mon Sep 17 00:00:00 2001 From: James R Date: Thu, 10 Oct 2019 11:07:34 -0700 Subject: [PATCH 06/28] 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 577dd403..d8c335e8 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2347,12 +2347,12 @@ static void Command_Map_f(void) G_SetGameModified(false, true); } - 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); + for (i = 0; gametype_cons_t[i].strvalue; i++) if (!strcasecmp(gametype_cons_t[i].strvalue, arg_gametype)) { From 6dc44c50ff334631c5649ee0c735ecad4be288d6 Mon Sep 17 00:00:00 2001 From: James R Date: Thu, 10 Oct 2019 11:43:21 -0700 Subject: [PATCH 07/28] Support -encore parameter in new map command --- src/d_netcmd.c | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 09c58be0..c49eb081 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2455,6 +2455,7 @@ static void Command_Map_f(void) size_t acceptableargc; size_t parm_force; size_t parm_gametype; + size_t parm_encore; const char *arg_gametype; /* debug? */ size_t parm_noresetplayers; @@ -2469,7 +2470,8 @@ static void Command_Map_f(void) size_t mapnamelen; char *realmapname = NULL; - INT32 newgametype = gametype; + INT32 newgametype = gametype; + boolean newencoremode = cv_kartencore.value; INT32 i; INT32 d; @@ -2494,6 +2496,12 @@ static void Command_Map_f(void) CHECKPARM (gametype, "-g", 1) || CHECKPARM (gametype, "-gt", 1) ); + (void) + ( + CHECKPARM (encore, "-encore", 0) || + CHECKPARM (encore, "-en", 0) || + CHECKPARM (encore, "-e", 0) + ); (void)CHECKPARM (noresetplayers, "-noresetplayers", 0); @@ -2523,12 +2531,13 @@ static void Command_Map_f(void) if (COM_Argc() != acceptableargc) { /* I'm going over the fucking lines and I DON'T CAREEEEE */ - CONS_Printf("map [-gametype ] [-force]:\n"); + CONS_Printf("map [-gametype ] [-encore] [-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")); + "* \"-gametype\" may be shortened to \"-g\" or \"-gt\".\n" + "* \"-encore\" may be shortened to \"-e\" or \"-en\".\n")); return; } @@ -2644,8 +2653,18 @@ static void Command_Map_f(void) return; } + if (parm_encore) + { + newencoremode = ! newencoremode; + if (! M_SecretUnlocked(SECRET_ENCORE) && newencoremode) + { + CONS_Alert(CONS_NOTICE, M_GetText("You haven't unlocked Encore Mode yet!\n")); + return; + } + } + fromlevelselect = false; - D_MapChange(newmapnum, newgametype, (boolean)cv_kartencore.value, newresetplayers, 0, false, false); + D_MapChange(newmapnum, newgametype, newencoremode, newresetplayers, 0, false, false); Z_Free(realmapname); } From c7a31ea5de32802fb6c0a1bdd50befdf203b2db8 Mon Sep 17 00:00:00 2001 From: James R Date: Fri, 25 Oct 2019 21:18:36 -0700 Subject: [PATCH 08/28] Oh right, the keywords... (cherry picked from commit a922de8242143a55eb0c19cc5a65bb9126213f29) --- src/dehacked.c | 5 ----- src/doomstat.h | 1 - src/g_game.c | 3 --- src/lua_maplib.c | 2 -- src/p_setup.c | 2 -- 5 files changed, 13 deletions(-) diff --git a/src/dehacked.c b/src/dehacked.c index afc8b798..00f4fa96 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -1052,11 +1052,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 66c6df4e..232a5af8 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -222,7 +222,6 @@ typedef struct char actnum[3]; ///< SRB2Kart: Now a 2 character long string. 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 c2a176ff..06699c6b 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4871,9 +4871,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 76a4662b..41875ea9 100644 --- a/src/lua_maplib.c +++ b/src/lua_maplib.c @@ -1449,8 +1449,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 6cb96692..5956a5f9 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -190,8 +190,6 @@ static void P_ClearSingleMapHeaderInfo(INT16 i) mapheaderinfo[num]->typeoflevel = 0; DEH_WriteUndoline("NEXTLEVEL", va("%d", mapheaderinfo[num]->nextlevel), UNDO_NONE); mapheaderinfo[num]->nextlevel = (INT16)(i + 1); - DEH_WriteUndoline("KEYWORD", mapheaderinfo[num]->keyword, UNDO_NONE); - mapheaderinfo[num]->keyword[0] = '\0'; DEH_WriteUndoline("MUSIC", mapheaderinfo[num]->musname, UNDO_NONE); snprintf(mapheaderinfo[num]->musname, 7, "%sM", G_BuildMapName(i)); mapheaderinfo[num]->musname[6] = 0; From 3506ae6d1dc178b7771b4161a7a724b2fecb9197 Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 27 Oct 2019 14:29:13 -0700 Subject: [PATCH 09/28] :oh: (cherry picked from commit fb9421893e555629f0fe42705f034e0b639c6905) --- src/d_netcmd.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index c5f94dcc..b7dc097e 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2518,7 +2518,6 @@ static void Command_Map_f(void) INT32 newgametype = gametype; boolean newencoremode = cv_kartencore.value; - INT32 i; INT32 d; char *p; From fb858dd64bafeab8773f309662afd6a4cd735a47 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 12 Nov 2019 16:08:41 -0800 Subject: [PATCH 10/28] So you don't like macros? (cherry picked from commit 5fd6561d464d53521343dd4abae6b08e42719885) --- src/d_netcmd.c | 134 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 89 insertions(+), 45 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index b7dc097e..3de734cc 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2479,16 +2479,49 @@ void D_PickVote(void) SendNetXCmd(XD_PICKVOTE, &buf, 2); } -/* -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. @@ -2497,13 +2530,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; - size_t parm_encore; + 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; @@ -2527,33 +2589,15 @@ 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 (encore, "-encore", 0) || - CHECKPARM (encore, "-en", 0) || - CHECKPARM (encore, "-e", 0) - ); - - (void)CHECKPARM (noresetplayers, "-noresetplayers", 0); - - newresetplayers = !parm_noresetplayers; + newresetplayers = !user_options[MAP_COMMAND_NORESETPLAYERS_OPTION]; mustmodifygame = !( netgame || multiplayer || majormods ); - 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")); @@ -2566,7 +2610,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; @@ -2633,16 +2677,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, true); } // 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); for (i = 0; gametype_cons_t[i].strvalue; i++) if (!strcasecmp(gametype_cons_t[i].strvalue, arg_gametype)) @@ -2672,8 +2716,8 @@ static void Command_Map_f(void) newresetplayers = false; // if not forcing and gametypes is the same // don't use a gametype the map doesn't support - if (cv_debug || parm_force || cv_skipmapcheck.value) - ; // The player wants us to trek on anyway. Do so. + 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 93c351a38e7282ccf052384da05819773cd66a14 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 12 Nov 2019 17:42:54 -0800 Subject: [PATCH 11/28] Get map name from multiple arguments (This means you don't need quoting.) (cherry picked from commit 46fbed8b716f96a7e87ac4c68ca3ee98c8c6a25c) --- 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 3de734cc..b794f606 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2490,12 +2490,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; @@ -2503,6 +2505,7 @@ static size_t CheckOptions( size_t n; arguments_used = 0; + first_argument = COM_Argc(); for (i = 0; i < num_options; ++i) { @@ -2514,14 +2517,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. @@ -2562,6 +2604,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}; @@ -2573,7 +2616,7 @@ static void Command_Map_f(void) INT32 newmapnum; - const char *mapname; + char * mapname; size_t mapnamelen; char *realmapname = NULL; @@ -2591,6 +2634,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]; @@ -2616,7 +2660,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 ] [-encore] [-force]:\n"); @@ -2629,7 +2673,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 */ @@ -2642,6 +2686,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; @@ -2656,6 +2701,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; @@ -2669,6 +2715,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; } @@ -2726,6 +2773,7 @@ static void Command_Map_f(void) CONS_Alert(CONS_WARNING, M_GetText("Course %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; } } @@ -2738,6 +2786,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 9a054ab9893fea85ceb8974a4d742cb971d0f76f Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 12 Nov 2019 18:29:42 -0800 Subject: [PATCH 12/28] Warn if the first argument is an option (cherry picked from commit c9aad2d1862e92fc78d8dfbd5d1f2c5969d90d84) --- 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 b794f606..b92bbc42 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2660,7 +2660,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 ] [-encore] [-force]:\n"); From 7c3aedc48e31f81902b9bc5ba7cafc0af1c9403e Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 13 Nov 2019 14:26:28 -0800 Subject: [PATCH 13/28] Optimizations from LJ and generic functions (cherry picked from commit 359d8a2c254222d4e44934ec71d56b9511dd9379) --- src/command.c | 34 ++++++++++++ src/command.h | 2 + src/d_netcmd.c | 143 ++++++++++++------------------------------------- 3 files changed, 69 insertions(+), 110 deletions(-) diff --git a/src/command.c b/src/command.c index 95fd5ef4..fba1d4dc 100644 --- a/src/command.c +++ b/src/command.c @@ -353,6 +353,40 @@ size_t COM_CheckParm(const char *check) return 0; } +/** \brief COM_CheckParm, but checks only the start of each argument. + * E.g. checking for "-no" would match "-noerror" too. + */ +size_t COM_CheckPartialParm(const char *check) +{ + int len; + size_t i; + + len = strlen(check); + + for (i = 1; i < com_argc; i++) + { + if (strncasecmp(check, com_argv[i], len) == 0) + return i; + } + return 0; +} + +/** Find the first argument that starts with a hyphen (-). + * \return The index of the argument, or 0 + * if there are no such arguments. + */ +size_t COM_FirstOption(void) +{ + size_t i; + + for (i = 1; i < com_argc; i++) + { + if (com_argv[i][0] == '-')/* options start with a hyphen */ + return i; + } + return 0; +} + /** Parses a string into command-line tokens. * * \param ptext A null-terminated string. Does not need to be diff --git a/src/command.h b/src/command.h index 0880065b..c1b2d591 100644 --- a/src/command.h +++ b/src/command.h @@ -29,6 +29,8 @@ size_t COM_Argc(void); const char *COM_Argv(size_t arg); // if argv > argc, returns empty string char *COM_Args(void); size_t COM_CheckParm(const char *check); // like M_CheckParm :) +size_t COM_CheckPartialParm(const char *check); +size_t COM_FirstOption(void); // match existing command or NULL const char *COM_CompleteCommand(const char *partial, INT32 skips); diff --git a/src/d_netcmd.c b/src/d_netcmd.c index b92bbc42..c46fdc47 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2479,56 +2479,6 @@ void D_PickVote(void) SendNetXCmd(XD_PICKVOTE, &buf, 2); } -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 *first_argumentp, - size_t *user_options, - const char ***option_names, - int *option_num_arguments -) -{ - int arguments_used; - size_t first_argument; - - int i; - const char **pp; - const char *name; - size_t n; - - arguments_used = 0; - first_argument = COM_Argc(); - - 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]; - if (n < first_argument) - first_argument = n; - } - } - while (( name = *++pp )) ; - } - - (*first_argumentp) = first_argument; - - return arguments_used; -} - static char * ConcatCommandArgv (int start, int end) { @@ -2572,43 +2522,10 @@ ConcatCommandArgv (int start, int end) // static void Command_Map_f(void) { - 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 first_argument; - - size_t user_options [NUM_MAP_COMMAND_OPTIONS] = {0}; - - const char *arg_gametype; + size_t first_option; + size_t option_force; + size_t option_gametype; + const char *gametypename; boolean newresetplayers; boolean mustmodifygame; @@ -2632,16 +2549,13 @@ static void Command_Map_f(void) return; } - /* 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]; + option_force = COM_CheckPartialParm("-f"); + option_gametype = COM_CheckPartialParm("-g"); + newresetplayers = ! COM_CheckParm("-noresetplayers"); mustmodifygame = !( netgame || multiplayer || majormods ); - if (mustmodifygame && !user_options[MAP_COMMAND_FORCE_OPTION]) + if (mustmodifygame && !option_force) { /* May want to be more descriptive? */ CONS_Printf(M_GetText("Sorry, level change disabled in single player.\n")); @@ -2654,27 +2568,37 @@ static void Command_Map_f(void) return; } - if (user_options[MAP_COMMAND_GAMETYPE_OPTION] && !multiplayer) + if (option_gametype) { - CONS_Printf(M_GetText("You can't switch gametypes in single player!\n")); - return; + if (!multiplayer) + { + CONS_Printf(M_GetText( + "You can't switch gametypes in single player!\n")); + return; + } + else 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 the first argument is an option, you fucked up. */ - if (COM_Argc() < acceptableargc || first_argument == 1) + 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 ] [-encore] [-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" - "* \"-encore\" may be shortened to \"-e\" or \"-en\".\n")); + "All parameters are case-insensitive and may be abbreviated.\n")); return; } - mapname = ConcatCommandArgv(1, first_argument); + mapname = ConcatCommandArgv(1, first_option); mapnamelen = strlen(mapname); if (mapnamelen == 2)/* maybe two digit code */ @@ -2725,16 +2649,16 @@ static void Command_Map_f(void) realmapname = G_BuildMapTitle(newmapnum); } - if (mustmodifygame && user_options[MAP_COMMAND_FORCE_OPTION]) + if (mustmodifygame && option_force) { G_SetGameModified(false, true); } // new gametype value // use current one by default - if (user_options[MAP_COMMAND_GAMETYPE_OPTION]) + if (option_gametype) { - arg_gametype = COM_Argv(user_options[MAP_COMMAND_GAMETYPE_OPTION] + 1); + gametypename = COM_Argv(option_gametype + 1); for (i = 0; gametype_cons_t[i].strvalue; i++) if (!strcasecmp(gametype_cons_t[i].strvalue, arg_gametype)) @@ -2747,7 +2671,7 @@ static void Command_Map_f(void) if (!gametype_cons_t[i].strvalue) // reached end of the list with no match { - d = atoi(arg_gametype); + d = atoi(gametypename); // assume they gave us a gametype number, which is okay too for (i = 0; gametype_cons_t[i].strvalue != NULL; i++) { @@ -2764,7 +2688,7 @@ static void Command_Map_f(void) newresetplayers = false; // if not forcing and gametypes is the same // don't use a gametype the map doesn't support - if (cv_debug || user_options[MAP_COMMAND_FORCE_OPTION] || cv_skipmapcheck.value) + if (cv_debug || option_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 @@ -2806,7 +2730,6 @@ static void Command_Map_f(void) Z_Free(realmapname); } -#undef CHECKPARM /** Receives a map command and changes the map. * From a5bf7719e78ee72f4b8ea4404cccb906733126af Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 13 Nov 2019 14:31:44 -0800 Subject: [PATCH 14/28] Warn if the gametype is not valid at all! (cherry picked from commit 7776c59cddaa4cc09dfdb2f15192a194fda36eb7) --- src/d_netcmd.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index c46fdc47..a758256d 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2671,15 +2671,21 @@ static void Command_Map_f(void) if (!gametype_cons_t[i].strvalue) // reached end of the list with no match { - d = atoi(gametypename); - // assume they gave us a gametype number, which is okay too - for (i = 0; gametype_cons_t[i].strvalue != NULL; i++) + /* Did they give us a gametype number? That's okay too! */ + if (isdigit(gametypename[0])) { - if (d == gametype_cons_t[i].value) - { - newgametype = gametype_cons_t[i].value; - break; - } + d = atoi(gametypename); + if (d >= 0 && d < NUMGAMETYPES) + newgametype = d; + } + else + { + CONS_Alert(CONS_ERROR, + "'%s' is not a gametype.\n", + gametypename); + Z_Free(realmapname); + Z_Free(mapname); + return; } } } From 19443fec4c89d829dd5d698e4c03c69f41aa7d43 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 19 Nov 2019 12:06:15 -0800 Subject: [PATCH 15/28] Merge descrepancies --- src/d_netcmd.c | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index a758256d..78b4c907 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2660,16 +2660,9 @@ static void Command_Map_f(void) { gametypename = COM_Argv(option_gametype + 1); - for (i = 0; gametype_cons_t[i].strvalue; i++) - if (!strcasecmp(gametype_cons_t[i].strvalue, arg_gametype)) - { - // Don't do any variable setting here. Wait until you get your - // map packet first to avoid sending the same info twice! - newgametype = gametype_cons_t[i].value; - break; - } + newgametype = G_GetGametypeByName(gametypename); - if (!gametype_cons_t[i].strvalue) // reached end of the list with no match + 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])) @@ -2690,7 +2683,7 @@ static void Command_Map_f(void) } } - if (!parm_force && newgametype == gametype) // SRB2Kart + if (!option_force && newgametype == gametype) // SRB2Kart newresetplayers = false; // if not forcing and gametypes is the same // don't use a gametype the map doesn't support From dc23895875c40035da3a417869978050a8bdbdf3 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 19 Nov 2019 12:43:25 -0800 Subject: [PATCH 16/28] Add back the encore parameter --- src/d_netcmd.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 78b4c907..493eb18e 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2525,6 +2525,7 @@ static void Command_Map_f(void) size_t first_option; size_t option_force; size_t option_gametype; + size_t option_encore; const char *gametypename; boolean newresetplayers; @@ -2551,6 +2552,7 @@ static void Command_Map_f(void) option_force = COM_CheckPartialParm("-f"); option_gametype = COM_CheckPartialParm("-g"); + option_encore = COM_CheckPartialParm("-e"); newresetplayers = ! COM_CheckParm("-noresetplayers"); mustmodifygame = !( netgame || multiplayer || majormods ); @@ -2714,7 +2716,7 @@ static void Command_Map_f(void) return; } - if (parm_encore) + if (option_encore) { newencoremode = ! newencoremode; if (! M_SecretUnlocked(SECRET_ENCORE) && newencoremode) From 05246f36a61e37584b58bd7b7bb2864563fea537 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 19 Nov 2019 11:16:17 -0800 Subject: [PATCH 17/28] Fix strcasestr SIGSEGV in the case of only upper case strchr returning NULL (cherry picked from commit c5d73e37bbc0c9fa8fd1dd874ac782b655068576) --- src/strcasestr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strcasestr.c b/src/strcasestr.c index 2077dc3f..86c7ec5b 100644 --- a/src/strcasestr.c +++ b/src/strcasestr.c @@ -75,7 +75,7 @@ strcasestr (const char *s, const char *q) if (!( (intptr_t)up|(intptr_t)lp )) return 0; - if (!lp || up < lp) + if (!lp || ( up && up < lp )) { ppa = &up; ppb = &lp; From 401f30f27d0c8732b18259b047f78f8faea400c6 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 19 Nov 2019 12:25:50 -0800 Subject: [PATCH 18/28] Check for out of range gametype on map change (cherry picked from commit 9b96964cbbfc20565a8cfdb741f4927f36df7899) --- src/d_netcmd.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 493eb18e..c21e5c28 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2778,7 +2778,9 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum) lastgametype = gametype; gametype = READUINT8(*cp); - if (gametype != lastgametype) + if (gametype < 0 || gametype >= NUMGAMETYPES) + gametype = lastgametype; + else if (gametype != lastgametype) D_GameTypeChanged(lastgametype); // emulate consvar_t behavior for gametype if (!G_RaceGametype()) From e5fa66a2a9cb8929dd056f8c61ebc9e070efaf15 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 19 Nov 2019 12:32:02 -0800 Subject: [PATCH 19/28] Warn if gametype number is out of range to map command (cherry picked from commit 6ffb18c4b157f9efecec6a104ef930dd71f8b986) --- src/d_netcmd.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index c21e5c28..1906f964 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2672,6 +2672,17 @@ static void Command_Map_f(void) 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 { From bd253c0e7ea946da4cb1beb6c44360330edbcdc6 Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 7 Dec 2019 17:03:46 -0800 Subject: [PATCH 20/28] Use a third variable of XOR nonsense (cherry picked from commit d5816d44f337c6eb82d937959f971fe45a5eec2a) --- src/strcasestr.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/strcasestr.c b/src/strcasestr.c index 86c7ec5b..4ff778bf 100644 --- a/src/strcasestr.c +++ b/src/strcasestr.c @@ -23,13 +23,6 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 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) @@ -45,8 +38,16 @@ trycmp (char **pp, char *cp, 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 **pp; + char *p; + + pp = *ppap; + *ppap = *ppbp; + *ppbp = pp; + + p = *cpap; + *cpap = *cpbp; + *cpbp = p; } char * From 1adae3deea3e4868dc2040d061ca849a326603b4 Mon Sep 17 00:00:00 2001 From: James R Date: Mon, 9 Dec 2019 20:04:09 -0800 Subject: [PATCH 21/28] Maybe I'm mistaken but this probably isn't needed (cherry picked from commit 6464df9876e472d1210aabce4237d02af38377e1) --- src/d_netcmd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 1906f964..8953e209 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2639,7 +2639,7 @@ static void Command_Map_f(void) } } - if (newmapnum == 0 || !mapheaderinfo[newmapnum-1]) + if (newmapnum == 0) { CONS_Alert(CONS_ERROR, M_GetText("Could not find any map described as '%s'.\n"), mapname); Z_Free(mapname); From 52a863d1fde8d4d5ca7a3f385dd210e0a6398bfe Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 29 Dec 2019 02:14:02 -0800 Subject: [PATCH 22/28] Split map code checking from Command_Map_f (cherry picked from commit 19aafbfd0b9bde40809ca91195d4636899504708) --- src/d_netcmd.c | 45 +---------------------------------------- src/g_game.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/g_game.h | 3 +++ 3 files changed, 58 insertions(+), 44 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 8953e209..85938522 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2530,19 +2530,16 @@ static void Command_Map_f(void) boolean newresetplayers; boolean mustmodifygame; - boolean usemapcode = false; INT32 newmapnum; char * mapname; - size_t mapnamelen; char *realmapname = NULL; INT32 newgametype = gametype; boolean newencoremode = cv_kartencore.value; INT32 d; - char *p; if (client && !IsPlayerAdmin(consoleplayer)) { @@ -2601,43 +2598,8 @@ static void Command_Map_f(void) } mapname = ConcatCommandArgv(1, first_option); - 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); - Z_Free(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); - Z_Free(mapname); - return; - } - usemapcode = true; - } - else - { - newmapnum = G_FindMap(mapname, &realmapname, NULL, NULL); - } - } + newmapnum = G_FindMapByNameOrCode(mapname, &realmapname); if (newmapnum == 0) { @@ -2646,11 +2608,6 @@ static void Command_Map_f(void) return; } - if (usemapcode) - { - realmapname = G_BuildMapTitle(newmapnum); - } - if (mustmodifygame && option_force) { G_SetGameModified(false, true); diff --git a/src/g_game.c b/src/g_game.c index 06699c6b..da5dac94 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4928,6 +4928,60 @@ void G_FreeMapSearch(mapsearchfreq_t *freq, INT32 freqc) Z_Free(freq); } +INT32 G_FindMapByNameOrCode(const char *mapname, char **realmapnamep) +{ + boolean usemapcode = false; + + INT32 newmapnum; + + size_t mapnamelen; + + char *p; + + 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 0; + } + 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 0; + } + usemapcode = true; + } + else + { + newmapnum = G_FindMap(mapname, realmapnamep, NULL, NULL); + } + } + + if (usemapcode) + { + (*realmapnamep) = G_BuildMapTitle(newmapnum); + } + + return newmapnum; +} + // // DEMO RECORDING // diff --git a/src/g_game.h b/src/g_game.h index 1febcac7..2b2f7e2c 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -193,6 +193,9 @@ INT32 G_FindMap(const char *query, char **foundmapnamep, mapsearchfreq_t **freqp, INT32 *freqc); void G_FreeMapSearch(mapsearchfreq_t *freq, INT32 freqc); +/* Match map name by search + 2 digit map code or map number. */ +INT32 G_FindMapByNameOrCode(const char *query, char **foundmapnamep); + // XMOD spawning mapthing_t *G_FindCTFStart(INT32 playernum); mapthing_t *G_FindMatchStart(INT32 playernum); From 9a2ff50f5a57bedac7104c36517f2d50e366bcda Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 29 Dec 2019 02:15:48 -0800 Subject: [PATCH 23/28] Who cares? (cherry picked from commit 2ffff56b391235257c65eb23e539926c4da6ac62) --- src/g_game.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index da5dac94..46a0cc5a 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4947,12 +4947,8 @@ INT32 G_FindMapByNameOrCode(const char *mapname, char **realmapnamep) } 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 0; - } - usemapcode = true; + if (( newmapnum = M_MapNumber(mapname[3], mapname[4]) )) + usemapcode = true; } if (!usemapcode) From 9885ab164a4a95c120e392a6502088cd5bd84111 Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 29 Dec 2019 02:16:37 -0800 Subject: [PATCH 24/28] I'm still an idiot (cherry picked from commit 0a014755b98fb909aed4a12feba00744bc2af225) --- src/g_game.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/g_game.c b/src/g_game.c index 46a0cc5a..ecc57fa1 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4972,7 +4972,8 @@ INT32 G_FindMapByNameOrCode(const char *mapname, char **realmapnamep) if (usemapcode) { - (*realmapnamep) = G_BuildMapTitle(newmapnum); + if (realmapnamep) + (*realmapnamep) = G_BuildMapTitle(newmapnum); } return newmapnum; From 9e33e16d269278cf01e086bbde9de37b16fb4a84 Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 29 Dec 2019 02:31:14 -0800 Subject: [PATCH 25/28] If we move the -warp code down, map searching can be used (cherry picked from commit 404f3c13e4ed1d9e1a375bba1c2ae59b4f694e0e) --- src/d_main.c | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/src/d_main.c b/src/d_main.c index 2c02565d..340165cd 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -1160,26 +1160,6 @@ void D_SRB2Main(void) if (M_CheckParm("-server") || dedicated) netgame = server = true; - if (M_CheckParm("-warp") && M_IsNextParm()) - { - const char *word = M_GetNextParm(); - char ch; // use this with sscanf to catch non-digits with - if (fastncmp(word, "MAP", 3)) // MAPxx name - pstartmap = M_MapNumber(word[3], word[4]); - else if (sscanf(word, "%d%c", &pstartmap, &ch) != 1) // a plain number - I_Error("Cannot warp to map %s (invalid map name)\n", word); - // Don't check if lump exists just yet because the wads haven't been loaded! - // Just do a basic range check here. - if (pstartmap < 1 || pstartmap > NUMMAPS) - I_Error("Cannot warp to map %d (out of range)\n", pstartmap); - else - { - if (!M_CheckParm("-server")) - G_SetGameModified(true, true); - autostart = true; - } - } - CONS_Printf("Z_Init(): Init zone memory allocation daemon. \n"); Z_Init(); @@ -1302,6 +1282,20 @@ void D_SRB2Main(void) } } + if (M_CheckParm("-warp") && M_IsNextParm()) + { + const char *word = M_GetNextParm(); + pstartmap = G_FindMapByNameOrCode(word, 0); + if (! pstartmap) + I_Error("Cannot find a map remotely named '%s'\n", word); + else + { + if (!M_CheckParm("-server")) + G_SetGameModified(true, true); + autostart = true; + } + } + cht_Init(); //---------------------------------------------------- READY SCREEN From 93a76c10de5d24c8717ac98d1dd39a957fa295f2 Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 29 Dec 2019 02:44:27 -0800 Subject: [PATCH 26/28] Don't let us warp to a map that doesn't exist (really!) Okay so 6464df9876e472d1210aabce4237d02af38377e1, I WAS mistaken! Except that's not how you check for a map's existence, at least not how the old map command did it. (cherry picked from commit 566b4a1626399b1c1621bf8b4ab1f8426c789c36) --- src/g_game.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/g_game.c b/src/g_game.c index ecc57fa1..0c850e6a 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4972,6 +4972,10 @@ INT32 G_FindMapByNameOrCode(const char *mapname, char **realmapnamep) if (usemapcode) { + /* we can't check mapheaderinfo for this hahahaha */ + if (W_CheckNumForName(G_BuildMapName(newmapnum)) == LUMPERROR) + return 0; + if (realmapnamep) (*realmapnamep) = G_BuildMapTitle(newmapnum); } From 31237935da80205fac62915240b41cd78119358d Mon Sep 17 00:00:00 2001 From: Monster Iestyn Date: Thu, 20 Feb 2020 20:31:11 +0000 Subject: [PATCH 27/28] move -warp code to a later part of D_SRB2Main so G_LoadGameData isn't upset by G_SetGameModified (cherry picked from commit 903cc311a711d80436215e7f7c897a447daf2539) --- src/d_main.c | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/d_main.c b/src/d_main.c index 340165cd..6d3cc72b 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -1282,20 +1282,6 @@ void D_SRB2Main(void) } } - if (M_CheckParm("-warp") && M_IsNextParm()) - { - const char *word = M_GetNextParm(); - pstartmap = G_FindMapByNameOrCode(word, 0); - if (! pstartmap) - I_Error("Cannot find a map remotely named '%s'\n", word); - else - { - if (!M_CheckParm("-server")) - G_SetGameModified(true, true); - autostart = true; - } - } - cht_Init(); //---------------------------------------------------- READY SCREEN @@ -1353,6 +1339,22 @@ void D_SRB2Main(void) //------------------------------------------------ COMMAND LINE PARAMS + // this must be done after loading gamedata, to avoid setting off the corrupted gamedata flag in G_LoadGameData + // -- Monster Iestyn 20/02/20 + if (M_CheckParm("-warp") && M_IsNextParm()) + { + const char *word = M_GetNextParm(); + pstartmap = G_FindMapByNameOrCode(word, 0); + if (! pstartmap) + I_Error("Cannot find a map remotely named '%s'\n", word); + else + { + if (!M_CheckParm("-server")) + G_SetGameModified(true, true); + autostart = true; + } + } + // Initialize CD-Audio if (M_CheckParm("-usecd") && !dedicated) I_InitCD(); From 8a5fd38bca118670f438a4ead45754572cf39125 Mon Sep 17 00:00:00 2001 From: Monster Iestyn Date: Thu, 20 Feb 2020 20:38:01 +0000 Subject: [PATCH 28/28] clarify the situation a bit more in the comments (cherry picked from commit 5c33ff04581abe44e1c8ad03e680ff918b9dc0d2) --- src/d_main.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/d_main.c b/src/d_main.c index 6d3cc72b..2851a968 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -1339,7 +1339,8 @@ void D_SRB2Main(void) //------------------------------------------------ COMMAND LINE PARAMS - // this must be done after loading gamedata, to avoid setting off the corrupted gamedata flag in G_LoadGameData + // this must be done after loading gamedata, + // to avoid setting off the corrupted gamedata code in G_LoadGameData if a SOC with custom gamedata is added // -- Monster Iestyn 20/02/20 if (M_CheckParm("-warp") && M_IsNextParm()) {