]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/mapinfo.qc
added string filter to map list
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / mapinfo.qc
1 #if defined(CSQC)
2         #include "../dpdefs/csprogsdefs.qh"
3     #include "../client/defs.qh"
4     #include "util.qh"
5     #include "buffs.qh"
6     #include "weapons/weapons.qh"
7     #include "mapinfo.qh"
8 #elif defined(MENUQC)
9 #elif defined(SVQC)
10         #include "../dpdefs/progsdefs.qh"
11     #include "../dpdefs/dpextensions.qh"
12     #include "util.qh"
13     #include "buffs.qh"
14     #include "monsters/monsters.qh"
15     #include "mapinfo.qh"
16 #endif
17
18 // generic string stuff
19
20 int _MapInfo_Cache_Active;
21 int _MapInfo_Cache_DB_NameToIndex;
22 int _MapInfo_Cache_Buf_IndexToMapData;
23
24 void MapInfo_Cache_Destroy()
25 {
26         if(!_MapInfo_Cache_Active)
27                 return;
28
29         db_close(_MapInfo_Cache_DB_NameToIndex);
30         buf_del(_MapInfo_Cache_Buf_IndexToMapData);
31         _MapInfo_Cache_Active = 0;
32 }
33
34 void MapInfo_Cache_Create()
35 {
36         MapInfo_Cache_Destroy();
37         _MapInfo_Cache_DB_NameToIndex = db_create();
38         _MapInfo_Cache_Buf_IndexToMapData = buf_create();
39         _MapInfo_Cache_Active = 1;
40 }
41
42 void MapInfo_Cache_Invalidate()
43 {
44         if(!_MapInfo_Cache_Active)
45                 return;
46
47         MapInfo_Cache_Create();
48 }
49
50 void MapInfo_Cache_Store()
51 {
52         float i;
53         string s;
54         if(!_MapInfo_Cache_Active)
55                 return;
56
57         s = db_get(_MapInfo_Cache_DB_NameToIndex, MapInfo_Map_bspname);
58         if(s == "")
59         {
60                 i = buf_getsize(_MapInfo_Cache_Buf_IndexToMapData);
61                 db_put(_MapInfo_Cache_DB_NameToIndex, MapInfo_Map_bspname, ftos(i));
62         }
63         else
64                 i = stof(s);
65
66         // now store all the stuff
67         bufstr_set(_MapInfo_Cache_Buf_IndexToMapData,   i, MapInfo_Map_bspname);
68         bufstr_set(_MapInfo_Cache_Buf_IndexToMapData, ++i, MapInfo_Map_title);
69         bufstr_set(_MapInfo_Cache_Buf_IndexToMapData, ++i, MapInfo_Map_titlestring);
70         bufstr_set(_MapInfo_Cache_Buf_IndexToMapData, ++i, MapInfo_Map_description);
71         bufstr_set(_MapInfo_Cache_Buf_IndexToMapData, ++i, MapInfo_Map_author);
72         bufstr_set(_MapInfo_Cache_Buf_IndexToMapData, ++i, ftos(MapInfo_Map_supportedGametypes));
73         bufstr_set(_MapInfo_Cache_Buf_IndexToMapData, ++i, ftos(MapInfo_Map_supportedFeatures));
74         bufstr_set(_MapInfo_Cache_Buf_IndexToMapData, ++i, ftos(MapInfo_Map_flags));
75 }
76
77 float MapInfo_Cache_Retrieve(string map)
78 {
79         float i;
80         string s;
81         if(!_MapInfo_Cache_Active)
82                 return 0;
83
84         s = db_get(_MapInfo_Cache_DB_NameToIndex, map);
85         if(s == "")
86                 return 0;
87         i = stof(s);
88
89         // now retrieve all the stuff
90         MapInfo_Map_bspname = bufstr_get(_MapInfo_Cache_Buf_IndexToMapData, i);
91         MapInfo_Map_title = bufstr_get(_MapInfo_Cache_Buf_IndexToMapData, ++i);
92         MapInfo_Map_titlestring = bufstr_get(_MapInfo_Cache_Buf_IndexToMapData, ++i);
93         MapInfo_Map_description = bufstr_get(_MapInfo_Cache_Buf_IndexToMapData, ++i);
94         MapInfo_Map_author = bufstr_get(_MapInfo_Cache_Buf_IndexToMapData, ++i);
95         MapInfo_Map_supportedGametypes = stof(bufstr_get(_MapInfo_Cache_Buf_IndexToMapData, ++i));
96         MapInfo_Map_supportedFeatures = stof(bufstr_get(_MapInfo_Cache_Buf_IndexToMapData, ++i));
97         MapInfo_Map_flags = stof(bufstr_get(_MapInfo_Cache_Buf_IndexToMapData, ++i));
98
99         return 1;
100 }
101
102 // GLOB HANDLING (for all BSP files)
103 float _MapInfo_globopen;
104 float _MapInfo_globcount;
105 float _MapInfo_globhandle;
106 string _MapInfo_GlobItem(float i)
107 {
108         string s;
109         if(!_MapInfo_globopen)
110                 return string_null;
111         s = search_getfilename(_MapInfo_globhandle, i);
112         return substring(s, 5, strlen(s) - 9); // without maps/ and .bsp
113 }
114
115 void MapInfo_Enumerate()
116 {
117         if(_MapInfo_globopen)
118         {
119                 search_end(_MapInfo_globhandle);
120                 _MapInfo_globopen = 0;
121         }
122         MapInfo_Cache_Invalidate();
123         _MapInfo_globhandle = search_begin("maps/*.bsp", true, true);
124         if(_MapInfo_globhandle >= 0)
125         {
126                 _MapInfo_globcount = search_getsize(_MapInfo_globhandle);
127                 _MapInfo_globopen = 1;
128         }
129         else
130                 _MapInfo_globcount = 0;
131 }
132
133 // filter the info by game type mask (updates MapInfo_count)
134 //
135 float _MapInfo_filtered;
136 float _MapInfo_filtered_allocated;
137 float MapInfo_FilterList_Lookup(float i)
138 {
139         return stof(bufstr_get(_MapInfo_filtered, i));
140 }
141
142 void _MapInfo_FilterList_swap(float i, float j, entity pass)
143 {
144         string h;
145         h = bufstr_get(_MapInfo_filtered, i);
146         bufstr_set(_MapInfo_filtered, i, bufstr_get(_MapInfo_filtered, j));
147         bufstr_set(_MapInfo_filtered, j, h);
148 }
149
150 float _MapInfo_FilterList_cmp(float i, float j, entity pass)
151 {
152         string a, b;
153         a = _MapInfo_GlobItem(stof(bufstr_get(_MapInfo_filtered, i)));
154         b = _MapInfo_GlobItem(stof(bufstr_get(_MapInfo_filtered, j)));
155         return strcasecmp(a, b);
156 }
157
158 float MapInfo_FilterGametype(int pGametype, int pFeatures, int pFlagsRequired, int pFlagsForbidden, bool pAbortOnGenerate)
159 {
160         float i, j;
161         if (!_MapInfo_filtered_allocated)
162         {
163                 _MapInfo_filtered_allocated = 1;
164                 _MapInfo_filtered = buf_create();
165         }
166         MapInfo_count = 0;
167         for(i = 0, j = -1; i < _MapInfo_globcount; ++i)
168         {
169                 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.
170                         if(pAbortOnGenerate)
171                         {
172                                 dprint("Autogenerated a .mapinfo, doing the rest later.\n");
173                                 MapInfo_progress = i / _MapInfo_globcount;
174                                 return 0;
175                         }
176                 if((MapInfo_Map_supportedGametypes & pGametype) != 0)
177                 if((MapInfo_Map_supportedFeatures & pFeatures) == pFeatures)
178                 if((MapInfo_Map_flags & pFlagsForbidden) == 0)
179                 if((MapInfo_Map_flags & pFlagsRequired) == pFlagsRequired)
180                         bufstr_set(_MapInfo_filtered, ++j, ftos(i));
181         }
182         MapInfo_count = j + 1;
183         MapInfo_ClearTemps();
184
185         // sometimes the glob isn't sorted nicely, so fix it here...
186         heapsort(MapInfo_count, _MapInfo_FilterList_swap, _MapInfo_FilterList_cmp, world);
187
188         return 1;
189 }
190 float MapInfo_FilterGametypeAndString(int pGametype, int pFeatures, int pFlagsRequired, int pFlagsForbidden, bool pAbortOnGenerate, string fs)
191 {
192         float i, j;
193         string title;
194         if (!_MapInfo_filtered_allocated)
195         {
196                 _MapInfo_filtered_allocated = 1;
197                 _MapInfo_filtered = buf_create();
198         }
199         MapInfo_count = 0;
200         for(i = 0, j = -1; i < _MapInfo_globcount; ++i)
201         {
202                 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.
203                         if(pAbortOnGenerate)
204                         {
205                                 dprint("Autogenerated a .mapinfo, doing the rest later.\n");
206                                 MapInfo_progress = i / _MapInfo_globcount;
207                                 return 0;
208                         }
209                 // prepare for keyword filter
210                 //localcmd(sprintf("say filterString in mapinfo filter: %s\n", fs));
211                 if (MapInfo_Map_title && strstrofs(MapInfo_Map_title, "<TITLE>", 0) == -1)
212                         title = MapInfo_Map_title;
213                 else
214                         title = MapInfo_Map_bspname;
215                 localcmd(sprintf("say map title: %s\n", title));
216                 // keyword filter
217                 if((strstrofs(strtolower(title), strtolower(fs), 0)) >= 0)
218                 if((MapInfo_Map_supportedGametypes & pGametype) != 0)
219                 if((MapInfo_Map_supportedFeatures & pFeatures) == pFeatures)
220                 if((MapInfo_Map_flags & pFlagsForbidden) == 0)
221                 if((MapInfo_Map_flags & pFlagsRequired) == pFlagsRequired)
222                         bufstr_set(_MapInfo_filtered, ++j, ftos(i));
223         }
224         MapInfo_count = j + 1;
225         MapInfo_ClearTemps();
226
227         // sometimes the glob isn't sorted nicely, so fix it here...
228         heapsort(MapInfo_count, _MapInfo_FilterList_swap, _MapInfo_FilterList_cmp, world);
229         return 1;
230 }
231
232 void MapInfo_Filter_Free()
233 {
234         if(_MapInfo_filtered_allocated)
235         {
236                 buf_del(_MapInfo_filtered);
237                 _MapInfo_filtered_allocated = 0;
238         }
239 }
240
241 // load info about the i-th map into the MapInfo_Map_* globals
242 string MapInfo_BSPName_ByID(float i)
243 {
244         return _MapInfo_GlobItem(MapInfo_FilterList_Lookup(i));
245 }
246
247 string unquote(string s)
248 {
249         float i, j, l;
250         l = strlen(s);
251         j = -1;
252         for(i = 0; i < l; ++i)
253         {
254                 string ch;
255                 ch = substring(s, i, 1);
256                 if(ch != " ") if(ch != "\"")
257                 {
258                         for(j = strlen(s) - i - 1; j > 0; --j)
259                         {
260                                 ch = substring(s, i+j, 1);
261                                 if(ch != " ") if(ch != "\"")
262                                         return substring(s, i, j+1);
263                         }
264                         return substring(s, i, 1);
265                 }
266         }
267         return "";
268 }
269
270 float MapInfo_Get_ByID(float i)
271 {
272         if(MapInfo_Get_ByName(MapInfo_BSPName_ByID(i), 0, 0))
273                 return 1;
274         return 0;
275 }
276
277 string _MapInfo_Map_worldspawn_music;
278
279 float _MapInfo_Generate(string pFilename) // 0: failure, 1: ok ent, 2: ok bsp
280 {
281         string fn;
282         float fh;
283         string s, k, v;
284         vector o;
285         float i;
286         float inWorldspawn;
287         float r;
288         float twoBaseModes;
289         float diameter, spawnpoints;
290         float spawnplaces;
291
292         vector mapMins, mapMaxs;
293
294         r = 1;
295         fn = strcat("maps/", pFilename, ".ent");
296         fh = fopen(fn, FILE_READ);
297         if(fh < 0)
298         {
299                 r = 2;
300                 fn = strcat("maps/", pFilename, ".bsp");
301                 fh = fopen(fn, FILE_READ);
302         }
303         if(fh < 0)
304                 return 0;
305         print("Analyzing ", fn, " to generate initial mapinfo\n");
306
307         inWorldspawn = 2;
308         MapInfo_Map_flags = 0;
309         MapInfo_Map_supportedGametypes = 0;
310         spawnpoints = 0;
311         spawnplaces = 0;
312         _MapInfo_Map_worldspawn_music = "";
313         mapMins = '0 0 0';
314         mapMaxs = '0 0 0';
315
316         for (;;)
317         {
318                 if (!((s = fgets(fh))))
319                         break;
320                 if(inWorldspawn == 1)
321                         if(startsWith(s, "}"))
322                                 inWorldspawn = 0;
323                 k = unquote(car(s));
324                 v = unquote(cdr(s));
325                 if(inWorldspawn)
326                 {
327                         if(k == "classname" && v == "worldspawn")
328                                 inWorldspawn = 1;
329                         else if(k == "author")
330                                 MapInfo_Map_author = v;
331                         else if(k == "_description")
332                                 MapInfo_Map_description = v;
333                         else if(k == "music")
334                                 _MapInfo_Map_worldspawn_music = v;
335                         else if(k == "noise")
336                                 _MapInfo_Map_worldspawn_music = v;
337                         else if(k == "message")
338                         {
339                                 i = strstrofs(v, " by ", 0);
340                                 if(MapInfo_Map_author == "<AUTHOR>" && i >= 0)
341                                 {
342                                         MapInfo_Map_title = substring(v, 0, i);
343                                         MapInfo_Map_author = substring(v, i + 4, strlen(v) - (i + 4));
344                                 }
345                                 else
346                                         MapInfo_Map_title = v;
347                         }
348                 }
349                 else
350                 {
351                         if(k == "origin")
352                         {
353                                 o = stov(strcat("'", v, "'"));
354                                 mapMins.x = min(mapMins.x, o.x);
355                                 mapMins.y = min(mapMins.y, o.y);
356                                 mapMins.z = min(mapMins.z, o.z);
357                                 mapMaxs.x = max(mapMaxs.x, o.x);
358                                 mapMaxs.y = max(mapMaxs.y, o.y);
359                                 mapMaxs.z = max(mapMaxs.z, o.z);
360                         }
361                         else if(k == "race_place")
362                         {
363                                 if(stof(v) > 0)
364                                         spawnplaces = 1;
365                         }
366                         else if(k == "classname")
367                         {
368                                 if(v == "dom_controlpoint")
369                                         MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_DOMINATION;
370                                 else if(v == "item_flag_team2")
371                                         MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CTF;
372                                 else if(v == "team_CTF_blueflag")
373                                         MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CTF;
374                                 else if(v == "invasion_spawnpoint")
375                                         MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_INVASION;
376                                 else if(v == "target_assault_roundend")
377                                         MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_ASSAULT;
378                                 else if(v == "onslaught_generator")
379                                         MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_ONSLAUGHT;
380                                 else if(substring(v, 0, 8) == "nexball_" || substring(v, 0, 4) == "ball")
381                                         MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_NEXBALL;
382                                 else if(v == "info_player_team1")
383                                         ++spawnpoints;
384                                 else if(v == "info_player_team2")
385                                         ++spawnpoints;
386                                 else if(v == "info_player_start")
387                                         ++spawnpoints;
388                                 else if(v == "info_player_deathmatch")
389                                         ++spawnpoints;
390                                 else if(v == "trigger_race_checkpoint")
391                                         MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_RACE;
392                                 else if(v == "target_startTimer")
393                                         MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CTS;
394                                 else if(v == "weapon_nex")
395                                         { }
396                                 else if(v == "weapon_railgun")
397                                         { }
398                                 else if(startsWith(v, "weapon_"))
399                                         MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_WEAPONS;
400                                 else if(startsWith(v, "turret_"))
401                                         MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_TURRETS;
402                                 else if(startsWith(v, "vehicle_"))
403                                         MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_VEHICLES;
404                                 else if(startsWith(v, "monster_"))
405                                         MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_MONSTERS;
406                                 else if(v == "target_music" || v == "trigger_music")
407                                         _MapInfo_Map_worldspawn_music = string_null; // don't use regular BGM
408                         }
409                 }
410         }
411         if(inWorldspawn)
412         {
413                 print(fn, " ended still in worldspawn, BUG\n");
414                 return 0;
415         }
416         diameter = vlen(mapMaxs - mapMins);
417
418         twoBaseModes = MapInfo_Map_supportedGametypes & (MAPINFO_TYPE_CTF | MAPINFO_TYPE_ASSAULT | MAPINFO_TYPE_RACE | MAPINFO_TYPE_NEXBALL);
419         if(twoBaseModes && (MapInfo_Map_supportedGametypes == twoBaseModes))
420         {
421                 // we have a CTF-only or Assault-only map. Don't add other modes then,
422                 // as the map is too symmetric for them.
423         }
424         else
425         {
426                 MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_DEATHMATCH;      // DM always works
427                 MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_LMS;             // LMS always works
428                 MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_KEEPAWAY;                // Keepaway always works
429
430                 if(spawnpoints >= 8  && diameter > 4096) {
431                         MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_TEAM_DEATHMATCH;
432                         MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_FREEZETAG;
433                         MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CA;
434                 }
435                 if(spawnpoints >= 12 && diameter > 5120)
436                         MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_KEYHUNT;
437         }
438
439         if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_RACE)
440         if(!spawnplaces)
441         {
442                 MapInfo_Map_supportedGametypes &= ~MAPINFO_TYPE_RACE;
443                 MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CTS;
444         }
445
446         dprint("-> diameter ",    ftos(diameter));
447         dprint(";  spawnpoints ", ftos(spawnpoints));
448         dprint(";  modes ",       ftos(MapInfo_Map_supportedGametypes), "\n");
449
450         fclose(fh);
451
452         return r;
453 }
454
455 void _MapInfo_Map_Reset()
456 {
457         MapInfo_Map_title = "<TITLE>";
458         MapInfo_Map_titlestring = "<TITLE>";
459         MapInfo_Map_description = "<DESCRIPTION>";
460         MapInfo_Map_author = "<AUTHOR>";
461         MapInfo_Map_supportedGametypes = 0;
462         MapInfo_Map_supportedFeatures = 0;
463         MapInfo_Map_flags = 0;
464         MapInfo_Map_clientstuff = "";
465         MapInfo_Map_fog = "";
466         MapInfo_Map_mins = '0 0 0';
467         MapInfo_Map_maxs = '0 0 0';
468 }
469
470 string _MapInfo_GetDefault(float t)
471 {
472         switch(t)
473         {
474                 case MAPINFO_TYPE_DEATHMATCH:      return "30 20 0";
475                 case MAPINFO_TYPE_TEAM_DEATHMATCH: return "50 20 2 0";
476                 case MAPINFO_TYPE_DOMINATION:      return "200 20 0";
477                 case MAPINFO_TYPE_CTF:             return "300 20 10 0";
478                 case MAPINFO_TYPE_LMS:             return "9 20 0";
479                 case MAPINFO_TYPE_CA:              return "10 20 0";
480                 case MAPINFO_TYPE_KEYHUNT:         return "1000 20 3 0";
481                 case MAPINFO_TYPE_ASSAULT:         return "20 0";
482                 case MAPINFO_TYPE_RACE:            return "20 5 7 15 0";
483                 case MAPINFO_TYPE_ONSLAUGHT:       return "20 0";
484                 case MAPINFO_TYPE_NEXBALL:         return "5 20 0";
485                 case MAPINFO_TYPE_CTS:             return "20 0 0";
486                 case MAPINFO_TYPE_FREEZETAG:       return "10 20 0";
487                 // NOTE: DO NOT ADD ANY MORE GAME TYPES HERE
488                 // THIS IS JUST LEGACY SUPPORT FOR NEXUIZ MAPS
489                 // ONLY ADD NEW STUFF TO _MapInfo_GetDefaultEx
490                 // THIS FUNCTION WILL EVENTUALLY BE REMOVED
491                 default:                           return "";
492         }
493 }
494
495 void _MapInfo_Map_ApplyGametype(string s, int pWantedType, int pThisType, int load_default)
496 {
497         string sa;
498         MapInfo_Map_supportedGametypes |= pThisType;
499         if(!(pThisType & pWantedType))
500                 return;
501
502         if(load_default)
503                 _MapInfo_Map_ApplyGametype(_MapInfo_GetDefault(pThisType), pWantedType, pThisType, false);
504
505         if(pWantedType == MAPINFO_TYPE_ASSAULT || pWantedType == MAPINFO_TYPE_ONSLAUGHT || pWantedType == MAPINFO_TYPE_RACE || pWantedType == MAPINFO_TYPE_CTS) // these modes don't use fraglimit
506         {
507                 cvar_set("fraglimit", "0");
508         }
509         else
510         {
511                 sa = car(s);
512                 if(sa != "")
513                         cvar_set("fraglimit", sa);
514                 s = cdr(s);
515         }
516
517         sa = car(s);
518         if(sa != "")
519                 cvar_set("timelimit", sa);
520         s = cdr(s);
521
522         if(pWantedType == MAPINFO_TYPE_TEAM_DEATHMATCH)
523         {
524                 sa = car(s);
525                 if(sa != "")
526                         cvar_set("g_tdm_teams", sa);
527                 s = cdr(s);
528         }
529
530         if(pWantedType == MAPINFO_TYPE_KEYHUNT)
531         {
532                 sa = car(s);
533                 if(sa != "")
534                         cvar_set("g_keyhunt_teams", sa);
535                 s = cdr(s);
536         }
537
538         if(pWantedType == MAPINFO_TYPE_CA)
539         {
540                 sa = car(s);
541                 if(sa != "")
542                         cvar_set("g_ca_teams", sa);
543                 s = cdr(s);
544         }
545
546         if(pWantedType == MAPINFO_TYPE_FREEZETAG)
547         {
548                 sa = car(s);
549                 if(sa != "")
550                         cvar_set("g_freezetag_teams", sa);
551                 s = cdr(s);
552         }
553
554         if(pWantedType == MAPINFO_TYPE_CTF)
555         {
556                 sa = car(s);
557                 if(sa != "")
558                         cvar_set("fraglimit", sa);
559                 s = cdr(s);
560         }
561
562         /* keepaway wuz here
563         if(pWantedType == MAPINFO_TYPE_KEEPAWAY)
564         {
565                 sa = car(s);
566                 if(sa != "")
567                         cvar_set("fraglimit", sa);
568                 s = cdr(s);
569         }
570         */
571
572         // rc = timelimit timelimit_qualification laps laps_teamplay
573         if(pWantedType == MAPINFO_TYPE_RACE)
574         {
575                 sa = car(s); if(sa == "") sa = cvar_string("timelimit");
576                 cvar_set("g_race_qualifying_timelimit", sa);
577                 s = cdr(s);
578
579                 sa = car(s);
580                 if(sa != "")
581                         if(cvar("g_race_teams") < 2)
582                                 cvar_set("fraglimit", sa);
583                 s = cdr(s);
584
585                 sa = car(s);
586                 if(sa != "")
587                         if(cvar("g_race_teams") >= 2)
588                                 cvar_set("fraglimit", sa);
589                 s = cdr(s);
590         }
591
592         if(pWantedType == MAPINFO_TYPE_CTS)
593         {
594                 sa = car(s);
595
596                 // this is the skill of the map
597                 // not parsed by anything yet
598                 // for map databases
599                 //if(sa != "")
600                 //      cvar_set("fraglimit", sa);
601
602                 s = cdr(s);
603         }
604
605         if(pWantedType == MAPINFO_TYPE_ASSAULT || pWantedType == MAPINFO_TYPE_ONSLAUGHT || pWantedType == MAPINFO_TYPE_CTS) // these modes don't use fraglimit
606         {
607                 cvar_set("leadlimit", "0");
608         }
609         else
610         {
611                 sa = car(s);
612                 if(sa != "")
613                         cvar_set("leadlimit", sa);
614                 s = cdr(s);
615         }
616 }
617
618 string _MapInfo_GetDefaultEx(float t)
619 {
620         entity e;
621         for(e = MapInfo_Type_first; e; e = e.enemy)
622                 if(t == e.items)
623                         return e.model2;
624         return "";
625 }
626
627 float _MapInfo_GetTeamPlayBool(float t)
628 {
629         entity e;
630         for(e = MapInfo_Type_first; e; e = e.enemy)
631                 if(t == e.items)
632                         return e.team;
633         return false;
634 }
635
636 void _MapInfo_Map_ApplyGametypeEx(string s, int pWantedType, int pThisType)
637 {
638         string sa, k, v;
639         float p;
640         string fraglimit_normal;
641         string fraglimit_teams;
642
643         MapInfo_Map_supportedGametypes |= pThisType;
644         if(!(pThisType & pWantedType))
645                 return;
646
647         // reset all the cvars to their defaults
648
649         cvar_set("timelimit", cvar_defstring("timelimit"));
650         cvar_set("leadlimit", cvar_defstring("leadlimit"));
651         cvar_set("fraglimit", cvar_defstring("fraglimit"));
652         cvar_set("g_tdm_teams", cvar_defstring("g_tdm_teams"));
653         cvar_set("g_ca_teams", cvar_defstring("g_ca_teams"));
654         cvar_set("g_freezetag_teams", cvar_defstring("g_freezetag_teams"));
655         cvar_set("g_keyhunt_teams", cvar_defstring("g_keyhunt_teams"));
656         cvar_set("g_domination_default_teams", cvar_defstring("g_domination_default_teams"));
657         cvar_set("g_race_qualifying_timelimit", cvar_defstring("g_race_qualifying_timelimit"));
658
659         fraglimit_normal = string_null;
660         fraglimit_teams = string_null;
661
662         s = strcat(_MapInfo_GetDefaultEx(pWantedType), " ", s);
663         while(s != "")
664         {
665                 sa = car(s);
666                 s = cdr(s);
667
668                 if(sa == "")
669                         continue;
670
671                 p = strstrofs(sa, "=", 0);
672                 if(p < 0)
673                 {
674                         print("Invalid gametype setting in mapinfo for gametype ", MapInfo_Type_ToString(pWantedType), ": ", sa, "\n");
675                         continue;
676                 }
677                 k = substring(sa, 0, p);
678                 v = substring(sa, p+1, -1);
679
680                 if(k == "timelimit")
681                 {
682                         cvar_set("timelimit", v);
683                 }
684                 else if(k == "leadlimit")
685                 {
686                         cvar_set("leadlimit", v);
687                 }
688                 else if(k == "pointlimit" || k == "fraglimit" || k == "lives" || k == "laplimit" || k == "caplimit")
689                 {
690                         fraglimit_normal = v;
691                 }
692                 else if(k == "teampointlimit" || k == "teamlaplimit")
693                 {
694                         fraglimit_teams = v;
695                 }
696                 else if(k == "teams")
697                 {
698                         cvar_set("g_tdm_teams", v);
699                         cvar_set("g_ca_teams", v);
700                         cvar_set("g_freezetag_teams", v);
701                         cvar_set("g_keyhunt_teams", v);
702                         cvar_set("g_domination_default_teams", v);
703                         cvar_set("g_invasion_teams", v);
704                 }
705                 else if(k == "qualifying_timelimit")
706                 {
707                         cvar_set("g_race_qualifying_timelimit", v);
708                 }
709                 else if(k == "skill")
710                 {
711                         // ignore
712                 }
713                 else
714                 {
715                         print("Invalid gametype setting in mapinfo for gametype ", MapInfo_Type_ToString(pWantedType), ": ", sa, "\n");
716                 }
717         }
718
719         if(pWantedType == MAPINFO_TYPE_RACE && cvar("g_race_teams") >= 2)
720         {
721                 if(fraglimit_teams)
722                         cvar_set("fraglimit", fraglimit_teams);
723         }
724         else
725         {
726                 if(fraglimit_normal)
727                         cvar_set("fraglimit", fraglimit_normal);
728         }
729 }
730
731 float MapInfo_Type_FromString(string t)
732 {
733         entity e;
734         if(t == "nexball")
735         {
736                 print("MapInfo_Type_FromString (probably ", MapInfo_Map_bspname, "): using deprecated name '", t);
737                 t = "nb";
738                 print("'. Should use '", t, "'.\n");
739         }
740         if(t == "freezetag")
741         {
742                 print("MapInfo_Type_FromString (probably ", MapInfo_Map_bspname, "): using deprecated name '", t);
743                 t = "ft";
744                 print("'. Should use '", t, "'.\n");
745         }
746         if(t == "keepaway")
747         {
748                 print("MapInfo_Type_FromString (probably ", MapInfo_Map_bspname, "): using deprecated name '", t);
749                 t = "ka";
750                 print("'. Should use '", t, "'.\n");
751         }
752         if(t == "invasion")
753         {
754                 print("MapInfo_Type_FromString (probably ", MapInfo_Map_bspname, "): using deprecated name '", t);
755                 t = "inv";
756                 print("'. Should use '", t, "'.\n");
757         }
758         if(t == "assault")
759         {
760                 print("MapInfo_Type_FromString (probably ", MapInfo_Map_bspname, "): using deprecated name '", t);
761                 t = "as";
762                 print("'. Should use '", t, "'.\n");
763         }
764         if(t == "race")
765         {
766                 print("MapInfo_Type_FromString (probably ", MapInfo_Map_bspname, "): using deprecated name '", t);
767                 t = "rc";
768                 print("'. Should use '", t, "'.\n");
769         }
770         if(t == "all")
771                 return MAPINFO_TYPE_ALL;
772         for(e = MapInfo_Type_first; e; e = e.enemy)
773                 if(t == e.mdl)
774                         return e.items;
775         return 0;
776 }
777
778 string MapInfo_Type_Description(float t)
779 {
780         entity e;
781         for(e = MapInfo_Type_first; e; e = e.enemy)
782                 if(t == e.items)
783                         return e.gametype_description;
784         return "";
785 }
786
787 string MapInfo_Type_ToString(float t)
788 {
789         entity e;
790         if(t == MAPINFO_TYPE_ALL)
791                 return "all";
792         for(e = MapInfo_Type_first; e; e = e.enemy)
793                 if(t == e.items)
794                         return e.mdl;
795         return "";
796 }
797
798 string MapInfo_Type_ToText(float t)
799 {
800         entity e;
801         for(e = MapInfo_Type_first; e; e = e.enemy)
802                 if(t == e.items)
803                         return e.message;
804         /* xgettext:no-c-format */
805         return _("@!#%'n Tuba Throwing");
806 }
807
808 void _MapInfo_Parse_Settemp(string pFilename, string acl, float type, string s, float recurse)
809 {
810         string t;
811         float fh, o;
812         t = car(s); s = cdr(s);
813
814         // limited support of "" and comments
815         //   remove trailing and leading " of t
816         if(substring(t, 0, 1) == "\"")
817         {
818                 if(substring(t, -1, 1) == "\"")
819                         t = substring(t, 1, -2);
820         }
821
822         //   remove leading " of s
823         if(substring(s, 0, 1) == "\"")
824         {
825                 s = substring(s, 1, -1);
826         }
827         //   remove trailing " of s, and all that follows (cvar description)
828         o = strstrofs(s, "\"", 0);
829         if(o >= 0)
830                 s = substring(s, 0, o);
831
832         //   remove // comments
833         o = strstrofs(s, "//", 0);
834         if(o >= 0)
835                 s = substring(s, 0, o);
836
837         //   remove trailing spaces
838         while(substring(s, -1, 1) == " ")
839                 s = substring(s, 0, -2);
840
841         if(t == "#include")
842         {
843                 if(recurse > 0)
844                 {
845                         fh = fopen(s, FILE_READ);
846                         if(fh < 0)
847                                 print("Map ", pFilename, " references not existing config file ", s, "\n");
848                         else
849                         {
850                                 for (;;)
851                                 {
852                                         if (!((s = fgets(fh))))
853                                                 break;
854
855                                         // catch different sorts of comments
856                                         if(s == "")                    // empty lines
857                                                 continue;
858                                         if(substring(s, 0, 1) == "#")  // UNIX style
859                                                 continue;
860                                         if(substring(s, 0, 2) == "//") // C++ style
861                                                 continue;
862                                         if(substring(s, 0, 1) == "_")  // q3map style
863                                                 continue;
864
865                                         if(substring(s, 0, 4) == "set ")
866                                                 s = substring(s, 4, -1);
867                                         if(substring(s, 0, 5) == "seta ")
868                                                 s = substring(s, 5, -1);
869
870                                         _MapInfo_Parse_Settemp(pFilename, acl, type, s, recurse - 1);
871                                 }
872                                 fclose(fh);
873                         }
874                 }
875                 else
876                         print("Map ", pFilename, " uses too many levels of inclusion\n");
877         }
878         else if(t == "")
879                 print("Map ", pFilename, " contains a potentially harmful setting, ignored\n");
880         else if (!cvar_value_issafe(t))
881                 print("Map ", pFilename, " contains a potentially harmful setting, ignored\n");
882         else if (!cvar_value_issafe(s))
883                 print("Map ", pFilename, " contains a potentially harmful setting, ignored\n");
884         else if(matchacl(MAPINFO_SETTEMP_ACL_SYSTEM, t) <= 0)
885                 print("Map ", pFilename, " contains a potentially harmful setting, ignored\n");
886         else if(matchacl(acl, t) <= 0)
887                 print("Map ", pFilename, " contains a denied setting, ignored\n");
888         else
889         {
890                 if(type == 0) // server set
891                 {
892                         dprint("Applying temporary setting ", t, " := ", s, "\n");
893                         if(cvar("g_campaign"))
894                                 cvar_set(t, s); // this is a wrapper and is always temporary anyway; no need to backup old values then
895                         else
896                                 cvar_settemp(t, s);
897                 }
898                 else
899                 {
900                         dprint("Applying temporary client setting ", t, " := ", s, "\n");
901                         MapInfo_Map_clientstuff = strcat(
902                                         MapInfo_Map_clientstuff, "cl_cmd settemp \"", t, "\" \"", s, "\"\n"
903                                         );
904                 }
905         }
906 }
907
908 float MapInfo_isRedundant(string fn, string t)
909 {
910         // normalize file name
911         fn = strreplace("_", "-", fn);
912
913         // normalize visible title
914         t = strreplace(": ", "-", t);
915         t = strreplace(":", "-", t);
916         t = strreplace(" ", "-", t);
917         t = strreplace("_", "-", t);
918         t = strreplace("'", "-", t);
919
920         if(!strcasecmp(fn, t))
921                 return true;
922
923         // we allow the visible title to have punctuation the file name does
924         // not, but not vice versa
925         t = strreplace("-", "", t);
926
927         if(!strcasecmp(fn, t))
928                 return true;
929
930         return false;
931 }
932
933 // load info about a map by name into the MapInfo_Map_* globals
934 float MapInfo_Get_ByName_NoFallbacks(string pFilename, int pAllowGenerate, int pGametypeToSet)
935 {
936         string fn;
937         string s, t;
938         float fh;
939         int f, i;
940         float r, n, p;
941         string acl;
942
943         acl = MAPINFO_SETTEMP_ACL_USER;
944
945         if(strstrofs(pFilename, "/", 0) >= 0)
946         {
947                 print("Invalid character in map name, ignored\n");
948                 return 0;
949         }
950
951         if(pGametypeToSet == 0)
952                 if(MapInfo_Cache_Retrieve(pFilename))
953                         return 1;
954
955         r = 1;
956
957         MapInfo_Map_bspname = pFilename;
958
959         // default all generic fields so they have "good" values in case something fails
960         fn = strcat("maps/", pFilename, ".mapinfo");
961         fh = fopen(fn, FILE_READ);
962         if(fh < 0)
963         {
964                 fn = strcat("maps/autogenerated/", pFilename, ".mapinfo");
965                 fh = fopen(fn, FILE_READ);
966                 if(fh < 0)
967                 {
968                         if(!pAllowGenerate)
969                                 return 0;
970                         _MapInfo_Map_Reset();
971                         r = _MapInfo_Generate(pFilename);
972                         if(!r)
973                                 return 0;
974                         fh = fopen(fn, FILE_WRITE);
975                         fputs(fh, strcat("title ", MapInfo_Map_title, "\n"));
976                         fputs(fh, strcat("description ", MapInfo_Map_description, "\n"));
977                         fputs(fh, strcat("author ", MapInfo_Map_author, "\n"));
978                         if(_MapInfo_Map_worldspawn_music != "")
979                         {
980                                 if(
981                                         substring(_MapInfo_Map_worldspawn_music, strlen(_MapInfo_Map_worldspawn_music) - 4, 4) == ".wav"
982                                         ||
983                                         substring(_MapInfo_Map_worldspawn_music, strlen(_MapInfo_Map_worldspawn_music) - 4, 4) == ".ogg"
984                                 )
985                                         fputs(fh, strcat("cdtrack ", substring(_MapInfo_Map_worldspawn_music, 0, strlen(_MapInfo_Map_worldspawn_music) - 4), "\n"));
986                                 else
987                                         fputs(fh, strcat("cdtrack ", _MapInfo_Map_worldspawn_music, "\n"));
988                         }
989                         else
990                         {
991                                 n = tokenize_console(cvar_string("g_cdtracks_remaplist"));
992                                 s = strcat(" ", cvar_string("g_cdtracks_dontusebydefault"), " ");
993                                 for (;;)
994                                 {
995                                         i = floor(random() * n);
996                                         if(strstrofs(s, strcat(" ", argv(i), " "), 0) < 0)
997                                                 break;
998                                 }
999                                 fputs(fh, strcat("cdtrack ", ftos(i + 1), "\n"));
1000                         }
1001                         if(MapInfo_Map_supportedFeatures & MAPINFO_FEATURE_WEAPONS)
1002                                 fputs(fh, "has weapons\n");
1003                         else
1004                                 fputs(fh, "// uncomment this if you added weapon pickups: has weapons\n");
1005                         if(MapInfo_Map_supportedFeatures & MAPINFO_FEATURE_TURRETS)
1006                                 fputs(fh, "has turrets\n");
1007                         else
1008                                 fputs(fh, "// uncomment this if you added turrets: has turrets\n");
1009                         if(MapInfo_Map_supportedFeatures & MAPINFO_FEATURE_VEHICLES)
1010                                 fputs(fh, "has vehicles\n");
1011                         else
1012                                 fputs(fh, "// uncomment this if you added vehicles: has vehicles\n");
1013                         if(MapInfo_Map_flags & MAPINFO_FLAG_FRUSTRATING)
1014                                 fputs(fh, "frustrating\n");
1015
1016                         for(i = 1; i <= MapInfo_Map_supportedGametypes; i *= 2)
1017                                 if(MapInfo_Map_supportedGametypes & i)
1018                                         fputs(fh, sprintf("gametype %s // defaults: %s\n", MapInfo_Type_ToString(i), _MapInfo_GetDefaultEx(i)));
1019
1020                         if(fexists(strcat("scripts/", pFilename, ".arena")))
1021                                 fputs(fh, "settemp_for_type all sv_q3acompat_machineshotgunswap 1\n");
1022
1023                         fputs(fh, "// optional: fog density red green blue alpha mindist maxdist\n");
1024                         fputs(fh, "// optional: settemp_for_type (all|gametypename) cvarname value\n");
1025                         fputs(fh, "// optional: clientsettemp_for_type (all|gametypename) cvarname value\n");
1026                         fputs(fh, "// optional: size mins_x mins_y mins_z maxs_x maxs_y maxs_z\n");
1027                         fputs(fh, "// optional: hidden\n");
1028
1029                         fclose(fh);
1030                         r = 2;
1031                         // return r;
1032                         fh = fopen(fn, FILE_READ);
1033                         if(fh < 0)
1034                                 error("... but I just wrote it!");
1035                 }
1036
1037                 print("WARNING: autogenerated mapinfo file ", fn, " has been loaded; please edit that file and move it to maps/", pFilename, ".mapinfo\n");
1038         }
1039
1040         _MapInfo_Map_Reset();
1041         for (;;)
1042         {
1043                 if (!((s = fgets(fh))))
1044                         break;
1045
1046                 // catch different sorts of comments
1047                 if(s == "")                    // empty lines
1048                         continue;
1049                 if(substring(s, 0, 1) == "#")  // UNIX style
1050                         continue;
1051                 if(substring(s, 0, 2) == "//") // C++ style
1052                         continue;
1053                 if(substring(s, 0, 1) == "_")  // q3map style
1054                         continue;
1055
1056                 p = strstrofs(s, "//", 0);
1057                 if(p >= 0)
1058                         s = substring(s, 0, p);
1059
1060                 t = car(s); s = cdr(s);
1061                 if(t == "title")
1062                         MapInfo_Map_title = s;
1063                 else if(t == "description")
1064                         MapInfo_Map_description = s;
1065                 else if(t == "author")
1066                         MapInfo_Map_author = s;
1067                 else if(t == "has")
1068                 {
1069                         t = car(s); // s = cdr(s);
1070                         if     (t == "weapons") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_WEAPONS;
1071                         else if(t == "turrets") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_TURRETS;
1072                         else if(t == "vehicles") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_VEHICLES;
1073                         else if(t == "monsters") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_MONSTERS;
1074                         else if(t == "new_toys") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_WEAPONS;
1075                         else
1076                                 dprint("Map ", pFilename, " supports unknown feature ", t, ", ignored\n");
1077                 }
1078                 else if(t == "hidden")
1079                 {
1080                         MapInfo_Map_flags |= MAPINFO_FLAG_HIDDEN;
1081                 }
1082                 else if(t == "forbidden")
1083                 {
1084                         MapInfo_Map_flags |= MAPINFO_FLAG_FORBIDDEN;
1085                 }
1086                 else if(t == "frustrating")
1087                 {
1088                         MapInfo_Map_flags |= MAPINFO_FLAG_FRUSTRATING;
1089                 }
1090                 else if(t == "noautomaplist")
1091                 {
1092                         MapInfo_Map_flags |= MAPINFO_FLAG_NOAUTOMAPLIST;
1093                 }
1094                 else if(t == "type")
1095                 {
1096                         t = car(s); s = cdr(s);
1097                         f = MapInfo_Type_FromString(t);
1098                         dprint("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");
1099                         if(f)
1100                                 _MapInfo_Map_ApplyGametype (s, pGametypeToSet, f, true);
1101                         else
1102                                 dprint("Map ", pFilename, " supports unknown game type ", t, ", ignored\n");
1103                 }
1104                 else if(t == "gametype")
1105                 {
1106                         t = car(s); s = cdr(s);
1107                         f = MapInfo_Type_FromString(t);
1108                         if(f)
1109                                 _MapInfo_Map_ApplyGametypeEx (s, pGametypeToSet, f);
1110                         else
1111                                 dprint("Map ", pFilename, " supports unknown game type ", t, ", ignored\n");
1112                 }
1113                 else if(t == "size")
1114                 {
1115                         float a, b, c, d, e;
1116                         t = car(s); s = cdr(s); a = stof(t);
1117                         t = car(s); s = cdr(s); b = stof(t);
1118                         t = car(s); s = cdr(s); c = stof(t);
1119                         t = car(s); s = cdr(s); d = stof(t);
1120                         t = car(s); s = cdr(s); e = stof(t);
1121                         if(s == "")
1122                                 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");
1123                         else
1124                         {
1125                                 t = car(s); s = cdr(s); f = stof(t);
1126                                 if(s != "")
1127                                         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");
1128                                 else
1129                                 {
1130                                         if(a >= d || b >= e || c >= f)
1131                                                 print("Map ", pFilename, " contains an incorrect size line, mins have to be < maxs\n");
1132                                         else
1133                                         {
1134                                                 MapInfo_Map_mins.x = a;
1135                                                 MapInfo_Map_mins.y = b;
1136                                                 MapInfo_Map_mins.z = c;
1137                                                 MapInfo_Map_maxs.x = d;
1138                                                 MapInfo_Map_maxs.y = e;
1139                                                 MapInfo_Map_maxs.z = f;
1140                                         }
1141                                 }
1142                         }
1143                 }
1144                 else if(t == "settemp_for_type")
1145                 {
1146                         t = car(s); s = cdr(s);
1147                         if((f = MapInfo_Type_FromString(t)))
1148                         {
1149                                 if(f & pGametypeToSet)
1150                                 {
1151                                         _MapInfo_Parse_Settemp(pFilename, acl, 0, s, 1);
1152                                 }
1153                         }
1154                         else
1155                         {
1156                                 dprint("Map ", pFilename, " has a setting for unknown game type ", t, ", ignored\n");
1157                         }
1158                 }
1159                 else if(t == "clientsettemp_for_type")
1160                 {
1161                         t = car(s); s = cdr(s);
1162                         if((f = MapInfo_Type_FromString(t)))
1163                         {
1164                                 if(f & pGametypeToSet)
1165                                 {
1166                                         _MapInfo_Parse_Settemp(pFilename, acl, 1, s, 1);
1167                                 }
1168                         }
1169                         else
1170                         {
1171                                 dprint("Map ", pFilename, " has a client setting for unknown game type ", t, ", ignored\n");
1172                         }
1173                 }
1174                 else if(t == "fog")
1175                 {
1176                         if (!cvar_value_issafe(s))
1177                                 print("Map ", pFilename, " contains a potentially harmful fog setting, ignored\n");
1178                         else
1179                                 MapInfo_Map_fog = s;
1180                 }
1181                 else if(t == "cdtrack")
1182                 {
1183                         t = car(s); s = cdr(s);
1184                         if(pGametypeToSet) // FIXME is this check right here?
1185                         {
1186                                 if (!cvar_value_issafe(t))
1187                                         print("Map ", pFilename, " contains a potentially harmful cdtrack, ignored\n");
1188                                 else
1189                                         MapInfo_Map_clientstuff = strcat(
1190                                                 MapInfo_Map_clientstuff, "cd loop \"", t, "\"\n"
1191                                         );
1192                         }
1193                 }
1194                 else
1195                         dprint("Map ", pFilename, " provides unknown info item ", t, ", ignored\n");
1196         }
1197         fclose(fh);
1198
1199         if(MapInfo_Map_title == "<TITLE>")
1200                 MapInfo_Map_titlestring = MapInfo_Map_bspname;
1201         else if(MapInfo_isRedundant(MapInfo_Map_bspname, MapInfo_Map_title))
1202                 MapInfo_Map_titlestring = MapInfo_Map_title;
1203         else
1204                 MapInfo_Map_titlestring = sprintf("%s: %s", MapInfo_Map_bspname, MapInfo_Map_title);
1205
1206         MapInfo_Cache_Store();
1207         if(MapInfo_Map_supportedGametypes != 0)
1208                 return r;
1209         dprint("Map ", pFilename, " supports no game types, ignored\n");
1210         return 0;
1211 }
1212 float MapInfo_Get_ByName(string pFilename, float pAllowGenerate, int pGametypeToSet)
1213 {
1214         float r = MapInfo_Get_ByName_NoFallbacks(pFilename, pAllowGenerate, pGametypeToSet);
1215
1216         if(cvar("g_tdm_on_dm_maps"))
1217         {
1218                 // if this is set, all DM maps support TDM too
1219                 if (!(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_TEAM_DEATHMATCH))
1220                         if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DEATHMATCH)
1221                                 _MapInfo_Map_ApplyGametypeEx ("", pGametypeToSet, MAPINFO_TYPE_TEAM_DEATHMATCH);
1222         }
1223
1224         if(pGametypeToSet)
1225         {
1226                 if(!(MapInfo_Map_supportedGametypes & pGametypeToSet))
1227                 {
1228                         error("Can't select the requested game type. This should never happen as the caller should prevent it!\n");
1229                         //_MapInfo_Map_ApplyGametypeEx("", pGametypeToSet, MAPINFO_TYPE_DEATHMATCH);
1230                         //return;
1231                 }
1232         }
1233
1234         return r;
1235 }
1236
1237 float MapInfo_FindName(string s)
1238 {
1239         // if there is exactly one map of prefix s, return it
1240         // if not, return the null string
1241         // note that DP sorts glob results... so I can use a binary search
1242         float l, r, m, cmp;
1243         l = 0;
1244         r = MapInfo_count;
1245         // invariants: r is behind s, l-1 is equal or before
1246         while(l != r)
1247         {
1248                 m = floor((l + r) / 2);
1249                 MapInfo_FindName_match = _MapInfo_GlobItem(MapInfo_FilterList_Lookup(m));
1250                 cmp = strcasecmp(MapInfo_FindName_match, s);
1251                 if(cmp == 0)
1252                         return m; // found and good
1253                 if(cmp < 0)
1254                         l = m + 1; // l-1 is before s
1255                 else
1256                         r = m; // behind s
1257         }
1258         MapInfo_FindName_match = _MapInfo_GlobItem(MapInfo_FilterList_Lookup(l));
1259         MapInfo_FindName_firstResult = l;
1260         // r == l, so: l is behind s, l-1 is before
1261         // SO: if there is any, l is the one with the right prefix
1262         //     and l+1 may be one too
1263         if(l == MapInfo_count)
1264         {
1265                 MapInfo_FindName_match = string_null;
1266                 MapInfo_FindName_firstResult = -1;
1267                 return -1; // no MapInfo_FindName_match, behind last item
1268         }
1269         if(!startsWithNocase(MapInfo_FindName_match, s))
1270         {
1271                 MapInfo_FindName_match = string_null;
1272                 MapInfo_FindName_firstResult = -1;
1273                 return -1; // wrong prefix
1274         }
1275         if(l == MapInfo_count - 1)
1276                 return l; // last one, nothing can follow => unique
1277         if(startsWithNocase(_MapInfo_GlobItem(MapInfo_FilterList_Lookup(l + 1)), s))
1278         {
1279                 MapInfo_FindName_match = string_null;
1280                 return -1; // ambigous MapInfo_FindName_match
1281         }
1282         return l;
1283 }
1284
1285 string MapInfo_FixName(string s)
1286 {
1287         MapInfo_FindName(s);
1288         return MapInfo_FindName_match;
1289 }
1290
1291 int MapInfo_CurrentFeatures()
1292 {
1293         int req = 0;
1294         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")))
1295                 req |= MAPINFO_FEATURE_WEAPONS;
1296         return req;
1297 }
1298
1299 int MapInfo_CurrentGametype()
1300 {
1301         entity e;
1302         int prev = cvar("gamecfg");
1303         for(e = MapInfo_Type_first; e; e = e.enemy)
1304                 if(cvar(e.netname))
1305                         if(prev != e.items)
1306                                 return e.items;
1307         if(prev)
1308                 return prev;
1309         return MAPINFO_TYPE_DEATHMATCH;
1310 }
1311
1312 float _MapInfo_CheckMap(string s) // returns 0 if the map can't be played with the current settings, 1 otherwise
1313 {
1314         if(!MapInfo_Get_ByName(s, 1, 0))
1315                 return 0;
1316         if((MapInfo_Map_supportedGametypes & MapInfo_CurrentGametype()) == 0)
1317                 return 0;
1318         if((MapInfo_Map_supportedFeatures & MapInfo_CurrentFeatures()) != MapInfo_CurrentFeatures())
1319                 return 0;
1320         return 1;
1321 }
1322
1323 float MapInfo_CheckMap(string s) // returns 0 if the map can't be played with the current settings, 1 otherwise
1324 {
1325         float r;
1326         r = _MapInfo_CheckMap(s);
1327         MapInfo_ClearTemps();
1328         return r;
1329 }
1330
1331 void MapInfo_SwitchGameType(int t)
1332 {
1333         for (entity e = MapInfo_Type_first; e; e = e.enemy) {
1334                 cvar_set(e.netname, (t == e.items) ? "1" : "0");
1335         }
1336 }
1337
1338 void MapInfo_LoadMap(string s, float reinit)
1339 {
1340         MapInfo_Map_supportedGametypes = 0;
1341         // we shouldn't need this, as LoadMapSettings already fixes the gametype
1342         //if(!MapInfo_CheckMap(s))
1343         //{
1344         //      print("EMERGENCY: can't play the selected map in the given game mode. Falling back to DM.\n");
1345         //      MapInfo_SwitchGameType(MAPINFO_TYPE_DEATHMATCH);
1346         //}
1347
1348         cvar_settemp_restore();
1349         if(reinit)
1350                 localcmd(strcat("\nmap ", s, "\n"));
1351         else
1352                 localcmd(strcat("\nchangelevel ", s, "\n"));
1353 }
1354
1355 string MapInfo_ListAllowedMaps(float type, float pRequiredFlags, float pForbiddenFlags)
1356 {
1357         string out;
1358         float i;
1359
1360         // to make absolutely sure:
1361         MapInfo_Enumerate();
1362         MapInfo_FilterGametype(type, MapInfo_CurrentFeatures(), pRequiredFlags, pForbiddenFlags, 0);
1363
1364         out = "";
1365         for(i = 0; i < MapInfo_count; ++i)
1366                 out = strcat(out, " ", _MapInfo_GlobItem(MapInfo_FilterList_Lookup(i)));
1367         return substring(out, 1, strlen(out) - 1);
1368 }
1369
1370 string MapInfo_ListAllAllowedMaps(float pRequiredFlags, float pForbiddenFlags)
1371 {
1372         string out;
1373         float i;
1374
1375         // to make absolutely sure:
1376         MapInfo_Enumerate();
1377         MapInfo_FilterGametype(MAPINFO_TYPE_ALL, 0, pRequiredFlags, pForbiddenFlags, 0);
1378
1379         out = "";
1380         for(i = 0; i < MapInfo_count; ++i)
1381                 out = strcat(out, " ", _MapInfo_GlobItem(MapInfo_FilterList_Lookup(i)));
1382
1383         MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), pRequiredFlags, pForbiddenFlags, 0);
1384
1385         return substring(out, 1, strlen(out) - 1);
1386 }
1387
1388 void MapInfo_LoadMapSettings_SaveGameType(float t)
1389 {
1390         MapInfo_SwitchGameType(t);
1391         cvar_set("gamecfg", ftos(t));
1392         MapInfo_LoadedGametype = t;
1393 }
1394
1395 void MapInfo_LoadMapSettings(string s) // to be called from worldspawn
1396 {
1397         float t;
1398
1399         t = MapInfo_CurrentGametype();
1400         MapInfo_LoadMapSettings_SaveGameType(t);
1401
1402         if(!_MapInfo_CheckMap(s)) // with underscore, it keeps temps
1403         {
1404                 if(cvar("g_mapinfo_allow_unsupported_modes_and_let_stuff_break"))
1405                 {
1406                         print("EMERGENCY: can't play the selected map in the given game mode. Working with only the override settings.\n");
1407                         _MapInfo_Map_ApplyGametypeEx("", t, t);
1408                         return; // do not call Get_ByName!
1409                 }
1410
1411                 if(MapInfo_Map_supportedGametypes == 0)
1412                 {
1413                         print("Mapinfo system is not functional at all. Assuming deathmatch.\n");
1414                         MapInfo_Map_supportedGametypes = MAPINFO_TYPE_DEATHMATCH;
1415                         MapInfo_LoadMapSettings_SaveGameType(MAPINFO_TYPE_DEATHMATCH);
1416                         _MapInfo_Map_ApplyGametypeEx("", MAPINFO_TYPE_DEATHMATCH, MAPINFO_TYPE_DEATHMATCH);
1417                         return; // do not call Get_ByName!
1418                 }
1419
1420                 t = 1;
1421                 while(!(MapInfo_Map_supportedGametypes & 1))
1422                 {
1423                         t *= 2;
1424                         MapInfo_Map_supportedGametypes = floor(MapInfo_Map_supportedGametypes / 2);
1425                 }
1426
1427                 // t is now a supported mode!
1428                 print("EMERGENCY: can't play the selected map in the given game mode. Falling back to a supported mode.\n");
1429                 MapInfo_LoadMapSettings_SaveGameType(t);
1430         }
1431         MapInfo_Get_ByName(s, 1, t);
1432 }
1433
1434 void MapInfo_ClearTemps()
1435 {
1436         MapInfo_Map_bspname = string_null;
1437         MapInfo_Map_title = string_null;
1438         MapInfo_Map_titlestring = string_null;
1439         MapInfo_Map_description = string_null;
1440         MapInfo_Map_author = string_null;
1441         MapInfo_Map_clientstuff = string_null;
1442         MapInfo_Map_supportedGametypes = 0;
1443         MapInfo_Map_supportedFeatures = 0;
1444 }
1445
1446 void MapInfo_Shutdown()
1447 {
1448         MapInfo_ClearTemps();
1449         MapInfo_Filter_Free();
1450         MapInfo_Cache_Destroy();
1451         if(_MapInfo_globopen)
1452         {
1453                 search_end(_MapInfo_globhandle);
1454                 _MapInfo_globhandle = -1;
1455                 _MapInfo_globopen = false;
1456         }
1457 }
1458
1459 int MapInfo_ForbiddenFlags()
1460 {
1461         int f = MAPINFO_FLAG_FORBIDDEN;
1462
1463 #ifndef MENUQC
1464         if (!cvar("g_maplist_allow_hidden"))
1465 #endif
1466                 f |= MAPINFO_FLAG_HIDDEN;
1467
1468         if (!cvar("g_maplist_allow_frustrating"))
1469                 f |= MAPINFO_FLAG_FRUSTRATING;
1470
1471         return f;
1472 }
1473
1474 int MapInfo_RequiredFlags()
1475 {
1476         int f = 0;
1477
1478         if(cvar("g_maplist_allow_frustrating") > 1)
1479                 f |= MAPINFO_FLAG_FRUSTRATING;
1480
1481         return f;
1482 }