diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 83482b527c3354f5c2b6f81cc2e47f66b108c2b1..a84c706cbb45fb0869729db26b6dd6ec4ed0b922 100755
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -1389,7 +1389,7 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime)
 
 	netbuffer->u.serverinfo.refusereason = GetRefuseReason(node);
 
-	strncpy(netbuffer->u.serverinfo.gametypename, Gametype_Names[gametype],
+	strncpy(netbuffer->u.serverinfo.gametypename, gametypes[gametype].name,
 			sizeof netbuffer->u.serverinfo.gametypename);
 	netbuffer->u.serverinfo.modifiedgame = (UINT8)modifiedgame;
 	netbuffer->u.serverinfo.cheatsenabled = CV_CheatsEnabled();
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 77a271825e6abad6fa18cb3d29c19d19094a8018..9bb972dc5493558d1e035d3bf19081b5416ef879 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -323,10 +323,6 @@ consvar_t cv_numlaps = CVAR_INIT ("numlaps", "4", CV_NETVAR|CV_CALL|CV_NOINIT|CV
 static CV_PossibleValue_t basenumlaps_cons_t[] = {{1, "MIN"}, {50, "MAX"}, {0, "Map default"}, {0, NULL}};
 consvar_t cv_basenumlaps = CVAR_INIT ("basenumlaps", "Map default", CV_SAVE|CV_NETVAR|CV_CALL|CV_CHEAT|CV_ALLOWLUA, basenumlaps_cons_t, BaseNumLaps_OnChange);
 
-// Point and time limits for every gametype
-INT32 pointlimits[NUMGAMETYPES];
-INT32 timelimits[NUMGAMETYPES];
-
 // log elemental hazards -- not a netvar, is local to current player
 consvar_t cv_hazardlog = CVAR_INIT ("hazardlog", "Yes", 0, CV_YesNo, NULL);
 
@@ -439,15 +435,7 @@ const char *netxcmdnames[MAXNETXCMD - 1] =
   */
 void D_RegisterServerCommands(void)
 {
-	INT32 i;
-
-	for (i = 0; i < NUMGAMETYPES; i++)
-	{
-		gametype_cons_t[i].value = i;
-		gametype_cons_t[i].strvalue = Gametype_Names[i];
-	}
-	gametype_cons_t[NUMGAMETYPES].value = 0;
-	gametype_cons_t[NUMGAMETYPES].strvalue = NULL;
+	G_UpdateGametypeSelections();
 
 	RegisterNetXCmd(XD_NAMEANDCOLOR, Got_NameAndColor);
 	RegisterNetXCmd(XD_WEAPONPREF, Got_WeaponPref);
@@ -1807,7 +1795,7 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pultmode, boolean rese
 	}
 	CONS_Debug(DBG_GAMELOGIC, "Map change: mapnum=%d gametype=%d ultmode=%d resetplayers=%d delay=%d skipprecutscene=%d\n",
 	           mapnum, newgametype, pultmode, resetplayers, delay, skipprecutscene);
-	if ((netgame || multiplayer) && !((gametype == newgametype) && (gametypedefaultrules[newgametype] & GTR_CAMPAIGN)))
+	if ((netgame || multiplayer) && !((gametype == newgametype) && (gametypes[newgametype].rules & GTR_CAMPAIGN)))
 		FLS = false;
 
 	if (delay != 2)
@@ -2049,7 +2037,7 @@ static void Command_Map_f(void)
 			fromlevelselect =
 				( netgame || multiplayer ) &&
 				newgametype == gametype    &&
-				gametypedefaultrules[newgametype] & GTR_CAMPAIGN;
+				gametypes[newgametype].rules & GTR_CAMPAIGN;
 		}
 	}
 
@@ -3921,7 +3909,7 @@ static void Command_ShowGametype_f(void)
 
 	// get name string for current gametype
 	if (gametype >= 0 && gametype < gametypecount)
-		gametypestr = Gametype_Names[gametype];
+		gametypestr = gametypes[gametype].name;
 
 	if (gametypestr)
 		CONS_Printf(M_GetText("Current gametype is %s\n"), gametypestr);
@@ -4183,9 +4171,9 @@ void D_GameTypeChanged(INT32 lastgametype)
 		const char *oldgt = NULL, *newgt = NULL;
 
 		if (lastgametype >= 0 && lastgametype < gametypecount)
