diff --git a/src/d_main.c b/src/d_main.c
index 51c04e17b116917c3be413608f68e230720a2325..e47ff38bfda9a0136864a8ea0aba5ff36778e4bc 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 02ba1f2a358661cc5aeb0efdbdf624b75609fe73..336fd1ac8df0d88afeeeb8c10480ecbf2a244e2f 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -3747,7 +3747,7 @@ static void CoopStarposts_OnChange(void)
 {
 	INT32 i;
 
-	if (!(netgame || multiplayer) || gametype != GT_COOP)
+	if (!(netgame || multiplayer) || !G_GametypeUsesCoopStarposts())
 		return;
 
 	switch (cv_coopstarposts.value)
@@ -3802,7 +3802,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 125086d4444da5d7ad304412fdb244996835b08d..4b6d76a6a044bf0c15b457cfa19365969225d4e3 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -8904,32 +8904,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/doomstat.h b/src/doomstat.h
index b7bb7a36228b5e732db282b0948b2ea4841d2a03..6d1d6eb3665b95eceb0c58683086722d0f98b92f 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 79ea2c70bbef5be74b13d252072d7be3ba28fabf..36d67b324d9170a7c09c36251fc6e90c59f161b4 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -2689,8 +2689,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
@@ -2891,11 +2890,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++)
 			{
@@ -2930,7 +2929,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++)
 			{
@@ -3006,7 +3005,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;
@@ -3077,7 +3076,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;
@@ -3194,24 +3193,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,
 };
 
 //
@@ -3252,50 +3251,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;
@@ -3440,6 +3457,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
 //
@@ -3493,6 +3532,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.
@@ -3734,7 +3783,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
 	{
@@ -3844,7 +3893,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
 		{
@@ -4547,7 +4596,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 238dd196420d860517630ca857fde566b60a8b31..68e78789fbd563e2e7e839f951aa632fa7936547 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -218,11 +218,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 657a0018bc8cf19e4064cbba3a83c24bb5d3a2f2..347afb7357c2833f3ddf4fece3a65502f36522c8 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -2437,7 +2437,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)
 		{
@@ -2746,7 +2746,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_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 6ef9a5e1962f0048f0807864ab5bf77be4977200..a97f574c2ef1e0820198bbaaf15a4d3f9be6ad0a 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -4735,7 +4735,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;
diff --git a/src/p_inter.c b/src/p_inter.c
index dc52cd3c0309441bc7849a465eb5267d327015e9..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++)
 				{
@@ -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 cd4ac3ca837d45843f409639226323b962687587..b291c484175f31f3ac33bc781055ff45fd2ce72d 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 d556193b9df9483c727663908f04608f2ecca33e..366fc5372be918f6db23ae18fe95d6d0ffde0385 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -2446,7 +2446,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 +2464,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 +2754,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 +2972,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 +3016,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
diff --git a/src/p_user.c b/src/p_user.c
index 4b5b3cd795c12bc3f3f24eb5026c487ef47050e4..29c5cf546112ca39eaec42f78cc966f347f162f2 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -1230,13 +1230,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)
@@ -1267,7 +1267,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)
@@ -1395,7 +1395,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);
@@ -9305,7 +9305,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;
 
@@ -9454,7 +9454,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)))
@@ -9469,19 +9469,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;
 				}
 			}
 
@@ -9495,7 +9498,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++)
 			{
@@ -11550,7 +11553,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/st_stuff.c b/src/st_stuff.c
index 9c4f0abd5674845c384f49885ca116477bb56896..53c00b4c8ba54b2658049e03f1227611972de22a 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,6 +878,82 @@ 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);
@@ -2202,7 +2222,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;
 
@@ -2226,7 +2246,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
@@ -2637,7 +2657,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