diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 4d6679559814ff07b792b049183cb08fa26c5620..b16781504169a1efbc78f874d35296586b866c72 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -2360,7 +2360,12 @@ static void Command_Teamchange_f(void)
 	if (COM_Argc() <= 1)
 	{
 		if (G_GametypeHasTeams())
-			CONS_Printf(M_GetText("changeteam <team>: switch to a new team (%s)\n"), "team name or spectator");
+		{
+			if (G_GametypeHasSpectators())
+				CONS_Printf(M_GetText("changeteam <team>: switch to a new team (%s)\n"), "team name or spectator");
+			else
+				CONS_Printf(M_GetText("changeteam <team>: switch to a new team (%s)\n"), "team name");
+		}
 		else if (G_GametypeHasSpectators())
 			CONS_Printf(M_GetText("changeteam <team>: switch to a new team (%s)\n"), "spectator or playing");
 		else
@@ -2370,7 +2375,7 @@ static void Command_Teamchange_f(void)
 
 	if (G_GametypeHasTeams())
 	{
-		if (!strcasecmp(COM_Argv(1), "spectator") || !strcasecmp(COM_Argv(1), "0"))
+		if (G_GametypeHasSpectators() && (!strcasecmp(COM_Argv(1), "spectator") || !strcasecmp(COM_Argv(1), "0")))
 			NetPacket.packet.newteam = 0;
 		else
 		{
@@ -2378,7 +2383,7 @@ static void Command_Teamchange_f(void)
 			if (M_StringOnlyHasDigits(COM_Argv(1)))
 			{
 				newteam = atoi(COM_Argv(1));
-				if (newteam >= teamsingame)
+				if (newteam == TEAM_NONE || newteam >= teamsingame)
 					newteam = MAXTEAMS;
 			}
 			else
@@ -2407,7 +2412,12 @@ static void Command_Teamchange_f(void)
 	if (error)
 	{
 		if (G_GametypeHasTeams())
-			CONS_Printf(M_GetText("changeteam <team>: switch to a new team (%s)\n"), "team name or spectator");
+		{
+			if (G_GametypeHasSpectators())
+				CONS_Printf(M_GetText("changeteam <team>: switch to a new team (%s)\n"), "team name or spectator");
+			else
+				CONS_Printf(M_GetText("changeteam <team>: switch to a new team (%s)\n"), "team name");
+		}
 		else if (G_GametypeHasSpectators())
 			CONS_Printf(M_GetText("changeteam <team>: switch to a new team (%s)\n"), "spectator or playing");
 		return;
@@ -2466,9 +2476,14 @@ static void Command_Teamchange2_f(void)
 	if (COM_Argc() <= 1)
 	{
 		if (G_GametypeHasTeams())
-			CONS_Printf(M_GetText("changeteam <team>: switch to a new team (%s)\n"), "team name or spectator");
+		{
+			if (G_GametypeHasSpectators())
+				CONS_Printf(M_GetText("changeteam2 <team>: switch to a new team (%s)\n"), "team name or spectator");
+			else
+				CONS_Printf(M_GetText("changeteam2 <team>: switch to a new team (%s)\n"), "team name");
+		}
 		else if (G_GametypeHasSpectators())
-			CONS_Printf(M_GetText("changeteam <team>: switch to a new team (%s)\n"), "spectator or playing");
+			CONS_Printf(M_GetText("changeteam2 <team>: switch to a new team (%s)\n"), "spectator or playing");
 		else
 			CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n"));
 		return;
@@ -2476,7 +2491,7 @@ static void Command_Teamchange2_f(void)
 
 	if (G_GametypeHasTeams())
 	{
-		if (!strcasecmp(COM_Argv(1), "spectator") || !strcasecmp(COM_Argv(1), "0"))
+		if (G_GametypeHasSpectators() && (!strcasecmp(COM_Argv(1), "spectator") || !strcasecmp(COM_Argv(1), "0")))
 			NetPacket.packet.newteam = 0;
 		else
 		{
@@ -2484,7 +2499,7 @@ static void Command_Teamchange2_f(void)
 			if (M_StringOnlyHasDigits(COM_Argv(1)))
 			{
 				newteam = atoi(COM_Argv(1));
-				if (newteam >= teamsingame)
+				if (newteam == TEAM_NONE || newteam >= teamsingame)
 					newteam = MAXTEAMS;
 			}
 			else
@@ -2514,7 +2529,12 @@ static void Command_Teamchange2_f(void)
 	if (error)
 	{
 		if (G_GametypeHasTeams())
-			CONS_Printf(M_GetText("changeteam2 <team>: switch to a new team (%s)\n"), "team name or spectator");
+		{
+			if (G_GametypeHasSpectators())
+				CONS_Printf(M_GetText("changeteam2 <team>: switch to a new team (%s)\n"), "team name or spectator");
+			else
+				CONS_Printf(M_GetText("changeteam2 <team>: switch to a new team (%s)\n"), "team name");
+		}
 		else if (G_GametypeHasSpectators())
 			CONS_Printf(M_GetText("changeteam2 <team>: switch to a new team (%s)\n"), "spectator or playing");
 		return;
@@ -2581,7 +2601,12 @@ static void Command_ServerTeamChange_f(void)
 		if (G_TagGametype())
 			CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: switch player to a new team (%s)\n"), "it, notit, playing, or spectator");
 		else if (G_GametypeHasTeams())
-			CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: switch player to a new team (%s)\n"), "team name or spectator");
+		{
+			if (G_GametypeHasSpectators())
+				CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: switch player to a new team (%s)\n"), "team name or spectator");
+			else
+				CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: switch player to a new team (%s)\n"), "team name");
+		}
 		else if (G_GametypeHasSpectators())
 			CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: switch player to a new team (%s)\n"), "spectator or playing");
 		else
@@ -2604,7 +2629,7 @@ static void Command_ServerTeamChange_f(void)
 	}
 	else if (G_GametypeHasTeams())
 	{
-		if (!strcasecmp(COM_Argv(1), "spectator") || !strcasecmp(COM_Argv(1), "0"))
+		if (G_GametypeHasSpectators() && (!strcasecmp(COM_Argv(1), "spectator") || !strcasecmp(COM_Argv(1), "0")))
 			NetPacket.packet.newteam = 0;
 		else
 		{
@@ -2612,7 +2637,7 @@ static void Command_ServerTeamChange_f(void)
 			if (M_StringOnlyHasDigits(COM_Argv(1)))
 			{
 				newteam = atoi(COM_Argv(1));
-				if (newteam >= teamsingame)
+				if (newteam == TEAM_NONE || newteam >= teamsingame)
 					newteam = MAXTEAMS;
 			}
 			else
@@ -2643,7 +2668,12 @@ static void Command_ServerTeamChange_f(void)
 		if (G_TagGametype())
 			CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: switch player to a new team (%s)\n"), "it, notit, playing, or spectator");
 		else if (G_GametypeHasTeams())
-			CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: switch player to a new team (%s)\n"), "team name or spectator");
+		{
+			if (G_GametypeHasSpectators())
+				CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: switch player to a new team (%s)\n"), "team name or spectator");
+			else
+				CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: switch player to a new team (%s)\n"), "team name");
+		}
 		else if (G_GametypeHasSpectators())
 			CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: switch player to a new team (%s)\n"), "spectator or playing");
 		return;
diff --git a/src/deh_lua.c b/src/deh_lua.c
index f43eacb43d2da2ccec77ca2311928ed19e66083d..ea94c32a9d604336d72edb1389b52dc742e3ec3e 100644
--- a/src/deh_lua.c
+++ b/src/deh_lua.c
@@ -133,19 +133,16 @@ static inline int lib_freeslot(lua_State *L)
 		}
 		else if (fastcmp(type, "TEAM"))
 		{
-			UINT8 i;
-			for (i = 0; i < MAXTEAMS; i++)
-				if (!teamnames[i]) {
-					CONS_Printf("Team TEAM_%s allocated.\n",word);
-					teamnames[i] = Z_Malloc(strlen(word)+1, PU_STATIC, NULL);
-					strcpy(teamnames[i],word);
-					lua_pushinteger(L, i);
-					numteams++;
-					r++;
-					break;
-				}
-			if (i == MAXTEAMS)
+			if (numteams == MAXTEAMS)
 				CONS_Alert(CONS_WARNING, "Ran out of free team slots!\n");
+			UINT8 i = numteams;
+			CONS_Printf("Team TEAM_%s allocated.\n",word);
+			teamnames[i] = Z_Malloc(strlen(word)+1, PU_STATIC, NULL);
+			strcpy(teamnames[i],word);
+			lua_pushinteger(L, i);
+			numteams++;
+			r++;
+			break;
 		}
 		else if (fastcmp(type, "SPR2"))
 		{
@@ -578,10 +575,8 @@ static int ScanConstants(lua_State *L, boolean mathlib, const char *word)
 	}
 	else if (fastncmp("TEAM_",word,5)) {
 		p = word+5;
-		for (i = 0; i < MAXTEAMS; i++)
+		for (i = 0; i < numteams; i++)
 		{
-			if (!teamnames[i])
-				break;
 			if (fastcmp(p, teamnames[i])) {
 				CacheAndPushConstant(L, word, i);
 				return 1;
diff --git a/src/deh_soc.c b/src/deh_soc.c
index dee24b37cee9636b4ed328c93aa341d82b5d347b..61d3d558a870b8211cd0107da8b283183ebdaa3f 100644
--- a/src/deh_soc.c
+++ b/src/deh_soc.c
@@ -475,13 +475,12 @@ void readfreeslots(MYFILE *f)
 			}
 			else if (fastcmp(type, "TEAM"))
 			{
-				for (i = 0; i < MAXTEAMS; i++)
-					if (!teamnames[i]) {
-						teamnames[i] = Z_Malloc(strlen(word)+1, PU_STATIC, NULL);
-						strcpy(teamnames[i],word);
-						numteams++;
-						break;
-					}
+				if (numteams < MAXTEAMS)
+				{
+					teamnames[numteams] = Z_Malloc(strlen(word)+1, PU_STATIC, NULL);
+					strcpy(teamnames[numteams],word);
+					numteams++;
+				}
 			}
 			else if (fastcmp(type, "SPR2"))
 			{
@@ -1139,8 +1138,7 @@ void readsprite2(MYFILE *f, INT32 num)
 	Z_Free(s);
 }
 
-// copypasted from readPlayer :]
-void readgametype(MYFILE *f, char *gtname)
+void readgametype(MYFILE *f, INT32 num)
 {
 	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
 	char *word;
@@ -1148,25 +1146,20 @@ void readgametype(MYFILE *f, char *gtname)
 	char *tmp;
 	INT32 i, j;
 
-	INT16 newgtidx = 0;
-	UINT32 newgtrules = 0;
-	UINT32 newgttol = 0;
-	INT32 newgtpointlimit = 0;
-	INT32 newgttimelimit = 0;
-	UINT8 newgtleftcolor = 0;
-	UINT8 newgtrightcolor = 0;
-	INT16 newgtrankingstype = -1;
-	int newgtinttype = 0;
+	char *gtname = gametypes[num].name;
+	char gtconst[32];
 	char gtdescription[441];
-	char gtconst[MAXLINELEN];
-	UINT8 teamcount = 0;
-	UINT8 teamlist[MAXTEAMS];
 
-	// Empty strings.
-	gtdescription[0] = '\0';
-	gtconst[0] = '\0';
+	UINT8 newgtleftcolor, newgtrightcolor;
+	boolean has_desc_colors[2] = { false, false };
 
-	strcpy(gtdescription, "???");
+	UINT8 teamcount = gametypes[num].teams.num;
+	UINT8 teamlist[MAXTEAMS];
+	boolean has_teams = false;
+
+	memset(gtconst, 0, sizeof(gtconst));
+	memset(gtdescription, 0, sizeof(gtconst));
+	memcpy(teamlist, gametypes[num].teams.list, sizeof(teamlist[0]) * teamcount);
 
 	do
 	{
@@ -1225,8 +1218,8 @@ void readgametype(MYFILE *f, char *gtname)
 			if (word2)
 			{
 				if (!word2lwr)
-					word2lwr = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
-				strcpy(word2lwr, word2);
+					word2lwr = Z_Calloc(MAXLINELEN, PU_STATIC, NULL);
+				strlcpy(word2lwr, word2, MAXLINELEN);
 				strupr(word2);
 			}
 			else
@@ -1234,13 +1227,21 @@ void readgametype(MYFILE *f, char *gtname)
 
 			if (word2[strlen(word2)-1] == '\n')
 				word2[strlen(word2)-1] = '\0';
+			if (word2lwr[strlen(word2lwr)-1] == '\n')
+				word2lwr[strlen(word2lwr)-1] = '\0';
 			i = atoi(word2);
 
+			// Name
+			if (fastcmp(word, "NAME"))
+			{
+				Z_Free(gametypes[num].name);
+				gametypes[num].name = Z_StrDup(word2lwr);
+			}
 			// Game type rules
-			if (fastcmp(word, "RULES"))
+			else if (fastcmp(word, "RULES"))
 			{
 				// GTR_
-				newgtrules = (UINT32)get_number(word2);
+				gametypes[num].rules = (UINT32)get_number(word2);
 			}
 			// Identifier
 			else if (fastcmp(word, "IDENTIFIER"))
@@ -1250,33 +1251,60 @@ void readgametype(MYFILE *f, char *gtname)
 			}
 			// Point and time limits
 			else if (fastcmp(word, "DEFAULTPOINTLIMIT"))
-				newgtpointlimit = (INT32)i;
+				gametypes[num].pointlimit = (INT32)i;
 			else if (fastcmp(word, "DEFAULTTIMELIMIT"))
-				newgttimelimit = (INT32)i;
+				gametypes[num].timelimit = (INT32)i;
 			// Level platter
 			else if (fastcmp(word, "HEADERCOLOR") || fastcmp(word, "HEADERCOLOUR"))
-				newgtleftcolor = newgtrightcolor = (UINT8)get_number(word2);
+			{
+				INT32 color = (INT32)i;
+				if (color >= 0 && color <= 255)
+				{
+					newgtleftcolor = newgtrightcolor = (UINT8)color;
+					has_desc_colors[0] = has_desc_colors[1] = true;
+				}
+				else
+					deh_warning("readgametype %d: Level platter header color %d out of range (0 - 255)", num, i);
+			}
 			else if (fastcmp(word, "HEADERLEFTCOLOR") || fastcmp(word, "HEADERLEFTCOLOUR"))
-				newgtleftcolor = (UINT8)get_number(word2);
+			{
+				INT32 color = (INT32)i;
+				if (color >= 0 && color <= 255)
+				{
+					newgtleftcolor = (UINT8)color;
+					has_desc_colors[0] = true;
+				}
+				else
+					deh_warning("readgametype %d: Level platter header left color %d out of range (0 - 255)", num, i);
+			}
 			else if (fastcmp(word, "HEADERRIGHTCOLOR") || fastcmp(word, "HEADERRIGHTCOLOUR"))
-				newgtrightcolor = (UINT8)get_number(word2);
+			{
+				INT32 color = (INT32)i;
+				if (color >= 0 && color < 255)
+				{
+					newgtrightcolor = (UINT8)color;
+					has_desc_colors[1] = true;
+				}
+				else
+					deh_warning("readgametype %d: Level platter header right color %d out of range (0 - 255)", num, i);
+			}
 			// Rankings type
 			else if (fastcmp(word, "RANKINGTYPE"))
 			{
 				// Case insensitive
-				newgtrankingstype = (int)get_number(word2);
+				gametypes[num].rankings_type = (int)get_number(word2);
 			}
 			// Intermission type
 			else if (fastcmp(word, "INTERMISSIONTYPE"))
 			{
 				// Case sensitive
-				newgtinttype = (int)get_number(word2lwr);
+				gametypes[num].intermission_type = (int)get_number(word2lwr);
 			}
 			// Type of level
 			else if (fastcmp(word, "TYPEOFLEVEL"))
 			{
 				if (i) // it's just a number
-					newgttol = (UINT32)i;
+					gametypes[num].typeoflevel = (UINT32)i;
 				else
 				{
 					UINT32 tol = 0;
@@ -1286,10 +1314,10 @@ void readgametype(MYFILE *f, char *gtname)
 							if (fasticmp(tmp, TYPEOFLEVEL[i].name))
 								break;
 						if (!TYPEOFLEVEL[i].name)
-							deh_warning("readgametype %s: unknown typeoflevel flag %s\n", gtname, tmp);
+							deh_warning("readgametype %d: unknown typeoflevel flag %s\n", num, tmp);
 						tol |= TYPEOFLEVEL[i].flag;
 					} while((tmp = strtok(NULL,",")) != NULL);
-					newgttol = tol;
+					gametypes[num].typeoflevel = tol;
 				}
 			}
 			// Teams
@@ -1299,26 +1327,20 @@ void readgametype(MYFILE *f, char *gtname)
 				do {
 					if (teamcount == MAXTEAMS)
 					{
-						deh_warning("readgametype %s: too many teams\n", gtname);
+						deh_warning("readgametype %d: too many teams\n", num);
 						break;
 					}
-					UINT8 team_id = TEAM_NONE;
-					for (i = 1; i < MAXTEAMS; i++)
-					{
-						if (!teamnames[i])
-							break;
-						if (fasticmp(tmp, teamnames[i]))
-						{
-							team_id = i;
-							break;
-						}
-					}
-					if (team_id == TEAM_NONE)
-						deh_warning("readgametype %s: unknown team %s\n", gtname, tmp);
-					else
+					char *tmp2 = Z_StrDup(tmp);
+					strupr(tmp2);
+					UINT8 team_id = get_team(tmp2);
+					if (team_id != TEAM_NONE)
 					{
 						teamlist[teamcount++] = team_id;
+						has_teams = true;
 					}
+					else
+						deh_warning("readgametype %d: unknown team %s\n", num, tmp);
+					Z_Free(tmp2);
 				} while((tmp = strtok(NULL,",")) != NULL);
 			}
 			// This SOC probably provided gametype rules as words, instead of using the RULES keyword.
@@ -1329,12 +1351,14 @@ void readgametype(MYFILE *f, char *gtname)
 				for (j = 0; GAMETYPERULE_LIST[j]; j++)
 					if (fastcmp(word, GAMETYPERULE_LIST[j])) {
 						wordgt |= (1<<j);
-						if (i || word2[0] == 'T' || word2[0] == 'Y')
-							newgtrules |= wordgt;
+						if (word2[0] == 'T' || word2[0] == 'Y')
+							gametypes[num].rules |= wordgt;
+						else if (word2[0] == 'F' || word2[0] == 'N')
+							gametypes[num].rules &= ~wordgt;
 						break;
 					}
 				if (!wordgt)
-					deh_warning("readgametype %s: unknown word '%s'", gtname, word);
+					deh_warning("readgametype %d: unknown word '%s'", num, word);
 			}
 		}
 	} while (!myfeof(f)); // finish when the line is empty
@@ -1344,43 +1368,161 @@ void readgametype(MYFILE *f, char *gtname)
 	if (word2lwr)
 		Z_Free(word2lwr);
 
-	// Ran out of gametype slots
-	if (gametypecount == NUMGAMETYPEFREESLOTS)
+	if (gtdescription[0])
+		G_SetGametypeDescription(num, gtdescription);
+	if (has_desc_colors[0])
+		G_SetGametypeDescriptionLeftColor(num, newgtleftcolor);
+	if (has_desc_colors[1])
+		G_SetGametypeDescriptionRightColor(num, newgtrightcolor);
+
+	// Copy the teams
+	if (has_teams)
 	{
-		CONS_Alert(CONS_WARNING, "Ran out of free gametype slots!\n");
-		return;
+		gametypes[num].teams.num = teamcount;
+		if (teamcount)
+			memcpy(gametypes[num].teams.list, teamlist, sizeof(teamlist[0]) * teamcount);
 	}
 
-	// Add the new gametype
-	newgtidx = G_AddGametype(newgtrules);
-	G_AddGametypeTOL(newgtidx, newgttol);
-	G_SetGametypeDescription(newgtidx, gtdescription, newgtleftcolor, newgtrightcolor);
+	// Write the constant name.
+	if (gametypes[num].constant_name == NULL)
+	{
+		if (gtconst[0] == '\0')
+			G_AddGametypeConstant(num, gtname);
+		else
+			G_AddGametypeConstant(num, gtconst);
+	}
+	else if (gtconst[0] != '\0')
+		G_AddGametypeConstant(num, gtconst);
 
-	// Not covered by G_AddGametype alone.
-	if (newgtrankingstype == -1)
-		newgtrankingstype = newgtidx;
-	gametypes[newgtidx].rankings_type = newgtrankingstype;
-	gametypes[newgtidx].intermission_type = newgtinttype;
-	gametypes[newgtidx].pointlimit = newgtpointlimit;
-	gametypes[newgtidx].timelimit = newgttimelimit;
+	// Update gametype_cons_t accordingly.
+	G_UpdateGametypeSelections();
+}
 
-	// Copy the teams
-	gametypes[newgtidx].teams.num = teamcount;
-	if (teamcount)
-		memcpy(gametypes[newgtidx].teams.list, teamlist, sizeof(teamlist[0]) * teamcount);
+void readteam(MYFILE *f, INT32 num)
+{
+	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+	char *word;
+	char *word2, *word2lwr = NULL;
+	INT32 i;
 
-	// Write the new gametype name.
-	gametypes[newgtidx].name = Z_StrDup(gtname);
+	team_t *team = &teams[num];
 
-	// Write the constant name.
-	if (gtconst[0] == '\0')
-		strncpy(gtconst, gtname, MAXLINELEN);
-	G_AddGametypeConstant(newgtidx, gtconst);
+	do
+	{
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
 
-	// Update gametype_cons_t accordingly.
-	G_UpdateGametypeSelections();
+			word = strtok(s, " ");
+			if (word)
+				strupr(word);
+			else
+				break;
+
+			word2 = strtok(NULL, " = ");
+			if (word2)
+			{
+				if (!word2lwr)
+					word2lwr = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+				strcpy(word2lwr, word2);
+				strupr(word2);
+			}
+			else
+				break;
+
+			if (word2[strlen(word2)-1] == '\n')
+				word2[strlen(word2)-1] = '\0';
+			i = atoi(word2);
 
-	CONS_Printf("Added gametype %s\n", gametypes[newgtidx].name);
+			if (fastcmp(word, "NAME"))
+			{
+				Z_Free(team->name);
+				team->name = Z_StrDup(word2lwr);
+			}
+			else if (fastcmp(word, "FLAGNAME"))
+			{
+				Z_Free(team->flag_name);
+				team->flag_name = Z_StrDup(word2lwr);
+			}
+			else if (fastcmp(word, "FLAG"))
+			{
+				team->flag = (UINT16)get_number(word2);
+			}
+			else if (fastcmp(word, "FLAGTYPE"))
+			{
+				if (i == 0 && word2[0] != '0') // If word2 isn't a number
+					i = get_mobjtype(word2); // find a thing by name
+				if (i < NUMMOBJTYPES && i > 0)
+					team->flag_mobj_type = i;
+				else
+				{
+					deh_warning("readteam %d: Thing %d out of range (1 - %d)", num, i, NUMMOBJTYPES-1);
+				}
+			}
+			else if (fastcmp(word, "COLOR"))
+			{
+				if (i == 0 && word2[0] != '0') // If word2 isn't a number
+					i = get_skincolor(word2); // find a skincolor by name
+				if (i && i < numskincolors)
+					team->color = i;
+				else
+				{
+					deh_warning("readteam %d: Color %d out of range (1 - %d)", num, i, numskincolors-1);
+				}
+			}
+			else if (fastcmp(word, "WEAPONCOLOR"))
+			{
+				if (i == 0 && word2[0] != '0') // If word2 isn't a number
+					i = get_skincolor(word2); // find a skincolor by name
+				if (i && i < numskincolors)
+					team->weapon_color = i;
+				else
+				{
+					deh_warning("readteam %d: Weapon color %d out of range (1 - %d)", num, i, numskincolors-1);
+				}
+			}
+			else if (fastcmp(word, "MISSILECOLOR"))
+			{
+				if (i == 0 && word2[0] != '0') // If word2 isn't a number
+					i = get_skincolor(word2); // find a skincolor by name
+				if (i && i < numskincolors)
+					team->missile_color = i;
+				else
+				{
+					deh_warning("readteam %d: Missile color %d out of range (1 - %d)", num, i, numskincolors-1);
+				}
+			}
+			else if (fastcmp(word, "ICON"))
+			{
+				G_SetTeamIcon(num, TEAM_ICON, word2lwr);
+			}
+			else if (fastcmp(word, "FLAGICON"))
+			{
+				G_SetTeamIcon(num, TEAM_ICON_FLAG, word2lwr);
+			}
+			else if (fastcmp(word, "GOTFLAGICON"))
+			{
+				G_SetTeamIcon(num, TEAM_ICON_GOT_FLAG, word2lwr);
+			}
+			else if (fastcmp(word, "MISSINGFLAGICON"))
+			{
+				G_SetTeamIcon(num, TEAM_ICON_MISSING_FLAG, word2lwr);
+			}
+			else
+			{
+				deh_warning("readteam %d: unknown word '%s'", num, word);
+			}
+		}
+	} while (!myfeof(f)); // finish when the line is empty
+
+	// Free strings.
+	Z_Free(s);
+	if (word2lwr)
+		Z_Free(word2lwr);
+
+	ST_LoadTeamIcons();
+	G_UpdateTeamSelection();
 }
 
 void readlevelheader(MYFILE *f, INT32 num)
@@ -4195,6 +4337,38 @@ skincolornum_t get_skincolor(const char *word)
 	return SKINCOLOR_GREEN;
 }
 
+INT16 get_gametype(const char *word)
+{
+	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 < gametypecount; i++)
+	{
+		if (fastcmp(word, gametypes[i].constant_name + 3))
+			return i;
+	}
+	deh_warning("Couldn't find gametype named 'GT_%s'",word);
+	return GT_COOP;
+}
+
+UINT8 get_team(const char *word)
+{
+	UINT8 i;
+	if (*word >= '0' && *word <= '9')
+		return atoi(word);
+	if (fastncmp("TEAM_",word,5))
+		word += 5; // take off the TEAM_
+	for (i = 0; i < numteams; i++)
+	{
+		if (fastcmp(word, teamnames[i]))
+			return i;
+	}
+	deh_warning("Couldn't find team named 'TEAM_%s'",word);
+	return TEAM_NONE;
+}
+
 spritenum_t get_sprite(const char *word)
 { // Returns the value of SPR_ enumerations
 	spritenum_t i;
diff --git a/src/deh_soc.h b/src/deh_soc.h
index 0cab545f680d6ec86d6e4707b53c2a16291439ba..1ac2199af1b2d3fa3610a5e22e02c16a73ea05e4 100644
--- a/src/deh_soc.h
+++ b/src/deh_soc.h
@@ -54,9 +54,9 @@ playersprite_t get_sprite2(const char *word);
 sfxenum_t get_sfx(const char *word);
 hudnum_t get_huditem(const char *word);
 menutype_t get_menutype(const char *word);
-//INT16 get_gametype(const char *word);
-//powertype_t get_power(const char *word);
 skincolornum_t get_skincolor(const char *word);
+INT16 get_gametype(const char *word);
+UINT8 get_team(const char *word);
 
 void readwipes(MYFILE *f);
 void readmaincfg(MYFILE *f);
@@ -71,7 +71,8 @@ void readmenu(MYFILE *f, INT32 num);
 void readtextprompt(MYFILE *f, INT32 num);
 void readcutscene(MYFILE *f, INT32 num);
 void readlevelheader(MYFILE *f, INT32 num);
-void readgametype(MYFILE *f, char *gtname);
+void readgametype(MYFILE *f, INT32 num);
+void readteam(MYFILE *f, INT32 num);
 void readsprite2(MYFILE *f, INT32 num);
 void readspriteinfo(MYFILE *f, INT32 num, boolean sprite2);
 #ifdef HWRENDER
diff --git a/src/dehacked.c b/src/dehacked.c
index fd2a701715e17ce7dab0b6c829147113844972fe..2c00ff133067899d86599f3edffcdbb152824d95 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -172,9 +172,9 @@ static void ignorelines(MYFILE *f)
 static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
 {
 	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
-	char textline[MAXLINELEN];
 	char *word;
 	char *word2;
+	char word2lwr[MAXLINELEN];
 	INT32 i;
 
 	if (!deh_loaded)
@@ -189,7 +189,6 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
 	while (!myfeof(f))
 	{
 		myfgets(s, MAXLINELEN, f);
-		memcpy(textline, s, MAXLINELEN);
 		if (s[0] == '\n' || s[0] == '#')
 			continue;
 
@@ -216,10 +215,14 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
 				continue;
 			}
 			word2 = strtok(NULL, " ");
+			word2lwr[0] = '\0';
 			if (word2) {
+				strcpy(word2lwr, word2);
 				strupr(word2);
 				if (word2[strlen(word2) - 1] == '\n')
 					word2[strlen(word2) - 1] = '\0';
+				if (word2lwr[strlen(word2lwr) - 1] == '\n')
+					word2lwr[strlen(word2lwr) - 1] = '\0';
 				i = atoi(word2);
 			}
 			else
@@ -381,32 +384,60 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
 				}
 				else if (fastcmp(word, "GAMETYPE"))
 				{
-					// Get the gametype name from textline
-					// instead of word2, so that gametype names
-					// aren't allcaps
-					INT32 c;
-					for (c = 0; c < MAXLINELEN; c++)
+					char *gtname = NULL;
+					if (word2lwr[0])
 					{
-						if (textline[c] == '\0')
-							break;
-						if (textline[c] == ' ')
+						gtname = word2lwr;
+
+						for (size_t j = 0; j < strlen(gtname); j++)
+						{
+							if (gtname[j] == '\0')
+								break;
+							if (gtname[j] < 32 || gtname[j] == '\n')
+							{
+								gtname[j] = '\0';
+								break;
+							}
+						}
+					}
+
+					if (!gtname || !strlen(gtname))
+					{
+						deh_warning("Invalid gametype name");
+						ignorelines(f);
+					}
+					else
+					{
+						INT32 gametype_id = G_GetGametypeByName(gtname);
+						if (gametype_id == -1)
 						{
-							char *gtname = (textline+c+1);
-							if (gtname)
+							if (!strncmp(gtname, "GT_", 3))
+								gametype_id = get_gametype(gtname + 3);
+							else if (gametypecount != NUMGAMETYPEFREESLOTS)
 							{
-								// remove funny characters
-								INT32 j;
-								for (j = 0; j < (MAXLINELEN - c); j++)
-								{
-									if (gtname[j] == '\0')
-										break;
-									if (gtname[j] < 32)
-										gtname[j] = '\0';
-								}
-								readgametype(f, gtname);
+								gametype_id = G_AddGametype();
+								CONS_Printf("Added gametype %s\n", gtname);
 							}
-							break;
+							else
+								deh_warning("Ran out of free gametype slots");
 						}
+
+						if (gametype_id != -1)
+							readgametype(f, gametype_id);
+						else
+							ignorelines(f);
+					}
+				}
+				else if (fastcmp(word, "TEAM"))
+				{
+					if (i == 0 && word2[0] != '0') // If word2 isn't a number
+						i = get_team(word2); // find a team by name
+					if (i >= 0 && i < MAXTEAMS)
+						readteam(f, i);
+					else
+					{
+						deh_warning("Team %d out of range (0 - %d)", i, MAXTEAMS);
+						ignorelines(f);
 					}
 				}
 				else if (fastcmp(word, "CUTSCENE"))
diff --git a/src/g_game.c b/src/g_game.c
index 584d84972ceb60b986013250fba1ab6c2a30ddef..429dfbc061652b9b0deee8a7d39459e5cbaf4807 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -3597,11 +3597,23 @@ void G_UpdateTeamSelection(void)
 		i++;
 	}
 
+	// If no selections were added, somehow, we add at least two fallbacks.
+	if (i == 0)
+	{
+		dummyteam_cons_t[i].value = 0;
+		dummyteam_cons_t[i].strvalue = "Spectator";
+		i++;
+
+		dummyteam_cons_t[i].value = 1;
+		dummyteam_cons_t[i].strvalue = "Playing";
+		i++;
+	}
+
 	dummyteam_cons_t[i].value = 0;
 	dummyteam_cons_t[i].strvalue = NULL;
 
 	cv_dummyteam.defaultvalue = dummyteam_cons_t[0].strvalue;
-	cv_dummyteam.value = 0;
+	cv_dummyteam.value = dummyteam_cons_t[0].value;
 	cv_dummyteam.string = cv_dummyteam.defaultvalue;
 }
 
@@ -3624,15 +3636,17 @@ void G_SetGametype(INT16 gtype)
 //
 // Adds a gametype. Returns the new gametype number.
 //
-INT16 G_AddGametype(UINT32 rules)
+INT16 G_AddGametype(void)
 {
 	INT16 newgtype = gametypecount;
 	gametypecount++;
 
 	gametypes[newgtype].name = Z_StrDup("???");
-	gametypes[newgtype].rules = rules;
+	gametypes[newgtype].rules = 0;
 
-	G_UpdateGametypeSelections();
+	G_SetGametypeDescription(newgtype, "???");
+	G_SetGametypeDescriptionLeftColor(newgtype, 54);
+	G_SetGametypeDescriptionRightColor(newgtype, 54);
 
 	return newgtype;
 }
@@ -3670,20 +3684,32 @@ void G_UpdateGametypeSelections(void)
 	}
 	gametype_cons_t[NUMGAMETYPES].value = 0;
 	gametype_cons_t[NUMGAMETYPES].strvalue = NULL;
