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/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/p_mobj.c b/src/p_mobj.c
index 3ca6093906b3da9725cf95f6edb3ee5b78b9cae0..6f5de476be900ce97f80bcb998482d4b0e3f170c 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -10355,6 +10355,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 +10701,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;
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..ff7c02bcccb6976f1759777820d0c8ad8887a0ce 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -2078,6 +2078,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_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);