diff --git a/debian/README.source b/debian/README.source
index 3f532203d8b50ecb2f0757e74ee21c0a1f990eac..ff2dddd41ca2c88c56206f5b26d0da2c195bf7ba 100644
--- a/debian/README.source
+++ b/debian/README.source
@@ -1,7 +1,7 @@
 srb2 for Debian
 ---------------
 
-Here it is! SRB2 v2.0 source code!
+Here it is! SRB2 v2.1.2 source code!
 
 GNU/Linux
 ~~~
diff --git a/readme.txt b/readme.txt
index 16ed7719407033e707f18e82ce8307bb3461f992..7a33a55d66789d9dcd78065e403cd3fb3627b5ca 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,4 +1,4 @@
-Here it is! SRB2 v2.1 (.1) source code!
+Here it is! SRB2 v2.1.2 source code!
 
 
 Win32 with Visual C (6SP6+Processor Pack OR 7)
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index e0c727f526abd1937749c15a197b1612f01c8adf..e1ef3c205c50431bd79bb611284340a9c79a55b6 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -986,7 +986,6 @@ static UINT32 SL_SearchServer(INT32 node)
 static void SL_InsertServer(serverinfo_pak* info, SINT8 node)
 {
 	UINT32 i;
-	boolean moved;
 
 	// search if not already on it
 	i = SL_SearchServer(node);
@@ -1008,49 +1007,8 @@ static void SL_InsertServer(serverinfo_pak* info, SINT8 node)
 	serverlist[i].info = *info;
 	serverlist[i].node = node;
 
-	// list is sorted, so move the entry until it is sorted
-	do
-	{
-		INT32 keycurr = 0, keyprev = 0, keynext = 0;
-		switch(cv_serversort.value)
-		{
-		case 0:		// Ping.
-			keycurr = (tic_t)LONG(serverlist[i].info.time);
-			if (i > 0) keyprev = (tic_t)LONG(serverlist[i-1].info.time);
-			if (i < serverlistcount - 1) keynext = (tic_t)LONG(serverlist[i+1].info.time);
-			break;
-		case 1:		// Players.
-			keycurr = serverlist[i].info.numberofplayer;
-			if (i > 0) keyprev = serverlist[i-1].info.numberofplayer;
-			if (i < serverlistcount - 1) keynext = serverlist[i+1].info.numberofplayer;
-			break;
-		case 2:		// Gametype.
-			keycurr = serverlist[i].info.gametype;
-			if (i > 0) keyprev = serverlist[i-1].info.gametype;
-			if (i < serverlistcount - 1) keynext = serverlist[i+1].info.gametype;
-			break;
-		}
-
-		moved = false;
-		if (i > 0 && keycurr < keyprev)
-		{
-			serverelem_t s;
-			s = serverlist[i];
-			serverlist[i] = serverlist[i-1];
-			serverlist[i-1] = s;
-			i--;
-			moved = true;
-		}
-		else if (i < serverlistcount - 1 && keycurr > keynext)
-		{
-			serverelem_t s;
-			s = serverlist[i];
-			serverlist[i] = serverlist[i+1];
-			serverlist[i+1] = s;
-			i++;
-			moved = true;
-		}
-	} while (moved);
+	// resort server list
+	M_SortServerList();
 }
 
 void CL_UpdateServerList(boolean internetsearch, INT32 room)
diff --git a/src/d_main.c b/src/d_main.c
index 8b97c8f0bf249b9d51d652790bb77c9c3b6388ef..61d5d409399cc9575ef6f1b1c989c4122613c337 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -1096,7 +1096,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, "c529930ee5aed6dbe33625dc8075520b"); // patch.dta
+	W_VerifyFileMD5(4, "17461512387ba6c5d7f2daa10346e1b5"); // 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 091c35ad0689b7bc645217f40397edb104685af2..0eddb5922ff16b5c2859c2cff7ba3d9592874821 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -3127,8 +3127,15 @@ static void Command_Addfile(void)
 	}
 
 	p = fn+strlen(fn);
-	while(p > fn && *p != '\\' && *p != '/' && *p != ':')
+	while(p > fn)
+	{
 		--p;
+		if (*p == '\\' || *p == '/' || *p == ':')
+		{
+			++p;
+			break;
+		}
+	}
 	WRITESTRINGN(buf_p,p,240);
 
 	{
diff --git a/src/dehacked.c b/src/dehacked.c
index a757f31e8f52871c748a944343fc3f197a84a1ac..4b1cbf76c81ac557caf8b8017a8097ed7e4d826d 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -6329,6 +6329,15 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_ORBITEM5",
 	"S_ORBITEM6",
 	"S_ORBITEM7",
+	"S_ORBITEM8",
+	"S_ORBITEM9",
+	"S_ORBITEM10",
+	"S_ORBITEM11",
+	"S_ORBITEM12",
+	"S_ORBITEM13",
+	"S_ORBITEM14",
+	"S_ORBITEM15",
+	"S_ORBITEM16",
 
 	// "Flicky" helper
 	"S_NIGHTOPIANHELPER1",
diff --git a/src/doomdef.h b/src/doomdef.h
index 6d6e745d66fd6a2fb36220a2eac98c81331ef648..a659b23203f5bf749279538eb3180d0689737ced 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 1   // more precise version number
-#define VERSIONSTRING "v2.1.1"
+#define SUBVERSION 2   // more precise version number
+#define VERSIONSTRING "v2.1.2"
 #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 5
+#define MODVERSION 6
 
 
 
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index 7fdbd1c8e4e40a9cd7f7e2d6926922b9f71cc71d..558083bdb6cc2dc0dcfafead9beb534fa5a27d52 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -604,8 +604,14 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
 		if (tempchar)
 			Z_Free(tempchar);
 	}
