diff --git a/src/deh_tables.c b/src/deh_tables.c
index f113f6b19c8eb8b0a9c511406778db8bebd2e81d..15991a936199e0059d5082ef8b79f0b0b509ee43 100644
--- a/src/deh_tables.c
+++ b/src/deh_tables.c
@@ -4483,6 +4483,8 @@ const char *const PLAYERFLAG_LIST[] = {
 	"CANCARRY", // Can carry?
 	"FINISHED",
 
+	"SHIELDDOWN", // Shield has been pressed.
+
 	NULL // stop loop here.
 };
 
diff --git a/src/filesrch.c b/src/filesrch.c
index ea19e00cdc0abd38d3fd1b869751faad316b0b83..67a2e8976f88548c0f6f77b2143fbef6888d820f 100644
--- a/src/filesrch.c
+++ b/src/filesrch.c
@@ -444,12 +444,11 @@ filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *want
 		strcpy(&searchpath[searchpathindex[depthleft]],dent->d_name);
 
 #if defined(__linux__) || defined(__FreeBSD__)
-		if (dent->d_type == DT_UNKNOWN)
-			if (lstat(searchpath,&fsstat) == 0 && S_ISDIR(fsstat.st_mode))
+		if (dent->d_type == DT_UNKNOWN || dent->d_type == DT_LNK)
+			if (stat(searchpath,&fsstat) == 0 && S_ISDIR(fsstat.st_mode))
 				dent->d_type = DT_DIR;
 
 		// Linux and FreeBSD has a special field for file type on dirent, so use that to speed up lookups.
-		// FIXME: should we also follow symlinks?
 		if (dent->d_type == DT_DIR && depthleft)
 #else
 		if (stat(searchpath,&fsstat) < 0) // do we want to follow symlinks? if not: change it to lstat
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 5dc0af89423547930e029503439b93154bde0473..87c5f8e328aa8e918d942cf9b1b506a0db9c2111 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -2880,7 +2880,7 @@ static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale)
 	}
 
 	HWR_Lighting(&sSurf, 0, colormap);
-	sSurf.PolyColor.s.alpha = alpha;
+	sSurf.PolyColor.s.alpha = FixedMul(thing->alpha, alpha);
 
 	if (HWR_UseShader())
 	{
@@ -3054,11 +3054,16 @@ static void HWR_SplitSprite(gl_vissprite_t *spr)
 	// baseWallVerts is used to know the final shape to easily get the vertex
 	// co-ordinates
 	memcpy(wallVerts, baseWallVerts, sizeof(baseWallVerts));
+	
+	fixed_t newalpha = spr->mobj->alpha;
 
 	// if sprite has linkdraw, then dont write to z-buffer (by not using PF_Occlude)
 	// this will result in sprites drawn afterwards to be drawn on top like intended when using linkdraw.
 	if ((spr->mobj->flags2 & MF2_LINKDRAW) && spr->mobj->tracer)
+	{
+		newalpha = spr->mobj->tracer->alpha;
 		occlusion = 0;
+	}
 	else
 		occlusion = PF_Occlude;
 
@@ -3094,6 +3099,8 @@ static void HWR_SplitSprite(gl_vissprite_t *spr)
 		blend = HWR_GetBlendModeFlag(blendmode)|occlusion;
 		if (!occlusion) use_linkdraw_hack = true;
 	}
+	
+	Surf.PolyColor.s.alpha = FixedMul(newalpha, Surf.PolyColor.s.alpha);
 
 	if (HWR_UseShader())
 	{
@@ -3543,11 +3550,15 @@ static void HWR_DrawSprite(gl_vissprite_t *spr)
 		FBITFIELD blend = 0;
 		FBITFIELD occlusion;
 		boolean use_linkdraw_hack = false;
+		fixed_t newalpha = spr->mobj->alpha;
 
 		// if sprite has linkdraw, then dont write to z-buffer (by not using PF_Occlude)
 		// this will result in sprites drawn afterwards to be drawn on top like intended when using linkdraw.
 		if ((spr->mobj->flags2 & MF2_LINKDRAW) && spr->mobj->tracer)
+		{
 			occlusion = 0;
+			newalpha = spr->mobj->tracer->alpha;
+		}
 		else
 			occlusion = PF_Occlude;
 
@@ -3583,6 +3594,8 @@ static void HWR_DrawSprite(gl_vissprite_t *spr)
 			blend = HWR_GetBlendModeFlag(blendmode)|occlusion;
 			if (!occlusion) use_linkdraw_hack = true;
 		}