-			oldgt = Gametype_Names[lastgametype];
+			oldgt = gametypes[lastgametype].name;
 		if (gametype >= 0 && lastgametype < gametypecount)
-			newgt = Gametype_Names[gametype];
+			newgt = gametypes[gametype].name;
 
 		if (oldgt && newgt)
 			CONS_Printf(M_GetText("Gametype was changed from %s to %s\n"), oldgt, newgt);
@@ -4199,55 +4187,13 @@ void D_GameTypeChanged(INT32 lastgametype)
 		else if (!cv_itemrespawn.changed || lastgametype == GT_COMPETITION)
 			CV_SetValue(&cv_itemrespawn, 1);
 
-		switch (gametype)
+		if (!cv_timelimit.changed && !cv_pointlimit.changed) // user hasn't changed limits
 		{
-			case GT_COOP:
-				if (!cv_itemrespawntime.changed)
-					CV_Set(&cv_itemrespawntime, cv_itemrespawntime.defaultvalue); // respawn normally
-				break;
-			case GT_MATCH:
-			case GT_TEAMMATCH:
-				if (!cv_timelimit.changed && !cv_pointlimit.changed) // user hasn't changed limits
-				{
-					// default settings for match: timelimit 10 mins, no pointlimit
-					CV_SetValue(&cv_pointlimit, 0);
-					CV_SetValue(&cv_timelimit, 10);
-				}
-				if (!cv_itemrespawntime.changed)
-					CV_Set(&cv_itemrespawntime, cv_itemrespawntime.defaultvalue); // respawn normally
-				break;
-			case GT_TAG:
-			case GT_HIDEANDSEEK:
-				if (!cv_timelimit.changed && !cv_pointlimit.changed) // user hasn't changed limits
-				{
-					// default settings for tag: 5 mins, no pointlimit
-					// Note that tag mode also uses an alternate timing mechanism in tandem with timelimit.
-					CV_SetValue(&cv_timelimit, 5);
-					CV_SetValue(&cv_pointlimit, 0);
-				}
-				if (!cv_itemrespawntime.changed)
-					CV_Set(&cv_itemrespawntime, cv_itemrespawntime.defaultvalue); // respawn normally
-				break;
-			case GT_CTF:
-				if (!cv_timelimit.changed && !cv_pointlimit.changed) // user hasn't changed limits
-				{
-					// default settings for CTF: no timelimit, pointlimit 5
-					CV_SetValue(&cv_timelimit, 0);
-					CV_SetValue(&cv_pointlimit, 5);
-				}
-				if (!cv_itemrespawntime.changed)
-					CV_Set(&cv_itemrespawntime, cv_itemrespawntime.defaultvalue); // respawn normally
-				break;
-			default:
-				if (!cv_timelimit.changed && !cv_pointlimit.changed) // user hasn't changed limits
-				{
-					CV_SetValue(&cv_timelimit, timelimits[gametype]);
-					CV_SetValue(&cv_pointlimit, pointlimits[gametype]);
-				}
-				if (!cv_itemrespawntime.changed)
-					CV_Set(&cv_itemrespawntime, cv_itemrespawntime.defaultvalue); // respawn normally
-				break;
+			CV_SetValue(&cv_timelimit, gametypes[gametype].timelimit);
+			CV_SetValue(&cv_pointlimit, gametypes[gametype].pointlimit);
 		}