+#ifdef _DEBUG
+	// I just want to point out while I'm here that because the data is still
+	// sent to all players, techincally anyone can see your chat if they really
+	// wanted to, even if you used sayto or sayteam.
+	// You should never send any sensitive info through sayto for that reason.
 	else
 		CONS_Printf("Dropped chat: %d %d %s\n", playernum, target, msg);
+#endif
 }
 #endif
 
diff --git a/src/info.c b/src/info.c
index 6f65095ed269d257cde1a63dd411b6a21dd93233..e69d84f0997ccd8ee0a1cdd08e107d6d5e52e074 100644
--- a/src/info.c
+++ b/src/info.c
@@ -2782,6 +2782,15 @@ state_t states[NUMSTATES] =
 	{SPR_CEMG, FF_FULLBRIGHT+4, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM5}, // S_ORBITEM5
 	{SPR_CEMG, FF_FULLBRIGHT+5, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM6}, // S_ORBITEM6
 	{SPR_CEMG, FF_FULLBRIGHT+6, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM7}, // S_ORBITEM7
+	{SPR_CEMG, FF_FULLBRIGHT+7, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM8}, // S_ORBITEM8
+	{SPR_CEMG, FF_FULLBRIGHT+8, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM8}, // S_ORBITEM9
+	{SPR_CEMG, FF_FULLBRIGHT+9, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM8}, // S_ORBITEM10
+	{SPR_CEMG, FF_FULLBRIGHT+10, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM8}, // S_ORBITEM11
+	{SPR_CEMG, FF_FULLBRIGHT+11, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM8}, // S_ORBITEM12
+	{SPR_CEMG, FF_FULLBRIGHT+12, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM8}, // S_ORBITEM13
+	{SPR_CEMG, FF_FULLBRIGHT+13, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM8}, // S_ORBITEM14
+	{SPR_CEMG, FF_FULLBRIGHT+14, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM8}, // S_ORBITEM15
+	{SPR_CEMG, FF_FULLBRIGHT+15, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM8}, // S_ORBITEM16
 
 	// Flicky helper for NiGHTS
 	{SPR_BIRD, 0, 1, {A_OrbitNights}, ANG2*2, 180 | 0x10000, S_NIGHTOPIANHELPER2}, // S_NIGHTOPIANHELPER1
diff --git a/src/info.h b/src/info.h
index 4e3962ea56e8b76d1ea2204856b29272f80b525a..63fe1a638e1874f098da11603dee727e8ce141bd 100644
--- a/src/info.h
+++ b/src/info.h
@@ -3231,6 +3231,15 @@ typedef enum state
 	S_ORBITEM5,
 	S_ORBITEM6,
 	S_ORBITEM7,
+	S_ORBITEM8,
+	S_ORBITEM9,
+	S_ORBITEM10,
+	S_ORBITEM11,
+	S_ORBITEM12,
+	S_ORBITEM13,
+	S_ORBITEM14,
+	S_ORBITEM15,
+	S_ORBITEM16,
 
 	// "Flicky" helper
 	S_NIGHTOPIANHELPER1,
diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c
index bd399304658f0d2ec168d93473efc2b5e41cc3e5..1b4bf1dd758c0bec70c61fae2ce6024e299d96da 100644
--- a/src/lua_hooklib.c
+++ b/src/lua_hooklib.c
@@ -408,7 +408,7 @@ void LUAh_ThinkFrame(void)
 			// Remove this function from the hook table to prevent further errors.
 			lua_pushvalue(gL, -1); // key
 			lua_pushnil(gL); // value
-			lua_rawset(gL, -5); // table
+			lua_rawset(gL, -4); // table
 			CONS_Printf("Hook removed.\n");
 		}
 	}
diff --git a/src/m_menu.c b/src/m_menu.c
index fffc159c39104671f0ab2e41a9ae31b4d0436b1f..5aad4e77df5ab72fb18169847d051454f2466d72 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -195,7 +195,6 @@ static void M_StopMessage(INT32 choice);
 static void M_HandleServerPage(INT32 choice);
 static void M_RoomMenu(INT32 choice);
 #endif
-static void M_SortServerList(void);
 
 // Prototyping is fun, innit?
 // ==========================================================================
