diff --git a/src/deh_soc.c b/src/deh_soc.c
index 07100e7036b04981439351188819b3327ccc1d0d..d37ff11cfc11d6878e47d266a6bda7a38a007046 100644
--- a/src/deh_soc.c
+++ b/src/deh_soc.c
@@ -3655,19 +3655,19 @@ void readmaincfg(MYFILE *f)
 			}
 			else if (fastcmp(word, "REDTEAM"))
 			{
-				skincolor_redteam = (UINT16)get_number(word2);
+				teams[TEAM_RED].color = (UINT16)get_number(word2);
 			}
 			else if (fastcmp(word, "BLUETEAM"))
 			{
-				skincolor_blueteam = (UINT16)get_number(word2);
+				teams[TEAM_BLUE].color = (UINT16)get_number(word2);
 			}
 			else if (fastcmp(word, "REDRING"))
 			{
-				skincolor_redring = (UINT16)get_number(word2);
+				teams[TEAM_RED].missile_color = (UINT16)get_number(word2);
 			}
 			else if (fastcmp(word, "BLUERING"))
 			{
-				skincolor_bluering = (UINT16)get_number(word2);
+				teams[TEAM_BLUE].missile_color = (UINT16)get_number(word2);
 			}
 			else if (fastcmp(word, "INVULNTICS"))
 			{
diff --git a/src/doomstat.h b/src/doomstat.h
index 7dbe3e7780c80de346605101352545a5218fd00b..3772456dfdff33f64877f7c65c31fd689155dc8b 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -154,9 +154,6 @@ extern INT32 tutorialanalog; // store cv_analog[0] user value
 
 extern boolean looptitle;
 
-// CTF colors.
-extern UINT16 skincolor_redteam, skincolor_blueteam, skincolor_redring, skincolor_bluering;
-
 extern tic_t countdowntimer;
 extern boolean countdowntimeup;
 extern boolean exitfadestarted;
@@ -254,10 +251,11 @@ extern UINT32 ssspheres; //  Total # of spheres in a level
 
 // Fun extra stuff
 extern INT16 lastmap; // Last level you were at (returning from special stages).
-extern mobj_t *redflag, *blueflag; // Pointers to physical flags
-extern mapthing_t *rflagpoint, *bflagpoint; // Pointers to the flag spawn locations
-#define GF_REDFLAG 1
-#define GF_BLUEFLAG 2
+
+enum {
+	GF_REDFLAG = 1,
+	GF_BLUEFLAG = 2
+};
 
 // A single point in space.
 typedef struct
@@ -389,6 +387,27 @@ typedef struct
 
 extern mapheader_t* mapheaderinfo[NUMMAPS];
 
+enum {
+	TEAM_NONE,
+	TEAM_RED,
+	TEAM_BLUE,
+	MAXTEAMS = 4
+};
+
+typedef struct
+{
+	char *name;
+	char *flag_name;
+	UINT8 flag;
+	UINT32 flag_mobj_type;
+	UINT16 color;
+	UINT16 weapon_color;
+	UINT16 missile_color;
+} team_t;
+
+extern team_t teams[MAXTEAMS];
+extern UINT8 numteams;
+
 #define NUMGAMETYPEFREESLOTS 128
 
 // Gametypes
@@ -531,8 +550,10 @@ extern UINT32 tokenlist; ///< List of tokens collected
 extern boolean gottoken; ///< Did you get a token? Used for end of act
 extern INT32 tokenbits; ///< Used for setting token bits
 extern INT32 sstimer; ///< Time allotted in the special stage
-extern UINT32 bluescore; ///< Blue Team Scores
-extern UINT32 redscore;  ///< Red Team Scores
+extern UINT8 teamsingame; ///< Current teams in game
+extern UINT32 teamscores[MAXTEAMS]; ///< Team scores
+extern mobj_t *flagmobjs[MAXTEAMS]; // Pointers to physical flags
+extern mapthing_t *flagpoints[MAXTEAMS]; // Pointers to the flag spawn locations
 
 // Eliminates unnecessary searching.
 extern boolean CheckForBustableBlocks;
diff --git a/src/g_game.c b/src/g_game.c
index d870208c3822d3e6423a03256ec9ebb271cb80a5..5e3f9ec9409b89602c58cd1b2e7b69741a303647 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -146,11 +146,6 @@ INT32 tutorialanalog = 0; // store cv_analog[0] user value
 
 boolean looptitle = false;
 
-UINT16 skincolor_redteam = SKINCOLOR_RED;
-UINT16 skincolor_blueteam = SKINCOLOR_BLUE;
-UINT16 skincolor_redring = SKINCOLOR_SALMON;
-UINT16 skincolor_bluering = SKINCOLOR_CORNFLOWER;
-
 tic_t countdowntimer = 0;
 boolean countdowntimeup = false;
 boolean exitfadestarted = false;
@@ -163,11 +158,9 @@ UINT8 skipstats;
 INT16 nextgametype = -1;
 
 // Pointers to each CTF flag
-mobj_t *redflag;
-mobj_t *blueflag;
+mobj_t *flagmobjs[MAXTEAMS];
 // Pointers to CTF spawn location
-mapthing_t *rflagpoint;
-mapthing_t *bflagpoint;
+mapthing_t *flagpoints[MAXTEAMS];
 
 struct quake quake;
 
@@ -190,7 +183,10 @@ INT32 tokenbits; // Used for setting token bits
 // Old Special Stage
 INT32 sstimer; // Time allotted in the special stage
 
-UINT32 bluescore, redscore; // CTF and Team Match team scores
+UINT8 numteams;
+UINT8 teamsingame;
+
+UINT32 teamscores[MAXTEAMS]; // CTF and Team Match team scores
 
 // ring count... for PERFECT!
 INT32 nummaprings = 0;
@@ -2869,7 +2865,7 @@ mapthing_t *G_FindCTFStart(INT32 playernum)
 		return NULL;
 	}
 
-	if ((!players[playernum].ctfteam && numredctfstarts && (!numbluectfstarts || P_RandomChance(FRACUNIT/2))) || players[playernum].ctfteam == 1) //red
+	if ((!players[playernum].ctfteam && numredctfstarts && (!numbluectfstarts || P_RandomChance(FRACUNIT/2))) || players[playernum].ctfteam == TEAM_RED) //red
 	{
 		if (!numredctfstarts)
 		{
@@ -2889,7 +2885,7 @@ mapthing_t *G_FindCTFStart(INT32 playernum)
 			CONS_Alert(CONS_WARNING, M_GetText("Could not spawn at any Red Team starts!\n"));
 		return NULL;
 	}
-	else if (!players[playernum].ctfteam || players[playernum].ctfteam == 2) //blue
+	else if (!players[playernum].ctfteam || players[playernum].ctfteam == TEAM_BLUE) //blue
 	{
 		if (!numbluectfstarts)
 		{
@@ -3499,6 +3495,44 @@ gametype_t gametypes[NUMGAMETYPES] = {
 	},
 };
 
+team_t teams[MAXTEAMS] = {
+	// TEAM_NONE
+	{ },
+	// TEAM_RED
+	{
+		.color = SKINCOLOR_RED,
+		.weapon_color = SKINCOLOR_RED,
+		.missile_color = SKINCOLOR_SALMON,
+		.flag = GF_REDFLAG,
+		.flag_mobj_type = MT_REDFLAG,
+	},
+	// TEAM_BLUE
+	{
+		.color = SKINCOLOR_BLUE,
+		.weapon_color = SKINCOLOR_BLUE,
+		.missile_color = SKINCOLOR_CORNFLOWER,
+		.flag = GF_BLUEFLAG,
+		.flag_mobj_type = MT_BLUEFLAG,
+	}
+};
+
+static void G_InitTeams(void)
+{
+	numteams = 3;
+	teamsingame = numteams;
+
+	teams[TEAM_NONE].name = Z_StrDup("None");
+	teams[TEAM_NONE].flag_name = Z_StrDup("Thingmabob");
+
+	teams[TEAM_RED].name = Z_StrDup("Red");
+	teams[TEAM_RED].flag_name = Z_StrDup("Red Flag");
+
+	teams[TEAM_BLUE].name = Z_StrDup("Blue");
+	teams[TEAM_BLUE].flag_name = Z_StrDup("Blue Flag");
+
+	G_UpdateTeamSelection();
+}
+
 void G_InitGametypes(void)
 {
 	for (unsigned i = 0; i <= GT_CTF; i++)
@@ -3506,6 +3540,25 @@ void G_InitGametypes(void)
 		gametypes[i].name = Z_StrDup(Gametype_Names[i]);
 		gametypes[i].constant_name = Z_StrDup(Gametype_ConstantNames[i]);
 	}
+
+	G_InitTeams();
+}
+
+void G_UpdateTeamSelection(void)
+{
+	UINT8 i;
+
+	dummyteam_cons_t[0].value = 0;
+	dummyteam_cons_t[0].strvalue = "Spectator";
+
+	for (i = 1; i <= teamsingame; i++)
+	{
+		dummyteam_cons_t[i].value = i;
+		dummyteam_cons_t[i].strvalue = teams[i].name;
+	}
+
+	dummyteam_cons_t[i].value = 0;
+	dummyteam_cons_t[i].strvalue = NULL;
 }
 
 //
@@ -3802,6 +3855,46 @@ UINT32 G_TOLFlag(INT32 pgametype)
 	return gametypes[pgametype].typeoflevel;
 }
 
