diff --git a/src/command.c b/src/command.c
index 6534fed3daf7c6ae4ed9b95097a6041ada90ffc0..00749ed8d9a79ee44c554f5564f2753e51f8f9b6 100644
--- a/src/command.c
+++ b/src/command.c
@@ -1165,7 +1165,7 @@ found:
 		if (var == &cv_forceskin)
 		{
 			var->value = R_SkinAvailable(var->string);
-			if (!R_SkinUnlock(var->value))
+			if (!R_SkinUnlock(-1, var->value))
 				var->value = -1;
 		}
 		else
@@ -1478,7 +1478,7 @@ void CV_AddValue(consvar_t *var, INT32 increment)
 			else if (newvalue >= numskins)
 				newvalue = -1;
 		} while ((oldvalue != newvalue)
-				&& !(R_SkinUnlock(newvalue)));
+				&& !(R_SkinUnlock(-1, newvalue)));
 	}
 	else
 		newvalue = var->value + increment;
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index f80011efb07abfd4b52c506c42cd7d427bd8cc6d..e2bee7e0f70134b81fbb1cbf516dd684a6a2a21e 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -528,6 +528,7 @@ static inline void resynch_write_player(resynch_pak *rsp, const size_t i)
 
 	rsp->skincolor = players[i].skincolor;
 	rsp->skin = LONG(players[i].skin);
+	rsp->availabilities = players[i].availabilities;
 	// Just in case Lua does something like
 	// modify these at runtime
 	rsp->camerascale = (fixed_t)LONG(players[i].camerascale);
@@ -657,6 +658,7 @@ static void resynch_read_player(resynch_pak *rsp)
 
 	players[i].skincolor = rsp->skincolor;
 	players[i].skin = LONG(rsp->skin);
+	players[i].availabilities = rsp->availabilities;
 	// Just in case Lua does something like
 	// modify these at runtime
 	players[i].camerascale = (fixed_t)LONG(rsp->camerascale);
diff --git a/src/d_clisrv.h b/src/d_clisrv.h
index 3823295398465efa2f1c47aa0df0f2497c0d78d7..8f0aedbf3bdd9178a47ea336753f06ecfc8a289f 100644
--- a/src/d_clisrv.h
+++ b/src/d_clisrv.h
@@ -172,6 +172,7 @@ typedef struct
 
 	UINT8 skincolor;
 	INT32 skin;
+	UINT32 availabilities;
 	// Just in case Lua does something like
 	// modify these at runtime
 	fixed_t camerascale;
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 55a3b30f9d5b43376aae0e427523d1b4be9557c4..7fcbac0922ddda17388e2c4c7edc403b9c9d8f6b 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -1109,6 +1109,8 @@ static void SendNameAndColor(void)
 	if (!Playing())
 		return;
 
+	players[consoleplayer].availabilities = R_GetSkinAvailabilities();
+
 	// If you're not in a netgame, merely update the skin, color, and name.
 	if (!netgame)
 	{
@@ -1127,7 +1129,7 @@ static void SendNameAndColor(void)
 			SetPlayerSkinByNum(consoleplayer, 0);
 			CV_StealthSet(&cv_skin, skins[0].name);
 		}