+
+	cv_newgametype.defaultvalue = gametype_cons_t[0].strvalue;
+
+	if (cv_newgametype.string && G_GetGametypeByName(cv_newgametype.string) == -1)
+	{
+		cv_newgametype.value = 0;
+		cv_newgametype.string = gametype_cons_t[0].strvalue;
+	}
 }
 
-//
-// G_SetGametypeDescription
-//
-// Set a description for the specified gametype.
-// (Level platter)
-//
-void G_SetGametypeDescription(INT16 gtype, char *descriptiontext, UINT8 leftcolor, UINT8 rightcolor)
+void G_SetGametypeDescription(INT16 gtype, const char *descriptiontext)
+{
+	if (descriptiontext)
+		strlcpy(gametypedesc[gtype].notes, descriptiontext, sizeof gametypedesc[gtype].notes);
+	else
+		memset(gametypedesc[gtype].notes, 0, sizeof gametypedesc[gtype].notes);
+}
+
+void G_SetGametypeDescriptionLeftColor(INT16 gtype, UINT8 color)
+{
+	gametypedesc[gtype].col[0] = color;
+}
+
+void G_SetGametypeDescriptionRightColor(INT16 gtype, UINT8 color)
 {
-	if (descriptiontext != NULL)
-		strncpy(gametypedesc[gtype].notes, descriptiontext, 441);
-	gametypedesc[gtype].col[0] = leftcolor;
-	gametypedesc[gtype].col[1] = rightcolor;
+	gametypedesc[gtype].col[1] = color;
 }
 
 tolinfo_t TYPEOFLEVEL[NUMTOLNAMES] = {
@@ -3732,16 +3758,6 @@ void G_AddTOL(UINT32 newtol, const char *tolname)
 	TYPEOFLEVEL[i].flag = newtol;
 }
 
