diff --git a/src/b_bot.c b/src/b_bot.c
index c300466ad65d00733657c38acb7a1ffb3986d02e..f83aaa34ce7d9e175374218d4ca2312918809e0f 100644
--- a/src/b_bot.c
+++ b/src/b_bot.c
@@ -339,27 +339,6 @@ static void B_BuildTailsTiccmd(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
 	jump_last = jump;
 	spin_last = spin;
 
-	// ********
-	// Thinkfly overlay
-	if (thinkfly)
-	{
-		if (!tails->hnext)
-		{
-			P_SetTarget(&tails->hnext, P_SpawnMobjFromMobj(tails, 0, 0, 0, MT_OVERLAY));
-			if (tails->hnext)
-			{
-				P_SetTarget(&tails->hnext->target, tails);
-				P_SetTarget(&tails->hnext->hprev, tails);
-				P_SetMobjState(tails->hnext, S_FLIGHTINDICATOR);
-			}
-		}
-	}
-	else if (tails->hnext && tails->hnext->type == MT_OVERLAY && tails->hnext->state == states+S_FLIGHTINDICATOR)
-	{
-		P_RemoveMobj(tails->hnext);
-		P_SetTarget(&tails->hnext, NULL);
-	}
-
 	// Turn the virtual keypresses into ticcmd_t.
 	B_KeysToTiccmd(tails, cmd, forward, backward, left, right, false, false, jump, spin);
 
@@ -569,3 +548,30 @@ void B_RespawnBot(INT32 playernum)
 	P_SetScale(tails, sonic->scale);
 	tails->destscale = sonic->destscale;
 }
+
+void B_HandleFlightIndicator(player_t *player)
+{
+	mobj_t *tails = player->mo;
+
+	if (!tails)
+		return;
+
+	if (thinkfly && player->bot == 1 && tails->health)
+	{
+		if (!tails->hnext)
+		{
+			P_SetTarget(&tails->hnext, P_SpawnMobjFromMobj(tails, 0, 0, 0, MT_OVERLAY));
+			if (tails->hnext)
+			{
+				P_SetTarget(&tails->hnext->target, tails);
+				P_SetTarget(&tails->hnext->hprev, tails);
+				P_SetMobjState(tails->hnext, S_FLIGHTINDICATOR);
+			}
+		}
+	}
+	else if (tails->hnext && tails->hnext->type == MT_OVERLAY && tails->hnext->state == states+S_FLIGHTINDICATOR)
+	{
+		P_RemoveMobj(tails->hnext);
+		P_SetTarget(&tails->hnext, NULL);
+	}
+}
diff --git a/src/b_bot.h b/src/b_bot.h
index b42577c5c94cbc0139b9f359886f7f32e8991f73..54ef300a342e15dc50008dec5a1abaa7b21be4ef 100644
--- a/src/b_bot.h
+++ b/src/b_bot.h
@@ -15,3 +15,4 @@ void B_KeysToTiccmd(mobj_t *mo, ticcmd_t *cmd, boolean forward, boolean backward
 boolean B_CheckRespawn(player_t *player);
 void B_MoveBlocked(player_t *player);
 void B_RespawnBot(INT32 playernum);
+void B_HandleFlightIndicator(player_t *player);
diff --git a/src/command.c b/src/command.c
index 51bfd70c74275f5500b7448fcc09d2a2c956d603..0afc0711815dd551e8fb6bcc9a9e53e2beccb69f 100644
--- a/src/command.c
+++ b/src/command.c
@@ -54,7 +54,7 @@ static void COM_Add_f(void);
 static void CV_EnforceExecVersion(void);
 static boolean CV_FilterVarByVersion(consvar_t *v, const char *valstr);
 static boolean CV_Command(void);
-static consvar_t *CV_FindVar(const char *name);
+consvar_t *CV_FindVar(const char *name);
 static const char *CV_StringValue(const char *var_name);
 static consvar_t *consvar_vars; // list of registered console variables
 
@@ -1106,7 +1106,7 @@ static const char *cv_null_string = "";
   * \return Pointer to the variable if found, or NULL.
   * \sa CV_FindNetVar
   */
-static consvar_t *CV_FindVar(const char *name)
+consvar_t *CV_FindVar(const char *name)
 {
 	consvar_t *cvar;
 
diff --git a/src/command.h b/src/command.h
index b1026437f131e75cc4b1fb84d12458f02d1f5d22..42bd6eb84fccc3b99c903e54ae8ca20560d914e0 100644
--- a/src/command.h
+++ b/src/command.h
@@ -149,6 +149,9 @@ void CV_ToggleExecVersion(boolean enable);
 // register a variable for use at the console
 void CV_RegisterVar(consvar_t *variable);
 
+// returns a console variable by name
+consvar_t *CV_FindVar(const char *name);
+
 // sets changed to 0 for every console variable
 void CV_ClearChangedFlags(void);
 
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index cbc1049c970612c91ac579e8884885729ed41d8c..46f404f9ad3d89e434886aa3306253080c4fd0c9 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -1293,7 +1293,8 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime)
 
 	netbuffer->u.serverinfo.numberofplayer = (UINT8)D_NumPlayers();
 	netbuffer->u.serverinfo.maxplayer = (UINT8)cv_maxplayers.value;
-	netbuffer->u.serverinfo.gametype = (UINT8)gametype;
+	strncpy(netbuffer->u.serverinfo.gametypename, Gametype_Names[gametype],
+			sizeof netbuffer->u.serverinfo.gametypename);
 	netbuffer->u.serverinfo.modifiedgame = (UINT8)modifiedgame;
 	netbuffer->u.serverinfo.cheatsenabled = CV_CheatsEnabled();
 	netbuffer->u.serverinfo.isdedicated = (UINT8)dedicated;
@@ -2122,13 +2123,10 @@ static void CL_ConnectToServer(boolean viams)
 
 	if (i != -1)
 	{
-		UINT16 num = serverlist[i].info.gametype;
-		const char *gametypestr = NULL;
+		char *gametypestr = serverlist[i].info.gametypename;
 		CONS_Printf(M_GetText("Connecting to: %s\n"), serverlist[i].info.servername);
-		if (num < gametypecount)
-			gametypestr = Gametype_Names[num];
-		if (gametypestr)
-			CONS_Printf(M_GetText("Gametype: %s\n"), gametypestr);
+		gametypestr[sizeof serverlist[i].info.gametypename - 1] = '\0';
+		CONS_Printf(M_GetText("Gametype: %s\n"), gametypestr);
 		CONS_Printf(M_GetText("Version: %d.%d.%u\n"), serverlist[i].info.version/100,
 		 serverlist[i].info.version%100, serverlist[i].info.subversion);
 	}
@@ -3656,6 +3654,8 @@ static void HandleServerInfo(SINT8 node)
 	netbuffer->u.serverinfo.servername[MAXSERVERNAME-1] = 0;
 	netbuffer->u.serverinfo.application
 		[sizeof netbuffer->u.serverinfo.application - 1] = '\0';
+	netbuffer->u.serverinfo.gametypename
+		[sizeof netbuffer->u.serverinfo.gametypename - 1] = '\0';
 
 	SL_InsertServer(&netbuffer->u.serverinfo, node);
 }
diff --git a/src/d_clisrv.h b/src/d_clisrv.h
index 49f8afc7630c6123a705f2ed2e66d3d150cab07b..c797e5ca8b4cde824851312d7757f7ca23bb9770 100644
--- a/src/d_clisrv.h
+++ b/src/d_clisrv.h
@@ -27,7 +27,7 @@ This version is independent of the mod name, and standard
 version and subversion. It should only account for the
 basic fields of the packet, and change infrequently.
 */
-#define PACKETVERSION 0
+#define PACKETVERSION 1
 
 // Network play related stuff.
 // There is a data struct that stores network
@@ -361,7 +361,7 @@ typedef struct
 	UINT8 subversion;
 	UINT8 numberofplayer;
 	UINT8 maxplayer;
-	UINT8 gametype;
+	char gametypename[24];
 	UINT8 modifiedgame;
 	UINT8 cheatsenabled;
 	UINT8 isdedicated;
