diff --git a/appveyor.yml b/appveyor.yml
index 0edc7ce280110a6af014bd204bc9a7054c3d6df8..c64e69665a6f77b3c9949f1664a638b57a02800d 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -55,8 +55,6 @@ cache:
 install:
 - if [%CONFIGURATION%] == [SDL64] ( set "X86_64=1" )
 - if [%CONFIGURATION%] == [SDL64] ( set "CONFIGURATION=SDL" )
-- if [%CONFIGURATION%] == [DD64] ( set "X86_64=1" )
-- if [%CONFIGURATION%] == [DD64] ( set "CONFIGURATION=DD" )
 - if [%X86_64%] == [1] ( set "MINGW_SDK=%MINGW_SDK_64%" )
 - if [%X86_64%] == [1] ( set "CCACHE_CC=%CCACHE_CC_64%" )
 
@@ -75,13 +73,6 @@ install:
 configuration:
 - SDL
 - SDL64
-- DD
-- DD64
-
-matrix:
-  allow_failures:
-    - configuration: DD
-    - configuration: DD64
 
 before_build:
 - set "Path=%MINGW_SDK%\bin;%Path%"
diff --git a/src/android/i_system.c b/src/android/i_system.c
index 58fca7c1943448ff81ede9a67854f3791c93bc00..752e9f6c6bb4138caaf17004cbcc0fcaa6b66cee 100644
--- a/src/android/i_system.c
+++ b/src/android/i_system.c
@@ -245,6 +245,8 @@ void I_GetJoystick2Events(void){}
 
 void I_GetMouseEvents(void){}
 
+void I_UpdateMouseGrab(void){}
+
 char *I_GetEnv(const char *name)
 {
   LOGW("I_GetEnv() called?!");
diff --git a/src/console.c b/src/console.c
index 6549370eeda227a1dc48b0ff062261115277f3cd..01d1ddaa25f1b275b3e5e27ab3d3389736af98c9 100644
--- a/src/console.c
+++ b/src/console.c
@@ -592,6 +592,8 @@ void CON_ToggleOff(void)
 	CON_ClearHUD();
 	con_forcepic = 0;
 	con_clipviewtop = -1; // remove console clipping of view
+
+	I_UpdateMouseGrab();
 }
 
 boolean CON_Ready(void)
@@ -616,6 +618,7 @@ void CON_Ticker(void)
 		consoletoggle = false;
 		con_destlines = 0;
 		CON_ClearHUD();
+		I_UpdateMouseGrab();
 	}
 
 	// console key was pushed
@@ -628,6 +631,7 @@ void CON_Ticker(void)
 		{
 			con_destlines = 0;
 			CON_ClearHUD();
+			I_UpdateMouseGrab();
 		}
 		else
 			CON_ChangeHeight();
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 302294557969a12977ac39a0d8c44def6b35b979..98ff3a6f8ed95785f76d0a53e6883d0797fe5167 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -3002,8 +3002,8 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 		CL_RemovePlayer(pnum, kickreason);
 }
 
-consvar_t cv_allownewplayer = {"allowjoin", "On", CV_SAVE|CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL	};
-consvar_t cv_joinnextround = {"joinnextround", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; /// \todo not done
+consvar_t cv_allownewplayer = {"allowjoin", "On", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL	};
+consvar_t cv_joinnextround = {"joinnextround", "Off", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; /// \todo not done
 static CV_PossibleValue_t maxplayers_cons_t[] = {{2, "MIN"}, {32, "MAX"}, {0, NULL}};
 consvar_t cv_maxplayers = {"maxplayers", "8", CV_SAVE, maxplayers_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 static CV_PossibleValue_t resynchattempts_cons_t[] = {{1, "MIN"}, {20, "MAX"}, {0, "No"}, {0, NULL}};
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 2668e9c70e330df55b095aa66e3889c9f6410533..19e1f1dad915e08d3559502f45b99f5851e48653 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -2170,6 +2170,8 @@ static void Got_Pause(UINT8 **cp, INT32 playernum)
 		else
 			S_ResumeAudio();
 	}
+
+	I_UpdateMouseGrab();
 }
 
 // Command for stuck characters in netgames, griefing, etc.
diff --git a/src/dehacked.c b/src/dehacked.c
index b412fa0b24dc9d5ac3d29c7612b7372bd4b9f663..e30d4dbe3bbc805d6f8734b33c8cbc95a77abe30 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -4804,11 +4804,13 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
 		if (introchanged)
 		{
 			menuactive = false;
+			I_UpdateMouseGrab();
 			COM_BufAddText("playintro");
 		}
 		else if (titlechanged)
 		{
 			menuactive = false;
+			I_UpdateMouseGrab();
 			COM_BufAddText("exitgame"); // Command_ExitGame_f() but delayed
 		}
 	}
diff --git a/src/doomdef.h b/src/doomdef.h
index 7d65398d7302723b5bb481a0b0e4426f750f0480..0a98c874aa5eb63ace8d9e04021df642d7790f80 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -464,6 +464,8 @@ extern void *(*M_Memcpy)(void* dest, const void* src, size_t n) FUNCNONNULL;
 char *va(const char *format, ...) FUNCPRINTF;
 char *M_GetToken(const char *inputString);
 void M_UnGetToken(void);
+UINT32 M_GetTokenPos(void);
+void M_SetTokenPos(UINT32 newPos);
 char *sizeu1(size_t num);
 char *sizeu2(size_t num);
 char *sizeu3(size_t num);
diff --git a/src/dummy/i_system.c b/src/dummy/i_system.c
index a3fe3077c2a46f6956a5723eb1de40127da7e798..5c0f7eb99ed6ab0d41fc93bcfd87fc89ab8d044b 100644
--- a/src/dummy/i_system.c
+++ b/src/dummy/i_system.c
@@ -150,6 +150,8 @@ void I_GetJoystick2Events(void){}
 
 void I_GetMouseEvents(void){}
 
+void I_UpdateMouseGrab(void){}
+
 char *I_GetEnv(const char *name)
 {
 	(void)name;
diff --git a/src/g_game.c b/src/g_game.c
index 52da6bea782d8d4667b2f17f22f3af7fe7f486dc..b4f5d4c64583d0177b1db25dd81662232c53f230 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -1840,6 +1840,7 @@ void G_DoLoadLevel(boolean resetplayer)
 		titlemapinaction = TITLEMAP_OFF;
 
 	G_SetGamestate(GS_LEVEL);
+	I_UpdateMouseGrab();
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index 347afb7357c2833f3ddf4fece3a65502f36522c8..f558b8c0c99fc0019695124ce55079b8761a655d 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -1173,6 +1173,8 @@ void HU_clearChatChars(void)
 		w_chat[i] = 0; // reset this.
 	chat_on = false;
 	c_input = 0;
+
+	I_UpdateMouseGrab();
 }
 
 #ifndef NONET
@@ -1323,6 +1325,7 @@ boolean HU_Responder(event_t *ev)
 			chat_on = false;
 			c_input = 0; // reset input cursor
 			chat_scrollmedown = true; // you hit enter, so you might wanna autoscroll to see what you just sent. :)