+		if (!cv_itemrespawntime.changed)
+			CV_Set(&cv_itemrespawntime, cv_itemrespawntime.defaultvalue); // respawn normally
 	}
 	else if (!multiplayer && !netgame)
 	{
diff --git a/src/deh_lua.c b/src/deh_lua.c
index 1b177eb25724e38eb42f81f08ff9919177372c29..fea96ce5d205676a1889f09c2908e7ff4b498809 100644
--- a/src/deh_lua.c
+++ b/src/deh_lua.c
@@ -307,8 +307,8 @@ static int ScanConstants(lua_State *L, boolean mathlib, const char *word)
 	}
 	else if (fastncmp("GT_", word, 3)) {
 		p = word;
-		for (i = 0; Gametype_ConstantNames[i]; i++)
-			if (fastcmp(p, Gametype_ConstantNames[i])) {
+		for (i = 0; i < gametypecount; i++)
+			if (fastcmp(p, gametypes[i].constant_name)) {
 				CacheAndPushConstant(L, word, i);
 				return 1;
 			}
diff --git a/src/deh_soc.c b/src/deh_soc.c
index 2193cd875cd00898c035c0d47d030f189045572c..df44b3caac9799509161d8ea75da59b82acb9b0a 100644
--- a/src/deh_soc.c
+++ b/src/deh_soc.c
@@ -1318,13 +1318,13 @@ void readgametype(MYFILE *f, char *gtname)
 	// Not covered by G_AddGametype alone.
 	if (newgtrankingstype == -1)
 		newgtrankingstype = newgtidx;
-	gametyperankings[newgtidx] = newgtrankingstype;
-	intermissiontypes[newgtidx] = newgtinttype;
-	pointlimits[newgtidx] = newgtpointlimit;
-	timelimits[newgtidx] = newgttimelimit;
+	gametypes[newgtidx].rankings_type = newgtrankingstype;
+	gametypes[newgtidx].intermission_type = newgtinttype;
+	gametypes[newgtidx].pointlimit = newgtpointlimit;
+	gametypes[newgtidx].timelimit = newgttimelimit;
 
 	// Write the new gametype name.
-	Gametype_Names[newgtidx] = Z_StrDup((const char *)gtname);
+	gametypes[newgtidx].name = Z_StrDup((const char *)gtname);
 
 	// Write the constant name.
 	if (gtconst[0] == '\0')
@@ -1334,7 +1334,7 @@ void readgametype(MYFILE *f, char *gtname)
 	// Update gametype_cons_t accordingly.
 	G_UpdateGametypeSelections();
 
-	CONS_Printf("Added gametype %s\n", Gametype_Names[newgtidx]);
+	CONS_Printf("Added gametype %s\n", gametypes[newgtidx].name);
 }
 
 void readlevelheader(MYFILE *f, INT32 num)
@@ -4221,34 +4221,6 @@ menutype_t get_menutype(const char *word)
 	return MN_NONE;
 }
 
-/*static INT16 get_gametype(const char *word)
-{ // Returns the value of GT_ enumerations
-	INT16 i;
-	if (*word >= '0' && *word <= '9')
-		return atoi(word);
-	if (fastncmp("GT_",word,3))
-		word += 3; // take off the GT_
-	for (i = 0; i < NUMGAMETYPES; i++)
-		if (fastcmp(word, Gametype_ConstantNames[i]+3))
-			return i;
-	deh_warning("Couldn't find gametype named 'GT_%s'",word);
-	return GT_COOP;
-}
-
-static powertype_t get_power(const char *word)
-{ // Returns the value of pw_ enumerations
-	powertype_t i;
-	if (*word >= '0' && *word <= '9')
-		return atoi(word);
-	if (fastncmp("PW_",word,3))
-		word += 3; // take off the pw_
-	for (i = 0; i < NUMPOWERS; i++)
-		if (fastcmp(word, POWERS_LIST[i]))
-			return i;
-	deh_warning("Couldn't find power named 'pw_%s'",word);
-	return pw_invulnerability;
-}*/
-
 /// \todo Make ANY of this completely over-the-top math craziness obey the order of operations.
 static fixed_t op_mul(fixed_t a, fixed_t b) { return a*b; }
 static fixed_t op_div(fixed_t a, fixed_t b) { return a/b; }
diff --git a/src/doomstat.h b/src/doomstat.h
index a812cc304f6e0b19cab8f2fecf37868264ed9b16..30f29799e90343d1d4bd59ccbf6a52b91d42ff82 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -98,7 +98,6 @@ extern boolean multiplayer;
 
 extern INT16 gametype;
 extern UINT32 gametyperules;
-extern INT16 gametypecount;
 
 extern boolean splitscreen;
 extern boolean circuitmap; // Does this level have 'circuit mode'?
@@ -409,7 +408,7 @@ enum GameType
 	GT_LASTFREESLOT = GT_FIRSTFREESLOT + NUMGAMETYPEFREESLOTS - 1,
 	NUMGAMETYPES
 };
-// If you alter this list, update deh_tables.c, MISC_ChangeGameTypeMenu in m_menu.c, and Gametype_Names in g_game.c
+// If you alter this list, update deh_tables.c, MISC_ChangeGameTypeMenu in m_menu.c, and gametypes in g_game.c
 
 // Gametype rules
 enum GameTypeRules
@@ -448,13 +447,27 @@ enum GameTypeRules
 	GTR_CUTSCENES        = 1<<31, // Play cutscenes, ending, credits, and evaluation
 };
 