-		else if ((foundskin = R_SkinAvailable(cv_skin.string)) != -1 && R_SkinUnlock(foundskin))
+		else if ((foundskin = R_SkinAvailable(cv_skin.string)) != -1 && R_SkinUnlock(consoleplayer, foundskin))
 		{
 			boolean notsame;
 
@@ -1174,7 +1176,7 @@ static void SendNameAndColor(void)
 	// check if player has the skin loaded (cv_skin may have
 	// the name of a skin that was available in the previous game)
 	cv_skin.value = R_SkinAvailable(cv_skin.string);
-	if ((cv_skin.value < 0) || !R_SkinUnlock(cv_skin.value))
+	if ((cv_skin.value < 0) || !R_SkinUnlock(consoleplayer, cv_skin.value))
 	{
 		CV_StealthSet(&cv_skin, DEFAULTSKIN);
 		cv_skin.value = 0;
@@ -1182,6 +1184,7 @@ static void SendNameAndColor(void)
 
 	// Finally write out the complete packet and send it off.
 	WRITESTRINGN(p, cv_playername.zstring, MAXPLAYERNAME);
+	WRITEUINT32(p, (UINT32)players[consoleplayer].availabilities);
 	WRITEUINT8(p, (UINT8)cv_playercolor.value);
 	WRITEUINT8(p, (UINT8)cv_skin.value);
 	SendNetXCmd(XD_NAMEANDCOLOR, buf, p - buf);
@@ -1224,6 +1227,8 @@ static void SendNameAndColor2(void)
 	if (!Playing())
 		return;
 
+	players[secondplaya].availabilities = R_GetSkinAvailabilities();
+
 	// If you're not in a netgame, merely update the skin, color, and name.
 	if (botingame)
 	{
@@ -1252,7 +1257,7 @@ static void SendNameAndColor2(void)
 			SetPlayerSkinByNum(secondplaya, forcedskin);
 			CV_StealthSet(&cv_skin2, skins[forcedskin].name);
 		}
-		else if ((foundskin = R_SkinAvailable(cv_skin2.string)) != -1 && R_SkinUnlock(foundskin))
+		else if ((foundskin = R_SkinAvailable(cv_skin2.string)) != -1 && R_SkinUnlock(secondplaya, foundskin))
 		{
 			boolean notsame;
 
@@ -1307,6 +1312,7 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum)
 #endif
 
 	READSTRINGN(*cp, name, MAXPLAYERNAME);
+	p->availabilities = READUINT32(*cp);
 	color = READUINT8(*cp);
 	skin = READUINT8(*cp);
 
@@ -4042,7 +4048,7 @@ static void Command_Archivetest_f(void)
   */
 static void ForceSkin_OnChange(void)
 {
-	if ((server || adminplayer == consoleplayer) && ((cv_forceskin.value == -1 && stricmp(cv_forceskin.string, "None")) || !(R_SkinUnlock(cv_forceskin.value))))
+	if ((server || adminplayer == consoleplayer) && ((cv_forceskin.value == -1 && stricmp(cv_forceskin.string, "None")) || !(R_SkinUnlock(-1, cv_forceskin.value))))
 	{
 		CONS_Printf("Please provide a valid skin name (\"None\" disables).\n");
 		CV_SetValue(&cv_forceskin, -1);
diff --git a/src/d_player.h b/src/d_player.h
index f316ec251d160c20298edf21c1961f2b78fb2594..8ff591a967bcedf610a32380bd1cb02cc3b44046 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -342,6 +342,7 @@ typedef struct player_s
 	UINT8 skincolor;
 
 	INT32 skin;
+	UINT32 availabilities;
 
 	UINT32 score; // player score
 	fixed_t dashspeed; // dashing speed
diff --git a/src/doomdef.h b/src/doomdef.h
index a35c17ba6cb971663fe7d44a4ca6836ec19fe9f4..ed197e20c2d48a10ded4f3ad5d311acb6f740257 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -222,7 +222,7 @@ extern FILE *logstream;
 // NOTE: it needs more than this to increase the number of players...
 
 #define MAXPLAYERS 32
-#define MAXSKINS MAXPLAYERS
+#define MAXSKINS 32
 #define PLAYERSMASK (MAXPLAYERS-1)
 #define MAXPLAYERNAME 21
 
diff --git a/src/g_game.c b/src/g_game.c
index b20d91f9fd67902bed254f82295851ac53c1d18e..3839e0448aa800d442b0bdbb5c065cbee9b61817 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -2080,6 +2080,7 @@ void G_PlayerReborn(INT32 player)
 	UINT8 mare;
 	UINT8 skincolor;
 	INT32 skin;
+	UINT32 availabilities;
 	tic_t jointime;
 	boolean spectator;
 	INT16 bot;
@@ -2104,6 +2105,7 @@ void G_PlayerReborn(INT32 player)
 
 	skincolor = players[player].skincolor;
 	skin = players[player].skin;
+	availabilities = players[player].availabilities;
 	camerascale = players[player].camerascale;
 	shieldscale = players[player].shieldscale;
 	charability = players[player].charability;
@@ -2149,6 +2151,7 @@ void G_PlayerReborn(INT32 player)
 	// save player config truth reborn
 	p->skincolor = skincolor;
 	p->skin = skin;
+	p->availabilities = availabilities;
 	p->camerascale = camerascale;
 	p->shieldscale = shieldscale;
 	p->charability = charability;
diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c
index 93febf209424fb616c69c503c6b405946415dbd0..0935f56de454c797d0505ab31f64a03a3691b21e 100644
--- a/src/lua_mobjlib.c
+++ b/src/lua_mobjlib.c
@@ -511,6 +511,8 @@ static int mobj_set(lua_State *L)
 		for (i = 0; i < numskins; i++)
 			if (fastcmp(skins[i].name, skin))
 			{
+				if (mo->player && !R_SkinUnlock(mo->player-players, i))
+					return luaL_error(L, "mobj.skin '%s' not found!", skin);
 				mo->skin = &skins[i];
 				return 0;
 			}
diff --git a/src/m_menu.c b/src/m_menu.c
index f682cd1b596b9ef3fc62be550698d1cf5d9bd9d4..f7025693f461a9b1356b9eff3179c21ca6872e1e 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -3417,7 +3417,7 @@ static void M_PatchSkinNameTable(void)
 
 	for (j = 0; j < MAXSKINS; j++)
 	{
-		if (skins[j].name[0] != '\0' && R_SkinUnlock(j))
+		if (skins[j].name[0] != '\0' && R_SkinUnlock(-1, j))
 		{
 			skins_cons_t[j].strvalue = skins[j].realname;
 			skins_cons_t[j].value = j+1;
@@ -4780,7 +4780,7 @@ static void M_SetupChoosePlayer(INT32 choice)
 		{
 			name = strtok(Z_StrDup(description[i].skinname), "&");
 			skinnum = R_SkinAvailable(name);
-			if ((skinnum != -1) && (R_SkinUnlock(skinnum)))
+			if ((skinnum != -1) && (R_SkinUnlock(-1, skinnum)))
 			{
 				// Handling order.
 				if (firstvalid == 255)
@@ -6525,7 +6525,7 @@ static void M_HandleSetupMultiPlayer(INT32 choice)
 					if (setupm_fakeskin < 0)
 						setupm_fakeskin = numskins-1;
 				}
-				while ((prev_setupm_fakeskin != setupm_fakeskin) && !(R_SkinUnlock(setupm_fakeskin)));
+				while ((prev_setupm_fakeskin != setupm_fakeskin) && !(R_SkinUnlock(-1, setupm_fakeskin)));
 			}
 			else if (itemOn == 1) // player color
 			{
@@ -6545,7 +6545,7 @@ static void M_HandleSetupMultiPlayer(INT32 choice)
 					if (setupm_fakeskin > numskins-1)
 						setupm_fakeskin = 0;
 				}
-				while ((prev_setupm_fakeskin != setupm_fakeskin) && !(R_SkinUnlock(setupm_fakeskin)));
+				while ((prev_setupm_fakeskin != setupm_fakeskin) && !(R_SkinUnlock(-1, setupm_fakeskin)));
 			}
 			else if (itemOn == 1) // player color
 			{
diff --git a/src/r_things.c b/src/r_things.c
index 089eddba8873dcae15252703e19e8ebaba3313ab..03125f921a618e1c6636428a9f3450b9be3ab5c2 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -2554,13 +2554,26 @@ void R_InitSkins(void)
 	numskins = 0;
 }
 
+UINT32 R_GetSkinAvailabilities(void)
+{
+	INT32 s;
+	UINT32 response = 0;
+
+	for (s = 0; s < MAXSKINS; s++)
+	{
+		if (!skins[s].availability || unlockables[skins[s].availability - 1].unlocked)
+			response |= (1 << s);
+	}
+	return response;
+}
+
 // returns true if available in circumstances, otherwise nope
 // warning don't use with an invalid skinnum other than -1 which always returns true
-boolean R_SkinUnlock(INT32 skinnum)
+boolean R_SkinUnlock(INT32 playernum, INT32 skinnum)
 {
 	return ((skinnum == -1) // Simplifies things elsewhere, since there's already plenty of checks for less-than-0...
 		|| (!skins[skinnum].availability)
-		|| (unlockables[skins[skinnum].availability - 1].unlocked)
+		|| ((playernum != -1) ? (players[playernum].availabilities & (1 << skinnum)) : (unlockables[skins[skinnum].availability - 1].unlocked))
 		|| (modeattacking) // If you have someone else's run you might as well take a look
 		|| (Playing() && (R_SkinAvailable(mapheaderinfo[gamemap-1]->forcecharacter) == skinnum)) // Force 1.
 		|| (netgame && !(server || adminplayer == consoleplayer) && (cv_forceskin.value == skinnum)) // Force 2.
@@ -2588,7 +2601,7 @@ void SetPlayerSkin(INT32 playernum, const char *skinname)
 	INT32 i = R_SkinAvailable(skinname);
 	player_t *player = &players[playernum];
 
-	if ((i != -1) && (!P_IsLocalPlayer(player) || R_SkinUnlock(i)))
+	if ((i != -1) && R_SkinUnlock(playernum, i))
 	{
 		SetPlayerSkinByNum(playernum, i);
 		return;
@@ -2610,8 +2623,7 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum)
 	skin_t *skin = &skins[skinnum];
 	UINT8 newcolor = 0;
 
-	if ((skinnum >= 0 && skinnum < numskins) // Make sure it exists!
-	&& (!P_IsLocalPlayer(player) || R_SkinUnlock(skinnum))) // ...but is it allowed? We must always allow external players to change skin. The server should vet that...
+	if (skinnum >= 0 && skinnum < numskins && R_SkinUnlock(playernum, skinnum)) // Make sure it exists!
 	{
 		player->skin = skinnum;
 
diff --git a/src/r_things.h b/src/r_things.h
index 5684f8a8945550bcb9240e5d25ddbf3fb7470f80..123ab2280600a3719638bfd9a411a4daae66c4c1 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -29,8 +29,6 @@
 #define VISSPRITESPERCHUNK (1 << VISSPRITECHUNKBITS)
 #define VISSPRITEINDEXMASK (VISSPRITESPERCHUNK - 1)
 
-#define DEFAULTNIGHTSSKIN 0
-
 // Constant arrays used for psprite clipping
 //  and initializing clipping.
 extern INT16 negonearray[MAXVIDWIDTH];
@@ -71,6 +69,7 @@ void R_DrawMasked(void);
 // should be all lowercase!! S_SKIN processing does a strlwr
 #define DEFAULTSKIN "sonic"
 #define DEFAULTSKIN2 "tails" // secondary player
+#define DEFAULTNIGHTSSKIN 0
 
 typedef struct
 {
@@ -204,7 +203,8 @@ extern skin_t skins[MAXSKINS + 1];
 
 void SetPlayerSkin(INT32 playernum,const char *skinname);
 void SetPlayerSkinByNum(INT32 playernum,INT32 skinnum); // Tails 03-16-2002
-boolean R_SkinUnlock(INT32 skinnum);
+boolean R_SkinUnlock(INT32 playernum, INT32 skinnum);
+UINT32 R_GetSkinAvailabilities(void);
 INT32 R_SkinAvailable(const char *name);
 void R_AddSkins(UINT16 wadnum);