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