-//
-// G_AddGametypeTOL
-//
-// Assigns a type of level to a gametype.
-//
-void G_AddGametypeTOL(INT16 gtype, UINT32 newtol)
-{
-	gametypes[gtype].typeoflevel = newtol;
-}
-
 //
 // G_GetGametypeByName
 //
diff --git a/src/g_game.h b/src/g_game.h
index 9bde7a9ee7bd52399e83c0ebc65a580f480b7c2f..035b4541e310e6d9ae11b49bef0f29aa1c0293e2 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -193,12 +193,14 @@ void G_SaveGameOver(UINT32 slot, boolean modifylives);
 void G_SetGametype(INT16 gametype);
 void G_InitGametypes(void);
 void G_UpdateTeamSelection(void);
-INT16 G_AddGametype(UINT32 rules);
+INT16 G_AddGametype(void);
 void G_AddGametypeConstant(INT16 gtype, const char *newgtconst);
 void G_UpdateGametypeSelections(void);
 void G_AddTOL(UINT32 newtol, const char *tolname);
-void G_AddGametypeTOL(INT16 gtype, UINT32 newtol);
-void G_SetGametypeDescription(INT16 gtype, char *descriptiontext, UINT8 leftcolor, UINT8 rightcolor);
+
+void G_SetGametypeDescription(INT16 gtype, const char *descriptiontext);
+void G_SetGametypeDescriptionLeftColor(INT16 gtype, UINT8 color);
+void G_SetGametypeDescriptionRightColor(INT16 gtype, UINT8 color);
 
 INT32 G_GetGametypeByName(const char *gametypestr);
 boolean G_IsSpecialStage(INT32 mapnum);
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index c8e342975f5b5f3c6c0b846eb8905e2d0ff925bc..dbd7c8e4ffda4d654228dbd5138727fe692ac74b 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -3411,15 +3411,14 @@ static int lib_gAddGametype(lua_State *L)
 
 	char *gtname = NULL;
 	char *gtconst = NULL;
