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