From 4d9cec285e6cf3c098943cb52d88c20f3c028acf Mon Sep 17 00:00:00 2001
From: "Dr. Jaska" <drjaska83@gmail.com>
Date: Wed, 10 Jul 2024 18:27:36 +0000
Subject: [PATCH] Add support for up to 72 gametypes

---
 .../gamemodes/gamemode/assault/assault.qh     |  2 +-
 qcsrc/common/gamemodes/gamemode/ctf/ctf.qh    |  2 +-
 qcsrc/common/gamemodes/gamemode/cts/cts.qh    |  2 +-
 .../gamemode/domination/domination.qh         |  2 +-
 qcsrc/common/gamemodes/gamemode/duel/duel.qh  |  2 +-
 .../gamemodes/gamemode/invasion/invasion.qh   |  2 +-
 .../gamemodes/gamemode/mayhem/mayhem.qh       |  4 +-
 .../gamemodes/gamemode/nexball/nexball.qh     |  2 +-
 .../gamemodes/gamemode/onslaught/onslaught.qh |  2 +-
 qcsrc/common/gamemodes/gamemode/race/race.qh  |  2 +-
 .../gamemodes/gamemode/survival/survival.qh   |  2 +-
 qcsrc/common/gamemodes/gamemode/tdm/tdm.qh    |  2 +-
 qcsrc/common/gamemodes/gamemode/tka/tka.qh    |  4 +-
 .../gamemodes/gamemode/tmayhem/tmayhem.qh     |  4 +-
 qcsrc/common/mapinfo.qc                       | 85 +++++++++++--------
 qcsrc/common/mapinfo.qh                       | 24 ++++--
 .../dialog_multiplayer_create_mapinfo.qc      |  2 +-
 qcsrc/server/mapvoting.qc                     | 15 ++--
 18 files changed, 93 insertions(+), 67 deletions(-)

