diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index 29e4970c1d3153536315e2d7151d0bf885854ad6..8175f1b9beacb2ac9e6920a66922c125d474500d 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -12,6 +12,7 @@
 
 #include "doomdef.h"
 #ifdef HAVE_BLUA
+#include "fastcmp.h"
 #include "r_defs.h"
 #include "r_local.h"
 #include "st_stuff.h" // hudinfo[]
@@ -343,6 +344,158 @@ static int libd_cachePatch(lua_State *L)
 	return 1;
 }
 
+// v.getSpritePatch(sprite, [frame, [angle]])
+static int libd_getSpritePatch(lua_State *L)
+{
+	UINT32 i; // sprite prefix
+	UINT32 frame = 0; // 'A'
+	UINT8 angle = 0;
+	spritedef_t *sprdef;
+	spriteframe_t *sprframe;
+	HUDONLY
+
+	if (lua_isnumber(L, 1)) // sprite number given, e.g. SPR_THOK
+	{
+		i = lua_tonumber(L, 1);
+		if (i >= NUMSPRITES)
+			return 0;
+	}
+	else if (lua_isstring(L, 1)) // sprite prefix name given, e.g. "THOK"
+	{
+		const char *name = lua_tostring(L, 1);
+		for (i = 0; i < NUMSPRITES; i++)
+			if (fastcmp(name, sprnames[i]))
+				break;
+		if (i >= NUMSPRITES)
+			return 0;
+	}
+	else
+		return 0;
+
+	if (i == SPR_PLAY) // Use getSprite2Patch instead!
+		return 0;
+
+	sprdef = &sprites[i];
+
+	// set frame number
+	frame = luaL_optinteger(L, 2, 0);
+	frame &= FF_FRAMEMASK; // ignore any bits that are not the actual frame, just in case
+	if (frame >= sprdef->numframes)
+		return 0;
+	// set angle number
+	sprframe = &sprdef->spriteframes[frame];
+	angle = luaL_optinteger(L, 3, 1);
+
+	// convert WAD editor angle numbers (1-8) to internal angle numbers (0-7)
+	// keep 0 the same since we'll make it default to angle 1 (which is internally 0)
+	// in case somebody didn't know that angle 0 really just maps all 8 angles to the same patch
+	if (angle != 0)
+		angle--;
+
+	if (angle >= 8) // out of range?
+		return 0;
+
+	// push both the patch and it's "flip" value
+	LUA_PushUserdata(L, W_CachePatchNum(sprframe->lumppat[angle], PU_STATIC), META_PATCH);
+	lua_pushboolean(L, (sprframe->flip & (1<<angle)) != 0);
+	return 2;
+}
+
+// v.getSprite2Patch(skin, sprite, [super?,] [frame, [angle]])
+static int libd_getSprite2Patch(lua_State *L)
+{
+	INT32 i; // skin number
+	UINT32 j; // sprite2 prefix
+	UINT32 frame = 0; // 'A'
+	UINT8 angle = 0;
+	spritedef_t *sprdef;
+	spriteframe_t *sprframe;
+	boolean super = false; // add FF_SPR2SUPER to sprite2 if true
+	HUDONLY
+
+	// get skin first!
+	if (lua_isnumber(L, 1)) // find skin by number
+	{
+		i = lua_tonumber(L, 1);
+		if (i < 0 || i >= MAXSKINS)
+			return luaL_error(L, "skin number %d out of range (0 - %d)", i, MAXSKINS-1);
+		if (i >= numskins)
+			return 0;
+	}
+	else // find skin by name
+	{
+		const char *name = luaL_checkstring(L, 1);
+		for (i = 0; i < numskins; i++)
+			if (fastcmp(skins[i].name, name))
+				break;
+		if (i >= numskins)
+			return 0;
+	}
+
+	lua_remove(L, 1); // remove skin now
+
+	if (lua_isnumber(L, 1)) // sprite number given, e.g. SPR2_STND
+	{
+		j = lua_tonumber(L, 1);
+		if (j & FF_SPR2SUPER) // e.g. SPR2_STND|FF_SPR2SUPER
+		{
+			super = true;
+			j &= ~FF_SPR2SUPER; // remove flag so the next check doesn't fail
+		}
+		if (j >= free_spr2)
+			return 0;
+	}
+	else if (lua_isstring(L, 1)) // sprite prefix name given, e.g. "STND"
+	{
+		const char *name = lua_tostring(L, 1);
+		for (j = 0; j < free_spr2; j++)
+			if (fastcmp(name, spr2names[j]))
+				break;
+		// if you want super flags you'll have to use the optional boolean following this
+		if (j >= free_spr2)
+			return 0;
+	}
+	else
+		return 0;
+
+	if (lua_isboolean(L, 2)) // optional boolean for superness
+	{
+		super = lua_toboolean(L, 2); // note: this can override FF_SPR2SUPER from sprite number
+		lua_remove(L, 2); // remove
+	}
+	// if it's not boolean then just assume it's the frame number
+
+	if (super)
+		j |= FF_SPR2SUPER;
+
+	j = P_GetSkinSprite2(&skins[i], j, NULL); // feed skin and current sprite2 through to change sprite2 used if necessary
+
+	sprdef = &skins[i].sprites[j];
+
+	// set frame number
+	frame = luaL_optinteger(L, 2, 0);
+	frame &= FF_FRAMEMASK; // ignore any bits that are not the actual frame, just in case
+	if (frame >= sprdef->numframes)
+		return 0;
+	// set angle number
+	sprframe = &sprdef->spriteframes[frame];
+	angle = luaL_optinteger(L, 3, 1);
+
+	// convert WAD editor angle numbers (1-8) to internal angle numbers (0-7)
+	// keep 0 the same since we'll make it default to angle 1 (which is internally 0)
+	// in case somebody didn't know that angle 0 really just maps all 8 angles to the same patch
+	if (angle != 0)
+		angle--;
+
+	if (angle >= 8) // out of range?
+		return 0;
+
+	// push both the patch and it's "flip" value
+	LUA_PushUserdata(L, W_CachePatchNum(sprframe->lumppat[angle], PU_STATIC), META_PATCH);
+	lua_pushboolean(L, (sprframe->flip & (1<<angle)) != 0);
+	return 2;
+}
+
 static int libd_draw(lua_State *L)
 {
 	INT32 x, y, flags;
@@ -568,6 +721,8 @@ static int libd_renderer(lua_State *L)
 static luaL_Reg lib_draw[] = {
 	{"patchExists", libd_patchExists},
 	{"cachePatch", libd_cachePatch},
+	{"getSpritePatch", libd_getSpritePatch},
+	{"getSprite2Patch", libd_getSprite2Patch},
 	{"draw", libd_draw},
 	{"drawScaled", libd_drawScaled},
 	{"drawNum", libd_drawNum},
diff --git a/src/p_map.c b/src/p_map.c
index 0339ca4a5a0700be14d14978b6b997b841721fd7..6d1760596520f13043d8e6048800fd264d127ffe 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -4332,6 +4332,11 @@ fixed_t P_FloorzAtPos(fixed_t x, fixed_t y, fixed_t z, fixed_t height)
 	sector_t *sec = R_PointInSubsector(x, y)->sector;
 	fixed_t floorz = sec->floorheight;
 
+#ifdef ESLOPE
+	if (sec->f_slope)
+		floorz = P_GetZAt(sec->f_slope, x, y);
+#endif
+
 	// Intercept the stupid 'fall through 3dfloors' bug Tails 03-17-2002
 	if (sec->ffloors)
 	{
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index f4ebb6df9fce7df919b0ba68aec1b96f7e51cc85..752c443f5b4e01d76c0daf77d98590450bfb729e 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -107,6 +107,9 @@ static SDL_bool disable_mouse = SDL_FALSE;
 // first entry in the modelist which is not bigger than MAXVIDWIDTHxMAXVIDHEIGHT
 static      INT32          firstEntry = 0;
 
+// Total mouse motion X/Y offsets
+static      INT32        mousemovex = 0, mousemovey = 0;
+
 // SDL vars
 static      SDL_Surface *vidSurface = NULL;
 static      SDL_Surface *bufSurface = NULL;
@@ -119,7 +122,8 @@ static       Uint8       BitsPerPixel = 16;
 Uint16      realwidth = BASEVIDWIDTH;
 Uint16      realheight = BASEVIDHEIGHT;
 static       SDL_bool    mousegrabok = SDL_TRUE;
-#define HalfWarpMouse(x,y) SDL_WarpMouseInWindow(window, (Uint16)(x/2),(Uint16)(y/2))
+static       SDL_bool    wrapmouseok = SDL_FALSE;
+#define HalfWarpMouse(x,y) if (wrapmouseok) SDL_WarpMouseInWindow(window, (Uint16)(x/2),(Uint16)(y/2))
 static       SDL_bool    videoblitok = SDL_FALSE;
 static       SDL_bool    exposevideo = SDL_FALSE;
 static       SDL_bool    usesdl2soft = SDL_FALSE;
@@ -348,6 +352,8 @@ static INT32 Impl_SDL_Scancode_To_Keycode(SDL_Scancode code)
 static void SDLdoUngrabMouse(void)
 {
 	SDL_SetWindowGrab(window, SDL_FALSE);
+	wrapmouseok = SDL_FALSE;
+	SDL_SetRelativeMouseMode(SDL_FALSE);
 }
 
 void SDLforceUngrabMouse(void)
@@ -355,6 +361,8 @@ void SDLforceUngrabMouse(void)
 	if (SDL_WasInit(SDL_INIT_VIDEO)==SDL_INIT_VIDEO && window != NULL)
 	{
 		SDL_SetWindowGrab(window, SDL_FALSE);
+		wrapmouseok = SDL_FALSE;
+		SDL_SetRelativeMouseMode(SDL_FALSE);
 	}
 }
 
@@ -603,36 +611,43 @@ static void Impl_HandleKeyboardEvent(SDL_KeyboardEvent evt, Uint32 type)
 
 static void Impl_HandleMouseMotionEvent(SDL_MouseMotionEvent evt)
 {
-	event_t event;
-	int wwidth, wheight;
-
 	if (USE_MOUSEINPUT)
 	{
-		SDL_GetWindowSize(window, &wwidth, &wheight);
-
 		if ((SDL_GetMouseFocus() != window && SDL_GetKeyboardFocus() != window))
 		{
 			SDLdoUngrabMouse();
 			return;
 		}
 
-		if ((evt.x == realwidth/2) && (evt.y == realheight/2))
+		// If using relative mouse mode, don't post an event_t just now,
+		// add on the offsets so we can make an overall event later.
+		if (SDL_GetRelativeMouseMode())
 		{
+			if (SDL_GetMouseFocus() == window && SDL_GetKeyboardFocus() == window)
+			{
+				mousemovex +=  evt.xrel;
+				mousemovey += -evt.yrel;
+				SDL_SetWindowGrab(window, SDL_TRUE);
+			}
 			return;
 		}
-		else
+
+		// If the event is from warping the pointer to middle
+		// of the screen then ignore it.
+		if ((evt.x == realwidth/2) && (evt.y == realheight/2))
 		{
-			event.data2 = (INT32)lround((evt.xrel) * ((float)wwidth / (float)realwidth));
-			event.data3 = (INT32)lround(-evt.yrel * ((float)wheight / (float)realheight));
+			return;
 		}
 
-		event.type = ev_mouse;
-
+		// Don't send an event_t if not in relative mouse mode anymore,
+		// just grab and set relative mode
+		// this fixes the stupid camera jerk on mouse entering bug
+		// -- Monster Iestyn
 		if (SDL_GetMouseFocus() == window && SDL_GetKeyboardFocus() == window)
 		{
-			D_PostEvent(&event);
 			SDL_SetWindowGrab(window, SDL_TRUE);
-			HalfWarpMouse(wwidth, wheight);
+			if (SDL_SetRelativeMouseMode(SDL_TRUE) == 0) // already warps mouse if successful
+				wrapmouseok = SDL_TRUE; // TODO: is wrapmouseok or HalfWarpMouse needed anymore?
 		}
 	}
 }
@@ -780,13 +795,15 @@ void I_GetEvent(void)
 	SDL_Event evt;
 	// We only want the first motion event,
 	// otherwise we'll end up catching the warp back to center.
-	int mouseMotionOnce = 0;
+	//int mouseMotionOnce = 0;
 
 	if (!graphics_started)
 	{
 		return;
 	}
 
+	mousemovex = mousemovey = 0;
+
 	while (SDL_PollEvent(&evt))
 	{
 		switch (evt.type)
@@ -799,8 +816,9 @@ void I_GetEvent(void)
 				Impl_HandleKeyboardEvent(evt.key, evt.type);
 				break;
 			case SDL_MOUSEMOTION:
-				if (!mouseMotionOnce) Impl_HandleMouseMotionEvent(evt.motion);
-				mouseMotionOnce = 1;
+				//if (!mouseMotionOnce)
+				Impl_HandleMouseMotionEvent(evt.motion);
+				//mouseMotionOnce = 1;
 				break;
 			case SDL_MOUSEBUTTONUP:
 			case SDL_MOUSEBUTTONDOWN:
@@ -823,6 +841,20 @@ void I_GetEvent(void)
 		}
 	}
 
+	// Send all relative mouse movement as one single mouse event.
+	if (mousemovex || mousemovey)
+	{
+		event_t event;
+		int wwidth, wheight;
+		SDL_GetWindowSize(window, &wwidth, &wheight);
+		//SDL_memset(&event, 0, sizeof(event_t));
+		event.type = ev_mouse;
+		event.data1 = 0;
+		event.data2 = (INT32)lround(mousemovex * ((float)wwidth / (float)realwidth));
+		event.data3 = (INT32)lround(mousemovey * ((float)wheight / (float)realheight));
+		D_PostEvent(&event);
+	}
+
 	// In order to make wheels act like buttons, we have to set their state to Up.
 	// This is because wheel messages don't have an up/down state.
 	gamekeydown[KEY_MOUSEWHEELDOWN] = gamekeydown[KEY_MOUSEWHEELUP] = 0;
@@ -836,7 +868,9 @@ void I_StartupMouse(void)
 		return;
 
 	if (!firsttimeonmouse)
+	{
 		HalfWarpMouse(realwidth, realheight); // warp to center
+	}
 	else
 		firsttimeonmouse = SDL_FALSE;
 	if (cv_usemouse.value)