diff --git a/src/command.c b/src/command.c
index 4973812e7c14bf87c951c92ed385b6d1689b04a1..0a46839f343f45c69034cf52918ac6a3cb4b56e2 100644
--- a/src/command.c
+++ b/src/command.c
@@ -33,6 +33,7 @@
 #include "p_setup.h"
 #include "lua_script.h"
 #include "d_netfil.h" // findfile
+#include "r_data.h" // Color_cons_t
 
 //========
 // protos.
@@ -818,6 +819,18 @@ static void COM_Help_f(void)
 					CONS_Printf("  Yes or No (On or Off, 1 or 0)\n");
 				else if (cvar->PossibleValue == CV_OnOff)
 					CONS_Printf("  On or Off (Yes or No, 1 or 0)\n");
+				else if (cvar->PossibleValue == Color_cons_t)
+				{
+					for (i = 1; i < numskincolors; ++i)
+					{
+						if (skincolors[i].accessible)
+						{
+							CONS_Printf("  %-2d : %s\n", i, skincolors[i].name);
+							if (i == cvar->value)
+								cvalue = skincolors[i].name;
+						}
+					}
+				}
 				else
 				{
 #define MINVAL 0
diff --git a/src/dehacked.c b/src/dehacked.c
index d02dc3d24ae797a5fe628bab54570e186d55bc79..8510d7a3d1ad92a98b3c8ddaf4a365a209e86ed9 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -806,8 +806,41 @@ static void readskincolor(MYFILE *f, INT32 num)
 
 			if (fastcmp(word, "NAME"))
 			{
-				deh_strlcpy(skincolors[num].name, word2,
-					sizeof (skincolors[num].name), va("Skincolor %d: name", num));
+				size_t namesize = sizeof(skincolors[num].name);
+				char truncword[namesize];
+
+				deh_strlcpy(truncword, word2, namesize, va("Skincolor %d: name", num)); // truncate here to check for dupes
+				if (truncword[0] != '\0' && (!stricmp(truncword, skincolors[SKINCOLOR_NONE].name) || R_GetColorByName(truncword)))
+				{
+					size_t lastchar = strlen(truncword);
+					char oldword[lastchar+1];
+					char dupenum = '1';
+
+					strlcpy(oldword, truncword, lastchar+1);
+					lastchar--;
+					if (lastchar == namesize-2) // exactly max length, replace last character with 0
+						truncword[lastchar] = '0';
+					else // append 0
+					{
+						strcat(truncword, "0");
+						lastchar++;
+					}
+
+					while (R_GetColorByName(truncword))
+					{
+						truncword[lastchar] = dupenum;
+						if (dupenum == '9')
+							dupenum = 'A';
+						else if (dupenum == 'Z') // give up :?
+							break;
+						else
+							dupenum++;
+					}
+
+					deh_warning("Skincolor %d: name %s is a duplicate of another skincolor's name - renamed to %s", num, oldword, truncword);
+				}
+
+				strlcpy(skincolors[num].name, truncword, namesize); // already truncated
 			}
 			else if (fastcmp(word, "RAMP"))
 			{
@@ -821,7 +854,11 @@ static void readskincolor(MYFILE *f, INT32 num)
 			}
 			else if (fastcmp(word, "INVCOLOR"))
 			{
-				skincolors[num].invcolor = (UINT16)get_number(word2);
+				UINT16 v = (UINT16)get_number(word2);
+				if (v < numskincolors)
+					skincolors[num].invcolor = v;
+				else
+					skincolors[num].invcolor = SKINCOLOR_GREEN;
 			}
 			else if (fastcmp(word, "INVSHADE"))
 			{
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 23aa060fe72a2507f0484d5c795ae04192c4fae6..3f62ef89016252908d67fe528248394a3d4784c0 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -2550,6 +2550,14 @@ static int lib_rGetColorByName(lua_State *L)
 	return 1;
 }
 
+static int lib_rGetSuperColorByName(lua_State *L)
+{
+	const char* colorname = luaL_checkstring(L, 1);
+	//HUDSAFE
+	lua_pushinteger(L, R_GetSuperColorByName(colorname));
+	return 1;
+}
+
 // Lua exclusive function, returns the name of a color from the SKINCOLOR_ constant.
 // SKINCOLOR_GREEN > "Green" for example
 static int lib_rGetNameByColor(lua_State *L)
@@ -3493,6 +3501,7 @@ static luaL_Reg lib[] = {
 
 	// r_draw
 	{"R_GetColorByName", lib_rGetColorByName},
+	{"R_GetSuperColorByName", lib_rGetSuperColorByName},
 	{"R_GetNameByColor", lib_rGetNameByColor},
 
 	// s_sound
diff --git a/src/lua_infolib.c b/src/lua_infolib.c
index 81a215c5363c96655b41782f24bb67d30aad671f..fe0ca759f4d9a199ba0d66f51fb0d86c61327257 100644
--- a/src/lua_infolib.c
+++ b/src/lua_infolib.c
@@ -19,6 +19,7 @@
 #include "z_zone.h"
 #include "r_patch.h"
 #include "r_things.h"
+#include "r_draw.h" // R_GetColorByName
 #include "doomstat.h" // luabanks[]
 
 #include "lua_script.h"
@@ -1540,7 +1541,16 @@ static int lib_setSkinColor(lua_State *L)
 			const char* n = luaL_checkstring(L, 3);
 			strlcpy(info->name, n, MAXCOLORNAME+1);
 			if (strlen(n) > MAXCOLORNAME)
-				CONS_Alert(CONS_WARNING, "skincolor_t field 'name' ('%s') longer than %d chars; shortened to %s.\n", n, MAXCOLORNAME, info->name);
+				CONS_Alert(CONS_WARNING, "skincolor_t field 'name' ('%s') longer than %d chars; clipped to %s.\n", n, MAXCOLORNAME, info->name);
+			if (strchr(info->name, ' ') != NULL)
+				CONS_Alert(CONS_WARNING, "skincolor_t field 'name' ('%s') contains spaces.\n", info->name);
+
+			if (info->name[0] != '\0') // don't check empty string for dupe
+			{
+				UINT16 dupecheck = R_GetColorByName(info->name);
+				if (!stricmp(info->name, skincolors[SKINCOLOR_NONE].name) || (dupecheck && (dupecheck != info-skincolors)))
+					CONS_Alert(CONS_WARNING, "skincolor_t field 'name' ('%s') is a duplicate of another skincolor's name.\n", info->name);
+			}
 		} else if (i == 2 || (str && fastcmp(str,"ramp"))) {
 			if (!lua_istable(L, 3) && luaL_checkudata(L, 3, META_COLORRAMP) == NULL)
 				return luaL_error(L, LUA_QL("skincolor_t") " field 'ramp' must be a table or array.");
@@ -1550,19 +1560,17 @@ static int lib_setSkinColor(lua_State *L)
 				for (j=0; j<COLORRAMPSIZE; j++)
 					info->ramp[j] = (*((UINT8 **)luaL_checkudata(L, 3, META_COLORRAMP)))[j];
 			R_FlushTranslationColormapCache();
-		} else if (i == 3 || (str && fastcmp(str,"invcolor")))
-			info->invcolor = (UINT16)luaL_checkinteger(L, 3);
-		else if (i == 4 || (str && fastcmp(str,"invshade")))
+		} else if (i == 3 || (str && fastcmp(str,"invcolor"))) {
+			UINT16 v = (UINT16)luaL_checkinteger(L, 3);
+			if (v >= numskincolors)
+				return luaL_error(L, "attempt to set skincolors[%d].invcolor to out of range value %d.", cnum, v);
+			info->invcolor = v;
+		} else if (i == 4 || (str && fastcmp(str,"invshade")))
 			info->invshade = (UINT8)luaL_checkinteger(L, 3)%COLORRAMPSIZE;
 		else if (i == 5 || (str && fastcmp(str,"chatcolor")))
 			info->chatcolor = (UINT16)luaL_checkinteger(L, 3);
