diff --git a/readme.txt b/readme.txt
index 14d9c92e7b1223e6f5b08907a6d90db6dc23bdda..71aa843f0fef3df703ae8e307b102dc67634fc4b 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,4 +1,4 @@
-Here it is! SRB2 v2.1.5 source code!
+Here it is! SRB2 v2.1.6 source code!
 
 
 Win32 with Visual C (6SP6+Processor Pack OR 7)
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index c6008e86dc3fab7c108fd34944b10179baafed33..dff495b10f632a907c9f48dd5b54706ad6a926a6 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -767,9 +767,11 @@ static inline void resynch_write_ctf(resynchend_pak *rst)
 				rst->flagplayer[i] = (SINT8)j;
 				break;
 			}
-			if (j == MAXPLAYERS)
-				I_Error("One of the flags has gone completely missing!");
-
+			if (j == MAXPLAYERS) // fine, no I_Error
+			{
+				CONS_Alert(CONS_ERROR, "One of the flags has gone completely missing...");
+				rst->flagplayer[i] = -2;
+			}
 			continue;
 		}
 
@@ -789,7 +791,9 @@ static inline void resynch_read_ctf(resynchend_pak *p)
 		players[i].gotflag = 0;
 
 	// Red flag
-	if (p->flagplayer[0] != -1) // Held by a player
+	if (p->flagplayer[0] == -2)
+		; // The server doesn't even know what happened to it...
+	else if (p->flagplayer[0] != -1) // Held by a player
 	{
 		if (!playeringame[p->flagplayer[0]])
 			 I_Error("Invalid red flag player %d who isn't in the game!", (INT32)p->flagplayer[0]);
@@ -815,7 +819,9 @@ static inline void resynch_read_ctf(resynchend_pak *p)
 	}
 
 	// Blue flag
-	if (p->flagplayer[1] != -1) // Held by a player
+	if (p->flagplayer[1] == -2)
+		; // The server doesn't even know what happened to it...
+	else if (p->flagplayer[1] != -1) // Held by a player
 	{
 		if (!playeringame[p->flagplayer[1]])
 			 I_Error("Invalid blue flag player %d who isn't in the game!", (INT32)p->flagplayer[1]);
@@ -832,11 +838,11 @@ static inline void resynch_read_ctf(resynchend_pak *p)
 			blueflag = P_SpawnMobj(0,0,0,MT_BLUEFLAG);
 
 		P_UnsetThingPosition(blueflag);
-		blueflag->x = (fixed_t)LONG(p->flagx[0]);
-		blueflag->y = (fixed_t)LONG(p->flagy[0]);
-		blueflag->z = (fixed_t)LONG(p->flagz[0]);
-		blueflag->flags2 = LONG(p->flagflags[0]);
-		blueflag->fuse = LONG(p->flagloose[0]);
+		blueflag->x = (fixed_t)LONG(p->flagx[1]);
+		blueflag->y = (fixed_t)LONG(p->flagy[1]);
+		blueflag->z = (fixed_t)LONG(p->flagz[1]);
+		blueflag->flags2 = LONG(p->flagflags[1]);
+		blueflag->fuse = LONG(p->flagloose[1]);
 		P_SetThingPosition(blueflag);
 	}
 }
@@ -1683,7 +1689,7 @@ static void CL_ConnectToServer(boolean viams)
 		}
 		if (gametypestr)
 			CONS_Printf(M_GetText("Gametype: %s\n"), gametypestr);
-		CONS_Printf(M_GetText("Version: %d.%.2d.%u\n"), serverlist[i].info.version/100,
+		CONS_Printf(M_GetText("Version: %d.%d.%u\n"), serverlist[i].info.version/100,
 		 serverlist[i].info.version%100, serverlist[i].info.subversion);
 	}
 	SL_ClearServerList(servernode);
