diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index c1fd87af3..840863eb0 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -1809,9 +1809,10 @@ static int lib_pFadeLight(lua_State *L)
 	INT16 tag = (INT16)luaL_checkinteger(L, 1);
 	INT32 destvalue = (INT32)luaL_checkinteger(L, 2);
 	INT32 speed = (INT32)luaL_checkinteger(L, 3);
+	boolean ticbased = lua_optboolean(L, 4);
 	NOHUD
 	INLEVEL
-	P_FadeLight(tag, destvalue, speed);
+	P_FadeLight(tag, destvalue, speed, ticbased);
 	return 0;
 }
 
diff --git a/src/p_lights.c b/src/p_lights.c
index 8aa2eedca..e37eda5fb 100644
--- a/src/p_lights.c
+++ b/src/p_lights.c
@@ -13,6 +13,7 @@
 ///        Fire flicker, light flash, strobe flash, lightning flash, glow, and fade.
 
 #include "doomdef.h"
+#include "doomstat.h" // gametic
 #include "p_local.h"
 #include "r_state.h"
 #include "z_zone.h"
@@ -329,12 +330,10 @@ glow_t *P_SpawnAdjustableGlowingLight(sector_t *minsector, sector_t *maxsector,
   * \param destvalue The final light value in these sectors.
   * \param speed     Speed of the fade; the change to the ligh
   *                  level in each sector per tic.
-  * \todo Calculate speed better so that it is possible to specify
-  *       the time for completion of the fade, and all lights fade
-  *       in this time regardless of initial values.
+  * \param ticbased  Use a specific duration for the fade, defined by speed
   * \sa T_LightFade
   */
-void P_FadeLight(INT16 tag, INT32 destvalue, INT32 speed)
+void P_FadeLight(INT16 tag, INT32 destvalue, INT32 speed, boolean ticbased)
 {
 	INT32 i;
 	lightlevel_t *ll;
@@ -346,6 +345,13 @@ void P_FadeLight(INT16 tag, INT32 destvalue, INT32 speed)
 		sector = &sectors[i];
 
 		P_RemoveLighting(sector); // remove the old lighting effect first
+
+		if ((ticbased && !speed) || sector->lightlevel == destvalue) // set immediately
+		{
+			sector->lightlevel = destvalue;
+			continue;
+		}
+
 		ll = Z_Calloc(sizeof (*ll), PU_LEVSPEC, NULL);
 		ll->thinker.function.acp1 = (actionf_p1)T_LightFade;
 		sector->lightingdata = ll; // set it to the lightlevel_t
@@ -354,7 +360,21 @@ void P_FadeLight(INT16 tag, INT32 destvalue, INT32 speed)
 
 		ll->sector = sector;
 		ll->destlevel = destvalue;
-		ll->speed = speed;
+
+		if (ticbased)
+		{
+			ll->duration = abs(speed);
+			ll->speed = FixedFloor(FixedDiv(destvalue - sector->lightlevel, ll->duration))/FRACUNIT;
+			if (!ll->speed)
+				ll->speed = (destvalue < sector->lightlevel) ? -1 : 1;
+			ll->interval = max(FixedFloor(FixedDiv(ll->duration, abs(destvalue - sector->lightlevel)))/FRACUNIT, 1);
+			ll->firsttic = gametic;
+		}
+		else
+		{
+			ll->duration = -1;
+			ll->speed = abs(speed);
+		}
 	}
 }
 