+			I_UpdateMouseGrab();
 		}
 		else if (c == KEY_ESCAPE
 			|| ((c == gamecontrol[gc_talkkey][0] || c == gamecontrol[gc_talkkey][1]
@@ -1331,6 +1334,7 @@ boolean HU_Responder(event_t *ev)
 		{
 			chat_on = false;
 			c_input = 0; // reset input cursor
+			I_UpdateMouseGrab();
 		}
 		else if ((c == KEY_UPARROW || c == KEY_MOUSEWHEELUP) && chat_scroll > 0 && !OLDCHAT) // CHAT SCROLLING YAYS!
 		{
diff --git a/src/i_system.h b/src/i_system.h
index 2532ba0ee3827f5d089bfced6ea84852e242f814..a60b56310dcc4cfe651dcd53677c732a5cb7ceca 100644
--- a/src/i_system.h
+++ b/src/i_system.h
@@ -288,6 +288,10 @@ void I_GetJoystick2Events(void);
 */
 void I_GetMouseEvents(void);
 
+/**	\brief Checks if the mouse needs to be grabbed
+*/
+void I_UpdateMouseGrab(void);
+
 char *I_GetEnv(const char *name);
 
 INT32 I_PutEnv(char *variable);
diff --git a/src/m_menu.c b/src/m_menu.c
index 8463010bf5b2062d0dfa5f0c21c7d0ecb0c92177..7e739e42e4b8f201bf9f641baf4ffdc6bda1433e 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -2877,6 +2877,7 @@ static void M_GoBack(INT32 choice)
 
 			menuactive = false;
 			wipetypepre = menupres[M_GetYoungestChildMenu()].exitwipe;
+			I_UpdateMouseGrab();
 			D_StartTitle();
 		}
 		else
@@ -3221,10 +3222,7 @@ boolean M_Responder(event_t *ev)
 
 			case KEY_ESCAPE: // Pop up menu
 				if (chat_on)
-				{
 					HU_clearChatChars();
-					chat_on = false;
-				}
 				else
 					M_StartControlPanel();
 				return true;
@@ -3602,6 +3600,8 @@ void M_ClearMenus(boolean callexitmenufunc)
 		currentMenu = &MainDef; // Not like it matters
 	menuactive = false;
 	hidetitlemap = false;
+
+	I_UpdateMouseGrab();
 }
 
 //
diff --git a/src/m_misc.c b/src/m_misc.c
index b0a1fb8c5926507789515baccebc6eb54fc7c089..edb24ab1ec9ce3996f34eafad1bd1909663a2a28 100644
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -1908,6 +1908,20 @@ void M_UnGetToken(void)
 	endPos = oldendPos;
 }
 
+/** Returns the current token's position.
+ */
+UINT32 M_GetTokenPos(void)
+{
+	return endPos;
+}
+
+/** Sets the current token's position.
+ */
+void M_SetTokenPos(UINT32 newPos)
+{
+	endPos = newPos;
+}
+
 /** Count bits in a number.
   */
 UINT8 M_CountBits(UINT32 num, UINT8 size)
diff --git a/src/p_setup.c b/src/p_setup.c
index 366fc5372be918f6db23ae18fe95d6d0ffde0385..bd1c53104501c3a4392fdb2529be93cdde01b7d1 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -83,6 +83,8 @@
 #include "p_slopes.h"
 #endif
 
+#include "fastcmp.h" // textmap parsing
+
 //
 // Map MD5, calculated on level load.
 // Sent to clients in PT_SERVERINFO.
@@ -863,11 +865,6 @@ static void P_InitializeSector(sector_t *ss)
 	ss->lightingdata = NULL;
 	ss->fadecolormapdata = NULL;
 
-	ss->floor_xoffs = ss->floor_yoffs = 0;
-	ss->ceiling_xoffs = ss->ceiling_yoffs = 0;
-
-	ss->floorpic_angle = ss->ceilingpic_angle = 0;
-
 	ss->heightsec = -1;
 	ss->camsec = -1;
 
@@ -943,6 +940,11 @@ static void P_LoadSectors(UINT8 *data)
 		ss->special = SHORT(ms->special);
 		ss->tag = SHORT(ms->tag);
 
+		ss->floor_xoffs = ss->floor_yoffs = 0;
+		ss->ceiling_xoffs = ss->ceiling_yoffs = 0;
+
+		ss->floorpic_angle = ss->ceilingpic_angle = 0;
+
 		P_InitializeSector(ss);
 	}
 }
@@ -1012,14 +1014,32 @@ static void P_InitializeLinedef(line_t *ld)
 	{
 		sides[ld->sidenum[0]].special = ld->special;
 		sides[ld->sidenum[0]].line = ld;
-
 	}
 	if (ld->sidenum[1] != 0xffff)
 	{
 		sides[ld->sidenum[1]].special = ld->special;
 		sides[ld->sidenum[1]].line = ld;
+	}
+}
 
+static void P_SetLinedefV1(size_t i, UINT16 vertex_num)
+{
+	if (vertex_num >= numvertexes)
+	{
+		CONS_Debug(DBG_SETUP, "P_SetLinedefV1: linedef %s has out-of-range v1 num %u\n", sizeu1(i), vertex_num);
+		vertex_num = 0;
 	}
+	lines[i].v1 = &vertexes[vertex_num];
+}
+
+static void P_SetLinedefV2(size_t i, UINT16 vertex_num)
+{
+	if (vertex_num >= numvertexes)
+	{
+		CONS_Debug(DBG_SETUP, "P_SetLinedefV2: linedef %s has out-of-range v2 num %u\n", sizeu1(i), vertex_num);
+		vertex_num = 0;
+	}
+	lines[i].v2 = &vertexes[vertex_num];
 }
 
 static void P_LoadLinedefs(UINT8 *data)
@@ -1033,8 +1053,8 @@ static void P_LoadLinedefs(UINT8 *data)
 		ld->flags = SHORT(mld->flags);
 		ld->special = SHORT(mld->special);
 		ld->tag = SHORT(mld->tag);
-		ld->v1 = &vertexes[SHORT(mld->v1)];
-		ld->v2 = &vertexes[SHORT(mld->v2)];
+		P_SetLinedefV1(i, SHORT(mld->v1));
+		P_SetLinedefV2(i, SHORT(mld->v2));
 
 		ld->sidenum[0] = SHORT(mld->sidenum[0]);
 		ld->sidenum[1] = SHORT(mld->sidenum[1]);
@@ -1043,6 +1063,30 @@ static void P_LoadLinedefs(UINT8 *data)
 	}
 }
 