diff --git a/src/d_main.c b/src/d_main.c
index bd8e12f2385186708d378c701404ce73bdc88b04..9475a77ae067d45e074c75e3a35462696f444578 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -1097,7 +1097,7 @@ void D_SRB2Main(void)
 	W_VerifyFileMD5(1, "a894044b555dfcc71865cee16a996e88"); // zones.dta
 	W_VerifyFileMD5(2, "4c410c1de6e0440cc5b2858dcca80c3e"); // player.dta
 	W_VerifyFileMD5(3, "85901ad4bf94637e5753d2ac2c03ea26"); // rings.dta
-	W_VerifyFileMD5(4, "4d56695e194a6fd3bc5c87610a215186"); // patch.dta
+	W_VerifyFileMD5(4, "386ab4ffc8c9fb0fa62f788a16e5c218"); // patch.dta
 
 	// don't check music.dta because people like to modify it, and it doesn't matter if they do
 	// ...except it does if they slip maps in there, and that's what W_VerifyNMUSlumps is for.
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 6b74e60241a75fde52c961eb0bf087ebc6b13770..13d4cb9d599619d5cb89f34ef3e8c6d7a371dc34 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -350,7 +350,7 @@ consvar_t cv_usemapnumlaps = {"usemaplaps", "Yes", CV_NETVAR, CV_YesNo, NULL, 0,
 // log elemental hazards -- not a netvar, is local to current player
 consvar_t cv_hazardlog = {"hazardlog", "Yes", 0, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
 
-consvar_t cv_forceskin = {"forceskin", "-1", CV_NETVAR|CV_CALL, NULL, ForceSkin_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_forceskin = {"forceskin", "-1", CV_NETVAR|CV_CALL|CV_CHEAT, NULL, ForceSkin_OnChange, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_downloading = {"downloading", "On", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_allowexitlevel = {"allowexitlevel", "No", CV_NETVAR, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
 
@@ -378,7 +378,6 @@ consvar_t cv_mute = {"mute", "Off", CV_NETVAR|CV_CALL, CV_OnOff, Mute_OnChange,
 
 consvar_t cv_sleep = {"cpusleep", "-1", CV_SAVE, sleeping_cons_t, NULL, -1, NULL, NULL, 0, 0, NULL};
 
-static boolean triggerforcedskin = false;
 INT16 gametype = GT_COOP;
 boolean splitscreen = false;
 boolean circuitmap = false;
@@ -1042,6 +1041,26 @@ UINT8 CanChangeSkin(INT32 playernum)
 	return true;
 }
 
+static void ForceAllSkins(INT32 forcedskin)
+{
+	INT32 i;
+	for (i = 0; i < MAXPLAYERS; ++i)
+	{
+		if (!playeringame[i])
+			continue;
+
+		SetPlayerSkinByNum(i, forcedskin);
+
+		// If it's me (or my brother), set appropriate skin value in cv_skin/cv_skin2
+		if (!dedicated) // But don't do this for dedicated servers, of course.
+		{
+			if (i == consoleplayer)
+				CV_StealthSet(&cv_skin, skins[forcedskin].name);
+			else if (i == secondarydisplayplayer)
+				CV_StealthSet(&cv_skin2, skins[forcedskin].name);
+		}
+	}
+}
 
 static INT32 snacpending = 0, snac2pending = 0, chmappending = 0;
 
@@ -1101,35 +1120,6 @@ static void SendNameAndColor(void)
 			SetPlayerSkinByNum(consoleplayer, 0);
 			CV_StealthSet(&cv_skin, skins[0].name);
 		}
-		else if (cv_forceskin.value >= 0 && (netgame || multiplayer)) // Server wants everyone to use the same player
-		{
-			const INT32 forcedskin = cv_forceskin.value;
-
-			if (triggerforcedskin)
-			{
-				INT32 i;
-
-				for (i = 0; i < MAXPLAYERS; i++)
-				{
-					if (playeringame[i])
-					{
-						SetPlayerSkinByNum(i, forcedskin);
-
-						// If it's me (or my brother), set appropriate skin value in cv_skin/cv_skin2
-						if (i == consoleplayer)
-							CV_StealthSet(&cv_skin, skins[forcedskin].name);
-						else if (i == secondarydisplayplayer)
-							CV_StealthSet(&cv_skin2, skins[forcedskin].name);
-					}
-				}
-				triggerforcedskin = false;
-			}
-			else
-			{
-				SetPlayerSkinByNum(consoleplayer, forcedskin);
-				CV_StealthSet(&cv_skin, skins[forcedskin].name);
-			}
-		}
 		else if ((foundskin = R_SkinAvailable(cv_skin.string)) != -1)
 		{
 			boolean notsame;
@@ -1277,7 +1267,6 @@ static void SendNameAndColor2(void)
 static void Got_NameAndColor(UINT8 **cp, INT32 playernum)
 {
 	player_t *p = &players[playernum];
-	INT32 i;
 	char name[MAXPLAYERNAME+1];
 	UINT8 color, skin;
 
@@ -1343,33 +1332,12 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum)
 	if (cv_forceskin.value >= 0 && (netgame || multiplayer)) // Server wants everyone to use the same player
 	{
 		const INT32 forcedskin = cv_forceskin.value;
+		SetPlayerSkinByNum(playernum, forcedskin);
 
-		if (triggerforcedskin)
-		{
-			for (i = 0; i < MAXPLAYERS; i++)
-			{
-				if (playeringame[i])
-				{
-					SetPlayerSkinByNum(i, forcedskin);
-
-					// If it's me (or my brother), set appropriate skin value in cv_skin/cv_skin2
-					if (i == consoleplayer)
-						CV_StealthSet(&cv_skin, skins[forcedskin].name);
-					else if (i == secondarydisplayplayer)
-						CV_StealthSet(&cv_skin2, skins[forcedskin].name);
-				}
-			}
-			triggerforcedskin = false;
-		}
-		else
-		{
-			SetPlayerSkinByNum(playernum, forcedskin);
-
-			if (playernum == consoleplayer)
-				CV_StealthSet(&cv_skin, skins[forcedskin].name);
-			else if (playernum == secondarydisplayplayer)
-				CV_StealthSet(&cv_skin2, skins[forcedskin].name);
-		}
+		if (playernum == consoleplayer)
+			CV_StealthSet(&cv_skin, skins[forcedskin].name);
+		else if (playernum == secondarydisplayplayer)
+			CV_StealthSet(&cv_skin2, skins[forcedskin].name);
 	}
 	else
 		SetPlayerSkinByNum(playernum, skin);
@@ -4179,21 +4147,27 @@ static void ForceSkin_OnChange(void)
 	if ((server || adminplayer == consoleplayer) && (cv_forceskin.value < -1 || cv_forceskin.value >= numskins))
 	{
 		if (cv_forceskin.value == -2)
-			CV_StealthSetValue(&cv_forceskin, numskins-1);
+			CV_SetValue(&cv_forceskin, numskins-1);
 		else
 		{
 			// hack because I can't restrict this and still allow added skins to be used with forceskin.
 			if (!menuactive)
 				CONS_Printf(M_GetText("Valid skin numbers are 0 to %d (-1 disables)\n"), numskins - 1);
 			CV_SetValue(&cv_forceskin, -1);
-			return;
 		}
+		return;
 	}
 
-	if (cv_forceskin.value >= 0 && (netgame || multiplayer)) // NOT in SP, silly!
+	// NOT in SP, silly!
+	if (!(netgame || multiplayer))
+		return;
+
+	if (cv_forceskin.value < 0)
+		CONS_Printf("The server has lifted the forced skin restrictions.\n");
+	else
 	{
-		triggerforcedskin = true;
-		SendNameAndColor(); // have it take effect immediately
+		CONS_Printf("The server is restricting all players to skin \"%s\".\n",skins[cv_forceskin.value].name);
+		ForceAllSkins(cv_forceskin.value);
 	}
 }
 
diff --git a/src/doomdef.h b/src/doomdef.h
index 6c9d7ebe40beec416a33beb6584774f7e6e9e65d..b38bb507475c6899c7502a1101b40341cfd2b14a 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -144,8 +144,8 @@ extern FILE *logstream;
 #define VERSIONSTRING "Trunk"
 #else
 #define VERSION    201 // Game version
-#define SUBVERSION 5  // more precise version number
-#define VERSIONSTRING "v2.1.5"
+#define SUBVERSION 6  // more precise version number
+#define VERSIONSTRING "v2.1.6"
 #endif
 
 // Modification options
@@ -201,7 +201,7 @@ extern FILE *logstream;
 // it's only for detection of the version the player is using so the MS can alert them of an update.
 // Only set it higher, not lower, obviously.
 // Note that we use this to help keep internal testing in check; this is why v2.1.0 is not version "1".
-#define MODVERSION 10
+#define MODVERSION 11
 
 
 
diff --git a/src/f_finale.c b/src/f_finale.c
index c6fde99b0d3c3d6f17aae971073c74a040d9466d..4b743238506ba8a43f6fede155a69e72ab48cda8 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -1096,10 +1096,7 @@ void F_StartCredits(void)
 	CON_ClearHUD();
 	S_StopMusic();
 
-	if (!modifiedgame && (M_SecretUnlocked(SECRET_PANDORA)) && ALL7EMERALDS(emeralds))
-		S_ChangeMusic(mus_mapl4m, false);
-	else
-		S_ChangeMusic(mus_credit, false);
+	S_ChangeMusic(mus_credit, false);
 
 	finalecount = 0;
 	animtimer = 0;
diff --git a/src/g_game.c b/src/g_game.c
index 8f0d5b910994ff8b90e75e442fa51473b6b1e143..414f27d06fc1d1a08f9347903466fa0d64000493 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -1624,7 +1624,7 @@ boolean G_Responder(event_t *ev)
 					 && (players[consoleplayer].pflags & PF_TAGIT) != (players[displayplayer].pflags & PF_TAGIT))
 						continue;
 				}
-				else if (G_RingSlingerGametype())
+				else if (G_GametypeHasSpectators() && G_RingSlingerGametype())
 				{
 					if (!players[consoleplayer].spectator)
 						continue;
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index 38b069610eb40ba152c7a2a250cd55f73ff77212..98f0ba37a4b984760470482699af858b56eaab25 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -200,7 +200,7 @@ static int libd_patchExists(lua_State *L)
 	if (!hud_running)
 		return luaL_error(L, "HUD rendering code should not be called outside of rendering hooks!");
 
-	lua_pushboolean(L, W_CheckNumForName(luaL_checkstring(L, 1)) != LUMPERROR);
+	lua_pushboolean(L, W_LumpExists(luaL_checkstring(L, 1)));
 	return 1;
 }
 
diff --git a/src/p_inter.c b/src/p_inter.c
index 398ad493acd02df5dbe9d06fc6a0c7515228700c..75a728341e6f3864d9a1dbbec12a4aa2cf36f38b 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -296,7 +296,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 		}
 
 		if (((toucher->player->pflags & PF_NIGHTSMODE) && (toucher->player->pflags & PF_DRILLING))
-		|| (toucher->player->pflags & (PF_JUMPED|PF_SPINNING))
+		|| (toucher->player->pflags & (PF_JUMPED|PF_SPINNING|PF_GLIDING))
 		|| toucher->player->powers[pw_invulnerability] || toucher->player->powers[pw_super]) // Do you possess the ability to subdue the object?
 		{
 			if (P_MobjFlip(toucher)*toucher->momz < 0)
@@ -339,7 +339,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			P_DamageMobj(toucher, special, special, 1);
 		}
 		else if (((toucher->player->pflags & PF_NIGHTSMODE) && (toucher->player->pflags & PF_DRILLING))
-		|| (toucher->player->pflags & (PF_JUMPED|PF_SPINNING))
+		|| (toucher->player->pflags & (PF_JUMPED|PF_SPINNING|PF_GLIDING))
 		|| toucher->player->powers[pw_invulnerability] || toucher->player->powers[pw_super]) // Do you possess the ability to subdue the object?
 		{
 			if (P_MobjFlip(toucher)*toucher->momz < 0)
@@ -1295,7 +1295,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 					S_StartSound(toucher, special->info->painsound);
 					return;
 				}
-				else if (((toucher->player->pflags & PF_NIGHTSMODE) && (toucher->player->pflags & PF_DRILLING)) || (toucher->player->pflags & PF_JUMPED) || (toucher->player->pflags & PF_SPINNING)
+				else if (((toucher->player->pflags & PF_NIGHTSMODE) && (toucher->player->pflags & PF_DRILLING)) || (toucher->player->pflags & (PF_JUMPED|PF_SPINNING|PF_GLIDING))
 						|| toucher->player->powers[pw_invulnerability] || toucher->player->powers[pw_super]) // Do you possess the ability to subdue the object?
 				{
 					// Shatter the shield!
@@ -2649,12 +2649,13 @@ void P_RemoveShield(player_t *player)
 
 static void P_ShieldDamage(player_t *player, mobj_t *inflictor, mobj_t *source, INT32 damage)
 {
+	// Must do pain first to set flashing -- P_RemoveShield can cause damage
+	P_DoPlayerPain(player, source, inflictor);
+
 	P_RemoveShield(player);
 
 	P_ForceFeed(player, 40, 10, TICRATE, 40 + min(damage, 100)*2);
 
-	P_DoPlayerPain(player, source, inflictor);
-
 	if (source && (source->type == MT_SPIKE || (source->type == MT_NULL && source->threshold == 43))) // spikes
 		S_StartSound(player->mo, sfx_spkdth);
 	else
diff --git a/src/p_map.c b/src/p_map.c
index eb7c6ad751a457c5df88db6d5939800ddcf4a032..25727fb3ddd6cf4762c8a1d58e5b778df6250042 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -1069,7 +1069,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 				tmsprung = true;
 			}
 			else if (thing->flags & MF_MONITOR
-				&& ((tmthing->player->pflags & PF_JUMPED) || (tmthing->player->pflags & PF_SPINNING)))
+				&& tmthing->player->pflags & (PF_JUMPED|PF_SPINNING|PF_GLIDING))
 			{
 				boolean flip = (thing->eflags & MFE_VERTICALFLIP) != 0; // Save this flag in case monitor gets removed.
 				fixed_t *momz = &tmthing->momz; // tmthing gets changed by P_DamageMobj, so we need a new pointer?! X_x;;
diff --git a/src/p_mobj.c b/src/p_mobj.c
index a7dc363fde21ac6d1e3bdbd2841f6eb6356e25b7..dd45e3fe569529a2fe8cf16b97c3f0b0d51e2cf0 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -6529,7 +6529,7 @@ void P_MobjThinker(mobj_t *mobj)
 							if (players[consoleplayer].ctfteam == 1)
 								S_StartSound(NULL, sfx_hoop1);
 
-							redflag = NULL;
+							redflag = flagmo;
 						}
 						else // MT_BLUEFLAG
 						{
@@ -6539,7 +6539,7 @@ void P_MobjThinker(mobj_t *mobj)
 							if (players[consoleplayer].ctfteam == 2)
 								S_StartSound(NULL, sfx_hoop1);
 
-							blueflag = NULL;
+							blueflag = flagmo;
 						}
 					}
 					P_RemoveMobj(mobj);
@@ -9459,7 +9459,7 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing)
 boolean P_CheckMissileSpawn(mobj_t *th)
 {
 	// move a little forward so an angle can be computed if it immediately explodes
-	if (th->flags & MF_GRENADEBOUNCE) // hack: bad! should be a flag.
+	if (!(th->flags & MF_GRENADEBOUNCE)) // hack: bad! should be a flag.
 	{
 		th->x += th->momx>>1;
 		th->y += th->momy>>1;
diff --git a/src/p_user.c b/src/p_user.c
index d5c17e0080a5f8166971185e78e64624d6a03670..322f2cd1e5e6c1c2d91e82aaabb5f69c24c8bb05 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -4071,8 +4071,17 @@ static void P_2dMovement(player_t *player)
 	if (player->exiting || player->pflags & PF_STASIS)
 	{
 		cmd->forwardmove = cmd->sidemove = 0;
-		if (player->pflags & PF_GLIDING && !player->skidtime)
-			player->pflags &= ~PF_GLIDING;
+		if (player->pflags & PF_GLIDING)
+		{
+			if (!player->skidtime)
+				player->pflags &= ~PF_GLIDING;
+			else if (player->exiting)
+			{
+				player->pflags &= ~PF_GLIDING;
+				P_SetPlayerMobjState(player->mo, S_PLAY_RUN1);
+				player->skidtime = 0;
+			}
+		}
 		if (player->pflags & PF_SPINNING && !player->exiting)
 		{
 			player->pflags &= ~PF_SPINNING;
@@ -4255,8 +4264,17 @@ static void P_3dMovement(player_t *player)
 	if (player->exiting || player->pflags & PF_STASIS)
 	{
 		cmd->forwardmove = cmd->sidemove = 0;
-		if (player->pflags & PF_GLIDING && !player->skidtime)
-			player->pflags &= ~PF_GLIDING;
+		if (player->pflags & PF_GLIDING)
+		{
+			if (!player->skidtime)
+				player->pflags &= ~PF_GLIDING;
+			else if (player->exiting)
+			{
+				player->pflags &= ~PF_GLIDING;
+				P_SetPlayerMobjState(player->mo, S_PLAY_RUN1);
+				player->skidtime = 0;
+			}
+		}
 		if (player->pflags & PF_SPINNING && !player->exiting)
 		{
 			player->pflags &= ~PF_SPINNING;
@@ -6184,9 +6202,24 @@ static void P_MovePlayer(player_t *player)
 		else if (G_GametypeHasTeams())
 		{
 			INT32 changeto = 0;
+			INT32 z, numplayersred = 0, numplayersblue = 0;
+
+			//find a team by num players, score, or random if all else fails.
+			for (z = 0; z < MAXPLAYERS; ++z)
+				if (playeringame[z])
+				{
+					if (players[z].ctfteam == 1)
+						++numplayersred;
+					else if (players[z].ctfteam == 2)
+						++numplayersblue;
+				}
+			// for z
 
-			//find a team by score, or random if all else fails.
-			if (bluescore > redscore)
+			if (numplayersblue > numplayersred)
+				changeto = 1;
+			else if (numplayersred > numplayersblue)
+				changeto = 2;
+			else if (bluescore > redscore)
 				changeto = 1;
 			else if (redscore > bluescore)
 				changeto = 2;
diff --git a/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj b/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
index fdda1c580c4298d2dcb9d83b7011d87e964310c2..16a01ee69c109a01a754ae47e87d0ce30d0ab2e7 100644
--- a/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
+++ b/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
@@ -1214,7 +1214,7 @@
 		C01FCF4B08A954540054247B /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.1.5;
+				CURRENT_PROJECT_VERSION = 2.1.6;
 				GCC_PREPROCESSOR_DEFINITIONS = (
 					"$(inherited)",
 					NORMALSRB2,
@@ -1226,7 +1226,7 @@
 		C01FCF4C08A954540054247B /* Release */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.1.5;
+				CURRENT_PROJECT_VERSION = 2.1.6;
 				GCC_ENABLE_FIX_AND_CONTINUE = NO;
 				GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
 				GCC_PREPROCESSOR_DEFINITIONS = (
diff --git a/src/st_stuff.c b/src/st_stuff.c
index 9f5a691eefbe687e953e1f50ae738a2167d2c9a5..780101d19b5b2ead693882ba74e552b20faaa6cc 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -60,7 +60,9 @@ patch_t *superprefix[MAXSKINS]; // super face status patches
 patch_t *sboscore; // Score logo
 patch_t *sbotime; // Time logo
 patch_t *sbocolon; // Colon for time
+patch_t *sboperiod; // Period for time centiseconds
 patch_t *livesback; // Lives icon background
+static patch_t *nrec_timer; // Timer for NiGHTS records
 static patch_t *sborings;
 static patch_t *sboover;
 static patch_t *timeover;
@@ -255,6 +257,8 @@ void ST_LoadGraphics(void)
 	rrings = W_CachePatchName("RRINGS", PU_HUDGFX);
 	sbotime = W_CachePatchName("SBOTIME", PU_HUDGFX); // Time logo
 	sbocolon = W_CachePatchName("SBOCOLON", PU_HUDGFX); // Colon for time
+	sboperiod = W_CachePatchName("SBOPERIO", PU_HUDGFX); // Period for time centiseconds
+	nrec_timer = W_CachePatchName("NGRTIMER", PU_HUDGFX); // Timer for NiGHTS
 	getall = W_CachePatchName("GETALL", PU_HUDGFX); // Special Stage HUD
 	timeup = W_CachePatchName("TIMEUP", PU_HUDGFX); // Special Stage HUD
 	race1 = W_CachePatchName("RACE1", PU_HUDGFX);
@@ -634,7 +638,7 @@ static void ST_drawTime(void)
 
 		if (!splitscreen && (cv_timetic.value == 2 || modeattacking)) // there's not enough room for tics in splitscreen, don't even bother trying!
 		{
-			ST_DrawPatchFromHud(HUD_TIMETICCOLON, sbocolon); // Colon
+			ST_DrawPatchFromHud(HUD_TIMETICCOLON, sboperiod); // Period
 			ST_DrawPadNumFromHud(HUD_TICS, tictrn, 2); // Tics
 		}
 	}
@@ -1201,7 +1205,34 @@ static void ST_drawNiGHTSHUD(void)
 	&& LUA_HudEnabled(hud_nightsscore)
 #endif
 	)
+	{
 		ST_DrawNightsOverlayNum(SCX(304), SCY(16), 0, stplyr->marescore, nightsnum, SKINCOLOR_STEELBLUE);
+	}
+
+	if (!stplyr->exiting
+#ifdef HAVE_BLUA
+	// TODO give this its own section for Lua
+	&& LUA_HudEnabled(hud_nightsscore)
+#endif
+	)
+	{
+		if (modeattacking == ATTACKING_NIGHTS)
+		{
+			INT32 maretime = max(stplyr->realtime - stplyr->marebegunat, 0);
+
+			ST_DrawOverlayPatch(SCX(298), SCY(180), W_CachePatchName("NGRTIMER", PU_HUDGFX));
+			ST_DrawPaddedOverlayNum(SCX(298), SCY(180), G_TicsToCentiseconds(maretime), 2);
+			ST_DrawOverlayPatch(SCX(274), SCY(180), sboperiod);
+			if (maretime < 60*TICRATE)
+				ST_DrawOverlayNum(SCX(274), SCY(180), G_TicsToSeconds(maretime));
+			else
+			{
+				ST_DrawPaddedOverlayNum(SCX(274), SCY(180), G_TicsToSeconds(maretime), 2);
+				ST_DrawOverlayPatch(SCX(250), SCY(180), sbocolon);
+				ST_DrawOverlayNum(SCX(250), SCY(180), G_TicsToMinutes(maretime, true));
+			}
+		}
+	}
 
 	// Ideya time remaining
 	if (!stplyr->exiting && stplyr->nightstime > 0
diff --git a/src/st_stuff.h b/src/st_stuff.h
index d303bc804024fb599c42aa9e7edf070439c64e7f..9873efbe749b3195082a1b331937e43fb7fcc9e3 100644
--- a/src/st_stuff.h
+++ b/src/st_stuff.h
@@ -65,6 +65,7 @@ extern patch_t *tallnum[10];
 extern patch_t *sboscore;
 extern patch_t *sbotime;
 extern patch_t *sbocolon;
+extern patch_t *sboperiod;
 extern patch_t *faceprefix[MAXSKINS]; // face status patches
 extern patch_t *superprefix[MAXSKINS]; // super face status patches
 extern patch_t *livesback;
diff --git a/src/w_wad.c b/src/w_wad.c
index 227a95464453a6ca4b691f1d02db520b4206b1b3..b09236a2078e5fa7f24e07781a77e0ef2c9557ff 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -660,6 +660,21 @@ lumpnum_t W_GetNumForName(const char *name)
 	return i;
 }
 
+// Used by Lua. Case sensitive lump checking, quickly...
+#include "fastcmp.h"
+UINT8 W_LumpExists(const char *name)
+{
+	INT32 i,j;
+	for (i = numwadfiles - 1; i >= 0; i--)
+	{
+		lumpinfo_t *lump_p = wadfiles[i]->lumpinfo;
+		for (j = 0; j < wadfiles[i]->numlumps; ++j, ++lump_p)
+			if (fastcmp(lump_p->name,name))
+				return true;
+	}
+	return false;
+}
+
 size_t W_LumpLengthPwad(UINT16 wad, UINT16 lump)
 {
 	if (!TestValidLump(wad, lump))
diff --git a/src/w_wad.h b/src/w_wad.h
index 0b2599adffe3fff87fd6c125ac2836c8ce1f48fb..76f52b0043cb9299b07f954d22ef6e29e92d546f 100644
--- a/src/w_wad.h
+++ b/src/w_wad.h
@@ -102,6 +102,7 @@ const char *W_CheckNameForNum(lumpnum_t lumpnum);
 UINT16 W_CheckNumForNamePwad(const char *name, UINT16 wad, UINT16 startlump); // checks only in one pwad
 lumpnum_t W_CheckNumForName(const char *name);
 lumpnum_t W_GetNumForName(const char *name); // like W_CheckNumForName but I_Error on LUMPERROR
+UINT8 W_LumpExists(const char *name); // Lua uses this.
 
 size_t W_LumpLengthPwad(UINT16 wad, UINT16 lump);
 size_t W_LumpLength(lumpnum_t lumpnum);
diff --git a/src/y_inter.c b/src/y_inter.c
index def5bbb13cb06780d614c3639b9370f75864ad5c..cd2201de98d234c6f8c8d4bfea0a4f1fb41c1f38 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -214,7 +214,7 @@ void Y_IntermissionDrawer(void)
 			{
 				V_DrawPaddedTallNum(hudinfo[HUD_TICS].x, hudinfo[HUD_TICS].y, V_SNAPTOLEFT,
 					G_TicsToCentiseconds(data.coop.tics), 2);
-				V_DrawScaledPatch(hudinfo[HUD_TIMETICCOLON].x, hudinfo[HUD_TIMETICCOLON].y, V_SNAPTOLEFT, sbocolon);
+				V_DrawScaledPatch(hudinfo[HUD_TIMETICCOLON].x, hudinfo[HUD_TIMETICCOLON].y, V_SNAPTOLEFT, sboperiod);
 			}
 
 			V_DrawPaddedTallNum(hudinfo[HUD_SECONDS].x, hudinfo[HUD_SECONDS].y, V_SNAPTOLEFT,