+const char *G_GetTeamName(UINT8 team)
+{
+	if (team >= numteams)
+		return "Unknown";
+
+	return teams[team].name;
+}
+
+const char *G_GetTeamFlagName(UINT8 team)
+{
+	if (team >= numteams)
+		return "";
+
+	return teams[team].flag_name;
+}
+
+UINT16 G_GetTeamColor(UINT8 team)
+{
+	if (team >= numteams)
+		return SKINCOLOR_NONE;
+
+	return teams[team].color;
+}
+
+UINT16 G_GetTeamWeaponColor(UINT8 team)
+{
+	if (team >= numteams)
+		return SKINCOLOR_NONE;
+
+	return teams[team].weapon_color;
+}
+
+UINT16 G_GetTeamMissileColor(UINT8 team)
+{
+	if (team >= numteams)
+		return SKINCOLOR_NONE;
+
+	return teams[team].missile_color;
+}
+
 /** Select a random map with the given typeoflevel flags.
   * If no map has those flags, this arbitrarily gives you map 1.
   * \param tolflags The typeoflevel flags to insist on. Other bits may
@@ -5012,9 +5105,11 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean
 	if (resetplayer)
 	{
 		// Clear a bunch of variables
-		numgameovers = tokenlist = token = sstimer = redscore = bluescore = lastmap = 0;
+		numgameovers = tokenlist = token = sstimer = lastmap = 0;
 		countdown = countdown2 = exitfadestarted = 0;
 
+		memset(teamscores, 0, sizeof(teamscores));
+
 		if (!FLS)
 		{
 			emeralds = 0;
diff --git a/src/g_game.h b/src/g_game.h
index 7b2e6620bb62778bf3c007044220ffb6b3243fbb..6906ae44be557346c43500e26f02e5b5bf5a6af3 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -194,6 +194,7 @@ void G_SaveGameOver(UINT32 slot, boolean modifylives);
 void G_SetGametype(INT16 gametype);
 void G_InitGametypes(void);
 INT16 G_AddGametype(void);
+void G_UpdateTeamSelection(void);
 void G_AddGametypeConstant(INT16 gtype, const char *newgtconst);
 void G_UpdateGametypeSelections(void);
 void G_AddTOL(UINT32 newtol, const char *tolname);
@@ -222,6 +223,12 @@ void G_UseContinue(void);
 void G_AfterIntermission(void);
 void G_EndGame(void); // moved from y_inter.c/h and renamed
 
+const char *G_GetTeamName(UINT8 team);
+const char *G_GetTeamFlagName(UINT8 team);
+UINT16 G_GetTeamColor(UINT8 team);
+UINT16 G_GetTeamWeaponColor(UINT8 team);
+UINT16 G_GetTeamMissileColor(UINT8 team);
+
 void G_Ticker(boolean run);
 boolean G_Responder(event_t *ev);
 boolean G_LuaResponder(event_t *ev);
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index 9339ad35744ce3f7601fa19b54c9bd3b4fcf0f3a..96417512878822fcb8609567b87a29b26f52fa93 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -620,6 +620,46 @@ static void Command_CSay_f(void)
 }
 static tic_t stop_spamming[MAXPLAYERS];
 
+const char *GetChatColorForSkincolor(UINT16 skincolor)
+{
+	UINT16 chatcolor = skincolors[skincolor].chatcolor;
+
+	if (!chatcolor || chatcolor%0x1000 || chatcolor>V_INVERTMAP)
+		return "\x80";
+	else if (chatcolor == V_MAGENTAMAP)
+		return "\x81";
+	else if (chatcolor == V_YELLOWMAP)
+		return "\x82";
+	else if (chatcolor == V_GREENMAP)
+		return "\x83";
+	else if (chatcolor == V_BLUEMAP)
+		return "\x84";
+	else if (chatcolor == V_REDMAP)
+		return "\x85";
+	else if (chatcolor == V_GRAYMAP)
+		return "\x86";
+	else if (chatcolor == V_ORANGEMAP)
+		return "\x87";
+	else if (chatcolor == V_SKYMAP)
+		return "\x88";
+	else if (chatcolor == V_PURPLEMAP)
+		return "\x89";
+	else if (chatcolor == V_AQUAMAP)
+		return "\x8a";
+	else if (chatcolor == V_PERIDOTMAP)
+		return "\x8b";
+	else if (chatcolor == V_AZUREMAP)
+		return "\x8c";
+	else if (chatcolor == V_BROWNMAP)
+		return "\x8d";
+	else if (chatcolor == V_ROSYMAP)
+		return "\x8e";
+	else if (chatcolor == V_INVERTMAP)
+		return "\x8f";
+
+	return "\x80";
+}
+
 /** Receives a message, processing an ::XD_SAY command.
   * \sa DoSayCommand
   * \author Graue <graue@oceanbase.org>
@@ -728,6 +768,7 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
 	{
 		const char *prefix = "", *cstart = "", *cend = "", *adminchar = "\x82~\x83", *remotechar = "\x82@\x83", *fmt2, *textcolor = "\x80";
 		char *tempchar = NULL;
+		char *tempteam = NULL;
 
 		// player is a spectator?
         if (players[playernum].spectator)
@@ -737,54 +778,13 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
 		}
 		else if (target == -1) // say team
 		{
-			if (players[playernum].ctfteam == 1) // red
-			{
-				cstart = "\x85";
-				textcolor = "\x85";
-			}
-			else // blue
-			{
-				cstart = "\x84";
-				textcolor = "\x84";
-			}
+			cstart = GetChatColorForSkincolor(G_GetTeamColor(players[playernum].ctfteam));
+			textcolor = cstart;
 		}
 		else
-        {
-			UINT16 chatcolor = skincolors[players[playernum].skincolor].chatcolor;
-
-			if (!chatcolor || chatcolor%0x1000 || chatcolor>V_INVERTMAP)
-				cstart = "\x80";
-			else if (chatcolor == V_MAGENTAMAP)
-				cstart = "\x81";
-			else if (chatcolor == V_YELLOWMAP)
-				cstart = "\x82";
-			else if (chatcolor == V_GREENMAP)
-				cstart = "\x83";
-			else if (chatcolor == V_BLUEMAP)
-				cstart = "\x84";
-			else if (chatcolor == V_REDMAP)
-				cstart = "\x85";
-			else if (chatcolor == V_GRAYMAP)
-				cstart = "\x86";
-			else if (chatcolor == V_ORANGEMAP)
-				cstart = "\x87";
-			else if (chatcolor == V_SKYMAP)
-				cstart = "\x88";
-			else if (chatcolor == V_PURPLEMAP)
-				cstart = "\x89";
-			else if (chatcolor == V_AQUAMAP)
-				cstart = "\x8a";
-			else if (chatcolor == V_PERIDOTMAP)
-				cstart = "\x8b";
-			else if (chatcolor == V_AZUREMAP)
-				cstart = "\x8c";
-			else if (chatcolor == V_BROWNMAP)
-				cstart = "\x8d";
-			else if (chatcolor == V_ROSYMAP)
-				cstart = "\x8e";
-			else if (chatcolor == V_INVERTMAP)
-				cstart = "\x8f";
-        }
+		{
+			cstart = GetChatColorForSkincolor(players[playernum].skincolor);
+		}
 		prefix = cstart;
 
 		// Give admins and remote admins their symbols.
@@ -828,10 +828,11 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
 			fmt2 = "%s<%s%s%s>\x80 %s%s";
 		else // To your team
 		{
-			if (players[playernum].ctfteam == 1) // red
-				prefix = "\x85[TEAM]";
-			else if (players[playernum].ctfteam == 2) // blue
-				prefix = "\x84[TEAM]";
+			if (players[playernum].ctfteam != 0)
+			{
+				tempteam = Z_StrDup(va("%s[TEAM]", GetChatColorForSkincolor(G_GetTeamColor(players[playernum].ctfteam))));
+				prefix = tempteam;
+			}
 			else
 				prefix = "\x83"; // makes sure this doesn't implode if you sayteam on non-team gamemodes
 
@@ -842,6 +843,8 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
 
 		if (tempchar)
 			Z_Free(tempchar);
+		if (tempteam)
+			Z_Free(tempteam);
 	}
 #ifdef _DEBUG
 	// I just want to point out while I'm here that because the data is still
@@ -1562,12 +1565,6 @@ static void HU_DrawChat(void)
 	if (teamtalk)
 	{
 		talk = ttalk;
-#if 0
-		if (players[consoleplayer].ctfteam == 1)
-			t = 0x500;  // Red
-		else if (players[consoleplayer].ctfteam == 2)
-			t = 0x400; // Blue
-#endif
 	}
 
 	if (CHAT_MUTE)
@@ -1727,12 +1724,6 @@ static void HU_DrawChat_Old(void)
 	if (teamtalk)
 	{
 		talk = ttalk;
-#if 0
-		if (players[consoleplayer].ctfteam == 1)
-			t = 0x500;  // Red
-		else if (players[consoleplayer].ctfteam == 2)
-			t = 0x400; // Blue
-#endif
 	}
 
 	while (talk[i])
@@ -2306,13 +2297,13 @@ static void HU_Draw32TeamTabRankings(playersort_t *tab, INT32 whiteplayer)
 		greycheck = greycheckdef;
 		supercheck = supercheckdef;
 
-		if (tab[i].color == skincolor_redteam) //red
+		if (tab[i].team == TEAM_RED) //red
 		{
 			redplayers++;
 			x = 14 + (BASEVIDWIDTH/2);
 			y = (redplayers * 9) + 20;
 		}
-		else if (tab[i].color == skincolor_blueteam) //blue
+		else if (tab[i].team == TEAM_BLUE) //blue
 		{
 			blueplayers++;
 			x = 14;
@@ -2394,7 +2385,7 @@ void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer)
 		if (players[tab[i].num].spectator)
 			continue; //ignore them.
 
-		if (tab[i].color == skincolor_redteam) //red
+		if (tab[i].team == TEAM_RED) //red
 		{
 			if (redplayers++ > 8)
 			{
@@ -2402,7 +2393,7 @@ void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer)
 				break; // don't make more loops than we need to.
 			}
 		}
-		else if (tab[i].color == skincolor_blueteam) //blue
+		else if (tab[i].team == TEAM_BLUE) //blue
 		{
 			if (blueplayers++ > 8)
 			{
@@ -2433,14 +2424,14 @@ void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer)
 		if (players[tab[i].num].spectator)
 			continue; //ignore them.
 
-		if (tab[i].color == skincolor_redteam) //red
+		if (tab[i].team == TEAM_RED) //red
 		{
 			if (redplayers++ > 8)
 				continue;
 			x = 32 + (BASEVIDWIDTH/2);
 			y = (redplayers * 16) + 16;
 		}
-		else if (tab[i].color == skincolor_blueteam) //blue
+		else if (tab[i].team == TEAM_BLUE) //blue
 		{
 			if (blueplayers++ > 8)
 				continue;
@@ -2884,6 +2875,7 @@ static void HU_DrawRankings(void)
 						tab[scorelines].count = players[i].laps+1;
 						tab[scorelines].num = i;
 						tab[scorelines].color = players[i].skincolor;
+						tab[scorelines].team = players[i].ctfteam;
 						tab[scorelines].name = player_names[i];
 					}
 				}
@@ -2894,6 +2886,7 @@ static void HU_DrawRankings(void)
 						tab[scorelines].count = players[i].realtime;
 						tab[scorelines].num = i;
 						tab[scorelines].color = players[i].skincolor;
+						tab[scorelines].team = players[i].ctfteam;
 						tab[scorelines].name = player_names[i];
 					}
 				}
@@ -2908,6 +2901,7 @@ static void HU_DrawRankings(void)
 					tab[scorelines].num = i;
 					tab[scorelines].color = players[i].skincolor;
 					tab[scorelines].name = player_names[i];
+					tab[scorelines].team = players[i].ctfteam;
 					tab[scorelines].emeralds = players[i].powers[pw_emeralds];
 				}
 			}
@@ -2919,6 +2913,7 @@ static void HU_DrawRankings(void)
 					tab[scorelines].num = i;
 					tab[scorelines].color = players[i].skincolor;
 					tab[scorelines].name = player_names[i];
+					tab[scorelines].team = players[i].ctfteam;
 					tab[scorelines].emeralds = players[i].powers[pw_emeralds];
 				}
 			}
diff --git a/src/hu_stuff.h b/src/hu_stuff.h
index 8647e4500cb2ce1ebf76015dbcc9031a0b5f4082..e10b45164458ca33c95021823439780f187fb7db 100644
--- a/src/hu_stuff.h
+++ b/src/hu_stuff.h
@@ -56,6 +56,7 @@ typedef struct
 	INT32 num;
 	INT32 color;
 	INT32 emeralds;
+	UINT8 team;
 	const char *name;
 } playersort_t;
 
@@ -121,6 +122,8 @@ void HU_DrawEmeralds(INT32 x, INT32 y, INT32 pemeralds);
 
 INT32 HU_CreateTeamScoresTbl(playersort_t *tab, UINT32 dmtotals[]);
 
+const char *GetChatColorForSkincolor(UINT16 skincolor);
+
 // CECHO interface.
 void HU_ClearCEcho(void);
 void HU_SetCEchoDuration(INT32 seconds);
diff --git a/src/info.c b/src/info.c
index 5790dd7c56ce3ee5f625f63581903a6d185a3a73..15ff40c675e8d3c05486c50c3aa2cdc110dd080d 100644
--- a/src/info.c
+++ b/src/info.c
@@ -7123,7 +7123,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		24*FRACUNIT,    // radius
 		64*FRACUNIT,    // height
 		0,              // display offset
-		16,             // mass
+		TEAM_RED,       // mass
 		0,              // damage
 		sfx_None,       // activesound
 		MF_SPECIAL,     // flags
@@ -7150,7 +7150,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		24*FRACUNIT,    // radius
 		64*FRACUNIT,    // height
 		0,              // display offset
-		16,             // mass
+		TEAM_BLUE,      // mass
 		0,              // damage
 		sfx_None,       // activesound
 		MF_SPECIAL,     // flags
diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c
index d12aa1c3fae8f5e1e0e9171118c21b03d1310350..80b586457f4f7db2f766d516f39c4dbdf017e9e9 100644
--- a/src/lua_playerlib.c
+++ b/src/lua_playerlib.c
@@ -1110,7 +1110,7 @@ static int player_set(lua_State *L)
 		plr->laps = (UINT8)luaL_checkinteger(L, 3);
 		break;
 	case player_ctfteam:
-		plr->ctfteam = (INT32)luaL_checkinteger(L, 3);
+		plr->ctfteam = (INT32)luaL_checkinteger(L, 3) % MAXTEAMS;
 		break;
 	case player_gotflag:
 		plr->gotflag = (UINT16)luaL_checkinteger(L, 3);
diff --git a/src/lua_script.c b/src/lua_script.c
index e352673c0ac8a44e89dbfc9137ab25438b69cb6f..40c6af3e097edc48ea392a94b5d498135e27fa3f 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -214,10 +214,10 @@ int LUA_PushGlobals(lua_State *L, const char *word)
 		lua_pushboolean(L, paused);
 		return 1;
 	} else if (fastcmp(word,"bluescore")) {
-		lua_pushinteger(L, bluescore);
+		lua_pushinteger(L, teamscores[TEAM_BLUE]);
 		return 1;
 	} else if (fastcmp(word,"redscore")) {
-		lua_pushinteger(L, redscore);
+		lua_pushinteger(L, teamscores[TEAM_RED]);
 		return 1;
 	} else if (fastcmp(word,"timelimit")) {
 		lua_pushinteger(L, cv_timelimit.value);
@@ -226,16 +226,16 @@ int LUA_PushGlobals(lua_State *L, const char *word)
 		lua_pushinteger(L, cv_pointlimit.value);
 		return 1;
 	} else if (fastcmp(word, "redflag")) {
-		LUA_PushUserdata(L, redflag, META_MOBJ);
+		LUA_PushUserdata(L, flagmobjs[TEAM_RED], META_MOBJ);
 		return 1;
 	} else if (fastcmp(word, "blueflag")) {
-		LUA_PushUserdata(L, blueflag, META_MOBJ);
+		LUA_PushUserdata(L, flagmobjs[TEAM_BLUE], META_MOBJ);
 		return 1;
 	} else if (fastcmp(word, "rflagpoint")) {
-		LUA_PushUserdata(L, rflagpoint, META_MAPTHING);
+		LUA_PushUserdata(L, flagpoints[TEAM_RED], META_MAPTHING);
 		return 1;
 	} else if (fastcmp(word, "bflagpoint")) {
-		LUA_PushUserdata(L, bflagpoint, META_MAPTHING);
+		LUA_PushUserdata(L, flagpoints[TEAM_BLUE], META_MAPTHING);
 		return 1;
 	// begin map vars
 	} else if (fastcmp(word,"spstage_start")) {
@@ -274,16 +274,16 @@ int LUA_PushGlobals(lua_State *L, const char *word)
 	// end map vars
 	// begin CTF colors
 	} else if (fastcmp(word,"skincolor_redteam")) {
-		lua_pushinteger(L, skincolor_redteam);
+		lua_pushinteger(L, G_GetTeamColor(TEAM_RED));
 		return 1;
 	} else if (fastcmp(word,"skincolor_blueteam")) {
-		lua_pushinteger(L, skincolor_blueteam);
+		lua_pushinteger(L, G_GetTeamColor(TEAM_BLUE));
 		return 1;
 	} else if (fastcmp(word,"skincolor_redring")) {
-		lua_pushinteger(L, skincolor_redring);
+		lua_pushinteger(L, G_GetTeamMissileColor(TEAM_RED));
 		return 1;
 	} else if (fastcmp(word,"skincolor_bluering")) {
-		lua_pushinteger(L, skincolor_bluering);
+		lua_pushinteger(L, G_GetTeamMissileColor(TEAM_BLUE));
 		return 1;
 	// end CTF colors
 	// begin timers
@@ -439,17 +439,17 @@ int LUA_PushGlobals(lua_State *L, const char *word)
 int LUA_CheckGlobals(lua_State *L, const char *word)
 {
 	if (fastcmp(word, "redscore"))
-		redscore = (UINT32)luaL_checkinteger(L, 2);
+		teamscores[TEAM_RED] = (UINT32)luaL_checkinteger(L, 2);
 	else if (fastcmp(word, "bluescore"))
-		bluescore = (UINT32)luaL_checkinteger(L, 2);
+		teamscores[TEAM_BLUE] = (UINT32)luaL_checkinteger(L, 2);
 	else if (fastcmp(word, "skincolor_redteam"))
-		skincolor_redteam = (UINT16)luaL_checkinteger(L, 2);
+		teams[TEAM_RED].color = (UINT16)luaL_checkinteger(L, 2);
 	else if (fastcmp(word, "skincolor_blueteam"))
-		skincolor_blueteam = (UINT16)luaL_checkinteger(L, 2);
+		teams[TEAM_BLUE].color = (UINT16)luaL_checkinteger(L, 2);
 	else if (fastcmp(word, "skincolor_redring"))
-		skincolor_redring = (UINT16)luaL_checkinteger(L, 2);
+		teams[TEAM_RED].missile_color = (UINT16)luaL_checkinteger(L, 2);
 	else if (fastcmp(word, "skincolor_bluering"))
-		skincolor_bluering = (UINT16)luaL_checkinteger(L, 2);
+		teams[TEAM_BLUE].missile_color = (UINT16)luaL_checkinteger(L, 2);
 	else if (fastcmp(word, "emeralds"))
 		emeralds = (UINT16)luaL_checkinteger(L, 2);
 	else if (fastcmp(word, "token"))
diff --git a/src/m_cheat.c b/src/m_cheat.c
index 2bcf43ad1ee1971c13ab3a769ef8b0fafc708883..0cb8eed2fe352b2db9486b3863a16e1a8e6894e3 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -773,15 +773,15 @@ void Command_CauseCfail_f(void)
 	// CTF consistency test
 	if (gametyperules & GTR_TEAMFLAGS)
 	{
-		if (blueflag) {
-			P_RemoveMobj(blueflag);
-			blueflag = NULL;
+		if (flagmobjs[TEAM_BLUE]) {
+			P_RemoveMobj(flagmobjs[TEAM_BLUE]);
+			flagmobjs[TEAM_BLUE] = NULL;
 		}
-		if (redflag)
+		if (flagmobjs[TEAM_RED])
 		{
-			redflag->x = 423423;
-			redflag->y = 666;
-			redflag->z = 123311;
+			flagmobjs[TEAM_RED]->x = 423423;
+			flagmobjs[TEAM_RED]->y = 666;
+			flagmobjs[TEAM_RED]->z = 123311;
 		}
 	}
 }
diff --git a/src/m_menu.c b/src/m_menu.c
index e69745a2aea960d184604e651418f4d20778ce51..853cb1f258dc990d361bb2e6d234e3ce5ae7b5e2 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -457,7 +457,6 @@ consvar_t cv_ghost_guest     = CVAR_INIT ("ghost_guest",     "Show", CV_SAVE, gh
 //Console variables used solely in the menu system.
 //todo: add a way to use non-console variables in the menu
 //      or make these consvars legitimate like color or skin.
-static CV_PossibleValue_t dummyteam_cons_t[] = {{0, "Spectator"}, {1, "Red"}, {2, "Blue"}, {0, NULL}};
 static CV_PossibleValue_t dummyscramble_cons_t[] = {{0, "Random"}, {1, "Points"}, {0, NULL}};
 static CV_PossibleValue_t ringlimit_cons_t[] = {{0, "MIN"}, {9999, "MAX"}, {0, NULL}};
 static CV_PossibleValue_t liveslimit_cons_t[] = {{1, "MIN"}, {99, "MAX"}, {-1, "Infinite"}, {0, NULL}};
@@ -466,6 +465,8 @@ static CV_PossibleValue_t dummymares_cons_t[] = {
 	{-1, "END"}, {0,"Overall"}, {1,"Mare 1"}, {2,"Mare 2"}, {3,"Mare 3"}, {4,"Mare 4"}, {5,"Mare 5"}, {6,"Mare 6"}, {7,"Mare 7"}, {8,"Mare 8"}, {0,NULL}
 };
 
+CV_PossibleValue_t dummyteam_cons_t[MAXTEAMS + 1];
+
 static consvar_t cv_dummyteam = CVAR_INIT ("dummyteam", "Spectator", CV_HIDEN, dummyteam_cons_t, NULL);
 static consvar_t cv_dummyscramble = CVAR_INIT ("dummyscramble", "Random", CV_HIDEN, dummyscramble_cons_t, NULL);
 static consvar_t cv_dummyrings = CVAR_INIT ("dummyrings", "0", CV_HIDEN, ringlimit_cons_t,	NULL);
diff --git a/src/m_menu.h b/src/m_menu.h
index b8fe3b808928b81c86bf3c09731e557f4b113616..c28467b78a354874a5ecc9f222af0d9078106792 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -436,6 +436,7 @@ extern description_t description[MAXSKINS];
 extern consvar_t cv_showfocuslost;
 extern consvar_t cv_newgametype, cv_nextmap, cv_chooseskin, cv_serversort;
 extern CV_PossibleValue_t gametype_cons_t[];
+extern CV_PossibleValue_t dummyteam_cons_t[];
 
 extern INT16 startmap;
 extern INT32 ultimate_selectable;
diff --git a/src/m_misc.c b/src/m_misc.c
index d4b272f1d3fae38e3ef6a49844706a286c6e3042..e8a77688f05ded800ff1756a3aa359648647e0e2 100644
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -2805,3 +2805,18 @@ boolean M_IsStringEmpty(const char *s)
 
 	return true;
 }
+
+// Returns true if the string only contains digits.
+boolean M_StringOnlyHasDigits(const char *s)
+{
+	if (M_IsStringEmpty(s))
+		return false;
+
+	while (*s)
+	{
+		if (!isdigit(*s++))
+			return false;
+	}
+
+	return true;
+}
diff --git a/src/m_misc.h b/src/m_misc.h
index 8cad7ba9a43353c0aaca9acf3688939ebf270775..c009962a6ad253f1d12db1e0805896df2c85329e 100644
--- a/src/m_misc.h
+++ b/src/m_misc.h
@@ -109,6 +109,9 @@ const char * M_Ftrim (double);
 // Returns true if the string is empty.
 boolean M_IsStringEmpty(const char *s);
 
+// Returns true if the string only contains digits.
+boolean M_StringOnlyHasDigits(const char *s);
+
 // counting bits, for weapon ammo code, usually
 FUNCMATH UINT8 M_CountBits(UINT32 num, UINT8 size);
 
diff --git a/src/netcode/d_netcmd.c b/src/netcode/d_netcmd.c
index 40c20576f9084d7ff44e4a7d48d62a0fd1e3028a..92161216cdf18cdc7821577e3d1e7a9b7d7d233f 100644
--- a/src/netcode/d_netcmd.c
+++ b/src/netcode/d_netcmd.c
@@ -1321,6 +1321,7 @@ static void SendNameAndColor2(void)
 	else // HACK
 		secondplaya = 1;
 
+	// don't allow inaccessible colors
 	if (!skincolors[cv_playercolor2.value].accessible)
 	{
 		if (players[secondplaya].skincolor && skincolors[players[secondplaya].skincolor].accessible)
@@ -2266,6 +2267,16 @@ static void Got_Clearscores(UINT8 **cp, INT32 playernum)
 	CONS_Printf(M_GetText("Scores have been reset by the server.\n"));
 }
 
+static UINT8 GetTeamByName(const char *name)
+{
+	for (UINT8 i = TEAM_RED; i < teamsingame; i++)
+	{
+		if (!stricmp(name, G_GetTeamName(i)))
+			return i;
+	}
+	return MAXTEAMS;
+}
+
 // Team changing functions
 static void Command_Teamchange_f(void)
 {
@@ -2282,9 +2293,9 @@ static void Command_Teamchange_f(void)
 		if (G_GametypeHasTeams())
 		{
 			if (G_GametypeHasSpectators())
-				CONS_Printf(M_GetText("changeteam <team>: switch to a new team (%s)\n"), "red, blue or spectator");
+				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"), "red or blue");
+				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");
@@ -2295,21 +2306,27 @@ static void Command_Teamchange_f(void)
 
 	if (G_GametypeHasTeams())
 	{
-		if (!strcasecmp(COM_Argv(1), "red") || !strcasecmp(COM_Argv(1), "1"))
-			NetPacket.packet.newteam = 1;
-		else if (!strcasecmp(COM_Argv(1), "blue") || !strcasecmp(COM_Argv(1), "2"))
-			NetPacket.packet.newteam = 2;
-		else if (G_GametypeHasSpectators() && (!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
-			error = true;
+		{
+			UINT8 newteam = MAXTEAMS;
+			if (M_StringOnlyHasDigits(COM_Argv(1)))
+				newteam = atoi(COM_Argv(1));
+			else
+				newteam = GetTeamByName(COM_Argv(1));
+			if (newteam != MAXTEAMS)
+				NetPacket.packet.newteam = newteam;
+			else
+				error = true;
+		}
 	}
 	else if (G_GametypeHasSpectators())
 	{
 		if (!strcasecmp(COM_Argv(1), "spectator") || !strcasecmp(COM_Argv(1), "0"))
 			NetPacket.packet.newteam = 0;
 		else if (!strcasecmp(COM_Argv(1), "playing") || !strcasecmp(COM_Argv(1), "1"))
-			NetPacket.packet.newteam = 3;
+			NetPacket.packet.newteam = MAXTEAMS;
 		else
 			error = true;
 	}
@@ -2324,9 +2341,9 @@ static void Command_Teamchange_f(void)
 		if (G_GametypeHasTeams())
 		{
 			if (G_GametypeHasSpectators())
-				CONS_Printf(M_GetText("changeteam <team>: switch to a new team (%s)\n"), "red, blue or spectator");
+				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"), "red or blue");
+				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");
@@ -2342,7 +2359,7 @@ static void Command_Teamchange_f(void)
 	else if (G_GametypeHasSpectators())
 	{
 		if ((players[consoleplayer].spectator && !NetPacket.packet.newteam) ||
-			(!players[consoleplayer].spectator && NetPacket.packet.newteam == 3))
+			(!players[consoleplayer].spectator && NetPacket.packet.newteam == MAXTEAMS))
 			error = true;
 	}
 #ifdef PARANOIA
@@ -2388,9 +2405,9 @@ static void Command_Teamchange2_f(void)
 		if (G_GametypeHasTeams())
 		{
 			if (G_GametypeHasSpectators())
-				CONS_Printf(M_GetText("changeteam2 <team>: switch to a new team (%s)\n"), "red, blue or spectator");
+				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"), "red or blue");
+				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");
@@ -2401,25 +2418,30 @@ static void Command_Teamchange2_f(void)
 
 	if (G_GametypeHasTeams())
 	{
-		if (!strcasecmp(COM_Argv(1), "red") || !strcasecmp(COM_Argv(1), "1"))
-			NetPacket.packet.newteam = 1;
-		else if (!strcasecmp(COM_Argv(1), "blue") || !strcasecmp(COM_Argv(1), "2"))
-			NetPacket.packet.newteam = 2;
-		else if (G_GametypeHasSpectators() && (!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
-			error = true;
+		{
+			UINT8 newteam = MAXTEAMS;
+			if (M_StringOnlyHasDigits(COM_Argv(1)))
+				newteam = atoi(COM_Argv(1));
+			else
+				newteam = GetTeamByName(COM_Argv(1));
+			if (newteam != MAXTEAMS)
+				NetPacket.packet.newteam = newteam;
+			else
+				error = true;
+		}
 	}
 	else if (G_GametypeHasSpectators())
 	{
 		if (!strcasecmp(COM_Argv(1), "spectator") || !strcasecmp(COM_Argv(1), "0"))
 			NetPacket.packet.newteam = 0;
 		else if (!strcasecmp(COM_Argv(1), "playing") || !strcasecmp(COM_Argv(1), "1"))
-			NetPacket.packet.newteam = 3;
+			NetPacket.packet.newteam = MAXTEAMS;
 		else
 			error = true;
 	}
-
 	else
 	{
 		CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n"));
@@ -2431,9 +2453,9 @@ static void Command_Teamchange2_f(void)
 		if (G_GametypeHasTeams())
 		{
 			if (G_GametypeHasSpectators())
-				CONS_Printf(M_GetText("changeteam2 <team>: switch to a new team (%s)\n"), "red, blue or spectator");
+				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"), "red or blue");
+				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");
@@ -2503,9 +2525,9 @@ static void Command_ServerTeamChange_f(void)
 		else if (G_GametypeHasTeams())
 		{
 			if (G_GametypeHasSpectators())
-				CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: switch player to a new team (%s)\n"), "red, blue or spectator");
+				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"), "red or blue");
+				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");
@@ -2521,7 +2543,7 @@ static void Command_ServerTeamChange_f(void)
 		else if (!strcasecmp(COM_Argv(2), "notit") || !strcasecmp(COM_Argv(2), "2"))
 			NetPacket.packet.newteam = 2;
 		else if (!strcasecmp(COM_Argv(2), "playing") || !strcasecmp(COM_Argv(2), "3"))
-			NetPacket.packet.newteam = 3;
+			NetPacket.packet.newteam = MAXTEAMS;
 		else if (!strcasecmp(COM_Argv(2), "spectator") || !strcasecmp(COM_Argv(2), "0"))
 			NetPacket.packet.newteam = 0;
 		else
@@ -2529,21 +2551,27 @@ static void Command_ServerTeamChange_f(void)
 	}
 	else if (G_GametypeHasTeams())
 	{
-		if (!strcasecmp(COM_Argv(2), "red") || !strcasecmp(COM_Argv(2), "1"))
-			NetPacket.packet.newteam = 1;
-		else if (!strcasecmp(COM_Argv(2), "blue") || !strcasecmp(COM_Argv(2), "2"))
-			NetPacket.packet.newteam = 2;
-		else if (G_GametypeHasSpectators() && (!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
-			error = true;
+		{
+			UINT8 newteam = MAXTEAMS;
+			if (M_StringOnlyHasDigits(COM_Argv(1)))
+				newteam = atoi(COM_Argv(1));
+			else
+				newteam = GetTeamByName(COM_Argv(1));
+			if (newteam != MAXTEAMS)
+				NetPacket.packet.newteam = newteam;
+			else
+				error = true;
+		}
 	}
 	else if (G_GametypeHasSpectators())
 	{
 		if (!strcasecmp(COM_Argv(2), "spectator") || !strcasecmp(COM_Argv(2), "0"))
 			NetPacket.packet.newteam = 0;
 		else if (!strcasecmp(COM_Argv(2), "playing") || !strcasecmp(COM_Argv(2), "1"))
-			NetPacket.packet.newteam = 3;
+			NetPacket.packet.newteam = MAXTEAMS;
 		else
 			error = true;
 	}
@@ -2560,9 +2588,9 @@ static void Command_ServerTeamChange_f(void)
 		else if (G_GametypeHasTeams())
 		{
 			if (G_GametypeHasSpectators())
-				CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: switch player to a new team (%s)\n"), "red, blue or spectator");
+				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"), "red or blue");
+				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");
@@ -2654,7 +2682,7 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum)
 		if (((players[playernum].pflags & PF_TAGIT) && NetPacket.packet.newteam == 1) ||
 			(!(players[playernum].pflags & PF_TAGIT) && NetPacket.packet.newteam == 2) ||
 			(players[playernum].spectator && NetPacket.packet.newteam == 0) ||
-			(!players[playernum].spectator && NetPacket.packet.newteam == 3))
+			(!players[playernum].spectator && NetPacket.packet.newteam == MAXTEAMS))
 			return;
 	}
 	else if (G_GametypeHasTeams())
@@ -2666,7 +2694,7 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum)
 	else if (G_GametypeHasSpectators())
 	{
 		if ((players[playernum].spectator && !NetPacket.packet.newteam) ||
-			(!players[playernum].spectator && NetPacket.packet.newteam == 3))
+			(!players[playernum].spectator && NetPacket.packet.newteam == MAXTEAMS))
 			return;
 	}
 	else
@@ -2784,14 +2812,15 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum)
 	}
 	else if (G_GametypeHasTeams())
 	{
-		if (!NetPacket.packet.newteam)
+		UINT8 team = NetPacket.packet.newteam % MAXTEAMS;
+		if (!team)
 		{
 			players[playernum].ctfteam = 0;
 			players[playernum].spectator = true;
 		}
 		else
 		{
-			players[playernum].ctfteam = NetPacket.packet.newteam;
+			players[playernum].ctfteam = team;
 			players[playernum].spectator = false;
 		}
 	}
@@ -2804,35 +2833,20 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum)
 	}
 
 	if (NetPacket.packet.autobalance)
-	{
-		if (NetPacket.packet.newteam == 1)
-			CONS_Printf(M_GetText("%s was autobalanced to the %c%s%c.\n"), player_names[playernum], '\x85', M_GetText("Red Team"), '\x80');
-		else if (NetPacket.packet.newteam == 2)
-			CONS_Printf(M_GetText("%s was autobalanced to the %c%s%c.\n"), player_names[playernum], '\x84', M_GetText("Blue Team"), '\x80');
-	}
+		CONS_Printf(M_GetText("%s was autobalanced to %s%s%c.\n"), player_names[playernum], GetChatColorForSkincolor(G_GetTeamColor(NetPacket.packet.newteam)), G_GetTeamName(NetPacket.packet.newteam), '\x80');
 	else if (NetPacket.packet.scrambled)
+		CONS_Printf(M_GetText("%s was scrambled to %s%s%c.\n"), player_names[playernum], GetChatColorForSkincolor(G_GetTeamColor(NetPacket.packet.newteam)), G_GetTeamName(NetPacket.packet.newteam), '\x80');
+	else if (NetPacket.packet.newteam == MAXTEAMS)
+		CONS_Printf(M_GetText("%s entered the game.\n"), player_names[playernum]);
+	else if (G_TagGametype() && NetPacket.packet.newteam != 0)
 	{
 		if (NetPacket.packet.newteam == 1)
-			CONS_Printf(M_GetText("%s was scrambled to the %c%s%c.\n"), player_names[playernum], '\x85', M_GetText("Red Team"), '\x80');
-		else if (NetPacket.packet.newteam == 2)
-			CONS_Printf(M_GetText("%s was scrambled to the %c%s%c.\n"), player_names[playernum], '\x84', M_GetText("Blue Team"), '\x80');
-	}
-	else if (NetPacket.packet.newteam == 1)
-	{
-		if (G_TagGametype())
 			CONS_Printf(M_GetText("%s is now IT!\n"), player_names[playernum]);
-		else
-			CONS_Printf(M_GetText("%s switched to the %c%s%c.\n"), player_names[playernum], '\x85', M_GetText("Red Team"), '\x80');
-	}
-	else if (NetPacket.packet.newteam == 2)
-	{
-		if (G_TagGametype())
+		else if (NetPacket.packet.newteam == 2)
 			CONS_Printf(M_GetText("%s is no longer IT!\n"), player_names[playernum]);
-		else
-			CONS_Printf(M_GetText("%s switched to the %c%s%c.\n"), player_names[playernum], '\x84', M_GetText("Blue Team"), '\x80');
 	}
-	else if (NetPacket.packet.newteam == 3)
-		CONS_Printf(M_GetText("%s entered the game.\n"), player_names[playernum]);
+	else if (G_GametypeHasTeams() && NetPacket.packet.newteam != 0)
+		CONS_Printf(M_GetText("%s switched to %s%s%c.\n"), player_names[playernum], GetChatColorForSkincolor(G_GetTeamColor(NetPacket.packet.newteam)), G_GetTeamName(NetPacket.packet.newteam), '\x80');
 	else
 		CONS_Printf(M_GetText("%s became a spectator.\n"), player_names[playernum]);
 
@@ -4360,7 +4374,7 @@ retryscramble:
 		{
 			if (repick)
 			{
-				newteam = (INT16)((M_RandomByte() % 2) + 1);
+				newteam = (INT16)((M_RandomByte() % 2) + TEAM_RED);
 				repick = false;
 			}
 			else if (i != 2) // Mystic's secret sauce - ABBA is better than ABAB, so team B doesn't get worse players all around
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 93c828fbecd1394e1adbeac6aa3fd43cc43437b6..d3a1a5f481aa39e13145ca2676612fe03d38d2ee 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -824,8 +824,8 @@ static boolean P_LookForShield(mobj_t *actor)
 			continue; // dead
 
 		//When in CTF, don't pull rings that you cannot pick up.
-		if ((actor->type == MT_REDTEAMRING && player->ctfteam != 1) ||
-			(actor->type == MT_BLUETEAMRING && player->ctfteam != 2))
+		if ((actor->type == MT_REDTEAMRING && player->ctfteam != TEAM_RED) ||
+			(actor->type == MT_BLUETEAMRING && player->ctfteam != TEAM_BLUE))
 			continue;
 
 		if ((player->powers[pw_shield] & SH_PROTECTELECTRIC)
@@ -6632,8 +6632,8 @@ void A_OldRingExplode(mobj_t *actor) {
 		{
 			if (!(gametyperules & GTR_TEAMS))
 				mo->color = actor->target->color; //copy color
-			else if (actor->target->player->ctfteam == 2)
-				mo->color = skincolor_bluering;
+			else if (actor->target->player->ctfteam == TEAM_BLUE)
+				mo->color = G_GetTeamMissileColor(TEAM_BLUE);
 		}
 	}
 
@@ -6648,8 +6648,8 @@ void A_OldRingExplode(mobj_t *actor) {
 	{
 		if (!(gametyperules & GTR_TEAMS))
 			mo->color = actor->target->color; //copy color
-		else if (actor->target->player->ctfteam == 2)
-			mo->color = skincolor_bluering;
+		else if (actor->target->player->ctfteam == TEAM_BLUE)
+			mo->color = G_GetTeamMissileColor(TEAM_BLUE);
 	}
 
 	mo = P_SpawnMobjFromMobj(actor, 0, 0, 0, locvar1);
@@ -6663,8 +6663,8 @@ void A_OldRingExplode(mobj_t *actor) {
 	{
 		if (!(gametyperules & GTR_TEAMS))
 			mo->color = actor->target->color; //copy color
-		else if (actor->target->player->ctfteam == 2)
-			mo->color = skincolor_bluering;
+		else if (actor->target->player->ctfteam == TEAM_BLUE)
+			mo->color = G_GetTeamMissileColor(TEAM_BLUE);
 	}
 }
 
diff --git a/src/p_inter.c b/src/p_inter.c
index c3811cbe4e827f41133f2c1693a932d33d36a48d..09ac61d0d392ef8ad6e11fdcb47dea6f16a98386 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -31,7 +31,7 @@
 #include "netcode/net_command.h"
 
 // CTF player names
-#define CTFTEAMCODE(pl) pl->ctfteam ? (pl->ctfteam == 1 ? "\x85" : "\x84") : ""
+#define CTFTEAMCODE(pl) pl->ctfteam ? GetChatColorForSkincolor(G_GetTeamColor(pl->ctfteam)) : ""
 #define CTFTEAMENDCODE(pl) pl->ctfteam ? "\x80" : ""
 
 void P_ForceFeed(const player_t *player, INT32 attack, INT32 fade, tic_t duration, INT32 period)
@@ -562,11 +562,11 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 // Rings, coins, spheres, weapon panels, etc //
 // ***************************************** //
 		case MT_REDTEAMRING:
-			if (player->ctfteam != 1)
+			if (player->ctfteam != TEAM_RED)
 				return;
 			/* FALLTHRU */
 		case MT_BLUETEAMRING: // Yes, I'm lazy. Oh well, deal with it.