+static void P_SetSidedefSector(size_t i, UINT16 sector_num)
+{
+	// cph 2006/09/30 - catch out-of-range sector numbers; use sector 0 instead
+	if (sector_num >= numsectors)
+	{
+		CONS_Debug(DBG_SETUP, "P_SetSidedefSector: sidedef %s has out-of-range sector num %u\n", sizeu1(i), sector_num);
+		sector_num = 0;
+	}
+	sides[i].sector = &sectors[sector_num];
+}
+
+static void P_InitializeSidedef(side_t *sd)
+{
+	if (!sd->line)
+	{
+		CONS_Debug(DBG_SETUP, "P_LoadSidedefs: Sidedef %s is not used by any linedef\n", sizeu1((size_t)(sd - sides)));
+		sd->line = &lines[0];
+		sd->special = sd->line->special;
+	}
+
+	sd->text = NULL;
+	sd->colormap_data = NULL;
+}
+
 static void P_LoadSidedefs(UINT8 *data)
 {
 	mapsidedef_t *msd = (mapsidedef_t*)data;
@@ -1051,22 +1095,28 @@ static void P_LoadSidedefs(UINT8 *data)
 
 	for (i = 0; i < numsides; i++, sd++, msd++)
 	{
-		UINT16 sector_num;
-		boolean isfrontside = !sd->line || sd->line->sidenum[0] == i;
+		INT16 textureoffset = SHORT(msd->textureoffset);
+		boolean isfrontside;
 
-		sd->textureoffset = SHORT(msd->textureoffset)<<FRACBITS;
-		sd->rowoffset = SHORT(msd->rowoffset)<<FRACBITS;
+		P_InitializeSidedef(sd);
+
+		isfrontside = sd->line->sidenum[0] == i;
 
-		// cph 2006/09/30 - catch out-of-range sector numbers; use sector 0 instead
-		sector_num = SHORT(msd->sector);
-		if (sector_num >= numsectors)
+		// Repeat count for midtexture
+		if (((sd->line->flags & (ML_TWOSIDED|ML_EFFECT5)) == (ML_TWOSIDED|ML_EFFECT5))
+			&& !(sd->special >= 300 && sd->special < 500)) // exempt linedef exec specials
 		{
-			CONS_Debug(DBG_SETUP, "P_LoadSidedefs: sidedef %s has out-of-range sector num %u\n", sizeu1(i), sector_num);
-			sector_num = 0;
+			sd->repeatcnt = (INT16)(((unsigned)textureoffset) >> 12);
+			sd->textureoffset = (((unsigned)textureoffset) & 2047) << FRACBITS;
 		}
-		sd->sector = &sectors[sector_num];
+		else
+		{
+			sd->repeatcnt = 0;
+			sd->textureoffset = textureoffset << FRACBITS;
+		}
+		sd->rowoffset = SHORT(msd->rowoffset)<<FRACBITS;
 
-		sd->colormap_data = NULL;
+		P_SetSidedefSector(i, SHORT(msd->sector));
 
 		// Special info stored in texture fields!
 		switch (sd->special)
@@ -1233,29 +1283,400 @@ static void P_LoadThings(UINT8 *data)
 			mt->z = mt->options; // NiGHTS Hoops use the full flags bits to set the height.
 		else
 			mt->z = mt->options >> ZSHIFT;
+
+		mt->mobj = NULL;
 	}
 }
 
