diff --git a/src/lua_colorlib.c b/src/lua_colorlib.c
index e1665a0296d5c1c7fb6e83ecf818ca2d1da67a79..2365ffb440bb27bf5ccbd7ee4c7bbc5e9edf8f6f 100644
--- a/src/lua_colorlib.c
+++ b/src/lua_colorlib.c
@@ -22,7 +22,14 @@
 #include "lua_script.h"
 #include "lua_libs.h"
 
-static colorlookup_t colormix_lut;
+#define COLORLIB_USE_LOOKUP
+
+#ifdef COLORLIB_USE_LOOKUP
+	static colorlookup_t colormix_lut;
+	#define GetNearestColor(r, g, b) GetColorLUT(&colormix_lut, r, g, b)
+#else
+	#define GetNearestColor(r, g, b) NearestPaletteColor(r, g, b, pMasterPalette)
+#endif
 
 static int colormap_get(lua_State *L)
 {
@@ -46,6 +53,13 @@ static int colormap_set(lua_State *L)
 	return 0;
 }
 
+static int colormap_len(lua_State *L)
+{
+	colormap_t *colormap = *((colormap_t **)luaL_checkudata(L, 1, META_COLORMAP));
+	lua_pushinteger(L, colormap->rows);
+	return 1;
+}
+
 static int colormap_free(lua_State *L)
 {
 	colormap_t *colormap = *((colormap_t **)luaL_checkudata(L, 1, META_COLORMAP));
@@ -53,12 +67,12 @@ static int colormap_free(lua_State *L)
 	return 0;
 }
 