-// String names for gametypes
-extern const char *Gametype_Names[NUMGAMETYPES];
-extern const char *Gametype_ConstantNames[NUMGAMETYPES];
+enum
+{
+	RANKINGS_DEFAULT,
+	RANKINGS_COMPETITION,
+	RANKINGS_RACE
+};
 
-// Point and time limits for every gametype
-extern INT32 pointlimits[NUMGAMETYPES];
-extern INT32 timelimits[NUMGAMETYPES];
+typedef struct
+{
+	const char *name;
+	const char *constant_name;
+	UINT32 rules;
+	UINT32 typeoflevel;
+	UINT8 intermission_type;
+	INT16 rankings_type;
+	INT32 pointlimit;
+	INT32 timelimit;
+} gametype_t;
+
+extern gametype_t gametypes[NUMGAMETYPES];
+extern INT16 gametypecount;
 
 // TypeOfLevel things
 enum TypeOfLevel
diff --git a/src/g_game.c b/src/g_game.c
index 04b1ffe4da0809c30e656a13ff70611febceb8ec..bf2d5ae1bfa818401969266ac5460ce697d24c77 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -3404,86 +3404,117 @@ void G_ExitLevel(void)
 	}
 }
 
-// See also the enum GameType in doomstat.h
-const char *Gametype_Names[NUMGAMETYPES] =
-{
-	"Co-op", // GT_COOP
-	"Competition", // GT_COMPETITION
-	"Race", // GT_RACE
-
-	"Match", // GT_MATCH
-	"Team Match", // GT_TEAMMATCH
-
-	"Tag", // GT_TAG
-	"Hide & Seek", // GT_HIDEANDSEEK
-
-	"CTF" // GT_CTF
-};
-
-// For dehacked
-const char *Gametype_ConstantNames[NUMGAMETYPES] =
-{
-	"GT_COOP", // GT_COOP
-	"GT_COMPETITION", // GT_COMPETITION
-	"GT_RACE", // GT_RACE
-
-	"GT_MATCH", // GT_MATCH
-	"GT_TEAMMATCH", // GT_TEAMMATCH
-
-	"GT_TAG", // GT_TAG
-	"GT_HIDEANDSEEK", // GT_HIDEANDSEEK
-
-	"GT_CTF" // GT_CTF
-};
-
-// Gametype rules
-UINT32 gametypedefaultrules[NUMGAMETYPES] =
-{
-	// Co-op
-	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_OVERTIME|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_OVERTIME|GTR_DEATHMATCHSTARTS|GTR_SPAWNINVUL|GTR_RESPAWNDELAY|GTR_PITYSHIELD,
-
-	// Tag
-	GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_TAG|GTR_SPECTATORS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_OVERTIME|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_OVERTIME|GTR_STARTCOUNTDOWN|GTR_HIDEFROZEN|GTR_BLINDFOLDED|GTR_DEATHMATCHSTARTS|GTR_SPAWNINVUL|GTR_RESPAWNDELAY,
-
-	// CTF
-	GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_SPECTATORS|GTR_TEAMS|GTR_TEAMFLAGS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_OVERTIME|GTR_POWERSTONES|GTR_DEATHMATCHSTARTS|GTR_SPAWNINVUL|GTR_RESPAWNDELAY|GTR_PITYSHIELD,
+gametype_t gametypes[NUMGAMETYPES] = {
+	// GT_COOP
+	{
+		.name = "Co-op",
+		.constant_name = "GT_COOP",
+		.rules = GTR_CAMPAIGN|GTR_LIVES|GTR_FRIENDLY|GTR_SPAWNENEMIES|GTR_ALLOWEXIT|GTR_EMERALDHUNT|GTR_EMERALDTOKENS|GTR_SPECIALSTAGES|GTR_CUTSCENES,
+		.typeoflevel = TOL_COOP,
+		.intermission_type = int_coop,
+		.rankings_type = RANKINGS_DEFAULT,
+	},
+	// GT_COMPETITION
+	{
+		.name = "Competition",
+		.constant_name = "GT_COMPETITION",
+		.rules = GTR_RACE|GTR_LIVES|GTR_SPAWNENEMIES|GTR_EMERALDTOKENS|GTR_SPAWNINVUL|GTR_ALLOWEXIT,
+		.typeoflevel = TOL_COMPETITION,
+		.intermission_type = int_comp,
+		.rankings_type = RANKINGS_COMPETITION,
+	},
+	// GT_RACE
+	{
+		.name = "Race",
+		.constant_name = "GT_RACE",
+		.rules = GTR_RACE|GTR_SPAWNENEMIES|GTR_SPAWNINVUL|GTR_ALLOWEXIT,
+		.typeoflevel = TOL_RACE,
+		.intermission_type = int_race,
+		.rankings_type = RANKINGS_RACE,
+	},
+	// GT_MATCH
+	{
+		.name = "Match",
+		.constant_name = "GT_MATCH",
+		.rules = GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_SPECTATORS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_OVERTIME|GTR_POWERSTONES|GTR_DEATHMATCHSTARTS|GTR_SPAWNINVUL|GTR_RESPAWNDELAY|GTR_PITYSHIELD|GTR_DEATHPENALTY,
+		.typeoflevel = TOL_MATCH,
+		.intermission_type = int_match,
+		.rankings_type = RANKINGS_DEFAULT,
+		// default settings for match: timelimit 10 mins, no pointlimit
+		.timelimit = 10,
+		.pointlimit = 0
+	},
+	// GT_TEAMMATCH
+	{
+		.name = "Team Match",
+		.constant_name = "GT_TEAMMATCH",
+		.rules = GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_SPECTATORS|GTR_TEAMS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_OVERTIME|GTR_DEATHMATCHSTARTS|GTR_SPAWNINVUL|GTR_RESPAWNDELAY|GTR_PITYSHIELD,
+		.typeoflevel = TOL_MATCH,
+		.intermission_type = int_teammatch,
+		.rankings_type = RANKINGS_DEFAULT,
+		// default settings for match: timelimit 10 mins, no pointlimit
+		.timelimit = 10,
+		.pointlimit = 0
+	},
+	// GT_TAG
+	{
+		.name = "Tag",
+		.constant_name = "GT_TAG",
+		.rules = GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_TAG|GTR_SPECTATORS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_OVERTIME|GTR_STARTCOUNTDOWN|GTR_BLINDFOLDED|GTR_DEATHMATCHSTARTS|GTR_SPAWNINVUL|GTR_RESPAWNDELAY,
+		.typeoflevel = TOL_TAG,
+		.intermission_type = int_match,
+		.rankings_type = RANKINGS_DEFAULT,
+		// default settings for tag: 5 mins, no pointlimit
+		// Note that tag mode also uses an alternate timing mechanism in tandem with timelimit.
+		.timelimit = 5,
+		.pointlimit = 0
+	},
+	// GT_HIDEANDSEEK
+	{
+		.name = "Hide & Seek",
+		.constant_name = "GT_HIDEANDSEEK",
+		.rules = GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_TAG|GTR_SPECTATORS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_OVERTIME|GTR_STARTCOUNTDOWN|GTR_HIDEFROZEN|GTR_BLINDFOLDED|GTR_DEATHMATCHSTARTS|GTR_SPAWNINVUL|GTR_RESPAWNDELAY,
+		.typeoflevel = TOL_TAG,
+		.intermission_type = int_match,
+		.rankings_type = RANKINGS_DEFAULT,
+		// default settings for tag: 5 mins, no pointlimit
+		// Note that tag mode also uses an alternate timing mechanism in tandem with timelimit.
+		.timelimit = 5,
+		.pointlimit = 0
+	},
+	// GT_CTF
+	{
+		.name = "CTF",
+		.constant_name = "GT_CTF",
+		.rules = GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_SPECTATORS|GTR_TEAMS|GTR_TEAMFLAGS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_OVERTIME|GTR_POWERSTONES|GTR_DEATHMATCHSTARTS|GTR_SPAWNINVUL|GTR_RESPAWNDELAY|GTR_PITYSHIELD,
+		.typeoflevel = TOL_CTF,
+		.intermission_type = int_ctf,
+		.rankings_type = RANKINGS_DEFAULT,
+		// default settings for CTF: no timelimit, pointlimit 5
+		.timelimit = 0,
+		.pointlimit = 5
+	},
 };
 
 //
-// G_SetGametype
-//
-// Set a new gametype, also setting gametype rules accordingly. Yay!
+// Sets a new gametype, also setting gametype rules accordingly.
 //
 void G_SetGametype(INT16 gtype)
 {
 	gametype = gtype;
-	gametyperules = gametypedefaultrules[gametype];
+	gametyperules = gametypes[gametype].rules;
 }
 
 //
-// G_AddGametype
-//
-// Add a gametype. Returns the new gametype number.
+// Adds a gametype. Returns the new gametype number.
 //
 INT16 G_AddGametype(UINT32 rules)
 {
 	INT16 newgtype = gametypecount;
 	gametypecount++;
 
-	// Set gametype rules.
-	gametypedefaultrules[newgtype] = rules;
-	Gametype_Names[newgtype] = "???";
+	gametypes[newgtype].name = "???";
+	gametypes[newgtype].rules = rules;
 
 	// Update gametype_cons_t accordingly.
 	G_UpdateGametypeSelections();
@@ -3491,78 +3522,27 @@ INT16 G_AddGametype(UINT32 rules)
 	return newgtype;
 }
 
-//
-// G_AddGametypeConstant
-//
-// Self-explanatory. Filters out "bad" characters.
-//
 void G_AddGametypeConstant(INT16 gtype, const char *newgtconst)
 {
-	size_t r = 0; // read
-	size_t w = 0; // write
 	char *gtconst = Z_Calloc(strlen(newgtconst) + 4, PU_STATIC, NULL);
 	char *tmpconst = Z_Calloc(strlen(newgtconst) + 1, PU_STATIC, NULL);
 
-	// Copy the gametype name.
 	strcpy(tmpconst, newgtconst);
-
-	// Make uppercase.
 	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++)
+	size_t w = strlen(gtconst);
+
+	for (size_t r = 0; 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++;
-		}
+		if (isalpha(rc) || isdigit(rc))
+			gtconst[w++] = rc;
 	}
 