@@ -396,8 +395,11 @@ consvar_t cv_newgametype = {"newgametype", "Co-op", CV_HIDEN|CV_CALL, gametype_c
 
 static CV_PossibleValue_t serversort_cons_t[] = {
 	{0,"Ping"},
-	{1,"Players"},
-	{2,"Gametype"},
+	{1,"Modified State"},
+	{2,"Most Players"},
+	{3,"Least Players"},
+	{4,"Max Players"},
+	{5,"Gametype"},
 	{0,NULL}
 };
 consvar_t cv_serversort = {"serversort", "Ping", CV_HIDEN | CV_CALL, serversort_cons_t, M_SortServerList, 0, NULL, NULL, 0, 0, NULL};
@@ -5770,15 +5772,46 @@ static boolean M_CancelConnect(void)
 #define SERVER_LIST_ENTRY_COMPARATOR(key) \
 static int ServerListEntryComparator_##key(const void *entry1, const void *entry2) \
 { \
-	return ((const serverelem_t*)entry1)->info.key - ((const serverelem_t*)entry2)->info.key; \
+	const serverelem_t *sa = (const serverelem_t*)entry1, *sb = (const serverelem_t*)entry2; \
+	if (sa->info.key != sb->info.key) \
+		return sa->info.key - sb->info.key; \
+	return strcmp(sa->info.servername, sb->info.servername); \
+}
+
+// This does descending instead of ascending.
+#define SERVER_LIST_ENTRY_COMPARATOR_REVERSE(key) \
+static int ServerListEntryComparator_##key##_reverse(const void *entry1, const void *entry2) \
+{ \
+	const serverelem_t *sa = (const serverelem_t*)entry1, *sb = (const serverelem_t*)entry2; \
+	if (sb->info.key != sa->info.key) \
+		return sb->info.key - sa->info.key; \
+	return strcmp(sb->info.servername, sa->info.servername); \
 }
 
 SERVER_LIST_ENTRY_COMPARATOR(time)
 SERVER_LIST_ENTRY_COMPARATOR(numberofplayer)
+SERVER_LIST_ENTRY_COMPARATOR_REVERSE(numberofplayer)
+SERVER_LIST_ENTRY_COMPARATOR_REVERSE(maxplayer)
 SERVER_LIST_ENTRY_COMPARATOR(gametype)
+
+// Special one for modified state.
+static int ServerListEntryComparator_modified(const void *entry1, const void *entry2)
+{
+	const serverelem_t *sa = (const serverelem_t*)entry1, *sb = (const serverelem_t*)entry2;
+
+	// Modified acts as 2 points, cheats act as one point.
+	int modstate_a = (sa->info.cheatsenabled ? 1 : 0) | (sa->info.modifiedgame ? 2 : 0);
+	int modstate_b = (sb->info.cheatsenabled ? 1 : 0) | (sb->info.modifiedgame ? 2 : 0);
+
+	if (modstate_a != modstate_b)
+		return modstate_a - modstate_b;
+
+	// Default to strcmp.
+	return strcmp(sa->info.servername, sb->info.servername);
+}
 #endif
 
-static void M_SortServerList(void)
+void M_SortServerList(void)
 {
 #ifndef NONET
 	switch(cv_serversort.value)
@@ -5786,10 +5819,19 @@ static void M_SortServerList(void)
 	case 0:		// Ping.
 		qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_time);
 		break;
-	case 1:		// Players.
+	case 1:		// Modified state.
+		qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_modified);
+		break;
+	case 2:		// Most players.
+		qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_numberofplayer_reverse);
+		break;
+	case 3:		// Least players.
 		qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_numberofplayer);
 		break;
-	case 2:		// Gametype.
+	case 4:		// Max players.
+		qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_maxplayer_reverse);
+		break;
+	case 5:		// Gametype.
 		qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_gametype);
 		break;
 	}
diff --git a/src/m_menu.h b/src/m_menu.h
index aec882db40f8e183633244f1462ca4d3b67a13cd..302a7fade50161afb6278742ba3b2a61bd6e2b2c 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -45,6 +45,9 @@ void M_StartControlPanel(void);
 // Called upon end of a mode attack run
 void M_EndModeAttackRun(void);
 
+// Called on new server add, or other reasons
+void M_SortServerList(void);
+
 // Draws a box with a texture inside as background for messages
 void M_DrawTextBox(INT32 x, INT32 y, INT32 width, INT32 boxlines);
 