+		
+		Surf.PolyColor.s.alpha = FixedMul(newalpha, Surf.PolyColor.s.alpha);
 
 		if (spr->renderflags & RF_SHADOWEFFECTS)
 		{
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index a1fbdaa14014da7ed48119ecddf29a4383f4f10a..2616d4085c4c9e8fa81e6ce82bfa2f30f072f68f 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -1289,6 +1289,11 @@ boolean HWR_DrawModel(gl_vissprite_t *spr)
 		// Apparently people don't like jump frames like that, so back it goes
 		//if (tics > durs)
 			//durs = tics;
+		
+		// Make linkdraw objects use their tracer's alpha value
+		fixed_t newalpha = spr->mobj->alpha;
+		if ((spr->mobj->flags2 & MF2_LINKDRAW) && spr->mobj->tracer)
+			newalpha = spr->mobj->tracer->alpha;
 
 		INT32 blendmode;
 		if (spr->mobj->frame & FF_BLENDMASK)
@@ -1303,6 +1308,8 @@ boolean HWR_DrawModel(gl_vissprite_t *spr)
 			Surf.PolyColor.s.alpha = (spr->mobj->flags2 & MF2_SHADOW) ? 0x40 : 0xff;
 			Surf.PolyFlags = HWR_GetBlendModeFlag(blendmode);
 		}
+		
+		Surf.PolyColor.s.alpha = FixedMul(newalpha, Surf.PolyColor.s.alpha);
 
 		// don't forget to enable the depth test because we can't do this
 		// like before: model polygons are not sorted
diff --git a/src/hardware/hw_shaders.c b/src/hardware/hw_shaders.c
index 36cbb5db949c7fae0e28c82b6c0fcb6bd1dd3a73..ee1e2acdf58cd73d810c1884cfc5ffe3e4797389 100644
--- a/src/hardware/hw_shaders.c
+++ b/src/hardware/hw_shaders.c
@@ -448,6 +448,101 @@ void HWR_LoadAllCustomShaders(void)
 		HWR_LoadCustomShadersFromFile(i, W_FileHasFolders(wadfiles[i]));
 }
 
+static const char version_directives[][14] = {
+	"#version 330\n",
+	"#version 150\n",
+	"#version 140\n",
+	"#version 130\n",
+	"#version 120\n",
+	"#version 110\n",
+};
+
+static boolean HWR_VersionDirectiveExists(const char* source)
+{
+    return strncmp(source, "#version", 8) == 0;
+}
+
+static char* HWR_PrependVersionDirective(const char* source, UINT32 version_index)
+{
+	const UINT32 version_len = sizeof(version_directives[version_index]) - 1;
+	const UINT32 source_len = strlen(source);
+
+	char* result = Z_Malloc(source_len + version_len + 1, PU_STATIC, NULL);
+	strcpy(result, version_directives[version_index]);
+	strcpy(result + version_len, source);
+
+	return result;
+}
+
+static void HWR_ReplaceVersionInplace(char* shader, UINT32 version_index)
+{
+	shader[9] = version_directives[version_index][9];
+	shader[10] = version_directives[version_index][10];
+	shader[11] = version_directives[version_index][11];
+}
+
+static boolean HWR_CheckVersionDirectives(const char* vert, const char* frag)
+{
+	return HWR_VersionDirectiveExists(vert) && HWR_VersionDirectiveExists(frag);
+}
+
+static void HWR_TryToCompileShaderWithImplicitVersion(INT32 shader_index, INT32 shaderxlat_id)
+{
+	char* vert_shader = gl_shaders[shader_index].vertex;
+	char* frag_shader = gl_shaders[shader_index].fragment;
+
+	boolean vert_shader_version_exists = HWR_VersionDirectiveExists(vert_shader);
+	boolean frag_shader_version_exists = HWR_VersionDirectiveExists(frag_shader);
+
+	if(!vert_shader_version_exists) {
+		CONS_Alert(CONS_WARNING, "HWR_LoadCustomShadersFromFile: vertex shader '%s' is missing a #version directive\n", HWR_GetShaderName(shaderxlat_id));
+	}
+
+	if(!frag_shader_version_exists) {
+		CONS_Alert(CONS_WARNING, "HWR_LoadCustomShadersFromFile: fragment shader '%s' is missing a #version directive\n", HWR_GetShaderName(shaderxlat_id));
+	}
+
+	// try to compile as is
+	HWR_CompileShader(shader_index);
+	if (gl_shaders[shader_index].compiled)
+		return;
+
+	// try each version directive
+	for(UINT32 i = 0; i < sizeof(version_directives) / sizeof(version_directives[0]); ++i) {
+		CONS_Alert(CONS_NOTICE, "HWR_TryToCompileShaderWithImplicitVersion: Trying %s\n", version_directives[i]);
+
+		if(!vert_shader_version_exists) {
+			// first time reallocation would have to be made
+
+			if(i == 0) {
+				void* old = (void*)gl_shaders[shader_index].vertex;
+				vert_shader = gl_shaders[shader_index].vertex = HWR_PrependVersionDirective(vert_shader, i);
+				Z_Free(old);
+			} else {
+				HWR_ReplaceVersionInplace(vert_shader, i);
+			}
+		}
+
+		if(!frag_shader_version_exists) {
+			if(i == 0) {
+				void* old = (void*)gl_shaders[shader_index].fragment;
+				frag_shader = gl_shaders[shader_index].fragment = HWR_PrependVersionDirective(frag_shader, i);
+				Z_Free(old);
+			} else {
+				HWR_ReplaceVersionInplace(frag_shader, i);
+			}
+		}
+
+		HWR_CompileShader(shader_index);
+		if (gl_shaders[shader_index].compiled) {
+			CONS_Alert(CONS_NOTICE, "HWR_TryToCompileShaderWithImplicitVersion: Compiled with %s\n",
+					   version_directives[i]);
+			CONS_Alert(CONS_WARNING, "Implicit GLSL version is used. Correct behavior is not guaranteed\n");
+			return;
+		}
+	}
+}
+
 void HWR_LoadCustomShadersFromFile(UINT16 wadnum, boolean PK3)
 {
 	UINT16 lump;
@@ -610,7 +705,13 @@ skip_field:
 				gl_shaders[shader_index].fragment = Z_StrDup(gl_shadersources[i].fragment);
 			if (!gl_shaders[shader_index].vertex)
 				gl_shaders[shader_index].vertex = Z_StrDup(gl_shadersources[i].vertex);
-			HWR_CompileShader(shader_index);
+
+			if(!HWR_CheckVersionDirectives(gl_shaders[shader_index].vertex, gl_shaders[shader_index].fragment)) {
+				HWR_TryToCompileShaderWithImplicitVersion(shader_index, i);
+			} else {
+				HWR_CompileShader(shader_index);
+			}
+
 			if (!gl_shaders[shader_index].compiled)
 				CONS_Alert(CONS_ERROR, "HWR_LoadCustomShadersFromFile: A compilation error occured for the %s shader in file %s. See the console messages above for more information.\n", shaderxlat[i].type, wadfiles[wadnum]->filename);
 		}
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 6d2c8661ee977603dc0f4013bdfdb43ef153d5b4..304c866c9c9ee6c8fc78b0c860a8a0ed09d83cb3 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -3878,7 +3878,7 @@ static int lib_gAddPlayer(lua_State *L)
 	player_t *newplayer;
 	SINT8 skinnum = 0, bot;
 
