X-Git-Url: https://de.git.xonotic.org/?p=xonotic%2Fxonotic-data.pk3dir.git;a=blobdiff_plain;f=qcsrc%2Fcommon%2Fmapinfo.qc;h=c0b67ff4d535e97e9752132fafd7134c28c9ae6d;hp=64ab8dacaafa1c5f1748765566f7d00ef56bc90c;hb=HEAD;hpb=185c7b56bb0d8bbb204cbbf0696dd96c069fffc3 diff --git a/qcsrc/common/mapinfo.qc b/qcsrc/common/mapinfo.qc index 64ab8daca..c1cdd7a90 100644 --- a/qcsrc/common/mapinfo.qc +++ b/qcsrc/common/mapinfo.qc @@ -1,19 +1,20 @@ #include "mapinfo.qh" #if defined(CSQC) - #include "../client/defs.qh" - #include "util.qh" - #include + #include + #include #elif defined(MENUQC) #elif defined(SVQC) - #include "util.qh" - #include + #include + #include #endif -#ifdef MENUQC -#define WARN_COND false +int autocvar_g_mapinfo_q3compat = 1; + +#ifdef SVQC + bool autocvar_g_mapinfo_ignore_warnings; + #define WARN_COND (!autocvar_g_mapinfo_ignore_warnings && MapInfo_Map_bspname == mi_shortname) #else -bool autocvar_g_mapinfo_ignore_warnings; -#define WARN_COND (!autocvar_g_mapinfo_ignore_warnings && MapInfo_Map_bspname == mi_shortname) + #define WARN_COND false #endif // generic string stuff @@ -254,11 +255,9 @@ string unquote(string s) return ""; } -float MapInfo_Get_ByID(float i) +bool MapInfo_Get_ByID(int i) { - if(MapInfo_Get_ByName(MapInfo_BSPName_ByID(i), 0, NULL)) - return 1; - return 0; + return MapInfo_Get_ByName(MapInfo_BSPName_ByID(i), 0, NULL) ? true : false; } string _MapInfo_Map_worldspawn_music; @@ -274,9 +273,31 @@ float _MapInfo_Generate(string pFilename) // 0: failure, 1: ok ent, 2: ok bsp float r; float diameter, spawnpoints; float spawnplaces; - + bool is_q3df_map = false; vector mapMins, mapMaxs; + if(autocvar_g_mapinfo_q3compat >= 2) // generate mapinfo using arena data + { + // try for .arena or .defi files, as they may have more accurate information + // supporting .arena AND .defi for the same map + bool success = false; + fh = -1; + fn = _MapInfo_FindArenaFile(pFilename, ".arena"); + if(fn != "" && (fh = fopen(fn, FILE_READ)) >= 0) + { + success = _MapInfo_ParseArena(fn, fh, pFilename, NULL, false, true); + fclose(fh); + } + fn = _MapInfo_FindArenaFile(pFilename, ".defi"); + if(fn != "" && (fh = fopen(fn, FILE_READ)) >= 0) + { + success |= _MapInfo_ParseArena(fn, fh, pFilename, NULL, true, true); + fclose(fh); + } + if (success && autocvar_g_mapinfo_q3compat == 3) + return 3; // skip entity analysis + } + r = 1; fn = strcat("maps/", pFilename, ".ent"); fh = fopen(fn, FILE_READ); @@ -288,11 +309,9 @@ float _MapInfo_Generate(string pFilename) // 0: failure, 1: ok ent, 2: ok bsp } if(fh < 0) return 0; - LOG_INFO("Analyzing ", fn, " to generate initial mapinfo"); + LOG_INFO("Generating ", pFilename, ".mapinfo: analyzing ", fn); inWorldspawn = 2; - MapInfo_Map_flags = 0; - MapInfo_Map_supportedGametypes = 0; spawnpoints = 0; spawnplaces = 0; _MapInfo_Map_worldspawn_music = ""; @@ -320,17 +339,8 @@ float _MapInfo_Generate(string pFilename) // 0: failure, 1: ok ent, 2: ok bsp _MapInfo_Map_worldspawn_music = v; else if(k == "noise") _MapInfo_Map_worldspawn_music = v; - else if(k == "message") - { - i = strstrofs(v, " by ", 0); - if(MapInfo_Map_author == "" && i >= 0) - { - MapInfo_Map_title = substring(v, 0, i); - MapInfo_Map_author = substring(v, i + 4, strlen(v) - (i + 4)); - } - else - MapInfo_Map_title = v; - } + else if(k == "message" && (!MapInfo_Map_title || MapInfo_Map_title == "") && v != "") + MapInfo_Map_title = v; } else { @@ -373,6 +383,8 @@ float _MapInfo_Generate(string pFilename) // 0: failure, 1: ok ent, 2: ok bsp MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_MONSTERS; else if(v == "target_music" || v == "trigger_music") _MapInfo_Map_worldspawn_music = string_null; // don't use regular BGM + else if(v == "target_stopTimer") + is_q3df_map = true; // don't support standard gametypes UNLESS we found them in .arena else FOREACH(Gametypes, true, it.m_generate_mapinfo(it, v)); } @@ -391,7 +403,7 @@ float _MapInfo_Generate(string pFilename) // 0: failure, 1: ok ent, 2: ok bsp { // we have a symmetrical map, don't add the modes without bases } - else + else if(!is_q3df_map) { FOREACH(Gametypes, it.m_isAlwaysSupported(it, spawnpoints, diameter), MapInfo_Map_supportedGametypes |= it.m_flags); } @@ -589,9 +601,10 @@ void _MapInfo_Map_ApplyGametypeEx(string s, Gametype pWantedType, Gametype pThis } } -Gametype MapInfo_Type_FromString(string gtype) +Gametype MapInfo_Type_FromString(string gtype, bool dowarn, bool is_q3compat) { string replacement = ""; + switch (gtype) { case "nexball": replacement = "nb"; break; @@ -600,10 +613,18 @@ Gametype MapInfo_Type_FromString(string gtype) case "invasion": replacement = "inv"; break; case "assault": replacement = "as"; break; case "race": replacement = "rc"; break; + // Q3/QL compat, see DoesQ3ARemoveThisEntity() in quake3.qc for complete lists + case "ffa": replacement = "dm"; break; + case "cctf": // from ThreeWave, maps with this should all have "ctf" too + case "oneflag": replacement = "ctf"; break; + case "tourney": replacement = "duel"; break; + case "arena": // which Q3 mod is this from? In Nexuiz it was 'duel with rounds'. + if(is_q3compat) { replacement = "ca"; } break; } - if (replacement != "" && WARN_COND) + if (replacement != "") { - LOG_WARNF("MapInfo_Type_FromString (probably %s): using deprecated name '%s'. Should use '%s'.", MapInfo_Map_bspname, gtype, replacement); + if (dowarn && WARN_COND) + LOG_WARNF("MapInfo_Type_FromString (probably %s): using deprecated name '%s'. Should use '%s'.", MapInfo_Map_bspname, gtype, replacement); gtype = replacement; } FOREACH(Gametypes, it.mdl == gtype, return it); @@ -629,7 +650,10 @@ string MapInfo_Type_ToText(Gametype t) void _MapInfo_Parse_Settemp(string pFilename, string acl, float type, string s, float recurse) { string t; - float fh, o; + float o; + // tabs are invalid, treat them as "empty" + s = strreplace("\t", "", s); + t = car(s); s = cdr(s); // limited support of "" and comments @@ -663,7 +687,7 @@ void _MapInfo_Parse_Settemp(string pFilename, string acl, float type, string s, { if(recurse > 0) { - fh = fopen(s, FILE_READ); + float fh = fopen(s, FILE_READ); if(fh < 0) { if(WARN_COND) @@ -671,11 +695,9 @@ void _MapInfo_Parse_Settemp(string pFilename, string acl, float type, string s, } else { - for (;;) + while((s = fgets(fh))) { - if (!((s = fgets(fh)))) - break; - + s = strreplace("\t", "", s); // treat tabs as "empty", perform here first to ensure coments are detected // catch different sorts of comments if(s == "") // empty lines continue; @@ -734,7 +756,35 @@ void _MapInfo_Parse_Settemp(string pFilename, string acl, float type, string s, } } -float MapInfo_isRedundant(string fn, string t) +/// Removes author string from title (if found) +/// and copies it to MapInfo_Map_author if that wasn't set. +string MapInfo_title_sans_author(string title) +{ + int offset; + + if ((offset = strstrofs(title, " by ", 0)) >= 0) + { + if (MapInfo_Map_author == "<AUTHOR>") + MapInfo_Map_author = substring(title, offset + 4, strlen(title) - (offset + 4)); + title = substring(title, 0, offset); + } + else if ((offset = strstrofs(title, " (by ", 0)) >= 0 || (offset = strstrofs(title, " [by ", 0)) >= 0) + { + if (MapInfo_Map_author == "<AUTHOR>") + MapInfo_Map_author = substring(title, offset + 5, strlen(title) - (offset + 5) - 1); + title = substring(title, 0, offset); + } + else if ((offset = strstrofs(title, "Made By ", 0)) >= 0) // often at the start of the string + { + if (MapInfo_Map_author == "<AUTHOR>") + MapInfo_Map_author = substring(title, offset + 8, strlen(title) - (offset + 8)); + title = substring(title, 0, offset); + } + + return title != "" ? title : "<TITLE>"; +} + +bool MapInfo_isRedundant(string fn, string t) { // normalize file name fn = strreplace("_", "", fn); @@ -756,6 +806,215 @@ float MapInfo_isRedundant(string fn, string t) return false; } +bool _MapInfo_ParseArena(string arena_filename, int fh, string pFilename, Gametype pGametypeToSet, bool isdefi, bool isgenerator) +{ + // NOTE: .arena files can hold more than 1 map's information! + // to handle this, we're going to store gathered information in local variables and save it if we encounter the correct map name + bool in_brackets = false; // testing a potential mapinfo section (within brackets) + bool dosave = false; + string stored_Map_description = ""; + string stored_Map_title = ""; + string stored_Map_author = ""; + int stored_supportedGametypes = 0; + int stored_supportedFeatures = 0; + int stored_flags = 0; + string t, s; + + if (isgenerator) + LOG_INFO("Generating ", pFilename, ".mapinfo: analyzing ", arena_filename); + + for (;;) + { + if (!((s = fgets(fh)))) + break; + + // catch different sorts of comments + if(s == "") // empty lines + continue; + if(substring(s, 0, 2) == "//") // C++ style + continue; + if(strstrofs(s, "{", 0) >= 0) + { + if(in_brackets) + return false; // edge case? already in a bracketed section! + in_brackets = true; + continue; + } + else if(!in_brackets) + { + // if we're not inside a bracket, don't process map info + continue; + } + if(strstrofs(s, "}", 0) >= 0) + { + if(!in_brackets) + return false; // no starting bracket! let the mapinfo generation system handle it + in_brackets = false; + if(dosave) + { + MapInfo_Map_description = stored_Map_description; + if(stored_Map_title != "") + MapInfo_Map_title = stored_Map_title; + if(stored_Map_author != "") // write the usual "<AUTHOR>" if we have nothing better + MapInfo_Map_author = stored_Map_author; + // might have .arena AND .defi for the same map so these bitfields are OR'd + if(isgenerator) + MapInfo_Map_supportedGametypes |= stored_supportedGametypes; + else + { + FOREACH(Gametypes, it.m_flags & stored_supportedGametypes, + { + _MapInfo_Map_ApplyGametype ("", pGametypeToSet, it, true); + }); + } + MapInfo_Map_supportedFeatures |= stored_supportedFeatures; + MapInfo_Map_flags |= stored_flags; + return true; // no need to continue through the file, we have our map! + } + else + { + // discard any gathered locals, we're not using the correct map! + stored_Map_description = ""; + stored_Map_title = ""; + stored_Map_author = ""; + stored_supportedGametypes = 0; + stored_supportedFeatures = 0; + stored_flags = 0; + continue; + } + } + + s = strreplace("\t", " ", s); + + float p = strstrofs(s, "//", 0); + if(p >= 0) + s = substring(s, 0, p); + + // perform an initial trim to ensure the first argument is properly obtained + // remove leading spaces + while(substring(s, 0, 1) == " ") + s = substring(s, 1, -1); + + t = car(s); s = cdr(s); + t = strtolower(t); // apparently some q3 maps use capitalized parameters + + // remove trailing spaces + while(substring(t, -1, 1) == " ") + t = substring(t, 0, -2); + + // remove trailing spaces + while(substring(s, -1, 1) == " ") + s = substring(s, 0, -2); + // remove leading spaces + while(substring(s, 0, 1) == " ") + s = substring(s, 1, -1); + // limited support of "" + // remove trailing and leading " of s + if(substring(s, 0, 1) == "\"") + { + if(substring(s, -1, 1) == "\"") + s = substring(s, 1, -2); + } + if(t == "longname") + stored_Map_title = s; + else if(t == "author") + stored_Map_author = s; + else if(t == "type") + { + // if there is a valid gametype in this .arena file, include it in the menu + stored_supportedFeatures |= MAPINFO_FEATURE_WEAPONS; + // type in quake 3 holds all the supported gametypes, so we must loop through all of them + // TODO: handle support here better to include more Xonotic teamplay modes + string types = s; + types = strreplace("team", "tdm ft", types); + types = strreplace("ffa", "dm lms ka", types); + types = strreplace("tourney", "duel", types); // QL used duel so the following check must support it + if(strstrofs(types, "duel", 0) < 0 && strstrofs(types, "tdm", 0) >= 0) // larger team map, support additional gamemodes! + types = cons(types, "ca kh"); + FOREACH_WORD(types, true, + { + Gametype f = MapInfo_Type_FromString(it, false, true); + if(f) + stored_supportedGametypes |= f.m_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; + } + else if(t == "map") + { + if(strtolower(s) == strtolower(pFilename)) + dosave = true; // yay, found our map! + } + else if(t == "quote") + stored_Map_description = s; + // TODO: fraglimit + } + + // if the map wasn't found in the .arena, fall back to generated .mapinfo + return false; +} + +string _MapInfo_CheckArenaFile(string pFilename, string pMapname) +{ + // returns the file name if valid, otherwise returns "" + // a string is returned to optimise the use cases where a filename is also returned + int fh = fopen(pFilename, FILE_READ); + if(fh < 0) + return ""; + for(string s; (s = fgets(fh)); ) + { + s = strreplace("\t", "", s); + while(substring(s, 0, 1) == " ") + s = substring(s, 1, -1); + if(substring(s, 0, 2) == "//") + continue; + if(s == "") + continue; + int offset = strstrofs(s, "map", 0); + if(offset >= 0) + { + if(strstrofs(strtolower(s), strcat("\"", strtolower(pMapname), "\""), offset) >= 0) // quake 3 is case insensitive + { + fclose(fh); + return pFilename; // FOUND IT! + } + } + } + fclose(fh); + return ""; // file did not contain a "map" field matching our map name +} + +string _MapInfo_FindArenaFile(string pFilename, string extension) +{ + string fallback = strcat("scripts/", pFilename, extension); + if(!checkextension("DP_QC_FS_SEARCH_PACKFILE")) + return _MapInfo_CheckArenaFile(fallback, pFilename); + string base_pack = whichpack(strcat("maps/", pFilename, ".bsp")); + if(base_pack == "") // this map isn't packaged! + return _MapInfo_CheckArenaFile(fallback, pFilename); + + int glob = search_packfile_begin(strcat("scripts/*", extension), true, true, base_pack); + if(glob < 0) + return _MapInfo_CheckArenaFile(fallback, pFilename); + int n = search_getsize(glob); + for(int j = 0; j < n; ++j) + { + string file = search_getfilename(glob, j); + if(_MapInfo_CheckArenaFile(file, pFilename) != "") + { + search_end(glob); + return file; + } + } + + search_end(glob); + return ""; // if we get here, a valid .arena file could not be found +} + // load info about a map by name into the MapInfo_Map_* globals float MapInfo_Get_ByName_NoFallbacks(string pFilename, int pAllowGenerate, Gametype pGametypeToSet) { @@ -763,7 +1022,7 @@ float MapInfo_Get_ByName_NoFallbacks(string pFilename, int pAllowGenerate, Gamet string s, t; float fh; int f, i; - float r, n, p; + float r, n; string acl; acl = MAPINFO_SETTEMP_ACL_USER; @@ -787,6 +1046,29 @@ float MapInfo_Get_ByName_NoFallbacks(string pFilename, int pAllowGenerate, Gamet fh = fopen(fn, FILE_READ); if(fh < 0) { + if(autocvar_g_mapinfo_q3compat == 1) // use arena data instead of generating a mapinfo file + { + // supporting .arena AND .defi for the same map + bool success = false; + fn = _MapInfo_FindArenaFile(pFilename, ".arena"); + if(fn != "" && (fh = fopen(fn, FILE_READ)) >= 0) + { + _MapInfo_Map_Reset(); + success = _MapInfo_ParseArena(fn, fh, pFilename, pGametypeToSet, false, false); + fclose(fh); + } + fn = _MapInfo_FindArenaFile(pFilename, ".defi"); + if(fn != "" && (fh = fopen(fn, FILE_READ)) >= 0) + { + if(!success) + _MapInfo_Map_Reset(); + success |= _MapInfo_ParseArena(fn, fh, pFilename, pGametypeToSet, true, false); + fclose(fh); + } + if(success) + goto mapinfo_handled; // skip generation + } + fn = strcat("maps/autogenerated/", pFilename, ".mapinfo"); fh = fopen(fn, FILE_READ); if(fh < 0) @@ -797,18 +1079,15 @@ float MapInfo_Get_ByName_NoFallbacks(string pFilename, int pAllowGenerate, Gamet r = _MapInfo_Generate(pFilename); if(!r) return 0; + MapInfo_Map_title = MapInfo_title_sans_author(MapInfo_Map_title); fh = fopen(fn, FILE_WRITE); fputs(fh, strcat("title ", MapInfo_Map_title, "\n")); fputs(fh, strcat("description ", MapInfo_Map_description, "\n")); fputs(fh, strcat("author ", MapInfo_Map_author, "\n")); if(_MapInfo_Map_worldspawn_music != "") { - if( - substring(_MapInfo_Map_worldspawn_music, strlen(_MapInfo_Map_worldspawn_music) - 4, 4) == ".wav" - || - substring(_MapInfo_Map_worldspawn_music, strlen(_MapInfo_Map_worldspawn_music) - 4, 4) == ".ogg" - ) - fputs(fh, strcat("cdtrack ", substring(_MapInfo_Map_worldspawn_music, 0, strlen(_MapInfo_Map_worldspawn_music) - 4), "\n")); + if(strcasecmp(substring(_MapInfo_Map_worldspawn_music, -4, 4), ".wav") == 0 || strcasecmp(substring(_MapInfo_Map_worldspawn_music, -4, 4), ".ogg") == 0) + fputs(fh, strcat("cdtrack ", substring(_MapInfo_Map_worldspawn_music, 0, -4), "\n")); else fputs(fh, strcat("cdtrack ", _MapInfo_Map_worldspawn_music, "\n")); } @@ -877,7 +1156,7 @@ float MapInfo_Get_ByName_NoFallbacks(string pFilename, int pAllowGenerate, Gamet if(substring(s, 0, 1) == "_") // q3map style continue; - p = strstrofs(s, "//", 0); + float p = strstrofs(s, "//", 0); if(p >= 0) s = substring(s, 0, p); @@ -911,19 +1190,19 @@ float MapInfo_Get_ByName_NoFallbacks(string pFilename, int pAllowGenerate, Gamet { MapInfo_Map_flags |= MAPINFO_FLAG_FRUSTRATING; } - else if(t == "noautomaplist") + else if(t == "donotwant" || t == "noautomaplist") { - MapInfo_Map_flags |= MAPINFO_FLAG_NOAUTOMAPLIST; + MapInfo_Map_flags |= MAPINFO_FLAG_DONOTWANT; } else if(t == "gameversion_min") { if (cvar("gameversion") < stof(s)) - MapInfo_Map_flags |= MAPINFO_FLAG_NOAUTOMAPLIST; + MapInfo_Map_flags |= MAPINFO_FLAG_DONOTWANT; } else if(t == "type") { t = car(s); s = cdr(s); - Gametype f = MapInfo_Type_FromString(t); + Gametype f = MapInfo_Type_FromString(t, true, false); //if(WARN_COND) //LOG_WARN("Map ", pFilename, " contains the legacy 'type' keyword which is deprecated and will be removed in the future. Please migrate the mapinfo file to 'gametype'."); if(f) @@ -934,7 +1213,7 @@ float MapInfo_Get_ByName_NoFallbacks(string pFilename, int pAllowGenerate, Gamet else if(t == "gametype") { t = car(s); s = cdr(s); - Gametype f = MapInfo_Type_FromString(t); + Gametype f = MapInfo_Type_FromString(t, true, false); if(f) _MapInfo_Map_ApplyGametypeEx (s, pGametypeToSet, f); else if(WARN_COND) @@ -985,7 +1264,7 @@ float MapInfo_Get_ByName_NoFallbacks(string pFilename, int pAllowGenerate, Gamet t = car(s); s = cdr(s); bool all = t == "all"; Gametype f = NULL; - if(all || (f = MapInfo_Type_FromString(t))) + if(all || (f = MapInfo_Type_FromString(t, true, false))) { if((all ? MAPINFO_TYPE_ALL : f.m_flags) & pGametypeToSet.m_flags) { @@ -1002,7 +1281,7 @@ float MapInfo_Get_ByName_NoFallbacks(string pFilename, int pAllowGenerate, Gamet t = car(s); s = cdr(s); bool all = t == "all"; Gametype f = NULL; - if(all || (f = MapInfo_Type_FromString(t))) + if(all || (f = MapInfo_Type_FromString(t, true, false))) { if((all ? MAPINFO_TYPE_ALL : f.m_flags) & pGametypeToSet.m_flags) { @@ -1051,12 +1330,33 @@ float MapInfo_Get_ByName_NoFallbacks(string pFilename, int pAllowGenerate, Gamet } fclose(fh); +LABEL(mapinfo_handled) +#ifdef SVQC + // if the map is currently loaded we can read worldspawn fields directly + if (pFilename == mi_shortname) + { + if (MapInfo_Map_title == "<TITLE>") + if (world.message != "") + MapInfo_Map_title = world.message; + if (MapInfo_Map_author == "<AUTHOR>") + if ((s = GetField_fullspawndata(world, "author")) != "") + MapInfo_Map_author = s; + } +#endif + // Could skip removing author from title when it's source is .mapinfo + // but must always do it for world.message and .arena/.defi as VQ3 didn't support author + // so mappers tended to put it in world.message and/or longname. + MapInfo_Map_title = MapInfo_title_sans_author(MapInfo_Map_title); // may set author if not set + if(MapInfo_Map_title == "<TITLE>") - MapInfo_Map_titlestring = MapInfo_Map_bspname; + MapInfo_Map_titlestring = strcat("^2", MapInfo_Map_bspname); else if(MapInfo_isRedundant(MapInfo_Map_bspname, MapInfo_Map_title)) - MapInfo_Map_titlestring = MapInfo_Map_title; + MapInfo_Map_titlestring = strcat("^2", MapInfo_Map_title); else - MapInfo_Map_titlestring = sprintf("%s: %s", MapInfo_Map_bspname, MapInfo_Map_title); + MapInfo_Map_titlestring = sprintf("^2%s ^7// ^2%s", MapInfo_Map_bspname, MapInfo_Map_title); + + if (MapInfo_Map_author == "<AUTHOR>") + 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) @@ -1084,6 +1384,23 @@ int MapInfo_Get_ByName(string pFilename, float pAllowGenerate, Gametype pGametyp return r; } +bool MapReadSizes(string map) +{ + // TODO: implement xonotic#28 / xonvote 172 (sizes in mapinfo) + string readsize_msg = strcat("MapReadSizes ", map); + float fh = fopen(strcat("maps/", map, ".sizes"), FILE_READ); + if(fh >= 0) + { + map_minplayers = stoi(fgets(fh)); + map_maxplayers = stoi(fgets(fh)); + fclose(fh); + LOG_TRACEF(readsize_msg, ": ok, min %d max %d", map_minplayers, map_maxplayers); + return true; + } + LOG_TRACE(readsize_msg, ": not found"); + return false; +} + float MapInfo_FindName(string s) { // if there is exactly one map of prefix s, return it @@ -1142,14 +1459,15 @@ int MapInfo_CurrentFeatures() { int req = 0; // TODO: find a better way to check if weapons are required on the map - if(!(cvar("g_lms") || cvar("g_instagib") || cvar("g_overkill") || cvar("g_nix") || cvar("g_weaponarena") || !cvar("g_pickup_items") || cvar("g_race") || cvar("g_cts") || cvar("g_nexball") || cvar("g_ca"))) + if(!(cvar("g_instagib") || cvar("g_overkill") || cvar("g_nix") || cvar("g_weaponarena") || !cvar("g_pickup_items") || !cvar("g_melee_only") + || cvar("g_race") || cvar("g_cts") || cvar("g_nexball") || cvar("g_ca") || cvar("g_freezetag") || cvar("g_lms"))) req |= MAPINFO_FEATURE_WEAPONS; return req; } Gametype MapInfo_CurrentGametype() { - Gametype prev = Gametypes_from(cvar("gamecfg")); + Gametype prev = MapInfo_Type_FromString(cvar_string("gamecfg"), false, false); FOREACH(Gametypes, cvar(it.netname) && it != prev, return it); return prev ? prev : MAPINFO_TYPE_DEATHMATCH; } @@ -1233,7 +1551,7 @@ string MapInfo_ListAllAllowedMaps(float pRequiredFlags, float pForbiddenFlags) void MapInfo_LoadMapSettings_SaveGameType(Gametype t) { MapInfo_SwitchGameType(t); - cvar_set("gamecfg", ftos(t.m_id)); + cvar_set("gamecfg", t.mdl); MapInfo_LoadedGametype = t; } @@ -1253,21 +1571,38 @@ void MapInfo_LoadMapSettings(string s) // to be called from worldspawn if(MapInfo_Map_supportedGametypes == 0) { - LOG_SEVERE("Mapinfo system is not functional at all. Assuming deathmatch."); - MapInfo_Map_supportedGametypes = MAPINFO_TYPE_DEATHMATCH.m_flags; - MapInfo_LoadMapSettings_SaveGameType(MAPINFO_TYPE_DEATHMATCH); - _MapInfo_Map_ApplyGametypeEx("", MAPINFO_TYPE_DEATHMATCH, MAPINFO_TYPE_DEATHMATCH); + RandomSelection_Init(); + FOREACH(Gametypes, it.m_priority == 2, + { + MapInfo_Map_supportedGametypes |= it.m_flags; + RandomSelection_AddEnt(it, 1, 1); + }); + if(RandomSelection_chosen_ent) + t = RandomSelection_chosen_ent; + LOG_SEVEREF("Mapinfo system is not functional at all. Falling back to a preferred mode (%s).", t.mdl); + MapInfo_LoadMapSettings_SaveGameType(t); + _MapInfo_Map_ApplyGametypeEx("", t, t); return; // do not call Get_ByName! } +#if 0 + // find the lowest bit in the supported gametypes + // unnecessary now that we select one at random int _t = 1; while(!(MapInfo_Map_supportedGametypes & 1)) { _t <<= 1; MapInfo_Map_supportedGametypes = floor(MapInfo_Map_supportedGametypes >> 1); } +#endif + RandomSelection_Init(); Gametype t_prev = t; - FOREACH(Gametypes, it.m_flags == _t, { t = it; break; }); + FOREACH(Gametypes, MapInfo_Map_supportedGametypes & it.m_flags, + { + RandomSelection_AddEnt(it, 1, it.m_priority); + }); + if(RandomSelection_chosen_ent) + t = RandomSelection_chosen_ent; // t is now a supported mode! LOG_WARNF("can't play the selected map in the given game mode (%s). Falling back to a supported mode (%s).", t_prev.mdl, t.mdl);