diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index e0851af8518e5146e8fac17af3f56a102799b7e9..f496de2ce99c6508a18cae7dbe8c1e3a568837fd 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -3046,13 +3046,13 @@ static void HWR_Subsector(size_t num)
 		}
 
 		light = R_GetPlaneLight(gl_frontsector, locFloorHeight, false);
-		if (gl_frontsector->floorlightsec == -1)
-			floorlightlevel = *gl_frontsector->lightlist[light].lightlevel;
+		if (gl_frontsector->floorlightsec == -1 && !gl_frontsector->floorlightabsolute)
+			floorlightlevel = max(0, min(255, *gl_frontsector->lightlist[light].lightlevel + gl_frontsector->floorlightlevel));
 		floorcolormap = *gl_frontsector->lightlist[light].extra_colormap;
 
 		light = R_GetPlaneLight(gl_frontsector, locCeilingHeight, false);
-		if (gl_frontsector->ceilinglightsec == -1)
-			ceilinglightlevel = *gl_frontsector->lightlist[light].lightlevel;
+		if (gl_frontsector->ceilinglightsec == -1 && !gl_frontsector->ceilinglightabsolute)
+			ceilinglightlevel = max(0, min(255, *gl_frontsector->lightlist[light].lightlevel + gl_frontsector->ceilinglightlevel));
 		ceilingcolormap = *gl_frontsector->lightlist[light].extra_colormap;
 	}
 
diff --git a/src/p_setup.c b/src/p_setup.c
index e6f46e0c05cc903dda69ae61e16abc1aa0ee6c13..7225da9856d942f26b4759d941c6a098631532aa 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -1043,6 +1043,9 @@ static void P_LoadSectors(UINT8 *data)
 
 		ss->floorpic_angle = ss->ceilingpic_angle = 0;
 
+		ss->floorlightlevel = ss->ceilinglightlevel = 0;
+		ss->floorlightabsolute = ss->ceilinglightabsolute = false;
+
 		ss->colormap_protected = false;
 
 		P_InitializeSector(ss);
@@ -1529,6 +1532,14 @@ static void ParseTextmapSectorParameter(UINT32 i, char *param, char *val)
 		sectors[i].ceilingpic = P_AddLevelFlat(val, foundflats);
 	else if (fastcmp(param, "lightlevel"))
 		sectors[i].lightlevel = atol(val);
+	else if (fastcmp(param, "lightfloor"))
+		sectors[i].floorlightlevel = atol(val);
+	else if (fastcmp(param, "lightfloorabsolute") && fastcmp("true", val))
+		sectors[i].floorlightabsolute = true;
+	else if (fastcmp(param, "lightceiling"))
+		sectors[i].ceilinglightlevel = atol(val);
+	else if (fastcmp(param, "lightceilingabsolute") && fastcmp("true", val))
+		sectors[i].ceilinglightabsolute = true;
 	else if (fastcmp(param, "special"))
 		sectors[i].special = atol(val);
 	else if (fastcmp(param, "id"))
@@ -1914,6 +1925,9 @@ static void P_LoadTextmap(void)
 
 		sc->floorpic_angle = sc->ceilingpic_angle = 0;
 
+		sc->floorlightlevel = sc->ceilinglightlevel = 0;
+		sc->floorlightabsolute = sc->ceilinglightabsolute = false;
+
 		sc->colormap_protected = false;
 
 		textmap_colormap.used = false;