-		else if (i == 6 || (str && fastcmp(str,"accessible"))) {
-			boolean v = lua_isboolean(L,3) ? lua_toboolean(L, 3) : true;
-			if (cnum < FIRSTSUPERCOLOR && v != skincolors[cnum].accessible)
-				return luaL_error(L, "skincolors[] index %d is a standard color; accessibility changes are prohibited.", i);
-			else
-				info->accessible = v;
-		}
+		else if (i == 6 || (str && fastcmp(str,"accessible")))
+			info->accessible = lua_toboolean(L, 3);
 		lua_pop(L, 1);
 	}
 	return 0;
@@ -1616,11 +1624,18 @@ static int skincolor_set(lua_State *L)
 
 	if (fastcmp(field,"name")) {
 		const char* n = luaL_checkstring(L, 3);
-		if (strchr(n, ' ') != NULL)
-			CONS_Alert(CONS_WARNING, "skincolor_t field 'name' ('%s') contains spaces.\n", n);
 		strlcpy(info->name, n, MAXCOLORNAME+1);
 		if (strlen(n) > MAXCOLORNAME)
 			CONS_Alert(CONS_WARNING, "skincolor_t field 'name' ('%s') longer than %d chars; clipped to %s.\n", n, MAXCOLORNAME, info->name);
+		if (strchr(info->name, ' ') != NULL)
+			CONS_Alert(CONS_WARNING, "skincolor_t field 'name' ('%s') contains spaces.\n", info->name);
+
+		if (info->name[0] != '\0') // don't check empty string for dupe
+		{
+			UINT16 dupecheck = R_GetColorByName(info->name);
+			if (!stricmp(info->name, skincolors[SKINCOLOR_NONE].name) || (dupecheck && (dupecheck != info-skincolors)))
+				CONS_Alert(CONS_WARNING, "skincolor_t field 'name' ('%s') is a duplicate of another skincolor's name.\n", info->name);
+		}
 	} else if (fastcmp(field,"ramp")) {
 		if (!lua_istable(L, 3) && luaL_checkudata(L, 3, META_COLORRAMP) == NULL)
 			return luaL_error(L, LUA_QL("skincolor_t") " field 'ramp' must be a table or array.");
@@ -1630,14 +1645,17 @@ static int skincolor_set(lua_State *L)
 			for (i=0; i<COLORRAMPSIZE; i++)
 				info->ramp[i] = (*((UINT8 **)luaL_checkudata(L, 3, META_COLORRAMP)))[i];
 		R_FlushTranslationColormapCache();
-	} else if (fastcmp(field,"invcolor"))
-		info->invcolor = (UINT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"invshade"))
+	} else if (fastcmp(field,"invcolor")) {
+		UINT16 v = (UINT16)luaL_checkinteger(L, 3);
+		if (v >= numskincolors)
+			return luaL_error(L, "attempt to set skincolor_t field 'invcolor' to out of range value %d.", v);
+		info->invcolor = v;
+	} else if (fastcmp(field,"invshade"))
 		info->invshade = (UINT8)luaL_checkinteger(L, 3)%COLORRAMPSIZE;
 	else if (fastcmp(field,"chatcolor"))
 		info->chatcolor = (UINT16)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"accessible"))