diff --git a/src/m_random.c b/src/m_random.c
index 12f646e4fa8c90b113ab12246e577efe7ab96f6c..ddf5e135b8a9a687019556c325a5415f5e572b25 100644
--- a/src/m_random.c
+++ b/src/m_random.c
@@ -93,7 +93,7 @@ UINT8 P_Random(void)
 #else
 UINT8 P_RandomD(const char *rfile, INT32 rline)
 {
-	CONS_Debug(DBG_RANDOMIZER, "P_Random() at: %sp %d\n", rfile, rline);
+	CONS_Printf("P_Random() at: %sp %d\n", rfile, rline);
 #endif
 	randomseed = (randomseed*746151647)+48205429;
 	return (UINT8)((randomseed >> 17)&255);
@@ -111,7 +111,7 @@ INT32 P_SignedRandom(void)
 #else
 INT32 P_SignedRandomD(const char *rfile, INT32 rline)
 {
-	CONS_Debug(DBG_RANDOMIZER, "P_SignedRandom() at: %sp %d\n", rfile, rline);
+	CONS_Printf("P_SignedRandom() at: %sp %d\n", rfile, rline);
 #endif
 	return P_Random() - 128;
 }
@@ -129,7 +129,7 @@ INT32 P_RandomKey(INT32 a)
 #else
 INT32 P_RandomKeyD(const char *rfile, INT32 rline, INT32 a)
 {
-	CONS_Debug(DBG_RANDOMIZER, "P_RandomKey() at: %sp %d\n", rfile, rline);
+	CONS_Printf("P_RandomKey() at: %sp %d\n", rfile, rline);
 #endif
 	return (INT32)(((P_Random()|(P_Random() << 8))/65536.0f)*a);
 }
@@ -146,7 +146,7 @@ INT32 P_RandomRange(INT32 a, INT32 b)
 #else
 INT32 P_RandomRangeD(const char *rfile, INT32 rline, INT32 a, INT32 b)
 {
-	CONS_Debug(DBG_RANDOMIZER, "P_RandomRange() at: %sp %d\n", rfile, rline);
+	CONS_Printf("P_RandomRange() at: %sp %d\n", rfile, rline);
 #endif
 	return (INT32)(((P_Random()|(P_Random() << 8))/65536.0f)*(b-a+1))+a;
 }
@@ -168,8 +168,14 @@ UINT8 P_RandomPeek(void)
   * \return Current random seed.
   * \sa P_SetRandSeed
   */
+#ifndef DEBUGRANDOM
 UINT32 P_GetRandSeed(void)
 {
+#else
+UINT32 P_GetRandSeedD(const char *rfile, INT32 rline)
+{
+	CONS_Printf("P_GetRandSeed() at: %sp %d\n", rfile, rline);
+#endif
 	return randomseed;
 }
 
@@ -178,8 +184,14 @@ UINT32 P_GetRandSeed(void)
   * \return Initial random seed.
   * \sa P_SetRandSeed
   */
+#ifndef DEBUGRANDOM
 UINT32 P_GetInitSeed(void)
 {
+#else
+UINT32 P_GetInitSeedD(const char *rfile, INT32 rline)
+{
+	CONS_Printf("P_GetInitSeed() at: %sp %d\n", rfile, rline);
+#endif
 	return initialseed;
 }
 
@@ -189,8 +201,14 @@ UINT32 P_GetInitSeed(void)
   * \param rindex New random index.
   * \sa P_GetRandSeed
   */
+#ifndef DEBUGRANDOM
 void P_SetRandSeed(UINT32 seed)
 {
+#else
+void P_SetRandSeedD(const char *rfile, INT32 rline, UINT32 seed)
+{
+	CONS_Printf("P_SetRandSeed() at: %sp %d\n", rfile, rline);
+#endif
 	randomseed = initialseed = seed;
 }
 
diff --git a/src/m_random.h b/src/m_random.h
index d8834b00e1130ba3e8d733ac4fc70f876ff5e937..42c8716086acddfa8ed7035b20433e60eb6332c7 100644
--- a/src/m_random.h
+++ b/src/m_random.h
@@ -18,6 +18,8 @@
 #include "doomtype.h"
 #include "m_fixed.h"
 
+//#define DEBUGRANDOM
+
 // M_Random functions pull random numbers of various types that aren't network synced.
 // P_Random functions pulls random bytes from a LCG PRNG that is network synced.
 
@@ -46,9 +48,18 @@ INT32 P_RandomRange(INT32 a, INT32 b);
 UINT8 P_RandomPeek(void);
 
 // Working with the seed for PRNG
+#ifdef DEBUGRANDOM
+#define P_GetRandSeed() P_GetRandSeedD(__FILE__, __LINE__)
+#define P_GetInitSeed() P_GetInitSeedD(__FILE__, __LINE__)
+#define P_SetRandSeed(s) P_SetRandSeedD(__FILE__, __LINE__, s)
+UINT32 P_GetRandSeedD(const char *rfile, INT32 rline);
+UINT32 P_GetInitSeedD(const char *rfile, INT32 rline);
+void P_SetRandSeedD(const char *rfile, INT32 rline, UINT32 seed);
+#else
 UINT32 P_GetRandSeed(void);
 UINT32 P_GetInitSeed(void);
 void P_SetRandSeed(UINT32 seed);
+#endif
 UINT32 M_RandomizedSeed(void);
 
 #endif
diff --git a/src/p_inter.c b/src/p_inter.c
index f0319d07638410c90898af121aece83df2279eb0..9954e8b61b7d2c1f766be88fe40ab3e2add26aea 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -550,7 +550,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 		// Secret emblem thingy
 		case MT_EMBLEM:
 			{
-				if (modeattacking == ATTACKING_RECORD || demoplayback || player->bot)
+				if (demoplayback || player->bot)
 					return;
 				emblemlocations[special->health-1].collected = true;
 
@@ -637,6 +637,17 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 					P_GiveEmerald(false);
 					// Don't play Ideya sound in special stage mode
 				}
+				else // Make sure that SOMEONE has the emerald, at least!
+				{
+					for (i = 0; i < MAXPLAYERS; i++)
+						if (playeringame[i] && players[i].playerstate == PST_LIVE
+						&& players[i].mo->tracer && players[i].mo->tracer->target
+						&& players[i].mo->tracer->target->type == MT_GOTEMERALD)
+							return;
+					// Well no one has an emerald, so exit anyway!
+					P_NightserizePlayer(player, special->health);
+					P_GiveEmerald(false);
+				}
 			}
 			else if (player->bonustime && !player->exiting) //After-mare bonus time/emerald reward in special stages.
 			{
@@ -1505,8 +1516,10 @@ static void P_HitDeathMessages(player_t *player, mobj_t *inflictor, mobj_t *sour
 				case MT_PLAYER:
 					if ((inflictor->player->powers[pw_shield] & SH_NOSTACK) == SH_BOMB)
 						str = M_GetText("%s%s's armageddon blast %s %s.\n");
-					else
+					else if (inflictor->player->powers[pw_invulnerability])
 						str = M_GetText("%s%s's invincibility aura %s %s.\n");
+					else
+						str = M_GetText("%s%s's tagging hand %s %s.\n");
 					break;
 				case MT_SPINFIRE:
 					str = M_GetText("%s%s's elemental fire trail %s %s.\n");
@@ -1534,7 +1547,7 @@ static void P_HitDeathMessages(player_t *player, mobj_t *inflictor, mobj_t *sour
 					if (inflictor->flags2 & MF2_RAILRING)
 						str = M_GetText("%s%s's rail ring %s %s.\n");
 					else
-						str = M_GetText("%s%s's red ring %s %s.\n");
+						str = M_GetText("%s%s's thrown ring %s %s.\n");
 					break;
 				default:
 					str = M_GetText("%s%s %s %s.\n");
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 1c4a193a262d0b8b4ed6390049eeabec5210ed17..c640a8b2909be0967798a668bbfb85a3ec7071df 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -9129,8 +9129,6 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing)
 		else if (maptol & TOL_XMAS)
 			P_SetMobjState(mobj, mobj->info->seestate);
 
-		if (mobj->tics > 0)
-			mobj->tics = 1 + P_RandomKey(mobj->tics);
 		mobj->angle = FixedAngle(mthing->angle*FRACUNIT);
 		mobj->flags |= MF_AMBUSH;
 		mthing->mobj = mobj;
@@ -9199,8 +9197,6 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing)
 			mobj->flags2 |= MF2_OBJECTFLIP;
 		}
 
-		if (mobj->tics > 0)
-			mobj->tics = 1 + P_RandomKey(mobj->tics);
 		mobj->angle = FixedAngle(mthing->angle*FRACUNIT);
 		mobj->flags |= MF_AMBUSH;
 		mthing->mobj = mobj;
@@ -9252,9 +9248,6 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing)
 				mobj->flags2 |= MF2_OBJECTFLIP;
 			}
 
-			if (mobj->tics > 0)
-				mobj->tics = 1 + P_RandomKey(mobj->tics);
-
 			mobj->angle = FixedAngle(mthing->angle*FRACUNIT);
 			if (mthing->options & MTF_AMBUSH)
 				mobj->flags |= MF_AMBUSH;
@@ -9309,9 +9302,6 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing)
 				mobj->flags2 |= MF2_OBJECTFLIP;
 			}
 
