#if defined(CSQC)
#include "../client/defs.qh"
#include "util.qh"
#include "buffs/all.qh"
#include "weapons/all.qh"
#include "mapinfo.qh"
#elif defined(MENUQC)
#elif defined(SVQC)
#include "util.qh"
#include "buffs/all.qh"
#include "monsters/all.qh"
#include "mapinfo.qh"
#endif
// generic string stuff
int _MapInfo_Cache_Active;
int _MapInfo_Cache_DB_NameToIndex;
int _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 == "")
{
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;
if(!_MapInfo_globopen)
return string_null;
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_globopen = 0;
}
MapInfo_Cache_Invalidate();
_MapInfo_globhandle = search_begin("maps/*.bsp", true, true);
if(_MapInfo_globhandle >= 0)
{
_MapInfo_globcount = search_getsize(_MapInfo_globhandle);
_MapInfo_globopen = 1;
}
else
_MapInfo_globcount = 0;
}
// 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(int pGametype, int pFeatures, int pFlagsRequired, int pFlagsForbidden, bool pAbortOnGenerate)
{
float i, j;
if (!_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)
{
LOG_TRACE("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_FilterString(string sf)
{
// this function further filters _MapInfo_filtered, which is prepared by MapInfo_FilterGametype by string
float i, j;
string title;
for(i = 0, j = -1; i < MapInfo_count; ++i)
{
if (MapInfo_Get_ByID(i))
{
// prepare for keyword filter
if (MapInfo_Map_title && strstrofs(MapInfo_Map_title, "
", 0) == -1)
title = MapInfo_Map_title;
else
title = MapInfo_Map_bspname;
// keyword filter
if((strstrofs(strtolower(title), strtolower(sf), 0)) >= 0)
bufstr_set(_MapInfo_filtered, ++j, bufstr_get(_MapInfo_filtered, 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);
}
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;
LOG_INFO("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 = "";
mapMins = '0 0 0';
mapMaxs = '0 0 0';
for (;;)
{
if (!((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 == "invasion_spawnpoint")
MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_INVASION;
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(startsWith(v, "monster_"))
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
}
}
}
if(inWorldspawn)
{
LOG_INFO(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_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(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;
}
LOG_TRACE("-> diameter ", ftos(diameter));
LOG_TRACE("; spawnpoints ", ftos(spawnpoints));
LOG_TRACE("; modes ", ftos(MapInfo_Map_supportedGametypes), "\n");
fclose(fh);
return r;
}
void _MapInfo_Map_Reset()
{
MapInfo_Map_title = "";
MapInfo_Map_titlestring = "";
MapInfo_Map_description = "";
MapInfo_Map_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_LMS: return "9 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, int pWantedType, int pThisType, int 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_CA)
{
sa = car(s);
if(sa != "")
cvar_set("g_ca_teams", sa);
s = cdr(s);
}
if(pWantedType == MAPINFO_TYPE_FREEZETAG)
{
sa = car(s);
if(sa != "")
cvar_set("g_freezetag_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)
{
FOREACH(Gametypes, it.items == t, LAMBDA(return it.model2));
return "";
}
float _MapInfo_GetTeamPlayBool(float t)
{
FOREACH(Gametypes, it.items == t, LAMBDA(return it.team));
return false;
}
void _MapInfo_Map_ApplyGametypeEx(string s, int pWantedType, int pThisType)
{
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"));
FOREACH(Gametypes, true, LAMBDA(it.m_parse_mapinfo(string_null, string_null)));
string fraglimit_normal = string_null;
string fraglimit_teams = string_null;
for (s = strcat(_MapInfo_GetDefaultEx(pWantedType), " ", s); s != ""; s = cdr(s)) {
string sa = car(s);
if (sa == "") continue;
int p = strstrofs(sa, "=", 0);
if (p < 0) {
LOG_WARNINGF("Invalid gametype setting in mapinfo for gametype %s: %s\n", MapInfo_Type_ToString(pWantedType), sa);
continue;
}
string k = substring(sa, 0, p);
string v = substring(sa, p + 1, -1);
bool handled = true;
switch (k) {
case "timelimit":
{
cvar_set("timelimit", v);
break;
}
case "leadlimit":
{
cvar_set("leadlimit", v);
break;
}
case "pointlimit":
case "fraglimit":
case "lives":
case "laplimit":
case "caplimit":
{
fraglimit_normal = v;
break;
}
case "teampointlimit":
case "teamlaplimit":
{
fraglimit_teams = v;
break;
}
default:
{
handled = false;
break;
}
}
FOREACH(Gametypes, true, LAMBDA(handled |= it.m_parse_mapinfo(k, v)));
if (!handled)
LOG_WARNINGF("Invalid gametype setting in mapinfo for gametype %s: %s\n", MapInfo_Type_ToString(pWantedType), sa);
}
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);
}
}
Gametype MapInfo_Type(int t)
{
FOREACH(Gametypes, it.items == t, LAMBDA(return it));
return NULL;
}
int MapInfo_Type_FromString(string t)
{
#define deprecate(from, to) do { \
if (t == #from) { \
string replacement = #to; \
LOG_WARNINGF("MapInfo_Type_FromString (probably %s): using deprecated name '%s'. Should use '%s'.\n", MapInfo_Map_bspname, t, replacement); \
t = replacement; \
} \
} while (0)
deprecate(nexball, nb);
deprecate(freezetag, ft);
deprecate(keepaway, ka);
deprecate(invasion, inv);
deprecate(assault, as);
deprecate(race, rc);
if (t == "all") return MAPINFO_TYPE_ALL;
FOREACH(Gametypes, it.mdl == t, LAMBDA(return it.items));
return 0;
#undef deprecate
}
string MapInfo_Type_Description(float t)
{
FOREACH(Gametypes, it.items == t, LAMBDA(return it.gametype_description));
return "";
}
string MapInfo_Type_ToString(float t)
{
if(t == MAPINFO_TYPE_ALL)
return "all";
FOREACH(Gametypes, it.items == t, LAMBDA(return it.mdl));
return "";
}
string MapInfo_Type_ToText(float t)
{
FOREACH(Gametypes, it.items == t, LAMBDA(return it.message));
/* xgettext:no-c-format */
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)
LOG_INFO("Map ", pFilename, " references not existing config file ", s, "\n");
else
{
for (;;)
{
if (!((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
LOG_INFO("Map ", pFilename, " uses too many levels of inclusion\n");
}
else if(t == "")
LOG_INFO("Map ", pFilename, " contains a potentially harmful setting, ignored\n");
else if (!cvar_value_issafe(t))
LOG_INFO("Map ", pFilename, " contains a potentially harmful setting, ignored\n");
else if (!cvar_value_issafe(s))
LOG_INFO("Map ", pFilename, " contains a potentially harmful setting, ignored\n");
else if(matchacl(MAPINFO_SETTEMP_ACL_SYSTEM, t) <= 0)
LOG_INFO("Map ", pFilename, " contains a potentially harmful setting, ignored\n");
else if(matchacl(acl, t) <= 0)
LOG_INFO("Map ", pFilename, " contains a denied setting, ignored\n");
else
{
if(type == 0) // server set
{
LOG_TRACE("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
{
LOG_TRACE("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, int pAllowGenerate, int pGametypeToSet)
{
string fn;
string s, t;
float fh;
int f, i;
float r, n, p;
string acl;
acl = MAPINFO_SETTEMP_ACL_USER;
if(strstrofs(pFilename, "/", 0) >= 0)
{
LOG_INFO("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 vehicles\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!");
}
LOG_INFO("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 (!((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 == "monsters") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_MONSTERS;
else if(t == "new_toys") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_WEAPONS;
else
LOG_TRACE("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 == "noautomaplist")
{
MapInfo_Map_flags |= MAPINFO_FLAG_NOAUTOMAPLIST;
}
else if(t == "type")
{
t = car(s); s = cdr(s);
f = MapInfo_Type_FromString(t);
LOG_WARNING("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
LOG_TRACE("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
LOG_TRACE("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 == "")
LOG_INFO("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 != "")
LOG_INFO("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)
LOG_INFO("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
{
LOG_TRACE("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
{
LOG_TRACE("Map ", pFilename, " has a client setting for unknown game type ", t, ", ignored\n");
}
}
else if(t == "fog")
{
if (!cvar_value_issafe(s))
LOG_INFO("Map ", pFilename, " contains a potentially harmful fog setting, ignored\n");
else
MapInfo_Map_fog = s;
}
else if(t == "cdtrack")
{
t = car(s); s = cdr(s);
// We do this only if pGametypeToSet even though this
// content is theoretically game type independent,
// because MapInfo_Map_clientstuff contains otherwise
// game type dependent stuff. That way this value stays
// empty when not setting a game type to not set any
// false expectations.
if(pGametypeToSet)
{
if (!cvar_value_issafe(t))
LOG_INFO("Map ", pFilename, " contains a potentially harmful cdtrack, ignored\n");
else
MapInfo_Map_clientstuff = strcat(
MapInfo_Map_clientstuff, "cd loop \"", t, "\"\n"
);
}
}
else
LOG_TRACE("Map ", pFilename, " provides unknown info item ", t, ", ignored\n");
}
fclose(fh);
if(MapInfo_Map_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;
LOG_TRACE("Map ", pFilename, " supports no game types, ignored\n");
return 0;
}
float MapInfo_Get_ByName(string pFilename, float pAllowGenerate, int 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 (!(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;
}
int MapInfo_CurrentFeatures()
{
int req = 0;
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")))
req |= MAPINFO_FEATURE_WEAPONS;
return req;
}
int MapInfo_CurrentGametype()
{
int prev = cvar("gamecfg");
FOREACH(Gametypes, cvar(it.netname) && it.items != prev, LAMBDA(return it.items));
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(int t)
{
FOREACH(Gametypes, true, LAMBDA(
cvar_set(it.netname, (it.items == t) ? "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 type, float pRequiredFlags, float pForbiddenFlags)
{
string out;
float i;
// to make absolutely sure:
MapInfo_Enumerate();
MapInfo_FilterGametype(type, 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"))
{
LOG_INFO("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)
{
LOG_INFO("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!
LOG_INFO("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;
}
}
int MapInfo_ForbiddenFlags()
{
int f = MAPINFO_FLAG_FORBIDDEN;
#ifndef MENUQC
if (!cvar("g_maplist_allow_hidden"))
#endif
f |= MAPINFO_FLAG_HIDDEN;
if (!cvar("g_maplist_allow_frustrating"))
f |= MAPINFO_FLAG_FRUSTRATING;
return f;
}
int MapInfo_RequiredFlags()
{
int f = 0;
if(cvar("g_maplist_allow_frustrating") > 1)
f |= MAPINFO_FLAG_FRUSTRATING;
return f;
}