diff --git a/src/p_spec.c b/src/p_spec.c
index be2141a673729970a4349709bcf81973e185a9de..f69b495684ecc5eac59a97d3469e36235f4c3029 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -2126,9 +2126,15 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 		case 402: // Set tagged sector's light level
 			{
 				INT16 newlightlevel;
+				INT16 newfloorlightlevel, newceilinglightlevel;
+				boolean newfloorlightabsolute, newceilinglightabsolute;
 				INT32 newfloorlightsec, newceilinglightsec;
 
 				newlightlevel = line->frontsector->lightlevel;
+				newfloorlightlevel = line->frontsector->floorlightlevel;
+				newfloorlightabsolute = line->frontsector->floorlightabsolute;
+				newceilinglightlevel = line->frontsector->ceilinglightlevel;
+				newceilinglightabsolute = line->frontsector->ceilinglightabsolute;
 				newfloorlightsec = line->frontsector->floorlightsec;
 				newceilinglightsec = line->frontsector->ceilinglightsec;
 
@@ -2147,6 +2153,10 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 					}
 
 					sectors[secnum].lightlevel = newlightlevel;
+					sectors[secnum].floorlightlevel = newfloorlightlevel;
+					sectors[secnum].floorlightabsolute = newfloorlightabsolute;
+					sectors[secnum].ceilinglightlevel = newceilinglightlevel;
+					sectors[secnum].ceilinglightabsolute = newceilinglightabsolute;
 					sectors[secnum].floorlightsec = newfloorlightsec;
 					sectors[secnum].ceilinglightsec = newceilinglightsec;
 				}
diff --git a/src/r_bsp.c b/src/r_bsp.c
index 5acd4e70c5fa083eaf675055be9d623e15f014e3..360c17be3fb4c6943c429c9244cc25fbc0c4970e 100644
--- a/src/r_bsp.c
+++ b/src/r_bsp.c
@@ -238,11 +238,11 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel,
 {
 	if (floorlightlevel)
 		*floorlightlevel = sec->floorlightsec == -1 ?
-			sec->lightlevel : sectors[sec->floorlightsec].lightlevel;
+			(sec->floorlightabsolute ? sec->floorlightlevel : max(0, min(255, sec->lightlevel + sec->floorlightlevel))) : sectors[sec->floorlightsec].lightlevel;
 
 	if (ceilinglightlevel)
 		*ceilinglightlevel = sec->ceilinglightsec == -1 ?
-			sec->lightlevel : sectors[sec->ceilinglightsec].lightlevel;
+			(sec->ceilinglightabsolute ? sec->ceilinglightlevel : max(0, min(255, sec->lightlevel + sec->ceilinglightlevel))) : sectors[sec->ceilinglightsec].lightlevel;
 
 	// if (sec->midmap != -1)
 	//	mapnum = sec->midmap;
@@ -301,11 +301,11 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel,
 			tempsec->lightlevel = s->lightlevel;
 
 			if (floorlightlevel)
-				*floorlightlevel = s->floorlightsec == -1 ? s->lightlevel
+				*floorlightlevel = s->floorlightsec == -1 ? (s->floorlightabsolute ? s->floorlightlevel : max(0, min(255, s->lightlevel + s->floorlightlevel)))
 					: sectors[s->floorlightsec].lightlevel;
 
 			if (ceilinglightlevel)
-				*ceilinglightlevel = s->ceilinglightsec == -1 ? s->lightlevel
+				*ceilinglightlevel = s->ceilinglightsec == -1 ? (s->ceilinglightabsolute ? s->ceilinglightlevel : max(0, min(255, s->lightlevel + s->ceilinglightlevel)))
 					: sectors[s->ceilinglightsec].lightlevel;
 		}
 		else if (heightsec != -1 && viewz >= sectors[heightsec].ceilingheight
@@ -339,12 +339,12 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel,
 			tempsec->lightlevel = s->lightlevel;
 
 			if (floorlightlevel)
-				*floorlightlevel = s->floorlightsec == -1 ? s->lightlevel :
-			sectors[s->floorlightsec].lightlevel;
+				*floorlightlevel = s->floorlightsec == -1 ? (s->floorlightabsolute ? s->floorlightlevel : max(0, min(255, s->lightlevel + s->floorlightlevel)))
+					: sectors[s->floorlightsec].lightlevel;
 
 			if (ceilinglightlevel)
-				*ceilinglightlevel = s->ceilinglightsec == -1 ? s->lightlevel :
-			sectors[s->ceilinglightsec].lightlevel;
+				*ceilinglightlevel = s->ceilinglightsec == -1 ? (s->ceilinglightabsolute ? s->ceilinglightlevel : max(0, min(255, s->lightlevel + s->ceilinglightlevel)))
+					: sectors[s->ceilinglightsec].lightlevel;
 		}
 		sec = tempsec;
 	}