-			if (mobj->tics > 0)
-				mobj->tics = 1 + P_RandomKey(mobj->tics);
-
 			mobj->angle = FixedAngle(mthing->angle*FRACUNIT);
 			if (mthing->options & MTF_AMBUSH)
 				mobj->flags |= MF_AMBUSH;
diff --git a/src/p_saveg.c b/src/p_saveg.c
index e9f3221611b40358dabbcf64e17e184e546c1551..17db00625764fe462caf831587b38ca171fcdbe5 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -34,6 +34,15 @@
 savedata_t savedata;
 UINT8 *save_p;
 
+// Block UINT32s to attempt to ensure that the correct data is
+// being sent and received
+#define ARCHIVEBLOCK_MISC     0x7FEEDEED
+#define ARCHIVEBLOCK_PLAYERS  0x7F448008
+#define ARCHIVEBLOCK_WORLD    0x7F8C08C0
+#define ARCHIVEBLOCK_POBJS    0x7F928546
+#define ARCHIVEBLOCK_THINKERS 0x7F37037C
+#define ARCHIVEBLOCK_SPECIALS 0x7F228378
+
 // Note: This cannot be bigger
 // than an UINT16
 typedef enum
@@ -102,6 +111,8 @@ static inline void P_NetArchivePlayers(void)
 	UINT16 flags;
 //	size_t q;
 
+	WRITEUINT32(save_p, ARCHIVEBLOCK_PLAYERS);
+
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
 		if (!playeringame[i])
@@ -272,6 +283,9 @@ static inline void P_NetUnArchivePlayers(void)
 	INT32 i, j;
 	UINT16 flags;
 
+	if (READUINT32(save_p) != ARCHIVEBLOCK_PLAYERS)
+		I_Error("Bad $$$.sav at archive block Players");
+
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
 		memset(&players[i], 0, sizeof (player_t));
@@ -414,7 +428,7 @@ static inline void P_NetUnArchivePlayers(void)
 		players[i].revitem = (mobjtype_t)READUINT32(save_p);
 		players[i].actionspd = READFIXED(save_p);
 		players[i].mindash = READFIXED(save_p);
-		players[i].maxdash = READINT32(save_p);
+		players[i].maxdash = READFIXED(save_p);
 		players[i].normalspeed = READFIXED(save_p);
 		players[i].runspeed = READFIXED(save_p);
 		players[i].thrustfactor = READUINT8(save_p);
@@ -464,7 +478,7 @@ static void P_NetArchiveWorld(void)
 	INT32 statsec = 0, statline = 0;
 	const line_t *li = lines;
 	const side_t *si;
-	UINT8 *put = save_p;
+	UINT8 *put;
 
 	// reload the map just to see difference
 	const mapsector_t *ms;
@@ -473,6 +487,9 @@ static void P_NetArchiveWorld(void)
 	const sector_t *ss = sectors;
 	UINT8 diff, diff2;
 