-			if (special->type == MT_BLUETEAMRING && player->ctfteam != 2)
+			if (special->type == MT_BLUETEAMRING && player->ctfteam != TEAM_BLUE)
 				return;
 			/* FALLTHRU */
 		case MT_RING:
@@ -847,23 +847,16 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				return;
 //			if (special->momz > 0)
 //				return;
+			if (special->info->mass < numteams)
 			{
-				UINT8 flagteam = (special->type == MT_REDFLAG) ? 1 : 2;
-				sectorspecialflags_t specialflag = (special->type == MT_REDFLAG) ? SSF_REDTEAMBASE : SSF_BLUETEAMBASE;
+				UINT8 flagteam = special->info->mass;
+				sectorspecialflags_t specialflag = flagteam == TEAM_RED ? SSF_REDTEAMBASE : SSF_BLUETEAMBASE;
 				const char *flagtext;
-				char flagcolor;
+				const char *flagcolor;
 				char plname[MAXPLAYERNAME+4];
 
-				if (special->type == MT_REDFLAG)
-				{
-					flagtext = M_GetText("Red flag");
-					flagcolor = '\x85';
-				}
-				else
-				{
-					flagtext = M_GetText("Blue flag");
-					flagcolor = '\x84';
-				}
+				flagtext = G_GetTeamFlagName(flagteam);
+				flagcolor = GetChatColorForSkincolor(G_GetTeamColor(flagteam));
 				snprintf(plname, sizeof(plname), "%s%s%s",
 						 CTFTEAMCODE(player),
 						 player_names[player - players],
@@ -881,7 +874,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 
 						if (!P_PlayerTouchingSectorSpecialFlag(player, specialflag))
 						{
-							CONS_Printf(M_GetText("%s returned the %c%s%c to base.\n"), plname, flagcolor, flagtext, 0x80);
+							CONS_Printf(M_GetText("%s returned the %s%s%c to base.\n"), plname, flagcolor, flagtext, 0x80);
 
 							// The fuse code plays this sound effect
 							//if (players[consoleplayer].ctfteam == player->ctfteam)
@@ -891,15 +884,12 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				}
 				else if (player->ctfteam) // Player is on the other team (and not a spectator)
 				{
-					UINT16 flagflag   = (special->type == MT_REDFLAG) ? GF_REDFLAG : GF_BLUEFLAG;
-					mobj_t **flagmobj = (special->type == MT_REDFLAG) ? &redflag : &blueflag;
-
 					if (player->powers[pw_super])
 						return;
 
-					player->gotflag |= flagflag;
-					CONS_Printf(M_GetText("%s picked up the %c%s%c!\n"), plname, flagcolor, flagtext, 0x80);
-					(*flagmobj) = NULL;
+					player->gotflag |= teams[flagteam].flag;
+					CONS_Printf(M_GetText("%s picked up the %s%s%c!\n"), plname, flagcolor, flagtext, 0x80);
+					flagmobjs[flagteam] = NULL;
 					// code for dealing with abilities is handled elsewhere now
 					break;
 				}
@@ -2294,7 +2284,7 @@ void P_CheckTimeLimit(void)
 			else
 			{
 				//In team match and CTF, determining a tie is much simpler. =P
-				if (redscore == bluescore)
+				if (teamscores[TEAM_RED] == teamscores[TEAM_BLUE])
 					return;
 			}
 		}
@@ -2329,7 +2319,7 @@ void P_CheckPointLimit(void)
 	if (G_GametypeHasTeams())
 	{
 		// Just check both teams
-		if ((UINT32)cv_pointlimit.value <= redscore || (UINT32)cv_pointlimit.value <= bluescore)
+		if ((UINT32)cv_pointlimit.value <= teamscores[TEAM_RED] || (UINT32)cv_pointlimit.value <= teamscores[TEAM_BLUE])
 		{
 			if (server)
 				D_SendExitLevel(false);
@@ -3337,7 +3327,7 @@ static void P_KillPlayer(player_t *player, mobj_t *source, INT32 damage)
 		player->mo->flags2 &= ~MF2_DONTDRAW;
 
 	P_SetPlayerMobjState(player->mo, player->mo->info->deathstate);
-	if ((gametyperules & GTR_TEAMFLAGS) && (player->gotflag & (GF_REDFLAG|GF_BLUEFLAG)))
+	if ((gametyperules & GTR_TEAMFLAGS) && player->gotflag)
 	{
 		P_PlayerFlagBurst(player, false);
 		if (source && source->player)
@@ -3462,7 +3452,7 @@ static void P_ShieldDamage(player_t *player, mobj_t *inflictor, mobj_t *source,
 	else
 		S_StartSound (player->mo, sfx_shldls); // Ba-Dum! Shield loss.
 
-	if ((gametyperules & GTR_TEAMFLAGS) && (player->gotflag & (GF_REDFLAG|GF_BLUEFLAG)))
+	if ((gametyperules & GTR_TEAMFLAGS) && player->gotflag)
 	{
 		P_PlayerFlagBurst(player, false);
 		if (source && source->player)
@@ -3496,7 +3486,7 @@ static void P_RingDamage(player_t *player, mobj_t *inflictor, mobj_t *source, IN
 			P_AddPlayerScore(source->player, 50);
 	}
 
-	if ((gametyperules & GTR_TEAMFLAGS) && (player->gotflag & (GF_REDFLAG|GF_BLUEFLAG)))
+	if ((gametyperules & GTR_TEAMFLAGS) && player->gotflag)
 	{
 		P_PlayerFlagBurst(player, false);
 		if (source && source->player)
@@ -3573,7 +3563,7 @@ void P_SpecialStageDamage(player_t *player, mobj_t *inflictor, mobj_t *source)
 
 	P_DoPlayerPain(player, inflictor, source);
 
-	if ((gametyperules & GTR_TEAMFLAGS) && player->gotflag & (GF_REDFLAG|GF_BLUEFLAG))
+	if ((gametyperules & GTR_TEAMFLAGS) && player->gotflag)
 		P_PlayerFlagBurst(player, false);
 
 	if (oldnightstime > 10*TICRATE
@@ -3661,10 +3651,10 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 	if (!force)
 	{
 		// Special case for team ring boxes
-		if (target->type == MT_RING_REDBOX && !(source->player->ctfteam == 1))
+		if (target->type == MT_RING_REDBOX && !(source->player->ctfteam == TEAM_RED))
 			return false;
 
-		if (target->type == MT_RING_BLUEBOX && !(source->player->ctfteam == 2))
+		if (target->type == MT_RING_BLUEBOX && !(source->player->ctfteam == TEAM_BLUE))
 			return false;
 	}
 
@@ -4383,85 +4373,68 @@ void P_PlayerEmeraldBurst(player_t *player, boolean toss)
   */
 void P_PlayerFlagBurst(player_t *player, boolean toss)
 {
-	mobj_t *flag;
-	mobjtype_t type;
-
-	if (!(player->gotflag & (GF_REDFLAG|GF_BLUEFLAG)))
+	if (!player->gotflag)
 		return;
 
-	if (player->gotflag & GF_REDFLAG)
-		type = MT_REDFLAG;
-	else
-		type = MT_BLUEFLAG;
-
-	flag = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, type);
-
-	if (player->mo->eflags & MFE_VERTICALFLIP)
+	for (UINT8 team = TEAM_RED; team < numteams; team++)
 	{
-		flag->z += player->mo->height - flag->height;
-		flag->flags2 |= MF2_OBJECTFLIP;
-	}
+		UINT32 flagflag = 1 << (team - 1);
+		if (!(player->gotflag & flagflag))
+			continue;
 
-	if (toss)
-		P_InstaThrust(flag, player->mo->angle, FixedMul(6*FRACUNIT, player->mo->scale));
-	else
-	{
-		angle_t fa = P_RandomByte()*FINEANGLES/256;
-		flag->momx = FixedMul(FINECOSINE(fa), FixedMul(6*FRACUNIT, player->mo->scale));
-		if (!(twodlevel || (player->mo->flags2 & MF2_TWOD)))
-			flag->momy = FixedMul(FINESINE(fa), FixedMul(6*FRACUNIT, player->mo->scale));
-	}
+		mobj_t *flag = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, teams[team].flag_mobj_type);
 
-	flag->momz = FixedMul(8*FRACUNIT, player->mo->scale);
-	if (player->mo->eflags & MFE_VERTICALFLIP)
-		flag->momz = -flag->momz;
+		if (player->mo->eflags & MFE_VERTICALFLIP)
+		{
+			flag->z += player->mo->height - flag->height;
+			flag->flags2 |= MF2_OBJECTFLIP;
+		}
 
-	if (type == MT_REDFLAG)
-		flag->spawnpoint = rflagpoint;
-	else
-		flag->spawnpoint = bflagpoint;
+		if (toss)
+			P_InstaThrust(flag, player->mo->angle, FixedMul(6*FRACUNIT, player->mo->scale));
+		else
+		{
+			angle_t fa = P_RandomByte()*FINEANGLES/256;
+			flag->momx = FixedMul(FINECOSINE(fa), FixedMul(6*FRACUNIT, player->mo->scale));
+			if (!(twodlevel || (player->mo->flags2 & MF2_TWOD)))
+				flag->momy = FixedMul(FINESINE(fa), FixedMul(6*FRACUNIT, player->mo->scale));
+		}
 
-	flag->fuse = cv_flagtime.value * TICRATE;
-	P_SetTarget(&flag->target, player->mo);
+		flag->momz = FixedMul(8*FRACUNIT, player->mo->scale);
+		if (player->mo->eflags & MFE_VERTICALFLIP)
+			flag->momz = -flag->momz;
 
-	// Flag text
-	{
-		char plname[MAXPLAYERNAME+4];
-		const char *flagtext;
-		char flagcolor;
+		flag->spawnpoint = flagpoints[team];
 
-		snprintf(plname, sizeof(plname), "%s%s%s",
-				 CTFTEAMCODE(player),
-				 player_names[player - players],
-				 CTFTEAMENDCODE(player));
+		flag->fuse = cv_flagtime.value * TICRATE;
+		P_SetTarget(&flag->target, player->mo);
 
-		if (type == MT_REDFLAG)
-		{
-			flagtext = M_GetText("Red flag");
-			flagcolor = '\x85';
-		}
-		else
+		// Flag text
 		{
-			flagtext = M_GetText("Blue flag");
-			flagcolor = '\x84';
+			char plname[MAXPLAYERNAME+4];
+			const char *flagtext;
+			const char *flagcolor;
+
+			snprintf(plname, sizeof(plname), "%s%s%s",
+					 CTFTEAMCODE(player),
+					 player_names[player - players],
+					 CTFTEAMENDCODE(player));
+
+			flagtext = G_GetTeamFlagName(team);
+			flagcolor = GetChatColorForSkincolor(G_GetTeamColor(team));
+
+			if (toss)
+				CONS_Printf(M_GetText("%s tossed the %s%s%c.\n"), plname, flagcolor, flagtext, 0x80);
+			else
+				CONS_Printf(M_GetText("%s dropped the %s%s%c.\n"), plname, flagcolor, flagtext, 0x80);
 		}
 
-		if (toss)
-			CONS_Printf(M_GetText("%s tossed the %c%s%c.\n"), plname, flagcolor, flagtext, 0x80);
-		else
-			CONS_Printf(M_GetText("%s dropped the %c%s%c.\n"), plname, flagcolor, flagtext, 0x80);
+		// Pointers set for displaying time value and for consistency restoration.
+		flagmobjs[team] = flag;
 	}
 
 	player->gotflag = 0;
 
-	// Pointers set for displaying time value and for consistency restoration.
-	if (type == MT_REDFLAG)
-		redflag = flag;
-	else
-		blueflag = flag;
-
 	if (toss)
 		player->tossdelay = 2*TICRATE;
-
-	return;
 }
diff --git a/src/p_map.c b/src/p_map.c
index 56a096ebb5ce7802e04583d09080447ec1faba76..528af655ba3c83fb52cf572c26a17f6175338d2e 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -1691,7 +1691,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		}
 		// Monitor?
 		else if (thing->flags & MF_MONITOR
-		&& !((thing->type == MT_RING_REDBOX && tmthing->player->ctfteam != 1) || (thing->type == MT_RING_BLUEBOX && tmthing->player->ctfteam != 2))
+		&& !((thing->type == MT_RING_REDBOX && tmthing->player->ctfteam != TEAM_RED) || (thing->type == MT_RING_BLUEBOX && tmthing->player->ctfteam != TEAM_BLUE))
 		&& (!(thing->flags & MF_SOLID) || P_PlayerCanDamage(tmthing->player, thing)))
 		{
 			if (thing->z - thing->scale <= tmthing->z + tmthing->height
diff --git a/src/p_mobj.c b/src/p_mobj.c
index eb5c7a0af1e8324c3b192e0dc00f8f0fb875c892..105e2e33b830ac943ebad22510ef0e7de4980d87 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -10026,31 +10026,19 @@ static void P_FlagFuseThink(mobj_t *mobj)
 		flagmo->flags2 |= MF2_OBJECTFLIP;
 	}
 
-	if (mobj->type == MT_REDFLAG)
+	UINT8 team = mobj->info->mass;
+	if (team < numteams)
 	{
 		if (!(mobj->flags2 & MF2_JUSTATTACKED))
-			CONS_Printf(M_GetText("The \205Red flag\200 has returned to base.\n"));
+			CONS_Printf(M_GetText("The %s%s\200 has returned to base.\n"), GetChatColorForSkincolor(G_GetTeamColor(team)), G_GetTeamFlagName(team));
 
 		// Assumedly in splitscreen players will be on opposing teams
-		if (players[consoleplayer].ctfteam == 1 || splitscreen)
+		if (players[consoleplayer].ctfteam == team || splitscreen)
 			S_StartSound(NULL, sfx_hoop1);
-		else if (players[consoleplayer].ctfteam == 2)
+		else if (players[consoleplayer].ctfteam != 0)
 			S_StartSound(NULL, sfx_hoop3);
 
-		redflag = flagmo;
-	}
-	else // MT_BLUEFLAG
-	{
-		if (!(mobj->flags2 & MF2_JUSTATTACKED))
-			CONS_Printf(M_GetText("The \204Blue flag\200 has returned to base.\n"));
-
-		// Assumedly in splitscreen players will be on opposing teams
-		if (players[consoleplayer].ctfteam == 2 || splitscreen)
-			S_StartSound(NULL, sfx_hoop1);
-		else if (players[consoleplayer].ctfteam == 1)
-			S_StartSound(NULL, sfx_hoop3);
-
-		blueflag = flagmo;
+		flagmobjs[team] = flagmo;
 	}
 }
 
@@ -10906,7 +10894,7 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 			}
 			break;
 		case MT_REDRING: // Make MT_REDRING red by default
-			mobj->color = skincolor_redring;
+			mobj->color = G_GetTeamMissileColor(TEAM_RED);
 			break;
 		case MT_SMALLBUBBLE: // Bubbles eventually dissipate, in case they get caught somewhere.
 		case MT_MEDIUMBUBBLE:
@@ -10923,10 +10911,10 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 			 mobj->lastlook = mobj->extravalue2 = -1;
 			break;
 		case MT_REDTEAMRING:
-			mobj->color = skincolor_redteam;
+			mobj->color = G_GetTeamWeaponColor(TEAM_RED);
 			break;
 		case MT_BLUETEAMRING:
-			mobj->color = skincolor_blueteam;
+			mobj->color = G_GetTeamWeaponColor(TEAM_BLUE);
 			break;
 		case MT_RING:
 		case MT_COIN:
@@ -11586,14 +11574,17 @@ void P_SpawnPlayer(INT32 playernum)
 				UINT16 usvalue;
 				NetPacket.value.l = NetPacket.value.b = 0;
 
+				UINT8 newteam = (playernum % (MAXTEAMS - 1)) + 1;
+
 				// Spawn as a spectator,
 				// yes even in splitscreen mode
 				p->spectator = true;
+				p->skincolor = teams[newteam].color;
 
 				// but immediately send a team change packet.
 				NetPacket.packet.playernum = playernum;
 				NetPacket.packet.verification = true;
-				NetPacket.packet.newteam = !(playernum&1) + 1;
+				NetPacket.packet.newteam = newteam;
 
 				usvalue = SHORT(NetPacket.value.l|NetPacket.value.b);
 				SendNetXCmd(XD_TEAMCHANGE, &usvalue, sizeof(usvalue));
@@ -11603,16 +11594,12 @@ void P_SpawnPlayer(INT32 playernum)
 		}
 	}
 
-	if (G_GametypeHasTeams())
+	if (G_GametypeHasTeams() && !p->spectator && !p->ctfteam)
 	{
-		// Fix stupid non spectator spectators.
-		if (!p->spectator && !p->ctfteam)
-		{
-			if (G_GametypeHasSpectators())
-				p->spectator = true;
-			else
-				p->ctfteam = 1;
-		}
+		if (G_GametypeHasSpectators())
+			p->spectator = true;
+		else
+			p->ctfteam = 1;
 	}
 
 	if ((netgame || multiplayer) && ((gametyperules & GTR_SPAWNINVUL) || leveltime) && !p->spectator && !(maptol & TOL_NIGHTS))
@@ -12094,9 +12081,12 @@ static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i)
 		if (i == MT_BLUEFLAG || i == MT_REDFLAG)
 			return false; // No flags in non-CTF modes!
 	}