-static void MakeRGBColormap(UINT8 *colormap, UINT8 cr, UINT8 cg, UINT8 cb, UINT8 ca)
+static void MakeRGBColormap(UINT8 *colormap, UINT8 cr, UINT8 cg, UINT8 cb, UINT8 ca, UINT16 start, UINT16 end)
 {
 	double r, g, b, cbrightness;
 	double map[256][3];
 
-	size_t i;
+	INT32 i;
 
 	/////////////////////
 	// Calc the RGBA mask
@@ -76,15 +90,19 @@ static void MakeRGBColormap(UINT8 *colormap, UINT8 cr, UINT8 cg, UINT8 cb, UINT8
 	cmaskg *= maskamt;
 	cmaskb *= maskamt;
 
+#ifdef COLORLIB_USE_LOOKUP
 	InitColorLUT(&colormix_lut, pMasterPalette, false);
+#endif
+
+	colormap += start;
 
 	// map[i] stores an RGB color (as double) for index i,
 	//  which is then converted to SRB2's palette
-	for (i = 0; i < 256; i++)
+	for (i = start; i <= end; i++)
 	{
-		r = pMasterPalette[i].s.red;
-		g = pMasterPalette[i].s.green;
-		b = pMasterPalette[i].s.blue;
+		r = pMasterPalette[*colormap].s.red;
+		g = pMasterPalette[*colormap].s.green;
+		b = pMasterPalette[*colormap].s.blue;
 		cbrightness = sqrt((r*r) + (g*g) + (b*b));
 
 		map[i][0] = (cbrightness * cmaskr) + (r * othermask);
@@ -99,7 +117,7 @@ static void MakeRGBColormap(UINT8 *colormap, UINT8 cr, UINT8 cg, UINT8 cb, UINT8
 		if (map[i][2] > 255.0l)
 			map[i][2] = 255.0l;
 
-		*colormap = GetColorLUT(&colormix_lut,
+		*colormap = GetNearestColor(
 			(UINT8)M_RoundUp(map[i][0]),
 			(UINT8)M_RoundUp(map[i][1]),
 			(UINT8)M_RoundUp(map[i][2]));
@@ -189,7 +207,7 @@ static void GenerateColormap(lua_State *L, UINT8 *colormap)
 			UINT8 g = luaL_checkinteger(L, 3);
 			UINT8 b = luaL_checkinteger(L, 4);
 			UINT8 a = luaL_optinteger(L, 5, 255);
-			MakeRGBColormap(colormap, r, g, b, a);
+			MakeRGBColormap(colormap, r, g, b, a, 0, NUM_PALETTE_ENTRIES - 1);
 			break;
 		}
 	}
@@ -240,7 +258,7 @@ static int lib_colormapMix(lua_State *L)
 	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;
+	INT32 end = luaL_optinteger(L, 6, NUM_PALETTE_ENTRIES) - 1, i;
 
 	if (colormapA->flags & GTC_CACHE)
 		return luaL_error(L, "colormap is read-only");
@@ -252,15 +270,17 @@ static int lib_colormapMix(lua_State *L)
 		luaL_error(L, "blend mode %d out of range (%d - %d)", AST_COPY, AST_OVERLAY);
 
 	if (start < 0 || start >= NUM_PALETTE_ENTRIES)
-		luaL_error(L, "translation start %d out of range (0 - %d)", start, NUM_PALETTE_ENTRIES-1);
+		luaL_error(L, "start index %d out of range (0 - %d)", start, NUM_PALETTE_ENTRIES-1);
 	if (end <= 0)
 		luaL_error(L, "invalid length %d (must be at least 1)", end);
 
 	end += start;
-	if (end > NUM_PALETTE_ENTRIES)
+	if (end >= NUM_PALETTE_ENTRIES)
 		end = NUM_PALETTE_ENTRIES - 1;
 
+#ifdef COLORLIB_USE_LOOKUP
 	InitColorLUT(&colormix_lut, pMasterPalette, false);
+#endif
 
 	for (i = start; i <= end; i++)
 	{
@@ -268,13 +288,55 @@ static int lib_colormapMix(lua_State *L)
 		RGBA_t bg = V_GetMasterColor(colormapA->map[i]);
 		RGBA_t fg = V_GetMasterColor(colormapB->map[i]);
 		texel.rgba = ASTBlendPixel(bg, fg, mode, amt);
-		colormapA->map[i] = GetColorLUT(&colormix_lut, texel.s.red, texel.s.green, texel.s.blue);
+		colormapA->map[i] = GetNearestColor(texel.s.red, texel.s.green, texel.s.blue);
 	}
 
 	return 0;
 }
 
-static int lib_colormapMixRGB(lua_State *L)
+static int lib_colormapBlend(lua_State *L)
+{
+	colormap_t *colormap = *((colormap_t **)luaL_checkudata(L, 1, META_COLORMAP));
+	RGBA_t fg = V_GetMasterColor((UINT8)luaL_checkinteger(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 (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);
+
+	if (mode < AST_COPY || mode > AST_OVERLAY)
+		luaL_error(L, "blend mode %d out of range (%d - %d)", AST_COPY, AST_OVERLAY);
+
+	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)
+		luaL_error(L, "invalid length %d (must be at least 1)", end);
+
+	end += start;
+	if (end >= NUM_PALETTE_ENTRIES)
+		end = NUM_PALETTE_ENTRIES - 1;
+
+#ifdef COLORLIB_USE_LOOKUP
+	InitColorLUT(&colormix_lut, pMasterPalette, false);
+#endif
+
+	for (i = start; i <= end; i++)
+	{
+		RGBA_t texel;
+		RGBA_t bg = V_GetMasterColor(colormap->map[i]);
+		texel.rgba = ASTBlendPixel(bg, fg, mode, amt);
+		colormap->map[i] = GetNearestColor(texel.s.red, texel.s.green, texel.s.blue);
+	}
+
+	return 0;
+}
+
+static int lib_colormapBlendRGB(lua_State *L)
 {
 	colormap_t *colormap = *((colormap_t **)luaL_checkudata(L, 1, META_COLORMAP));
 	UINT8 r = luaL_checkinteger(L, 2);
@@ -283,7 +345,7 @@ static int lib_colormapMixRGB(lua_State *L)
 	INT32 amt = luaL_checkinteger(L, 5);
 	INT32 mode = luaL_optinteger(L, 6, AST_TRANSLUCENT);
 	INT32 start = luaL_optinteger(L, 7, 0);
-	INT32 end = luaL_optinteger(L, 8, NUM_PALETTE_ENTRIES-1), i;
+	INT32 end = luaL_optinteger(L, 8, NUM_PALETTE_ENTRIES) - 1, i;
 
 	RGBA_t fg;
 
@@ -297,15 +359,17 @@ static int lib_colormapMixRGB(lua_State *L)
 		luaL_error(L, "blend mode %d out of range (%d - %d)", AST_COPY, AST_OVERLAY);
 
 	if (start < 0 || start >= NUM_PALETTE_ENTRIES)
-		luaL_error(L, "translation start %d out of range (0 - %d)", start, NUM_PALETTE_ENTRIES-1);
+		luaL_error(L, "start index %d out of range (0 - %d)", start, NUM_PALETTE_ENTRIES-1);
 	if (end <= 0)
 		luaL_error(L, "invalid length %d (must be at least 1)", end);
 
 	end += start;
-	if (end > NUM_PALETTE_ENTRIES)
+	if (end >= NUM_PALETTE_ENTRIES)
 		end = NUM_PALETTE_ENTRIES - 1;
 
+#ifdef COLORLIB_USE_LOOKUP
 	InitColorLUT(&colormix_lut, pMasterPalette, false);
+#endif
 
 	fg.s.red = r;
 	fg.s.green = g;
@@ -317,29 +381,87 @@ static int lib_colormapMixRGB(lua_State *L)
 		RGBA_t texel;
 		RGBA_t bg = V_GetMasterColor(colormap->map[i]);
 		texel.rgba = ASTBlendPixel(bg, fg, mode, amt);
-		colormap->map[i] = GetColorLUT(&colormix_lut, texel.s.red, texel.s.green, texel.s.blue);
+		colormap->map[i] = GetNearestColor(texel.s.red, texel.s.green, texel.s.blue);
 	}
 
 	return 0;
 }
 
+static int lib_colormapTint(lua_State *L)
+{
+	colormap_t *colormap = *((colormap_t **)luaL_checkudata(L, 1, META_COLORMAP));
+	RGBA_t fg = V_GetMasterColor((UINT8)luaL_checkinteger(L, 2));
+	INT32 amt = luaL_checkinteger(L, 3);
+	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);
+
+	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)
+		luaL_error(L, "invalid length %d (must be at least 1)", end);
+
+	end += start;
+	if (end >= NUM_PALETTE_ENTRIES)
+		end = NUM_PALETTE_ENTRIES - 1;
+
+	MakeRGBColormap(colormap->map, fg.s.red, fg.s.green, fg.s.blue, amt, start, end);
+
+	return 0;
+}
+
+static int lib_colormapTintRGB(lua_State *L)
+{
+	colormap_t *colormap = *((colormap_t **)luaL_checkudata(L, 1, META_COLORMAP));
+	UINT8 r = luaL_checkinteger(L, 2);
+	UINT8 g = luaL_checkinteger(L, 3);
+	UINT8 b = luaL_checkinteger(L, 4);
+	INT32 amt = luaL_checkinteger(L, 5);
+	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);
+
+	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)
+		luaL_error(L, "invalid length %d (must be at least 1)", end);
+
+	end += start;
+	if (end >= NUM_PALETTE_ENTRIES)
+		end = NUM_PALETTE_ENTRIES - 1;
+
+	MakeRGBColormap(colormap->map, r, g, b, amt, start, end);
+
+	return 0;
+}
+
 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));
 	INT32 start = luaL_optinteger(L, 3, 0);