-	for (i = 0; i < MAXPLAYERS; i++)
+	for (i = 1; i < MAXPLAYERS; i++)
 	{
 		if (!playeringame[i])
 			break;
diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c
index de7065790abb137e82f505c961215ca6acb10429..eadcbe33b0b676447f43f6479693046e507c02b6 100644
--- a/src/lua_mobjlib.c
+++ b/src/lua_mobjlib.c
@@ -69,6 +69,7 @@ enum mobj_e {
 	mobj_color,
 	mobj_translation,
 	mobj_blendmode,
+	mobj_alpha,
 	mobj_bnext,
 	mobj_bprev,
 	mobj_hnext,
@@ -150,6 +151,7 @@ static const char *const mobj_opt[] = {
 	"color",
 	"translation",
 	"blendmode",
+	"alpha",
 	"bnext",
 	"bprev",
 	"hnext",
@@ -354,6 +356,9 @@ static int mobj_get(lua_State *L)
 	case mobj_blendmode:
 		lua_pushinteger(L, mo->blendmode);
 		break;
+	case mobj_alpha:
+		lua_pushfixed(L, mo->alpha);
+		break;
 	case mobj_bnext:
 		if (mo->blocknode && mo->blocknode->bnext) {
 			LUA_PushUserdata(L, mo->blocknode->bnext->mobj, META_MOBJ);
@@ -733,6 +738,16 @@ static int mobj_set(lua_State *L)
 		mo->blendmode = blendmode;
 		break;
 	}
+	case mobj_alpha:
+	{
+		fixed_t alpha = luaL_checkfixed(L, 3);
+		if (alpha < 0)
+			alpha = 0;
+		else if (alpha > FRACUNIT)
+			alpha = FRACUNIT;
+		mo->alpha = alpha;
+		break;
+	}
 	case mobj_bnext:
 		return NOSETPOS;
 	case mobj_bprev:
diff --git a/src/m_menu.c b/src/m_menu.c
index c4fccb372c376e72f15e54cc1410999f3648f878..7d4049df235adf33046a3883916e05ed41710ca9 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -8610,7 +8610,7 @@ static void M_DrawLoadGameData(void)
 				if (savegameinfo[savetodraw].lives == -42)
 					col = 26;
 				else if (savegameinfo[savetodraw].botskin == 3) // & knuckles
-					col = 105;
+					col = 106;
 				else if (savegameinfo[savetodraw].botskin) // tailsbot or custom
 					col = 134;
 				else
diff --git a/src/netcode/d_clisrv.c b/src/netcode/d_clisrv.c
index 2bf9d2dde7cb60266687282748142af5e676c917..d46e75c3f79dc475fa969cb6bdc48c06a4a938a8 100644
--- a/src/netcode/d_clisrv.c
+++ b/src/netcode/d_clisrv.c
@@ -114,6 +114,7 @@ static CV_PossibleValue_t playbackspeed_cons_t[] = {{1, "MIN"}, {10, "MAX"}, {0,
 consvar_t cv_playbackspeed = CVAR_INIT ("playbackspeed", "1", 0, playbackspeed_cons_t, NULL);
 
 consvar_t cv_idletime = CVAR_INIT ("idletime", "0", CV_SAVE, CV_Unsigned, NULL);
+consvar_t cv_idlespectate = CVAR_INIT ("idlespectate", "On", CV_SAVE, CV_OnOff, NULL);
 consvar_t cv_dedicatedidletime = CVAR_INIT ("dedicatedidletime", "10", CV_SAVE, CV_Unsigned, NULL);
 
 consvar_t cv_httpsource = CVAR_INIT ("http_source", "", CV_SAVE, NULL, NULL);
@@ -1362,7 +1363,7 @@ static void IdleUpdate(void)
 
 	for (i = 1; i < MAXPLAYERS; i++)
 	{
-		if (cv_idletime.value && playeringame[i] && playernode[i] != UINT8_MAX && !players[i].quittime && !players[i].spectator && !players[i].bot && !IsPlayerAdmin(i) && i != serverplayer && gamestate == GS_LEVEL)
+		if (cv_idletime.value && playeringame[i] && playernode[i] != UINT8_MAX && !players[i].quittime && !players[i].spectator && !players[i].bot && !IsPlayerAdmin(i) && i != serverplayer && gamestate == GS_LEVEL && !(players[i].pflags & PF_FINISHED))
 		{
 			if (players[i].cmd.forwardmove || players[i].cmd.sidemove || players[i].cmd.buttons)
 				players[i].lastinputtime = 0;
@@ -1372,7 +1373,21 @@ static void IdleUpdate(void)
 			if (players[i].lastinputtime > (tic_t)cv_idletime.value * TICRATE * 60)
 			{
 				players[i].lastinputtime = 0;
-				SendKick(i, KICK_MSG_IDLE | KICK_MSG_KEEP_BODY);
+				if (cv_idlespectate.value && G_GametypeHasSpectators())
+				{
+					changeteam_union NetPacket;
+					UINT16 usvalue;
+					NetPacket.value.l = NetPacket.value.b = 0;
+					NetPacket.packet.newteam = 0;
+					NetPacket.packet.playernum = i;
+					NetPacket.packet.verification = true; // This signals that it's a server change
+					usvalue = SHORT(NetPacket.value.l|NetPacket.value.b);
+					SendNetXCmd(XD_TEAMCHANGE, &usvalue, sizeof(usvalue));
+				}
+				else
+				{
+					SendKick(i, KICK_MSG_IDLE | KICK_MSG_KEEP_BODY);
+				}
 			}
 		}
 		else
diff --git a/src/netcode/d_clisrv.h b/src/netcode/d_clisrv.h
index 5aac4693d2fdd166cee7a796166ba7c798e183c8..342173dff5bf575a762433a4cb2ad753a61685a0 100644
--- a/src/netcode/d_clisrv.h
+++ b/src/netcode/d_clisrv.h
@@ -73,7 +73,7 @@ extern UINT32 realpingtable[MAXPLAYERS];
 extern UINT32 playerpingtable[MAXPLAYERS];
 extern tic_t servermaxping;
 
-extern consvar_t cv_netticbuffer, cv_resynchattempts, cv_blamecfail, cv_playbackspeed, cv_idletime, cv_dedicatedidletime;
+extern consvar_t cv_netticbuffer, cv_resynchattempts, cv_blamecfail, cv_playbackspeed, cv_idletime, cv_idlespectate, cv_dedicatedidletime;
 extern consvar_t cv_httpsource;
 
 // Used in d_net, the only dependence
diff --git a/src/netcode/d_netcmd.c b/src/netcode/d_netcmd.c
index f7b98c4dbaff55d58481b5c439623a92fcd4e4c4..4134c633e5a32e23e6be085a32300bb308a694df 100644
--- a/src/netcode/d_netcmd.c
+++ b/src/netcode/d_netcmd.c
@@ -620,6 +620,7 @@ void D_RegisterServerCommands(void)
 	CV_RegisterVar(&cv_blamecfail);
 	CV_RegisterVar(&cv_dedicatedidletime);
 	CV_RegisterVar(&cv_idletime);
+	CV_RegisterVar(&cv_idlespectate);
 	CV_RegisterVar(&cv_httpsource);
 
 	COM_AddCommand("ping", Command_Ping_f, COM_LUA);
@@ -2510,11 +2511,11 @@ static void MutePlayer(boolean mute)
 
 	if (COM_Argc() < 2)
 	{
-		CONS_Printf(M_GetText("muteplayer <playernum>: mute a player\n"));
+		CONS_Printf(M_GetText("muteplayer <playername/playernum>: mute a player\n"));
 		return;
 	}
 
-	data[0] = atoi(COM_Argv(1));
+	data[0] = nametonum(COM_Argv(1));
 	if (data[0] >= MAXPLAYERS || !playeringame[data[0]])
 	{
 		CONS_Alert(CONS_NOTICE, M_GetText("There is no player %u!\n"), (unsigned int)data[0]);
@@ -2603,11 +2604,11 @@ static void Command_ServerTeamChange_f(void)
 	if (COM_Argc() < 3)
 	{
 		if (G_TagGametype())
-			CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: switch player to a new team (%s)\n"), "it, notit, playing, or spectator");
+			CONS_Printf(M_GetText("serverchangeteam <playername/playernum> <team>: switch player to a new team (%s)\n"), "it, notit, playing, or spectator");
 		else if (G_GametypeHasTeams())
-			CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: switch player to a new team (%s)\n"), "red, blue or spectator");
+			CONS_Printf(M_GetText("serverchangeteam <playername/playernum> <team>: switch player to a new team (%s)\n"), "red, blue or spectator");
 		else if (G_GametypeHasSpectators())
-			CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: switch player to a new team (%s)\n"), "spectator or playing");
+			CONS_Printf(M_GetText("serverchangeteam <playername/playernum> <team>: switch player to a new team (%s)\n"), "spectator or playing");
 		else
 			CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n"));
 		return;
@@ -2655,19 +2656,19 @@ static void Command_ServerTeamChange_f(void)
 	if (error)
 	{
 		if (G_TagGametype())
-			CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: switch player to a new team (%s)\n"), "it, notit, playing, or spectator");
+			CONS_Printf(M_GetText("serverchangeteam <playername/playernum> <team>: switch player to a new team (%s)\n"), "it, notit, playing, or spectator");
 		else if (G_GametypeHasTeams())
-			CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: switch player to a new team (%s)\n"), "red, blue or spectator");
+			CONS_Printf(M_GetText("serverchangeteam <playername/playernum> <team>: switch player to a new team (%s)\n"), "red, blue or spectator");
 		else if (G_GametypeHasSpectators())
-			CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: switch player to a new team (%s)\n"), "spectator or playing");
+			CONS_Printf(M_GetText("serverchangeteam <playername/playernum> <team>: switch player to a new team (%s)\n"), "spectator or playing");
 		return;
 	}
 
-	NetPacket.packet.playernum = atoi(COM_Argv(1));
+	NetPacket.packet.playernum = nametonum(COM_Argv(1));
 
-	if (!playeringame[NetPacket.packet.playernum])
+	if (NetPacket.packet.playernum == -1 || !playeringame[NetPacket.packet.playernum])
 	{
-		CONS_Alert(CONS_NOTICE, M_GetText("There is no player %d!\n"), NetPacket.packet.playernum);
+		CONS_Alert(CONS_NOTICE, M_GetText("There is no player %s!\n"), COM_Argv(1));
 		return;
 	}
 
@@ -3106,13 +3107,16 @@ static void Command_Verify_f(void)
 
 	if (COM_Argc() != 2)
 	{
-		CONS_Printf(M_GetText("promote <playernum>: give admin privileges to a player\n"));
+		CONS_Printf(M_GetText("promote <playername/playernum>: give admin privileges to a player\n"));
 		return;
 	}
 
-	strlcpy(buf, COM_Argv(1), sizeof (buf));
-
-	playernum = atoi(buf);
+	playernum = nametonum(COM_Argv(1));
+	if (playernum == -1)
+	{
+		CONS_Alert(CONS_NOTICE, M_GetText("There is no player %s!\n"), COM_Argv(1));
+		return;
+	}
 
 	temp = buf;
 
@@ -3156,13 +3160,16 @@ static void Command_RemoveAdmin_f(void)
 
 	if (COM_Argc() != 2)
 	{
-		CONS_Printf(M_GetText("demote <playernum>: remove admin privileges from a player\n"));
+		CONS_Printf(M_GetText("demote <playername/playernum>: remove admin privileges from a player\n"));
 		return;
 	}
 
-	strlcpy(buf, COM_Argv(1), sizeof(buf));
-
-	playernum = atoi(buf);
+	playernum = nametonum(COM_Argv(1));
+	if (playernum == -1)
+	{
+		CONS_Alert(CONS_NOTICE, M_GetText("There is no player %s!\n"), COM_Argv(1));
+		return;
+	}
 
 	temp = buf;
 
diff --git a/src/p_inter.c b/src/p_inter.c
index e73cd1fce675ee17721e6236e7dc764ede8e9d69..cbd56183df855ed0d8afc9e308bea5e05a7d0fee 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -3786,6 +3786,8 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 
 		if (player->powers[pw_carry] == CR_NIGHTSMODE) // NiGHTS damage handling
 		{
+			if (player->powers[pw_flashing])
+				return false;
 			if (!force)
 			{
 				if (source == target)
@@ -3803,6 +3805,10 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 
 		if (G_IsSpecialStage(gamemap) && !(damagetype & DMG_DEATHMASK))
 		{
+			if (player->powers[pw_flashing])
+				return false;
+			if (LUA_HookMobjDamage(target, inflictor, source, damage, damagetype))
+				return true;
 			P_SpecialStageDamage(player, inflictor, source);
 			return true;
 		}
diff --git a/src/p_map.c b/src/p_map.c
index 668a8ab6413ccda17adccf994c18e7f2dc58f58d..b8584205b8082cd284a5aae81382882781d85ab6 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -1258,8 +1258,9 @@ static unsigned PIT_DoCheckThing(mobj_t *thing)
 
 		if (tmthing->type != MT_SHELL && tmthing->target && tmthing->target->type == thing->type)
 		{
-			// Don't hit same species as originator.
-			if (thing == tmthing->target)
+			// Don't hit yourself, and if a player, don't hit bots
+			if (thing == tmthing->target
+				|| (thing->player && tmthing->target->player && (thing->player->bot == BOT_2PAI || thing->player->bot == BOT_2PHUMAN)))
 				return CHECKTHING_IGNORE;
 
 			if (thing->type != MT_PLAYER)
@@ -3960,23 +3961,25 @@ papercollision:
 		mo->momy = tmymove;
 	}
 
+	const fixed_t tmradius = mo->radius > 8 ? mo->radius : 8;
+
 	do {
-		if (tmxmove > mo->radius) {
-			newx = mo->x + mo->radius;
-			tmxmove -= mo->radius;
-		} else if (tmxmove < -mo->radius) {
-			newx = mo->x - mo->radius;
-			tmxmove += mo->radius;
+		if (tmxmove > tmradius) {
+			newx = mo->x + tmradius;
+			tmxmove -= tmradius;
+		} else if (tmxmove < -tmradius) {
+			newx = mo->x - tmradius;
+			tmxmove += tmradius;
 		} else {
 			newx = mo->x + tmxmove;
 			tmxmove = 0;
 		}
-		if (tmymove > mo->radius) {
-			newy = mo->y + mo->radius;
-			tmymove -= mo->radius;
-		} else if (tmymove < -mo->radius) {
-			newy = mo->y - mo->radius;
-			tmymove += mo->radius;
+		if (tmymove > tmradius) {
+			newy = mo->y + tmradius;
+			tmymove -= tmradius;
+		} else if (tmymove < -tmradius) {
+			newy = mo->y - tmradius;
+			tmymove += tmradius;
 		} else {
 			newy = mo->y + tmymove;
 			tmymove = 0;
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 3ca6093906b3da9725cf95f6edb3ee5b78b9cae0..1ec09ab8582adea2716db0d817b670bf4aba0d2c 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -2246,7 +2246,7 @@ boolean P_ZMovement(mobj_t *mo)
 		else if (!onground)
 			P_SlopeLaunch(mo);
 	}
-	
+
 	if (!mo->player && P_CheckDeathPitCollide(mo) && mo->health
 	&& !(mo->flags & MF_NOCLIPHEIGHT) && !(mo->flags2 & MF2_BOSSDEAD))
 	{
@@ -2939,7 +2939,7 @@ boolean P_SceneryZMovement(mobj_t *mo)
 		mo->eflags &= ~MFE_APPLYPMOMZ;
 	}
 	mo->z += mo->momz;
-	
+
 	if (!mo->player && P_CheckDeathPitCollide(mo) && mo->health
 	&& !(mo->flags & MF_NOCLIPHEIGHT) && !(mo->flags2 & MF2_BOSSDEAD))
 	{
@@ -3782,7 +3782,7 @@ static void P_PlayerMobjThinker(mobj_t *mobj)
 
 	// always do the gravity bit now, that's simpler
 	// BUT CheckPosition only if wasn't done before.
-	if (!(mobj->eflags & MFE_ONGROUND) || mobj->momz
+	if (mobj->momz
 		|| ((mobj->eflags & MFE_VERTICALFLIP) && mobj->z + mobj->height != mobj->ceilingz)
 		|| (!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z != mobj->floorz)
 		|| P_IsObjectInGoop(mobj))
@@ -3795,17 +3795,6 @@ static void P_PlayerMobjThinker(mobj_t *mobj)
 	}
 	else
 	{
-#if 0 // i don't know why this is here, it's causing a few undesired state glitches, and disabling it doesn't appear to negatively affect the game, but i don't want it gone permanently just in case some obscure bug crops up
-		if (!(mobj->player->powers[pw_carry] == CR_NIGHTSMODE)) // used for drilling
-			mobj->player->pflags &= ~PF_STARTJUMP;
-		mobj->player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE);
-		if (mobj->player->secondjump || mobj->player->powers[pw_tailsfly])
-		{
-			mobj->player->secondjump = 0;
-			mobj->player->powers[pw_tailsfly] = 0;
-			P_SetMobjState(mobj, S_PLAY_WALK);
-		}
-#endif
 		mobj->eflags &= ~MFE_JUSTHITFLOOR;
 	}
 
@@ -10355,6 +10344,7 @@ void P_MobjThinker(mobj_t *mobj)
 		case MT_GRENADEPICKUP:
 			if (mobj->health == 0) // Fading tile
 			{
+				// TODO: Maybe use mobj->alpha instead of messing with frame flags
 				INT32 value = mobj->info->damage/10;
 				value = mobj->fuse/value;
 				value = 10-value;
@@ -10700,6 +10690,7 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type, ...)
 
 	// Sprite rendering
 	mobj->blendmode = AST_TRANSLUCENT;
+	mobj->alpha = FRACUNIT;
 	mobj->spritexscale = mobj->spriteyscale = mobj->scale;
 	mobj->spritexoffset = mobj->spriteyoffset = 0;
 	mobj->floorspriteslope = NULL;
@@ -10744,7 +10735,7 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type, ...)
 
 	// Set shadowscale here, before spawn hook so that Lua can change it
 	mobj->shadowscale = P_DefaultMobjShadowScale(mobj);
-	
+
 	// A monitor can't respawn if we're not in multiplayer,
 	// or if we're in co-op and it's score or a 1up
 	if (mobj->flags & MF_MONITOR && (!(netgame || multiplayer)
diff --git a/src/p_mobj.h b/src/p_mobj.h
index 2f013a2f30fac7f7d117cb190fcbc5bd1db17e54..f281410f6ddc7ece41acc963afe63a6b23420269 100644
--- a/src/p_mobj.h
+++ b/src/p_mobj.h
@@ -313,6 +313,7 @@ typedef struct mobj_s
 
 	UINT32 renderflags; // render flags
 	INT32 blendmode; // blend mode
+	fixed_t alpha; // alpha
 	fixed_t spritexscale, spriteyscale;
 	fixed_t spritexoffset, spriteyoffset;
 	fixed_t old_spritexscale, old_spriteyscale, old_spritexscale2, old_spriteyscale2;
@@ -456,6 +457,7 @@ typedef struct precipmobj_s
 
 	UINT32 renderflags; // render flags
 	INT32 blendmode; // blend mode
+	fixed_t alpha; // alpha
 	fixed_t spritexscale, spriteyscale;
 	fixed_t spritexoffset, spriteyoffset;
 	fixed_t old_spritexscale, old_spriteyscale, old_spritexscale2, old_spriteyscale2;
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 5e4d6d0760441e6bc94c6815824b8b7e1ab38c80..219410bb63741e0ca0eb138cc57cf3323665e297 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -1746,7 +1746,8 @@ typedef enum
 	MD2_DISPOFFSET          = 1<<23,
 	MD2_DRAWONLYFORPLAYER   = 1<<24,
 	MD2_DONTDRAWFORVIEWMOBJ = 1<<25,
-	MD2_TRANSLATION         = 1<<26
+	MD2_TRANSLATION         = 1<<26,
+	MD2_ALPHA               = 1<<27
 } mobj_diff2_t;
 
 typedef enum
@@ -1989,6 +1990,8 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 		diff2 |= MD2_DONTDRAWFORVIEWMOBJ;
 	if (mobj->dispoffset != mobj->info->dispoffset)
 		diff2 |= MD2_DISPOFFSET;
+	if (mobj->alpha != FRACUNIT)
+		diff2 |= MD2_ALPHA;
 
 	if (diff2 != 0)
 		diff |= MD_MORE;
@@ -2172,6 +2175,8 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 		WRITEINT32(save_p, mobj->dispoffset);
 	if (diff2 & MD2_TRANSLATION)
 		WRITEUINT16(save_p, mobj->translation);
+	if (diff2 & MD2_ALPHA)
+		WRITEFIXED(save_p, mobj->alpha);
 
 	WRITEUINT32(save_p, mobj->mobjnum);
 }
@@ -3238,6 +3243,8 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker)
 		mobj->dispoffset = mobj->info->dispoffset;
 	if (diff2 & MD2_TRANSLATION)
 		mobj->translation = READUINT16(save_p);
+	if (diff2 & MD2_ALPHA)
+		mobj->alpha = READFIXED(save_p);
 
 	if (diff & MD_REDFLAG)
 	{
diff --git a/src/p_user.c b/src/p_user.c
index 7ad5bccbb58db303a28c9b3fe438b7effdea3c90..ea68f9443cb4b7eb438f4992899683a1b5b7553b 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -976,6 +976,9 @@ pflags_t P_GetJumpFlags(player_t *player)
 //
 boolean P_PlayerInPain(player_t *player)
 {
+	if (P_MobjWasRemoved(player->mo))
+		return false;
+		
 	// no silly, sliding isn't pain
 	if (!(player->pflags & PF_SLIDING) && player->mo->state == &states[player->mo->info->painstate] && player->powers[pw_flashing])
 		return true;
@@ -2078,6 +2081,7 @@ mobj_t *P_SpawnGhostMobj(mobj_t *mobj)
 
 	ghost->renderflags = mobj->renderflags;
 	ghost->blendmode = mobj->blendmode;
+	ghost->alpha = mobj->alpha;
 
 	ghost->spritexscale = mobj->spritexscale;
 	ghost->spriteyscale = mobj->spriteyscale;
diff --git a/src/r_segs.c b/src/r_segs.c
index 8e9368bafde86847e8b4ddca0bc25c5aef6fe52e..303de88807bcbbb7289b948e268c71c0d3a30914 100644
--- a/src/r_segs.c
+++ b/src/r_segs.c
@@ -1004,25 +1004,21 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 		{
 			dc_iscale = 0xffffffffu / (unsigned)spryscale;
 
-			// Skip if texture is multipatch
-			if (textures[texnum]->transparency)
+			// Column has a single post and it matches the texture height, use regular column drawers
+			if (col->num_posts == 1 && col->posts[0].topdelta == 0 && col->posts[0].length == (unsigned)dc_texheight)
 			{
-				// Column has a single post and it matches the texture height, use regular column drawers
-				if (col->num_posts == 1 && col->posts[0].topdelta == 0 && col->posts[0].length == (unsigned)dc_texheight)
-				{
-					if (fuzzy)
-						colfunc = colfuncs[COLDRAWFUNC_FUZZY];
-					else
-						colfunc = colfuncs[BASEDRAWFUNC];
-				}
+				if (fuzzy)
+					colfunc = colfuncs[COLDRAWFUNC_FUZZY];
 				else
-				{
-					// Otherwise use column drawers with extra checks
-					if (fuzzy)
-						colfunc = colfuncs[COLDRAWFUNC_CLAMPEDTRANS];
-					else
-						colfunc = colfuncs[COLDRAWFUNC_CLAMPED];
-				}
+					colfunc = colfuncs[BASEDRAWFUNC];
+			}
+			else
+			{
+				// Otherwise use column drawers with extra checks
+				if (fuzzy)
+					colfunc = colfuncs[COLDRAWFUNC_CLAMPEDTRANS];
+				else
+					colfunc = colfuncs[COLDRAWFUNC_CLAMPED];
 			}
 		}
 
diff --git a/src/r_things.c b/src/r_things.c
index dc9cab99770817e5f05c6d13a281f3fa37668790..b32181670aa0ad9e3c5c59bcdc9f9fb2a810108d 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -996,6 +996,12 @@ UINT8 *R_GetTranslationForThing(mobj_t *mobj, skincolornum_t color, UINT16 trans
 	return NULL;
 }
 
+// Based off of R_GetLinedefTransTable
+transnum_t R_GetThingTransTable(fixed_t alpha, transnum_t transmap)
+{
+	return (20*(FRACUNIT - ((alpha * (10 - transmap))/10) - 1) + FRACUNIT) >> (FRACBITS+1);
+}
+
 //
 // R_DrawVisSprite
 //  mfloorclip and mceilingclip should also be set.
@@ -1501,6 +1507,7 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale,
 	floordiff = abs((isflipped ? interp.height : 0) + interp.z - groundz);
 
 	trans = floordiff / (100*FRACUNIT) + 3;
+	trans = R_GetThingTransTable(thing->alpha, trans);
 	if (trans >= 9) return;
 
 	scalemul = FixedMul(FRACUNIT - floordiff/640, scale);
@@ -2191,6 +2198,11 @@ static void R_ProjectSprite(mobj_t *thing)
 	}
 	else
 		trans = 0;
+	
+	if ((oldthing->flags2 & MF2_LINKDRAW) && oldthing->tracer)
+		trans = R_GetThingTransTable(oldthing->tracer->alpha, trans);
+	else
+		trans = R_GetThingTransTable(oldthing->alpha, trans);
 
 	// Check if this sprite needs to be rendered like a shadow
 	shadowdraw = (!!(thing->renderflags & RF_SHADOWDRAW) && !(papersprite || splat));
@@ -3652,6 +3664,7 @@ boolean R_ThingVisible (mobj_t *thing)
 		(thing->sprite == SPR_NULL) || // Don't draw null-sprites
 		(thing->flags2 & MF2_DONTDRAW) || // Don't draw MF2_LINKDRAW objects
 		(thing->drawonlyforplayer && thing->drawonlyforplayer != viewplayer) || // Don't draw other players' personal objects
+		(!R_BlendLevelVisible(thing->blendmode, R_GetThingTransTable(thing->alpha, 0))) ||
 		(!P_MobjWasRemoved(r_viewmobj) && (
 		  (r_viewmobj == thing) || // Don't draw first-person players or awayviewmobj objects
 		  (r_viewmobj->player && r_viewmobj->player->followmobj == thing) || // Don't draw first-person players' followmobj
diff --git a/src/r_things.h b/src/r_things.h
index 3daf55c4235bc753aeb5f1c897856e6d9303338c..55ab71ec3847d5b85295904a18de516afa6f1b74 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -93,6 +93,7 @@ boolean R_ThingIsFullDark (mobj_t *thing);
 boolean R_ThingIsFlashing (mobj_t *thing);
 
 UINT8 *R_GetTranslationForThing(mobj_t *mobj, skincolornum_t color, UINT16 translation);
+transnum_t R_GetThingTransTable(fixed_t alpha, transnum_t transmap);
 
 void R_ThingOffsetOverlay (mobj_t *thing, fixed_t *outx, fixed_t *outy);