-	const char *gtdescription = NULL;
-	INT16 newgtidx = 0;
+	char *gtdescription = NULL;
 	UINT32 newgtrules = 0;
 	UINT32 newgttol = 0;
 	INT32 newgtpointlimit = 0;
 	INT32 newgttimelimit = 0;
-	UINT8 newgtleftcolor = 0;
-	UINT8 newgtrightcolor = 0;
-	INT16 newgtrankingstype = -1;
+	UINT8 newgtleftcolor = 54;
+	UINT8 newgtrightcolor = 54;
+	INT16 newgtrankingstype = RANKINGS_DEFAULT;
 	int newgtinttype = 0;
 	UINT8 teamcount = 0;
 	UINT8 teamlist[MAXTEAMS];
@@ -3540,40 +3539,52 @@ static int lib_gAddGametype(lua_State *L)
 	// Set defaults
 	if (gtname == NULL)
 		gtname = Z_StrDup("Unnamed gametype");
-	if (gtdescription == NULL)
-		gtdescription = Z_StrDup("???");
+
+	if (G_GetGametypeByName(gtname) != -1)
+	{
+		luaL_error(L, "gametype %s already exists", gtname);
+		Z_Free(gtname);
+		Z_Free(gtconst);
+		Z_Free(gtdescription);
+		return 0;
+	}
 
 	// Add the new gametype