-	// Free the temporary string.
 	Z_Free(tmpconst);
 
-	// Finally, set the constant string.
-	Gametype_ConstantNames[gtype] = gtconst;
+	gametypes[gtype].constant_name = gtconst;
 }
 
 //
@@ -3576,7 +3556,7 @@ void G_UpdateGametypeSelections(void)
 	for (i = 0; i < gametypecount; i++)
 	{
 		gametype_cons_t[i].value = i;
-		gametype_cons_t[i].strvalue = Gametype_Names[i];
+		gametype_cons_t[i].strvalue = gametypes[i].name;
 	}
 	gametype_cons_t[NUMGAMETYPES].value = 0;
 	gametype_cons_t[NUMGAMETYPES].strvalue = NULL;
@@ -3596,38 +3576,6 @@ void G_SetGametypeDescription(INT16 gtype, char *descriptiontext, UINT8 leftcolo
 	gametypedesc[gtype].col[1] = rightcolor;
 }
 
-// Gametype rankings
-INT16 gametyperankings[NUMGAMETYPES] =
-{
-	GT_COOP,
-	GT_COMPETITION,
-	GT_RACE,
-
-	GT_MATCH,
-	GT_TEAMMATCH,
-
-	GT_TAG,
-	GT_HIDEANDSEEK,
-
-	GT_CTF,
-};
-
-// Gametype to TOL (Type Of Level)
-UINT32 gametypetol[NUMGAMETYPES] =
-{
-	TOL_COOP, // Co-op
-	TOL_COMPETITION, // Competition
-	TOL_RACE, // Race
-
-	TOL_MATCH, // Match
-	TOL_MATCH, // Team Match
-
-	TOL_TAG, // Tag
-	TOL_TAG, // Hide and Seek
-
-	TOL_CTF, // CTF
-};
-
 tolinfo_t TYPEOFLEVEL[NUMTOLNAMES] = {
 	{"SOLO",TOL_SP},
 	{"SP",TOL_SP},
@@ -3681,7 +3629,7 @@ void G_AddTOL(UINT32 newtol, const char *tolname)
 //
 void G_AddGametypeTOL(INT16 gtype, UINT32 newtol)
 {
-	gametypetol[gtype] = newtol;
+	gametypes[gtype].typeoflevel = newtol;
 }
 
 //
@@ -3694,7 +3642,7 @@ INT32 G_GetGametypeByName(const char *gametypestr)
 	INT32 i;
 
 	for (i = 0; i < gametypecount; i++)
-		if (!stricmp(gametypestr, Gametype_Names[i]))
+		if (!stricmp(gametypestr, gametypes[i].name))
 			return i;
 
 	return -1; // unknown gametype
@@ -3840,7 +3788,8 @@ UINT32 G_TOLFlag(INT32 pgametype)
 {
 	if (!multiplayer)
 		return TOL_SP;
-	return gametypetol[pgametype];
+
+	return gametypes[pgametype].typeoflevel;
 }
 
 /** Select a random map with the given typeoflevel flags.
diff --git a/src/g_game.h b/src/g_game.h
index a8c285f79f442b04a266f7e4ccbdd34997029833..8bcbbfdadc66f377879111ce5bd108e740792bf4 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -190,10 +190,6 @@ void G_SaveGame(UINT32 slot, INT16 mapnum);
 
 void G_SaveGameOver(UINT32 slot, boolean modifylives);
 
-extern UINT32 gametypedefaultrules[NUMGAMETYPES];
-extern UINT32 gametypetol[NUMGAMETYPES];
-extern INT16 gametyperankings[NUMGAMETYPES];
-
 void G_SetGametype(INT16 gametype);
 INT16 G_AddGametype(UINT32 rules);
 void G_AddGametypeConstant(INT16 gtype, const char *newgtconst);
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index 091e2b2fba50e46638aa6ee52bff2d7e6c81f9f0..2f3d29f3e440a441ed8e560cac440318b2cadaf3 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -1905,7 +1905,7 @@ static void HU_drawGametype(void)
 	if (gametype < 0 || gametype >= gametypecount)
 		return; // not a valid gametype???
 
-	strvalue = Gametype_Names[gametype];
+	strvalue = gametypes[gametype].name;
 
 	if (splitscreen)
 		V_DrawString(4, 184, 0, strvalue);
@@ -2188,7 +2188,7 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
 
 	for (i = 0; i < scorelines; i++)
 	{
-		if (players[tab[i].num].spectator && gametyperankings[gametype] != GT_COOP)
+		if (players[tab[i].num].spectator && !G_GametypeUsesCoopLives())
 			continue; //ignore them.
 
 		greycheck = greycheckdef;
@@ -2265,7 +2265,7 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
 		if (players[tab[i].num].exiting || (players[tab[i].num].pflags & PF_FINISHED))
 			V_DrawSmallScaledPatch(x - exiticon->width/2 - 1, y-3, 0, exiticon);
 
-		if (gametyperankings[gametype] == GT_RACE)
+		if (gametypes[gametype].rankings_type == RANKINGS_RACE)
 		{
 			if (circuitmap)
 			{
@@ -2546,7 +2546,7 @@ void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scoreline
 
 	for (i = 0; i < scorelines; i++)
 	{
-		if (players[tab[i].num].spectator && gametyperankings[gametype] != GT_COOP)
+		if (players[tab[i].num].spectator && !G_GametypeUsesCoopLives())
 			continue; //ignore them.
 
 		greycheck = greycheckdef;
@@ -2613,7 +2613,7 @@ void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scoreline
 		}
 
 		// All data drawn with thin string for space.
-		if (gametyperankings[gametype] == GT_RACE)
+		if (gametypes[gametype].rankings_type == RANKINGS_RACE)
 		{
 			if (circuitmap)
 			{
@@ -2653,7 +2653,7 @@ static void HU_Draw32TabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scor
 
 	for (i = 0; i < scorelines; i++)
 	{
-		if (players[tab[i].num].spectator && gametyperankings[gametype] != GT_COOP)
+		if (players[tab[i].num].spectator && !G_GametypeUsesCoopLives())
 			continue; //ignore them.
 
 		greycheck = greycheckdef;
@@ -2724,7 +2724,7 @@ static void HU_Draw32TabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scor
 		}
 
 		// All data drawn with thin string for space.
-		if (gametyperankings[gametype] == GT_RACE)
+		if (gametypes[gametype].rankings_type == RANKINGS_RACE)
 		{
 			if (circuitmap)
 			{
@@ -2884,7 +2884,7 @@ static void HU_DrawRankings(void)
 		tab[i].num = -1;
 		tab[i].name = 0;
 
-		if (gametyperankings[gametype] == GT_RACE && !circuitmap)
+		if (gametypes[gametype].rankings_type == RANKINGS_RACE && !circuitmap)
 			tab[i].count = INT32_MAX;
 	}
 
@@ -2904,7 +2904,7 @@ static void HU_DrawRankings(void)
 			if (!G_PlatformGametype() && players[i].spectator)
 				continue;
 
-			if (gametyperankings[gametype] == GT_RACE)
+			if (gametypes[gametype].rankings_type == RANKINGS_RACE)
 			{
 				if (circuitmap)
 				{
@@ -2927,7 +2927,7 @@ static void HU_DrawRankings(void)
 					}
 				}
 			}
-			else if (gametyperankings[gametype] == GT_COMPETITION)
+			else if (gametypes[gametype].rankings_type == RANKINGS_COMPETITION)
 			{
 				// todo put something more fitting for the gametype here, such as current
 				// number of categories led
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 915669bb2786b5f78cb3e95af08fa2e947545b07..a6f8cf9a0840832f7af0abde08ca9458a2a04d9e 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -3519,13 +3519,13 @@ static int lib_gAddGametype(lua_State *L)
 	// Not covered by G_AddGametype alone.
 	if (newgtrankingstype == -1)
 		newgtrankingstype = newgtidx;
-	gametyperankings[newgtidx] = newgtrankingstype;
-	intermissiontypes[newgtidx] = newgtinttype;
-	pointlimits[newgtidx] = newgtpointlimit;
-	timelimits[newgtidx] = newgttimelimit;
+	gametypes[newgtidx].rankings_type = newgtrankingstype;
+	gametypes[newgtidx].intermission_type = newgtinttype;
+	gametypes[newgtidx].pointlimit = newgtpointlimit;
+	gametypes[newgtidx].timelimit = newgttimelimit;
 
 	// Write the new gametype name.
-	Gametype_Names[newgtidx] = gtname;
+	gametypes[newgtidx].name = gtname;
 
 	// Write the constant name.
 	if (gtconst == NULL)
@@ -3536,7 +3536,7 @@ static int lib_gAddGametype(lua_State *L)
 	G_UpdateGametypeSelections();
 
 	// done
-	CONS_Printf("Added gametype %s\n", Gametype_Names[newgtidx]);
+	CONS_Printf("Added gametype %s\n", gametypes[newgtidx].name);
 	return 0;
 }
 
diff --git a/src/m_menu.c b/src/m_menu.c
index 3451b90d63a5b5ed1f3b0ca663a0a45392c90286..6828063918983b0c244b634a4d3b769f369df4c4 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -5085,7 +5085,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 & gametypes[gt].typeoflevel))
 				return true;
 
 			return false;
diff --git a/src/p_setup.c b/src/p_setup.c
index cbb817d83baa5b89b61a407264a7c5beccef326e..4669761cd3b3e75146108987d8fd3da01d291a20 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -7646,7 +7646,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
 
 	// This is needed. Don't touch.
 	maptol = mapheaderinfo[gamemap-1]->typeoflevel;
-	gametyperules = gametypedefaultrules[gametype];
+	gametyperules = gametypes[gametype].rules;
 
 	CON_Drawer(); // let the user know what we are going to do
 	I_FinishUpdate(); // page flip or blit buffer
diff --git a/src/y_inter.c b/src/y_inter.c
index 5e071171f33670ac1ce747bfa6d880247e215567..dbc114ff706431f29634435f79c49a4f85114910 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -164,7 +164,6 @@ static INT32 tallydonetic = -1;
 static INT32 endtic = -1;
 
 intertype_t intertype = int_none;
-intertype_t intermissiontypes[NUMGAMETYPES];
 
 static huddrawlist_h luahuddrawlist_intermission;
 
@@ -1275,25 +1274,10 @@ void Y_Ticker(void)
 //
 void Y_DetermineIntermissionType(void)
 {
-	// set to int_none initially
-	intertype = int_none;
+	intertype = gametypes[gametype].intermission_type;
 
-	if (intermissiontypes[gametype] != int_none)
-		intertype = intermissiontypes[gametype];
-	else if (gametype == GT_COOP)
-		intertype = (G_IsSpecialStage(gamemap)) ? int_spec : int_coop;
-	else if (gametype == GT_TEAMMATCH)
-		intertype = int_teammatch;
-	else if (gametype == GT_MATCH
-	 || gametype == GT_TAG
-	 || gametype == GT_HIDEANDSEEK)
-		intertype = int_match;
-	else if (gametype == GT_RACE)
-		intertype = int_race;
-	else if (gametype == GT_COMPETITION)
-		intertype = int_comp;
-	else if (gametype == GT_CTF)
-		intertype = int_ctf;
+	if (intertype == int_coop && G_IsSpecialStage(gamemap))
+		intertype = int_spec;
 }
 
 //