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=ae387e3db9a576df164c92765a250bd1d9681923;hb=HEAD;hpb=5adc9ca82afe8f5bb4bd1346a0cf9934622818ba diff --git a/qcsrc/common/mapinfo.qc b/qcsrc/common/mapinfo.qc index ae387e3db9..e66ebb7671 100644 --- a/qcsrc/common/mapinfo.qc +++ b/qcsrc/common/mapinfo.qc @@ -1,14 +1,15 @@ #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 +int autocvar_g_mapinfo_q3compat = 1; + #ifdef MENUQC #define WARN_COND false #else @@ -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,6 +273,7 @@ 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; @@ -299,6 +299,28 @@ float _MapInfo_Generate(string pFilename) // 0: failure, 1: ok ent, 2: ok bsp mapMins = '0 0 0'; mapMaxs = '0 0 0'; + if(autocvar_g_mapinfo_q3compat == 2) // generate mapinfo using arena data + { + // try for .arena or .defi files, as they may have more accurate information + bool isdefi = false; + float arena_fh = -1; + string arena_fn = _MapInfo_FindArenaFile(pFilename, ".arena"); + if(arena_fn != "") + arena_fh = fopen(arena_fn, FILE_READ); + if(arena_fh < 0) + { + isdefi = true; + arena_fn = _MapInfo_FindArenaFile(pFilename, ".defi"); + if(arena_fn != "") + arena_fh = fopen(arena_fn, FILE_READ); + } + if(arena_fh >= 0) + { + _MapInfo_ParseArena(arena_fn, arena_fh, pFilename, NULL, isdefi, true); + fclose(arena_fh); + } + } + for (;;) { if (!((s = fgets(fh)))) @@ -320,7 +342,7 @@ 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") + else if(k == "message" && (!MapInfo_Map_title || MapInfo_Map_title == "")) { i = strstrofs(v, " by ", 0); if(MapInfo_Map_author == "<AUTHOR>" && i >= 0) @@ -373,6 +395,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 gamemodes else FOREACH(Gametypes, true, it.m_generate_mapinfo(it, v)); } @@ -391,7 +415,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 +613,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 = ""; + bool do_warn = true; switch (gtype) { case "nexball": replacement = "nb"; break; @@ -600,10 +625,18 @@ Gametype MapInfo_Type_FromString(string gtype) case "invasion": replacement = "inv"; break; case "assault": replacement = "as"; break; case "race": replacement = "rc"; break; + // quake 3 compat + case "ffa": replacement = "dm"; do_warn = false; break; + case "cctf": + case "oneflag": replacement = "ctf"; do_warn = false; break; + case "tournament": + case "tourney": replacement = "duel"; do_warn = false; break; + case "arena": if(is_q3compat) { replacement = "ca"; do_warn = false; } 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 +662,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 +699,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) @@ -673,6 +709,7 @@ void _MapInfo_Parse_Settemp(string pFilename, string acl, float type, string s, { while((s = fgets(fh))) { + 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; @@ -753,6 +790,208 @@ 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; + 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; + MapInfo_Map_author = stored_Map_author; + 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); + if(strstrofs(types, "tournament", 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) { @@ -760,7 +999,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; @@ -784,6 +1023,30 @@ float MapInfo_Get_ByName_NoFallbacks(string pFilename, int pAllowGenerate, Gamet fh = fopen(fn, FILE_READ); if(fh < 0) { + if(autocvar_g_mapinfo_q3compat) // use arena data instead of generating a mapinfo file + { + bool isdefi = false; + if(autocvar_g_mapinfo_q3compat == 1) // only parse .arena files in mode 1 + { + fn = _MapInfo_FindArenaFile(pFilename, ".arena"); + if(fn != "") + fh = fopen(fn, FILE_READ); + } + if(fh < 0 || autocvar_g_mapinfo_q3compat == 2) + { + isdefi = true; + fn = _MapInfo_FindArenaFile(pFilename, ".defi"); + if(fn != "") + fh = fopen(fn, FILE_READ); + } + if(fh >= 0) + { + _MapInfo_Map_Reset(); + if(_MapInfo_ParseArena(fn, fh, pFilename, pGametypeToSet, isdefi, false)) + goto mapinfo_handled; // skip generation + } + } + fn = strcat("maps/autogenerated/", pFilename, ".mapinfo"); fh = fopen(fn, FILE_READ); if(fh < 0) @@ -800,12 +1063,8 @@ float MapInfo_Get_ByName_NoFallbacks(string pFilename, int pAllowGenerate, Gamet 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")); } @@ -874,7 +1133,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); @@ -908,19 +1167,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) @@ -931,7 +1190,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) @@ -982,7 +1241,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) { @@ -999,7 +1258,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) { @@ -1046,6 +1305,7 @@ float MapInfo_Get_ByName_NoFallbacks(string pFilename, int pAllowGenerate, Gamet else if(WARN_COND) LOG_WARN("Map ", pFilename, " provides unknown info item ", t, ", ignored"); } + LABEL(mapinfo_handled) fclose(fh); if(MapInfo_Map_title == "<TITLE>") @@ -1081,6 +1341,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 @@ -1139,7 +1416,7 @@ int MapInfo_CurrentFeatures() { int req = 0; // TODO: find a better way to check if weapons are required on the map - if(!(cvar("g_instagib") || cvar("g_overkill") || cvar("g_nix") || cvar("g_weaponarena") || !cvar("g_pickup_items") + 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; @@ -1147,7 +1424,7 @@ int MapInfo_CurrentFeatures() Gametype MapInfo_CurrentGametype() { - Gametype prev = REGISTRY_GET(Gametypes, 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; } @@ -1231,7 +1508,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; } @@ -1251,21 +1528,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);