diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 350c1585fab2478bf4dfa438306215a510b330b2..05e19f9d538483bd4280051c5655c816336b0ee5 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -210,6 +210,9 @@ static const struct {
 	{META_HUDINFO,      "hudinfo_t"},
 	{META_PATCH,        "patch_t"},
 	{META_COLORMAP,     "colormap"},
+	{META_TRANSLATION,  "translation"},
+	{META_EXTRACOLORMAP,"extracolormap_t"},
+	{META_LIGHTTABLE,   "lighttable_t"},
 	{META_CAMERA,       "camera_t"},
 
 	{META_ACTION,       "action"},
diff --git a/src/lua_colorlib.c b/src/lua_colorlib.c
index 2365ffb440bb27bf5ccbd7ee4c7bbc5e9edf8f6f..41df3aaebff945fa70c7dee91db229b10674ccf7 100644
--- a/src/lua_colorlib.c
+++ b/src/lua_colorlib.c
@@ -31,6 +31,36 @@
 	#define GetNearestColor(r, g, b) NearestPaletteColor(r, g, b, pMasterPalette)
 #endif
 
+///////////////////
+// Colormap library
+///////////////////
+
+UINT8* LUA_CheckColormap(lua_State *L, int ud)
+{
+	void *p = lua_touserdata(L, ud);
+
+	if (p != NULL && lua_getmetatable(L, ud)) {
+		lua_getfield(L, LUA_REGISTRYINDEX, META_COLORMAP);
+		if (lua_rawequal(L, -1, -2)) {
+			lua_pop(L, 2);
+			return (*((colormap_t **)p))->map;
+		}
+
+		lua_pop(L, 1);
+
+		lua_getfield(L, LUA_REGISTRYINDEX, META_TRANSLATION);
+		if (lua_rawequal(L, -1, -2)) {
+			lua_pop(L, 2);
+			return *((UINT8 **)p);
+		}
+
+		lua_pop(L, 2);
+	}
+
+	luaL_typerror(L, ud, "colormap or translation");
+	return NULL;
+}
+
 static int colormap_get(lua_State *L)
 {
 	colormap_t *colormap = *((colormap_t **)luaL_checkudata(L, 1, META_COLORMAP));
@@ -45,8 +75,6 @@ static int colormap_set(lua_State *L)
 {
 	colormap_t *colormap = *((colormap_t **)luaL_checkudata(L, 1, META_COLORMAP));
 	UINT32 i = luaL_checkinteger(L, 2);
-	if (colormap->flags & GTC_CACHE)
-		return luaL_error(L, "colormap is read-only");
 	if (i >= 256)
 		return luaL_error(L, "colormap index %d out of range (0 - %d)", i, 255);
 	colormap->map[i] = (UINT8)luaL_checkinteger(L, 3);
@@ -55,8 +83,7 @@ static int colormap_set(lua_State *L)
 
 static int colormap_len(lua_State *L)
 {
-	colormap_t *colormap = *((colormap_t **)luaL_checkudata(L, 1, META_COLORMAP));
-	lua_pushinteger(L, colormap->rows);
+	lua_pushinteger(L, NUM_PALETTE_ENTRIES);
 	return 1;
 }
 
@@ -67,6 +94,24 @@ static int colormap_free(lua_State *L)
 	return 0;
 }
 
+static int translation_get(lua_State *L)
+{
+	UINT8 *colormap = *((UINT8 **)luaL_checkudata(L, 1, META_TRANSLATION));
+	UINT32 i = luaL_checkinteger(L, 2);
+	if (i >= 256)
+		return luaL_error(L, "colormap index %d out of range (0 - %d)", i, 255);
+	lua_pushinteger(L, colormap[i]);
+	return 1;
+}
+
+// See colormap_len. I assume there isn't any problem with two metatables
+// sharing the same metamethod, but I didn't want to risk it...
+static int translation_len(lua_State *L)
+{
+	lua_pushinteger(L, NUM_PALETTE_ENTRIES);
+	return 1;
+}
+
 static void MakeRGBColormap(UINT8 *colormap, UINT8 cr, UINT8 cg, UINT8 cb, UINT8 ca, UINT16 start, UINT16 end)
 {
 	double r, g, b, cbrightness;
@@ -222,10 +267,7 @@ static int lib_colormapCreate(lua_State *L)
 
 	GenerateColormap(L, generated_colormap);
 
-	colormap = Z_MallocAlign(sizeof(colormap_t) + NUM_PALETTE_ENTRIES, PU_STATIC, NULL, 8);
-	colormap->rows = 1;
-	colormap->flags = 0;
-
+	colormap = Z_MallocAlign(sizeof(colormap_t), PU_STATIC, NULL, 8);
 	M_Memcpy(colormap->map, generated_colormap, NUM_PALETTE_ENTRIES);
 
 	userdata = lua_newuserdata(L, sizeof(void *));
@@ -241,9 +283,6 @@ static int lib_colormapGenerate(lua_State *L)
 {
 	colormap_t *colormap = *((colormap_t **)luaL_checkudata(L, 1, META_COLORMAP));
 
-	if (colormap->flags & GTC_CACHE)
-		return luaL_error(L, "colormap is read-only");
-
 	lua_remove(L, 1);
 
 	GenerateColormap(L, colormap->map);
@@ -254,15 +293,12 @@ static int lib_colormapGenerate(lua_State *L)
 static int lib_colormapMix(lua_State *L)
 {
 	colormap_t *colormapA = *((colormap_t **)luaL_checkudata(L, 1, META_COLORMAP));
-	colormap_t *colormapB = *((colormap_t **)luaL_checkudata(L, 2, META_COLORMAP));
+	UINT8 *colormapB = LUA_CheckColormap(L, 2);
 	INT32 amt = luaL_checkinteger(L, 3);
 	INT32 mode = luaL_optinteger(L, 4, AST_TRANSLUCENT);
 	INT32 start = luaL_optinteger(L, 5, 0);
 	INT32 end = luaL_optinteger(L, 6, NUM_PALETTE_ENTRIES) - 1, i;
 
-	if (colormapA->flags & GTC_CACHE)
-		return luaL_error(L, "colormap is read-only");
-
 	if (amt < 0 || amt > 255)
 		luaL_error(L, "blend amount %d out of range (0 - %d)", amt, 255);
 
@@ -286,7 +322,7 @@ static int lib_colormapMix(lua_State *L)
 	{
 		RGBA_t texel;
 		RGBA_t bg = V_GetMasterColor(colormapA->map[i]);
-		RGBA_t fg = V_GetMasterColor(colormapB->map[i]);
+		RGBA_t fg = V_GetMasterColor(colormapB[i]);
 		texel.rgba = ASTBlendPixel(bg, fg, mode, amt);
 		colormapA->map[i] = GetNearestColor(texel.s.red, texel.s.green, texel.s.blue);
 	}
@@ -303,9 +339,6 @@ static int lib_colormapBlend(lua_State *L)
 	INT32 start = luaL_optinteger(L, 5, 0);
 	INT32 end = luaL_optinteger(L, 6, NUM_PALETTE_ENTRIES) - 1, i;
 
-	if (colormap->flags & GTC_CACHE)
-		return luaL_error(L, "colormap is read-only");
-
 	if (amt < 0 || amt > 255)
 		luaL_error(L, "blend amount %d out of range (0 - %d)", amt, 255);
 
@@ -349,9 +382,6 @@ static int lib_colormapBlendRGB(lua_State *L)
 
 	RGBA_t fg;
 
-	if (colormap->flags & GTC_CACHE)
-		return luaL_error(L, "colormap is read-only");
-
 	if (amt < 0 || amt > 255)
 		luaL_error(L, "blend amount %d out of range (0 - %d)", amt, 255);
 
@@ -395,9 +425,6 @@ static int lib_colormapTint(lua_State *L)
 	INT32 start = luaL_optinteger(L, 4, 0);
 	INT32 end = luaL_optinteger(L, 5, NUM_PALETTE_ENTRIES) - 1;
 
-	if (colormap->flags & GTC_CACHE)
-		return luaL_error(L, "colormap is read-only");
-
 	if (amt < 0 || amt > 255)
 		luaL_error(L, "tint amount %d out of range (0 - %d)", amt, 255);
 
@@ -425,9 +452,6 @@ static int lib_colormapTintRGB(lua_State *L)
 	INT32 start = luaL_optinteger(L, 6, 0);
 	INT32 end = luaL_optinteger(L, 7, NUM_PALETTE_ENTRIES) - 1;
 
-	if (colormap->flags & GTC_CACHE)
-		return luaL_error(L, "colormap is read-only");
-
 	if (amt < 0 || amt > 255)
 		luaL_error(L, "tint amount %d out of range (0 - %d)", amt, 255);
 
@@ -448,13 +472,10 @@ static int lib_colormapTintRGB(lua_State *L)
 static int lib_colormapCopy(lua_State *L)
 {
 	colormap_t *colormapA = *((colormap_t **)luaL_checkudata(L, 1, META_COLORMAP));
-	colormap_t *colormapB = *((colormap_t **)luaL_checkudata(L, 2, META_COLORMAP));
+	UINT8 *colormapB = LUA_CheckColormap(L, 2);
 	INT32 start = luaL_optinteger(L, 3, 0);
 	INT32 end = luaL_optinteger(L, 4, NUM_PALETTE_ENTRIES) - 1, i;
 
-	if (colormapA->flags & GTC_CACHE)
-		return luaL_error(L, "colormap is read-only");
-
 	if (start < 0 || start >= NUM_PALETTE_ENTRIES)
 		luaL_error(L, "start index %d out of range (0 - %d)", start, NUM_PALETTE_ENTRIES-1);
 	if (end <= 0)
@@ -465,7 +486,7 @@ static int lib_colormapCopy(lua_State *L)
 		end = NUM_PALETTE_ENTRIES - 1;
 
 	for (i = start; i <= end; i++)
-		colormapA->map[i] = colormapB->map[i];
+		colormapA->map[i] = colormapB[i];
 
 	return 0;
 }
@@ -477,9 +498,6 @@ static int lib_colormapCopySkinColor(lua_State *L)
 	INT32 start = luaL_optinteger(L, 3, DEFAULT_STARTTRANSCOLOR);
 	INT32 end, i, j = 0;
 
-	if (colormap->flags & GTC_CACHE)
-		return luaL_error(L, "colormap is read-only");
-
 	if (skincolor < 0 || skincolor >= numskincolors)
 		luaL_error(L, "skin color %d out of range (0 - %d)", skincolor, numskincolors-1);
 
@@ -496,7 +514,7 @@ static int lib_colormapCopySkinColor(lua_State *L)
 	return 0;
 }
 
-static luaL_Reg lib[] = {
+static luaL_Reg colormap_lib[] = {
 	{"create", lib_colormapCreate},
 	{"generate", lib_colormapGenerate},
 	{"mix", lib_colormapMix},
@@ -509,6 +527,246 @@ static luaL_Reg lib[] = {
 	{NULL, NULL}
 };
 
+/////////////////////////
+// extracolormap userdata
+/////////////////////////
+
+enum extracolormap_e {
+	extracolormap_r = 0,
+	extracolormap_g,
+	extracolormap_b,
+	extracolormap_a,
+	extracolormap_rgba,
+	extracolormap_fade_r,
+	extracolormap_fade_g,
+	extracolormap_fade_b,
+	extracolormap_fade_a,
+	extracolormap_fade_rgba,
+	extracolormap_fade_start,
+	extracolormap_fade_end,
+	extracolormap_colormap
+};
+
+static const char *const extracolormap_opt[] = {
+	"r",
+	"g",
+	"b",
+	"a",
+	"rgba",
+	"fade_r",
+	"fade_g",
+	"fade_b",
+	"fade_a",
+	"fade_rgba",
+	"fade_start",
+	"fade_end",
+	"colormap",
+	NULL};
+
+static int extracolormap_get(lua_State *L)
+{
+	extracolormap_t *exc = *((extracolormap_t **)luaL_checkudata(L, 1, META_EXTRACOLORMAP));
+	enum extracolormap_e field = luaL_checkoption(L, 2, NULL, extracolormap_opt);
+
+	switch (field)
+	{
+	case extracolormap_r:
+		lua_pushinteger(L, R_GetRgbaR(exc->rgba));
+		break;
+	case extracolormap_g:
+		lua_pushinteger(L, R_GetRgbaG(exc->rgba));
+		break;
+	case extracolormap_b:
+		lua_pushinteger(L, R_GetRgbaB(exc->rgba));
+		break;
+	case extracolormap_a:
+		lua_pushinteger(L, R_GetRgbaA(exc->rgba));
+		break;
+	case extracolormap_rgba:
+		lua_pushinteger(L, R_GetRgbaR(exc->rgba));
+		lua_pushinteger(L, R_GetRgbaG(exc->rgba));
+		lua_pushinteger(L, R_GetRgbaB(exc->rgba));
+		lua_pushinteger(L, R_GetRgbaA(exc->rgba));
+		return 4;
+	case extracolormap_fade_r:
+		lua_pushinteger(L, R_GetRgbaR(exc->fadergba));
+		break;
+	case extracolormap_fade_g:
+		lua_pushinteger(L, R_GetRgbaG(exc->fadergba));
+		break;
+	case extracolormap_fade_b:
+		lua_pushinteger(L, R_GetRgbaB(exc->fadergba));
+		break;
+	case extracolormap_fade_a:
+		lua_pushinteger(L, R_GetRgbaA(exc->fadergba));
+		break;
+	case extracolormap_fade_rgba:
+		lua_pushinteger(L, R_GetRgbaR(exc->fadergba));
+		lua_pushinteger(L, R_GetRgbaG(exc->fadergba));
+		lua_pushinteger(L, R_GetRgbaB(exc->fadergba));
+		lua_pushinteger(L, R_GetRgbaA(exc->fadergba));
+		return 4;
+	case extracolormap_fade_start:
+		lua_pushinteger(L, R_GetRgbaA(exc->fadestart));
+		break;
+	case extracolormap_fade_end:
+		lua_pushinteger(L, R_GetRgbaA(exc->fadeend));
+		break;
+	case extracolormap_colormap:
+		LUA_PushUserdata(L, exc->colormap, META_LIGHTTABLE);
+		break;
+	}
+	return 1;
+}
+
+static void GetExtraColormapRGBA(lua_State *L, UINT8 *rgba)
+{
+	luaL_checktype(L, 3, LUA_TTABLE);
+	lua_remove(L, 1);
+	lua_remove(L, 1);
+	lua_settop(L, 1);
+	lua_pushnil(L);
+
+	while (lua_next(L, 1)) {
+		lua_Integer i = 0;
+		const char *field = NULL;
+		if (lua_isnumber(L, 2))
+			i = lua_tointeger(L, 2);
+		else
+			field = luaL_checkstring(L, 2);
+
+#define CHECKFIELD(p, c) (i == p || (field && fastcmp(field, c)))
+#define RGBASET(p) rgba[p] = (UINT8)luaL_checkinteger(L, 3)
+
+		if (CHECKFIELD(1, "r")) {
+			RGBASET(0);
+		} else if (CHECKFIELD(2, "g")) {
+			RGBASET(1);
+		} else if (CHECKFIELD(3, "b")) {
+			RGBASET(2);
+		} else if (CHECKFIELD(4, "a")) {
+			RGBASET(3);
+		}
+
+#undef CHECKFIELD
+#undef RGBASET
+
+		lua_pop(L, 1);
+	}
+}
+
+static int extracolormap_set(lua_State *L)
+{
+	extracolormap_t *exc = *((extracolormap_t **)luaL_checkudata(L, 1, META_EXTRACOLORMAP));
+	enum extracolormap_e field = luaL_checkoption(L, 2, NULL, extracolormap_opt);
+
+	UINT8 r = R_GetRgbaR(exc->rgba);
+	UINT8 g = R_GetRgbaG(exc->rgba);
+	UINT8 b = R_GetRgbaB(exc->rgba);
+	UINT8 a = R_GetRgbaA(exc->rgba);
+
+	UINT8 fr = R_GetRgbaR(exc->fadergba);
+	UINT8 fg = R_GetRgbaG(exc->fadergba);
+	UINT8 fb = R_GetRgbaB(exc->fadergba);
+	UINT8 fa = R_GetRgbaA(exc->fadergba);
+
+	UINT8 rgba[4];
+
+	INT32 old_rgba = exc->rgba, old_fade_rgba = exc->fadergba; // It's not unsigned?
+	UINT8 old_fade_start = exc->fadestart, old_fade_end = exc->fadeend;
+
+#define val luaL_checkinteger(L, 3)
+
+	switch(field)
+	{
+	case extracolormap_r:
+		exc->rgba = R_PutRgbaRGBA(val, g, b, a);
+		break;
+	case extracolormap_g:
+		exc->rgba = R_PutRgbaRGBA(r, val, b, a);
+		break;
+	case extracolormap_b:
+		exc->rgba = R_PutRgbaRGBA(r, g, val, a);
+		break;
+	case extracolormap_a:
+		exc->rgba = R_PutRgbaRGBA(r, g, b, val);
+		break;
+	case extracolormap_rgba:
+		rgba[0] = r;
+		rgba[1] = g;
+		rgba[2] = b;
+		rgba[3] = a;
+		GetExtraColormapRGBA(L, rgba);
+		exc->rgba = R_PutRgbaRGBA(rgba[0], rgba[1], rgba[2], rgba[3]);
+		break;
+	case extracolormap_fade_r:
+		exc->fadergba = R_PutRgbaRGBA(val, fg, fb, fa);
+		break;
+	case extracolormap_fade_g:
+		exc->fadergba = R_PutRgbaRGBA(fr, val, fb, fa);
+		break;
+	case extracolormap_fade_b:
+		exc->fadergba = R_PutRgbaRGBA(fr, fg, val, fa);
+		break;
+	case extracolormap_fade_a:
+		exc->fadergba = R_PutRgbaRGBA(fr, fg, fb, val);
+		break;
+	case extracolormap_fade_rgba:
+		rgba[0] = fr;
+		rgba[1] = fg;
+		rgba[2] = fb;
+		rgba[3] = fa;
+		GetExtraColormapRGBA(L, rgba);
+		exc->fadergba = R_PutRgbaRGBA(rgba[0], rgba[1], rgba[2], rgba[3]);
+		break;
+	case extracolormap_fade_start:
+		if (val > 31)
+			return luaL_error(L, "fade start %d out of range (0 - 31)", val);
+		exc->fadestart = val;
+		break;
+	case extracolormap_fade_end:
+		if (val > 31)
+			return luaL_error(L, "fade end %d out of range (0 - 31)", val);
+		exc->fadeend = val;
+		break;
+	case extracolormap_colormap:
+		return luaL_error(L, LUA_QL("extracolormap_t") " field " LUA_QS " should not be set directly.", extracolormap_opt[field]);
+	}
+
+#undef val
+
+	if (exc->rgba != old_rgba
+		|| exc->fadergba != old_fade_rgba
+		|| exc->fadestart != old_fade_start
+		|| exc->fadeend != old_fade_end)
+	R_GenerateLightTable(exc, true);
+
+	return 0;
+}
+
+static int lighttable_get(lua_State *L)
+{
+	void **userdata;
+
+	lighttable_t *table = *((lighttable_t **)luaL_checkudata(L, 1, META_LIGHTTABLE));
+	UINT32 row = luaL_checkinteger(L, 2);
+	if (row < 1 || row > 34)
+		return luaL_error(L, "lighttable row %d out of range (1 - %d)", row, 34);
+
+	userdata = lua_newuserdata(L, sizeof(void *));
+	*userdata = &table[256 * (row - 1)];
+	luaL_getmetatable(L, META_TRANSLATION);
+	lua_setmetatable(L, -2);
+
+	return 1;
+}
+
+static int lighttable_len(lua_State *L)
+{
+	lua_pushinteger(L, NUM_PALETTE_ENTRIES);
+	return 1;
+}
+
 int LUA_ColorLib(lua_State *L)
 {
 	luaL_newmetatable(L, META_COLORMAP);
@@ -525,6 +783,30 @@ int LUA_ColorLib(lua_State *L)
 		lua_setfield(L, -2, "__gc");
 	lua_pop(L, 1);
 
-	luaL_register(L, "colormap", lib);
+	luaL_newmetatable(L, META_TRANSLATION);
+		lua_pushcfunction(L, translation_get);
+		lua_setfield(L, -2, "__index");
+
+		lua_pushcfunction(L, translation_len);
+		lua_setfield(L, -2, "__len");
+	lua_pop(L, 1);
+
+	luaL_newmetatable(L, META_EXTRACOLORMAP);
+		lua_pushcfunction(L, extracolormap_get);
+		lua_setfield(L, -2, "__index");
+
+		lua_pushcfunction(L, extracolormap_set);
+		lua_setfield(L, -2, "__newindex");
+	lua_pop(L, 1);
+
+	luaL_newmetatable(L, META_LIGHTTABLE);
+		lua_pushcfunction(L, lighttable_get);
+		lua_setfield(L, -2, "__index");
+
+		lua_pushcfunction(L, lighttable_len);
+		lua_setfield(L, -2, "__len");
+	lua_pop(L, 1);
+
+	luaL_register(L, "colormap", colormap_lib);
 	return 0;
 }
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index 5b3f622da14ed6967a6070782e5647b73f780bf7..2228d897424752e95715790866338ca02d7fce4d 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -645,7 +645,7 @@ static int libd_draw(lua_State *L)
 		return LUA_ErrInvalid(L, "patch_t");
 	flags = luaL_optinteger(L, 4, 0);
 	if (!lua_isnoneornil(L, 5))
-		colormap = (*((colormap_t **)luaL_checkudata(L, 5, META_COLORMAP)))->map;
+		colormap = LUA_CheckColormap(L, 5);
 
 	flags &= ~V_PARAMMASK; // Don't let crashes happen.
 
@@ -671,7 +671,7 @@ static int libd_drawScaled(lua_State *L)
 		return LUA_ErrInvalid(L, "patch_t");
 	flags = luaL_optinteger(L, 5, 0);
 	if (!lua_isnoneornil(L, 6))
-		colormap = (*((colormap_t **)luaL_checkudata(L, 6, META_COLORMAP)))->map;
+		colormap = LUA_CheckColormap(L, 6);
 
 	flags &= ~V_PARAMMASK; // Don't let crashes happen.
 
@@ -698,7 +698,7 @@ static int libd_drawStretched(lua_State *L)
 	patch = *((patch_t **)luaL_checkudata(L, 5, META_PATCH));
 	flags = luaL_optinteger(L, 6, 0);
 	if (!lua_isnoneornil(L, 7))
-		colormap = (*((colormap_t **)luaL_checkudata(L, 7, META_COLORMAP)))->map;
+		colormap = LUA_CheckColormap(L, 7);
 
 	flags &= ~V_PARAMMASK; // Don't let crashes happen.
 
@@ -725,7 +725,7 @@ static int libd_drawCropped(lua_State *L)
 	patch = *((patch_t **)luaL_checkudata(L, 5, META_PATCH));
 	flags = luaL_checkinteger(L, 6);
 	if (!lua_isnoneornil(L, 7))
-		colormap = (*((colormap_t **)luaL_checkudata(L, 7, META_COLORMAP)))->map;
+		colormap = LUA_CheckColormap(L, 7);
 	sx = luaL_checkinteger(L, 8);
 	if (sx < 0) // Don't crash. Now, we could do "x-=sx*FRACUNIT; sx=0;" here...
 		return luaL_error(L, "negative crop sx");
@@ -1008,7 +1008,7 @@ static int libd_getColormap(lua_State *L)
 {
 	INT32 skinnum = TC_DEFAULT;
 	skincolornum_t color = luaL_optinteger(L, 2, 0);
-	colormap_t* colormap = NULL;
+	UINT8* colormap = NULL;
 	HUDONLY
 	if (lua_isnoneornil(L, 1))
 		; // defaults to TC_DEFAULT
@@ -1029,8 +1029,8 @@ static int libd_getColormap(lua_State *L)
 	}
 
 	// all was successful above, now we generate the colormap at last!
-	colormap = R_GetTranslationColormap(skinnum, color, GTC_CACHE);
-	LUA_PushUserdata(L, colormap, META_COLORMAP); // push as META_COLORMAP userdata, specifically for patches to use!
+	colormap = R_GetCachedTranslation(skinnum, color);
+	LUA_PushUserdata(L, colormap, META_TRANSLATION); // push as META_TRANSLATION userdata, specifically for patches to use!
 	return 1;
 }
 
@@ -1043,9 +1043,7 @@ static int libd_getStringColormap(lua_State *L)
 	HUDONLY
 	colormap = V_GetStringColormap(flags & V_CHARCOLORMASK);
 	if (colormap) {
-		lua_colormap = Z_MallocAlign(sizeof(colormap_t) + NUM_PALETTE_ENTRIES, PU_STATIC, NULL, 8);
-		lua_colormap->rows = 1;
-		lua_colormap->flags = 0;
+		lua_colormap = Z_MallocAlign(sizeof(colormap_t), PU_STATIC, NULL, 8);
 		M_Memcpy(lua_colormap->map, colormap, NUM_PALETTE_ENTRIES * sizeof(UINT8));
 
 		userdata = lua_newuserdata(L, sizeof(void *));
diff --git a/src/lua_libs.h b/src/lua_libs.h
index 6c96eb2d752dd1620547e1a567d326e9a9008a80..bc37aab6efea1976cda8e2cd93be3e3df7b74bd0 100644
--- a/src/lua_libs.h
+++ b/src/lua_libs.h
@@ -81,9 +81,13 @@ extern boolean mousegrabbedbylua;
 
 #define META_BBOX "BOUNDING_BOX"
 
+#define META_COLORMAP "COLORMAP"
+#define META_TRANSLATION "TRANSLATION"
+#define META_EXTRACOLORMAP "EXTRACOLORMAP_T"
+#define META_LIGHTTABLE "LIGHTTABLE_T"
+
 #define META_HUDINFO "HUDINFO_T*"
 #define META_PATCH "PATCH_T*"
-#define META_COLORMAP "COLORMAP"
 #define META_CAMERA "CAMERA_T*"
 
 #define META_ACTION "ACTIONF_T*"
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index 9031c99f13a981fc6c235caea12a0fbfb9201e49..688e6c4554135021b2d6a0ff2ff00842100b6187 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -44,7 +44,8 @@ enum sector_e {
 	sector_lines,
 	sector_ffloors,
 	sector_fslope,
-	sector_cslope
+	sector_cslope,
+	sector_extracolormap
 };
 
 static const char *const sector_opt[] = {
@@ -64,6 +65,7 @@ static const char *const sector_opt[] = {
 	"ffloors",
 	"f_slope",
 	"c_slope",
+	"extra_colormap",
 	NULL};
 
 enum subsector_e {
@@ -617,6 +619,9 @@ static int sector_get(lua_State *L)
 	case sector_cslope: // c_slope
 		LUA_PushUserdata(L, sector->c_slope, META_SLOPE);
 		return 1;
+	case sector_extracolormap: // extra_colormap
+		LUA_PushUserdata(L, sector->extra_colormap, META_EXTRACOLORMAP);
+		return 1;
 	}
 	return 0;
 }
@@ -644,6 +649,7 @@ static int sector_set(lua_State *L)
 	case sector_ffloors: // ffloors
 	case sector_fslope: // f_slope
 	case sector_cslope: // c_slope
+	case sector_extracolormap: // extra_colormap
 	default:
 		return luaL_error(L, "sector_t field " LUA_QS " cannot be set.", sector_opt[field]);
 	case sector_floorheight: { // floorheight
diff --git a/src/lua_script.h b/src/lua_script.h
index e882569414452951429a99986c313137cc9613e9..d736bb20121557a715752157f5e738b92bc1e123 100644
--- a/src/lua_script.h
+++ b/src/lua_script.h
@@ -96,6 +96,8 @@ void LUA_InvalidateLevel(void);
 void LUA_InvalidateMapthings(void);
 void LUA_InvalidatePlayer(player_t *player);
 
+UINT8* LUA_CheckColormap(lua_State *L, int ud);
+
 // Console wrapper
 void COM_Lua_f(void);
 
diff --git a/src/r_data.c b/src/r_data.c
index 22e556e7a83b0cbf79e9e7c062001d99d75ffcfb..6f97a0262e3750458234cfaa665060a14784d74b 100644
--- a/src/r_data.c
+++ b/src/r_data.c
@@ -692,19 +692,26 @@ extracolormap_t *R_ColormapForName(char *name)
 //
 static double deltas[256][3], map[256][3];
 
-lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap)
+static colorlookup_t lighttable_lut;
+
+static UINT8 LightTableNearest(UINT8 r, UINT8 g, UINT8 b)
 {
-	colormap_t *c = Z_MallocAlign(sizeof(colormap_t) + (256 * 34) + 10, PU_LEVEL, NULL, 8);
-	extra_colormap->colormap = (lighttable_t *)((UINT8 *)c + sizeof(colormap_t));
+	return NearestColor(r, g, b);
+}
 
-	c->rows = 34;
-	c->flags = GTC_CACHE;
-	R_GenerateLightTable(extra_colormap);
+static UINT8 LightTableNearest_LUT(UINT8 r, UINT8 g, UINT8 b)
+{
+	return GetColorLUT(&lighttable_lut, r, g, b);
+}
 
+lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap)
+{
+	extra_colormap->colormap = Z_MallocAlign((256 * 34) + 10, PU_LEVEL, NULL, 8);
+	R_GenerateLightTable(extra_colormap, false);
 	return extra_colormap->colormap;
 }
 
-void R_GenerateLightTable(extracolormap_t *extra_colormap)
+void R_GenerateLightTable(extracolormap_t *extra_colormap, boolean uselookup)
 {
 	double cmaskr, cmaskg, cmaskb, cdestr, cdestg, cdestb;
 	double maskamt = 0, othermask = 0;
@@ -762,6 +769,16 @@ void R_GenerateLightTable(extracolormap_t *extra_colormap)
 		int p;
 		char *colormap_p;
 
+		UINT8 (*NearestColorFunc)(UINT8, UINT8, UINT8);
+
+		if (uselookup)
+		{
+			InitColorLUT(&lighttable_lut, pMasterPalette, false);
+			NearestColorFunc = LightTableNearest_LUT;
+		}
+		else
+			NearestColorFunc = LightTableNearest;
+
 		// Initialise the map and delta arrays
 		// map[i] stores an RGB color (as double) for index i,
 		//  which is then converted to SRB2's palette later
@@ -800,7 +817,7 @@ void R_GenerateLightTable(extracolormap_t *extra_colormap)
 		{
 			for (i = 0; i < 256; i++)
 			{
-				*colormap_p = NearestColor((UINT8)M_RoundUp(map[i][0]),
+				*colormap_p = NearestColorFunc((UINT8)M_RoundUp(map[i][0]),
 					(UINT8)M_RoundUp(map[i][1]),
 					(UINT8)M_RoundUp(map[i][2]));
 				colormap_p++;
diff --git a/src/r_data.h b/src/r_data.h
index 8642a69fae60a7024cbd68d839b753f31e0088cc..be003c7ec2fd448621810bc9273b60a7aadd89f4 100644
--- a/src/r_data.h
+++ b/src/r_data.h
@@ -95,7 +95,7 @@ typedef enum
 	TMCF_OVERRIDE     = 1<<13,
 } textmapcolormapflags_t;
 
-void R_GenerateLightTable(extracolormap_t *extra_colormap);
+void R_GenerateLightTable(extracolormap_t *extra_colormap, boolean uselookup);
 lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap);
 extracolormap_t * R_CreateColormapFromLinedef(char *p1, char *p2, char *p3);
 extracolormap_t* R_CreateColormap(INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 flags);
diff --git a/src/r_defs.h b/src/r_defs.h
index 74779c120317fa3a7a32168897461ae79e2d614e..6130994560ef9498466e8151819778ead1a5011b 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -58,9 +58,7 @@ typedef UINT8 lighttable_t;
 
 typedef struct colormap_s
 {
-	UINT8 rows;
-	UINT8 flags;
-	UINT8 map[0];
+	UINT8 map[NUM_PALETTE_ENTRIES];
 } colormap_t;
 
 #define CMF_FADEFULLBRIGHTSPRITES  1
diff --git a/src/r_draw.c b/src/r_draw.c
index b67acc6907b087cb2dcfdabaa868d4870384bab8..a1cad798d6a8bfab6b5cd636d66d959062fc9df1 100644
--- a/src/r_draw.c
+++ b/src/r_draw.c
@@ -133,7 +133,7 @@ UINT32 nflatxshift, nflatyshift, nflatshiftup, nflatmask;
 #define BLINK_TT_CACHE_INDEX (MAXSKINS + 5)
 #define DASHMODE_TT_CACHE_INDEX (MAXSKINS + 6)
 
-static colormap_t **translationtablecache[MAXSKINS + 7] = {NULL};
+static UINT8 **translationtablecache[MAXSKINS + 7] = {NULL};
 UINT8 skincolor_modified[MAXSKINCOLORS];
 
 static INT32 SkinToCacheIndex(INT32 skinnum)
@@ -577,9 +577,9 @@ void R_GenerateTranslationColormap(UINT8 *dest_colormap, INT32 skinnum, INT32 st
 
 	\return	Colormap. If not cached, caller should Z_Free.
 */
-colormap_t* R_GetTranslationColormap(INT32 skinnum, skincolornum_t color, UINT8 flags)
+UINT8* R_GetTranslationColormap(INT32 skinnum, skincolornum_t color, UINT8 flags)
 {
-	colormap_t* ret;
+	UINT8* ret;
 	INT32 skintableindex = SkinToCacheIndex(skinnum); // Adjust if we want the default colormap
 	INT32 starttranscolor, i;
 
@@ -587,7 +587,7 @@ colormap_t* R_GetTranslationColormap(INT32 skinnum, skincolornum_t color, UINT8
 	{
 		// Allocate table for skin if necessary
 		if (!translationtablecache[skintableindex])
-			translationtablecache[skintableindex] = Z_Calloc(MAXSKINCOLORS * sizeof(colormap_t**), PU_STATIC, NULL);
+			translationtablecache[skintableindex] = Z_Calloc(MAXSKINCOLORS * sizeof(UINT8**), PU_STATIC, NULL);
 
 		// Get colormap
 		ret = translationtablecache[skintableindex][color];
@@ -600,7 +600,7 @@ colormap_t* R_GetTranslationColormap(INT32 skinnum, skincolornum_t color, UINT8
 				{
 					INT32 skin = CacheIndexToSkin(i);
 					starttranscolor = (skin >= 0) ? skins[skin].starttranscolor : DEFAULT_STARTTRANSCOLOR;
-					R_GenerateTranslationColormap(translationtablecache[i][color]->map, skin, starttranscolor, color);
+					R_GenerateTranslationColormap(translationtablecache[i][color], skin, starttranscolor, color);
 				}
 
 			skincolor_modified[color] = false;
@@ -611,12 +611,10 @@ colormap_t* R_GetTranslationColormap(INT32 skinnum, skincolornum_t color, UINT8
 	// Generate the colormap if necessary
 	if (!ret)
 	{
-		ret = Z_MallocAlign(sizeof(colormap_t) + NUM_PALETTE_ENTRIES, (flags & GTC_CACHE) ? PU_LEVEL : PU_STATIC, NULL, 8);
-		ret->rows = 1;
-		ret->flags = flags;
+		ret = Z_MallocAlign(NUM_PALETTE_ENTRIES, (flags & GTC_CACHE) ? PU_LEVEL : PU_STATIC, NULL, 8);
 
 		starttranscolor = (skinnum >= 0) ? skins[skinnum].starttranscolor : DEFAULT_STARTTRANSCOLOR;
-		R_GenerateTranslationColormap(ret->map, skinnum, starttranscolor, color);
+		R_GenerateTranslationColormap(ret, skinnum, starttranscolor, color);
 
 		// Cache the colormap if desired
 		if (flags & GTC_CACHE)
@@ -628,7 +626,7 @@ colormap_t* R_GetTranslationColormap(INT32 skinnum, skincolornum_t color, UINT8
 
 UINT8* R_GetCachedTranslation(INT32 skinnum, skincolornum_t color)
 {
-	return R_GetTranslationColormap(skinnum, color, GTC_CACHE)->map;
+	return R_GetTranslationColormap(skinnum, color, GTC_CACHE);
 }
 
 /**	\brief	Flushes cache of translation colormaps.
@@ -645,7 +643,7 @@ void R_FlushTranslationColormapCache(void)
 
 	for (i = 0; i < (INT32)(sizeof(translationtablecache) / sizeof(translationtablecache[0])); i++)
 		if (translationtablecache[i])
-			memset(translationtablecache[i], 0, MAXSKINCOLORS * sizeof(colormap_t**));
+			memset(translationtablecache[i], 0, MAXSKINCOLORS * sizeof(UINT8**));
 }
 
 UINT16 R_GetColorByName(const char *name)
diff --git a/src/r_draw.h b/src/r_draw.h
index 62dda0719113dad9308af7d3c22464269e97f0f5..057d2afb5045ac605aac83b364503cacc15573f3 100644
--- a/src/r_draw.h
+++ b/src/r_draw.h
@@ -120,7 +120,7 @@ enum
 
 // Custom player skin translation
 // Initialize color translation tables, for player rendering etc.
-colormap_t* R_GetTranslationColormap(INT32 skinnum, skincolornum_t color, UINT8 flags);
+UINT8* R_GetTranslationColormap(INT32 skinnum, skincolornum_t color, UINT8 flags);
 UINT8* R_GetCachedTranslation(INT32 skinnum, skincolornum_t color);
 void R_FlushTranslationColormapCache(void);
 void R_GenerateTranslationColormap(UINT8 *dest_colormap, INT32 skinnum, INT32 starttranscolor, UINT16 color);