X-Git-Url: http://git.xonotic.org/?a=blobdiff_plain;f=qcsrc%2Fcommon%2Fmapinfo.qc;h=82a7e758413de31ffd73eae094b93717c67fb678;hb=318c2deead2bdcdc9ff69d118f2ebc1ff0795c7a;hp=29538b621a4f8f586a0066803c36985fd5a5187c;hpb=02780fb9caead37e8d82121aa8612d1245700e4d;p=xonotic%2Fxonotic-data.pk3dir.git diff --git a/qcsrc/common/mapinfo.qc b/qcsrc/common/mapinfo.qc index 29538b621..82a7e7584 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,7 +613,7 @@ 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; @@ -605,12 +629,13 @@ Gametype MapInfo_Type_FromString(string gtype) case "ffa": replacement = "dm"; do_warn = false; break; case "cctf": case "oneflag": replacement = "ctf"; do_warn = false; break; - case "team": replacement = "tdm"; 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 != "") { - if(do_warn && WARN_COND) + 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; } @@ -637,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 @@ -671,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) @@ -681,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; @@ -761,16 +790,18 @@ float MapInfo_isRedundant(string fn, string t) return false; } -bool _MapInfo_ParseArena(string arena_filename, int fh, string pFilename, Gametype pGametypeToSet, bool isdefi) +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 = (arena_filename == strcat("scripts/", pFilename, ((isdefi) ? ".defi" : ".arena"))); // if the map is using the fallback, just accept the first found mapinfo (it's probably correct!) + 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 (;;) { @@ -780,12 +811,8 @@ bool _MapInfo_ParseArena(string arena_filename, int fh, string pFilename, Gamety // catch different sorts of comments if(s == "") // empty lines continue; - if(substring(s, 0, 1) == "#") // UNIX style - continue; if(substring(s, 0, 2) == "//") // C++ style continue; - if(substring(s, 0, 1) == "_") // q3map style - continue; if(strstrofs(s, "{", 0) >= 0) { if(in_brackets) @@ -809,10 +836,17 @@ bool _MapInfo_ParseArena(string arena_filename, int fh, string pFilename, Gamety if(stored_Map_title != "") MapInfo_Map_title = stored_Map_title; MapInfo_Map_author = stored_Map_author; - FOREACH(Gametypes, it.m_flags & stored_supportedGametypes, + if(isgenerator) + MapInfo_Map_supportedGametypes = stored_supportedGametypes; + else { - _MapInfo_Map_ApplyGametype ("", pGametypeToSet, it, true); - }); + 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 @@ -822,6 +856,8 @@ bool _MapInfo_ParseArena(string arena_filename, int fh, string pFilename, Gamety stored_Map_title = ""; stored_Map_author = ""; stored_supportedGametypes = 0; + stored_supportedFeatures = 0; + stored_flags = 0; continue; } } @@ -864,11 +900,17 @@ bool _MapInfo_ParseArena(string arena_filename, int fh, string pFilename, Gamety else if(t == "type") { // if there is a valid gametype in this .arena file, include it in the menu - MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_WEAPONS; + stored_supportedFeatures |= MAPINFO_FEATURE_WEAPONS; // type in quake 3 holds all the supported gametypes, so we must loop through all of them - FOREACH_WORD(s, true, + // 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); + Gametype f = MapInfo_Type_FromString(it, false, true); if(f) stored_supportedGametypes |= f.m_flags; }); @@ -893,47 +935,61 @@ bool _MapInfo_ParseArena(string arena_filename, int fh, string pFilename, Gamety return false; } -#if defined(CSQC) || defined(MENUQC) -string(string filename) whichpack = #503; -#endif +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 base_pack = whichpack(strcat("maps/", pFilename, ".bsp")); 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 fallback; + return _MapInfo_CheckArenaFile(fallback, pFilename); - int glob = search_begin(strcat("scripts/*", extension), true, true); + int glob = search_packfile_begin(strcat("scripts/*", extension), true, true, base_pack); if(glob < 0) - return fallback; + return _MapInfo_CheckArenaFile(fallback, pFilename); int n = search_getsize(glob); for(int j = 0; j < n; ++j) { string file = search_getfilename(glob, j); - if(whichpack(file) != base_pack) - continue; // not in the same pk3! - - int fh = fopen(file, FILE_READ); - if(fh < 0) - continue; // how? - for(string s; (s = fgets(fh)); ) + if(_MapInfo_CheckArenaFile(file, pFilename) != "") { - int offset = strstrofs(s, "map", 0); - if(offset >= 0) - { - if(strstrofs(strtolower(s), strcat("\"", strtolower(pFilename), "\""), offset) >= 0) // quake 3 is case insensitive - { - fclose(fh); - search_end(glob); - return file; // FOUND IT! - } - } + search_end(glob); + return file; } - fclose(fh); } search_end(glob); - return fallback; // if we get here, a valid .arena file could not be found + 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 @@ -967,21 +1023,28 @@ float MapInfo_Get_ByName_NoFallbacks(string pFilename, int pAllowGenerate, Gamet fh = fopen(fn, FILE_READ); if(fh < 0) { - bool isdefi = false; - // try for a .arena or .defi file if no .mapinfo exists - fn = _MapInfo_FindArenaFile(pFilename, ".arena"); - fh = fopen(fn, FILE_READ); - if(fh < 0) + if(autocvar_g_mapinfo_q3compat) // use arena data instead of generating a mapinfo file { - isdefi = true; - fn = _MapInfo_FindArenaFile(pFilename, ".defi"); - fh = fopen(fn, FILE_READ); - } - if(fh >= 0) - { - _MapInfo_Map_Reset(); - if(_MapInfo_ParseArena(fn, fh, pFilename, pGametypeToSet, isdefi)) - goto mapinfo_handled; // skip generation + 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"); @@ -1000,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")); } @@ -1120,7 +1179,7 @@ float MapInfo_Get_ByName_NoFallbacks(string pFilename, int pAllowGenerate, Gamet 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) @@ -1131,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) @@ -1182,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) { @@ -1199,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) { @@ -1282,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 @@ -1340,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; @@ -1348,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; } @@ -1432,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; } @@ -1452,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);