+	WRITEUINT32(save_p, ARCHIVEBLOCK_WORLD);
+	put = save_p;
+
 	ms = W_CacheLumpNum(lastloadedmaplumpnum+ML_SECTORS, PU_CACHE);
 
 	for (i = 0; i < numsectors; i++, ss++, ms++)
@@ -653,6 +670,9 @@ static void P_NetUnArchiveWorld(void)
 	UINT8 *get;
 	UINT8 diff, diff2;
 
+	if (READUINT32(save_p) != ARCHIVEBLOCK_WORLD)
+		I_Error("Bad $$$.sav at archive block World");
+
 	get = save_p;
 
 	for (;;)
@@ -1272,6 +1292,8 @@ static void P_NetArchiveThinkers(void)
 	UINT32 diff;
 	UINT16 diff2;
 
+	WRITEUINT32(save_p, ARCHIVEBLOCK_THINKERS);
+
 	// save off the current thinkers
 	for (th = thinkercap.next; th != &thinkercap; th = th->next)
 	{
@@ -2189,6 +2211,9 @@ static void P_NetUnArchiveThinkers(void)
 	UINT8 restoreNum = false;
 	fixed_t z, floorz, ceilingz;
 
+	if (READUINT32(save_p) != ARCHIVEBLOCK_THINKERS)
+		I_Error("Bad $$$.sav at archive block Thinkers");
+
 	// remove all the current thinkers
 	currentthinker = thinkercap.next;
 	for (currentthinker = thinkercap.next; currentthinker != &thinkercap; currentthinker = next)
@@ -2247,6 +2272,9 @@ static void P_NetUnArchiveThinkers(void)
 				else
 					mobj = Z_Calloc(sizeof (*mobj), PU_LEVEL, NULL);
 
+				// declare this as a valid mobj as soon as possible.
+				mobj->thinker.function.acp1 = (actionf_p1)P_MobjThinker;
+
 				mobj->z = z;
 				mobj->floorz = floorz;
 				mobj->ceilingz = ceilingz;
@@ -2279,7 +2307,7 @@ static void P_NetUnArchiveThinkers(void)
 				{
 					mobj->x = mobj->spawnpoint->x << FRACBITS;
 					mobj->y = mobj->spawnpoint->y << FRACBITS;
-					mobj->angle = ANGLE_45 * (mobj->spawnpoint->angle/45); /// \bug unknown
+					mobj->angle = FixedAngle(mobj->spawnpoint->angle*FRACUNIT);
 				}
 				if (diff & MD_MOM)
 				{
@@ -2421,7 +2449,6 @@ static void P_NetUnArchiveThinkers(void)
 						mobj->player->viewz = mobj->player->mo->z + mobj->player->viewheight;
 				}
 
-				mobj->thinker.function.acp1 = (actionf_p1)P_MobjThinker;
 				P_AddThinker(&mobj->thinker);
 
 				mobj->info = (mobjinfo_t *)next; // temporarily, set when leave this function
@@ -2628,6 +2655,8 @@ static inline void P_ArchivePolyObjects(void)
 {
 	INT32 i;
 
+	WRITEUINT32(save_p, ARCHIVEBLOCK_POBJS);
+
 	// save number of polyobjects
 	WRITEINT32(save_p, numPolyObjects);
 
@@ -2639,6 +2668,9 @@ static inline void P_UnArchivePolyObjects(void)
 {
 	INT32 i, numSavedPolys;
 
+	if (READUINT32(save_p) != ARCHIVEBLOCK_POBJS)
+		I_Error("Bad $$$.sav at archive block Pobjs");
+
 	numSavedPolys = READINT32(save_p);
 
 	if (numSavedPolys != numPolyObjects)
@@ -2752,6 +2784,8 @@ static inline void P_NetArchiveSpecials(void)
 {
 	size_t i, z;
 
+	WRITEUINT32(save_p, ARCHIVEBLOCK_SPECIALS);
+
 	// itemrespawn queue for deathmatch
 	i = iquetail;
 	while (iquehead != i)
@@ -2786,6 +2820,9 @@ static void P_NetUnArchiveSpecials(void)
 	size_t i;
 	INT32 j;
 
+	if (READUINT32(save_p) != ARCHIVEBLOCK_SPECIALS)
+		I_Error("Bad $$$.sav at archive block Specials");
+
 	// BP: added save itemrespawn queue for deathmatch
 	iquetail = iquehead = 0;
 	while ((i = READUINT32(save_p)) != 0xffffffff)
@@ -2878,14 +2915,17 @@ static void P_NetArchiveMisc(void)
 	UINT32 pig = 0;
 	INT32 i;
 
+	WRITEUINT32(save_p, ARCHIVEBLOCK_MISC);
+
 	WRITEINT16(save_p, gamemap);
-	WRITEUINT16(save_p, gamestate);
+	WRITEINT16(save_p, gamestate);
 
 	for (i = 0; i < MAXPLAYERS; i++)
 		pig |= (playeringame[i] != 0)<<i;
 	WRITEUINT32(save_p, pig);
 
 	WRITEUINT32(save_p, P_GetRandSeed());
+
 	WRITEUINT32(save_p, tokenlist);
 
 	WRITEUINT32(save_p, leveltime);
@@ -2934,6 +2974,9 @@ static inline boolean P_NetUnArchiveMisc(void)
 	UINT32 pig;
 	INT32 i;
 
+	if (READUINT32(save_p) != ARCHIVEBLOCK_MISC)
+		I_Error("Bad $$$.sav at archive block Misc");
+
 	gamemap = READINT16(save_p);
 
 	// gamemap changed; we assume that its map header is always valid,
@@ -2941,13 +2984,17 @@ static inline boolean P_NetUnArchiveMisc(void)
 	if(!mapheaderinfo[gamemap-1])
 		P_AllocMapHeader(gamemap-1);
 
+	// tell the sound code to reset the music since we're skipping what
+	// normally sets this flag
+	mapmusic |= MUSIC_RELOADRESET;
+
 	G_SetGamestate(READINT16(save_p));
 
 	pig = READUINT32(save_p);
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
 		playeringame[i] = (pig & (1<<i)) != 0;
-		players[i].playerstate = PST_REBORN;
+		// playerstate is set in unarchiveplayers
 	}
 
 	P_SetRandSeed(READUINT32(save_p));
@@ -3086,6 +3133,9 @@ boolean P_LoadNetGame(void)
 	LUA_UnArchive();
 #endif
 
+	// This is stupid and hacky, but maybe it'll work!
+	P_SetRandSeed(P_GetInitSeed());
+
 	// The precipitation would normally be spawned in P_SetupLevel, which is called by
 	// P_NetUnArchiveMisc above. However, that would place it up before P_NetUnArchiveThinkers,
 	// so the thinkers would be deleted later. Therefore, P_SetupLevel will *not* spawn
diff --git a/src/p_setup.c b/src/p_setup.c
index 0564b56da2a74f6ed6bfa1782c655cc4982ad811..25bcd0d57e6ec092ab1bd1897adfe022ec62726a 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -1055,7 +1055,7 @@ static inline void P_SpawnEmblems(void)
 static void P_SpawnSecretItems(boolean loademblems)
 {
 	// Now let's spawn those funky emblem things! Tails 12-08-2002
-	if (netgame || multiplayer || (modifiedgame && !savemoddata) || modeattacking == ATTACKING_RECORD) // No cheating!!
+	if (netgame || multiplayer || (modifiedgame && !savemoddata)) // No cheating!!
 		return;
 
 	if (loademblems)
diff --git a/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj b/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
index cf9ceec65d3bce9663b19f1549027b7f1fcf43f0..f898a993435663a23f26bc22af4e8b9a7e2a38c8 100644
--- a/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
+++ b/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
@@ -1212,7 +1212,7 @@
 		C01FCF4B08A954540054247B /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.1.1;
+				CURRENT_PROJECT_VERSION = 2.1.2;
 				GCC_PREPROCESSOR_DEFINITIONS = (
 					"$(inherited)",
 					NORMALSRB2,
@@ -1224,7 +1224,7 @@
 		C01FCF4C08A954540054247B /* Release */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.1.1;
+				CURRENT_PROJECT_VERSION = 2.1.2;
 				GCC_ENABLE_FIX_AND_CONTINUE = NO;
 				GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
 				GCC_PREPROCESSOR_DEFINITIONS = (
diff --git a/src/sounds.c b/src/sounds.c
index 67574309759c29d413deb12df9c42f3e9fd6dc78..a1e3bd791f0198ea8b8ccbe76f3f77ae3919dbd1 100644
--- a/src/sounds.c
+++ b/src/sounds.c
@@ -1233,7 +1233,7 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"statu1",  true,  64,  2, -1, NULL, 0,        -1,  -1, LUMPERROR},
   {"statu2",  true,  64,  2, -1, NULL, 0,        -1,  -1, LUMPERROR},
   {"strpst",  true, 192,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Starpost Sound Tails 07-04-2002
-  {"supert", false, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
+  {"supert",  true, 127,  2, -1, NULL, 0,        -1,  -1, LUMPERROR},
   {"telept", false,  32,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
   {"tink" ,  false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
   {"token" ,  true, 224,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // SS token
diff --git a/src/st_stuff.c b/src/st_stuff.c
index cd74ccee4f3f5e96f7cce2baeac26ab36798c42f..56ce8e4e95504ec7e4281ecc5e03569962c8502f 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -796,41 +796,54 @@ static void ST_drawFirstPersonHUD(void)
 {
 	player_t *player = stplyr;
 	patch_t *p = NULL;
+	UINT16 invulntime = 0;
+
+	if (player->playerstate != PST_LIVE)
+		return;
 
 	// Graue 06-18-2004: no V_NOSCALESTART, no SCX, no SCY, snap to right
 	if (player->powers[pw_shield] & SH_FORCE)
 	{
 		if ((player->powers[pw_shield] & 0xFF) > 0 || leveltime & 1)
-			V_DrawScaledPatch(304, STRINGY(32), V_SNAPTORIGHT|V_TRANSLUCENT, forceshield);
+			p = forceshield;
 	}
 	else switch (player->powers[pw_shield] & SH_NOSTACK)
 	{
-	case SH_JUMP:
-		V_DrawScaledPatch(304, STRINGY(32), V_SNAPTORIGHT|V_TRANSLUCENT, jumpshield);
-		break;
-	case SH_ELEMENTAL:
-		V_DrawScaledPatch(304, STRINGY(32), V_SNAPTORIGHT|V_TRANSLUCENT, watershield);
-		break;
-	case SH_BOMB:
-		V_DrawScaledPatch(304, STRINGY(32), V_SNAPTORIGHT|V_TRANSLUCENT, bombshield);
-		break;
-	case SH_ATTRACT:
-		V_DrawScaledPatch(304, STRINGY(32), V_SNAPTORIGHT|V_TRANSLUCENT, ringshield);
-		break;
-	case SH_PITY:
-		V_DrawScaledPatch(304, STRINGY(32), V_SNAPTORIGHT|V_TRANSLUCENT, pityshield);
-		break;
-	default:
-		break;
-	}
-
-	if (player->playerstate != PST_DEAD && ((player->powers[pw_invulnerability] > 3*TICRATE || (player->powers[pw_invulnerability]
-		&& leveltime & 1)) || ((player->powers[pw_flashing] && leveltime & 1))))
-		V_DrawScaledPatch(304, STRINGY(60), V_SNAPTORIGHT|V_TRANSLUCENT, invincibility);
-
-	if (player->powers[pw_sneakers] > 3*TICRATE || (player->powers[pw_sneakers]
-		&& leveltime & 1))
-		V_DrawScaledPatch(304, STRINGY(88), V_SNAPTORIGHT|V_TRANSLUCENT, sneakers);
+	case SH_JUMP:      p = jumpshield;  break;
+	case SH_ELEMENTAL: p = watershield; break;
+	case SH_BOMB:      p = bombshield;  break;
+	case SH_ATTRACT:   p = ringshield;  break;
+	case SH_PITY:      p = pityshield;  break;
+	default: break;
+	}
+
+	if (p)
+	{
+		if (splitscreen)
+			V_DrawSmallScaledPatch(312, STRINGY(24), V_SNAPTORIGHT|V_SNAPTOTOP|V_TRANSLUCENT, p);
+		else
+			V_DrawScaledPatch(304, 24, V_SNAPTORIGHT|V_SNAPTOTOP|V_TRANSLUCENT, p);
+	}
+
+	// pw_flashing just sets the icon to flash no matter what.
+	invulntime = player->powers[pw_flashing] ? 1 : player->powers[pw_invulnerability];
+	if (invulntime > 3*TICRATE || (invulntime && leveltime & 1))
+	{
+		if (splitscreen)
+			V_DrawSmallScaledPatch(312, STRINGY(24) + 14, V_SNAPTORIGHT|V_SNAPTOTOP|V_TRANSLUCENT, invincibility);
+		else
+			V_DrawScaledPatch(304, 24 + 28, V_SNAPTORIGHT|V_SNAPTOTOP|V_TRANSLUCENT, invincibility);
+	}
+
+	if (player->powers[pw_sneakers] > 3*TICRATE || (player->powers[pw_sneakers] && leveltime & 1))
+	{
+		if (splitscreen)
+			V_DrawSmallScaledPatch(312, STRINGY(24) + 28, V_SNAPTORIGHT|V_SNAPTOTOP|V_TRANSLUCENT, sneakers);
+		else
+			V_DrawScaledPatch(304, 24 + 56, V_SNAPTORIGHT|V_SNAPTOTOP|V_TRANSLUCENT, sneakers);
+	}
+
+	p = NULL;
 
 	// Display the countdown drown numbers!
 	if ((player->powers[pw_underwater] <= 11*TICRATE + 1
@@ -1505,15 +1518,15 @@ static void ST_drawCTFHUD(void)
 			break; // both flags were found, let's stop early
 	}
 
-	if (stplyr->gotflag & GF_REDFLAG)
-	{
-		// YOU HAVE THE RED FLAG
-		V_DrawScaledPatch(224, (splitscreen) ? STRINGY(160) : STRINGY(176), 0, gotrflag);
-	}
-	else if (stplyr->gotflag & GF_BLUEFLAG)
+	// YOU have a flag. Display a monitor-like icon for it.
+	if (stplyr->gotflag)
 	{
-		// YOU HAVE THE BLUE FLAG
-		V_DrawScaledPatch(224, (splitscreen) ? STRINGY(160) : STRINGY(176), 0, gotbflag);
+		patch_t *p = (stplyr->gotflag & GF_REDFLAG) ? gotrflag : gotbflag;
+
+		if (splitscreen)
+			V_DrawSmallScaledPatch(312, STRINGY(24) + 42, V_SNAPTORIGHT|V_SNAPTOTOP|V_TRANSLUCENT, p);
+		else
+			V_DrawScaledPatch(304, 24 + 84, V_SNAPTORIGHT|V_SNAPTOTOP|V_TRANSLUCENT, p);
 	}
 
 	// Display a countdown timer showing how much time left until the flag your team dropped returns to base.
diff --git a/src/y_inter.c b/src/y_inter.c
index 9e98ee4c41a2b304b389a3707e300e30a624429d..e35f2855321f37f066a7f563efc326c1cfc8053d 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -1058,8 +1058,11 @@ void Y_StartIntermission(void)
 		case int_spec: // coop or single player, special stage
 		{
 			// Update visitation flags?
-			if (!stagefailed)
-				mapvisited[gamemap-1] |= MV_BEATEN;
+			if ((!modifiedgame || savemoddata) && !multiplayer && !demoplayback)
+			{
+				if (!stagefailed)
+					mapvisited[gamemap-1] |= MV_BEATEN;
+			}
 
 			// give out ring bonuses
 			Y_AwardSpecialStageBonus();