diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 438cdcd5454a93fefce6822bf8a344cdb12c88c2..5ed78165373b5780d9ee3d56ae6cc75c1bcfe00d 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -2180,117 +2180,275 @@ 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 <mapname> 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)
+	INT32 i;
+	INT32 d;
+	char *p;
+
+	if (client && !IsPlayerAdmin(consoleplayer))
 	{
-		CONS_Printf(M_GetText("map <mapname> [-gametype <type> [-force]: warp to map\n"));
+		CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
 		return;
 	}
 
-	if (client && !IsPlayerAdmin(consoleplayer))
+	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_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
+		/* May want to be more descriptive? */
+		CONS_Printf(M_GetText("Sorry, level change disabled in single player.\n"));
 		return;
 	}
 
-	// internal wad lump always: map command doesn't support external files as in doom legacy
-	if (W_CheckNumForName(COM_Argv(1)) == LUMPERROR)
+	if (!newresetplayers && !cv_debug)
+	{
+		CONS_Printf(M_GetText("DEVMODE must be enabled.\n"));
+		return;
+	}
+
+	if (parm_gametype && !multiplayer)
+	{
+		CONS_Printf(M_GetText("You can't switch gametypes in single player!\n"));
+		return;
+	}
+
+	if (COM_Argc() != acceptableargc)
 	{
-		CONS_Alert(CONS_ERROR, M_GetText("Internal game level '%s' not found\n"), COM_Argv(1));
+		/* I'm going over the fucking lines and I DON'T CAREEEEE */
+		CONS_Printf("map <name / [MAP]code / number> [-gametype <type>] [-force]:\n");
+		CONS_Printf(M_GetText(
+					"Warp to a map, by its name, two character code, with optional \"MAP\" prefix, or by its number (though why would you).\n"
+					"All parameters are case-insensitive.\n"
+					"* \"-force\" may be shortened to \"-f\".\n"
+					"* \"-gametype\" may be shortened to \"-g\" or \"-gt\".\n"));
 		return;
 	}
 
-	if (!(netgame || multiplayer) && !majormods)
+	mapname = COM_Argv(1);
+	mapnamelen = strlen(mapname);
+
+	if (mapnamelen == 2)/* maybe two digit code */
 	{
-		if (COM_CheckParm("-force"))
+		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)
 		{
-			G_SetGameModified(false, true);
+			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
 		{
-			CONS_Printf(M_GetText("Sorry, level change disabled in single player.\n"));
-			return;
+			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);
 		}
 	}
 
-	newresetplayers = !COM_CheckParm("-noresetplayers");
-
-	if (!newresetplayers && !cv_debug)
+	if (newmapnum == 0 || !mapheaderinfo[newmapnum-1])
 	{
-		CONS_Printf(M_GetText("DEVMODE must be enabled.\n"));
+		CONS_Alert(CONS_ERROR, M_GetText("Could not find any map described as '%s'.\n"), mapname);
 		return;
 	}
 
-	mapname = COM_Argv(1);
-	if (strlen(mapname) != 5
-	|| (newmapnum = M_MapNumber(mapname[3], mapname[4])) == 0)
+	if (usemapcode)
 	{
-		CONS_Alert(CONS_ERROR, M_GetText("Invalid level name %s\n"), mapname);
-		return;
+		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
-	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;
-		}
-
-		for (j = 0; gametype_cons_t[j].strvalue; j++)
-			if (!strcasecmp(gametype_cons_t[j].strvalue, COM_Argv(i+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[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 ab863c6f64b7b9597864c57fe88603ef28a3aa7a..c1af6e5908da3fc2821841fbaeae1e6b9be6c8f0 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 5d6434845a7013422f9a7eebf661f7fce641c57b..89032ae1523baffb2c5f8eb891197ca3f79fbf84 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 c95aa392ccf46a598466b57ec32b9141368a58a6..d28577c0f26fbdef73ad611c8608f3ad8946d5ff 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!
   * <Inuyasha> I don't know if this belongs here specifically, but it sure
   *            doesn't belong in p_spec.c, that's for sure