// generic string stuff float _MapInfo_Cache_Active; float _MapInfo_Cache_DB_NameToIndex; float _MapInfo_Cache_Buf_IndexToMapData; void MapInfo_Cache_Destroy() { if(!_MapInfo_Cache_Active) return; db_close(_MapInfo_Cache_DB_NameToIndex); buf_del(_MapInfo_Cache_Buf_IndexToMapData); _MapInfo_Cache_Active = 0; } void MapInfo_Cache_Create() { MapInfo_Cache_Destroy(); _MapInfo_Cache_DB_NameToIndex = db_create(); _MapInfo_Cache_Buf_IndexToMapData = buf_create(); _MapInfo_Cache_Active = 1; } void MapInfo_Cache_Invalidate() { if(!_MapInfo_Cache_Active) return; MapInfo_Cache_Create(); } void MapInfo_Cache_Store() { float i; string s; if(!_MapInfo_Cache_Active) return; s = db_get(_MapInfo_Cache_DB_NameToIndex, MapInfo_Map_bspname); if(!s) // empty string is NOT valid here! { i = buf_getsize(_MapInfo_Cache_Buf_IndexToMapData); db_put(_MapInfo_Cache_DB_NameToIndex, MapInfo_Map_bspname, ftos(i)); } else i = stof(s); // now store all the stuff bufstr_set(_MapInfo_Cache_Buf_IndexToMapData, i, MapInfo_Map_bspname); bufstr_set(_MapInfo_Cache_Buf_IndexToMapData, ++i, MapInfo_Map_title); 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, ftos(MapInfo_Map_supportedFeatures)); bufstr_set(_MapInfo_Cache_Buf_IndexToMapData, ++i, ftos(MapInfo_Map_flags)); } float MapInfo_Cache_Retrieve(string map) { float i; string s; if(!_MapInfo_Cache_Active) return 0; s = db_get(_MapInfo_Cache_DB_NameToIndex, map); if(!s) return 0; i = stof(s); // now retrieve all the stuff MapInfo_Map_bspname = bufstr_get(_MapInfo_Cache_Buf_IndexToMapData, i); MapInfo_Map_title = bufstr_get(_MapInfo_Cache_Buf_IndexToMapData, ++i); 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_supportedFeatures = stof(bufstr_get(_MapInfo_Cache_Buf_IndexToMapData, ++i)); MapInfo_Map_flags = stof(bufstr_get(_MapInfo_Cache_Buf_IndexToMapData, ++i)); return 1; } // GLOB HANDLING (for all BSP files) float _MapInfo_globopen; float _MapInfo_globcount; float _MapInfo_globhandle; string _MapInfo_GlobItem(float i) { string s; s = search_getfilename(_MapInfo_globhandle, i); return substring(s, 5, strlen(s) - 9); // without maps/ and .bsp } void MapInfo_Enumerate() { if(_MapInfo_globopen) search_end(_MapInfo_globhandle); MapInfo_Cache_Invalidate(); _MapInfo_globhandle = search_begin("maps/*.bsp", TRUE, TRUE); _MapInfo_globcount = search_getsize(_MapInfo_globhandle); _MapInfo_globopen = 1; } // filter the info by game type mask (updates MapInfo_count) // float _MapInfo_filtered; float _MapInfo_filtered_allocated; float MapInfo_FilterList_Lookup(float i) { return stof(bufstr_get(_MapInfo_filtered, i)); } void _MapInfo_FilterList_swap(float i, float j, entity pass) { string h; h = bufstr_get(_MapInfo_filtered, i); bufstr_set(_MapInfo_filtered, i, bufstr_get(_MapInfo_filtered, j)); bufstr_set(_MapInfo_filtered, j, h); } float _MapInfo_FilterList_cmp(float i, float j, entity pass) { string a, b; a = _MapInfo_GlobItem(stof(bufstr_get(_MapInfo_filtered, i))); b = _MapInfo_GlobItem(stof(bufstr_get(_MapInfo_filtered, j))); return strcasecmp(a, b); } float MapInfo_FilterGametype(float pGametype, float pFeatures, float pFlagsRequired, float pFlagsForbidden, float pAbortOnGenerate) { float i, j; if not(_MapInfo_filtered_allocated) { _MapInfo_filtered_allocated = 1; _MapInfo_filtered = buf_create(); } MapInfo_count = 0; for(i = 0, j = -1; i < _MapInfo_globcount; ++i) { if(MapInfo_Get_ByName(_MapInfo_GlobItem(i), 1, 0) == 2) // if we generated one... BAIL OUT and let the caller continue in the next frame. if(pAbortOnGenerate) { dprint("Autogenerated a .mapinfo, doing the rest later.\n"); MapInfo_progress = i / _MapInfo_globcount; return 0; } if((MapInfo_Map_supportedGametypes & pGametype) != 0) if((MapInfo_Map_supportedFeatures & pFeatures) == pFeatures) if((MapInfo_Map_flags & pFlagsForbidden) == 0) if((MapInfo_Map_flags & pFlagsRequired) == pFlagsRequired) bufstr_set(_MapInfo_filtered, ++j, ftos(i)); } MapInfo_count = j + 1; MapInfo_ClearTemps(); // sometimes the glob isn't sorted nicely, so fix it here... heapsort(MapInfo_count, _MapInfo_FilterList_swap, _MapInfo_FilterList_cmp, world); return 1; } void MapInfo_Filter_Free() { if(_MapInfo_filtered_allocated) { buf_del(_MapInfo_filtered); _MapInfo_filtered_allocated = 0; } } // load info about the i-th map into the MapInfo_Map_* globals string MapInfo_BSPName_ByID(float i) { return _MapInfo_GlobItem(MapInfo_FilterList_Lookup(i)); } string unquote(string s) { float i, j, l; l = strlen(s); j = -1; for(i = 0; i < l; ++i) { string ch; ch = substring(s, i, 1); if(ch != " ") if(ch != "\"") { for(j = strlen(s) - i - 1; j > 0; --j) { ch = substring(s, i+j, 1); if(ch != " ") if(ch != "\"") return substring(s, i, j+1); } return substring(s, i, 1); } } return ""; } float MapInfo_Get_ByID(float i) { if(MapInfo_Get_ByName(MapInfo_BSPName_ByID(i), 0, 0)) return 1; return 0; } string _MapInfo_Map_worldspawn_music; float _MapInfo_Generate(string pFilename) // 0: failure, 1: ok ent, 2: ok bsp { string fn; float fh; string s, k, v; vector o; float i; float inWorldspawn; float r; float twoBaseModes; float diameter, spawnpoints; float spawnplaces; vector mapMins, mapMaxs; r = 1; fn = strcat("maps/", pFilename, ".ent"); fh = fopen(fn, FILE_READ); if(fh < 0) { r = 2; fn = strcat("maps/", pFilename, ".bsp"); fh = fopen(fn, FILE_READ); } if(fh < 0) return 0; print("Analyzing ", fn, " to generate initial mapinfo\n"); inWorldspawn = 2; MapInfo_Map_flags = 0; MapInfo_Map_supportedGametypes = 0; spawnpoints = 0; spawnplaces = 0; _MapInfo_Map_worldspawn_music = ""; for(;;) { if not((s = fgets(fh))) break; if(inWorldspawn == 1) if(startsWith(s, "}")) inWorldspawn = 0; k = unquote(car(s)); v = unquote(cdr(s)); if(inWorldspawn) { if(k == "classname" && v == "worldspawn") inWorldspawn = 1; else if(k == "author") MapInfo_Map_author = v; else if(k == "_description") MapInfo_Map_description = v; else if(k == "music") _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 == "origin") { o = stov(strcat("'", v, "'")); mapMins_x = min(mapMins_x, o_x); mapMins_y = min(mapMins_y, o_y); mapMins_z = min(mapMins_z, o_z); mapMaxs_x = max(mapMaxs_x, o_x); mapMaxs_y = max(mapMaxs_y, o_y); mapMaxs_z = max(mapMaxs_z, o_z); } else if(k == "race_place") { if(stof(v) > 0) spawnplaces = 1; } else if(k == "classname") { if(v == "dom_controlpoint") MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_DOMINATION; else if(v == "item_flag_team2") MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CTF; else if(v == "team_CTF_blueflag") MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CTF; else if(v == "runematch_spawn_point") MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_RUNEMATCH; else if(v == "target_assault_roundend") MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_ASSAULT; else if(v == "onslaught_generator") MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_ONSLAUGHT; else if(substring(v, 0, 8) == "nexball_" || substring(v, 0, 4) == "ball") MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_NEXBALL; else if(v == "info_player_team1") ++spawnpoints; else if(v == "info_player_team2") ++spawnpoints; else if(v == "info_player_start") ++spawnpoints; else if(v == "info_player_deathmatch") ++spawnpoints; else if(v == "trigger_race_checkpoint") MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_RACE; else if(v == "target_startTimer") MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CTS; else if(v == "weapon_nex") { } else if(v == "weapon_railgun") { } else if(startsWith(v, "weapon_")) MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_WEAPONS; else if(startsWith(v, "turret_")) MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_TURRETS; else if(startsWith(v, "vehicle_")) MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_VEHICLES; else if(v == "target_music" || v == "trigger_music") _MapInfo_Map_worldspawn_music = string_null; // don't use regular BGM } } } if(inWorldspawn) { print(fn, " ended still in worldspawn, BUG\n"); return 0; } diameter = vlen(mapMaxs - mapMins); twoBaseModes = MapInfo_Map_supportedGametypes & (MAPINFO_TYPE_CTF | MAPINFO_TYPE_ASSAULT | MAPINFO_TYPE_RACE | MAPINFO_TYPE_NEXBALL); if(twoBaseModes && (MapInfo_Map_supportedGametypes == twoBaseModes)) { // we have a CTF-only or Assault-only map. Don't add other modes then, // as the map is too symmetric for them. } else { MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_DEATHMATCH; // DM always works MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_RUNEMATCH; // Rune always works MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_LMS; // LMS always works MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_KEEPAWAY; // Keepaway always works if(spawnpoints >= 8 && diameter > 4096) { MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_TEAM_DEATHMATCH; MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_FREEZETAG; MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CA; } if( diameter < 4096) MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_ARENA; if(spawnpoints >= 12 && diameter > 5120) MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_KEYHUNT; } if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_RACE) if(!spawnplaces) { MapInfo_Map_supportedGametypes &~= MAPINFO_TYPE_RACE; MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CTS; } dprint("-> diameter ", ftos(diameter)); dprint("; spawnpoints ", ftos(spawnpoints)); dprint("; modes ", ftos(MapInfo_Map_supportedGametypes), "\n"); fclose(fh); return r; } void _MapInfo_Map_Reset() { MapInfo_Map_title = ""; MapInfo_Map_titlestring = "<TITLE>"; MapInfo_Map_description = "<DESCRIPTION>"; MapInfo_Map_author = "<AUTHOR>"; MapInfo_Map_supportedGametypes = 0; MapInfo_Map_supportedFeatures = 0; MapInfo_Map_flags = 0; MapInfo_Map_clientstuff = ""; MapInfo_Map_fog = ""; MapInfo_Map_mins = '0 0 0'; MapInfo_Map_maxs = '0 0 0'; } string _MapInfo_GetDefault(float t) { switch(t) { case MAPINFO_TYPE_DEATHMATCH: return "30 20 0"; case MAPINFO_TYPE_TEAM_DEATHMATCH: return "50 20 2 0"; case MAPINFO_TYPE_DOMINATION: return "200 20 0"; case MAPINFO_TYPE_CTF: return "300 20 10 0"; case MAPINFO_TYPE_RUNEMATCH: return "200 20 0"; case MAPINFO_TYPE_LMS: return "9 20 0"; case MAPINFO_TYPE_ARENA: return "10 20 0"; case MAPINFO_TYPE_CA: return "10 20 0"; case MAPINFO_TYPE_KEYHUNT: return "1000 20 3 0"; case MAPINFO_TYPE_ASSAULT: return "20 0"; case MAPINFO_TYPE_RACE: return "20 5 7 15 0"; case MAPINFO_TYPE_ONSLAUGHT: return "20 0"; case MAPINFO_TYPE_NEXBALL: return "5 20 0"; case MAPINFO_TYPE_CTS: return "20 0 0"; case MAPINFO_TYPE_FREEZETAG: return "10 20 0"; // NOTE: DO NOT ADD ANY MORE GAME TYPES HERE // THIS IS JUST LEGACY SUPPORT FOR NEXUIZ MAPS // ONLY ADD NEW STUFF TO _MapInfo_GetDefaultEx // THIS FUNCTION WILL EVENTUALLY BE REMOVED default: return ""; } } void _MapInfo_Map_ApplyGametype(string s, float pWantedType, float pThisType, float load_default) { string sa; MapInfo_Map_supportedGametypes |= pThisType; if(!(pThisType & pWantedType)) return; if(load_default) _MapInfo_Map_ApplyGametype(_MapInfo_GetDefault(pThisType), pWantedType, pThisType, FALSE); if(pWantedType == MAPINFO_TYPE_ASSAULT || pWantedType == MAPINFO_TYPE_ONSLAUGHT || pWantedType == MAPINFO_TYPE_RACE || pWantedType == MAPINFO_TYPE_CTS) // these modes don't use fraglimit { cvar_set("fraglimit", "0"); } else { sa = car(s); if(sa != "") cvar_set("fraglimit", sa); s = cdr(s); } sa = car(s); if(sa != "") cvar_set("timelimit", sa); s = cdr(s); if(pWantedType == MAPINFO_TYPE_TEAM_DEATHMATCH) { sa = car(s); if(sa != "") cvar_set("g_tdm_teams", sa); s = cdr(s); } if(pWantedType == MAPINFO_TYPE_KEYHUNT) { sa = car(s); if(sa != "") cvar_set("g_keyhunt_teams", sa); s = cdr(s); } if(pWantedType == MAPINFO_TYPE_CTF) { sa = car(s); if(sa != "") cvar_set("fraglimit", sa); s = cdr(s); } /* keepaway wuz here if(pWantedType == MAPINFO_TYPE_KEEPAWAY) { sa = car(s); if(sa != "") cvar_set("fraglimit", sa); s = cdr(s); } */ // rc = timelimit timelimit_qualification laps laps_teamplay if(pWantedType == MAPINFO_TYPE_RACE) { sa = car(s); if(sa == "") sa = cvar_string("timelimit"); cvar_set("g_race_qualifying_timelimit", sa); s = cdr(s); sa = car(s); if(sa != "") if(cvar("g_race_teams") < 2) cvar_set("fraglimit", sa); s = cdr(s); sa = car(s); if(sa != "") if(cvar("g_race_teams") >= 2) cvar_set("fraglimit", sa); s = cdr(s); } if(pWantedType == MAPINFO_TYPE_CTS) { sa = car(s); // this is the skill of the map // not parsed by anything yet // for map databases //if(sa != "") // cvar_set("fraglimit", sa); s = cdr(s); } if(pWantedType == MAPINFO_TYPE_ASSAULT || pWantedType == MAPINFO_TYPE_ONSLAUGHT || pWantedType == MAPINFO_TYPE_CTS) // these modes don't use fraglimit { cvar_set("leadlimit", "0"); } else { sa = car(s); if(sa != "") cvar_set("leadlimit", sa); s = cdr(s); } } string _MapInfo_GetDefaultEx(float t) { entity e; for(e = MapInfo_Type_first; e; e = e.enemy) if(t == e.weapons) return e.model2; return ""; } void _MapInfo_Map_ApplyGametypeEx(string s, float pWantedType, float pThisType) { string sa, k, v; float p; string fraglimit_normal; string fraglimit_teams; MapInfo_Map_supportedGametypes |= pThisType; if(!(pThisType & pWantedType)) return; // reset all the cvars to their defaults cvar_set("timelimit", cvar_defstring("timelimit")); cvar_set("leadlimit", cvar_defstring("leadlimit")); cvar_set("fraglimit", cvar_defstring("fraglimit")); cvar_set("g_tdm_teams", cvar_defstring("g_tdm_teams")); cvar_set("g_keyhunt_teams", cvar_defstring("g_keyhunt_teams")); cvar_set("g_domination_default_teams", cvar_defstring("g_domination_default_teams")); cvar_set("g_race_qualifying_timelimit", cvar_defstring("g_race_qualifying_timelimit")); fraglimit_normal = string_null; fraglimit_teams = string_null; s = strcat(_MapInfo_GetDefaultEx(pWantedType), " ", s); while(s != "") { sa = car(s); s = cdr(s); if(sa == "") continue; p = strstrofs(sa, "=", 0); if(p < 0) { k = "timelimit"; v = s; } else { k = substring(sa, 0, p); v = substring(sa, p+1, -1); } if(k == "timelimit") { cvar_set("timelimit", v); } else if(k == "leadlimit") { cvar_set("leadlimit", v); } else if(k == "pointlimit" || k == "fraglimit" || k == "lives" || k == "laplimit" || k == "caplimit") { fraglimit_normal = v; } else if(k == "teampointlimit" || k == "teamlaplimit") { fraglimit_teams = v; } else if(k == "teams") { cvar_set("g_tdm_teams", v); cvar_set("g_keyhunt_teams", v); cvar_set("g_domination_default_teams", v); } else if(k == "qualifying_timelimit") { cvar_set("g_race_qualifying_timelimit", v); } else if(k == "skill") { // ignore } else { print("Invalid gametype key in mapinfo: ", k, "\n"); } } if(pWantedType == MAPINFO_TYPE_RACE && cvar("g_race_teams") >= 2) { if(fraglimit_teams) cvar_set("fraglimit", fraglimit_teams); } else { if(fraglimit_normal) cvar_set("fraglimit", fraglimit_normal); } } float MapInfo_Type_FromString(string t) { entity e; if(t == "all") return MAPINFO_TYPE_ALL; for(e = MapInfo_Type_first; e; e = e.enemy) if(t == e.mdl) return e.weapons; return 0; } string MapInfo_Type_ToString(float t) { entity e; if(t == MAPINFO_TYPE_ALL) return "all"; for(e = MapInfo_Type_first; e; e = e.enemy) if(t == e.weapons) return e.mdl; return ""; } string MapInfo_Type_ToText(float t) { entity e; for(e = MapInfo_Type_first; e; e = e.enemy) if(t == e.weapons) return e.message; return _("@!#%'n Tuba Throwing"); } void _MapInfo_Parse_Settemp(string pFilename, string acl, float type, string s, float recurse) { string t; float fh, o; t = car(s); s = cdr(s); // limited support of "" and comments // remove trailing and leading " of t if(substring(t, 0, 1) == "\"") { if(substring(t, -1, 1) == "\"") t = substring(t, 1, -2); } // remove leading " of s if(substring(s, 0, 1) == "\"") { s = substring(s, 1, -1); } // remove trailing " of s, and all that follows (cvar description) o = strstrofs(s, "\"", 0); if(o >= 0) s = substring(s, 0, o); // remove // comments o = strstrofs(s, "//", 0); if(o >= 0) s = substring(s, 0, o); // remove trailing spaces while(substring(s, -1, 1) == " ") s = substring(s, 0, -2); if(t == "#include") { if(recurse > 0) { fh = fopen(s, FILE_READ); if(fh < 0) print("Map ", pFilename, " references not existing config file ", s, "\n"); else { for(;;) { if not((s = fgets(fh))) break; // 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(substring(s, 0, 4) == "set ") s = substring(s, 4, -1); if(substring(s, 0, 5) == "seta ") s = substring(s, 5, -1); _MapInfo_Parse_Settemp(pFilename, acl, type, s, recurse - 1); } fclose(fh); } } else print("Map ", pFilename, " uses too many levels of inclusion\n"); } else if(t == "") print("Map ", pFilename, " contains a potentially harmful setting, ignored\n"); else if not(cvar_value_issafe(t)) print("Map ", pFilename, " contains a potentially harmful setting, ignored\n"); else if not (cvar_value_issafe(s)) print("Map ", pFilename, " contains a potentially harmful setting, ignored\n"); else if(matchacl(MAPINFO_SETTEMP_ACL_SYSTEM, t) <= 0) print("Map ", pFilename, " contains a potentially harmful setting, ignored\n"); else if(matchacl(acl, t) <= 0) print("Map ", pFilename, " contains a denied setting, ignored\n"); else { if(type == 0) // server set { dprint("Applying temporary setting ", t, " := ", s, "\n"); if(cvar("g_campaign")) cvar_set(t, s); // this is a wrapper and is always temporary anyway; no need to backup old values then else cvar_settemp(t, s); } else { dprint("Applying temporary client setting ", t, " := ", s, "\n"); MapInfo_Map_clientstuff = strcat( MapInfo_Map_clientstuff, "cl_cmd settemp \"", t, "\" \"", s, "\"\n" ); } } } float MapInfo_isRedundant(string fn, string t) { // normalize file name fn = strreplace("_", "-", fn); // normalize visible title t = strreplace(": ", "-", t); t = strreplace(":", "-", t); t = strreplace(" ", "-", t); t = strreplace("_", "-", t); t = strreplace("'", "-", t); if(!strcasecmp(fn, t)) return TRUE; // we allow the visible title to have punctuation the file name does // not, but not vice versa t = strreplace("-", "", t); if(!strcasecmp(fn, t)) return TRUE; return FALSE; } // load info about a map by name into the MapInfo_Map_* globals float MapInfo_Get_ByName_NoFallbacks(string pFilename, float pAllowGenerate, float pGametypeToSet) { string fn; string s, t; float fh; float r, f, n, i, p; string acl; acl = MAPINFO_SETTEMP_ACL_USER; if(strstrofs(pFilename, "/", 0) >= 0) { print("Invalid character in map name, ignored\n"); return 0; } if(pGametypeToSet == 0) if(MapInfo_Cache_Retrieve(pFilename)) return 1; r = 1; MapInfo_Map_bspname = pFilename; // default all generic fields so they have "good" values in case something fails fn = strcat("maps/", pFilename, ".mapinfo"); fh = fopen(fn, FILE_READ); if(fh < 0) { fn = strcat("maps/autogenerated/", pFilename, ".mapinfo"); fh = fopen(fn, FILE_READ); if(fh < 0) { if(!pAllowGenerate) return 0; _MapInfo_Map_Reset(); r = _MapInfo_Generate(pFilename); if(!r) return 0; 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")); else fputs(fh, strcat("cdtrack ", _MapInfo_Map_worldspawn_music, "\n")); } else { n = tokenize_console(cvar_string("g_cdtracks_remaplist")); s = strcat(" ", cvar_string("g_cdtracks_dontusebydefault"), " "); for(;;) { i = floor(random() * n); if(strstrofs(s, strcat(" ", argv(i), " "), 0) < 0) break; } fputs(fh, strcat("cdtrack ", ftos(i + 1), "\n")); } if(MapInfo_Map_supportedFeatures & MAPINFO_FEATURE_WEAPONS) fputs(fh, "has weapons\n"); else fputs(fh, "// uncomment this if you added weapon pickups: has weapons\n"); if(MapInfo_Map_supportedFeatures & MAPINFO_FEATURE_TURRETS) fputs(fh, "has turrets\n"); else fputs(fh, "// uncomment this if you added turrets: has turrets\n"); if(MapInfo_Map_supportedFeatures & MAPINFO_FEATURE_VEHICLES) fputs(fh, "has weapons\n"); else fputs(fh, "// uncomment this if you added vehicles: has vehicles\n"); if(MapInfo_Map_flags & MAPINFO_FLAG_FRUSTRATING) fputs(fh, "frustrating\n"); for(i = 1; i <= MapInfo_Map_supportedGametypes; i *= 2) if(MapInfo_Map_supportedGametypes & i) fputs(fh, sprintf("gametype %s // defaults: %s\n", MapInfo_Type_ToString(i), _MapInfo_GetDefaultEx(i))); if(fexists(strcat("scripts/", pFilename, ".arena"))) fputs(fh, "settemp_for_type all sv_q3acompat_machineshotgunswap 1\n"); fputs(fh, "// optional: fog density red green blue alpha mindist maxdist\n"); fputs(fh, "// optional: settemp_for_type (all|gametypename) cvarname value\n"); fputs(fh, "// optional: clientsettemp_for_type (all|gametypename) cvarname value\n"); fputs(fh, "// optional: size mins_x mins_y mins_z maxs_x maxs_y maxs_z\n"); fputs(fh, "// optional: hidden\n"); fclose(fh); r = 2; // return r; fh = fopen(fn, FILE_READ); if(fh < 0) error("... but I just wrote it!"); } print("WARNING: autogenerated mapinfo file ", fn, " has been loaded; please edit that file and move it to maps/", pFilename, ".mapinfo\n"); } _MapInfo_Map_Reset(); for(;;) { if not((s = fgets(fh))) break; // 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; p = strstrofs(s, "//", 0); if(p >= 0) s = substring(s, 0, p); t = car(s); s = cdr(s); if(t == "title") MapInfo_Map_title = s; else if(t == "description") MapInfo_Map_description = s; else if(t == "author") MapInfo_Map_author = s; else if(t == "has") { t = car(s); s = cdr(s); if (t == "weapons") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_WEAPONS; else if(t == "turrets") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_TURRETS; else if(t == "vehicles") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_VEHICLES; else if(t == "new_toys") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_WEAPONS; else dprint("Map ", pFilename, " supports unknown feature ", t, ", ignored\n"); } else if(t == "hidden") { MapInfo_Map_flags |= MAPINFO_FLAG_HIDDEN; } else if(t == "forbidden") { MapInfo_Map_flags |= MAPINFO_FLAG_FORBIDDEN; } else if(t == "frustrating") { MapInfo_Map_flags |= MAPINFO_FLAG_FRUSTRATING; } else if(t == "type") { t = car(s); s = cdr(s); f = MapInfo_Type_FromString(t); print("Map ", pFilename, " contains the legacy 'type' keyword which is deprecated and will be removed in the future. Please migrate the mapinfo file to 'gametype'.\n"); if(f) _MapInfo_Map_ApplyGametype (s, pGametypeToSet, f, TRUE); else dprint("Map ", pFilename, " supports unknown game type ", t, ", ignored\n"); } else if(t == "gametype") { t = car(s); s = cdr(s); f = MapInfo_Type_FromString(t); if(f) _MapInfo_Map_ApplyGametypeEx (s, pGametypeToSet, f); else dprint("Map ", pFilename, " supports unknown game type ", t, ", ignored\n"); } else if(t == "size") { float a, b, c, d, e; t = car(s); s = cdr(s); a = stof(t); t = car(s); s = cdr(s); b = stof(t); t = car(s); s = cdr(s); c = stof(t); t = car(s); s = cdr(s); d = stof(t); t = car(s); s = cdr(s); e = stof(t); if(s == "") print("Map ", pFilename, " contains an incorrect size line (not enough params), syntax: size mins_x mins_y mins_z maxs_x maxs_y maxs_z\n"); else { t = car(s); s = cdr(s); f = stof(t); if(s != "") print("Map ", pFilename, " contains an incorrect size line (too many params), syntax: size mins_x mins_y mins_z maxs_x maxs_y maxs_z\n"); else { if(a >= d || b >= e || c >= f) print("Map ", pFilename, " contains an incorrect size line, mins have to be < maxs\n"); else { MapInfo_Map_mins_x = a; MapInfo_Map_mins_y = b; MapInfo_Map_mins_z = c; MapInfo_Map_maxs_x = d; MapInfo_Map_maxs_y = e; MapInfo_Map_maxs_z = f; } } } } else if(t == "settemp_for_type") { t = car(s); s = cdr(s); if((f = MapInfo_Type_FromString(t))) { if(f & pGametypeToSet) { _MapInfo_Parse_Settemp(pFilename, acl, 0, s, 1); } } else { dprint("Map ", pFilename, " has a setting for unknown game type ", t, ", ignored\n"); } } else if(t == "clientsettemp_for_type") { t = car(s); s = cdr(s); if((f = MapInfo_Type_FromString(t))) { if(f & pGametypeToSet) { _MapInfo_Parse_Settemp(pFilename, acl, 1, s, 1); } } else { dprint("Map ", pFilename, " has a client setting for unknown game type ", t, ", ignored\n"); } } else if(t == "fog") { if not(cvar_value_issafe(t)) print("Map ", pFilename, " contains a potentially harmful fog setting, ignored\n"); else MapInfo_Map_fog = s; } else if(t == "cdtrack") { if(pGametypeToSet) { if not(cvar_value_issafe(t)) print("Map ", pFilename, " contains a potentially harmful cdtrack, ignored\n"); else MapInfo_Map_clientstuff = strcat( MapInfo_Map_clientstuff, "cd loop \"", s, "\"\n" ); } } else dprint("Map ", pFilename, " provides unknown info item ", t, ", ignored\n"); } fclose(fh); if(MapInfo_Map_title == "<TITLE>") MapInfo_Map_titlestring = MapInfo_Map_bspname; else if(MapInfo_isRedundant(MapInfo_Map_bspname, MapInfo_Map_title)) MapInfo_Map_titlestring = MapInfo_Map_title; else MapInfo_Map_titlestring = sprintf(_("%s: %s"), MapInfo_Map_bspname, MapInfo_Map_title); MapInfo_Cache_Store(); if(MapInfo_Map_supportedGametypes != 0) return r; dprint("Map ", pFilename, " supports no game types, ignored\n"); return 0; } float MapInfo_Get_ByName(string pFilename, float pAllowGenerate, float pGametypeToSet) { float r = MapInfo_Get_ByName_NoFallbacks(pFilename, pAllowGenerate, pGametypeToSet); if(cvar("g_tdm_on_dm_maps")) { // if this is set, all DM maps support TDM too if not(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_TEAM_DEATHMATCH) if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DEATHMATCH) _MapInfo_Map_ApplyGametypeEx ("", pGametypeToSet, MAPINFO_TYPE_TEAM_DEATHMATCH); } if(pGametypeToSet) { if(!(MapInfo_Map_supportedGametypes & pGametypeToSet)) { 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); //return; } } return r; } float MapInfo_FindName(string s) { // if there is exactly one map of prefix s, return it // if not, return the null string // note that DP sorts glob results... so I can use a binary search float l, r, m, cmp; l = 0; r = MapInfo_count; // invariants: r is behind s, l-1 is equal or before while(l != r) { m = floor((l + r) / 2); MapInfo_FindName_match = _MapInfo_GlobItem(MapInfo_FilterList_Lookup(m)); cmp = strcasecmp(MapInfo_FindName_match, s); if(cmp == 0) return m; // found and good if(cmp < 0) l = m + 1; // l-1 is before s else r = m; // behind s } MapInfo_FindName_match = _MapInfo_GlobItem(MapInfo_FilterList_Lookup(l)); MapInfo_FindName_firstResult = l; // r == l, so: l is behind s, l-1 is before // SO: if there is any, l is the one with the right prefix // and l+1 may be one too if(l == MapInfo_count) { MapInfo_FindName_match = string_null; MapInfo_FindName_firstResult = -1; return -1; // no MapInfo_FindName_match, behind last item } if(!startsWithNocase(MapInfo_FindName_match, s)) { MapInfo_FindName_match = string_null; MapInfo_FindName_firstResult = -1; return -1; // wrong prefix } if(l == MapInfo_count - 1) return l; // last one, nothing can follow => unique if(startsWithNocase(_MapInfo_GlobItem(MapInfo_FilterList_Lookup(l + 1)), s)) { MapInfo_FindName_match = string_null; return -1; // ambigous MapInfo_FindName_match } return l; } string MapInfo_FixName(string s) { MapInfo_FindName(s); return MapInfo_FindName_match; } float MapInfo_CurrentFeatures() { float req; req = 0; if(!(cvar("g_lms") || cvar("g_minstagib") || cvar("g_nix") || cvar("g_weaponarena") || !cvar("g_pickup_items") || cvar("g_race") || cvar("g_cts") || cvar("g_nexball"))) req |= MAPINFO_FEATURE_WEAPONS; return req; } float MapInfo_CurrentGametype() { float prev; entity e; prev = cvar("gamecfg"); for(e = MapInfo_Type_first; e; e = e.enemy) if(cvar(e.netname)) if(prev != e.weapons) return e.weapons; if(prev) return prev; return MAPINFO_TYPE_DEATHMATCH; } float _MapInfo_CheckMap(string s) // returns 0 if the map can't be played with the current settings, 1 otherwise { if(!MapInfo_Get_ByName(s, 1, 0)) return 0; if((MapInfo_Map_supportedGametypes & MapInfo_CurrentGametype()) == 0) return 0; if((MapInfo_Map_supportedFeatures & MapInfo_CurrentFeatures()) != MapInfo_CurrentFeatures()) return 0; return 1; } float MapInfo_CheckMap(string s) // returns 0 if the map can't be played with the current settings, 1 otherwise { float r; r = _MapInfo_CheckMap(s); MapInfo_ClearTemps(); return r; } void MapInfo_SwitchGameType(float t) { entity e; for(e = MapInfo_Type_first; e; e = e.enemy) cvar_set(e.netname, (t == e.weapons) ? "1" : "0"); } void MapInfo_LoadMap(string s, float reinit) { MapInfo_Map_supportedGametypes = 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); //} cvar_settemp_restore(); if(reinit) localcmd(strcat("\nmap ", s, "\n")); else localcmd(strcat("\nchangelevel ", s, "\n")); } string MapInfo_ListAllowedMaps(float pRequiredFlags, float pForbiddenFlags) { string out; float i; // to make absolutely sure: MapInfo_Enumerate(); MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), pRequiredFlags, pForbiddenFlags, 0); out = ""; for(i = 0; i < MapInfo_count; ++i) out = strcat(out, " ", _MapInfo_GlobItem(MapInfo_FilterList_Lookup(i))); return substring(out, 1, strlen(out) - 1); } string MapInfo_ListAllAllowedMaps(float pRequiredFlags, float pForbiddenFlags) { string out; float i; // to make absolutely sure: MapInfo_Enumerate(); MapInfo_FilterGametype(MAPINFO_TYPE_ALL, 0, pRequiredFlags, pForbiddenFlags, 0); out = ""; for(i = 0; i < MapInfo_count; ++i) out = strcat(out, " ", _MapInfo_GlobItem(MapInfo_FilterList_Lookup(i))); MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), pRequiredFlags, pForbiddenFlags, 0); return substring(out, 1, strlen(out) - 1); } void MapInfo_LoadMapSettings_SaveGameType(float t) { MapInfo_SwitchGameType(t); cvar_set("gamecfg", ftos(t)); MapInfo_LoadedGametype = t; } void MapInfo_LoadMapSettings(string s) // to be called from worldspawn { float t; t = MapInfo_CurrentGametype(); MapInfo_LoadMapSettings_SaveGameType(t); if(!_MapInfo_CheckMap(s)) // with underscore, it keeps temps { if(cvar("g_mapinfo_allow_unsupported_modes_and_let_stuff_break")) { print("EMERGENCY: can't play the selected map in the given game mode. Working with only the override settings.\n"); _MapInfo_Map_ApplyGametypeEx("", t, t); return; // do not call Get_ByName! } if(MapInfo_Map_supportedGametypes == 0) { print("Mapinfo system is not functional at all. Assuming deathmatch.\n"); MapInfo_Map_supportedGametypes = MAPINFO_TYPE_DEATHMATCH; MapInfo_LoadMapSettings_SaveGameType(MAPINFO_TYPE_DEATHMATCH); _MapInfo_Map_ApplyGametypeEx("", MAPINFO_TYPE_DEATHMATCH, MAPINFO_TYPE_DEATHMATCH); return; // do not call Get_ByName! } t = 1; while(!(MapInfo_Map_supportedGametypes & 1)) { t *= 2; MapInfo_Map_supportedGametypes = floor(MapInfo_Map_supportedGametypes / 2); } // t is now a supported mode! print("EMERGENCY: can't play the selected map in the given game mode. Falling back to a supported mode.\n"); MapInfo_LoadMapSettings_SaveGameType(t); } MapInfo_Get_ByName(s, 1, t); } void MapInfo_ClearTemps() { MapInfo_Map_bspname = string_null; MapInfo_Map_title = string_null; MapInfo_Map_titlestring = string_null; MapInfo_Map_description = string_null; MapInfo_Map_author = string_null; MapInfo_Map_clientstuff = string_null; MapInfo_Map_supportedGametypes = 0; MapInfo_Map_supportedFeatures = 0; } void MapInfo_Shutdown() { MapInfo_ClearTemps(); MapInfo_Filter_Free(); MapInfo_Cache_Destroy(); if(_MapInfo_globopen) { search_end(_MapInfo_globhandle); _MapInfo_globhandle = -1; _MapInfo_globopen = FALSE; } } float MapInfo_ForbiddenFlags() { float f; f = MAPINFO_FLAG_FORBIDDEN; #ifndef MENUQC if not(cvar("g_maplist_allow_hidden")) #endif f |= MAPINFO_FLAG_HIDDEN; if not(cvar("g_maplist_allow_frustrating")) f |= MAPINFO_FLAG_FRUSTRATING; return f; } float MapInfo_RequiredFlags() { float f; f = 0; if(cvar("g_maplist_allow_frustrating") > 1) f |= MAPINFO_FLAG_FRUSTRATING; return f; }