@@ -370,6 +370,10 @@ boolean R_IsEmptyLine(seg_t *line, sector_t *front, sector_t *back)
 		&& back->ceiling_yoffs == front->ceiling_yoffs
 		&& back->ceilingpic_angle == front->ceilingpic_angle
 		// Consider altered lighting.
+		&& back->floorlightlevel == front->floorlightlevel
+		&& back->floorlightabsolute == front->floorlightabsolute
+		&& back->ceilinglightlevel == front->ceilinglightlevel
+		&& back->ceilinglightabsolute == front->ceilinglightabsolute
 		&& back->floorlightsec == front->floorlightsec
 		&& back->ceilinglightsec == front->ceilinglightsec
 		// Consider colormaps
@@ -872,12 +876,12 @@ static void R_Subsector(size_t num)
 		}
 
 		light = R_GetPlaneLight(frontsector, floorcenterz, false);
-		if (frontsector->floorlightsec == -1)
-			floorlightlevel = *frontsector->lightlist[light].lightlevel;
+		if (frontsector->floorlightsec == -1 && !frontsector->floorlightabsolute)
+			floorlightlevel = max(0, min(255, *frontsector->lightlist[light].lightlevel + frontsector->floorlightlevel));
 		floorcolormap = *frontsector->lightlist[light].extra_colormap;
 		light = R_GetPlaneLight(frontsector, ceilingcenterz, false);
-		if (frontsector->ceilinglightsec == -1)
-			ceilinglightlevel = *frontsector->lightlist[light].lightlevel;
+		if (frontsector->ceilinglightsec == -1 && !frontsector->ceilinglightabsolute)
+			ceilinglightlevel = max(0, min(255, *frontsector->lightlist[light].lightlevel + frontsector->ceilinglightlevel));
 		ceilingcolormap = *frontsector->lightlist[light].extra_colormap;
 	}
 
diff --git a/src/r_defs.h b/src/r_defs.h
index cb21d50430bc2dff87f756529b65fef4d99ea523..ab2b9fb2fb4204af63f1fb2a0d3df1f2ff38786a 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -341,7 +341,11 @@ typedef struct sector_s
 	INT32 heightsec; // other sector, or -1 if no other sector
 	INT32 camsec; // used for camera clipping
 
-	INT32 floorlightsec, ceilinglightsec;
+	// floor and ceiling lighting
+	INT16 floorlightlevel, ceilinglightlevel;
+	boolean floorlightabsolute, ceilinglightabsolute; // absolute or relative to sector's light level?
+	INT32 floorlightsec, ceilinglightsec; // take floor/ceiling light level from another sector
+
 	INT32 crumblestate; // used for crumbling and bobbing
 
 	// list of mobjs that are at least partially in the sector