-	newgtidx = G_AddGametype(newgtrules);
-	G_AddGametypeTOL(newgtidx, newgttol);
-	G_SetGametypeDescription(newgtidx, gtdescription, newgtleftcolor, newgtrightcolor);
-
-	// Not covered by G_AddGametype alone.
-	if (newgtrankingstype == -1)
-		newgtrankingstype = newgtidx;
-	gametypes[newgtidx].rankings_type = newgtrankingstype;
-	gametypes[newgtidx].intermission_type = newgtinttype;
-	gametypes[newgtidx].pointlimit = newgtpointlimit;
-	gametypes[newgtidx].timelimit = newgttimelimit;
+	INT16 gtype = G_AddGametype();
+
+	gametype_t *gt = &gametypes[gtype];
+
+	gt->name = gtname;
+
+	if (gtconst)
+		G_AddGametypeConstant(gtype, gtconst);
+	else
+		G_AddGametypeConstant(gtype, gtname);
+
+	if (gtdescription)
+		G_SetGametypeDescription(gtype, gtdescription);
+	G_SetGametypeDescriptionLeftColor(gtype, newgtleftcolor);
+	G_SetGametypeDescriptionRightColor(gtype, newgtrightcolor);
+
+	gt->rules = newgtrules;
+	gt->typeoflevel = newgttol;
+	gt->rankings_type = newgtrankingstype;
+	gt->intermission_type = newgtinttype;
+	gt->pointlimit = newgtpointlimit;
+	gt->timelimit = newgttimelimit;
 
 	// Copy the teams