-static void P_LoadMapData(const virtres_t *virt)
+// Stores positions for relevant map data spread through a TEXTMAP.
+UINT32 mapthingsPos[UINT16_MAX];
+UINT32 linesPos[UINT16_MAX];
+UINT32 sidesPos[UINT16_MAX];
+UINT32 vertexesPos[UINT16_MAX];
+UINT32 sectorsPos[UINT16_MAX];
+
+// Determine total amount of map data in TEXTMAP.
+static boolean TextmapCount(UINT8 *data, size_t size)
 {
-	virtlump_t* virtvertexes = NULL, * virtsectors = NULL, * virtsidedefs = NULL, * virtlinedefs = NULL, * virtthings = NULL;
-#ifdef UDMF
-	virtlump_t* textmap = vres_Find(virt, "TEXTMAP");
+	char *tkn = M_GetToken((char *)data);
+	UINT8 brackets = 0;
 
-	// Count map data.
-	if (textmap)
+	nummapthings = 0;
+	numlines = 0;
+	numsides = 0;
+	numvertexes = 0;
+	numsectors = 0;
+
+	// Look for namespace at the beginning.
+	if (!fastcmp(tkn, "namespace"))
+	{
+		Z_Free(tkn);
+		CONS_Alert(CONS_ERROR, "No namespace at beginning of lump!\n");
+		return false;
+	}
+	Z_Free(tkn);
+
+	// Check if namespace is valid.
+	tkn = M_GetToken(NULL);
+	if (!fastcmp(tkn, "srb2"))
+		CONS_Alert(CONS_WARNING, "Invalid namespace '%s', only 'srb2' is supported.\n", tkn);
+	Z_Free(tkn);
+
+	tkn = M_GetToken(NULL);
+	while (tkn && M_GetTokenPos() < size)
+	{
+		// Avoid anything inside bracketed stuff, only look for external keywords.
+		if (brackets)
+		{
+			if (fastcmp(tkn, "}"))
+				brackets--;
+		}
+		else if (fastcmp(tkn, "{"))
+			brackets++;
+		// Check for valid fields.
+		else if (fastcmp(tkn, "thing"))
+			mapthingsPos[nummapthings++] = M_GetTokenPos();
+		else if (fastcmp(tkn, "linedef"))
+			linesPos[numlines++] = M_GetTokenPos();
+		else if (fastcmp(tkn, "sidedef"))
+			sidesPos[numsides++] = M_GetTokenPos();
+		else if (fastcmp(tkn, "vertex"))
+			vertexesPos[numvertexes++] = M_GetTokenPos();
+		else if (fastcmp(tkn, "sector"))
+			sectorsPos[numsectors++] = M_GetTokenPos();
+		else
+			CONS_Alert(CONS_NOTICE, "Unknown field '%s'.\n", tkn);
+
+		Z_Free(tkn);
+		tkn = M_GetToken(NULL);
+	}
+
+	Z_Free(tkn);
+
+	if (brackets)
+	{
+		CONS_Alert(CONS_ERROR, "Unclosed brackets detected in textmap lump.\n");
+		return false;
+	}
+
+	return true;
+}
+
+static void ParseTextmapVertexParameter(UINT32 i, char *param, char *val)
+{
+	if (fastcmp(param, "x"))
+		vertexes[i].x = FLOAT_TO_FIXED(atof(val));
+	else if (fastcmp(param, "y"))
+		vertexes[i].y = FLOAT_TO_FIXED(atof(val));
+}
+
+static void ParseTextmapSectorParameter(UINT32 i, char *param, char *val)
+{
+	if (fastcmp(param, "heightfloor"))
+		sectors[i].floorheight = atol(val) << FRACBITS;
+	else if (fastcmp(param, "heightceiling"))
+		sectors[i].ceilingheight = atol(val) << FRACBITS;
+	if (fastcmp(param, "texturefloor"))
+		sectors[i].floorpic = P_AddLevelFlat(val, foundflats);
+	else if (fastcmp(param, "textureceiling"))
+		sectors[i].ceilingpic = P_AddLevelFlat(val, foundflats);
+	else if (fastcmp(param, "lightlevel"))
+		sectors[i].lightlevel = atol(val);
+	else if (fastcmp(param, "special"))
+		sectors[i].special = atol(val);
+	else if (fastcmp(param, "id"))
+		sectors[i].tag = atol(val);
+	else if (fastcmp(param, "xpanningfloor"))
+		sectors[i].floor_xoffs = FLOAT_TO_FIXED(atof(val));
+	else if (fastcmp(param, "ypanningfloor"))
+		sectors[i].floor_yoffs = FLOAT_TO_FIXED(atof(val));
+	else if (fastcmp(param, "xpanningceiling"))
+		sectors[i].ceiling_xoffs = FLOAT_TO_FIXED(atof(val));
+	else if (fastcmp(param, "ypanningceiling"))
+		sectors[i].ceiling_yoffs = FLOAT_TO_FIXED(atof(val));
+	else if (fastcmp(param, "rotationfloor"))
+		sectors[i].floorpic_angle = FixedAngle(FLOAT_TO_FIXED(atof(val)));
+	else if (fastcmp(param, "rotationceiling"))
+		sectors[i].ceilingpic_angle = FixedAngle(FLOAT_TO_FIXED(atof(val)));
+}
+
+static void ParseTextmapSidedefParameter(UINT32 i, char *param, char *val)
+{
+	if (fastcmp(param, "offsetx"))
+		sides[i].textureoffset = atol(val)<<FRACBITS;
+	else if (fastcmp(param, "offsety"))
+		sides[i].rowoffset = atol(val)<<FRACBITS;
+	else if (fastcmp(param, "texturetop"))
+		sides[i].toptexture = R_TextureNumForName(val);
+	else if (fastcmp(param, "texturebottom"))
+		sides[i].bottomtexture = R_TextureNumForName(val);
+	else if (fastcmp(param, "texturemiddle"))
+		sides[i].midtexture = R_TextureNumForName(val);
+	else if (fastcmp(param, "sector"))
+		P_SetSidedefSector(i, atol(val));
+	else if (fastcmp(param, "repeatcnt"))
+		sides[i].repeatcnt = atol(val);
+}
+
+static void ParseTextmapLinedefParameter(UINT32 i, char *param, char *val)
+{
+	if (fastcmp(param, "id"))
+		lines[i].tag = atol(val);
+	else if (fastcmp(param, "special"))
+		lines[i].special = atol(val);
+	else if (fastcmp(param, "v1"))
+		P_SetLinedefV1(i, atol(val));
+	else if (fastcmp(param, "v2"))
+		P_SetLinedefV2(i, atol(val));
+	else if (fastcmp(param, "sidefront"))
+		lines[i].sidenum[0] = atol(val);
+	else if (fastcmp(param, "sideback"))
+		lines[i].sidenum[1] = atol(val);
+
+	// Flags
+	else if (fastcmp(param, "blocking") && fastcmp("true", val))
+		lines[i].flags |= ML_IMPASSIBLE;
+	else if (fastcmp(param, "blockmonsters") && fastcmp("true", val))
+		lines[i].flags |= ML_BLOCKMONSTERS;
+	else if (fastcmp(param, "twosided") && fastcmp("true", val))
+		lines[i].flags |= ML_TWOSIDED;
+	else if (fastcmp(param, "dontpegtop") && fastcmp("true", val))
+		lines[i].flags |= ML_DONTPEGTOP;
+	else if (fastcmp(param, "dontpegbottom") && fastcmp("true", val))
+		lines[i].flags |= ML_DONTPEGBOTTOM;
+	else if (fastcmp(param, "skewtd") && fastcmp("true", val))
+		lines[i].flags |= ML_EFFECT1;
+	else if (fastcmp(param, "noclimb") && fastcmp("true", val))
+		lines[i].flags |= ML_NOCLIMB;
+	else if (fastcmp(param, "noskew") && fastcmp("true", val))
+		lines[i].flags |= ML_EFFECT2;
+	else if (fastcmp(param, "midpeg") && fastcmp("true", val))
+		lines[i].flags |= ML_EFFECT3;
+	else if (fastcmp(param, "midsolid") && fastcmp("true", val))
+		lines[i].flags |= ML_EFFECT4;
+	else if (fastcmp(param, "wrapmidtex") && fastcmp("true", val))
+		lines[i].flags |= ML_EFFECT5;
+	else if (fastcmp(param, "effect6") && fastcmp("true", val))
+		lines[i].flags |= ML_EFFECT6;
+	else if (fastcmp(param, "nonet") && fastcmp("true", val))
+		lines[i].flags |= ML_NONET;
+	else if (fastcmp(param, "netonly") && fastcmp("true", val))
+		lines[i].flags |= ML_NETONLY;
+	else if (fastcmp(param, "bouncy") && fastcmp("true", val))
+		lines[i].flags |= ML_BOUNCY;
+	else if (fastcmp(param, "transfer") && fastcmp("true", val))
+		lines[i].flags |= ML_TFERLINE;
+}
+
+static void ParseTextmapThingParameter(UINT32 i, char *param, char *val)
+{
+	if (fastcmp(param, "x"))
+		mapthings[i].x = atol(val);
+	else if (fastcmp(param, "y"))
+		mapthings[i].y = atol(val);
+	else if (fastcmp(param, "height"))
+		mapthings[i].z = atol(val);
+	else if (fastcmp(param, "angle"))
+		mapthings[i].angle = atol(val);
+	else if (fastcmp(param, "type"))
+		mapthings[i].type = atol(val);
+
+	// Flags
+	else if (fastcmp(param, "extra") && fastcmp("true", val))
+		mapthings[i].options |= MTF_EXTRA;
+	else if (fastcmp(param, "flip") && fastcmp("true", val))
+		mapthings[i].options |= MTF_OBJECTFLIP;
+	else if (fastcmp(param, "special") && fastcmp("true", val))
+		mapthings[i].options |= MTF_OBJECTSPECIAL;
+	else if (fastcmp(param, "ambush") && fastcmp("true", val))
+		mapthings[i].options |= MTF_AMBUSH;
+}
+
+/** From a given position table, run a specified parser function through a {}-encapsuled text.
+  *
+  * \param Position of the data to parse, in the textmap.
+  * \param Structure number (mapthings, sectors, ...).
+  * \param Parser function pointer.
+  */
+static void TextmapParse(UINT32 dataPos, size_t num, void (*parser)(UINT32, char *, char *))
+{
+	char *param, *val;
+
+	M_SetTokenPos(dataPos);
+	param = M_GetToken(NULL);
+	if (!fastcmp(param, "{"))
+	{
+		Z_Free(param);
+		CONS_Alert(CONS_WARNING, "Invalid UDMF data capsule!\n");
+		return;
+	}
+	Z_Free(param);
+
+	while (true)
+	{
+		param = M_GetToken(NULL);
+		if (fastcmp(param, "}"))
+		{
+			Z_Free(param);
+			break;
+		}
+		val = M_GetToken(NULL);
+		parser(num, param, val);
+		Z_Free(param);
+		Z_Free(val);
+	}
+}
+
+/** Provides a fix to the flat alignment coordinate transform from standard Textmaps.
+ */
+static void TextmapFixFlatOffsets(sector_t *sec)
+{
+	if (sec->floorpic_angle)
+	{
+		fixed_t pc = FINECOSINE(sec->floorpic_angle>>ANGLETOFINESHIFT);
+		fixed_t ps = FINESINE  (sec->floorpic_angle>>ANGLETOFINESHIFT);
+		fixed_t xoffs = sec->floor_xoffs;
+		fixed_t yoffs = sec->floor_yoffs;
+		sec->floor_xoffs = (FixedMul(xoffs, pc) % MAXFLATSIZE) - (FixedMul(yoffs, ps) % MAXFLATSIZE);
+		sec->floor_yoffs = (FixedMul(xoffs, ps) % MAXFLATSIZE) + (FixedMul(yoffs, pc) % MAXFLATSIZE);
+	}
+
+	if (sec->ceilingpic_angle)
+	{
+		fixed_t pc = FINECOSINE(sec->ceilingpic_angle>>ANGLETOFINESHIFT);
+		fixed_t ps = FINESINE  (sec->ceilingpic_angle>>ANGLETOFINESHIFT);
+		fixed_t xoffs = sec->ceiling_xoffs;
+		fixed_t yoffs = sec->ceiling_yoffs;
+		sec->ceiling_xoffs = (FixedMul(xoffs, pc) % MAXFLATSIZE) - (FixedMul(yoffs, ps) % MAXFLATSIZE);
+		sec->ceiling_yoffs = (FixedMul(xoffs, ps) % MAXFLATSIZE) + (FixedMul(yoffs, pc) % MAXFLATSIZE);
+	}
+}
+
+/** Loads the textmap data, after obtaining the elements count and allocating their respective space.
+  */
+static void P_LoadTextmap(void)
+{
+	UINT32 i;
+
+	vertex_t   *vt;
+	sector_t   *sc;
+	line_t     *ld;
+	side_t     *sd;
+	mapthing_t *mt;
+
+	CONS_Alert(CONS_NOTICE, "UDMF support is still a work-in-progress; its specs and features are prone to change until it is fully implemented.\n");
+
+	/// Given the UDMF specs, some fields are given a default value.
+	/// If an element's field has a default value set, it is omitted
+	/// from the textmap, and therefore we have to account for it by
+	/// preemptively setting that value beforehand.
+
+	for (i = 0, vt = vertexes; i < numvertexes; i++, vt++)
+	{
+		// Defaults.
+		vt->x = vt->y = INT32_MAX;
+		vt->z = 0;
+
+		TextmapParse(vertexesPos[i], i, ParseTextmapVertexParameter);
+
+		if (vt->x == INT32_MAX)
+			I_Error("P_LoadTextmap: vertex %s has no x value set!\n", sizeu1(i));
+		if (vt->y == INT32_MAX)
+			I_Error("P_LoadTextmap: vertex %s has no y value set!\n", sizeu1(i));
+	}
+
+	for (i = 0, sc = sectors; i < numsectors; i++, sc++)
+	{
+		// Defaults.
+		sc->floorheight = 0;
+		sc->ceilingheight = 0;
+
+		sc->floorpic = 0;
+		sc->ceilingpic = 0;
+
+		sc->lightlevel = 255;
+
+		sc->special = 0;
+		sc->tag = 0;
+
+		sc->floor_xoffs = sc->floor_yoffs = 0;
+		sc->ceiling_xoffs = sc->ceiling_yoffs = 0;
+
+		sc->floorpic_angle = sc->ceilingpic_angle = 0;
+
+		TextmapParse(sectorsPos[i], i, ParseTextmapSectorParameter);
+		P_InitializeSector(sc);
+		TextmapFixFlatOffsets(sc);
+	}
+
+	for (i = 0, ld = lines; i < numlines; i++, ld++)
+	{
+		// Defaults.
+		ld->v1 = ld->v2 = NULL;
+		ld->flags = 0;
+		ld->special = 0;
+		ld->tag = 0;
+		ld->sidenum[0] = 0xffff;
+		ld->sidenum[1] = 0xffff;
+
+		TextmapParse(linesPos[i], i, ParseTextmapLinedefParameter);
+
+		if (!ld->v1)
+			I_Error("P_LoadTextmap: linedef %s has no v1 value set!\n", sizeu1(i));
+		if (!ld->v2)
+			I_Error("P_LoadTextmap: linedef %s has no v2 value set!\n", sizeu1(i));
+		if (ld->sidenum[0] == 0xffff)
+			I_Error("P_LoadTextmap: linedef %s has no sidefront value set!\n", sizeu1(i));
+
+		P_InitializeLinedef(ld);
+	}
+
+	for (i = 0, sd = sides; i < numsides; i++, sd++)
+	{
+		// Defaults.
+		sd->textureoffset = 0;
+		sd->rowoffset = 0;
+		sd->toptexture = R_TextureNumForName("-");
+		sd->midtexture = R_TextureNumForName("-");
+		sd->bottomtexture = R_TextureNumForName("-");
+		sd->sector = NULL;
+		sd->repeatcnt = 0;
+
+		TextmapParse(sidesPos[i], i, ParseTextmapSidedefParameter);
+
+		if (!sd->sector)
+			I_Error("P_LoadTextmap: sidedef %s has no sector value set!\n", sizeu1(i));
+
+		P_InitializeSidedef(sd);
+	}
+
+	for (i = 0, mt = mapthings; i < nummapthings; i++, mt++)
 	{
-		nummapthings = 0;
-		numlines = 0;
-		numsides = 0;
-		numvertexes = 0;
-		numsectors = 0;
+		// Defaults.
+		mt->x = mt->y = 0;
+		mt->angle = 0;
+		mt->type = 0;
+		mt->options = 0;
+		mt->z = 0;
+		mt->extrainfo = 0;
+		mt->mobj = NULL;
 
-		// Count how many entries for each type we got in textmap.
-		//TextmapCount(vtextmap->data, vtextmap->size);
+		TextmapParse(mapthingsPos[i], i, ParseTextmapThingParameter);
+	}
+}
+
+static boolean P_LoadMapData(const virtres_t *virt)
+{
+	virtlump_t *virtvertexes = NULL, *virtsectors = NULL, *virtsidedefs = NULL, *virtlinedefs = NULL, *virtthings = NULL;
+	virtlump_t *textmap = vres_Find(virt, "TEXTMAP");
+
+	// Count map data.
+	if (textmap) // Count how many entries for each type we got in textmap.
+	{
+		if (!TextmapCount(textmap->data, textmap->size))
+			return false;
 	}
 	else
-#endif
 	{
 		virtthings   = vres_Find(virt, "THINGS");
 		virtvertexes = vres_Find(virt, "VERTEXES");
@@ -1305,15 +1726,11 @@ static void P_LoadMapData(const virtres_t *virt)
 
 	numlevelflats = 0;
 
-#ifdef UDMF
+	// Load map data.
 	if (textmap)
-	{
-
-	}
+		P_LoadTextmap();
 	else
-#endif
 	{
-		// Strict map data
 		P_LoadVertices(virtvertexes->data);
 		P_LoadSectors(virtsectors->data);
 		P_LoadLinedefs(virtlinedefs->data);
@@ -1341,6 +1758,8 @@ static void P_LoadMapData(const virtres_t *virt)
 	memcpy(spawnsectors, sectors, numsectors * sizeof (*sectors));
 	memcpy(spawnlines, lines, numlines * sizeof (*lines));
 	memcpy(spawnsides, sides, numsides * sizeof (*sides));
+
+	return true;
 }
 
 static void P_InitializeSubsector(subsector_t *ss)
@@ -1421,10 +1840,13 @@ static inline float P_SegLengthFloat(seg_t *seg)
 
 static void P_InitializeSeg(seg_t *seg)
 {
-	seg->sidedef = &sides[seg->linedef->sidenum[seg->side]];
+	if (seg->linedef)
+	{
+		seg->sidedef = &sides[seg->linedef->sidenum[seg->side]];
 
-	seg->frontsector = seg->sidedef->sector;
-	seg->backsector = (seg->linedef->flags & ML_TWOSIDED) ? sides[seg->linedef->sidenum[seg->side ^ 1]].sector : NULL;
+		seg->frontsector = seg->sidedef->sector;
+		seg->backsector = (seg->linedef->flags & ML_TWOSIDED) ? sides[seg->linedef->sidenum[seg->side ^ 1]].sector : NULL;
+	}
 
 #ifdef HWRENDER
 	seg->pv1 = seg->pv2 = NULL;
@@ -1607,20 +2029,21 @@ static boolean P_LoadExtendedSubsectorsAndSegs(UINT8 **data, nodetype_t nodetype
 		case NT_XGL3:
 			for (m = 0; m < subsectors[i].numlines; m++, k++)
 			{
+				UINT32 vertexnum = READUINT32((*data));
 				UINT16 linenum;
-				UINT32 vert = READUINT32((*data));
 
-				segs[k].v1 = &vertexes[vert];
-				if (m == 0)
-					segs[k + subsectors[i].numlines - 1].v2 = &vertexes[vert];
-				else
-					segs[k - 1].v2 = segs[k].v1;
+				if (vertexnum >= numvertexes)
+					I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %d has invalid vertex %d!\n", sizeu1(k), m, vertexnum);
 
-				(*data) += 4; // partner, can be ignored by software renderer
+				segs[k - 1 + ((m == 0) ? subsectors[i].numlines : 0)].v2 = segs[k].v1 = &vertexes[vertexnum];
+
+				READUINT32((*data)); // partner, can be ignored by software renderer
 				if (nodetype == NT_XGL3)
-					(*data) += 2; // Line number is 32-bit in XGL3, but we're limited to 16 bits.
+					READUINT16((*data)); // Line number is 32-bit in XGL3, but we're limited to 16 bits.
 
 				linenum = READUINT16((*data));
+				if (linenum != 0xFFFF && linenum >= numlines)
+					I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %d has invalid linedef %d!\n", sizeu1(k), m, linenum);
 				segs[k].glseg = (linenum == 0xFFFF);
 				segs[k].linedef = (linenum == 0xFFFF) ? NULL : &lines[linenum];
 				segs[k].side = READUINT8((*data));
@@ -1630,9 +2053,20 @@ static boolean P_LoadExtendedSubsectorsAndSegs(UINT8 **data, nodetype_t nodetype
 		case NT_XNOD:
 			for (m = 0; m < subsectors[i].numlines; m++, k++)
 			{
-				segs[k].v1 = &vertexes[READUINT32((*data))];
-				segs[k].v2 = &vertexes[READUINT32((*data))];
-				segs[k].linedef = &lines[READUINT16((*data))];
+				UINT32 v1num = READUINT32((*data));
+				UINT32 v2num = READUINT32((*data));
+				UINT16 linenum = READUINT16((*data));
+
+				if (v1num >= numvertexes)
+					I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %d has invalid v1 %d!\n", sizeu1(k), m, v1num);
+				if (v2num >= numvertexes)
+					I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %d has invalid v2 %d!\n", sizeu1(k), m, v2num);
+				if (linenum >= numlines)
+					I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %d has invalid linedef %d!\n", sizeu1(k), m, linenum);
+
+				segs[k].v1 = &vertexes[v1num];
+				segs[k].v2 = &vertexes[v2num];
+				segs[k].linedef = &lines[linenum];
 				segs[k].side = READUINT8((*data));
 				segs[k].glseg = false;
 			}
@@ -1649,7 +2083,8 @@ static boolean P_LoadExtendedSubsectorsAndSegs(UINT8 **data, nodetype_t nodetype
 		vertex_t *v2 = seg->v2;
 		P_InitializeSeg(seg);
 		seg->angle = R_PointToAngle2(v1->x, v1->y, v2->x, v2->y);
-		seg->offset = FixedHypot(v1->x - seg->linedef->v1->x, v1->y - seg->linedef->v1->y);
+		if (seg->linedef)
+			segs[i].offset = FixedHypot(v1->x - seg->linedef->v1->x, v1->y - seg->linedef->v1->y);
 	}
 
 	return true;
@@ -2091,16 +2526,6 @@ static void P_ProcessLinedefsWithSidedefs(void)
 		ld->frontsector = sides[ld->sidenum[0]].sector; //e6y: Can't be -1 here
 		ld->backsector  = ld->sidenum[1] != 0xffff ? sides[ld->sidenum[1]].sector : 0;
 
-		// Repeat count for midtexture
-		if ((ld->flags & ML_EFFECT5) && (ld->sidenum[1] != 0xffff)
-			&& !(ld->special >= 300 && ld->special < 500)) // exempt linedef exec specials
-		{
-			sides[ld->sidenum[0]].repeatcnt = (INT16)(((unsigned)sides[ld->sidenum[0]].textureoffset >> FRACBITS) >> 12);
-			sides[ld->sidenum[0]].textureoffset = (((unsigned)sides[ld->sidenum[0]].textureoffset >> FRACBITS) & 2047) << FRACBITS;
-			sides[ld->sidenum[1]].repeatcnt = (INT16)(((unsigned)sides[ld->sidenum[1]].textureoffset >> FRACBITS) >> 12);
-			sides[ld->sidenum[1]].textureoffset = (((unsigned)sides[ld->sidenum[1]].textureoffset >> FRACBITS) & 2047) << FRACBITS;
-		}
-
 		// Compile linedef 'text' from both sidedefs 'text' for appropriate specials.
 		switch(ld->special)
 		{
@@ -2123,69 +2548,6 @@ static void P_ProcessLinedefsWithSidedefs(void)
 	}
 }
 
-static void P_CompressSidedefs(void)
-{
-	side_t *newsides;
-	size_t numnewsides = 0;
-	size_t z;
-	size_t i;
-
-	for (i = 0; i < numsides; i++)
-	{
-		size_t j, k;
-		line_t *ld;
-
-		if (!sides[i].sector)
-			continue;
-
-		for (k = numlines, ld = lines; k--; ld++)
-		{
-			if (ld->sidenum[0] == i)
-				ld->sidenum[0] = (UINT16)numnewsides;
-
-			if (ld->sidenum[1] == i)
-				ld->sidenum[1] = (UINT16)numnewsides;
-		}
-
-		for (j = i + 1; j < numsides; j++)
-		{
-			if (!sides[j].sector)
-				continue;
-
-			if (!memcmp(&sides[i], &sides[j], sizeof(side_t)))
-			{
-				// Find the linedefs that belong to this one
-				for (k = numlines, ld = lines; k--; ld++)
-				{
-					if (ld->sidenum[0] == j)
-						ld->sidenum[0] = (UINT16)numnewsides;
-
-					if (ld->sidenum[1] == j)
-						ld->sidenum[1] = (UINT16)numnewsides;
-				}
-				sides[j].sector = NULL; // Flag for deletion
-			}
-		}
-		numnewsides++;
-	}
-
-	// We're loading crap into this block anyhow, so no point in zeroing it out.
-	newsides = Z_Malloc(numnewsides*sizeof(*newsides), PU_LEVEL, NULL);
-
-	// Copy the sides to their new block of memory.
-	for (i = 0, z = 0; i < numsides; i++)
-	{
-		if (sides[i].sector)
-			M_Memcpy(&newsides[z++], &sides[i], sizeof(side_t));
-	}
-
-	CONS_Debug(DBG_SETUP, "P_CompressSidedefs: Old sides is %s, new sides is %s\n", sizeu1(numsides), sizeu1(numnewsides));
-
-	Z_Free(sides);
-	sides = newsides;
-	numsides = numnewsides;
-}
-
 //
 // P_LinkMapData
 // Builds sector line lists and subsector sector numbers.
@@ -2310,47 +2672,54 @@ static INT32 P_MakeBufferMD5(const char *buffer, size_t len, void *resblock)
 
 static void P_MakeMapMD5(virtres_t *virt, void *dest)
 {
-	unsigned char linemd5[16];
-	unsigned char sectormd5[16];
-	unsigned char thingmd5[16];
-	unsigned char sidedefmd5[16];
+	virtlump_t *textmap = vres_Find(virt, "TEXTMAP");
 	unsigned char resmd5[16];
-	UINT8 i;
 
-	// Create a hash for the current map
-	// get the actual lumps!
-	virtlump_t *virtlines = vres_Find(virt, "LINEDEFS");
-	virtlump_t *virtsectors = vres_Find(virt, "SECTORS");
-	virtlump_t *virtmthings = vres_Find(virt, "THINGS");
-	virtlump_t *virtsides = vres_Find(virt, "SIDEDEFS");
+	if (textmap)
+		P_MakeBufferMD5((char*)textmap->data, textmap->size, resmd5);
+	else
+	{
+		unsigned char linemd5[16];
+		unsigned char sectormd5[16];
+		unsigned char thingmd5[16];
+		unsigned char sidedefmd5[16];
+		UINT8 i;
 
-	P_MakeBufferMD5((char*)virtlines->data, virtlines->size, linemd5);
-	P_MakeBufferMD5((char*)virtsectors->data, virtsectors->size, sectormd5);
-	P_MakeBufferMD5((char*)virtmthings->data, virtmthings->size, thingmd5);
-	P_MakeBufferMD5((char*)virtsides->data, virtsides->size, sidedefmd5);
+		// Create a hash for the current map
+		// get the actual lumps!
+		virtlump_t* virtlines   = vres_Find(virt, "LINEDEFS");
+		virtlump_t* virtsectors = vres_Find(virt, "SECTORS");
+		virtlump_t* virtmthings = vres_Find(virt, "THINGS");
+		virtlump_t* virtsides   = vres_Find(virt, "SIDEDEFS");
 
-	for (i = 0; i < 16; i++)
-		resmd5[i] = (linemd5[i] + sectormd5[i] + thingmd5[i] + sidedefmd5[i]) & 0xFF;
+		P_MakeBufferMD5((char*)virtlines->data,   virtlines->size, linemd5);
+		P_MakeBufferMD5((char*)virtsectors->data, virtsectors->size,  sectormd5);
+		P_MakeBufferMD5((char*)virtmthings->data, virtmthings->size,   thingmd5);
+		P_MakeBufferMD5((char*)virtsides->data,   virtsides->size, sidedefmd5);
+
+		for (i = 0; i < 16; i++)
+			resmd5[i] = (linemd5[i] + sectormd5[i] + thingmd5[i] + sidedefmd5[i]) & 0xFF;
+	}
 
 	M_Memcpy(dest, &resmd5, 16);
 }
 
-static void P_LoadMapFromFile(void)
+static boolean P_LoadMapFromFile(void)
 {
 	virtres_t *virt = vres_GetMap(lastloadedmaplumpnum);
 
-	P_LoadMapData(virt);
+	if (!P_LoadMapData(virt))
+		return false;
 	P_LoadMapBSP(virt);
 	P_LoadMapLUT(virt);
 
 	P_ProcessLinedefsWithSidedefs();
-	if (M_CheckParm("-compress"))
-		P_CompressSidedefs();
 	P_LinkMapData();
 
 	P_MakeMapMD5(virt, &mapmd5);
 
 	vres_Free(virt);
+	return true;
 }
 
 //
@@ -3180,7 +3549,7 @@ boolean P_LoadLevel(boolean fromnetsave)
 	// internal game map
 	maplumpname = G_BuildMapName(gamemap);
 	lastloadedmaplumpnum = W_CheckNumForName(maplumpname);
-	if (lastloadedmaplumpnum == INT16_MAX)
+	if (lastloadedmaplumpnum == LUMPERROR)
 		I_Error("Map %s not found.\n", maplumpname);
 
 	R_ReInitColormaps(mapheaderinfo[gamemap-1]->palette);
@@ -3193,8 +3562,8 @@ boolean P_LoadLevel(boolean fromnetsave)
 
 	P_MapStart();
 
-	if (lastloadedmaplumpnum)
-		P_LoadMapFromFile();
+	if (!P_LoadMapFromFile())
+		return false;
 
 	// init gravity, tag lists,
 	// anything that P_ResetDynamicSlopes/P_LoadThings needs to know
diff --git a/src/p_spec.c b/src/p_spec.c
index 4de488654f23e9aa0eecd685925d20bcb39ff007..a97d1f92ffd467389da8834a951b6c278c25558e 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -52,9 +52,6 @@ mobj_t *skyboxcenterpnts[16]; // array of MT_SKYBOX centerpoint mobjs
 // Amount (dx, dy) vector linedef is shifted right to get scroll amount
 #define SCROLL_SHIFT 5
 
-// This must be updated whenever we up the max flat size - quicker to assume rather than figuring out the sqrt of the specific flat's filesize.
-#define MAXFLATSIZE (2048<<FRACBITS)
-
 /** Animated texture descriptor
   * This keeps track of an animated texture or an animated flat.
   * \sa P_UpdateSpecials, P_InitPicAnims, animdef_t
diff --git a/src/p_spec.h b/src/p_spec.h
index 630bcb3de1045cdc9eec2077a6972c0662167bea..4b64fe05d9bed944511f852b84f70cf86db000a9 100644
--- a/src/p_spec.h
+++ b/src/p_spec.h
@@ -27,6 +27,9 @@ extern mobj_t *skyboxcenterpnts[16]; // array of MT_SKYBOX centerpoint mobjs
 //
 #define GETSECSPECIAL(i,j) ((i >> ((j-1)*4))&15)
 
+// This must be updated whenever we up the max flat size - quicker to assume rather than figuring out the sqrt of the specific flat's filesize.
+#define MAXFLATSIZE (2048<<FRACBITS)
+
 // at game start
 void P_InitPicAnims(void);
 
diff --git a/src/r_things.c b/src/r_things.c
index b9871c06bf155e478b2dd804eeb5e5a7089e784d..927d199a54b361bc836ec4e9bc15e886010bf1d0 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -987,7 +987,7 @@ static void R_DrawPrecipitationVisSprite(vissprite_t *vis)
 
 //
 // R_SplitSprite
-// runs through a sector's lightlist and
+// runs through a sector's lightlist and Knuckles
 static void R_SplitSprite(vissprite_t *sprite)
 {
 	INT32 i, lightnum, lindex;
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index 8065162929001c4e92de7ad75bac7dc5278a51ab..e6a40327be1cbbe1efcfd56abe0ed0b9322362b8 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -67,6 +67,7 @@
 #include "../s_sound.h"
 #include "../i_joy.h"
 #include "../st_stuff.h"
+#include "../hu_stuff.h"
 #include "../g_game.h"
 #include "../i_video.h"
 #include "../console.h"
@@ -99,6 +100,7 @@ boolean highcolor = false;
 // synchronize page flipping with screen refresh
 consvar_t cv_vidwait = {"vid_wait", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 static consvar_t cv_stretch = {"stretch", "Off", CV_SAVE|CV_NOSHOWHELP, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+static consvar_t cv_alwaysgrabmouse = {"alwaysgrabmouse", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 UINT8 graphics_started = 0; // Is used in console.c and screen.c
 
@@ -108,6 +110,7 @@ static SDL_bool disable_fullscreen = SDL_FALSE;
 #define USE_FULLSCREEN (disable_fullscreen||!allow_fullscreen)?0:cv_fullscreen.value
 static SDL_bool disable_mouse = SDL_FALSE;
 #define USE_MOUSEINPUT (!disable_mouse && cv_usemouse.value && havefocus)
+#define IGNORE_MOUSE (!cv_alwaysgrabmouse.value && (menuactive || paused || con_destlines || chat_on || gamestate != GS_LEVEL))
 #define MOUSE_MENU false //(!disable_mouse && cv_usemouse.value && menuactive && !USE_FULLSCREEN)
 #define MOUSEBUTTONS_MAX MOUSEBUTTONS
 
@@ -378,12 +381,15 @@ static void SDLdoUngrabMouse(void)
 void SDLforceUngrabMouse(void)
 {
 	if (SDL_WasInit(SDL_INIT_VIDEO)==SDL_INIT_VIDEO && window != NULL)
-	{
-		SDL_ShowCursor(SDL_ENABLE);
-		SDL_SetWindowGrab(window, SDL_FALSE);
-		wrapmouseok = SDL_FALSE;
-		SDL_SetRelativeMouseMode(SDL_FALSE);
-	}
+		SDLdoUngrabMouse();
+}
+
+void I_UpdateMouseGrab(void)
+{
+	if (SDL_WasInit(SDL_INIT_VIDEO) == SDL_INIT_VIDEO && window != NULL
+	&& SDL_GetMouseFocus() == window && SDL_GetKeyboardFocus() == window
+	&& !IGNORE_MOUSE)
+		SDLdoGrabMouse();
 }
 
 static void VID_Command_NumModes_f (void)
@@ -590,7 +596,7 @@ static void Impl_HandleWindowEvent(SDL_WindowEvent evt)
 		}
 		//else firsttimeonmouse = SDL_FALSE;
 
-		if (USE_MOUSEINPUT)
+		if (USE_MOUSEINPUT && !IGNORE_MOUSE)
 			SDLdoGrabMouse();
 	}
 	else if (!mousefocus && !kbfocus)
@@ -637,11 +643,14 @@ static void Impl_HandleKeyboardEvent(SDL_KeyboardEvent evt, Uint32 type)
 
 static void Impl_HandleMouseMotionEvent(SDL_MouseMotionEvent evt)
 {
+	static boolean firstmove = true;
+
 	if (USE_MOUSEINPUT)
 	{
-		if ((SDL_GetMouseFocus() != window && SDL_GetKeyboardFocus() != window))
+		if ((SDL_GetMouseFocus() != window && SDL_GetKeyboardFocus() != window) || (IGNORE_MOUSE && !firstmove))
 		{
 			SDLdoUngrabMouse();
+			firstmove = false;
 			return;
 		}
 
@@ -655,6 +664,7 @@ static void Impl_HandleMouseMotionEvent(SDL_MouseMotionEvent evt)
 				mousemovey += -evt.yrel;
 				SDL_SetWindowGrab(window, SDL_TRUE);
 			}
+			firstmove = false;
 			return;
 		}
 
@@ -662,6 +672,7 @@ static void Impl_HandleMouseMotionEvent(SDL_MouseMotionEvent evt)
 		// of the screen then ignore it.
 		if ((evt.x == realwidth/2) && (evt.y == realheight/2))
 		{
+			firstmove = false;
 			return;
 		}
 
@@ -674,6 +685,8 @@ static void Impl_HandleMouseMotionEvent(SDL_MouseMotionEvent evt)
 			SDLdoGrabMouse();
 		}
 	}
+
+	firstmove = false;
 }
 
 static void Impl_HandleMouseButtonEvent(SDL_MouseButtonEvent evt, Uint32 type)
@@ -687,7 +700,7 @@ static void Impl_HandleMouseButtonEvent(SDL_MouseButtonEvent evt, Uint32 type)
 	// this apparently makes a mouse button down event but not a mouse button up event,
 	// resulting in whatever key was pressed down getting "stuck" if we don't ignore it.
 	// -- Monster Iestyn (28/05/18)
-	if (SDL_GetMouseFocus() != window)
+	if (SDL_GetMouseFocus() != window || IGNORE_MOUSE)
 		return;
 
 	/// \todo inputEvent.button.which
@@ -1069,7 +1082,7 @@ void I_StartupMouse(void)
 	}
 	else
 		firsttimeonmouse = SDL_FALSE;
-	if (cv_usemouse.value)
+	if (cv_usemouse.value && !IGNORE_MOUSE)
 		SDLdoGrabMouse();
 	else
 		SDLdoUngrabMouse();
@@ -1614,6 +1627,7 @@ void I_StartupGraphics(void)
 	COM_AddCommand ("vid_mode", VID_Command_Mode_f);
 	CV_RegisterVar (&cv_vidwait);
 	CV_RegisterVar (&cv_stretch);
+	CV_RegisterVar (&cv_alwaysgrabmouse);
 	disable_mouse = M_CheckParm("-nomouse");
 	disable_fullscreen = M_CheckParm("-win") ? 1 : 0;
 
@@ -1702,12 +1716,7 @@ void I_StartupGraphics(void)
 	SDL_RaiseWindow(window);
 
 	if (mousegrabok && !disable_mouse)
-	{
-		SDL_ShowCursor(SDL_DISABLE);
-		SDL_SetRelativeMouseMode(SDL_TRUE);
-		wrapmouseok = SDL_TRUE;
-		SDL_SetWindowGrab(window, SDL_TRUE);
-	}
+		SDLdoGrabMouse();
 
 	graphics_started = true;
 }
diff --git a/src/win32/win_sys.c b/src/win32/win_sys.c
index c9fdb1c9742fa87a803aed9e88fb1578aceac904..42733c30909897d2437387dbe3b6365a096b40d8 100644
--- a/src/win32/win_sys.c
+++ b/src/win32/win_sys.c
@@ -1354,6 +1354,8 @@ getBufferedData:
 	}
 }
 
+void I_UpdateMouseGrab(void) {}
+
 // ===========================================================================================
 //                                                                       DIRECT INPUT JOYSTICK
 // ===========================================================================================