diff --git a/qcsrc/common/gamemodes/gamemode/assault/assault.qh b/qcsrc/common/gamemodes/gamemode/assault/assault.qh
index f4f4b3f92..c07002d8f 100644
--- a/qcsrc/common/gamemodes/gamemode/assault/assault.qh
+++ b/qcsrc/common/gamemodes/gamemode/assault/assault.qh
@@ -10,7 +10,7 @@ CLASS(Assault, Gametype)
     METHOD(Assault, m_generate_mapinfo, void(Gametype this, string v))
     {
         if(v == "target_assault_roundend")
-            MapInfo_Map_supportedGametypes |= this.m_flags;
+            MapInfo_Map_supportedGametypes |= this.gametype_flags;
     }
     METHOD(Assault, m_isTwoBaseMode, bool())
     {
diff --git a/qcsrc/common/gamemodes/gamemode/ctf/ctf.qh b/qcsrc/common/gamemodes/gamemode/ctf/ctf.qh
index 5d74f31cc..c5d15fec9 100644
--- a/qcsrc/common/gamemodes/gamemode/ctf/ctf.qh
+++ b/qcsrc/common/gamemodes/gamemode/ctf/ctf.qh
@@ -14,7 +14,7 @@ CLASS(CaptureTheFlag, Gametype)
     METHOD(CaptureTheFlag, m_generate_mapinfo, void(Gametype this, string v))
     {
         if(v == "item_flag_team2" || v == "team_CTF_blueflag")
-            MapInfo_Map_supportedGametypes |= this.m_flags;
+            MapInfo_Map_supportedGametypes |= this.gametype_flags;
     }
     METHOD(CaptureTheFlag, m_isTwoBaseMode, bool())
     {
diff --git a/qcsrc/common/gamemodes/gamemode/cts/cts.qh b/qcsrc/common/gamemodes/gamemode/cts/cts.qh
index c59b73c36..c677ff602 100644
--- a/qcsrc/common/gamemodes/gamemode/cts/cts.qh
+++ b/qcsrc/common/gamemodes/gamemode/cts/cts.qh
@@ -13,7 +13,7 @@ CLASS(RaceCTS, Gametype)
     METHOD(RaceCTS, m_generate_mapinfo, void(Gametype this, string v))
     {
         if(v == "target_startTimer")
-            MapInfo_Map_supportedGametypes |= this.m_flags;
+            MapInfo_Map_supportedGametypes |= this.gametype_flags;
     }
     METHOD(RaceCTS, m_setTeams, void(string sa))
     {
diff --git a/qcsrc/common/gamemodes/gamemode/domination/domination.qh b/qcsrc/common/gamemodes/gamemode/domination/domination.qh
index 494737c36..3117c2980 100644
--- a/qcsrc/common/gamemodes/gamemode/domination/domination.qh
+++ b/qcsrc/common/gamemodes/gamemode/domination/domination.qh
@@ -27,7 +27,7 @@ CLASS(Domination, Gametype)
     METHOD(Domination, m_generate_mapinfo, void(Gametype this, string v))
     {
         if(v == "dom_controlpoint" || v == "team_dom_point")
-            MapInfo_Map_supportedGametypes |= this.m_flags;
+            MapInfo_Map_supportedGametypes |= this.gametype_flags;
     }
     METHOD(Domination, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns))
     {
diff --git a/qcsrc/common/gamemodes/gamemode/duel/duel.qh b/qcsrc/common/gamemodes/gamemode/duel/duel.qh
index ede5f9b42..316b6c341 100644
--- a/qcsrc/common/gamemodes/gamemode/duel/duel.qh
+++ b/qcsrc/common/gamemodes/gamemode/duel/duel.qh
@@ -18,7 +18,7 @@ CLASS(Duel, Gametype)
         {
             // if this is set, all DM maps support duel too
             // TODO: we should really check the size of maps, some DM maps do not work for duel!
-            if(!(MapInfo_Map_supportedGametypes & this.m_flags) && (MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DEATHMATCH.m_flags))
+            if(!(MapInfo_Map_supportedGametypes & this.gametype_flags) && (MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DEATHMATCH.gametype_flags))
                 return true; // TODO: references another gametype (alternatively, we could check which gamemodes are always enabled and append this if any are supported)
         }
         return false;
diff --git a/qcsrc/common/gamemodes/gamemode/invasion/invasion.qh b/qcsrc/common/gamemodes/gamemode/invasion/invasion.qh
index 7ac091d5c..71f6b7d14 100644
--- a/qcsrc/common/gamemodes/gamemode/invasion/invasion.qh
+++ b/qcsrc/common/gamemodes/gamemode/invasion/invasion.qh
@@ -19,7 +19,7 @@ CLASS(Invasion, Gametype)
     METHOD(Invasion, m_generate_mapinfo, void(Gametype this, string v))
     {
         if(v == "invasion_spawnpoint")
-            MapInfo_Map_supportedGametypes |= this.m_flags;
+            MapInfo_Map_supportedGametypes |= this.gametype_flags;
     }
     METHOD(Invasion, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns))
     {
diff --git a/qcsrc/common/gamemodes/gamemode/mayhem/mayhem.qh b/qcsrc/common/gamemodes/gamemode/mayhem/mayhem.qh
index 930e3067b..68237e465 100644
--- a/qcsrc/common/gamemodes/gamemode/mayhem/mayhem.qh
+++ b/qcsrc/common/gamemodes/gamemode/mayhem/mayhem.qh
@@ -15,11 +15,11 @@ CLASS(mayhem, Gametype)
 	}
 	METHOD(mayhem, m_isForcedSupported, bool(Gametype this))
 	{
-		if(!(MapInfo_Map_supportedGametypes & this.m_flags) && (MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DEATHMATCH.m_flags))
+		if(!(MapInfo_Map_supportedGametypes & this.gametype_flags) && (MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DEATHMATCH.gametype_flags))
 		{
 			return true;
 		}
-		if(!(MapInfo_Map_supportedGametypes & this.m_flags) && (MapInfo_Map_supportedGametypes & MAPINFO_TYPE_TEAM_DEATHMATCH.m_flags))
+		if(!(MapInfo_Map_supportedGametypes & this.gametype_flags) && (MapInfo_Map_supportedGametypes & MAPINFO_TYPE_TEAM_DEATHMATCH.gametype_flags))
 		{
 			return true;
 		}
diff --git a/qcsrc/common/gamemodes/gamemode/nexball/nexball.qh b/qcsrc/common/gamemodes/gamemode/nexball/nexball.qh
index be11699dd..845fb2ec1 100644
--- a/qcsrc/common/gamemodes/gamemode/nexball/nexball.qh
+++ b/qcsrc/common/gamemodes/gamemode/nexball/nexball.qh
@@ -13,7 +13,7 @@ CLASS(NexBall, Gametype)
     METHOD(NexBall, m_generate_mapinfo, void(Gametype this, string v))
     {
         if(substring(v, 0, 8) == "nexball_" || substring(v, 0, 4) == "ball")
-            MapInfo_Map_supportedGametypes |= this.m_flags;
+            MapInfo_Map_supportedGametypes |= this.gametype_flags;
     }
     METHOD(NexBall, m_isTwoBaseMode, bool())
     {
diff --git a/qcsrc/common/gamemodes/gamemode/onslaught/onslaught.qh b/qcsrc/common/gamemodes/gamemode/onslaught/onslaught.qh
index 3e7dece70..fc3938b92 100644
--- a/qcsrc/common/gamemodes/gamemode/onslaught/onslaught.qh
+++ b/qcsrc/common/gamemodes/gamemode/onslaught/onslaught.qh
@@ -10,7 +10,7 @@ CLASS(Onslaught, Gametype)
     METHOD(Onslaught, m_generate_mapinfo, void(Gametype this, string v))
     {
         if(v == "onslaught_generator")
-            MapInfo_Map_supportedGametypes |= this.m_flags;
+            MapInfo_Map_supportedGametypes |= this.gametype_flags;
     }
     METHOD(Onslaught, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns))
     {
diff --git a/qcsrc/common/gamemodes/gamemode/race/race.qh b/qcsrc/common/gamemodes/gamemode/race/race.qh
index 2eed3433f..ddc11089c 100644
--- a/qcsrc/common/gamemodes/gamemode/race/race.qh
+++ b/qcsrc/common/gamemodes/gamemode/race/race.qh
@@ -26,7 +26,7 @@ CLASS(Race, Gametype)
     METHOD(Race, m_generate_mapinfo, void(Gametype this, string v))
     {
         if(v == "trigger_race_checkpoint")
-            MapInfo_Map_supportedGametypes |= this.m_flags;
+            MapInfo_Map_supportedGametypes |= this.gametype_flags;
     }
     METHOD(Race, m_isTwoBaseMode, bool())
     {
diff --git a/qcsrc/common/gamemodes/gamemode/survival/survival.qh b/qcsrc/common/gamemodes/gamemode/survival/survival.qh
index baddfb5c2..7c5d8c7c4 100644
--- a/qcsrc/common/gamemodes/gamemode/survival/survival.qh
+++ b/qcsrc/common/gamemodes/gamemode/survival/survival.qh
@@ -20,7 +20,7 @@ CLASS(Survival, Gametype)
         if(!cvar("g_survival_not_lms_maps"))
         {
             // if this is unset, all LMS maps support Survival too
-            if(!(MapInfo_Map_supportedGametypes & this.m_flags) && (MapInfo_Map_supportedGametypes & MAPINFO_TYPE_LMS.m_flags))
+            if(!(MapInfo_Map_supportedGametypes & this.gametype_flags) && (MapInfo_Map_supportedGametypes & MAPINFO_TYPE_LMS.gametype_flags))
                 return true; // TODO: references another gametype (alternatively, we could check which gamemodes are always enabled and append this if any are supported)
         }
         return false;
diff --git a/qcsrc/common/gamemodes/gamemode/tdm/tdm.qh b/qcsrc/common/gamemodes/gamemode/tdm/tdm.qh
index eeee581f4..ab7a16427 100644
--- a/qcsrc/common/gamemodes/gamemode/tdm/tdm.qh
+++ b/qcsrc/common/gamemodes/gamemode/tdm/tdm.qh
@@ -32,7 +32,7 @@ CLASS(TeamDeathmatch, Gametype)
         if(cvar("g_tdm_on_dm_maps"))
         {
             // if this is set, all DM maps support TDM too
-            if(!(MapInfo_Map_supportedGametypes & this.m_flags) && (MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DEATHMATCH.m_flags))
+            if(!(MapInfo_Map_supportedGametypes & this.gametype_flags) && (MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DEATHMATCH.gametype_flags))
                 return true; // TODO: references another gametype (alternatively, we could check which gamemodes are always enabled and append this if any are supported)
         }
         return false;
diff --git a/qcsrc/common/gamemodes/gamemode/tka/tka.qh b/qcsrc/common/gamemodes/gamemode/tka/tka.qh
index c6de0eefd..af9d60db3 100644
--- a/qcsrc/common/gamemodes/gamemode/tka/tka.qh
+++ b/qcsrc/common/gamemodes/gamemode/tka/tka.qh
@@ -36,13 +36,13 @@ CLASS(TeamKeepaway, Gametype)
         if(cvar("g_tka_on_ka_maps"))
         {
             // if this is set, all KA maps support TKA too
-            if(!(MapInfo_Map_supportedGametypes & this.m_flags) && (MapInfo_Map_supportedGametypes & MAPINFO_TYPE_KEEPAWAY.m_flags))
+            if(!(MapInfo_Map_supportedGametypes & this.gametype_flags) && (MapInfo_Map_supportedGametypes & MAPINFO_TYPE_KEEPAWAY.gametype_flags))
                 return true; // TODO: references another gametype (alternatively, we could check which gamemodes are always enabled and append this if any are supported)
         }
         if(cvar("g_tka_on_tdm_maps"))
         {
             // if this is set, all TDM maps support TKA too
-            if(!(MapInfo_Map_supportedGametypes & this.m_flags) && (MapInfo_Map_supportedGametypes & MAPINFO_TYPE_TEAM_DEATHMATCH.m_flags))
+            if(!(MapInfo_Map_supportedGametypes & this.gametype_flags) && (MapInfo_Map_supportedGametypes & MAPINFO_TYPE_TEAM_DEATHMATCH.gametype_flags))
                 return true; // TODO: references another gametype (alternatively, we could check which gamemodes are always enabled and append this if any are supported)
         }
         return false;
diff --git a/qcsrc/common/gamemodes/gamemode/tmayhem/tmayhem.qh b/qcsrc/common/gamemodes/gamemode/tmayhem/tmayhem.qh
index 6e37f666f..5280e79fc 100644
--- a/qcsrc/common/gamemodes/gamemode/tmayhem/tmayhem.qh
+++ b/qcsrc/common/gamemodes/gamemode/tmayhem/tmayhem.qh
@@ -28,10 +28,10 @@ CLASS(tmayhem, Gametype)
 	}
 	METHOD(tmayhem, m_isForcedSupported, bool(Gametype this))
 	{
-		if(!(MapInfo_Map_supportedGametypes & this.m_flags) && (MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DEATHMATCH.m_flags)){
+		if(!(MapInfo_Map_supportedGametypes & this.gametype_flags) && (MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DEATHMATCH.gametype_flags)){
 			return true;
 		}
-		if(!(MapInfo_Map_supportedGametypes & this.m_flags) && (MapInfo_Map_supportedGametypes & MAPINFO_TYPE_TEAM_DEATHMATCH.m_flags)){
+		if(!(MapInfo_Map_supportedGametypes & this.gametype_flags) && (MapInfo_Map_supportedGametypes & MAPINFO_TYPE_TEAM_DEATHMATCH.gametype_flags)){
 			return true;
 		}
 		return false;
diff --git a/qcsrc/common/mapinfo.qc b/qcsrc/common/mapinfo.qc
index 707c6826c..677a5f8f7 100644
--- a/qcsrc/common/mapinfo.qc
+++ b/qcsrc/common/mapinfo.qc
@@ -17,6 +17,23 @@ int autocvar_g_mapinfo_q3compat = 1;
 	#define WARN_COND false
 #endif
 
+vector _GametypeFlags_FromGametype(int a)
+{
+	if (REGISTRY_MAX(Gametypes) > 24)
+		if (a >= 24)
+		{
+			a -= 24;
+			if (REGISTRY_MAX(Gametypes) > 48)
+				if (a >= 24)
+				{
+					a -= 24;
+					return '0 0 1' * BIT(a);
+				}
+			return '0 1 0' * BIT(a);
+		}
+	return '1 0 0' * BIT(a);
+}
+
 // generic string stuff
 
 int _MapInfo_Cache_Active;
@@ -71,7 +88,7 @@ void MapInfo_Cache_Store()
 	bufstr_set(_MapInfo_Cache_Buf_IndexToMapData, ++i, MapInfo_Map_titlestring);
 	bufstr_set(_MapInfo_Cache_Buf_IndexToMapData, ++i, MapInfo_Map_description);
 	bufstr_set(_MapInfo_Cache_Buf_IndexToMapData, ++i, MapInfo_Map_author);
-	bufstr_set(_MapInfo_Cache_Buf_IndexToMapData, ++i, ftos(MapInfo_Map_supportedGametypes));
+	bufstr_set(_MapInfo_Cache_Buf_IndexToMapData, ++i, vtos(MapInfo_Map_supportedGametypes));
 	bufstr_set(_MapInfo_Cache_Buf_IndexToMapData, ++i, ftos(MapInfo_Map_supportedFeatures));
 	bufstr_set(_MapInfo_Cache_Buf_IndexToMapData, ++i, ftos(MapInfo_Map_flags));
 }
@@ -94,7 +111,7 @@ float MapInfo_Cache_Retrieve(string map)
 	MapInfo_Map_titlestring = bufstr_get(_MapInfo_Cache_Buf_IndexToMapData, ++i);
 	MapInfo_Map_description = bufstr_get(_MapInfo_Cache_Buf_IndexToMapData, ++i);
 	MapInfo_Map_author = bufstr_get(_MapInfo_Cache_Buf_IndexToMapData, ++i);
-	MapInfo_Map_supportedGametypes = stof(bufstr_get(_MapInfo_Cache_Buf_IndexToMapData, ++i));
+	MapInfo_Map_supportedGametypes = stov(bufstr_get(_MapInfo_Cache_Buf_IndexToMapData, ++i));
 	MapInfo_Map_supportedFeatures = stof(bufstr_get(_MapInfo_Cache_Buf_IndexToMapData, ++i));
 	MapInfo_Map_flags = stof(bufstr_get(_MapInfo_Cache_Buf_IndexToMapData, ++i));
 
@@ -159,9 +176,9 @@ float _MapInfo_FilterList_cmp(float i, float j, entity pass)
 
 float MapInfo_FilterGametype(Gametype pGametype, int pFeatures, int pFlagsRequired, int pFlagsForbidden, bool pAbortOnGenerate)
 {
-	return _MapInfo_FilterGametype(pGametype.m_flags, pFeatures, pFlagsRequired, pFlagsForbidden, pAbortOnGenerate);
+	return _MapInfo_FilterGametype(pGametype.gametype_flags, pFeatures, pFlagsRequired, pFlagsForbidden, pAbortOnGenerate);
 }
-float _MapInfo_FilterGametype(int pGametype, int pFeatures, int pFlagsRequired, int pFlagsForbidden, bool pAbortOnGenerate)
+float _MapInfo_FilterGametype(vector pGametype, int pFeatures, int pFlagsRequired, int pFlagsForbidden, bool pAbortOnGenerate)
 {
 	float i, j;
 	if (!_MapInfo_filtered_allocated)
@@ -179,7 +196,7 @@ float _MapInfo_FilterGametype(int pGametype, int pFeatures, int pFlagsRequired,
 				MapInfo_progress = i / _MapInfo_globcount;
 				return 0;
 			}
-		if((MapInfo_Map_supportedGametypes & pGametype) != 0)
+		if(MapInfo_Map_supportedGametypes & pGametype)
 		if((MapInfo_Map_supportedFeatures & pFeatures) == pFeatures)
 		if((MapInfo_Map_flags & pFlagsForbidden) == 0)
 		if((MapInfo_Map_flags & pFlagsRequired) == pFlagsRequired)
@@ -397,27 +414,27 @@ float _MapInfo_Generate(string pFilename) // 0: failure, 1: ok ent, 2: ok bsp
 	}
 	diameter = vlen(mapMaxs - mapMins);
 
-	int twoBaseModes = 0;
-	FOREACH(Gametypes, it.m_isTwoBaseMode(), twoBaseModes |= it.m_flags);
+	vector twoBaseModes = '0 0 0';
+	FOREACH(Gametypes, it.m_isTwoBaseMode(), twoBaseModes |= it.gametype_flags);
 	if(twoBaseModes && (twoBaseModes &= MapInfo_Map_supportedGametypes))
 	{
 		// we have a symmetrical map, don't add the modes without bases
 	}
 	else if(!is_q3df_map)
 	{
-		FOREACH(Gametypes, it.m_isAlwaysSupported(it, spawnpoints, diameter), MapInfo_Map_supportedGametypes |= it.m_flags);
+		FOREACH(Gametypes, it.m_isAlwaysSupported(it, spawnpoints, diameter), MapInfo_Map_supportedGametypes |= it.gametype_flags);
 	}
 
-	if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_RACE.m_flags)
+	if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_RACE.gametype_flags)
 	if(!spawnplaces)
 	{
-		MapInfo_Map_supportedGametypes &= ~MAPINFO_TYPE_RACE.m_flags;
-		MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CTS.m_flags;
+		MapInfo_Map_supportedGametypes &= ~MAPINFO_TYPE_RACE.gametype_flags;
+		MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CTS.gametype_flags;
 	}
 
 	LOG_TRACE("-> diameter ",    ftos(diameter));
 	LOG_TRACE(";  spawnpoints ", ftos(spawnpoints));
-	LOG_TRACE(";  modes ",       ftos(MapInfo_Map_supportedGametypes));
+	LOG_TRACE(";  modes ",       vtos(MapInfo_Map_supportedGametypes));
 
 	fclose(fh);
 
@@ -430,7 +447,7 @@ void _MapInfo_Map_Reset()
 	MapInfo_Map_titlestring = "<TITLE>";
 	MapInfo_Map_description = "<DESCRIPTION>";
 	MapInfo_Map_author = "<AUTHOR>";
-	MapInfo_Map_supportedGametypes = 0;
+	MapInfo_Map_supportedGametypes = '0 0 0';
 	MapInfo_Map_supportedFeatures = 0;
 	MapInfo_Map_flags = 0;
 	MapInfo_Map_clientstuff = "";
@@ -447,8 +464,8 @@ string _MapInfo_GetDefault(Gametype t)
 void _MapInfo_Map_ApplyGametype(string s, Gametype pWantedType, Gametype pThisType, int load_default)
 {
 	string sa;
-	MapInfo_Map_supportedGametypes |= pThisType.m_flags;
-	if(!(pThisType.m_flags & pWantedType.m_flags))
+	MapInfo_Map_supportedGametypes |= pThisType.gametype_flags;
+	if(!(pThisType.gametype_flags & pWantedType.gametype_flags))
 		return;
 
 	if(load_default)
@@ -526,8 +543,8 @@ float _MapInfo_GetTeamPlayBool(Gametype t)
 
 void _MapInfo_Map_ApplyGametypeEx(string s, Gametype pWantedType, Gametype pThisType)
 {
-	MapInfo_Map_supportedGametypes |= pThisType.m_flags;
-	if (!(pThisType.m_flags & pWantedType.m_flags))
+	MapInfo_Map_supportedGametypes |= pThisType.gametype_flags;
+	if (!(pThisType.gametype_flags & pWantedType.gametype_flags))
 		return;
 
 	// reset all the cvars to their defaults
@@ -815,7 +832,7 @@ bool _MapInfo_ParseArena(string arena_filename, int fh, string pFilename, Gamety
 	string stored_Map_description = "";
 	string stored_Map_title = "";
 	string stored_Map_author = "";
-	int stored_supportedGametypes = 0;
+	vector stored_supportedGametypes = '0 0 0';
 	int stored_supportedFeatures = 0;
 	int stored_flags = 0;
 	string t, s;
@@ -862,7 +879,7 @@ bool _MapInfo_ParseArena(string arena_filename, int fh, string pFilename, Gamety
 					MapInfo_Map_supportedGametypes |= stored_supportedGametypes;
 				else
 				{
-					FOREACH(Gametypes, it.m_flags & stored_supportedGametypes,
+					FOREACH(Gametypes, it.gametype_flags & stored_supportedGametypes,
 					{
 						_MapInfo_Map_ApplyGametype ("", pGametypeToSet, it, true);
 					});
@@ -877,7 +894,7 @@ bool _MapInfo_ParseArena(string arena_filename, int fh, string pFilename, Gamety
 				stored_Map_description = "";
 				stored_Map_title = "";
 				stored_Map_author = "";
-				stored_supportedGametypes = 0;
+				stored_supportedGametypes = '0 0 0';
 				stored_supportedFeatures = 0;
 				stored_flags = 0;
 				continue;
@@ -935,14 +952,14 @@ bool _MapInfo_ParseArena(string arena_filename, int fh, string pFilename, Gamety
 			{
 				Gametype f = MapInfo_Type_FromString(it, false, true);
 				if(f)
-					stored_supportedGametypes |= f.m_flags;
+					stored_supportedGametypes |= f.gametype_flags;
 			});
 		}
 		else if(t == "style" && isdefi)
 		{
 			// we have a defrag map on our hands, add CTS!
 			// TODO: styles
-			stored_supportedGametypes |= MAPINFO_TYPE_CTS.m_flags;
+			stored_supportedGametypes |= MAPINFO_TYPE_CTS.gametype_flags;
 		}
 		else if(t == "map")
 		{
@@ -1118,7 +1135,7 @@ float MapInfo_Get_ByName_NoFallbacks(string pFilename, int pAllowGenerate, Gamet
 			if(MapInfo_Map_flags & MAPINFO_FLAG_FRUSTRATING)
 				fputs(fh, "frustrating\n");
 
-			FOREACH(Gametypes, MapInfo_Map_supportedGametypes & it.m_flags, {
+			FOREACH(Gametypes, MapInfo_Map_supportedGametypes & it.gametype_flags, {
 				fputs(fh, sprintf("gametype %s // defaults: %s\n", MapInfo_Type_ToString(it), _MapInfo_GetDefaultEx(it)));
 			});
 
@@ -1266,7 +1283,7 @@ float MapInfo_Get_ByName_NoFallbacks(string pFilename, int pAllowGenerate, Gamet
 			Gametype f = NULL;
 			if(all || (f = MapInfo_Type_FromString(t, true, false)))
 			{
-				if((all ? MAPINFO_TYPE_ALL : f.m_flags) & pGametypeToSet.m_flags)
+				if((all ? MAPINFO_TYPE_ALL : f.gametype_flags) & pGametypeToSet.gametype_flags)
 				{
 					_MapInfo_Parse_Settemp(pFilename, acl, 0, s, 1);
 				}
@@ -1283,7 +1300,7 @@ float MapInfo_Get_ByName_NoFallbacks(string pFilename, int pAllowGenerate, Gamet
 			Gametype f = NULL;
 			if(all || (f = MapInfo_Type_FromString(t, true, false)))
 			{
-				if((all ? MAPINFO_TYPE_ALL : f.m_flags) & pGametypeToSet.m_flags)
+				if((all ? MAPINFO_TYPE_ALL : f.gametype_flags) & pGametypeToSet.gametype_flags)
 				{
 					_MapInfo_Parse_Settemp(pFilename, acl, 1, s, 1);
 				}
@@ -1359,7 +1376,7 @@ LABEL(mapinfo_handled)
 		MapInfo_Map_author = ""; // don't display "<AUTHOR>" in the UI (we do write it to .mapinfo files)
 
 	MapInfo_Cache_Store();
-	if(MapInfo_Map_supportedGametypes != 0)
+	if(MapInfo_Map_supportedGametypes != '0 0 0')
 		return r;
 	if (WARN_COND)
 		LOG_WARN("Map ", pFilename, " supports no game types, ignored");
@@ -1373,7 +1390,7 @@ int MapInfo_Get_ByName(string pFilename, float pAllowGenerate, Gametype pGametyp
 
 	if(pGametypeToSet)
 	{
-		if(!(MapInfo_Map_supportedGametypes & pGametypeToSet.m_flags))
+		if(!(MapInfo_Map_supportedGametypes & pGametypeToSet.gametype_flags))
 		{
 			error("Can't select the requested game type. This should never happen as the caller should prevent it!\n");
 			//_MapInfo_Map_ApplyGametypeEx("", pGametypeToSet, MAPINFO_TYPE_DEATHMATCH);
@@ -1476,7 +1493,7 @@ float _MapInfo_CheckMap(string s, bool gametype_only) // returns 0 if the map ca
 {
 	if(!MapInfo_Get_ByName(s, 1, NULL))
 		return 0;
-	if((MapInfo_Map_supportedGametypes & MapInfo_CurrentGametype().m_flags) == 0)
+	if(!(MapInfo_Map_supportedGametypes & MapInfo_CurrentGametype().gametype_flags))
 		return 0;
 	if (gametype_only)
 		return 1;
@@ -1500,12 +1517,12 @@ void MapInfo_SwitchGameType(Gametype t)
 
 void MapInfo_LoadMap(string s, float reinit)
 {
-	MapInfo_Map_supportedGametypes = 0;
+	MapInfo_Map_supportedGametypes = '0 0 0';
 	// we shouldn't need this, as LoadMapSettings already fixes the gametype
 	//if(!MapInfo_CheckMap(s))
 	//{
 	//	print("EMERGENCY: can't play the selected map in the given game mode. Falling back to DM.\n");
-	//	MapInfo_SwitchGameType(MAPINFO_TYPE_DEATHMATCH.m_flags);
+	//	MapInfo_SwitchGameType(MAPINFO_TYPE_DEATHMATCH.gametype_flags);
 	//}
 
 	LOG_INFO("Switching to map ", s);
@@ -1569,12 +1586,12 @@ void MapInfo_LoadMapSettings(string s) // to be called from worldspawn
 			return; // do not call Get_ByName!
 		}
 
-		if(MapInfo_Map_supportedGametypes == 0)
+		if(MapInfo_Map_supportedGametypes == '0 0 0')
 		{
 			RandomSelection_Init();
 			FOREACH(Gametypes, it.m_priority == 2, 
 			{
-				MapInfo_Map_supportedGametypes |= it.m_flags;
+				MapInfo_Map_supportedGametypes |= it.gametype_flags;
 				RandomSelection_AddEnt(it, 1, 1);
 			});
 			if(RandomSelection_chosen_ent)
@@ -1597,7 +1614,7 @@ void MapInfo_LoadMapSettings(string s) // to be called from worldspawn
 #endif
 		RandomSelection_Init();
 		Gametype t_prev = t;
-		FOREACH(Gametypes, MapInfo_Map_supportedGametypes & it.m_flags,
+		FOREACH(Gametypes, MapInfo_Map_supportedGametypes & it.gametype_flags,
 		{
 			RandomSelection_AddEnt(it, 1, it.m_priority);
 		});
@@ -1622,7 +1639,7 @@ void MapInfo_ClearTemps()
 	MapInfo_Map_description = string_null;
 	MapInfo_Map_author = string_null;
 	MapInfo_Map_clientstuff = string_null;
-	MapInfo_Map_supportedGametypes = 0;
+	MapInfo_Map_supportedGametypes = '0 0 0';
 	MapInfo_Map_supportedFeatures = 0;
 }
 
diff --git a/qcsrc/common/mapinfo.qh b/qcsrc/common/mapinfo.qh
index 4d65695ae..e1368871e 100644
--- a/qcsrc/common/mapinfo.qh
+++ b/qcsrc/common/mapinfo.qh
@@ -10,7 +10,7 @@ string MapInfo_Map_description;
 string MapInfo_Map_author;
 string MapInfo_Map_clientstuff; // not in cache, only for map load
 string MapInfo_Map_fog; // not in cache, only for map load
-int MapInfo_Map_supportedGametypes;
+vector MapInfo_Map_supportedGametypes;
 int MapInfo_Map_supportedFeatures;
 int MapInfo_Map_flags;
 vector MapInfo_Map_mins; // these are '0 0 0' if not supported!
@@ -24,8 +24,11 @@ const int GAMETYPE_FLAG_HIDELIMITS      = BIT(4); // don't display a score limit
 const int GAMETYPE_FLAG_WEAPONARENA     = BIT(5); // gametype has a forced weapon arena, weapon arena mutators should disable themselves when this is set
 const int GAMETYPE_FLAG_1V1             = BIT(6); // 1v1 gameplay
 
-int MAPINFO_TYPE_ALL;
-.int m_flags;
+vector MAPINFO_TYPE_ALL;
+.vector gametype_flags;
+
+// must be defined before the registry
+vector _GametypeFlags_FromGametype(int a);
 
 CLASS(Gametype, Object)
     ATTRIB(Gametype, m_id, int, 0);
@@ -115,9 +118,6 @@ CLASS(Gametype, Object)
         this.m_hidelimits = (gflags & GAMETYPE_FLAG_HIDELIMITS);
         this.m_weaponarena = (gflags & GAMETYPE_FLAG_WEAPONARENA);
         this.m_1v1 = (gflags & GAMETYPE_FLAG_1V1);
-
-        // same as `1 << m_id`
-        MAPINFO_TYPE_ALL |= this.items = this.m_flags = (MAPINFO_TYPE_ALL + 1);
     }
 ENDCLASS(Gametype)
 
@@ -127,7 +127,15 @@ REGISTRY_SORT(Gametypes)
 REGISTRY_CHECK(Gametypes)
 
 REGISTRY_DEFINE_GET(Gametypes, NULL)
-STATIC_INIT(Gametypes_renumber) { FOREACH(Gametypes, true, it.m_id = i); }
+STATIC_INIT(Gametypes_renumber)
+{
+    FOREACH(Gametypes, true,
+    {
+        it.m_id = i;
+        vector set = it.gametype_flags = _GametypeFlags_FromGametype(it.m_id);
+        MAPINFO_TYPE_ALL |= set;
+    });
+}
 #define REGISTER_GAMETYPE(NAME, inst) REGISTER(Gametypes, MAPINFO_TYPE, NAME, m_id, inst)
 
 #ifndef CSQC
@@ -154,7 +162,7 @@ void MapInfo_Enumerate();
 // filter the info by game type mask (updates MapInfo_count)
 float MapInfo_progress;
 float MapInfo_FilterGametype(Gametype gametypeFlags, float features, float pFlagsRequired, float pFlagsForbidden, float pAbortOnGenerate); // 1 on success, 0 on temporary failure (call it again next frame then; use MapInfo_progress as progress indicator)
-float _MapInfo_FilterGametype(int gametypeFlags, float features, float pFlagsRequired, float pFlagsForbidden, float pAbortOnGenerate); // 1 on success, 0 on temporary failure (call it again next frame then; use MapInfo_progress as progress indicator)
+float _MapInfo_FilterGametype(vector gametypeFlags, float features, float pFlagsRequired, float pFlagsForbidden, float pAbortOnGenerate); // 1 on success, 0 on temporary failure (call it again next frame then; use MapInfo_progress as progress indicator)
 void MapInfo_FilterString(string sf); // filter _MapInfo_filtered (created by MapInfo_FilterGametype) with keyword
 int MapInfo_CurrentFeatures(); // retrieves currently required features from cvars
 Gametype MapInfo_CurrentGametype(); // retrieves current gametype from cvars
diff --git a/qcsrc/menu/xonotic/dialog_multiplayer_create_mapinfo.qc b/qcsrc/menu/xonotic/dialog_multiplayer_create_mapinfo.qc
index d32bd0f84..80de0be27 100644
--- a/qcsrc/menu/xonotic/dialog_multiplayer_create_mapinfo.qc
+++ b/qcsrc/menu/xonotic/dialog_multiplayer_create_mapinfo.qc
@@ -33,7 +33,7 @@ void XonoticMapInfoDialog_loadMapInfo(entity me, int i, entity mlb)
 	{
 		entity e;
 		e = me.(typeLabels[i]);
-		e.disabled = !(MapInfo_Map_supportedGametypes & GameType_GetID(i).m_flags);
+		e.disabled = !(MapInfo_Map_supportedGametypes & GameType_GetID(i).gametype_flags);
 	}
 
 	MapInfo_ClearTemps();
diff --git a/qcsrc/server/mapvoting.qc b/qcsrc/server/mapvoting.qc
index b9d19f611..72969a0ab 100644
--- a/qcsrc/server/mapvoting.qc
+++ b/qcsrc/server/mapvoting.qc
@@ -73,24 +73,25 @@ int GameTypeVote_AvailabilityStatus(string type_name)
 	{
 		if ( !MapInfo_Get_ByName(autocvar_nextmap, false, NULL) )
 			return flag;
-		if (!(MapInfo_Map_supportedGametypes & type.m_flags))
+		if (!(MapInfo_Map_supportedGametypes & type.gametype_flags))
 			return flag;
 	}
 
 	return flag | GTV_AVAILABLE;
 }
 
-int GameTypeVote_GetMask()
+vector GameTypeVote_GetMask()
 {
-	int n, j, gametype_mask;
+	int n, j;
+	vector gametype_mask;
 	n = tokenizebyseparator(autocvar_sv_vote_gametype_options, " ");
 	n = min(MAPVOTE_COUNT, n);
-	gametype_mask = 0;
+	gametype_mask = '0 0 0';
 	for(j = 0; j < n; ++j)
-		gametype_mask |= GameTypeVote_Type_FromString(argv(j)).m_flags;
+		gametype_mask |= GameTypeVote_Type_FromString(argv(j)).gametype_flags;
 
-	if (gametype_mask == 0)
-		gametype_mask |= MapInfo_CurrentGametype().m_flags;
+	if (gametype_mask == '0 0 0')
+		gametype_mask |= MapInfo_CurrentGametype().gametype_flags;
 
 	return gametype_mask;
 }
-- 
2.39.5