diff --git a/src/d_main.c b/src/d_main.c
index 61e18edab97bcb206f3b324ebfcff0783969560a..92492e74715aa9e1717387305d4fa683c793b763 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -766,7 +766,7 @@ void D_StartTitle(void)
 
 	if (netgame)
 	{
-		if (gametype == GT_COOP)
+		if (gametyperules & GTR_CAMPAIGN)
 		{
 			G_SetGamestate(GS_WAITINGPLAYERS); // hack to prevent a command repeat
 
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 4edeee355efb491a0f1c6ed8939f9f4bd1de4b2d..29e68143c49d1d363d9aa9fee5b3d2af276e399f 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -3726,7 +3726,7 @@ static void CoopStarposts_OnChange(void)
 {
 	INT32 i;
 
-	if (!(netgame || multiplayer) || gametype != GT_COOP)
+	if (!(netgame || multiplayer) || !G_GametypeUsesCoopStarposts())
 		return;
 
 	switch (cv_coopstarposts.value)
@@ -3781,7 +3781,7 @@ static void CoopLives_OnChange(void)
 {
 	INT32 i;
 
-	if (!(netgame || multiplayer) || gametype != GT_COOP)
+	if (!(netgame || multiplayer) || !G_GametypeUsesCoopLives())
 		return;
 
 	switch (cv_cooplives.value)
diff --git a/src/dehacked.c b/src/dehacked.c
index 986b543bc5988ce660e2e8eb6688552f2edfae0d..161a41c461126447def70035894f403741032661 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -8906,32 +8906,35 @@ static const char *const GAMETYPERULE_LIST[] = {
 	"CAMPAIGN",
 	"RINGSLINGER",
 	"SPECTATORS",
-	"FRIENDLYFIRE",
 	"LIVES",
 	"TEAMS",
+	"FIRSTPERSON",
+	"POWERSTONES",
+	"TEAMFLAGS",
+	"FRIENDLY",
+	"SPECIALSTAGES",
+	"EMERALDTOKENS",
+	"EMERALDHUNT",
 	"RACE",
 	"TAG",
 	"POINTLIMIT",
 	"TIMELIMIT",
-	"HIDETIME",
+	"OVERTIME",
+	"HURTMESSAGES",
+	"FRIENDLYFIRE",
+	"STARTCOUNTDOWN",
 	"HIDEFROZEN",
 	"BLINDFOLDED",
-	"FIRSTPERSON",
-	"MATCHEMERALDS",
-	"TEAMFLAGS",
+	"RESPAWNDELAY",
 	"PITYSHIELD",
 	"DEATHPENALTY",
 	"NOSPECTATORSPAWN",
 	"DEATHMATCHSTARTS",
-	"SPECIALSTAGES",
-	"EMERALDTOKENS",
-	"EMERALDHUNT",
+	"SPAWNINVUL",
 	"SPAWNENEMIES",
 	"ALLOWEXIT",
 	"NOTITLECARD",
-	"OVERTIME",
-	"HURTMESSAGES",
-	"SPAWNINVUL",
+	"CUTSCENES",
 	NULL
 };
 
diff --git a/src/doomdef.h b/src/doomdef.h
index 7d65398d7302723b5bb481a0b0e4426f750f0480..0a98c874aa5eb63ace8d9e04021df642d7790f80 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -464,6 +464,8 @@ extern void *(*M_Memcpy)(void* dest, const void* src, size_t n) FUNCNONNULL;
 char *va(const char *format, ...) FUNCPRINTF;
 char *M_GetToken(const char *inputString);
 void M_UnGetToken(void);
+UINT32 M_GetTokenPos(void);
+void M_SetTokenPos(UINT32 newPos);
 char *sizeu1(size_t num);
 char *sizeu2(size_t num);
 char *sizeu3(size_t num);
diff --git a/src/doomstat.h b/src/doomstat.h
index 36ef44c2609604c2ab3d48edb34eebc3d209ed95..7e961677fb3221d51ca6fa3e71a4d967836b993d 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -397,32 +397,35 @@ enum GameTypeRules
 	GTR_CAMPAIGN         = 1,     // Linear Co-op map progression, don't allow random maps
 	GTR_RINGSLINGER      = 1<<1,  // Outside of Co-op, Competition, and Race (overriden by cv_ringslinger)
 	GTR_SPECTATORS       = 1<<2,  // Outside of Co-op, Competition, and Race
-	GTR_FRIENDLYFIRE     = 1<<3,  // Always allow friendly fire
-	GTR_LIVES            = 1<<4,  // Co-op and Competition
-	GTR_TEAMS            = 1<<5,  // Team Match, CTF
-	GTR_RACE             = 1<<6,  // Race and Competition
-	GTR_TAG              = 1<<7,  // Tag and Hide and Seek
-	GTR_POINTLIMIT       = 1<<8,  // Ringslinger point limit
-	GTR_TIMELIMIT        = 1<<9,  // Ringslinger time limit
-	GTR_HIDETIME         = 1<<10, // Hide time (Tag and Hide and Seek)
-	GTR_HIDEFROZEN       = 1<<11, // Frozen after hide time (Hide and Seek, but not Tag)
-	GTR_BLINDFOLDED      = 1<<12, // Blindfolded view (Tag and Hide and Seek)
-	GTR_FIRSTPERSON      = 1<<13, // First person camera
-	GTR_MATCHEMERALDS    = 1<<14, // Ringslinger emeralds (Match and CTF)
-	GTR_TEAMFLAGS        = 1<<15, // Gametype has team flags (CTF)
-	GTR_PITYSHIELD       = 1<<16, // Award pity shield
-	GTR_DEATHPENALTY     = 1<<17, // Death score penalty
-	GTR_NOSPECTATORSPAWN = 1<<18, // Use with GTR_SPECTATORS, spawn in the map instead of with the spectators
-	GTR_DEATHMATCHSTARTS = 1<<19, // Use deathmatch starts
-	GTR_SPECIALSTAGES    = 1<<20, // Allow special stages
-	GTR_EMERALDTOKENS    = 1<<21, // Spawn emerald tokens
-	GTR_EMERALDHUNT      = 1<<22, // Emerald Hunt
-	GTR_SPAWNENEMIES     = 1<<23, // Spawn enemies
-	GTR_ALLOWEXIT        = 1<<24, // Allow exit sectors
-	GTR_NOTITLECARD      = 1<<25, // Don't show the title card
-	GTR_OVERTIME         = 1<<26, // Allow overtime
-	GTR_HURTMESSAGES     = 1<<27, // Hit and death messages
-	GTR_SPAWNINVUL       = 1<<28, // Babysitting deterrent
+	GTR_LIVES            = 1<<3,  // Co-op and Competition
+	GTR_TEAMS            = 1<<4,  // Team Match, CTF
+	GTR_FIRSTPERSON      = 1<<5,  // First person camera
+	GTR_POWERSTONES      = 1<<6,  // Power stones (Match and CTF)
+	GTR_TEAMFLAGS        = 1<<7,  // Gametype has team flags (CTF)
+	GTR_FRIENDLY         = 1<<8,  // Co-op
+	GTR_SPECIALSTAGES    = 1<<9,  // Allow special stages
+	GTR_EMERALDTOKENS    = 1<<10, // Spawn emerald tokens
+	GTR_EMERALDHUNT      = 1<<11, // Emerald Hunt
+	GTR_RACE             = 1<<12, // Race and Competition
+	GTR_TAG              = 1<<13, // Tag and Hide and Seek
+	GTR_POINTLIMIT       = 1<<14, // Ringslinger point limit
+	GTR_TIMELIMIT        = 1<<15, // Ringslinger time limit
+	GTR_OVERTIME         = 1<<16, // Allow overtime
+	GTR_HURTMESSAGES     = 1<<17, // Hit and death messages
+	GTR_FRIENDLYFIRE     = 1<<18, // Always allow friendly fire
+	GTR_STARTCOUNTDOWN   = 1<<19, // Hide time countdown (Tag and Hide and Seek)
+	GTR_HIDEFROZEN       = 1<<20, // Frozen after hide time (Hide and Seek, but not Tag)
+	GTR_BLINDFOLDED      = 1<<21, // Blindfolded view (Tag and Hide and Seek)
+	GTR_RESPAWNDELAY     = 1<<22, // Respawn delay
+	GTR_PITYSHIELD       = 1<<23, // Award pity shield
+	GTR_DEATHPENALTY     = 1<<24, // Death score penalty
+	GTR_NOSPECTATORSPAWN = 1<<25, // Use with GTR_SPECTATORS, spawn in the map instead of with the spectators
+	GTR_DEATHMATCHSTARTS = 1<<26, // Use deathmatch starts
+	GTR_SPAWNINVUL       = 1<<27, // Babysitting deterrent
+	GTR_SPAWNENEMIES     = 1<<28, // Spawn enemies
+	GTR_ALLOWEXIT        = 1<<29, // Allow exit sectors
+	GTR_NOTITLECARD      = 1<<30, // Don't show the title card
+	GTR_CUTSCENES        = 1<<31, // Play cutscenes, ending, credits, and evaluation
 };
 
 // String names for gametypes
diff --git a/src/g_game.c b/src/g_game.c
index 1c73a1a38ff4a45c685a68e8a79c83e3b9dfaefb..08e6865e365cf256c8cb1932a6c4b9b12c5d999d 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -1617,6 +1617,7 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
 			G_CopyTiccmd(cmd,  I_BaseTiccmd2(), 1); // empty, or external driver
 			B_BuildTiccmd(player, cmd);
 		}
+		B_HandleFlightIndicator(player);
 	}
 	else if (player->bot == 2)
 		*myangle = localangle; // Fix offset angle for P2-controlled Tailsbot when P2's controls are set to non-Legacy
@@ -2676,8 +2677,7 @@ void G_SpawnPlayer(INT32 playernum, boolean starpost)
 
 	// -- DM/Tag/CTF-spectator/etc --
 	// Order: DM->CTF->Coop
-	else if ((gametyperules & GTR_DEATHMATCHSTARTS) || gametype == GT_MATCH || gametype == GT_TEAMMATCH || gametype == GT_CTF
-	 || ((gametype == GT_TAG || gametype == GT_HIDEANDSEEK) && !(players[playernum].pflags & PF_TAGIT)))
+	else if ((gametyperules & GTR_DEATHMATCHSTARTS) && !(players[playernum].pflags & PF_TAGIT))
 	{
 		if (!(spawnpoint = G_FindMatchStart(playernum)) // find a DM start
 		&& !(spawnpoint = G_FindCTFStart(playernum))) // find a CTF start
@@ -2878,11 +2878,11 @@ void G_DoReborn(INT32 playernum)
 
 	if (countdowntimeup || (!(netgame || multiplayer) && gametype == GT_COOP))
 		resetlevel = true;
-	else if (gametype == GT_COOP && (netgame || multiplayer) && !G_IsSpecialStage(gamemap))
+	else if ((G_GametypeUsesCoopLives() || G_GametypeUsesCoopStarposts()) && (netgame || multiplayer) && !G_IsSpecialStage(gamemap))
 	{
 		boolean notgameover = true;
 
-		if (cv_cooplives.value != 0 && player->lives <= 0) // consider game over first
+		if (G_GametypeUsesCoopLives() && (cv_cooplives.value != 0 && player->lives <= 0)) // consider game over first
 		{
 			for (i = 0; i < MAXPLAYERS; i++)
 			{
@@ -2917,7 +2917,7 @@ void G_DoReborn(INT32 playernum)
 			}
 		}
 
-		if (notgameover && cv_coopstarposts.value == 2)
+		if (G_GametypeUsesCoopStarposts() && (notgameover && cv_coopstarposts.value == 2))
 		{
 			for (i = 0; i < MAXPLAYERS; i++)
 			{
@@ -2993,7 +2993,7 @@ void G_DoReborn(INT32 playernum)
 			}
 
 			// restore time in netgame (see also p_setup.c)
-			if ((netgame || multiplayer) && gametype == GT_COOP && cv_coopstarposts.value == 2)
+			if ((netgame || multiplayer) && G_GametypeUsesCoopStarposts() && cv_coopstarposts.value == 2)
 			{
 				// is this a hack? maybe
 				tic_t maxstarposttime = 0;
@@ -3064,7 +3064,7 @@ void G_AddPlayer(INT32 playernum)
 			if (!players[i].exiting)
 				notexiting++;
 
-			if (!(cv_coopstarposts.value && (gametype == GT_COOP) && (p->starpostnum < players[i].starpostnum)))
+			if (!(cv_coopstarposts.value && G_GametypeUsesCoopStarposts() && (p->starpostnum < players[i].starpostnum)))
 				continue;
 
 			p->starpostscale = players[i].starpostscale;
@@ -3181,24 +3181,24 @@ const char *Gametype_ConstantNames[NUMGAMETYPES] =
 UINT32 gametypedefaultrules[NUMGAMETYPES] =
 {
 	// Co-op
-	GTR_CAMPAIGN|GTR_LIVES|GTR_SPAWNENEMIES|GTR_ALLOWEXIT|GTR_EMERALDHUNT|GTR_EMERALDTOKENS|GTR_SPECIALSTAGES,
+	GTR_CAMPAIGN|GTR_LIVES|GTR_FRIENDLY|GTR_SPAWNENEMIES|GTR_ALLOWEXIT|GTR_EMERALDHUNT|GTR_EMERALDTOKENS|GTR_SPECIALSTAGES|GTR_CUTSCENES,
 	// Competition
 	GTR_RACE|GTR_LIVES|GTR_SPAWNENEMIES|GTR_EMERALDTOKENS|GTR_SPAWNINVUL|GTR_ALLOWEXIT,
 	// Race
 	GTR_RACE|GTR_SPAWNENEMIES|GTR_SPAWNINVUL|GTR_ALLOWEXIT,
 
 	// Match
-	GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_SPECTATORS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_MATCHEMERALDS|GTR_SPAWNINVUL|GTR_PITYSHIELD|GTR_DEATHPENALTY,
+	GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_SPECTATORS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_POWERSTONES|GTR_DEATHMATCHSTARTS|GTR_SPAWNINVUL|GTR_RESPAWNDELAY|GTR_PITYSHIELD|GTR_DEATHPENALTY,
 	// Team Match
-	GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_SPECTATORS|GTR_TEAMS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_SPAWNINVUL|GTR_PITYSHIELD,
+	GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_SPECTATORS|GTR_TEAMS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_DEATHMATCHSTARTS|GTR_SPAWNINVUL|GTR_RESPAWNDELAY|GTR_PITYSHIELD,
 
 	// Tag
-	GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_TAG|GTR_SPECTATORS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_HIDETIME|GTR_BLINDFOLDED|GTR_SPAWNINVUL,
+	GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_TAG|GTR_SPECTATORS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_STARTCOUNTDOWN|GTR_BLINDFOLDED|GTR_DEATHMATCHSTARTS|GTR_SPAWNINVUL|GTR_RESPAWNDELAY,
 	// Hide and Seek
-	GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_TAG|GTR_SPECTATORS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_HIDETIME|GTR_BLINDFOLDED|GTR_SPAWNINVUL,
+	GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_TAG|GTR_SPECTATORS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_STARTCOUNTDOWN|GTR_BLINDFOLDED|GTR_DEATHMATCHSTARTS|GTR_SPAWNINVUL|GTR_RESPAWNDELAY,
 
 	// CTF
-	GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_SPECTATORS|GTR_TEAMS|GTR_TEAMFLAGS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_MATCHEMERALDS|GTR_SPAWNINVUL|GTR_PITYSHIELD,
+	GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_SPECTATORS|GTR_TEAMS|GTR_TEAMFLAGS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_POWERSTONES|GTR_DEATHMATCHSTARTS|GTR_SPAWNINVUL|GTR_RESPAWNDELAY|GTR_PITYSHIELD,
 };
 
 //
@@ -3239,50 +3239,68 @@ INT16 G_AddGametype(UINT32 rules)
 //
 void G_AddGametypeConstant(INT16 gtype, const char *newgtconst)
 {
-	char *gtconst = Z_Malloc(strlen(newgtconst) + 3, PU_STATIC, NULL);
-	// Copy GT_ and the gametype name.
-	strcpy(gtconst, "GT_");
-	strcat(gtconst, newgtconst);
+	size_t r = 0; // read
+	size_t w = 0; // write
+	char *gtconst = Z_Calloc(strlen(newgtconst) + 3, PU_STATIC, NULL);
+	char *tmpconst = Z_Calloc(strlen(newgtconst), PU_STATIC, NULL);
+
+	// Copy the gametype name.
+	strcpy(tmpconst, newgtconst);
+
 	// Make uppercase.
-	strupr(gtconst);
-	// Remove characters.
-#define REMOVECHAR(chr) \
-	{ \
-		char *chrfind = strchr(gtconst, chr); \
-		while (chrfind) \
-		{ \
-			*chrfind = '_'; \
-			chrfind = strchr(chrfind, chr); \
-		} \
-	}
-
-	// Space
-	REMOVECHAR(' ')
-	// Used for operations
-	REMOVECHAR('+')
-	REMOVECHAR('-')
-	REMOVECHAR('*')
-	REMOVECHAR('/')
-	REMOVECHAR('%')
-	REMOVECHAR('^')
-	// Part of Lua's syntax
-	REMOVECHAR('#')
-	REMOVECHAR('=')
-	REMOVECHAR('~')
-	REMOVECHAR('<')
-	REMOVECHAR('>')
-	REMOVECHAR('(')
-	REMOVECHAR(')')
-	REMOVECHAR('{')
-	REMOVECHAR('}')
-	REMOVECHAR('[')
-	REMOVECHAR(']')
-	REMOVECHAR(':')
-	REMOVECHAR(';')
-	REMOVECHAR(',')
-	REMOVECHAR('.')
-
-#undef REMOVECHAR
+	strupr(tmpconst);
+
+	// Prepare to write the new constant string now.
+	strcpy(gtconst, "GT_");
+
+	// Remove characters that will not be allowed in the constant string.
+	for (; r < strlen(tmpconst); r++)
+	{
+		boolean writechar = true;
+		char rc = tmpconst[r];
+		switch (rc)
+		{
+			// Space, at sign and question mark
+			case ' ':
+			case '@':
+			case '?':
+			// Used for operations
+			case '+':
+			case '-':
+			case '*':
+			case '/':
+			case '%':
+			case '^':
+			case '&':
+			case '!':
+			// Part of Lua's syntax
+			case '#':
+			case '=':
+			case '~':
+			case '<':
+			case '>':
+			case '(':
+			case ')':
+			case '{':
+			case '}':
+			case '[':
+			case ']':
+			case ':':
+			case ';':
+			case ',':
+			case '.':
+				writechar = false;
+				break;
+		}
+		if (writechar)
+		{
+			gtconst[3 + w] = rc;
+			w++;
+		}
+	}
+
+	// Free the temporary string.
+	Z_Free(tmpconst);
 
 	// Finally, set the constant string.
 	Gametype_ConstantNames[gtype] = gtconst;
@@ -3427,6 +3445,28 @@ boolean G_GametypeUsesLives(void)
 	return false;
 }
 
+//
+// G_GametypeUsesCoopLives
+//
+// Returns true if the current gametype uses
+// the cooplives CVAR.  False otherwise.
+//
+boolean G_GametypeUsesCoopLives(void)
+{
+	return (gametyperules & (GTR_LIVES|GTR_FRIENDLY)) == (GTR_LIVES|GTR_FRIENDLY);
+}
+
+//
+// G_GametypeUsesCoopStarposts
+//
+// Returns true if the current gametype uses
+// the coopstarposts CVAR.  False otherwise.
+//
+boolean G_GametypeUsesCoopStarposts(void)
+{
+	return (gametyperules & GTR_FRIENDLY);
+}
+
 //
 // G_GametypeHasTeams
 //
@@ -3480,6 +3520,16 @@ boolean G_TagGametype(void)
 	return (gametyperules & GTR_TAG);
 }
 
+//
+// G_CompetitionGametype
+//
+// For gametypes that are race gametypes, and have lives.
+//
+boolean G_CompetitionGametype(void)
+{
+	return ((gametyperules & GTR_RACE) && (gametyperules & GTR_LIVES));
+}
+
 /** Get the typeoflevel flag needed to indicate support of a gametype.
   * In single-player, this always returns TOL_SP.
   * \param gametype The gametype for which support is desired.
@@ -3721,7 +3771,7 @@ void G_AfterIntermission(void)
 
 	HU_ClearCEcho();
 
-	if (mapheaderinfo[gamemap-1]->cutscenenum && !modeattacking && skipstats <= 1) // Start a custom cutscene.
+	if ((gametyperules & GTR_CUTSCENES) && mapheaderinfo[gamemap-1]->cutscenenum && !modeattacking && skipstats <= 1) // Start a custom cutscene.
 		F_StartCustomCutscene(mapheaderinfo[gamemap-1]->cutscenenum-1, false, false);
 	else
 	{
@@ -3831,7 +3881,7 @@ static void G_DoContinued(void)
 void G_EndGame(void)
 {
 	// Only do evaluation and credits in coop games.
-	if (gametype == GT_COOP)
+	if (gametyperules & GTR_CUTSCENES)
 	{
 		if (nextmap == 1103-1) // end game with ending
 		{
@@ -4534,7 +4584,7 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean
 	automapactive = false;
 	imcontinuing = false;
 
-	if (!skipprecutscene && mapheaderinfo[gamemap-1]->precutscenenum && !modeattacking) // Start a custom cutscene.
+	if ((gametyperules & GTR_CUTSCENES) && !skipprecutscene && mapheaderinfo[gamemap-1]->precutscenenum && !modeattacking) // Start a custom cutscene.
 		F_StartCustomCutscene(mapheaderinfo[gamemap-1]->precutscenenum-1, true, resetplayer);
 	else
 		G_DoLoadLevel(resetplayer);
diff --git a/src/g_game.h b/src/g_game.h
index 8967eca91cc377b2f102b4f49a1a8b4b796e4083..a589a89177f7cd1c362ccc6227604e20bcc16f88 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -245,11 +245,14 @@ void G_SetGametypeDescription(INT16 gtype, char *descriptiontext, UINT8 leftcolo
 INT32 G_GetGametypeByName(const char *gametypestr);
 boolean G_IsSpecialStage(INT32 mapnum);
 boolean G_GametypeUsesLives(void);
+boolean G_GametypeUsesCoopLives(void);
+boolean G_GametypeUsesCoopStarposts(void);
 boolean G_GametypeHasTeams(void);
 boolean G_GametypeHasSpectators(void);
 boolean G_RingSlingerGametype(void);
 boolean G_PlatformGametype(void);
 boolean G_TagGametype(void);
+boolean G_CompetitionGametype(void);
 boolean G_EnoughPlayersFinished(void);
 void G_ExitLevel(void);
 void G_NextLevel(void);
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index 2b3a470f15301f677199ebb6b7420d83c2270282..c6a92487c9f9accdfc5c03d82906daa81f54233f 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -2445,7 +2445,7 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
 			}
 		}
 
-		if (G_GametypeUsesLives() && !(gametyperankings[gametype] == GT_COOP && (cv_cooplives.value == 0 || cv_cooplives.value == 3)) && (players[tab[i].num].lives != INFLIVES)) //show lives
+		if (G_GametypeUsesLives() && !(G_GametypeUsesCoopLives() && (cv_cooplives.value == 0 || cv_cooplives.value == 3)) && (players[tab[i].num].lives != INFLIVES)) //show lives
 			V_DrawRightAlignedString(x, y+4, V_ALLOWLOWERCASE|(greycheck ? V_60TRANS : 0), va("%dx", players[tab[i].num].lives));
 		else if (G_TagGametype() && players[tab[i].num].pflags & PF_TAGIT)
 		{
@@ -2754,7 +2754,7 @@ void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scoreline
 		             | (greycheck ? V_TRANSLUCENT : 0)
 		             | V_ALLOWLOWERCASE, name);
 
-		if (G_GametypeUsesLives() && !(gametyperankings[gametype] == GT_COOP && (cv_cooplives.value == 0 || cv_cooplives.value == 3)) && (players[tab[i].num].lives != INFLIVES)) //show lives
+		if (G_GametypeUsesLives() && !(G_GametypeUsesCoopLives() && (cv_cooplives.value == 0 || cv_cooplives.value == 3)) && (players[tab[i].num].lives != INFLIVES)) //show lives
 			V_DrawRightAlignedString(x, y+4, V_ALLOWLOWERCASE, va("%dx", players[tab[i].num].lives));
 		else if (G_TagGametype() && players[tab[i].num].pflags & PF_TAGIT)
 			V_DrawSmallScaledPatch(x-28, y-4, 0, tagico);
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 695e9367e57d2fb972e48f833bae160d94f71744..2a82ec5129c11410d008090ae41f29e0a5e69a87 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -2854,6 +2854,22 @@ static int lib_gGametypeUsesLives(lua_State *L)
 	return 1;
 }
 
+static int lib_gGametypeUsesCoopLives(lua_State *L)
+{
+	//HUDSAFE
+	INLEVEL
+	lua_pushboolean(L, G_GametypeUsesCoopLives());
+	return 1;
+}
+
+static int lib_gGametypeUsesCoopStarposts(lua_State *L)
+{
+	//HUDSAFE
+	INLEVEL
+	lua_pushboolean(L, G_GametypeUsesCoopStarposts());
+	return 1;
+}
+
 static int lib_gGametypeHasTeams(lua_State *L)
 {
 	//HUDSAFE
@@ -2894,6 +2910,14 @@ static int lib_gTagGametype(lua_State *L)
 	return 1;
 }
 
+static int lib_gCompetitionGametype(lua_State *L)
+{
+	//HUDSAFE
+	INLEVEL
+	lua_pushboolean(L, G_CompetitionGametype());
+	return 1;
+}
+
 static int lib_gTicsToHours(lua_State *L)
 {
 	tic_t rtic = luaL_checkinteger(L, 1);
@@ -3139,11 +3163,14 @@ static luaL_Reg lib[] = {
 	{"G_ExitLevel",lib_gExitLevel},
 	{"G_IsSpecialStage",lib_gIsSpecialStage},
 	{"G_GametypeUsesLives",lib_gGametypeUsesLives},
+	{"G_GametypeUsesCoopLives",lib_gGametypeUsesCoopLives},
+	{"G_GametypeUsesCoopStarposts",lib_gGametypeUsesCoopStarposts},
 	{"G_GametypeHasTeams",lib_gGametypeHasTeams},
 	{"G_GametypeHasSpectators",lib_gGametypeHasSpectators},
 	{"G_RingSlingerGametype",lib_gRingSlingerGametype},
 	{"G_PlatformGametype",lib_gPlatformGametype},
 	{"G_TagGametype",lib_gTagGametype},
+	{"G_CompetitionGametype",lib_gCompetitionGametype},
 	{"G_TicsToHours",lib_gTicsToHours},
 	{"G_TicsToMinutes",lib_gTicsToMinutes},
 	{"G_TicsToSeconds",lib_gTicsToSeconds},
diff --git a/src/lua_consolelib.c b/src/lua_consolelib.c
index 837612e521f77854a72fb4cd3346fe6a1c2a1bd5..55854082909bfa43c097b586e58f42c2be3f5a95 100644
--- a/src/lua_consolelib.c
+++ b/src/lua_consolelib.c
@@ -427,6 +427,26 @@ static int lib_cvRegisterVar(lua_State *L)
 	return 1;
 }
 
+static int lib_cvFindVar(lua_State *L)
+{
+	consvar_t *cv;
+	if (( cv = CV_FindVar(luaL_checkstring(L,1)) ))
+	{
+		lua_settop(L,1);/* We only want one argument in the stack. */
+		lua_pushlightuserdata(L, cv);/* Now the second value on stack. */
+		luaL_getmetatable(L, META_CVAR);
+		/*
+		The metatable is the last value on the stack, so this
+		applies it to the second value, which is the cvar.
+		*/
+		lua_setmetatable(L,2);
+		lua_pushvalue(L,2);
+		return 1;
+	}
+	else
+		return 0;
+}
+
 // CONS_Printf for a single player
 // Use 'print' in baselib for a global message.
 static int lib_consPrintf(lua_State *L)
@@ -466,6 +486,7 @@ static luaL_Reg lib[] = {
 	{"COM_BufAddText", lib_comBufAddText},
 	{"COM_BufInsertText", lib_comBufInsertText},
 	{"CV_RegisterVar", lib_cvRegisterVar},
+	{"CV_FindVar", lib_cvFindVar},
 	{"CONS_Printf", lib_consPrintf},
 	{NULL, NULL}
 };
diff --git a/src/lua_hook.h b/src/lua_hook.h
index 68efbce93d89a0e81cd0d7cd0ede54e2a5e09b05..023090897ad5083364df15cccc87997df9f53509 100644
--- a/src/lua_hook.h
+++ b/src/lua_hook.h
@@ -53,6 +53,7 @@ enum hook {
 	hook_IntermissionThinker,
 	hook_TeamSwitch,
 	hook_ViewpointSwitch,
+	hook_SeenPlayer,
 
 	hook_MAX // last hook
 };
@@ -97,5 +98,8 @@ void LUAh_PlayerQuit(player_t *plr, int reason); // Hook for player quitting
 void LUAh_IntermissionThinker(void); // Hook for Y_Ticker
 boolean LUAh_TeamSwitch(player_t *player, int newteam, boolean fromspectators, boolean tryingautobalance, boolean tryingscramble); // Hook for team switching in... uh....
 UINT8 LUAh_ViewpointSwitch(player_t *player, player_t *newdisplayplayer, boolean forced); // Hook for spy mode
+#ifdef SEENAMES
+boolean LUAh_SeenPlayer(player_t *player, player_t *seenfriend); // Hook for MT_NAMECHECK
+#endif
 
 #endif
diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c
index 91b4c699249a77a5103c1b61911aa1de51a223db..5faa625b330d2733c1769a26311382f3d2e1fe1d 100644
--- a/src/lua_hooklib.c
+++ b/src/lua_hooklib.c
@@ -64,6 +64,7 @@ const char *const hookNames[hook_MAX+1] = {
 	"IntermissionThinker",
 	"TeamSwitch",
 	"ViewpointSwitch",
+	"SeenPlayer",
 	NULL
 };
 
@@ -207,6 +208,7 @@ static int lib_addHook(lua_State *L)
 	case hook_PlayerCanDamage:
 	case hook_TeamSwitch:
 	case hook_ViewpointSwitch:
+	case hook_SeenPlayer:
 	case hook_ShieldSpawn:
 	case hook_ShieldSpecial:
 		lastp = &playerhooks;
@@ -1412,7 +1414,7 @@ UINT8 LUAh_ViewpointSwitch(player_t *player, player_t *newdisplayplayer, boolean
 		return 0;
 
 	lua_settop(gL, 0);
-	hud_running = true;
+	hud_running = true; // local hook
 
 	for (hookp = playerhooks; hookp; hookp = hookp->next)
 	{
@@ -1453,4 +1455,49 @@ UINT8 LUAh_ViewpointSwitch(player_t *player, player_t *newdisplayplayer, boolean
 	return canSwitchView;
 }
 
+// Hook for MT_NAMECHECK
+#ifdef SEENAMES
+boolean LUAh_SeenPlayer(player_t *player, player_t *seenfriend)
+{
+	hook_p hookp;
+	boolean hasSeenPlayer = true;
+	if (!gL || !(hooksAvailable[hook_SeenPlayer/8] & (1<<(hook_SeenPlayer%8))))
+		return 0;
+
+	lua_settop(gL, 0);
+	hud_running = true; // local hook
+
+	for (hookp = playerhooks; hookp; hookp = hookp->next)
+	{
+		if (hookp->type != hook_SeenPlayer)
+			continue;
+
+		if (lua_gettop(gL) == 0)
+		{
+			LUA_PushUserdata(gL, player, META_PLAYER);
+			LUA_PushUserdata(gL, seenfriend, META_PLAYER);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -3);
+		lua_pushvalue(gL, -3);
+		if (lua_pcall(gL, 2, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
+		}
+		if (!lua_isnil(gL, -1) && !lua_toboolean(gL, -1))
+			hasSeenPlayer = false; // Hasn't seen player
+		lua_pop(gL, 1);
+	}
+
+	lua_settop(gL, 0);
+	hud_running = false;
+
+	return hasSeenPlayer;
+}
+#endif // SEENAMES
+
 #endif
diff --git a/src/lua_script.c b/src/lua_script.c
index 18d9a87c22e70de737351160ca018545c92aa12f..eb1afaf09ffc3fb9f6abc8305098be7908669456 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -299,9 +299,7 @@ int LUA_PushGlobals(lua_State *L, const char *word)
 // See the above.
 int LUA_CheckGlobals(lua_State *L, const char *word)
 {
-	if (fastcmp(word, "gametyperules"))
-		gametyperules = (UINT32)luaL_checkinteger(L, 2);
-	else if (fastcmp(word, "redscore"))
+	if (fastcmp(word, "redscore"))
 		redscore = (UINT32)luaL_checkinteger(L, 2);
 	else if (fastcmp(word, "bluescore"))
 		bluescore = (UINT32)luaL_checkinteger(L, 2);
diff --git a/src/m_menu.c b/src/m_menu.c
index 87e9ec4fd238ef85bedb69e51ea90f323457ad1e..fb29abb8ff61949fb02713ac4edb28f294877230 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -4966,7 +4966,7 @@ static boolean M_CanShowLevelOnPlatter(INT32 mapnum, INT32 gt)
 			if (gt == GT_RACE && (mapheaderinfo[mapnum]->typeoflevel & TOL_RACE))
 				return true;
 
-			if (gt > 0 && gt < gametypecount && (mapheaderinfo[mapnum]->typeoflevel & gametypetol[gt]))
+			if (gt >= 0 && gt < gametypecount && (mapheaderinfo[mapnum]->typeoflevel & gametypetol[gt]))
 				return true;
 
 			return false;
@@ -10202,7 +10202,7 @@ static void M_DrawRoomMenu(void)
 static void M_DrawConnectMenu(void)
 {
 	UINT16 i;
-	const char *gt = "Unknown";
+	char *gt;
 	INT32 numPages = (serverlistcount+(SERVERS_PER_PAGE-1))/SERVERS_PER_PAGE;
 
 	for (i = FIRSTSERVERLINE; i < min(localservercount, SERVERS_PER_PAGE)+FIRSTSERVERLINE; i++)
@@ -10246,14 +10246,17 @@ static void M_DrawConnectMenu(void)
 		V_DrawSmallString(currentMenu->x, S_LINEY(i)+8, globalflags,
 		                     va("Ping: %u", (UINT32)LONG(serverlist[slindex].info.time)));
 
-		gt = "Unknown";
-		if (serverlist[slindex].info.gametype < gametypecount)
-			gt = Gametype_Names[serverlist[slindex].info.gametype];
+		gt = serverlist[slindex].info.gametypename;
 
 		V_DrawSmallString(currentMenu->x+46,S_LINEY(i)+8, globalflags,
 		                         va("Players: %02d/%02d", serverlist[slindex].info.numberofplayer, serverlist[slindex].info.maxplayer));
 
-		V_DrawSmallString(currentMenu->x+112, S_LINEY(i)+8, globalflags, va("Gametype: %s", gt));
+		if (strlen(gt) > 11)
+			gt = va("Gametype: %.11s...", gt);
+		else
+			gt = va("Gametype: %s", gt);
+
+		V_DrawSmallString(currentMenu->x+112, S_LINEY(i)+8, globalflags, gt);
 
 		MP_ConnectMenu[i+FIRSTSERVERLINE].status = IT_STRING | IT_CALL;
 	}
@@ -10294,7 +10297,15 @@ 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)
+
+static int ServerListEntryComparator_gametypename(const void *entry1, const void *entry2)
+{
+	const serverelem_t *sa = (const serverelem_t*)entry1, *sb = (const serverelem_t*)entry2;
+	int c;
+	if (( c = strcasecmp(sa->info.gametypename, sb->info.gametypename) ))
+		return c;
+	return strcmp(sa->info.servername, sb->info.servername); \
+}
 
 // Special one for modified state.
 static int ServerListEntryComparator_modified(const void *entry1, const void *entry2)
@@ -10334,7 +10345,7 @@ void M_SortServerList(void)
 		qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_maxplayer_reverse);
 		break;
 	case 5:		// Gametype.
-		qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_gametype);
+		qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_gametypename);
 		break;
 	}
 #endif
diff --git a/src/m_misc.c b/src/m_misc.c
index 13607a66c6908b3274172e4242a7657a59ea8a29..a5091c257294fea8b84ca5b5d19f4586de9cbf87 100644
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -1908,6 +1908,20 @@ void M_UnGetToken(void)
 	endPos = oldendPos;
 }
 