diff --git a/src/r_segs.c b/src/r_segs.c
index a8c85ec33bade941039e1c88fea53906d37a3ee7..68fb2419526b37aaafbdb669e8ea35a2e53562b7 100644
--- a/src/r_segs.c
+++ b/src/r_segs.c
@@ -1908,18 +1908,20 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 		else if (worldlow != worldbottom
 			|| worldlowslope != worldbottomslope
 			|| backsector->f_slope != frontsector->f_slope
-		    || backsector->floorpic != frontsector->floorpic
-		    || backsector->lightlevel != frontsector->lightlevel
-		    //SoM: 3/22/2000: Check floor x and y offsets.
-		    || backsector->floor_xoffs != frontsector->floor_xoffs
-		    || backsector->floor_yoffs != frontsector->floor_yoffs
-		    || backsector->floorpic_angle != frontsector->floorpic_angle
-		    //SoM: 3/22/2000: Prevents bleeding.
-		    || (frontsector->heightsec != -1 && frontsector->floorpic != skyflatnum)
-		    || backsector->floorlightsec != frontsector->floorlightsec
-		    //SoM: 4/3/2000: Check for colormaps
-		    || frontsector->extra_colormap != backsector->extra_colormap
-		    || (frontsector->ffloors != backsector->ffloors && !Tag_Compare(&frontsector->tags, &backsector->tags)))
+			|| backsector->floorpic != frontsector->floorpic
+			|| backsector->lightlevel != frontsector->lightlevel
+			//SoM: 3/22/2000: Check floor x and y offsets.
+			|| backsector->floor_xoffs != frontsector->floor_xoffs
+			|| backsector->floor_yoffs != frontsector->floor_yoffs
+			|| backsector->floorpic_angle != frontsector->floorpic_angle
+			//SoM: 3/22/2000: Prevents bleeding.
+			|| (frontsector->heightsec != -1 && frontsector->floorpic != skyflatnum)
+			|| backsector->floorlightlevel != frontsector->floorlightlevel
+			|| backsector->floorlightabsolute != frontsector->floorlightabsolute
+			|| backsector->floorlightsec != frontsector->floorlightsec
+			//SoM: 4/3/2000: Check for colormaps
+			|| frontsector->extra_colormap != backsector->extra_colormap
+			|| (frontsector->ffloors != backsector->ffloors && !Tag_Compare(&frontsector->tags, &backsector->tags)))
 		{
 			markfloor = true;
 		}
@@ -1939,18 +1941,20 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 		else if (worldhigh != worldtop
 			|| worldhighslope != worldtopslope
 			|| backsector->c_slope != frontsector->c_slope
-		    || backsector->ceilingpic != frontsector->ceilingpic
-		    || backsector->lightlevel != frontsector->lightlevel
-		    //SoM: 3/22/2000: Check floor x and y offsets.
-		    || backsector->ceiling_xoffs != frontsector->ceiling_xoffs
-		    || backsector->ceiling_yoffs != frontsector->ceiling_yoffs
-		    || backsector->ceilingpic_angle != frontsector->ceilingpic_angle
-		    //SoM: 3/22/2000: Prevents bleeding.
-		    || (frontsector->heightsec != -1 && frontsector->ceilingpic != skyflatnum)
-		    || backsector->ceilinglightsec != frontsector->ceilinglightsec
-		    //SoM: 4/3/2000: Check for colormaps
-		    || frontsector->extra_colormap != backsector->extra_colormap
-		    || (frontsector->ffloors != backsector->ffloors && !Tag_Compare(&frontsector->tags, &backsector->tags)))
+			|| backsector->ceilingpic != frontsector->ceilingpic
+			|| backsector->lightlevel != frontsector->lightlevel
+			//SoM: 3/22/2000: Check floor x and y offsets.
+			|| backsector->ceiling_xoffs != frontsector->ceiling_xoffs
+			|| backsector->ceiling_yoffs != frontsector->ceiling_yoffs
+			|| backsector->ceilingpic_angle != frontsector->ceilingpic_angle
+			//SoM: 3/22/2000: Prevents bleeding.
+			|| (frontsector->heightsec != -1 && frontsector->ceilingpic != skyflatnum)
+			|| backsector->ceilinglightlevel != frontsector->ceilinglightlevel
+			|| backsector->ceilinglightabsolute != frontsector->ceilinglightabsolute
+			|| backsector->ceilinglightsec != frontsector->ceilinglightsec
+			//SoM: 4/3/2000: Check for colormaps
+			|| frontsector->extra_colormap != backsector->extra_colormap
+			|| (frontsector->ffloors != backsector->ffloors && !Tag_Compare(&frontsector->tags, &backsector->tags)))
 		{
 				markceiling = true;
 		}