@@ -365,6 +385,23 @@ void P_FadeLight(INT16 tag, INT32 destvalue, INT32 speed)
   */
 void T_LightFade(lightlevel_t *ll)
 {
+	if (ll->duration >= 0) // tic-based
+	{
+		if (gametic - ll->firsttic >= ll->duration)
+		{
+			ll->sector->lightlevel = (INT16)ll->destlevel; // set to dest lightlevel
+			P_RemoveLighting(ll->sector); // clear lightingdata, remove thinker
+		}
+		else if (!((gametic - ll->firsttic) % ll->interval))
+		{
+			if (ll->speed < 0)
+				ll->sector->lightlevel = max(ll->sector->lightlevel + (INT16)ll->speed, (INT16)ll->destlevel);
+			else
+				ll->sector->lightlevel = min(ll->sector->lightlevel + (INT16)ll->speed, (INT16)ll->destlevel);
+		}
+		return;
+	}
+
 	if (ll->sector->lightlevel < ll->destlevel)
 	{
 		// increase the lightlevel
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 22d43f358..8ec5b5ea8 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -1539,6 +1539,9 @@ static void SaveLightlevelThinker(const thinker_t *th, const UINT8 type)
 	WRITEUINT32(save_p, SaveSector(ht->sector));
 	WRITEINT32(save_p, ht->destlevel);
 	WRITEINT32(save_p, ht->speed);
+	WRITEINT32(save_p, ht->duration);
+	WRITEUINT32(save_p, ht->interval);
+	WRITEUINT32(save_p, (UINT32)ht->firsttic);
 }
 
 //
@@ -2512,6 +2515,9 @@ static inline void LoadLightlevelThinker(actionf_p1 thinker)
 	ht->sector = LoadSector(READUINT32(save_p));
 	ht->destlevel = READINT32(save_p);
 	ht->speed = READINT32(save_p);
+	ht->duration = READINT32(save_p);
+	ht->interval = READUINT32(save_p);
+	ht->firsttic = (tic_t)READUINT32(save_p);
 	if (ht->sector)
 		ht->sector->lightingdata = ht;
 	P_AddThinker(&ht->thinker);
diff --git a/src/p_spec.c b/src/p_spec.c
index 6c359c9cc..a894dcbfb 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -2778,7 +2778,10 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 420: // Fade light levels in tagged sectors to new value
-			P_FadeLight(line->tag, line->frontsector->lightlevel, P_AproxDistance(line->dx, line->dy)>>FRACBITS);
+			P_FadeLight(line->tag,
+				(line->flags & ML_BLOCKMONSTERS) ? max(min(sides[line->sidenum[0]].textureoffset>>FRACBITS, 255), 0) : line->frontsector->lightlevel,
+				(line->flags & ML_BLOCKMONSTERS) ? max(sides[line->sidenum[0]].rowoffset>>FRACBITS, 0) : P_AproxDistance(line->dx, line->dy)>>FRACBITS,
+				(line->flags & ML_NOCLIMB));
 			break;
 
 		case 421: // Stop lighting effect in tagged sectors
diff --git a/src/p_spec.h b/src/p_spec.h
index e0bcc18eb..8e296891b 100644
--- a/src/p_spec.h
+++ b/src/p_spec.h
@@ -138,6 +138,11 @@ typedef struct
 	sector_t *sector;  ///< Sector where action is taking place.
 	INT32 destlevel;   ///< Light level we're fading to.
 	INT32 speed;       ///< Speed at which to change light level.
+
+	// Tic-based behavior
+	INT32 duration;    ///< If <0, do not use tic-based behavior. If 0, set instantly. If >0, fade lasts this duration.
+	UINT32 interval;   ///< Interval to deduct light level
+	tic_t firsttic;    ///< First gametic to count from
 } lightlevel_t;
 
 #define GLOWSPEED 8
@@ -156,7 +161,7 @@ strobe_t * P_SpawnAdjustableStrobeFlash(sector_t *minsector, sector_t *maxsector
 void T_Glow(glow_t *g);
 glow_t *P_SpawnAdjustableGlowingLight(sector_t *minsector, sector_t *maxsector, INT32 length);
 
-void P_FadeLight(INT16 tag, INT32 destvalue, INT32 speed);
+void P_FadeLight(INT16 tag, INT32 destvalue, INT32 speed, boolean ticbased);
 void T_LightFade(lightlevel_t *ll);
 
 typedef enum