-		info->accessible = lua_isboolean(L,3);
+		info->accessible = lua_toboolean(L, 3);
 	else
 		CONS_Debug(DBG_LUA, M_GetText("'%s' has no field named '%s'; returning nil.\n"), "skincolor_t", field);
 	return 1;
diff --git a/src/r_draw.c b/src/r_draw.c
index 5351ef37f079b151a0c5c3effd5fe3d006109783..6492d9d36e1dd920d21a71f6d37b094c7ee3f270 100644
--- a/src/r_draw.c
+++ b/src/r_draw.c
@@ -427,12 +427,12 @@ UINT16 R_GetColorByName(const char *name)
 	for (color = 1; color < numskincolors; color++)
 		if (!stricmp(skincolors[color].name, name))
 			return color;
-	return SKINCOLOR_GREEN;
+	return SKINCOLOR_NONE;
 }
 
 UINT16 R_GetSuperColorByName(const char *name)
 {
-	UINT16 i, color = SKINCOLOR_SUPERGOLD1;
+	UINT16 i, color = SKINCOLOR_NONE;
 	char *realname = Z_Malloc(MAXCOLORNAME+1, PU_STATIC, NULL);
 	snprintf(realname, MAXCOLORNAME+1, "Super %s 1", name);
 	for (i = 1; i < numskincolors; i++)
diff --git a/src/r_skins.c b/src/r_skins.c
index 57ce382c4183618a66dde1c45a0874b48d5a7535..a1484a2b3d754c95731620e9d7dfd2cd5818acd7 100644
--- a/src/r_skins.c
+++ b/src/r_skins.c
@@ -464,12 +464,19 @@ static boolean R_ProcessPatchableFields(skin_t *skin, char *stoken, char *value)
 	GETINT(contangle)
 #undef GETINT
 
-#define GETSKINCOLOR(field) else if (!stricmp(stoken, #field)) skin->field = R_GetColorByName(value);
+#define GETSKINCOLOR(field) else if (!stricmp(stoken, #field)) \
+{ \
+	UINT16 color = R_GetColorByName(value); \
+	skin->field = (color ? color : SKINCOLOR_GREEN); \
+}
 	GETSKINCOLOR(prefcolor)
 	GETSKINCOLOR(prefoppositecolor)
 #undef GETSKINCOLOR
 	else if (!stricmp(stoken, "supercolor"))
-		skin->supercolor = R_GetSuperColorByName(value);
+	{
+		UINT16 color = R_GetSuperColorByName(value);
+		skin->supercolor = (color ? color : SKINCOLOR_SUPERGOLD1);
+	}
 
 #define GETFLOAT(field) else if (!stricmp(stoken, #field)) skin->field = FLOAT_TO_FIXED(atof(value));
 	GETFLOAT(jumpfactor)