+/** Returns the current token's position.
+ */
+UINT32 M_GetTokenPos(void)
+{
+	return endPos;
+}
+
+/** Sets the current token's position.
+ */
+void M_SetTokenPos(UINT32 newPos)
+{
+	endPos = newPos;
+}
+
 /** Count bits in a number.
   */
 UINT8 M_CountBits(UINT32 num, UINT8 size)
diff --git a/src/p_inter.c b/src/p_inter.c
index f39415cd67f5cbe8ef0901ffb74ac538621cc814..71740822e6a7ed1b02903490298be939b448a47f 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -1451,7 +1451,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			if (player->starpostnum >= special->health)
 				return; // Already hit this post
 
-			if (cv_coopstarposts.value && gametype == GT_COOP && (netgame || multiplayer))
+			if (cv_coopstarposts.value && G_GametypeUsesCoopStarposts() && (netgame || multiplayer))
 			{
 				for (i = 0; i < MAXPLAYERS; i++)
 				{
@@ -1807,7 +1807,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			return;
 
 		case MT_MINECARTSPAWNER:
-			if (!player->bot && (special->fuse < TICRATE || player->powers[pw_carry] != CR_MINECART))
+			if (!player->bot && special->fuse <= TICRATE && player->powers[pw_carry] != CR_MINECART)
 			{
 				mobj_t *mcart = P_SpawnMobj(special->x, special->y, special->z, MT_MINECART);
 				P_SetTarget(&mcart->target, toucher);
@@ -2521,7 +2521,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 		target->colorized = false;
 		G_GhostAddColor(GHC_NORMAL);
 
-		if ((target->player->lives <= 1) && (netgame || multiplayer) && (gametype == GT_COOP) && (cv_cooplives.value == 0))
+		if ((target->player->lives <= 1) && (netgame || multiplayer) && G_GametypeUsesCoopLives() && (cv_cooplives.value == 0))
 			;
 		else if (!target->player->bot && !target->player->spectator && (target->player->lives != INFLIVES)
 		 && G_GametypeUsesLives())
@@ -2531,7 +2531,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 			if (target->player->lives <= 0) // Tails 03-14-2000
 			{
 				boolean gameovermus = false;
-				if ((netgame || multiplayer) && (gametype == GT_COOP) && (cv_cooplives.value != 1))
+				if ((netgame || multiplayer) && G_GametypeUsesCoopLives() && (cv_cooplives.value != 1))
 				{
 					INT32 i;
 					for (i = 0; i < MAXPLAYERS; i++)
@@ -3199,10 +3199,12 @@ static void P_KillPlayer(player_t *player, mobj_t *source, INT32 damage)
 	player->powers[pw_carry] = CR_NONE;
 
 	// Burst weapons and emeralds in Match/CTF only
-	if (source && (gametype == GT_MATCH || gametype == GT_TEAMMATCH || gametype == GT_CTF))
+	if (source)
 	{
-		P_PlayerRingBurst(player, player->rings);
-		P_PlayerEmeraldBurst(player, false);
+		if ((gametyperules & GTR_RINGSLINGER) && !(gametyperules & GTR_TAG))
+			P_PlayerRingBurst(player, player->rings);
+		if (gametyperules & GTR_POWERSTONES)
+			P_PlayerEmeraldBurst(player, false);
 	}
 
 	// Get rid of shield
diff --git a/src/p_map.c b/src/p_map.c
index 0c78f7fb8a1b2aa4486dd81a574369e04303d021..95e180f83b942acdc25a2fa343c5d276adfe87d7 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -745,9 +745,8 @@ static boolean PIT_CheckThing(mobj_t *thing)
   // So that NOTHING ELSE can see MT_NAMECHECK because it is client-side.
 	if (tmthing->type == MT_NAMECHECK)
 	{
-	  // Ignore things that aren't players, ignore spectators, ignore yourself.
-		// (also don't bother to check that tmthing->target->player is non-NULL because we're not actually using it here.)
-		if (!thing->player || thing->player->spectator || (tmthing->target && thing->player == tmthing->target->player))
+		// Ignore things that aren't players, ignore spectators, ignore yourself.
+		if (!thing->player || !(tmthing->target && tmthing->target->player) || thing->player->spectator || (tmthing->target && thing->player == tmthing->target->player))
 			return true;
 
 		// Now check that you actually hit them.
@@ -760,6 +759,12 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		if (tmthing->z + tmthing->height < thing->z)
 			return true; // underneath
 
+#ifdef HAVE_BLUA
+		// REX HAS SEEN YOU
+		if (!LUAh_SeenPlayer(tmthing->target->player, thing->player))
+			return false;
+#endif
+
 		seenplayer = thing->player;
 		return false;
 	}
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 5c0ed12923cb6bebe494e401c532a80a76157f31..e8461957fa3e851587e1b1eba3afc9b0a5e14abc 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -11823,7 +11823,7 @@ static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i)
 		if (!cv_powerstones.value)
 			return false;
 
-		if (!(gametyperules & GTR_MATCHEMERALDS))
+		if (!(gametyperules & GTR_POWERSTONES))
 			return false;
 
 		runemeraldmanager = true;
diff --git a/src/p_setup.c b/src/p_setup.c
index a6dd25a8d4a6985fe92891527ffd1e7a7b798ccd..75dc385df55c324791a1e8436ad7d05e63e88166 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -83,6 +83,8 @@
 #include "p_slopes.h"
 #endif
 
+#include "fastcmp.h" // textmap parsing
+
 //
 // Map MD5, calculated on level load.
 // Sent to clients in PT_SERVERINFO.
@@ -863,11 +865,6 @@ static void P_InitializeSector(sector_t *ss)
 	ss->lightingdata = NULL;
 	ss->fadecolormapdata = NULL;
 
-	ss->floor_xoffs = ss->floor_yoffs = 0;
-	ss->ceiling_xoffs = ss->ceiling_yoffs = 0;
-
-	ss->floorpic_angle = ss->ceilingpic_angle = 0;
-
 	ss->heightsec = -1;
 	ss->camsec = -1;
 
@@ -943,6 +940,11 @@ static void P_LoadSectors(UINT8 *data)
 		ss->special = SHORT(ms->special);
 		ss->tag = SHORT(ms->tag);
 
+		ss->floor_xoffs = ss->floor_yoffs = 0;
+		ss->ceiling_xoffs = ss->ceiling_yoffs = 0;
+
+		ss->floorpic_angle = ss->ceilingpic_angle = 0;
+
 		P_InitializeSector(ss);
 	}
 }
@@ -1012,14 +1014,32 @@ static void P_InitializeLinedef(line_t *ld)
 	{
 		sides[ld->sidenum[0]].special = ld->special;
 		sides[ld->sidenum[0]].line = ld;
-
 	}
 	if (ld->sidenum[1] != 0xffff)
 	{
 		sides[ld->sidenum[1]].special = ld->special;
 		sides[ld->sidenum[1]].line = ld;
+	}
+}
+
+static void P_SetLinedefV1(size_t i, UINT16 vertex_num)
+{
+	if (vertex_num >= numvertexes)
+	{
+		CONS_Debug(DBG_SETUP, "P_SetLinedefV1: linedef %s has out-of-range v1 num %u\n", sizeu1(i), vertex_num);
+		vertex_num = 0;
+	}
+	lines[i].v1 = &vertexes[vertex_num];
+}
 
+static void P_SetLinedefV2(size_t i, UINT16 vertex_num)
+{
+	if (vertex_num >= numvertexes)
+	{
+		CONS_Debug(DBG_SETUP, "P_SetLinedefV2: linedef %s has out-of-range v2 num %u\n", sizeu1(i), vertex_num);
+		vertex_num = 0;
 	}
+	lines[i].v2 = &vertexes[vertex_num];
 }
 
 static void P_LoadLinedefs(UINT8 *data)
@@ -1033,8 +1053,8 @@ static void P_LoadLinedefs(UINT8 *data)
 		ld->flags = SHORT(mld->flags);
 		ld->special = SHORT(mld->special);
 		ld->tag = SHORT(mld->tag);
-		ld->v1 = &vertexes[SHORT(mld->v1)];
-		ld->v2 = &vertexes[SHORT(mld->v2)];
+		P_SetLinedefV1(i, SHORT(mld->v1));
+		P_SetLinedefV2(i, SHORT(mld->v2));
 
 		ld->sidenum[0] = SHORT(mld->sidenum[0]);
 		ld->sidenum[1] = SHORT(mld->sidenum[1]);
@@ -1043,6 +1063,30 @@ static void P_LoadLinedefs(UINT8 *data)
 	}
 }
 