-	INT32 end = luaL_optinteger(L, 4, NUM_PALETTE_ENTRIES-1), i;
+	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, "index %d out of range (0 - %d)", start, NUM_PALETTE_ENTRIES-1);
+		luaL_error(L, "start index %d out of range (0 - %d)", start, NUM_PALETTE_ENTRIES-1);
 	if (end <= 0)
 		luaL_error(L, "invalid length %d (must be at least 1)", end);
 
 	end += start;
-	if (end > NUM_PALETTE_ENTRIES)
+	if (end >= NUM_PALETTE_ENTRIES)
 		end = NUM_PALETTE_ENTRIES - 1;
 
 	for (i = start; i <= end; i++)
@@ -362,10 +484,10 @@ static int lib_colormapCopySkinColor(lua_State *L)
 		luaL_error(L, "skin color %d out of range (0 - %d)", skincolor, numskincolors-1);
 
 	if (start < 0 || start >= NUM_PALETTE_ENTRIES)
-		luaL_error(L, "translation start %d out of range (0 - %d)", start, NUM_PALETTE_ENTRIES-1);
+		luaL_error(L, "start index %d out of range (0 - %d)", start, NUM_PALETTE_ENTRIES-1);
 
-	end = start + 16;
-	if (end > NUM_PALETTE_ENTRIES)
+	end = start + 15;
+	if (end >= NUM_PALETTE_ENTRIES)
 		end = NUM_PALETTE_ENTRIES - 1;
 
 	for (i = start; i <= end; i++, j++)
@@ -378,7 +500,10 @@ static luaL_Reg lib[] = {
 	{"create", lib_colormapCreate},
 	{"generate", lib_colormapGenerate},
 	{"mix", lib_colormapMix},
-	{"mixRGB", lib_colormapMixRGB},
+	{"blend", lib_colormapBlend},
+	{"blendRGB", lib_colormapBlendRGB},
+	{"tint", lib_colormapTint},
+	{"tintRGB", lib_colormapTintRGB},
 	{"copy", lib_colormapCopy},
 	{"copySkinColor", lib_colormapCopySkinColor},
 	{NULL, NULL}
@@ -393,9 +518,12 @@ int LUA_ColorLib(lua_State *L)
 		lua_pushcfunction(L, colormap_set);
 		lua_setfield(L, -2, "__newindex");
 
+		lua_pushcfunction(L, colormap_len);
+		lua_setfield(L, -2, "__len");
+
 		lua_pushcfunction(L, colormap_free);
 		lua_setfield(L, -2, "__gc");
-	lua_pop(L,1);
+	lua_pop(L, 1);
 
 	luaL_register(L, "colormap", lib);
 	return 0;