-	else
+	else if (i == MT_BLUEFLAG || i == MT_REDFLAG)
 	{
-		if ((i == MT_BLUEFLAG && blueflag) || (i == MT_REDFLAG && redflag))
+		UINT8 team = mobjinfo[i].mass;
+		if (team >= numteams)
+			return false;
+		else if (flagmobjs[team])
 		{
 			CONS_Alert(CONS_ERROR, M_GetText("Only one flag per team allowed in CTF!\n"));
 			return false;
@@ -13198,12 +13188,12 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 		}
 		break;
 	case MT_REDFLAG:
-		redflag = mobj;
-		rflagpoint = mobj->spawnpoint;
+		flagmobjs[TEAM_RED] = mobj;
+		flagpoints[TEAM_RED] = mobj->spawnpoint;
 		break;
 	case MT_BLUEFLAG:
-		blueflag = mobj;
-		bflagpoint = mobj->spawnpoint;
+		flagmobjs[TEAM_BLUE] = mobj;
+		flagpoints[TEAM_BLUE] = mobj->spawnpoint;
 		break;
 	case MT_NIGHTSSTAR:
 		if (maptol & TOL_XMAS)
@@ -14054,10 +14044,8 @@ void P_ColorTeamMissile(mobj_t *missile, player_t *source)
 {
 	if (G_GametypeHasTeams())
 	{
-		if (source->ctfteam == 2)
-			missile->color = skincolor_bluering;
-		else if (source->ctfteam == 1)
-			missile->color = skincolor_redring;
+		if (source->ctfteam != 0)
+			missile->color = G_GetTeamMissileColor(source->ctfteam);
 	}
 	/*
 	else
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 4aa2318fd2902d172c7a35a82a1c9d54dcf113ad..f2b32f299267f2d7d445b3ec0e60589008e007a3 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -1756,9 +1756,9 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 	if (mobj->scalespeed != FRACUNIT/12)
 		diff2 |= MD2_SCALESPEED;
 
-	if (mobj == redflag)
+	if (mobj == flagmobjs[TEAM_RED])
 		diff |= MD_REDFLAG;
-	if (mobj == blueflag)
+	if (mobj == flagmobjs[TEAM_BLUE])
 		diff |= MD_BLUEFLAG;
 
 	if (mobj->cusval)
@@ -3065,13 +3065,13 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker)
 
 	if (diff & MD_REDFLAG)
 	{
-		redflag = mobj;
-		rflagpoint = mobj->spawnpoint;
+		flagmobjs[TEAM_RED] = mobj;
+		flagpoints[TEAM_RED] = mobj->spawnpoint;
 	}
 	if (diff & MD_BLUEFLAG)
 	{
-		blueflag = mobj;
-		bflagpoint = mobj->spawnpoint;
+		flagmobjs[TEAM_BLUE] = mobj;
+		flagpoints[TEAM_BLUE] = mobj->spawnpoint;
 	}
 
 	// set sprev, snext, bprev, bnext, subsector
@@ -4337,13 +4337,15 @@ static void P_NetArchiveMisc(boolean resending)
 
 	WRITEUINT32(save_p, token);
 	WRITEINT32(save_p, sstimer);
-	WRITEUINT32(save_p, bluescore);
-	WRITEUINT32(save_p, redscore);
 
-	WRITEUINT16(save_p, skincolor_redteam);
-	WRITEUINT16(save_p, skincolor_blueteam);
-	WRITEUINT16(save_p, skincolor_redring);
-	WRITEUINT16(save_p, skincolor_bluering);
+	WRITEUINT8(save_p, teamsingame);
+	for (i = 0; i < MAXTEAMS; i++)
+	{
+		WRITEUINT32(save_p, teamscores[i]);
+		WRITEUINT16(save_p, teams[i].color);
+		WRITEUINT16(save_p, teams[i].weapon_color);
+		WRITEUINT16(save_p, teams[i].missile_color);
+	}
 
 	WRITEINT32(save_p, modulothing);
 
@@ -4435,13 +4437,15 @@ static inline boolean P_NetUnArchiveMisc(boolean reloading)
 
 	token = READUINT32(save_p);
 	sstimer = READINT32(save_p);
-	bluescore = READUINT32(save_p);
-	redscore = READUINT32(save_p);
 
-	skincolor_redteam = READUINT16(save_p);
-	skincolor_blueteam = READUINT16(save_p);
-	skincolor_redring = READUINT16(save_p);
-	skincolor_bluering = READUINT16(save_p);
+	teamsingame = READUINT8(save_p);
+	for (i = 0; i < MAXTEAMS; i++)
+	{
+		teamscores[i] = READUINT32(save_p);
+		teams[i].color = READUINT16(save_p);
+		teams[i].weapon_color = READUINT16(save_p);
+		teams[i].missile_color = READUINT16(save_p);
+	}
 
 	modulothing = READINT32(save_p);
 
diff --git a/src/p_setup.c b/src/p_setup.c
index 51ff9382c97eda770aa916a7f7925fd061048d0d..7096d7c2797c9ba208eb4a354f4754d48dd1b0c6 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -7058,8 +7058,11 @@ static void P_InitLevelSettings(void)
 	countdowntimeup = false;
 
 	// clear ctf pointers
-	redflag = blueflag = NULL;
-	rflagpoint = bflagpoint = NULL;
+	for (i = 0; i < MAXTEAMS; i++)
+	{
+		flagmobjs[i] = NULL;
+		flagpoints[i] = NULL;
+	}
 
 	// circuit, race and competition stuff
 	circuitmap = false;
diff --git a/src/p_spec.c b/src/p_spec.c
index aa4ee37cf76f22eedcaed4367635ce654dca51bc..a151bab346ff2da1fc85a858eca9388a684f377f 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -1783,7 +1783,7 @@ void P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller)
 			// Only red/blue team members can activate this.
 			if (!(actor && actor->player))
 				return;
-			if (actor->player->ctfteam != ((triggerline->args[1] == TMT_RED) ? 1 : 2))
+			if (actor->player->ctfteam != ((triggerline->args[1] == TMT_RED) ? TEAM_RED : TEAM_BLUE))
 				return;
 			break;
 		case 314:
@@ -4634,7 +4634,7 @@ static void P_ProcessExitSector(player_t *player, mtag_t sectag)
 		skipstats = 1;
 }
 
-static void P_ProcessTeamBase(player_t *player, boolean redteam)
+static void P_ProcessTeamBase(player_t *player, UINT8 team)
 {
 	mobj_t *mo;
 
@@ -4644,36 +4644,36 @@ static void P_ProcessTeamBase(player_t *player, boolean redteam)
 	if (!P_IsObjectOnGround(player->mo))
 		return;
 
-	if (player->ctfteam != (redteam ? 1 : 2))
+	if (player->ctfteam != team)
 		return;
 
-	if (!(player->gotflag & (redteam ? GF_BLUEFLAG : GF_REDFLAG)))
+	UINT8 otherteam = team == TEAM_RED ? TEAM_BLUE : TEAM_RED;
+	UINT32 teamflag = teams[otherteam].flag;
+
+	if (!(player->gotflag & teamflag))
 		return;
 
 	// Make sure the team still has their own
 	// flag at their base so they can score.
-	if (!P_IsFlagAtBase(redteam ? MT_REDFLAG : MT_BLUEFLAG))
+	if (!P_IsFlagAtBase(teams[team].flag_mobj_type))
 		return;
 
 	HU_SetCEchoFlags(V_AUTOFADEOUT|V_ALLOWLOWERCASE);
 	HU_SetCEchoDuration(5);
-	HU_DoCEcho(va(M_GetText("%s%s\200\\CAPTURED THE %s%s FLAG\200.\\\\\\\\"), redteam ? "\205" : "\204", player_names[player-players], redteam ? "\204" : "\205", redteam ? "BLUE" : "RED"));
+	HU_DoCEcho(va(M_GetText("%s%s\200\\CAPTURED THE %s%s FLAG\200.\\\\\\\\"), GetChatColorForSkincolor(G_GetTeamColor(team)), player_names[player-players], GetChatColorForSkincolor(teams[otherteam].color), G_GetTeamName(otherteam)));
 
-	if (splitscreen || players[consoleplayer].ctfteam == (redteam ? 1 : 2))
+	if (splitscreen || players[consoleplayer].ctfteam == team)
 		S_StartSound(NULL, sfx_flgcap);
-	else if (players[consoleplayer].ctfteam == (redteam ? 2 : 1))
+	else if (players[consoleplayer].ctfteam != team)
 		S_StartSound(NULL, sfx_lose);
 
-	mo = P_SpawnMobj(player->mo->x,player->mo->y,player->mo->z, redteam ? MT_BLUEFLAG : MT_REDFLAG);
-	player->gotflag &= ~(redteam ? GF_BLUEFLAG : GF_REDFLAG);
+	mo = P_SpawnMobj(player->mo->x,player->mo->y,player->mo->z, teams[otherteam].flag_mobj_type);
+	player->gotflag &= ~teamflag;
 	mo->flags &= ~MF_SPECIAL;
 	mo->fuse = TICRATE;
-	mo->spawnpoint = redteam ? bflagpoint : rflagpoint;
+	mo->spawnpoint = flagpoints[team];
 	mo->flags2 |= MF2_JUSTATTACKED;
-	if (redteam)
-		redscore += 1;
-	else
-		bluescore += 1;
+	teamscores[team]++;
 	P_AddPlayerScore(player, 250);
 }
 
@@ -4996,9 +4996,9 @@ static void P_EvaluateSpecialFlags(player_t *player, sector_t *sector, sector_t
 	if ((sector->specialflags & SSF_SPECIALSTAGEPIT) && isTouching)
 		P_ProcessSpecialStagePit(player);
 	if ((sector->specialflags & SSF_REDTEAMBASE) && isTouching)
-		P_ProcessTeamBase(player, true);
+		P_ProcessTeamBase(player, TEAM_RED);
 	if ((sector->specialflags & SSF_BLUETEAMBASE) && isTouching)
-		P_ProcessTeamBase(player, false);
+		P_ProcessTeamBase(player, TEAM_BLUE);
 	if (sector->specialflags & SSF_FAN)
 	{
 		player->mo->momz += mobjinfo[MT_FAN].mass/4;
diff --git a/src/p_tick.c b/src/p_tick.c
index 1bc7b78bf1a39499576794b8e80d7181f58cd43c..a462c6dd48680acad8171bbfc817e81584d561ed 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -464,11 +464,11 @@ static void P_DoAutobalanceTeams(void)
 	{
 		if (playeringame[i] && players[i].ctfteam)
 		{
-			if (players[i].ctfteam == 1)
+			if (players[i].ctfteam == TEAM_RED)
 			{
 				if (!players[i].gotflag)
 				{
-					redarray[red] = i; //store the player's node.
+					redarray[red] = i; //store the player's number.
 					red++;
 				}
 				else
@@ -478,7 +478,7 @@ static void P_DoAutobalanceTeams(void)
 			{
 				if (!players[i].gotflag)
 				{
-					bluearray[blue] = i; //store the player's node.
+					bluearray[blue] = i; //store the player's number.
 					blue++;
 				}
 				else
@@ -495,7 +495,7 @@ static void P_DoAutobalanceTeams(void)
 		if (totalred > totalblue)
 		{
 			i = M_RandomKey(red);
-			NetPacket.packet.newteam = 2;
+			NetPacket.packet.newteam = TEAM_BLUE;
 			NetPacket.packet.playernum = redarray[i];
 			NetPacket.packet.verification = true;
 			NetPacket.packet.autobalance = true;
@@ -503,10 +503,10 @@ static void P_DoAutobalanceTeams(void)
 			usvalue  = SHORT(NetPacket.value.l|NetPacket.value.b);
 			SendNetXCmd(XD_TEAMCHANGE, &usvalue, sizeof(usvalue));
 		}
-		else //if (totalblue > totalred)
+		else
 		{
 			i = M_RandomKey(blue);
-			NetPacket.packet.newteam = 1;
+			NetPacket.packet.newteam = TEAM_RED;
 			NetPacket.packet.playernum = bluearray[i];
 			NetPacket.packet.verification = true;
 			NetPacket.packet.autobalance = true;
diff --git a/src/p_user.c b/src/p_user.c
index c0e87bf73f8b9888420b22150e9a49e4c00af750..4757714002f72b0c0fd08c1c21ef0e387b0c4b42 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -1452,23 +1452,20 @@ void P_AddPlayerScore(player_t *player, UINT32 amount)
 	// In team match, all awarded points are incremented to the team's running score.
 	if ((gametyperules & (GTR_TEAMS|GTR_TEAMFLAGS)) == GTR_TEAMS)
 	{
-		if (player->ctfteam == 1)
-			redscore += amount;
-		else if (player->ctfteam == 2)
-			bluescore += amount;
+		teamscores[player->ctfteam] += amount;
 	}
 }
 
 // Steals from every enemy's score.
 void P_StealPlayerScore(player_t *player, UINT32 amount)
 {
-	boolean teams = G_GametypeHasTeams();
+	boolean hasTeams = G_GametypeHasTeams();
 	UINT32 stolen = 0;
 	int i;
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
 		if (&players[i] == player
-		|| (teams && players[i].ctfteam == player->ctfteam))
+		|| (hasTeams && players[i].ctfteam == player->ctfteam))
 			continue;
 		if (players[i].score >= amount)
 		{
@@ -1486,10 +1483,7 @@ void P_StealPlayerScore(player_t *player, UINT32 amount)
 		// In team match, all stolen points are removed from the enemy team's running score.
 		if ((gametyperules & (GTR_TEAMS|GTR_TEAMFLAGS)) == GTR_TEAMS)
 		{
-			if (player->ctfteam == 1)
-				bluescore -= amount;
-			else if (player->ctfteam == 2)
-				redscore -= amount;
+			teamscores[player->ctfteam] -= amount;
 		}
 		P_AddPlayerScore(player, stolen);
 	}
@@ -3158,7 +3152,7 @@ static void P_DoPlayerHeadSigns(player_t *player)
 			}
 		}
 	}
-	else if ((gametyperules & GTR_TEAMFLAGS) && (player->gotflag & (GF_REDFLAG|GF_BLUEFLAG))) // If you have the flag (duh).
+	else if ((gametyperules & GTR_TEAMFLAGS) && player->gotflag) // If you have the flag (duh).
 	{
 		// Spawn a got-flag message over the head of the player that has it
 		// (but not on your own screen if you have the flag, unless you're spectating).
@@ -10526,29 +10520,26 @@ boolean P_SpectatorJoinGame(player_t *player)
 	else if (G_GametypeHasTeams())
 	{
 		INT32 changeto = 0;
-		INT32 z, numplayersred = 0, numplayersblue = 0;
+		INT32 z, numplayers[MAXTEAMS];
 
 		//find a team by num players, score, or random if all else fails.
 		for (z = 0; z < MAXPLAYERS; ++z)
 			if (playeringame[z])
 			{
-				if (players[z].ctfteam == 1)
-					++numplayersred;
-				else if (players[z].ctfteam == 2)
-					++numplayersblue;
+				numplayers[players[z].ctfteam]++;
 			}
 		// for z
 
-		if (numplayersblue > numplayersred)
-			changeto = 1;
-		else if (numplayersred > numplayersblue)
-			changeto = 2;
-		else if (bluescore > redscore)
-			changeto = 1;
-		else if (redscore > bluescore)
-			changeto = 2;
+		if (numplayers[TEAM_BLUE] > numplayers[TEAM_RED])
+			changeto = TEAM_RED;
+		else if (numplayers[TEAM_RED] > numplayers[TEAM_BLUE])
+			changeto = TEAM_BLUE;
+		else if (teamscores[TEAM_BLUE] > teamscores[TEAM_RED])
+			changeto = TEAM_RED;
+		else if (teamscores[TEAM_RED] > teamscores[TEAM_BLUE])
+			changeto = TEAM_BLUE;
 		else
-			changeto = (P_RandomFixed() & 1) + 1;
+			changeto = (P_RandomFixed() & 1) + TEAM_RED;
 
 		if (!LUA_HookTeamSwitch(player, changeto, true, false, false))
 			return false;
@@ -10571,10 +10562,8 @@ boolean P_SpectatorJoinGame(player_t *player)
 			displayplayer = consoleplayer;
 		}
 
-		if (changeto == 1)
-			CONS_Printf(M_GetText("%s switched to the %c%s%c.\n"), player_names[player-players], '\x85', M_GetText("Red team"), '\x80');
-		else if (changeto == 2)
-			CONS_Printf(M_GetText("%s switched to the %c%s%c.\n"), player_names[player-players], '\x84', M_GetText("Blue team"), '\x80');
+		if (changeto != 0)
+			CONS_Printf(M_GetText("%s switched to %s%s%c.\n"), player_names[player-players], GetChatColorForSkincolor(G_GetTeamColor(changeto)), G_GetTeamName(changeto), '\x80');;
 
 		return true; // no more player->mo, cannot continue.
 	}
@@ -13127,12 +13116,7 @@ boolean P_PlayerShouldUseSpinHeight(player_t *player)
 UINT16 P_GetPlayerColor(player_t *player)
 {
 	if (G_GametypeHasTeams() && player->ctfteam)
-	{
-		if (player->ctfteam == 1)
-			return skincolor_redteam;
-		else if (player->ctfteam == 2)
-			return skincolor_blueteam;
-	}
+		return G_GetTeamColor(player->ctfteam);
 
 	return player->skincolor;
 }
diff --git a/src/st_stuff.c b/src/st_stuff.c
index a975379785570a6dca859031932c0f749c75dc70..88f27d476572b6dc529d68abb86cfc434b962905 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -891,13 +891,9 @@ static void ST_drawLivesArea(void)
 		}
 		else if (G_GametypeHasTeams())
 		{
-			if (stplyr->ctfteam == 1)
+			if (stplyr->ctfteam != 0)
 			{
-				v_colmap = V_REDMAP;
-			}
-			else if (stplyr->ctfteam == 2)
-			{
-				v_colmap = V_BLUEMAP;
+				v_colmap = skincolors[G_GetTeamColor(stplyr->ctfteam)].chatcolor;
 			}
 		}
 
@@ -982,13 +978,9 @@ static void ST_drawLivesArea(void)
 			}
 			else if (G_GametypeHasTeams())
 			{
-				if (stplyr->ctfteam == 1)
-				{
-					V_DrawRightAlignedString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y+8, V_HUDTRANS|hudinfo[HUD_LIVES].f|V_PERPLAYER, "RED");
-				}
-				else if (stplyr->ctfteam == 2)
+				if (stplyr->ctfteam != 0)
 				{
-					V_DrawRightAlignedString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y+8, V_HUDTRANS|hudinfo[HUD_LIVES].f|V_PERPLAYER, "BLUE");
+					V_DrawRightAlignedString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y+8, V_HUDTRANS|hudinfo[HUD_LIVES].f|V_PERPLAYER, G_GetTeamName(stplyr->ctfteam));
 				}
 			}
 		}
@@ -2467,20 +2459,20 @@ static void ST_drawTeamHUD(void)
 
 		// Display a countdown timer showing how much time left until the flag returns to base.
 		{
-			if (blueflag && blueflag->fuse > 1 && LUA_HudEnabled(hud_teamscores))
-				V_DrawCenteredString(BASEVIDWIDTH/2 - SEP, 8, V_YELLOWMAP|V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, va("%u", (blueflag->fuse / TICRATE)));
+			if (flagmobjs[TEAM_BLUE] && flagmobjs[TEAM_BLUE]->fuse > 1 && LUA_HudEnabled(hud_teamscores))
+				V_DrawCenteredString(BASEVIDWIDTH/2 - SEP, 8, V_YELLOWMAP|V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, va("%u", (flagmobjs[TEAM_BLUE]->fuse / TICRATE)));
 
-			if (redflag && redflag->fuse > 1 && LUA_HudEnabled(hud_teamscores))
-				V_DrawCenteredString(BASEVIDWIDTH/2 + SEP, 8, V_YELLOWMAP|V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, va("%u", (redflag->fuse / TICRATE)));
+			if (flagmobjs[TEAM_RED] && flagmobjs[TEAM_RED]->fuse > 1 && LUA_HudEnabled(hud_teamscores))
+				V_DrawCenteredString(BASEVIDWIDTH/2 + SEP, 8, V_YELLOWMAP|V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, va("%u", (flagmobjs[TEAM_RED]->fuse / TICRATE)));
 		}
 	}
 
 num:
 	if (LUA_HudEnabled(hud_teamscores))
-		V_DrawCenteredString(BASEVIDWIDTH/2 - SEP, 16, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, va("%u", bluescore));
+		V_DrawCenteredString(BASEVIDWIDTH/2 - SEP, 16, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, va("%u", teamscores[TEAM_BLUE]));
 
 	if (LUA_HudEnabled(hud_teamscores))
-		V_DrawCenteredString(BASEVIDWIDTH/2 + SEP, 16, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, va("%u", redscore));
+		V_DrawCenteredString(BASEVIDWIDTH/2 + SEP, 16, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, va("%u", teamscores[TEAM_RED]));
 
 #undef SEP
 }
@@ -2840,7 +2832,7 @@ void ST_Drawer(void)
 				break;
 			case 2: // Team
 				if (G_GametypeHasTeams())
-					c = (seenplayer->ctfteam == 1) ? V_REDMAP : V_BLUEMAP;
+					c = skincolors[G_GetTeamColor(seenplayer->ctfteam)].chatcolor;
 				break;
 			case 3: // Ally/Foe
 			default:
diff --git a/src/y_inter.c b/src/y_inter.c
index 6bbca09cc2ca364b5be528e62ec5ec30ae63390f..444579bd926868913567d8a9586f7428fa1b871c 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -820,10 +820,10 @@ void Y_IntermissionDrawer(void)
 
 		// Show the team flags and the team score at the top instead of "RESULTS"
 		V_DrawSmallScaledPatch(128 - (data.match.blueflag->width / 4), 2, 0, data.match.blueflag);
-		V_DrawCenteredString(128, 16, 0, va("%u", bluescore));
+		V_DrawCenteredString(128, 16, 0, va("%u", teamscores[TEAM_BLUE]));
 
 		V_DrawSmallScaledPatch(192 - (data.match.redflag->width / 4), 2, 0, data.match.redflag);
-		V_DrawCenteredString(192, 16, 0, va("%u", redscore));
+		V_DrawCenteredString(192, 16, 0, va("%u", teamscores[TEAM_RED]));
 
 		// draw the level name
 		V_DrawCenteredString(BASEVIDWIDTH/2, 24, 0, data.match.levelstring);