+static void P_SetSidedefSector(size_t i, UINT16 sector_num)
+{
+	// cph 2006/09/30 - catch out-of-range sector numbers; use sector 0 instead
+	if (sector_num >= numsectors)
+	{
+		CONS_Debug(DBG_SETUP, "P_SetSidedefSector: sidedef %s has out-of-range sector num %u\n", sizeu1(i), sector_num);
+		sector_num = 0;
+	}
+	sides[i].sector = &sectors[sector_num];
+}
+
+static void P_InitializeSidedef(side_t *sd)
+{
+	if (!sd->line)
+	{
+		CONS_Debug(DBG_SETUP, "P_LoadSidedefs: Sidedef %s is not used by any linedef\n", sizeu1((size_t)(sd - sides)));
+		sd->line = &lines[0];
+		sd->special = sd->line->special;
+	}
+
+	sd->text = NULL;
+	sd->colormap_data = NULL;
+}
+
 static void P_LoadSidedefs(UINT8 *data)
 {
 	mapsidedef_t *msd = (mapsidedef_t*)data;
@@ -1051,22 +1095,28 @@ static void P_LoadSidedefs(UINT8 *data)
 
 	for (i = 0; i < numsides; i++, sd++, msd++)
 	{
-		UINT16 sector_num;
-		boolean isfrontside = !sd->line || sd->line->sidenum[0] == i;
+		INT16 textureoffset = SHORT(msd->textureoffset);
+		boolean isfrontside;
 
-		sd->textureoffset = SHORT(msd->textureoffset)<<FRACBITS;
-		sd->rowoffset = SHORT(msd->rowoffset)<<FRACBITS;
+		P_InitializeSidedef(sd);
 
-		// cph 2006/09/30 - catch out-of-range sector numbers; use sector 0 instead
-		sector_num = SHORT(msd->sector);
-		if (sector_num >= numsectors)
+		isfrontside = sd->line->sidenum[0] == i;
+
+		// Repeat count for midtexture
+		if (((sd->line->flags & (ML_TWOSIDED|ML_EFFECT5)) == (ML_TWOSIDED|ML_EFFECT5))
+			&& !(sd->special >= 300 && sd->special < 500)) // exempt linedef exec specials
+		{
+			sd->repeatcnt = (INT16)(((unsigned)textureoffset) >> 12);
+			sd->textureoffset = (((unsigned)textureoffset) & 2047) << FRACBITS;
+		}
+		else
 		{
-			CONS_Debug(DBG_SETUP, "P_LoadSidedefs: sidedef %s has out-of-range sector num %u\n", sizeu1(i), sector_num);
-			sector_num = 0;
+			sd->repeatcnt = 0;
+			sd->textureoffset = textureoffset << FRACBITS;
 		}
-		sd->sector = &sectors[sector_num];
+		sd->rowoffset = SHORT(msd->rowoffset)<<FRACBITS;
 
-		sd->colormap_data = NULL;
+		P_SetSidedefSector(i, SHORT(msd->sector));
 
 		// Special info stored in texture fields!
 		switch (sd->special)
@@ -1233,29 +1283,400 @@ static void P_LoadThings(UINT8 *data)
 			mt->z = mt->options; // NiGHTS Hoops use the full flags bits to set the height.
 		else
 			mt->z = mt->options >> ZSHIFT;
+
+		mt->mobj = NULL;
 	}
 }
 