-	gametypes[newgtidx].teams.num = teamcount;
+	gt->teams.num = teamcount;
 	if (teamcount)
-		memcpy(gametypes[newgtidx].teams.list, teamlist, sizeof(teamlist[0]) * teamcount);
+		memcpy(gt->teams.list, teamlist, sizeof(teamlist[0]) * teamcount);
 
-	// Write the new gametype name.
-	gametypes[newgtidx].name = gtname;
+	G_UpdateGametypeSelections();
 
-	// Write the constant name.
-	if (gtconst == NULL)
-		gtconst = gtname;
-	G_AddGametypeConstant(newgtidx, gtconst);
+	CONS_Printf("Added gametype %s\n", gt->name);
 
-	// Update gametype_cons_t accordingly.
-	G_UpdateGametypeSelections();
+	Z_Free(gtconst);
+	Z_Free(gtdescription);
 
-	// done
-	CONS_Printf("Added gametype %s\n", gametypes[newgtidx].name);
 	return 0;
 }
 
diff --git a/src/p_mobj.c b/src/p_mobj.c
index b755f64825a931cac8ba56ef419598b2838e672e..5532bb34b89c470507e919ae26627517dad1f84b 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -11573,7 +11573,12 @@ void P_SpawnPlayer(INT32 playernum)
 	{
 		// Fix stupid non spectator spectators.
 		if (!p->spectator && !p->ctfteam)
-			p->spectator = true;
+		{
+			if (G_GametypeHasSpectators())
+				p->spectator = true;
+			else
+				p->ctfteam = 1;
+		}
 
 		// Fix team colors.
 		// This code isn't being done right somewhere else. Oh well.