-static void P_LoadMapData(const virtres_t *virt)
+// Stores positions for relevant map data spread through a TEXTMAP.
+UINT32 mapthingsPos[UINT16_MAX];
+UINT32 linesPos[UINT16_MAX];
+UINT32 sidesPos[UINT16_MAX];
+UINT32 vertexesPos[UINT16_MAX];
+UINT32 sectorsPos[UINT16_MAX];
+
+// Determine total amount of map data in TEXTMAP.
+static boolean TextmapCount(UINT8 *data, size_t size)
 {
-	virtlump_t* virtvertexes = NULL, * virtsectors = NULL, * virtsidedefs = NULL, * virtlinedefs = NULL, * virtthings = NULL;
-#ifdef UDMF
-	virtlump_t* textmap = vres_Find(virt, "TEXTMAP");
+	char *tkn = M_GetToken((char *)data);
+	UINT8 brackets = 0;
 
-	// Count map data.
-	if (textmap)
+	nummapthings = 0;
+	numlines = 0;
+	numsides = 0;
+	numvertexes = 0;
+	numsectors = 0;
+
+	// Look for namespace at the beginning.
+	if (!fastcmp(tkn, "namespace"))
+	{
+		Z_Free(tkn);
+		CONS_Alert(CONS_ERROR, "No namespace at beginning of lump!\n");
+		return false;
+	}
+	Z_Free(tkn);
+
+	// Check if namespace is valid.
+	tkn = M_GetToken(NULL);
+	if (!fastcmp(tkn, "srb2"))
+		CONS_Alert(CONS_WARNING, "Invalid namespace '%s', only 'srb2' is supported.\n", tkn);
+	Z_Free(tkn);
+
+	tkn = M_GetToken(NULL);
+	while (tkn && M_GetTokenPos() < size)
 	{
-		nummapthings = 0;
-		numlines = 0;
-		numsides = 0;
-		numvertexes = 0;
-		numsectors = 0;
+		// Avoid anything inside bracketed stuff, only look for external keywords.
+		if (brackets)
+		{
+			if (fastcmp(tkn, "}"))
+				brackets--;
+		}
+		else if (fastcmp(tkn, "{"))
+			brackets++;
+		// Check for valid fields.
+		else if (fastcmp(tkn, "thing"))
+			mapthingsPos[nummapthings++] = M_GetTokenPos();
+		else if (fastcmp(tkn, "linedef"))
+			linesPos[numlines++] = M_GetTokenPos();
+		else if (fastcmp(tkn, "sidedef"))
+			sidesPos[numsides++] = M_GetTokenPos();
+		else if (fastcmp(tkn, "vertex"))
+			vertexesPos[numvertexes++] = M_GetTokenPos();
+		else if (fastcmp(tkn, "sector"))
+			sectorsPos[numsectors++] = M_GetTokenPos();
+		else
+			CONS_Alert(CONS_NOTICE, "Unknown field '%s'.\n", tkn);
 
-		// Count how many entries for each type we got in textmap.
-		//TextmapCount(vtextmap->data, vtextmap->size);
+		Z_Free(tkn);
+		tkn = M_GetToken(NULL);
+	}
+
+	Z_Free(tkn);
+
+	if (brackets)
+	{
+		CONS_Alert(CONS_ERROR, "Unclosed brackets detected in textmap lump.\n");
+		return false;
+	}
+
+	return true;
+}
+
+static void ParseTextmapVertexParameter(UINT32 i, char *param, char *val)
+{
+	if (fastcmp(param, "x"))
+		vertexes[i].x = FLOAT_TO_FIXED(atof(val));
+	else if (fastcmp(param, "y"))
+		vertexes[i].y = FLOAT_TO_FIXED(atof(val));
+}
+
+static void ParseTextmapSectorParameter(UINT32 i, char *param, char *val)
+{
+	if (fastcmp(param, "heightfloor"))
+		sectors[i].floorheight = atol(val) << FRACBITS;
+	else if (fastcmp(param, "heightceiling"))
+		sectors[i].ceilingheight = atol(val) << FRACBITS;
+	if (fastcmp(param, "texturefloor"))
+		sectors[i].floorpic = P_AddLevelFlat(val, foundflats);
+	else if (fastcmp(param, "textureceiling"))
+		sectors[i].ceilingpic = P_AddLevelFlat(val, foundflats);
+	else if (fastcmp(param, "lightlevel"))
+		sectors[i].lightlevel = atol(val);
+	else if (fastcmp(param, "special"))
+		sectors[i].special = atol(val);
+	else if (fastcmp(param, "id"))
+		sectors[i].tag = atol(val);
+	else if (fastcmp(param, "xpanningfloor"))
+		sectors[i].floor_xoffs = FLOAT_TO_FIXED(atof(val));
+	else if (fastcmp(param, "ypanningfloor"))
+		sectors[i].floor_yoffs = FLOAT_TO_FIXED(atof(val));
+	else if (fastcmp(param, "xpanningceiling"))
+		sectors[i].ceiling_xoffs = FLOAT_TO_FIXED(atof(val));
+	else if (fastcmp(param, "ypanningceiling"))
+		sectors[i].ceiling_yoffs = FLOAT_TO_FIXED(atof(val));
+	else if (fastcmp(param, "rotationfloor"))
+		sectors[i].floorpic_angle = FixedAngle(FLOAT_TO_FIXED(atof(val)));
+	else if (fastcmp(param, "rotationceiling"))
+		sectors[i].ceilingpic_angle = FixedAngle(FLOAT_TO_FIXED(atof(val)));
+}
+
+static void ParseTextmapSidedefParameter(UINT32 i, char *param, char *val)
+{
+	if (fastcmp(param, "offsetx"))
+		sides[i].textureoffset = atol(val)<<FRACBITS;
+	else if (fastcmp(param, "offsety"))
+		sides[i].rowoffset = atol(val)<<FRACBITS;
+	else if (fastcmp(param, "texturetop"))
+		sides[i].toptexture = R_TextureNumForName(val);
+	else if (fastcmp(param, "texturebottom"))
+		sides[i].bottomtexture = R_TextureNumForName(val);
+	else if (fastcmp(param, "texturemiddle"))
+		sides[i].midtexture = R_TextureNumForName(val);
+	else if (fastcmp(param, "sector"))
+		P_SetSidedefSector(i, atol(val));
+	else if (fastcmp(param, "repeatcnt"))
+		sides[i].repeatcnt = atol(val);
+}
+
+static void ParseTextmapLinedefParameter(UINT32 i, char *param, char *val)
+{
+	if (fastcmp(param, "id"))
+		lines[i].tag = atol(val);
+	else if (fastcmp(param, "special"))
+		lines[i].special = atol(val);
+	else if (fastcmp(param, "v1"))
+		P_SetLinedefV1(i, atol(val));
+	else if (fastcmp(param, "v2"))
+		P_SetLinedefV2(i, atol(val));
+	else if (fastcmp(param, "sidefront"))
+		lines[i].sidenum[0] = atol(val);
+	else if (fastcmp(param, "sideback"))
+		lines[i].sidenum[1] = atol(val);
+
+	// Flags
+	else if (fastcmp(param, "blocking") && fastcmp("true", val))
+		lines[i].flags |= ML_IMPASSIBLE;
+	else if (fastcmp(param, "blockmonsters") && fastcmp("true", val))
+		lines[i].flags |= ML_BLOCKMONSTERS;
+	else if (fastcmp(param, "twosided") && fastcmp("true", val))
+		lines[i].flags |= ML_TWOSIDED;
+	else if (fastcmp(param, "dontpegtop") && fastcmp("true", val))
+		lines[i].flags |= ML_DONTPEGTOP;
+	else if (fastcmp(param, "dontpegbottom") && fastcmp("true", val))
+		lines[i].flags |= ML_DONTPEGBOTTOM;
+	else if (fastcmp(param, "skewtd") && fastcmp("true", val))
+		lines[i].flags |= ML_EFFECT1;
+	else if (fastcmp(param, "noclimb") && fastcmp("true", val))
+		lines[i].flags |= ML_NOCLIMB;
+	else if (fastcmp(param, "noskew") && fastcmp("true", val))
+		lines[i].flags |= ML_EFFECT2;
+	else if (fastcmp(param, "midpeg") && fastcmp("true", val))
+		lines[i].flags |= ML_EFFECT3;
+	else if (fastcmp(param, "midsolid") && fastcmp("true", val))
+		lines[i].flags |= ML_EFFECT4;
+	else if (fastcmp(param, "wrapmidtex") && fastcmp("true", val))
+		lines[i].flags |= ML_EFFECT5;
+	else if (fastcmp(param, "effect6") && fastcmp("true", val))
+		lines[i].flags |= ML_EFFECT6;
+	else if (fastcmp(param, "nonet") && fastcmp("true", val))
+		lines[i].flags |= ML_NONET;
+	else if (fastcmp(param, "netonly") && fastcmp("true", val))
+		lines[i].flags |= ML_NETONLY;
+	else if (fastcmp(param, "bouncy") && fastcmp("true", val))
+		lines[i].flags |= ML_BOUNCY;
+	else if (fastcmp(param, "transfer") && fastcmp("true", val))
+		lines[i].flags |= ML_TFERLINE;
+}
+
+static void ParseTextmapThingParameter(UINT32 i, char *param, char *val)
+{
+	if (fastcmp(param, "x"))
+		mapthings[i].x = atol(val);
+	else if (fastcmp(param, "y"))
+		mapthings[i].y = atol(val);
+	else if (fastcmp(param, "height"))
+		mapthings[i].z = atol(val);
+	else if (fastcmp(param, "angle"))
+		mapthings[i].angle = atol(val);
+	else if (fastcmp(param, "type"))
+		mapthings[i].type = atol(val);
+
+	// Flags
+	else if (fastcmp(param, "extra") && fastcmp("true", val))
+		mapthings[i].options |= MTF_EXTRA;
+	else if (fastcmp(param, "flip") && fastcmp("true", val))
+		mapthings[i].options |= MTF_OBJECTFLIP;
+	else if (fastcmp(param, "special") && fastcmp("true", val))
+		mapthings[i].options |= MTF_OBJECTSPECIAL;
+	else if (fastcmp(param, "ambush") && fastcmp("true", val))
+		mapthings[i].options |= MTF_AMBUSH;
+}
+
+/** From a given position table, run a specified parser function through a {}-encapsuled text.
+  *
+  * \param Position of the data to parse, in the textmap.
+  * \param Structure number (mapthings, sectors, ...).
+  * \param Parser function pointer.
+  */
+static void TextmapParse(UINT32 dataPos, size_t num, void (*parser)(UINT32, char *, char *))
+{
+	char *param, *val;
+
+	M_SetTokenPos(dataPos);
+	param = M_GetToken(NULL);
+	if (!fastcmp(param, "{"))
+	{
+		Z_Free(param);
+		CONS_Alert(CONS_WARNING, "Invalid UDMF data capsule!\n");
+		return;
+	}
+	Z_Free(param);
+
+	while (true)
+	{
+		param = M_GetToken(NULL);
+		if (fastcmp(param, "}"))
+		{
+			Z_Free(param);
+			break;
+		}
+		val = M_GetToken(NULL);
+		parser(num, param, val);
+		Z_Free(param);
+		Z_Free(val);
+	}
+}
+
+/** Provides a fix to the flat alignment coordinate transform from standard Textmaps.
+ */
+static void TextmapFixFlatOffsets(sector_t *sec)
+{
+	if (sec->floorpic_angle)
+	{
+		fixed_t pc = FINECOSINE(sec->floorpic_angle>>ANGLETOFINESHIFT);
+		fixed_t ps = FINESINE  (sec->floorpic_angle>>ANGLETOFINESHIFT);
+		fixed_t xoffs = sec->floor_xoffs;
+		fixed_t yoffs = sec->floor_yoffs;
+		sec->floor_xoffs = (FixedMul(xoffs, pc) % MAXFLATSIZE) - (FixedMul(yoffs, ps) % MAXFLATSIZE);
+		sec->floor_yoffs = (FixedMul(xoffs, ps) % MAXFLATSIZE) + (FixedMul(yoffs, pc) % MAXFLATSIZE);
+	}
+
+	if (sec->ceilingpic_angle)
+	{
+		fixed_t pc = FINECOSINE(sec->ceilingpic_angle>>ANGLETOFINESHIFT);
+		fixed_t ps = FINESINE  (sec->ceilingpic_angle>>ANGLETOFINESHIFT);
+		fixed_t xoffs = sec->ceiling_xoffs;
+		fixed_t yoffs = sec->ceiling_yoffs;
+		sec->ceiling_xoffs = (FixedMul(xoffs, pc) % MAXFLATSIZE) - (FixedMul(yoffs, ps) % MAXFLATSIZE);
+		sec->ceiling_yoffs = (FixedMul(xoffs, ps) % MAXFLATSIZE) + (FixedMul(yoffs, pc) % MAXFLATSIZE);
+	}
+}
+
+/** Loads the textmap data, after obtaining the elements count and allocating their respective space.
+  */
+static void P_LoadTextmap(void)
+{
+	UINT32 i;
+
+	vertex_t   *vt;
+	sector_t   *sc;
+	line_t     *ld;
+	side_t     *sd;
+	mapthing_t *mt;
+
+	CONS_Alert(CONS_NOTICE, "UDMF support is still a work-in-progress; its specs and features are prone to change until it is fully implemented.\n");
+
+	/// Given the UDMF specs, some fields are given a default value.
+	/// If an element's field has a default value set, it is omitted
+	/// from the textmap, and therefore we have to account for it by
+	/// preemptively setting that value beforehand.
+
+	for (i = 0, vt = vertexes; i < numvertexes; i++, vt++)
+	{
+		// Defaults.
+		vt->x = vt->y = INT32_MAX;
+		vt->z = 0;
+
+		TextmapParse(vertexesPos[i], i, ParseTextmapVertexParameter);
+
+		if (vt->x == INT32_MAX)
+			I_Error("P_LoadTextmap: vertex %s has no x value set!\n", sizeu1(i));
+		if (vt->y == INT32_MAX)
+			I_Error("P_LoadTextmap: vertex %s has no y value set!\n", sizeu1(i));
+	}
+
+	for (i = 0, sc = sectors; i < numsectors; i++, sc++)
+	{
+		// Defaults.
+		sc->floorheight = 0;
+		sc->ceilingheight = 0;
+
+		sc->floorpic = 0;
+		sc->ceilingpic = 0;
+
+		sc->lightlevel = 255;
+
+		sc->special = 0;
+		sc->tag = 0;
+
+		sc->floor_xoffs = sc->floor_yoffs = 0;
+		sc->ceiling_xoffs = sc->ceiling_yoffs = 0;
+
+		sc->floorpic_angle = sc->ceilingpic_angle = 0;
+
+		TextmapParse(sectorsPos[i], i, ParseTextmapSectorParameter);
+		P_InitializeSector(sc);
+		TextmapFixFlatOffsets(sc);
+	}
+
+	for (i = 0, ld = lines; i < numlines; i++, ld++)
+	{
+		// Defaults.
+		ld->v1 = ld->v2 = NULL;
+		ld->flags = 0;
+		ld->special = 0;
+		ld->tag = 0;
+		ld->sidenum[0] = 0xffff;
+		ld->sidenum[1] = 0xffff;
+
+		TextmapParse(linesPos[i], i, ParseTextmapLinedefParameter);
+
+		if (!ld->v1)
+			I_Error("P_LoadTextmap: linedef %s has no v1 value set!\n", sizeu1(i));
+		if (!ld->v2)
+			I_Error("P_LoadTextmap: linedef %s has no v2 value set!\n", sizeu1(i));
+		if (ld->sidenum[0] == 0xffff)
+			I_Error("P_LoadTextmap: linedef %s has no sidefront value set!\n", sizeu1(i));
+
+		P_InitializeLinedef(ld);
+	}
+
+	for (i = 0, sd = sides; i < numsides; i++, sd++)
+	{
+		// Defaults.
+		sd->textureoffset = 0;
+		sd->rowoffset = 0;
+		sd->toptexture = R_TextureNumForName("-");
+		sd->midtexture = R_TextureNumForName("-");
+		sd->bottomtexture = R_TextureNumForName("-");
+		sd->sector = NULL;
+		sd->repeatcnt = 0;
+
+		TextmapParse(sidesPos[i], i, ParseTextmapSidedefParameter);
+
+		if (!sd->sector)
+			I_Error("P_LoadTextmap: sidedef %s has no sector value set!\n", sizeu1(i));
+
+		P_InitializeSidedef(sd);
+	}
+
+	for (i = 0, mt = mapthings; i < nummapthings; i++, mt++)
+	{
+		// Defaults.
+		mt->x = mt->y = 0;
+		mt->angle = 0;
+		mt->type = 0;
+		mt->options = 0;
+		mt->z = 0;
+		mt->extrainfo = 0;
+		mt->mobj = NULL;
+
+		TextmapParse(mapthingsPos[i], i, ParseTextmapThingParameter);
+	}
+}
+
+static boolean P_LoadMapData(const virtres_t *virt)
+{
+	virtlump_t *virtvertexes = NULL, *virtsectors = NULL, *virtsidedefs = NULL, *virtlinedefs = NULL, *virtthings = NULL;
+	virtlump_t *textmap = vres_Find(virt, "TEXTMAP");
+
+	// Count map data.
+	if (textmap) // Count how many entries for each type we got in textmap.
+	{
+		if (!TextmapCount(textmap->data, textmap->size))
+			return false;
 	}
 	else
-#endif
 	{
 		virtthings   = vres_Find(virt, "THINGS");
 		virtvertexes = vres_Find(virt, "VERTEXES");
@@ -1305,15 +1726,11 @@ static void P_LoadMapData(const virtres_t *virt)
 
 	numlevelflats = 0;
 
-#ifdef UDMF
+	// Load map data.
 	if (textmap)
-	{
-
-	}
+		P_LoadTextmap();
 	else
-#endif
 	{
-		// Strict map data
 		P_LoadVertices(virtvertexes->data);
 		P_LoadSectors(virtsectors->data);
 		P_LoadLinedefs(virtlinedefs->data);
@@ -1341,6 +1758,8 @@ static void P_LoadMapData(const virtres_t *virt)
 	memcpy(spawnsectors, sectors, numsectors * sizeof (*sectors));
 	memcpy(spawnlines, lines, numlines * sizeof (*lines));
 	memcpy(spawnsides, sides, numsides * sizeof (*sides));
+
+	return true;
 }
 
 static void P_InitializeSubsector(subsector_t *ss)
@@ -1421,10 +1840,13 @@ static inline float P_SegLengthFloat(seg_t *seg)
 
 static void P_InitializeSeg(seg_t *seg)
 {
-	seg->sidedef = &sides[seg->linedef->sidenum[seg->side]];
+	if (seg->linedef)
+	{
+		seg->sidedef = &sides[seg->linedef->sidenum[seg->side]];
 
-	seg->frontsector = seg->sidedef->sector;
-	seg->backsector = (seg->linedef->flags & ML_TWOSIDED) ? sides[seg->linedef->sidenum[seg->side ^ 1]].sector : NULL;
+		seg->frontsector = seg->sidedef->sector;
+		seg->backsector = (seg->linedef->flags & ML_TWOSIDED) ? sides[seg->linedef->sidenum[seg->side ^ 1]].sector : NULL;
+	}
 
 #ifdef HWRENDER
 	seg->pv1 = seg->pv2 = NULL;
@@ -1607,20 +2029,21 @@ static boolean P_LoadExtendedSubsectorsAndSegs(UINT8 **data, nodetype_t nodetype
 		case NT_XGL3:
 			for (m = 0; m < subsectors[i].numlines; m++, k++)
 			{
+				UINT32 vertexnum = READUINT32((*data));
 				UINT16 linenum;
-				UINT32 vert = READUINT32((*data));
 
-				segs[k].v1 = &vertexes[vert];
-				if (m == 0)
-					segs[k + subsectors[i].numlines - 1].v2 = &vertexes[vert];
-				else
-					segs[k - 1].v2 = segs[k].v1;
+				if (vertexnum >= numvertexes)
+					I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %d has invalid vertex %d!\n", sizeu1(k), m, vertexnum);
 
-				(*data) += 4; // partner, can be ignored by software renderer
+				segs[k - 1 + ((m == 0) ? subsectors[i].numlines : 0)].v2 = segs[k].v1 = &vertexes[vertexnum];
+
+				READUINT32((*data)); // partner, can be ignored by software renderer
 				if (nodetype == NT_XGL3)
-					(*data) += 2; // Line number is 32-bit in XGL3, but we're limited to 16 bits.
+					READUINT16((*data)); // Line number is 32-bit in XGL3, but we're limited to 16 bits.
 
 				linenum = READUINT16((*data));
+				if (linenum != 0xFFFF && linenum >= numlines)
+					I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %d has invalid linedef %d!\n", sizeu1(k), m, linenum);
 				segs[k].glseg = (linenum == 0xFFFF);
 				segs[k].linedef = (linenum == 0xFFFF) ? NULL : &lines[linenum];
 				segs[k].side = READUINT8((*data));
@@ -1630,9 +2053,20 @@ static boolean P_LoadExtendedSubsectorsAndSegs(UINT8 **data, nodetype_t nodetype
 		case NT_XNOD:
 			for (m = 0; m < subsectors[i].numlines; m++, k++)
 			{
-				segs[k].v1 = &vertexes[READUINT32((*data))];
-				segs[k].v2 = &vertexes[READUINT32((*data))];
-				segs[k].linedef = &lines[READUINT16((*data))];
+				UINT32 v1num = READUINT32((*data));
+				UINT32 v2num = READUINT32((*data));
+				UINT16 linenum = READUINT16((*data));
+
+				if (v1num >= numvertexes)
+					I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %d has invalid v1 %d!\n", sizeu1(k), m, v1num);
+				if (v2num >= numvertexes)
+					I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %d has invalid v2 %d!\n", sizeu1(k), m, v2num);
+				if (linenum >= numlines)
+					I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %d has invalid linedef %d!\n", sizeu1(k), m, linenum);
+
+				segs[k].v1 = &vertexes[v1num];
+				segs[k].v2 = &vertexes[v2num];
+				segs[k].linedef = &lines[linenum];
 				segs[k].side = READUINT8((*data));
 				segs[k].glseg = false;
 			}
@@ -1649,7 +2083,8 @@ static boolean P_LoadExtendedSubsectorsAndSegs(UINT8 **data, nodetype_t nodetype
 		vertex_t *v2 = seg->v2;
 		P_InitializeSeg(seg);
 		seg->angle = R_PointToAngle2(v1->x, v1->y, v2->x, v2->y);
-		seg->offset = FixedHypot(v1->x - seg->linedef->v1->x, v1->y - seg->linedef->v1->y);
+		if (seg->linedef)
+			segs[i].offset = FixedHypot(v1->x - seg->linedef->v1->x, v1->y - seg->linedef->v1->y);
 	}
 
 	return true;
@@ -2091,16 +2526,6 @@ static void P_ProcessLinedefsWithSidedefs(void)
 		ld->frontsector = sides[ld->sidenum[0]].sector; //e6y: Can't be -1 here
 		ld->backsector  = ld->sidenum[1] != 0xffff ? sides[ld->sidenum[1]].sector : 0;
 
-		// Repeat count for midtexture
-		if ((ld->flags & ML_EFFECT5) && (ld->sidenum[1] != 0xffff)
-			&& !(ld->special >= 300 && ld->special < 500)) // exempt linedef exec specials
-		{
-			sides[ld->sidenum[0]].repeatcnt = (INT16)(((unsigned)sides[ld->sidenum[0]].textureoffset >> FRACBITS) >> 12);
-			sides[ld->sidenum[0]].textureoffset = (((unsigned)sides[ld->sidenum[0]].textureoffset >> FRACBITS) & 2047) << FRACBITS;
-			sides[ld->sidenum[1]].repeatcnt = (INT16)(((unsigned)sides[ld->sidenum[1]].textureoffset >> FRACBITS) >> 12);
-			sides[ld->sidenum[1]].textureoffset = (((unsigned)sides[ld->sidenum[1]].textureoffset >> FRACBITS) & 2047) << FRACBITS;
-		}
-
 		// Compile linedef 'text' from both sidedefs 'text' for appropriate specials.
 		switch(ld->special)
 		{
@@ -2123,69 +2548,6 @@ static void P_ProcessLinedefsWithSidedefs(void)
 	}
 }
 
-static void P_CompressSidedefs(void)
-{
-	side_t *newsides;
-	size_t numnewsides = 0;
-	size_t z;
-	size_t i;
-
-	for (i = 0; i < numsides; i++)
-	{
-		size_t j, k;
-		line_t *ld;
-
-		if (!sides[i].sector)
-			continue;
-
-		for (k = numlines, ld = lines; k--; ld++)
-		{
-			if (ld->sidenum[0] == i)
-				ld->sidenum[0] = (UINT16)numnewsides;
-
-			if (ld->sidenum[1] == i)
-				ld->sidenum[1] = (UINT16)numnewsides;
-		}
-
-		for (j = i + 1; j < numsides; j++)
-		{
-			if (!sides[j].sector)
-				continue;
-
-			if (!memcmp(&sides[i], &sides[j], sizeof(side_t)))
-			{
-				// Find the linedefs that belong to this one
-				for (k = numlines, ld = lines; k--; ld++)
-				{
-					if (ld->sidenum[0] == j)
-						ld->sidenum[0] = (UINT16)numnewsides;
-
-					if (ld->sidenum[1] == j)
-						ld->sidenum[1] = (UINT16)numnewsides;
-				}
-				sides[j].sector = NULL; // Flag for deletion
-			}
-		}
-		numnewsides++;
-	}
-
-	// We're loading crap into this block anyhow, so no point in zeroing it out.
-	newsides = Z_Malloc(numnewsides*sizeof(*newsides), PU_LEVEL, NULL);
-
-	// Copy the sides to their new block of memory.
-	for (i = 0, z = 0; i < numsides; i++)
-	{
-		if (sides[i].sector)
-			M_Memcpy(&newsides[z++], &sides[i], sizeof(side_t));
-	}
-
-	CONS_Debug(DBG_SETUP, "P_CompressSidedefs: Old sides is %s, new sides is %s\n", sizeu1(numsides), sizeu1(numnewsides));
-
-	Z_Free(sides);
-	sides = newsides;
-	numsides = numnewsides;
-}
-
 //
 // P_LinkMapData
 // Builds sector line lists and subsector sector numbers.
@@ -2310,47 +2672,54 @@ static INT32 P_MakeBufferMD5(const char *buffer, size_t len, void *resblock)
 
 static void P_MakeMapMD5(virtres_t *virt, void *dest)
 {
-	unsigned char linemd5[16];
-	unsigned char sectormd5[16];
-	unsigned char thingmd5[16];
-	unsigned char sidedefmd5[16];
+	virtlump_t *textmap = vres_Find(virt, "TEXTMAP");
 	unsigned char resmd5[16];
-	UINT8 i;
 
-	// Create a hash for the current map
-	// get the actual lumps!
-	virtlump_t *virtlines = vres_Find(virt, "LINEDEFS");
-	virtlump_t *virtsectors = vres_Find(virt, "SECTORS");
-	virtlump_t *virtmthings = vres_Find(virt, "THINGS");
-	virtlump_t *virtsides = vres_Find(virt, "SIDEDEFS");
+	if (textmap)
+		P_MakeBufferMD5((char*)textmap->data, textmap->size, resmd5);
+	else
+	{
+		unsigned char linemd5[16];
+		unsigned char sectormd5[16];
+		unsigned char thingmd5[16];
+		unsigned char sidedefmd5[16];
+		UINT8 i;
 
-	P_MakeBufferMD5((char*)virtlines->data, virtlines->size, linemd5);
-	P_MakeBufferMD5((char*)virtsectors->data, virtsectors->size, sectormd5);
-	P_MakeBufferMD5((char*)virtmthings->data, virtmthings->size, thingmd5);
-	P_MakeBufferMD5((char*)virtsides->data, virtsides->size, sidedefmd5);
+		// Create a hash for the current map
+		// get the actual lumps!
+		virtlump_t* virtlines   = vres_Find(virt, "LINEDEFS");
+		virtlump_t* virtsectors = vres_Find(virt, "SECTORS");
+		virtlump_t* virtmthings = vres_Find(virt, "THINGS");
+		virtlump_t* virtsides   = vres_Find(virt, "SIDEDEFS");
 
-	for (i = 0; i < 16; i++)
-		resmd5[i] = (linemd5[i] + sectormd5[i] + thingmd5[i] + sidedefmd5[i]) & 0xFF;
+		P_MakeBufferMD5((char*)virtlines->data,   virtlines->size, linemd5);
+		P_MakeBufferMD5((char*)virtsectors->data, virtsectors->size,  sectormd5);
+		P_MakeBufferMD5((char*)virtmthings->data, virtmthings->size,   thingmd5);
+		P_MakeBufferMD5((char*)virtsides->data,   virtsides->size, sidedefmd5);
+
+		for (i = 0; i < 16; i++)
+			resmd5[i] = (linemd5[i] + sectormd5[i] + thingmd5[i] + sidedefmd5[i]) & 0xFF;
+	}
 
 	M_Memcpy(dest, &resmd5, 16);
 }
 
-static void P_LoadMapFromFile(void)
+static boolean P_LoadMapFromFile(void)
 {
 	virtres_t *virt = vres_GetMap(lastloadedmaplumpnum);
 
-	P_LoadMapData(virt);
+	if (!P_LoadMapData(virt))
+		return false;
 	P_LoadMapBSP(virt);
 	P_LoadMapLUT(virt);
 
 	P_ProcessLinedefsWithSidedefs();
-	if (M_CheckParm("-compress"))
-		P_CompressSidedefs();
 	P_LinkMapData();
 
 	P_MakeMapMD5(virt, &mapmd5);
 
 	vres_Free(virt);
+	return true;
 }
 
 //
@@ -2446,7 +2815,7 @@ static void P_InitLevelSettings(void)
 	// earthquake camera
 	memset(&quake,0,sizeof(struct quake));
 
-	if ((netgame || multiplayer) && gametype == GT_COOP && cv_coopstarposts.value == 2)
+	if ((netgame || multiplayer) && G_GametypeUsesCoopStarposts() && cv_coopstarposts.value == 2)
 	{
 		for (i = 0; i < MAXPLAYERS; i++)
 		{
@@ -2464,7 +2833,7 @@ static void P_InitLevelSettings(void)
 	{
 		G_PlayerReborn(i, true);
 
-		if (canresetlives && (netgame || multiplayer) && playeringame[i] && (gametype == GT_COMPETITION || players[i].lives <= 0))
+		if (canresetlives && (netgame || multiplayer) && playeringame[i] && (G_CompetitionGametype() || players[i].lives <= 0))
 		{
 			// In Co-Op, replenish a user's lives if they are depleted.
 			players[i].lives = cv_startinglives.value;
@@ -2754,17 +3123,10 @@ static void P_SetupCamera(void)
 	{
 		mapthing_t *thing;
 
-		switch (gametype)
-		{
-		case GT_MATCH:
-		case GT_TAG:
+		if (gametyperules & GTR_DEATHMATCHSTARTS)
 			thing = deathmatchstarts[0];
-			break;
-
-		default:
+		else
 			thing = playerstarts[0];
-			break;
-		}
 
 		if (thing)
 		{
@@ -2979,7 +3341,7 @@ static void P_InitGametype(void)
 	P_InitPlayers();
 
 	// restore time in netgame (see also g_game.c)
-	if ((netgame || multiplayer) && gametype == GT_COOP && cv_coopstarposts.value == 2)
+	if ((netgame || multiplayer) && G_GametypeUsesCoopStarposts() && cv_coopstarposts.value == 2)
 	{
 		// is this a hack? maybe
 		tic_t maxstarposttime = 0;
@@ -3023,6 +3385,7 @@ boolean P_LoadLevel(boolean fromnetsave)
 
 	// This is needed. Don't touch.
 	maptol = mapheaderinfo[gamemap-1]->typeoflevel;
+	gametyperules = gametypedefaultrules[gametype];
 
 	CON_Drawer(); // let the user know what we are going to do
 	I_FinishUpdate(); // page flip or blit buffer
@@ -3189,7 +3552,7 @@ boolean P_LoadLevel(boolean fromnetsave)
 	// internal game map
 	maplumpname = G_BuildMapName(gamemap);
 	lastloadedmaplumpnum = W_CheckNumForName(maplumpname);
-	if (lastloadedmaplumpnum == INT16_MAX)
+	if (lastloadedmaplumpnum == LUMPERROR)
 		I_Error("Map %s not found.\n", maplumpname);
 
 	R_ReInitColormaps(mapheaderinfo[gamemap-1]->palette);
@@ -3202,8 +3565,8 @@ boolean P_LoadLevel(boolean fromnetsave)
 
 	P_MapStart();
 
-	if (lastloadedmaplumpnum)
-		P_LoadMapFromFile();
+	if (!P_LoadMapFromFile())
+		return false;
 
 	// init gravity, tag lists,
 	// anything that P_ResetDynamicSlopes/P_LoadThings needs to know
diff --git a/src/p_spec.c b/src/p_spec.c
index 0851e3199cac0e8d4133f6e79066dd430a43139d..a65f027068392bd45587797117a88a1ccd1cac6f 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -52,9 +52,6 @@ mobj_t *skyboxcenterpnts[16]; // array of MT_SKYBOX centerpoint mobjs
 // Amount (dx, dy) vector linedef is shifted right to get scroll amount
 #define SCROLL_SHIFT 5
 
-// This must be updated whenever we up the max flat size - quicker to assume rather than figuring out the sqrt of the specific flat's filesize.
-#define MAXFLATSIZE (2048<<FRACBITS)
-
 /** Animated texture descriptor
   * This keeps track of an animated texture or an animated flat.
   * \sa P_UpdateSpecials, P_InitPicAnims, animdef_t
diff --git a/src/p_spec.h b/src/p_spec.h
index 630bcb3de1045cdc9eec2077a6972c0662167bea..4b64fe05d9bed944511f852b84f70cf86db000a9 100644
--- a/src/p_spec.h
+++ b/src/p_spec.h
@@ -27,6 +27,9 @@ extern mobj_t *skyboxcenterpnts[16]; // array of MT_SKYBOX centerpoint mobjs
 //
 #define GETSECSPECIAL(i,j) ((i >> ((j-1)*4))&15)
 
+// This must be updated whenever we up the max flat size - quicker to assume rather than figuring out the sqrt of the specific flat's filesize.
+#define MAXFLATSIZE (2048<<FRACBITS)
+
 // at game start
 void P_InitPicAnims(void);
 
diff --git a/src/p_user.c b/src/p_user.c
index cd338c989693cb48819a408f428ce123e0aab194..c5024d7ec34e4676eaccc26f4122b3b0afc88d67 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -349,6 +349,11 @@ void P_GiveEmerald(boolean spawnObj)
 				continue;
 			P_SetTarget(&emmo->target, players[i].mo);
 			P_SetMobjState(emmo, mobjinfo[MT_GOTEMERALD].meleestate + em);
+			
+			// Make sure we're not being carried before our tracer is changed
+			if (players[i].powers[pw_carry] != CR_NIGHTSMODE)
+				players[i].powers[pw_carry] = CR_NONE;
+			
 			P_SetTarget(&players[i].mo->tracer, emmo);
 
 			if (pnum == 255)
@@ -1232,13 +1237,13 @@ void P_GivePlayerLives(player_t *player, INT32 numlives)
 
 	if (gamestate == GS_LEVEL)
 	{
-		if (player->lives == INFLIVES || (gametype != GT_COOP && gametype != GT_COMPETITION))
+		if (player->lives == INFLIVES || !(gametyperules & GTR_LIVES))
 		{
 			P_GivePlayerRings(player, 100*numlives);
 			return;
 		}
 
-		if ((netgame || multiplayer) && gametype == GT_COOP && cv_cooplives.value == 0)
+		if ((netgame || multiplayer) && G_GametypeUsesCoopLives() && cv_cooplives.value == 0)
 		{
 			P_GivePlayerRings(player, 100*numlives);
 			if (player->lives - prevlives >= numlives)
@@ -1269,7 +1274,7 @@ docooprespawn:
 
 void P_GiveCoopLives(player_t *player, INT32 numlives, boolean sound)
 {
-	if (!((netgame || multiplayer) && gametype == GT_COOP))
+	if (!((netgame || multiplayer) && G_GametypeUsesCoopLives()))
 	{
 		P_GivePlayerLives(player, numlives);
 		if (sound)
@@ -1397,7 +1402,7 @@ void P_AddPlayerScore(player_t *player, UINT32 amount)
 		player->score = MAXSCORE;
 
 	// check for extra lives every 50000 pts
-	if (!ultimatemode && !modeattacking && player->score > oldscore && player->score % 50000 < amount && (gametype == GT_COMPETITION || gametype == GT_COOP))
+	if (!ultimatemode && !modeattacking && player->score > oldscore && player->score % 50000 < amount && (gametyperules & GTR_LIVES))
 	{
 		P_GivePlayerLives(player, (player->score/50000) - (oldscore/50000));
 		P_PlayLivesJingle(player);
@@ -7791,6 +7796,7 @@ void P_ElementalFire(player_t *player, boolean cropcircle)
 			flame->fuse = TICRATE*7; // takes about an extra second to hit the ground
 			flame->destscale = player->mo->scale;
 			P_SetScale(flame, player->mo->scale);
+			flame->flags2 = (flame->flags2 & ~MF2_OBJECTFLIP)|(player->mo->flags2 & MF2_OBJECTFLIP);
 			flame->eflags = (flame->eflags & ~MFE_VERTICALFLIP)|(player->mo->eflags & MFE_VERTICALFLIP);
 			P_InstaThrust(flame, flame->angle, FixedMul(3*FRACUNIT, flame->scale));
 			P_SetObjectMomZ(flame, 3*FRACUNIT, false);
@@ -9460,7 +9466,7 @@ boolean P_GetLives(player_t *player)
 {
 	INT32 i, maxlivesplayer = -1, livescheck = 1;
 	if (!(netgame || multiplayer)
-	|| (gametype != GT_COOP)
+	|| !G_GametypeUsesCoopLives()
 	|| (player->lives == INFLIVES))
 		return true;
 
@@ -9609,7 +9615,7 @@ static void P_DeathThink(player_t *player)
 		player->playerstate = PST_REBORN;
 	else if ((player->lives > 0 || j != MAXPLAYERS) && !(!(netgame || multiplayer) && G_IsSpecialStage(gamemap))) // Don't allow "click to respawn" in special stages!
 	{
-		if (gametype == GT_COOP && (netgame || multiplayer) && cv_coopstarposts.value == 2)
+		if (G_GametypeUsesCoopStarposts() && (netgame || multiplayer) && cv_coopstarposts.value == 2)
 		{
 			P_ConsiderAllGone();
 			if ((player->deadtimer > TICRATE<<1) || ((cmd->buttons & BT_JUMP) && (player->deadtimer > TICRATE)))
@@ -9624,19 +9630,22 @@ static void P_DeathThink(player_t *player)
 			// Respawn with jump button, force respawn time (3 second default, cheat protected) in shooter modes.
 			if (cmd->buttons & BT_JUMP)
 			{
-				if (gametype != GT_COOP && player->spectator)
+				// You're a spectator, so respawn right away.
+				if ((gametyperules & GTR_SPECTATORS) && player->spectator)
 					player->playerstate = PST_REBORN;
-				else switch(gametype) {
-					case GT_COOP:
-					case GT_COMPETITION:
-					case GT_RACE:
-						if (player->deadtimer > TICRATE)
-							player->playerstate = PST_REBORN;
-						break;
-					default:
-						if (player->deadtimer > cv_respawntime.value*TICRATE)
-							player->playerstate = PST_REBORN;
-						break;
+				else
+				{
+					// Give me one second.
+					INT32 respawndelay = TICRATE;
+
+					// Non-platform gametypes
+					if (gametyperules & GTR_RESPAWNDELAY)
+						respawndelay = (cv_respawntime.value*TICRATE);
+
+					// You've been dead for enough time.
+					// You may now respawn.
+					if (player->deadtimer > respawndelay)
+						player->playerstate = PST_REBORN;
 				}
 			}
 
@@ -9650,7 +9659,7 @@ static void P_DeathThink(player_t *player)
 		INT32 i, deadtimercheck = INT32_MAX;
 
 		// In a net/multiplayer game, and out of lives
-		if (gametype == GT_COMPETITION)
+		if (G_CompetitionGametype())
 		{
 			for (i = 0; i < MAXPLAYERS; i++)
 			{
@@ -11477,7 +11486,10 @@ static void P_DoMetalJetFume(player_t *player, mobj_t *fume)
 		if (panim == PA_WALK)
 		{
 			if (stat != fume->info->spawnstate)
+			{
+				fume->threshold = 0;
 				P_SetMobjState(fume, fume->info->spawnstate);
+			}
 			return;
 		}
 	}
@@ -11508,6 +11520,12 @@ static void P_DoMetalJetFume(player_t *player, mobj_t *fume)
 		if (underwater)
 		{
 			fume->frame = (fume->frame & FF_FRAMEMASK) | FF_ANIMATE | (P_RandomRange(0, 9) * FF_TRANS10);
+			fume->threshold = 1;
+		}
+		else if (fume->threshold)
+		{
+			fume->frame = (fume->frame & FF_FRAMEMASK) | fume->state->frame;
+			fume->threshold = 0;
 		}
 	}
 
@@ -11767,7 +11785,7 @@ void P_PlayerThink(player_t *player)
 #else
 	if (player->spectator &&
 #endif
-	gametype == GT_COOP && (netgame || multiplayer) && cv_coopstarposts.value == 2)
+	G_GametypeUsesCoopStarposts() && (netgame || multiplayer) && cv_coopstarposts.value == 2)
 		P_ConsiderAllGone();
 
 	if (player->playerstate == PST_DEAD)
diff --git a/src/sounds.c b/src/sounds.c
index 175bd8960d1b936515e3fc062cbcdbc75ec20c21..720ba851e4a88ed4c6f0ab08b8dfcb904de96082 100644
--- a/src/sounds.c
+++ b/src/sounds.c
@@ -187,6 +187,7 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"shield", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Pity Shield"}, // generic GET!
   {"wirlsg", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Whirlwind Shield"}, // Whirlwind GET!
   {"forcsg", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Force Shield"}, // Force GET!
+  {"frcssg", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Weak Force Shield"}, // Force GET...? (consider making a custom shield with this instead of a single-hit force shield!)
   {"elemsg", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Elemental Shield"}, // Elemental GET!
   {"armasg", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Armageddon Shield"}, // Armaggeddon GET!
   {"attrsg", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Attraction Shield"}, // Attract GET!
@@ -220,6 +221,9 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"sprong", false, 112,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Power spring"},
   {"lvfal1",  true,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Rumble"},
   {"pscree", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "SCREE!"},
+  {"iceb",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ice crack"},
+  {"shattr",  true,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Shattering"},
+  {"antiri",  true, 255,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Depletion"},
 
   // Menu, interface
   {"chchng", false, 120,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Score"},
@@ -233,6 +237,9 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"wepchg",  true,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Weapon change"}, // Weapon switch is identical to menu for now
   {"wtrdng",  true, 212,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Aquaphobia"}, // make sure you can hear the DING DING! Tails 03-08-2000
   {"zelda",  false, 120,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Discovery"},
+  {"adderr",  true,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Error"},
+  {"notadd",  true,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Reject"},
+  {"addfil",  true,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Accept"},
 
   // NiGHTS
   {"ideya",  false, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Success"},
@@ -427,24 +434,9 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"s25e",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
   {"s25f",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
   {"s260",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
-  {"s261",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
-  {"s262",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
-  {"s263",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
-  {"s264",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
-  {"s265",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
-  {"s266",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
-  {"s267",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
-  {"s268",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
-  {"s269",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
-  {"s26a",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
-  {"s26b",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
-  {"s26c",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
-  {"s26d",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
-  {"s26e",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
-  {"s26f",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
-  {"s270",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
 
   // S3&K sounds
+  {"s3k2b",   true, 120,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Got Emerald"}, // Got Emerald!
   {"s3k33",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Sparkle"}, // stereo in original game, identical to latter
   {"s3k34",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Sparkle"}, // mono in original game, identical to previous
   {"s3k35",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Hurt"},
@@ -566,6 +558,21 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"s3ka9",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Aquaphobia"},
   {"s3kaa",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bumper"},
   {"s3kab",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spindash"},
+  {"s3kab1", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spindash"},
+  {"s3kab2", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spindash"},
+  {"s3kab3", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spindash"},
+  {"s3kab4", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spindash"},
+  {"s3kab5", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spindash"},
+  {"s3kab6", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spindash"},
+  {"s3kab7", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spindash"},
+  {"s3kab8", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spindash"},
+  {"s3kab9", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spindash"},
+  {"s3kaba", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spindash"},
+  {"s3kabb", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spindash"},
+  {"s3kabc", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spindash"},
+  {"s3kabd", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spindash"},
+  {"s3kabe", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spindash"},
+  {"s3kabf", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spindash"},
   {"s3kac",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Got Continue"},
   {"s3kad",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "GO!"},
   {"s3kae",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Pinball flipper"},
@@ -604,7 +611,8 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"s3kc5l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Revving up"}, // ditto
   {"s3kc6s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Orbiting"},
   {"s3kc6l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Orbiting"}, // ditto
-  {"s3kc7",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Aiming"},
+  {"s3kc7s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Aiming"},
+  {"s3kc7l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Aiming"}, // ditto
   {"s3kc8s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Sliding"},
   {"s3kc8l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Sliding"}, // ditto
   {"s3kc9s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Swinging"},
diff --git a/src/sounds.h b/src/sounds.h
index e520c6243b893de867d19ee4cfaa9ae55674674f..039349d4f2ee0a84e63bfdc85a29381a0c2d2f39 100644
--- a/src/sounds.h
+++ b/src/sounds.h
@@ -236,6 +236,7 @@ typedef enum
 	sfx_shield,
 	sfx_wirlsg,
 	sfx_forcsg,
+	sfx_frcssg,
 	sfx_elemsg,
 	sfx_armasg,
 	sfx_attrsg,
@@ -269,6 +270,9 @@ typedef enum
 	sfx_sprong,
 	sfx_lvfal1,
 	sfx_pscree,
+	sfx_iceb,
+	sfx_shattr,
+	sfx_antiri,
 
 	// Menu, interface
 	sfx_chchng,
@@ -282,6 +286,9 @@ typedef enum
 	sfx_wepchg,
 	sfx_wtrdng,
 	sfx_zelda,
+	sfx_adderr,
+	sfx_notadd,
+	sfx_addfil,
 
 	// NiGHTS
 	sfx_ideya,
@@ -476,24 +483,9 @@ typedef enum
 	sfx_s25e,
 	sfx_s25f,
 	sfx_s260,
-	sfx_s261,
-	sfx_s262,
-	sfx_s263,
-	sfx_s264,
-	sfx_s265,
-	sfx_s266,
-	sfx_s267,
-	sfx_s268,
-	sfx_s269,
-	sfx_s26a,
-	sfx_s26b,
-	sfx_s26c,
-	sfx_s26d,
-	sfx_s26e,
-	sfx_s26f,
-	sfx_s270,
 
 	// S3&K sounds
+	sfx_s3k2b,
 	sfx_s3k33,
 	sfx_s3k34,
 	sfx_s3k35,
@@ -615,6 +607,21 @@ typedef enum
 	sfx_s3ka9,
 	sfx_s3kaa,
 	sfx_s3kab,
+	sfx_s3kab1,
+	sfx_s3kab2,
+	sfx_s3kab3,
+	sfx_s3kab4,
+	sfx_s3kab5,
+	sfx_s3kab6,
+	sfx_s3kab7,
+	sfx_s3kab8,
+	sfx_s3kab9,
+	sfx_s3kaba,
+	sfx_s3kabb,
+	sfx_s3kabc,
+	sfx_s3kabd,
+	sfx_s3kabe,
+	sfx_s3kabf,
 	sfx_s3kac,
 	sfx_s3kad,
 	sfx_s3kae,
@@ -653,7 +660,8 @@ typedef enum
 	sfx_s3kc5l,
 	sfx_s3kc6s,
 	sfx_s3kc6l,
-	sfx_s3kc7,
+	sfx_s3kc7s,
+	sfx_s3kc7l,
 	sfx_s3kc8s,
 	sfx_s3kc8l,
 	sfx_s3kc9s,
diff --git a/src/st_stuff.c b/src/st_stuff.c
index bc4ab1a544b216c1456e16a8d4305bfdd46ac738..68803b6651f7492d4127c079a8f6d300586e2db2 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -694,7 +694,7 @@ static void ST_drawTime(void)
 	else
 	{
 		// Counting down the hidetime?
-		if ((gametyperules & GTR_HIDETIME) && (stplyr->realtime <= (hidetime*TICRATE)))
+		if ((gametyperules & GTR_STARTCOUNTDOWN) && (stplyr->realtime <= (hidetime*TICRATE)))
 		{
 			tics = (hidetime*TICRATE - stplyr->realtime);
 			if (tics < 3*TICRATE)
@@ -705,7 +705,7 @@ static void ST_drawTime(void)
 		else
 		{
 			// Hidetime finish!
-			if ((gametyperules & GTR_HIDETIME) && (stplyr->realtime < ((hidetime+1)*TICRATE)))
+			if ((gametyperules & GTR_STARTCOUNTDOWN) && (stplyr->realtime < ((hidetime+1)*TICRATE)))
 				ST_drawRaceNum(hidetime*TICRATE - stplyr->realtime);
 
 			// Time limit?
@@ -723,7 +723,7 @@ static void ST_drawTime(void)
 				downwards = true;
 			}
 			// Post-hidetime normal.
-			else if (gametyperules & GTR_TAG)
+			else if (gametyperules & GTR_STARTCOUNTDOWN)
 				tics = stplyr->realtime - hidetime*TICRATE;
 			// "Shadow! What are you doing? Hurry and get back here
 			// right now before the island blows up with you on it!"
@@ -845,69 +845,13 @@ static void ST_drawLivesArea(void)
 			hudinfo[HUD_LIVES].f|V_PERPLAYER|V_HUDTRANS, faceprefix[stplyr->skin], colormap);
 	}
 
-	// Lives number
+	// Metal Sonic recording
 	if (metalrecording)
 	{
 		if (((2*leveltime)/TICRATE) & 1)
 			V_DrawRightAlignedString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y+8,
 				hudinfo[HUD_LIVES].f|V_PERPLAYER|V_REDMAP|V_HUDTRANS, "REC");
 	}
-	else if (G_GametypeUsesLives() || gametype == GT_RACE)
-	{
-		// x
-		V_DrawScaledPatch(hudinfo[HUD_LIVES].x+22, hudinfo[HUD_LIVES].y+10,
-			hudinfo[HUD_LIVES].f|V_PERPLAYER|V_HUDTRANS, stlivex);
-
-		// lives number
-		if (gametype == GT_RACE)
-		{
-			livescount = INFLIVES;
-			notgreyedout = true;
-		}
-		else if ((netgame || multiplayer) && gametype == GT_COOP && cv_cooplives.value == 3)
-		{
-			INT32 i;
-			livescount = 0;
-			notgreyedout = (stplyr->lives > 0);
-			for (i = 0; i < MAXPLAYERS; i++)
-			{
-				if (!playeringame[i])
-					continue;
-
-				if (players[i].lives < 1)
-					continue;
-
-				if (players[i].lives > 1)
-					notgreyedout = true;
-
-				if (players[i].lives == INFLIVES)
-				{
-					livescount = INFLIVES;
-					break;
-				}
-				else if (livescount < 99)
-					livescount += (players[i].lives);
-			}
-		}
-		else
-		{
-			livescount = (((netgame || multiplayer) && gametype == GT_COOP && cv_cooplives.value == 0) ? INFLIVES : stplyr->lives);
-			notgreyedout = true;
-		}
-
-		if (livescount == INFLIVES)
-			V_DrawCharacter(hudinfo[HUD_LIVES].x+50, hudinfo[HUD_LIVES].y+8,
-				'\x16' | 0x80 | hudinfo[HUD_LIVES].f|V_PERPLAYER|V_HUDTRANS, false);
-		else
-		{
-			if (stplyr->playerstate == PST_DEAD && !(stplyr->spectator) && (livescount || stplyr->deadtimer < (TICRATE<<1)))
-				livescount++;
-			if (livescount > 99)
-				livescount = 99;
-			V_DrawRightAlignedString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y+8,
-				hudinfo[HUD_LIVES].f|V_PERPLAYER|(notgreyedout ? V_HUDTRANS : V_HUDTRANSHALF), va("%d",livescount));
-		}
-	}
 	// Spectator
 	else if (stplyr->spectator)
 		v_colmap = V_GRAYMAP;
@@ -934,11 +878,89 @@ static void ST_drawLivesArea(void)
 			v_colmap = V_BLUEMAP;
 		}
 	}
+	// Lives number
+	else
+	{
+		boolean candrawlives = true;
+
+		// Co-op and Competition, normal life counter
+		if (G_GametypeUsesLives())
+		{
+			// Handle cooplives here
+			if ((netgame || multiplayer) && G_GametypeUsesCoopLives() && cv_cooplives.value == 3)
+			{
+				INT32 i;
+				livescount = 0;
+				notgreyedout = (stplyr->lives > 0);
+				for (i = 0; i < MAXPLAYERS; i++)
+				{
+					if (!playeringame[i])
+						continue;
+
+					if (players[i].lives < 1)
+						continue;
+
+					if (players[i].lives > 1)
+						notgreyedout = true;
+
+					if (players[i].lives == INFLIVES)
+					{
+						livescount = INFLIVES;
+						break;
+					}
+					else if (livescount < 99)
+						livescount += (players[i].lives);
+				}
+			}
+			else
+			{
+				livescount = (((netgame || multiplayer) && G_GametypeUsesCoopLives() && cv_cooplives.value == 0) ? INFLIVES : stplyr->lives);
+				notgreyedout = true;
+			}
+		}
+		// Infinity symbol (Race)
+		else if (G_PlatformGametype() && !(gametyperules & GTR_LIVES))
+		{
+			livescount = INFLIVES;
+			notgreyedout = true;
+		}
+		// Otherwise nothing, sorry.
+		// Special Stages keep not showing lives,
+		// as G_GametypeUsesLives() returns false in
+		// Special Stages, and the infinity symbol
+		// cannot show up because Special Stages
+		// still have the GTR_LIVES gametype rule
+		// by default.
+		else
+			candrawlives = false;
+
+		// Draw the lives counter here.
+		if (candrawlives)
+		{
+			// x
+			V_DrawScaledPatch(hudinfo[HUD_LIVES].x+22, hudinfo[HUD_LIVES].y+10, hudinfo[HUD_LIVES].f|V_PERPLAYER|V_HUDTRANS, stlivex);
+			if (livescount == INFLIVES)
+				V_DrawCharacter(hudinfo[HUD_LIVES].x+50, hudinfo[HUD_LIVES].y+8,
+					'\x16' | 0x80 | hudinfo[HUD_LIVES].f|V_PERPLAYER|V_HUDTRANS, false);
+			else
+			{
+				if (stplyr->playerstate == PST_DEAD && !(stplyr->spectator) && (livescount || stplyr->deadtimer < (TICRATE<<1)))
+					livescount++;
+				if (livescount > 99)
+					livescount = 99;
+				V_DrawRightAlignedString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y+8,
+					hudinfo[HUD_LIVES].f|V_PERPLAYER|(notgreyedout ? V_HUDTRANS : V_HUDTRANSHALF), va("%d",livescount));
+			}
+		}
+#undef ST_drawLivesX
+	}
 
 	// name
 	v_colmap |= (V_HUDTRANS|hudinfo[HUD_LIVES].f|V_PERPLAYER);
 	if (strlen(skins[stplyr->skin].hudname) <= 5)
 		V_DrawRightAlignedString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y, v_colmap, skins[stplyr->skin].hudname);
+	else if (V_StringWidth(skins[stplyr->skin].hudname, v_colmap) <= 48)
+		V_DrawString(hudinfo[HUD_LIVES].x+18, hudinfo[HUD_LIVES].y, v_colmap, skins[stplyr->skin].hudname);
 	else if (V_ThinStringWidth(skins[stplyr->skin].hudname, v_colmap) <= 40)
 		V_DrawRightAlignedThinString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y, v_colmap, skins[stplyr->skin].hudname);
 	else
@@ -2212,7 +2234,7 @@ static void ST_drawTextHUD(void)
 			donef12 = true;
 		}
 	}
-	else if (!G_PlatformGametype() && stplyr->playerstate == PST_DEAD && stplyr->lives) // Death overrides spectator text.
+	else if ((gametyperules & GTR_RESPAWNDELAY) && stplyr->playerstate == PST_DEAD && stplyr->lives) // Death overrides spectator text.
 	{
 		INT32 respawntime = cv_respawntime.value - stplyr->deadtimer/TICRATE;
 
@@ -2236,7 +2258,7 @@ static void ST_drawTextHUD(void)
 			textHUDdraw(M_GetText("\x82""Wait for the stage to end..."))
 		else if (G_PlatformGametype())
 		{
-			if (gametype == GT_COOP)
+			if (G_GametypeUsesCoopLives())
 			{
 				if (stplyr->lives <= 0
 				&& cv_cooplives.value == 2
@@ -2647,7 +2669,7 @@ static void ST_overlayDrawer(void)
 		INT32 i = MAXPLAYERS;
 		INT32 deadtimer = stplyr->spectator ? TICRATE : (stplyr->deadtimer-(TICRATE<<1));
 
-		if ((gametype == GT_COOP)
+		if (G_GametypeUsesCoopLives()
 		&& (netgame || multiplayer)
 		&& (cv_cooplives.value != 1))
 		{
diff --git a/src/y_inter.c b/src/y_inter.c
index be0550e72acb03a68d17bf7bd50024cf9559ce46..214dee92ee354e0ab236a6242047545a57be0f40 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -1997,7 +1997,7 @@ static void Y_AwardCoopBonuses(void)
 
 		if (i == consoleplayer)
 		{
-			data.coop.gotlife = (((netgame || multiplayer) && gametype == GT_COOP && cv_cooplives.value == 0) ? 0 : ptlives);
+			data.coop.gotlife = (((netgame || multiplayer) && G_GametypeUsesCoopLives() && cv_cooplives.value == 0) ? 0 : ptlives);
 			M_Memcpy(&data.coop.bonuses, &localbonuses, sizeof(data.coop.bonuses));
 		}
 	}
@@ -2052,7 +2052,7 @@ static void Y_AwardSpecialStageBonus(void)
 
 		if (i == consoleplayer)
 		{
-			data.spec.gotlife = (((netgame || multiplayer) && gametype == GT_COOP && cv_cooplives.value == 0) ? 0 : ptlives);
+			data.spec.gotlife = (((netgame || multiplayer) && G_GametypeUsesCoopLives() && cv_cooplives.value == 0) ? 0 : ptlives);
 			M_Memcpy(&data.spec.bonuses, &localbonuses, sizeof(data.spec.bonuses));
 
 			// Continues related