Xonotic QuakeC
The free, fast arena FPS with crisp movement and a wide array of weapons
mapinfo.qc
Go to the documentation of this file.
1#include "mapinfo.qh"
2#if defined(CSQC)
3 #include <common/util.qh>
5#elif defined(MENUQC)
6#elif defined(SVQC)
7 #include <common/util.qh>
9#endif
10
12
13#ifdef SVQC
15 #define WARN_COND (!autocvar_g_mapinfo_ignore_warnings && MapInfo_Map_bspname == mi_shortname)
16#else
17 #define WARN_COND false
18#endif
19
21{
22 if (REGISTRY_MAX(Gametypes) > 24)
23 if (a >= 24)
24 {
25 a -= 24;
26 if (REGISTRY_MAX(Gametypes) > 48)
27 if (a >= 24)
28 {
29 a -= 24;
30 return '0 0 1' * BIT(a);
31 }
32 return '0 1 0' * BIT(a);
33 }
34 return '1 0 0' * BIT(a);
35}
36
37// generic string stuff
38
42
52
60
62{
64 return;
65
67}
68
95
96float MapInfo_Cache_Retrieve(string map)
97{
98 float i;
99 string s;
101 return 0;
102
104 if(s == "")
105 return 0;
106 i = stof(s);
107
108 // now retrieve all the stuff
117
118 return 1;
119}
120
121// GLOB HANDLING (for all BSP files)
125string _MapInfo_GlobItem(float i)
126{
127 string s;
129 return string_null;
131 return substring(s, 5, strlen(s) - 9); // without maps/ and .bsp
132}
133
135{
137 {
140 }
142 _MapInfo_globhandle = search_begin("maps/*.bsp", true, true);
143 if(_MapInfo_globhandle >= 0)
144 {
147 }
148 else
150}
151
152// filter the info by gametype mask (updates MapInfo_count)
153//
157{
158 return stof(bufstr_get(_MapInfo_filtered, i));
159}
160
161void _MapInfo_FilterList_swap(float i, float j, entity pass)
162{
163 string h = bufstr_get(_MapInfo_filtered, i);
164 bufstr_set(_MapInfo_filtered, i, bufstr_get(_MapInfo_filtered, j));
165 bufstr_set(_MapInfo_filtered, j, h);
166}
167
168float _MapInfo_FilterList_cmp(float i, float j, entity pass)
169{
170 string a, b;
171 a = _MapInfo_GlobItem(stof(bufstr_get(_MapInfo_filtered, i)));
172 b = _MapInfo_GlobItem(stof(bufstr_get(_MapInfo_filtered, j)));
173 return strcasecmp(a, b);
174}
175
176float MapInfo_FilterGametype(Gametype pGametype, int pFeatures, int pFlagsRequired, int pFlagsForbidden, bool pAbortOnGenerate)
177{
178 return _MapInfo_FilterGametype(pGametype.gametype_flags, pFeatures, pFlagsRequired, pFlagsForbidden, pAbortOnGenerate);
179}
180float _MapInfo_FilterGametype(vector pGametype, int pFeatures, int pFlagsRequired, int pFlagsForbidden, bool pAbortOnGenerate)
181{
182 float i, j;
184 {
187 }
188 MapInfo_count = 0;
189 for(i = 0, j = -1; i < _MapInfo_globcount; ++i)
190 {
191 if(MapInfo_Get_ByName(_MapInfo_GlobItem(i), 1, NULL) == 2) // if we generated one... BAIL OUT and let the caller continue in the next frame.
192 if(pAbortOnGenerate)
193 {
194 LOG_TRACE("Autogenerated a .mapinfo, doing the rest later.");
196 return 0;
197 }
198 if(MapInfo_Map_supportedGametypes & pGametype)
199 if((MapInfo_Map_supportedFeatures & pFeatures) == pFeatures)
200 if((MapInfo_Map_flags & pFlagsForbidden) == 0)
201 if((MapInfo_Map_flags & pFlagsRequired) == pFlagsRequired)
202 bufstr_set(_MapInfo_filtered, ++j, ftos(i));
203 }
204 MapInfo_count = j + 1;
206
207 // sometimes the glob isn't sorted nicely, so fix it here...
209
210 return 1;
211}
212void MapInfo_FilterString(string sf)
213{
214 // this function further filters _MapInfo_filtered, which is prepared by MapInfo_FilterGametype by string
215 float i, j;
216 string title;
217
218 for(i = 0, j = -1; i < MapInfo_count; ++i)
219 {
220 if (MapInfo_Get_ByID(i))
221 {
222 // prepare for keyword filter
223 if (MapInfo_Map_title && strstrofs(MapInfo_Map_title, "<TITLE>", 0) == -1)
224 title = MapInfo_Map_title;
225 else
226 title = MapInfo_Map_bspname;
227 // keyword filter
228 if((strstrofs(strtolower(title), strtolower(sf), 0)) >= 0)
229 bufstr_set(_MapInfo_filtered, ++j, bufstr_get(_MapInfo_filtered, i));
230 }
231 }
232 MapInfo_count = j + 1;
234
235 // sometimes the glob isn't sorted nicely, so fix it here...
237}
238
240{
242 {
243 buf_del(_MapInfo_filtered);
245 }
246}
247
248// load info about the i-th map into the MapInfo_Map_* globals
249string MapInfo_BSPName_ByID(float i)
250{
252}
253
254string unquote(string s)
255{
256 float l = strlen(s);
257 for(float i = 0; i < l; ++i)
258 {
259 string ch = substring(s, i, 1);
260 if((ch != " ") && (ch != "\""))
261 {
262 for(float j = l - i - 1; j > 0; --j)
263 {
264 ch = substring(s, i+j, 1);
265 if(ch != " ") if(ch != "\"")
266 return substring(s, i, j+1);
267 }
268 return substring(s, i, 1);
269 }
270 }
271 return "";
272}
273
275{
276 return MapInfo_Get_ByName(MapInfo_BSPName_ByID(i), 0, NULL) ? true : false;
277}
278
280
281float _MapInfo_Generate(string pFilename) // 0: failure, 1: ok ent, 2: ok bsp
282{
283 string fn;
284 float fh;
285 string s, k, v;
286 vector o;
287 float i;
288 float inWorldspawn;
289 float r;
290 float diameter, spawnpoints;
291 float spawnplaces;
292 bool is_q3df_map = false;
293 vector mapMins, mapMaxs;
294
295 if(autocvar_g_mapinfo_q3compat >= 2) // generate mapinfo using arena data
296 {
297 // try for .arena or .defi files, as they may have more accurate information
298 // supporting .arena AND .defi for the same map
299 bool success = false;
300 fh = -1;
301 fn = _MapInfo_FindArenaFile(pFilename, ".arena");
302 if(fn != "" && (fh = fopen(fn, FILE_READ)) >= 0)
303 {
304 success = _MapInfo_ParseArena(fn, fh, pFilename, NULL, false, true);
305 fclose(fh);
306 }
307 fn = _MapInfo_FindArenaFile(pFilename, ".defi");
308 if(fn != "" && (fh = fopen(fn, FILE_READ)) >= 0)
309 {
310 success |= _MapInfo_ParseArena(fn, fh, pFilename, NULL, true, true);
311 fclose(fh);
312 }
313 if (success && autocvar_g_mapinfo_q3compat == 3)
314 return 3; // skip entity analysis
315 }
316
317 r = 1;
318 fn = strcat("maps/", pFilename, ".ent");
319 fh = fopen(fn, FILE_READ);
320 if(fh < 0)
321 {
322 r = 2;
323 fn = strcat("maps/", pFilename, ".bsp");
324 fh = fopen(fn, FILE_READ);
325 }
326 if(fh < 0)
327 return 0;
328 LOG_INFO("Generating ", pFilename, ".mapinfo: analyzing ", fn);
329
330 inWorldspawn = 2;
331 spawnpoints = 0;
332 spawnplaces = 0;
334 mapMins = '0 0 0';
335 mapMaxs = '0 0 0';
336
337 for (;;)
338 {
339 if (!((s = fgets(fh))))
340 break;
341 if(inWorldspawn == 1)
342 if(startsWith(s, "}"))
343 inWorldspawn = 0;
344 k = unquote(car(s));
345 v = unquote(cdr(s));
346 if(inWorldspawn)
347 {
348 if(k == "classname" && v == "worldspawn")
349 inWorldspawn = 1;
350 else if(k == "author")
352 else if(k == "_description")
354 else if(k == "music")
356 else if(k == "noise")
358 else if(k == "message" && (!MapInfo_Map_title || MapInfo_Map_title == "<TITLE>") && v != "")
360 }
361 else
362 {
363 if(k == "origin")
364 {
365 o = stov(strcat("'", v, "'"));
366 mapMins.x = min(mapMins.x, o.x);
367 mapMins.y = min(mapMins.y, o.y);
368 mapMins.z = min(mapMins.z, o.z);
369 mapMaxs.x = max(mapMaxs.x, o.x);
370 mapMaxs.y = max(mapMaxs.y, o.y);
371 mapMaxs.z = max(mapMaxs.z, o.z);
372 }
373 else if(k == "race_place")
374 {
375 if(stof(v) > 0)
376 spawnplaces = 1;
377 }
378 else if(k == "classname")
379 {
380 if(v == "info_player_team1")
381 ++spawnpoints;
382 else if(v == "info_player_team2")
383 ++spawnpoints;
384 else if(v == "info_player_start")
385 ++spawnpoints;
386 else if(v == "info_player_deathmatch")
387 ++spawnpoints;
388 else if(v == "weapon_nex")
389 { }
390 else if(v == "weapon_railgun")
391 { }
392 else if(startsWith(v, "weapon_"))
394 else if(startsWith(v, "turret_"))
396 else if(startsWith(v, "vehicle_"))
398 else if(startsWith(v, "monster_"))
400 else if(v == "target_music" || v == "trigger_music")
401 _MapInfo_Map_worldspawn_music = string_null; // don't use regular BGM
402 else if(v == "target_stopTimer")
403 is_q3df_map = true; // don't support standard gametypes UNLESS we found them in .arena
404 else
405 FOREACH(Gametypes, true, it.m_generate_mapinfo(it, v));
406 }
407 }
408 }
409 if(inWorldspawn)
410 {
411 LOG_WARN(fn, " ended still in worldspawn, BUG");
412 return 0;
413 }
414 diameter = vlen(mapMaxs - mapMins);
415
416 vector twoBaseModes = '0 0 0';
417 FOREACH(Gametypes, it.m_isTwoBaseMode(), twoBaseModes |= it.gametype_flags);
418 if(twoBaseModes && (twoBaseModes &= MapInfo_Map_supportedGametypes))
419 {
420 // we have a symmetrical map, don't add the modes without bases
421 }
422 else if(!is_q3df_map)
423 {
424 FOREACH(Gametypes, it.m_isAlwaysSupported(it, spawnpoints, diameter), MapInfo_Map_supportedGametypes |= it.gametype_flags);
425 }
426
427 if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_RACE.gametype_flags)
428 if(!spawnplaces)
429 {
430 MapInfo_Map_supportedGametypes &= ~MAPINFO_TYPE_RACE.gametype_flags;
431 MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CTS.gametype_flags;
432 }
433
434 LOG_TRACE("-> diameter ", ftos(diameter));
435 LOG_TRACE("; spawnpoints ", ftos(spawnpoints));
437
438 fclose(fh);
439
440 return r;
441}
442
444{
445 MapInfo_Map_title = "<TITLE>";
446 MapInfo_Map_titlestring = "<TITLE>";
447 MapInfo_Map_description = "<DESCRIPTION>";
448 MapInfo_Map_author = "<AUTHOR>";
453 MapInfo_Map_fog = "";
454 MapInfo_Map_mins = '0 0 0';
455 MapInfo_Map_maxs = '0 0 0';
456}
457
459{
460 return t.m_legacydefaults;
461}
462
463void _MapInfo_Map_ApplyGametype(string s, Gametype pWantedType, Gametype pThisType, int load_default)
464{
465 string sa;
466 MapInfo_Map_supportedGametypes |= pThisType.gametype_flags;
467 if(!(pThisType.gametype_flags & pWantedType.gametype_flags))
468 return;
469
470 if(load_default)
471 _MapInfo_Map_ApplyGametype(_MapInfo_GetDefault(pThisType), pWantedType, pThisType, false);
472
473 if(!pWantedType.frags) // these modes don't use fraglimit
474 {
475 cvar_set("fraglimit", "0");
476 }
477 else
478 {
479 sa = car(s);
480 if(sa != "")
481 cvar_set("fraglimit", sa);
482 s = cdr(s);
483 }
484
485 sa = car(s);
486 if(sa != "")
487 cvar_set("timelimit", sa);
488 s = cdr(s);
489
490 if(pWantedType.m_setTeams)
491 {
492 sa = car(s);
493 if(sa != "")
494 pWantedType.m_setTeams(sa);
495 s = cdr(s);
496 }
497
498 // rc = timelimit timelimit_qualification laps laps_teamplay
499 if(pWantedType == MAPINFO_TYPE_RACE)
500 {
501 cvar_set("fraglimit", "0"); // special case!
502
503 sa = car(s); if(sa == "") sa = cvar_string("timelimit");
504 cvar_set("g_race_qualifying_timelimit", sa);
505 s = cdr(s);
506
507 sa = car(s);
508 if(sa != "")
509 if(cvar("g_race_teams") < 2)
510 cvar_set("fraglimit", sa);
511 s = cdr(s);
512
513 sa = car(s);
514 if(sa != "")
515 if(cvar("g_race_teams") >= 2)
516 cvar_set("fraglimit", sa);
517 s = cdr(s);
518 }
519
520 if(!pWantedType.frags) // these modes don't use fraglimit
521 {
522 cvar_set("leadlimit", "0");
523 }
524 else
525 {
526 sa = car(s);
527 if(sa != "")
528 cvar_set("leadlimit", sa);
529 s = cdr(s);
530 }
531}
532
534{
535 return t ? t.model2 : "";
536}
537
539{
540 return t ? t.team : false;
541}
542
543void _MapInfo_Map_ApplyGametypeEx(string s, Gametype pWantedType, Gametype pThisType)
544{
545 MapInfo_Map_supportedGametypes |= pThisType.gametype_flags;
546 if (!(pThisType.gametype_flags & pWantedType.gametype_flags))
547 return;
548
549 // reset all the cvars to their defaults
550
551 cvar_set("timelimit", cvar_defstring("timelimit"));
552 cvar_set("leadlimit", cvar_defstring("leadlimit"));
553 cvar_set("fraglimit", cvar_defstring("fraglimit"));
554 FOREACH(Gametypes, true, it.m_parse_mapinfo(string_null, string_null));
555
556 string fraglimit_normal = string_null;
557 string fraglimit_teams = string_null;
558
559 for (s = strcat(_MapInfo_GetDefaultEx(pWantedType), " ", s); s != ""; s = cdr(s)) {
560 string sa = car(s);
561 if (sa == "") continue;
562 int p = strstrofs(sa, "=", 0);
563 if (p < 0) {
564 if(WARN_COND)
565 LOG_WARNF("Invalid gametype setting in mapinfo for gametype %s: %s", MapInfo_Type_ToString(pWantedType), sa);
566 continue;
567 }
568 string k = substring(sa, 0, p);
569 string v = substring(sa, p + 1, -1);
570 bool handled = true;
571 switch (k) {
572 case "timelimit":
573 {
574 cvar_set("timelimit", v);
575 break;
576 }
577 case "leadlimit":
578 {
579 cvar_set("leadlimit", v);
580 break;
581 }
582 case "pointlimit":
583 case "fraglimit":
584 case "lives":
585 case "laplimit":
586 case "caplimit":
587 {
588 fraglimit_normal = v;
589 break;
590 }
591 case "teampointlimit":
592 case "teamlaplimit":
593 {
594 fraglimit_teams = v;
595 break;
596 }
597 default:
598 {
599 handled = false;
600 break;
601 }
602 }
603 FOREACH(Gametypes, true, handled |= it.m_parse_mapinfo(k, v));
604 if (!handled && WARN_COND)
605 LOG_WARNF("Invalid gametype setting in mapinfo for gametype %s: %s", MapInfo_Type_ToString(pWantedType), sa);
606 }
607
608 if (pWantedType == MAPINFO_TYPE_RACE && cvar("g_race_teams") >= 2)
609 {
610 if(fraglimit_teams)
611 cvar_set("fraglimit", fraglimit_teams);
612 }
613 else
614 {
615 if(fraglimit_normal)
616 cvar_set("fraglimit", fraglimit_normal);
617 }
618}
619
620Gametype MapInfo_Type_FromString(string gtype, bool dowarn, bool is_q3compat)
621{
622 string replacement = "";
623
624 switch (gtype)
625 {
626 case "nexball": replacement = "nb"; break;
627 case "freezetag": replacement = "ft"; break;
628 case "keepaway": replacement = "ka"; break;
629 case "invasion": replacement = "inv"; break;
630 case "assault": replacement = "as"; break;
631 case "race": replacement = "rc"; break;
632 // Q3/QL compat, see DoesQ3ARemoveThisEntity() in quake3.qc for complete lists
633 case "ffa": replacement = "dm"; break;
634 case "cctf": // from ThreeWave, maps with this should all have "ctf" too
635 case "oneflag": replacement = "ctf"; break;
636 case "tourney": replacement = "duel"; break;
637 case "arena": // which Q3 mod is this from? In Nexuiz it was 'duel with rounds'.
638 if(is_q3compat) { replacement = "ca"; } break;
639 }
640 if (replacement != "")
641 {
642 if (dowarn && WARN_COND)
643 LOG_WARNF("MapInfo_Type_FromString (probably %s): using deprecated name '%s'. Should use '%s'.", MapInfo_Map_bspname, gtype, replacement);
644 gtype = replacement;
645 }
646 FOREACH(Gametypes, it.mdl == gtype, return it);
647 return NULL;
648}
649
651{
652 return t ? t.m_description : "";
653}
654
656{
657 return t ? t.mdl : "";
658}
659
661{
662 /* xgettext:no-c-format */
663 return t ? t.message : _("@!#%'n Tuba Throwing");
664}
665
666void _MapInfo_Parse_Settemp(string pFilename, string acl, float type, string s, float recurse)
667{
668 string t;
669 float o;
670 // tabs are invalid, treat them as "empty"
671 s = strreplace("\t", "", s);
672
673 t = car(s); s = cdr(s);
674
675 // limited support of "" and comments
676 // remove trailing and leading " of t
677 if(substring(t, 0, 1) == "\"")
678 {
679 if(substring(t, -1, 1) == "\"")
680 t = substring(t, 1, -2);
681 }
682
683 // remove leading " of s
684 if(substring(s, 0, 1) == "\"")
685 {
686 s = substring(s, 1, -1);
687 }
688 // remove trailing " of s, and all that follows (cvar description)
689 o = strstrofs(s, "\"", 0);
690 if(o >= 0)
691 s = substring(s, 0, o);
692
693 // remove // comments
694 o = strstrofs(s, "//", 0);
695 if(o >= 0)
696 s = substring(s, 0, o);
697
698 // remove trailing spaces
699 while(substring(s, -1, 1) == " ")
700 s = substring(s, 0, -2);
701
702 if(t == "#include")
703 {
704 if(recurse > 0)
705 {
706 float fh = fopen(s, FILE_READ);
707 if(fh < 0)
708 {
709 if(WARN_COND)
710 LOG_WARN("Map ", pFilename, " references not existing config file ", s);
711 }
712 else
713 {
714 while((s = fgets(fh)))
715 {
716 s = strreplace("\t", "", s); // treat tabs as "empty", perform here first to ensure coments are detected
717 // catch different sorts of comments
718 if(s == "") // empty lines
719 continue;
720 if(substring(s, 0, 1) == "#") // UNIX style
721 continue;
722 if(substring(s, 0, 2) == "//") // C++ style
723 continue;
724 if(substring(s, 0, 1) == "_") // q3map style
725 continue;
726
727 if(substring(s, 0, 4) == "set ")
728 s = substring(s, 4, -1);
729 if(substring(s, 0, 5) == "seta ")
730 s = substring(s, 5, -1);
731
732 _MapInfo_Parse_Settemp(pFilename, acl, type, s, recurse - 1);
733 }
734 fclose(fh);
735 }
736 }
737 else if(WARN_COND)
738 LOG_WARN("Map ", pFilename, " uses too many levels of inclusion");
739 }
740 else if(t == ""
741 || !cvar_value_issafe(t)
742 || !cvar_value_issafe(s)
744 {
745 if (WARN_COND)
746 LOG_WARN("Map ", pFilename, " contains a potentially harmful setting, ignored");
747 }
748 else if(matchacl(acl, t) <= 0)
749 {
750 if (WARN_COND)
751 LOG_WARN("Map ", pFilename, " contains a denied setting, ignored");
752 }
753 else
754 {
755 if(type == 0) // server set
756 {
757 LOG_TRACE("Applying temporary setting ", t, " := ", s);
758 cvar_settemp(t, s);
759 }
760 else
761 {
762 LOG_TRACE("Applying temporary client setting ", t, " := ", s);
764 MapInfo_Map_clientstuff, "cl_cmd settemp \"", t, "\" \"", s, "\"\n"
765 );
766 }
767 }
768}
769
772string MapInfo_title_sans_author(string title)
773{
774 int offset;
775
776 if ((offset = strstrofs(title, " by ", 0)) >= 0)
777 {
778 if (MapInfo_Map_author == "<AUTHOR>")
779 MapInfo_Map_author = substring(title, offset + 4, strlen(title) - (offset + 4));
780 title = substring(title, 0, offset);
781 }
782 else if ((offset = strstrofs(title, " (by ", 0)) >= 0 || (offset = strstrofs(title, " [by ", 0)) >= 0)
783 {
784 if (MapInfo_Map_author == "<AUTHOR>")
785 MapInfo_Map_author = substring(title, offset + 5, strlen(title) - (offset + 5) - 1);
786 title = substring(title, 0, offset);
787 }
788 else if ((offset = strstrofs(title, "Made By ", 0)) >= 0) // often at the start of the string
789 {
790 if (MapInfo_Map_author == "<AUTHOR>")
791 MapInfo_Map_author = substring(title, offset + 8, strlen(title) - (offset + 8));
792 title = substring(title, 0, offset);
793 }
794
795 return title != "" ? title : "<TITLE>";
796}
797
798bool MapInfo_isRedundant(string fn, string t)
799{
800 // normalize file name
801 fn = strreplace("_", "", fn);
802 fn = strreplace("-", "", fn);
803
804 // normalize visible title
805 t = strreplace(":", "", t);
806 t = strreplace(" ", "", t);
807 t = strreplace("_", "", t);
808 t = strreplace("-", "", t);
809 t = strreplace("'", "", t);
810 t = strdecolorize(t);
811
812 // we allow the visible title to have punctuation the file name does
813 // not, but not vice versa
814 if(!strcasecmp(fn, t))
815 return true;
816
817 return false;
818}
819
820bool _MapInfo_ParseArena(string arena_filename, int fh, string pFilename, Gametype pGametypeToSet, bool isdefi, bool isgenerator)
821{
822 // NOTE: .arena files can hold more than 1 map's information!
823 // to handle this, we're going to store gathered information in local variables and save it if we encounter the correct map name
824 bool in_brackets = false; // testing a potential mapinfo section (within brackets)
825 bool dosave = false;
826 string stored_Map_description = "";
827 string stored_Map_title = "";
828 string stored_Map_author = "";
829 vector stored_supportedGametypes = '0 0 0';
830 int stored_supportedFeatures = 0;
831 int stored_flags = 0;
832 string t, s;
833
834 if (isgenerator)
835 LOG_INFO("Generating ", pFilename, ".mapinfo: analyzing ", arena_filename);
836
837 for (;;)
838 {
839 if (!((s = fgets(fh))))
840 break;
841
842 // catch different sorts of comments
843 if(s == "") // empty lines
844 continue;
845 if(substring(s, 0, 2) == "//") // C++ style
846 continue;
847 if(strstrofs(s, "{", 0) >= 0)
848 {
849 if(in_brackets)
850 return false; // edge case? already in a bracketed section!
851 in_brackets = true;
852 continue;
853 }
854 else if(!in_brackets)
855 {
856 // if we're not inside a bracket, don't process map info
857 continue;
858 }
859 if(strstrofs(s, "}", 0) >= 0)
860 {
861 if(!in_brackets)
862 return false; // no starting bracket! let the mapinfo generation system handle it
863 in_brackets = false;
864 if(dosave)
865 {
866 MapInfo_Map_description = stored_Map_description;
867 if(stored_Map_title != "")
868 MapInfo_Map_title = stored_Map_title;
869 if(stored_Map_author != "") // write the usual "<AUTHOR>" if we have nothing better
870 MapInfo_Map_author = stored_Map_author;
871 // might have .arena AND .defi for the same map so these bitfields are OR'd
872 if(isgenerator)
873 MapInfo_Map_supportedGametypes |= stored_supportedGametypes;
874 else
875 {
876 FOREACH(Gametypes, it.gametype_flags & stored_supportedGametypes,
877 {
878 _MapInfo_Map_ApplyGametype ("", pGametypeToSet, it, true);
879 });
880 }
881 MapInfo_Map_supportedFeatures |= stored_supportedFeatures;
882 MapInfo_Map_flags |= stored_flags;
883 return true; // no need to continue through the file, we have our map!
884 }
885 else
886 {
887 // discard any gathered locals, we're not using the correct map!
888 stored_Map_description = "";
889 stored_Map_title = "";
890 stored_Map_author = "";
891 stored_supportedGametypes = '0 0 0';
892 stored_supportedFeatures = 0;
893 stored_flags = 0;
894 continue;
895 }
896 }
897
898 s = strreplace("\t", " ", s);
899
900 float p = strstrofs(s, "//", 0);
901 if(p >= 0)
902 s = substring(s, 0, p);
903
904 // perform an initial trim to ensure the first argument is properly obtained
905 // remove leading spaces
906 while(substring(s, 0, 1) == " ")
907 s = substring(s, 1, -1);
908
909 t = car(s); s = cdr(s);
910 t = strtolower(t); // apparently some q3 maps use capitalized parameters
911
912 // remove trailing spaces
913 while(substring(t, -1, 1) == " ")
914 t = substring(t, 0, -2);
915
916 // remove trailing spaces
917 while(substring(s, -1, 1) == " ")
918 s = substring(s, 0, -2);
919 // remove leading spaces
920 while(substring(s, 0, 1) == " ")
921 s = substring(s, 1, -1);
922 // limited support of ""
923 // remove trailing and leading " of s
924 if(substring(s, 0, 1) == "\"")
925 {
926 if(substring(s, -1, 1) == "\"")
927 s = substring(s, 1, -2);
928 }
929 if(t == "longname")
930 stored_Map_title = s;
931 else if(t == "author")
932 stored_Map_author = s;
933 else if(t == "type")
934 {
935 // if there is a valid gametype in this .arena file, include it in the menu
936 stored_supportedFeatures |= MAPINFO_FEATURE_WEAPONS;
937 // type in quake 3 holds all the supported gametypes, so we must loop through all of them
938 // TODO: handle support here better to include more Xonotic teamplay modes
939 string types = s;
940 types = strreplace("team", "tdm ft", types);
941 types = strreplace("ffa", "dm lms ka", types);
942 types = strreplace("tourney", "duel", types); // QL used duel so the following check must support it
943 if(strstrofs(types, "duel", 0) < 0 && strstrofs(types, "tdm", 0) >= 0) // larger team map, support additional gametypes!
944 types = cons(types, "ca kh");
945 FOREACH_WORD(types, true,
946 {
947 Gametype f = MapInfo_Type_FromString(it, false, true);
948 if(f)
949 stored_supportedGametypes |= f.gametype_flags;
950 });
951 }
952 else if(t == "style" && isdefi)
953 {
954 // we have a defrag map on our hands, add CTS!
955 // TODO: styles
956 stored_supportedGametypes |= MAPINFO_TYPE_CTS.gametype_flags;
957 }
958 else if(t == "map")
959 {
960 if(strtolower(s) == strtolower(pFilename))
961 dosave = true; // yay, found our map!
962 }
963 else if(t == "quote")
964 stored_Map_description = s;
965 // TODO: fraglimit
966 }
967
968 // if the map wasn't found in the .arena, fall back to generated .mapinfo
969 return false;
970}
971
972string _MapInfo_CheckArenaFile(string pFilename, string pMapname)
973{
974 // returns the file name if valid, otherwise returns ""
975 // a string is returned to optimise the use cases where a filename is also returned
976 int fh = fopen(pFilename, FILE_READ);
977 if(fh < 0)
978 return "";
979 for(string s; (s = fgets(fh)); )
980 {
981 s = strreplace("\t", "", s);
982 while(substring(s, 0, 1) == " ")
983 s = substring(s, 1, -1);
984 if(substring(s, 0, 2) == "//")
985 continue;
986 if(s == "")
987 continue;
988 int offset = strstrofs(s, "map", 0);
989 if(offset >= 0)
990 {
991 if(strstrofs(strtolower(s), strcat("\"", strtolower(pMapname), "\""), offset) >= 0) // quake 3 is case insensitive
992 {
993 fclose(fh);
994 return pFilename; // FOUND IT!
995 }
996 }
997 }
998 fclose(fh);
999 return ""; // file did not contain a "map" field matching our map name
1000}
1001
1002string _MapInfo_FindArenaFile(string pFilename, string extension)
1003{
1004 // A valid arena file with the "default" name may have been created as a map-specific
1005 // override of an arena file with a different name (possibly multi-map) in the pk3
1006 // (occasionally in the wild such file exists in the pk3 but isn't valid for a map in the pk3).
1007 // Therefore we try the "default" name first.
1008 string afile = _MapInfo_CheckArenaFile(strcat("scripts/", pFilename, extension), pFilename);
1009 if (afile != ""
1010 || !checkextension("DP_QC_FS_SEARCH_PACKFILE"))
1011 return afile;
1012
1013 string base_pack = whichpack(strcat("maps/", pFilename, ".bsp"));
1014 if(base_pack == "") // this map isn't packaged!
1015 return "";
1016 int glob = search_packfile_begin(strcat("scripts/*", extension), true, true, base_pack);
1017 if(glob < 0) // there's no matching file in the pack
1018 return "";
1019 int n = search_getsize(glob);
1020 for(int j = 0; j < n; ++j)
1021 {
1022 afile = search_getfilename(glob, j);
1023 if(_MapInfo_CheckArenaFile(afile, pFilename) != "")
1024 {
1025 search_end(glob);
1026 return afile;
1027 }
1028 }
1029 search_end(glob);
1030
1031 return ""; // if we get here, a valid .arena file could not be found
1032}
1033
1034// load info about a map by name into the MapInfo_Map_* globals
1035float MapInfo_Get_ByName_NoFallbacks(string pFilename, int pAllowGenerate, Gametype pGametypeToSet)
1036{
1037 string fn;
1038 string s, t;
1039 float fh;
1040 int f, i;
1041 float r, n;
1042 string acl = MAPINFO_SETTEMP_ACL_USER;
1043
1044 if(strstrofs(pFilename, "/", 0) >= 0)
1045 {
1046 LOG_WARN("Invalid character in map name, ignored");
1047 return 0;
1048 }
1049
1050 if(pGametypeToSet == NULL)
1051 if(MapInfo_Cache_Retrieve(pFilename))
1052 return 1;
1053
1054 r = 1;
1055
1056 MapInfo_Map_bspname = pFilename;
1057
1058 // default all generic fields so they have "good" values in case something fails
1059 fn = strcat("maps/", pFilename, ".mapinfo");
1060 fh = fopen(fn, FILE_READ);
1061 if(fh < 0)
1062 {
1063 if(autocvar_g_mapinfo_q3compat == 1) // use arena data instead of generating a mapinfo file
1064 {
1065 // supporting .arena AND .defi for the same map
1066 bool success = false;
1067 fn = _MapInfo_FindArenaFile(pFilename, ".arena");
1068 if(fn != "" && (fh = fopen(fn, FILE_READ)) >= 0)
1069 {
1071 success = _MapInfo_ParseArena(fn, fh, pFilename, pGametypeToSet, false, false);
1072 fclose(fh);
1073 }
1074 fn = _MapInfo_FindArenaFile(pFilename, ".defi");
1075 if(fn != "" && (fh = fopen(fn, FILE_READ)) >= 0)
1076 {
1077 if(!success)
1079 success |= _MapInfo_ParseArena(fn, fh, pFilename, pGametypeToSet, true, false);
1080 fclose(fh);
1081 }
1082 if(success)
1083 goto mapinfo_handled; // skip generation
1084 }
1085
1086 fn = strcat("maps/autogenerated/", pFilename, ".mapinfo");
1087 fh = fopen(fn, FILE_READ);
1088 if(fh < 0)
1089 {
1090 if(!pAllowGenerate)
1091 return 0;
1093 r = _MapInfo_Generate(pFilename);
1094 if(!r)
1095 return 0;
1097 fh = fopen(fn, FILE_WRITE);
1098 fputs(fh, strcat("title ", MapInfo_Map_title, "\n"));
1099 fputs(fh, strcat("description ", MapInfo_Map_description, "\n"));
1100 fputs(fh, strcat("author ", MapInfo_Map_author, "\n"));
1102 {
1104 fputs(fh, strcat("cdtrack ", substring(_MapInfo_Map_worldspawn_music, 0, -4), "\n"));
1105 else
1106 fputs(fh, strcat("cdtrack ", _MapInfo_Map_worldspawn_music, "\n"));
1107 }
1108 else
1109 {
1110 n = tokenize_console(cvar_string("g_cdtracks_remaplist"));
1111 s = strcat(" ", cvar_string("g_cdtracks_dontusebydefault"), " ");
1112 for (;;)
1113 {
1114 i = floor(random() * n);
1115 if(strstrofs(s, strcat(" ", argv(i), " "), 0) < 0)
1116 break;
1117 }
1118 fputs(fh, strcat("cdtrack ", ftos(i + 1), "\n"));
1119 }
1121 fputs(fh, "has weapons\n");
1122 else
1123 fputs(fh, "// uncomment this if you added weapon pickups: has weapons\n");
1125 fputs(fh, "has turrets\n");
1126 else
1127 fputs(fh, "// uncomment this if you added turrets: has turrets\n");
1129 fputs(fh, "has vehicles\n");
1130 else
1131 fputs(fh, "// uncomment this if you added vehicles: has vehicles\n");
1133 fputs(fh, "frustrating\n");
1134
1135 FOREACH(Gametypes, MapInfo_Map_supportedGametypes & it.gametype_flags, {
1136 fputs(fh, sprintf("gametype %s // defaults: %s\n", MapInfo_Type_ToString(it), _MapInfo_GetDefaultEx(it)));
1137 });
1138
1139 fputs(fh, "// optional: fog density red green blue alpha mindist maxdist\n");
1140 fputs(fh, "// optional: settemp_for_type (all|gametypename) cvarname value\n");
1141 fputs(fh, "// optional: clientsettemp_for_type (all|gametypename) cvarname value\n");
1142 fputs(fh, "// optional: size mins_x mins_y mins_z maxs_x maxs_y maxs_z\n");
1143 fputs(fh, "// optional: hidden\n");
1144
1145 fclose(fh);
1146 r = 2;
1147 // return r;
1148 fh = fopen(fn, FILE_READ);
1149 if(fh < 0)
1150 error("... but I just wrote it!");
1151 }
1152
1153 if(WARN_COND)
1154 LOG_WARN("autogenerated mapinfo file ", fn, " has been loaded; please edit that file and move it to maps/", pFilename, ".mapinfo");
1155 }
1156
1158 for (;;)
1159 {
1160 if (!((s = fgets(fh))))
1161 break;
1162
1163 // catch different sorts of comments
1164 if(s == "") // empty lines
1165 continue;
1166 if(substring(s, 0, 1) == "#") // UNIX style
1167 continue;
1168 if(substring(s, 0, 2) == "//") // C++ style
1169 continue;
1170 if(substring(s, 0, 1) == "_") // q3map style
1171 continue;
1172
1173 float p = strstrofs(s, "//", 0);
1174 if(p >= 0)
1175 s = substring(s, 0, p);
1176
1177 t = car(s); s = cdr(s);
1178 if(t == "title")
1180 else if(t == "description")
1182 else if(t == "author")
1184 else if(t == "has")
1185 {
1186 t = car(s); // s = cdr(s);
1188 else if(t == "turrets") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_TURRETS;
1189 else if(t == "vehicles") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_VEHICLES;
1190 else if(t == "monsters") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_MONSTERS;
1191 else if(t == "new_toys") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_WEAPONS;
1192 else if(WARN_COND)
1193 LOG_WARN("Map ", pFilename, " supports unknown feature ", t, ", ignored");
1194 }
1195 else if(t == "hidden")
1196 {
1198 }
1199 else if(t == "forbidden")
1200 {
1202 }
1203 else if(t == "frustrating")
1204 {
1206 }
1207 else if(t == "donotwant" || t == "noautomaplist")
1208 {
1210 }
1211 else if(t == "gameversion_min")
1212 {
1213 if (cvar("gameversion") < stof(s))
1215 }
1216 else if(t == "type")
1217 {
1218 t = car(s); s = cdr(s);
1219 Gametype f = MapInfo_Type_FromString(t, true, false);
1220 //if(WARN_COND)
1221 //LOG_WARN("Map ", pFilename, " contains the legacy 'type' keyword which is deprecated and will be removed in the future. Please migrate the mapinfo file to 'gametype'.");
1222 if(f)
1223 _MapInfo_Map_ApplyGametype (s, pGametypeToSet, f, true);
1224 else if(WARN_COND)
1225 LOG_DEBUG("Map ", pFilename, " supports unknown gametype ", t, ", ignored");
1226 }
1227 else if(t == "gametype")
1228 {
1229 t = car(s); s = cdr(s);
1230 Gametype f = MapInfo_Type_FromString(t, true, false);
1231 if(f)
1232 _MapInfo_Map_ApplyGametypeEx (s, pGametypeToSet, f);
1233 else if(WARN_COND)
1234 LOG_DEBUG("Map ", pFilename, " supports unknown gametype ", t, ", ignored");
1235 }
1236 else if(t == "size")
1237 {
1238 float a, b, c, d, e;
1239 t = car(s); s = cdr(s); a = stof(t);
1240 t = car(s); s = cdr(s); b = stof(t);
1241 t = car(s); s = cdr(s); c = stof(t);
1242 t = car(s); s = cdr(s); d = stof(t);
1243 t = car(s); s = cdr(s); e = stof(t);
1244 if(s == "")
1245 {
1246 if(WARN_COND)
1247 LOG_WARN("Map ", pFilename, " contains an incorrect size line (not enough params), syntax: size mins_x mins_y mins_z maxs_x maxs_y maxs_z");
1248 }
1249 else
1250 {
1251 t = car(s); s = cdr(s); f = stof(t);
1252 if(s != "")
1253 {
1254 if(WARN_COND)
1255 LOG_WARN("Map ", pFilename, " contains an incorrect size line (too many params), syntax: size mins_x mins_y mins_z maxs_x maxs_y maxs_z");
1256 }
1257 else
1258 {
1259 if(a >= d || b >= e || c >= f)
1260 {
1261 if(WARN_COND)
1262 LOG_WARN("Map ", pFilename, " contains an incorrect size line, mins have to be < maxs");
1263 }
1264 else
1265 {
1266 MapInfo_Map_mins.x = a;
1267 MapInfo_Map_mins.y = b;
1268 MapInfo_Map_mins.z = c;
1269 MapInfo_Map_maxs.x = d;
1270 MapInfo_Map_maxs.y = e;
1271 MapInfo_Map_maxs.z = f;
1272 }
1273 }
1274 }
1275 }
1276 else if(t == "settemp_for_type")
1277 {
1278 t = car(s); s = cdr(s);
1279 bool all = t == "all";
1280 Gametype f = NULL;
1281 if(all || (f = MapInfo_Type_FromString(t, true, false)))
1282 {
1283 if((all ? MAPINFO_TYPE_ALL : f.gametype_flags) & pGametypeToSet.gametype_flags)
1284 {
1285 _MapInfo_Parse_Settemp(pFilename, acl, 0, s, 1);
1286 }
1287 }
1288 else
1289 {
1290 LOG_DEBUG("Map ", pFilename, " has a setting for unknown gametype ", t, ", ignored");
1291 }
1292 }
1293 else if(t == "clientsettemp_for_type")
1294 {
1295 t = car(s); s = cdr(s);
1296 bool all = t == "all";
1297 Gametype f = NULL;
1298 if(all || (f = MapInfo_Type_FromString(t, true, false)))
1299 {
1300 if((all ? MAPINFO_TYPE_ALL : f.gametype_flags) & pGametypeToSet.gametype_flags)
1301 {
1302 _MapInfo_Parse_Settemp(pFilename, acl, 1, s, 1);
1303 }
1304 }
1305 else
1306 {
1307 LOG_DEBUG("Map ", pFilename, " has a client setting for unknown gametype ", t, ", ignored");
1308 }
1309 }
1310 else if(t == "fog")
1311 {
1312 if (!cvar_value_issafe(s))
1313 {
1314 if(WARN_COND)
1315 LOG_WARN("Map ", pFilename, " contains a potentially harmful fog setting, ignored");
1316 }
1317 else
1318 MapInfo_Map_fog = s;
1319 }
1320 else if(t == "cdtrack")
1321 {
1322 t = car(s); s = cdr(s);
1323 // We do this only if pGametypeToSet even though this
1324 // content is theoretically gametype independent,
1325 // because MapInfo_Map_clientstuff contains otherwise
1326 // gametype dependent stuff. That way this value stays
1327 // empty when not setting a gametype to not set any
1328 // false expectations.
1329 if(pGametypeToSet)
1330 {
1331 if (!cvar_value_issafe(t))
1332 {
1333 if(WARN_COND)
1334 LOG_WARN("Map ", pFilename, " contains a potentially harmful cdtrack, ignored");
1335 }
1336 else
1338 MapInfo_Map_clientstuff, "cd loop \"", t, "\"\n"
1339 );
1340 }
1341 }
1342 else if(WARN_COND)
1343 LOG_WARN("Map ", pFilename, " provides unknown info item ", t, ", ignored");
1344 }
1345 fclose(fh);
1346
1347LABEL(mapinfo_handled)
1348#ifdef SVQC
1349 // if the map is currently loaded we can read worldspawn fields directly
1350 if (pFilename == mi_shortname)
1351 {
1352 if (MapInfo_Map_title == "<TITLE>")
1353 if (world.message != "")
1354 MapInfo_Map_title = world.message;
1355 if (MapInfo_Map_author == "<AUTHOR>")
1356 if ((s = GetField_fullspawndata(world, "author", false)) != "")
1358 }
1359#endif
1360 // Could skip removing author from title when it's source is .mapinfo
1361 // but must always do it for world.message and .arena/.defi as VQ3 didn't support author
1362 // so mappers tended to put it in world.message and/or longname.
1363 MapInfo_Map_title = MapInfo_title_sans_author(MapInfo_Map_title); // may set author if not set
1364
1365 if(MapInfo_Map_title == "<TITLE>")
1369 else
1371
1372 if (MapInfo_Map_author == "<AUTHOR>")
1373 MapInfo_Map_author = ""; // don't display "<AUTHOR>" in the UI (we do write it to .mapinfo files)
1374
1376 if(MapInfo_Map_supportedGametypes != '0 0 0')
1377 return r;
1378 if (WARN_COND)
1379 LOG_WARN("Map ", pFilename, " supports no gametypes, ignored");
1380 return 0;
1381}
1382int MapInfo_Get_ByName(string pFilename, float pAllowGenerate, Gametype pGametypeToSet)
1383{
1384 int r = MapInfo_Get_ByName_NoFallbacks(pFilename, pAllowGenerate, pGametypeToSet);
1385
1386 FOREACH(Gametypes, it.m_isForcedSupported(it), _MapInfo_Map_ApplyGametypeEx("", pGametypeToSet, it));
1387
1388 if(pGametypeToSet)
1389 {
1390 if(!(MapInfo_Map_supportedGametypes & pGametypeToSet.gametype_flags))
1391 {
1392 error("Can't select the requested gametype. This should never happen as the caller should prevent it!\n");
1393 //_MapInfo_Map_ApplyGametypeEx("", pGametypeToSet, MAPINFO_TYPE_DEATHMATCH);
1394 //return;
1395 }
1396 }
1397
1398 return r;
1399}
1400
1401bool MapReadSizes(string map)
1402{
1403 // TODO: implement xonotic#28 / xonvote 172 (sizes in mapinfo)
1404 string readsize_msg = strcat("MapReadSizes ", map);
1405 float fh = fopen(strcat("maps/", map, ".sizes"), FILE_READ);
1406 if(fh >= 0)
1407 {
1408 map_minplayers = stoi(fgets(fh));
1409 map_maxplayers = stoi(fgets(fh));
1410 fclose(fh);
1411 LOG_TRACEF(readsize_msg, ": ok, min %d max %d", map_minplayers, map_maxplayers);
1412 return true;
1413 }
1414 LOG_TRACE(readsize_msg, ": not found");
1415 return false;
1416}
1417
1418float MapInfo_FindName(string s)
1419{
1420 // if there is exactly one map of prefix s, return it
1421 // if not, return the null string
1422 // note that DP sorts glob results... so I can use a binary search
1423 float l, r, m, cmp;
1424 l = 0;
1425 r = MapInfo_count;
1426 // invariants: r is behind s, l-1 is equal or before
1427 while(l != r)
1428 {
1429 m = floor((l + r) / 2);
1432 if(cmp == 0)
1433 return m; // found and good
1434 if(cmp < 0)
1435 l = m + 1; // l-1 is before s
1436 else
1437 r = m; // behind s
1438 }
1441 // r == l, so: l is behind s, l-1 is before
1442 // SO: if there is any, l is the one with the right prefix
1443 // and l+1 may be one too
1444 if(l == MapInfo_count)
1445 {
1448 return -1; // no MapInfo_FindName_match, behind last item
1449 }
1451 {
1454 return -1; // wrong prefix
1455 }
1456 if(l == MapInfo_count - 1)
1457 return l; // last one, nothing can follow => unique
1459 {
1461 return -1; // ambigous MapInfo_FindName_match
1462 }
1463 return l;
1464}
1465
1466string MapInfo_FixName(string s)
1467{
1470}
1471
1473{
1474 int req = 0;
1475 // TODO: find a better way to check if weapons are required on the map
1476 if(!(cvar("g_instagib") || cvar("g_overkill") || cvar("g_nix") || cvar("g_weaponarena") || !cvar("g_pickup_items") || !cvar("g_melee_only")
1477 || cvar("g_race") || cvar("g_cts") || cvar("g_nexball") || cvar("g_ca") || cvar("g_freezetag") || cvar("g_lms")))
1479 return req;
1480}
1481
1483{
1484 Gametype prev = MapInfo_Type_FromString(cvar_string("gamecfg"), false, false);
1485 FOREACH(Gametypes, cvar(it.netname) && it != prev, return it);
1486 return prev ? prev : MAPINFO_TYPE_DEATHMATCH;
1487}
1488
1489float _MapInfo_CheckMap(string s, bool gametype_only) // returns 0 if the map can't be played with the current settings, 1 otherwise
1490{
1491 if(!MapInfo_Get_ByName(s, 1, NULL))
1492 return 0;
1494 return 0;
1495 if (gametype_only)
1496 return 1;
1498 return 0;
1499 return 1;
1500}
1501
1502float MapInfo_CheckMap(string s) // returns 0 if the map can't be played with the current settings, 1 otherwise
1503{
1504 float r = _MapInfo_CheckMap(s, false);
1506 return r;
1507}
1508
1510{
1511 FOREACH(Gametypes, true, cvar_set(it.netname, (it == t) ? "1" : "0"));
1512}
1513
1514void MapInfo_LoadMap(string s, float reinit)
1515{
1517 // we shouldn't need this, as LoadMapSettings already fixes the gametype
1518 //if(!MapInfo_CheckMap(s))
1519 //{
1520 // print("EMERGENCY: can't play the selected map in the given gametype. Falling back to DM.\n");
1521 // MapInfo_SwitchGameType(MAPINFO_TYPE_DEATHMATCH.gametype_flags);
1522 //}
1523
1524 LOG_INFO("Switching to map ", s);
1525
1527 if(reinit)
1528 localcmd(strcat("\nmap ", s, "\n"));
1529 else
1530 localcmd(strcat("\nchangelevel ", s, "\n"));
1531}
1532
1533string MapInfo_ListAllowedMaps(Gametype type, float pRequiredFlags, float pForbiddenFlags)
1534{
1535 string out;
1536
1537 // to make absolutely sure:
1539 MapInfo_FilterGametype(type, MapInfo_CurrentFeatures(), pRequiredFlags, pForbiddenFlags, 0);
1540
1541 out = "";
1542 for(float i = 0; i < MapInfo_count; ++i)
1544 return substring(out, 1, strlen(out) - 1);
1545}
1546
1547string MapInfo_ListAllAllowedMaps(float pRequiredFlags, float pForbiddenFlags)
1548{
1549 string out;
1550
1551 // to make absolutely sure:
1553 _MapInfo_FilterGametype(MAPINFO_TYPE_ALL, 0, pRequiredFlags, pForbiddenFlags, 0);
1554
1555 out = "";
1556 for(float i = 0; i < MapInfo_count; ++i)
1558
1559 MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), pRequiredFlags, pForbiddenFlags, 0);
1560
1561 return substring(out, 1, strlen(out) - 1);
1562}
1563
1565{
1567 cvar_set("gamecfg", t.mdl);
1569}
1570
1571void MapInfo_LoadMapSettings(string s) // to be called from worldspawn
1572{
1575
1576 if(!_MapInfo_CheckMap(s, true)) // with underscore, it keeps temps
1577 {
1578 if(cvar("g_mapinfo_allow_unsupported_modes_and_let_stuff_break"))
1579 {
1580 LOG_SEVERE("can't play the selected map in the given gametype. Working with only the override settings.");
1582 return; // do not call Get_ByName!
1583 }
1584
1585 if(MapInfo_Map_supportedGametypes == '0 0 0')
1586 {
1588 FOREACH(Gametypes, it.m_priority == 2,
1589 {
1590 MapInfo_Map_supportedGametypes |= it.gametype_flags;
1591 RandomSelection_AddEnt(it, 1, 1);
1592 });
1595 LOG_SEVEREF("Mapinfo system is not functional at all. Falling back to a preferred mode (%s).", t.mdl);
1598 return; // do not call Get_ByName!
1599 }
1600
1601#if 0
1602 // find the lowest bit in the supported gametypes
1603 // unnecessary now that we select one at random
1604 int _t = 1;
1605 while(!(MapInfo_Map_supportedGametypes & 1))
1606 {
1607 _t <<= 1;
1609 }
1610#endif
1612 Gametype t_prev = t;
1613 FOREACH(Gametypes, MapInfo_Map_supportedGametypes & it.gametype_flags,
1614 {
1615 RandomSelection_AddEnt(it, 1, it.m_priority);
1616 });
1619
1620 // t is now a supported mode!
1621 LOG_WARNF("can't play the selected map in the given gametype (%s). Falling back to a supported mode (%s).", t_prev.mdl, t.mdl);
1623 }
1624 if(!_MapInfo_CheckMap(s, false)) { // with underscore, it keeps temps
1625 LOG_WARNF("the selected map lacks features required by current settings; playing anyway.");
1626 }
1627 MapInfo_Get_ByName(s, 1, t);
1628}
1629
1641
1654
1656{
1657 int f = MAPINFO_FLAG_FORBIDDEN;
1658
1659#ifdef GAMEQC
1660 if (!cvar("g_maplist_allow_hidden"))
1661#endif
1663
1664 if (!cvar("g_maplist_allow_frustrating"))
1666
1667 return f;
1668}
1669
1671{
1672 int f = 0;
1673
1674 if(cvar("g_maplist_allow_frustrating") > 1)
1676
1677 return f;
1678}
#define BIT(n)
Only ever assign into the first 24 bits in QC (so max is BIT(23)).
Definition bits.qh:8
var entity(vector mins, vector maxs,.entity tofield) findbox_tofield_OrFallback
string m_legacydefaults
DO NOT USE, this is compatibility for legacy maps!
Definition mapinfo.qh:68
bool team
does this gametype support teamplay?
Definition mapinfo.qh:46
bool frags
does this gametype use a point limit?
Definition mapinfo.qh:48
float matchacl(string acl, string str)
Definition util.qc:1437
int cvar_settemp_restore()
Definition util.qc:845
float cvar_settemp(string tmp_cvar, string tmp_value)
Definition util.qc:807
string mi_shortname
Definition util.qh:126
string strtolower(string s)
#define LABEL(id)
Definition compiler.qh:34
const float FILE_READ
const float FILE_WRITE
#define true
Definition csprogsdefs.qh:5
ERASEABLE bool cvar_value_issafe(string s)
Definition cvar.qh:11
#define strstrofs
#define strlen
#define tokenize_console
#define buf_create
#define strcasecmp
#define pass(name, colormin, colormax)
prev
Definition all.qh:73
#define stoi(s)
Definition int.qh:4
#define FOREACH_WORD(words, cond, body)
Definition iter.qh:33
#define FOREACH(list, cond, body)
Definition iter.qh:19
#define LOG_WARNF(...)
Definition log.qh:62
#define LOG_TRACEF(...)
Definition log.qh:77
#define LOG_INFO(...)
Definition log.qh:65
#define LOG_TRACE(...)
Definition log.qh:76
#define LOG_SEVEREF(...)
Definition log.qh:58
#define LOG_DEBUG(...)
Definition log.qh:80
#define LOG_SEVERE(...)
Definition log.qh:57
#define LOG_WARN(...)
Definition log.qh:61
ERASEABLE void db_close(int db)
Definition map.qh:84
ERASEABLE int db_create()
Definition map.qh:25
ERASEABLE string db_get(int db, string key)
Definition map.qh:91
ERASEABLE void db_put(int db, string key, string value)
Definition map.qh:101
float _MapInfo_globopen
Definition mapinfo.qc:122
void _MapInfo_Parse_Settemp(string pFilename, string acl, float type, string s, float recurse)
Definition mapinfo.qc:666
bool MapReadSizes(string map)
Definition mapinfo.qc:1401
float MapInfo_CheckMap(string s)
Definition mapinfo.qc:1502
void _MapInfo_Map_ApplyGametypeEx(string s, Gametype pWantedType, Gametype pThisType)
Definition mapinfo.qc:543
float _MapInfo_globcount
Definition mapinfo.qc:123
int _MapInfo_Cache_DB_NameToIndex
Definition mapinfo.qc:40
void MapInfo_LoadMapSettings_SaveGameType(Gametype t)
Definition mapinfo.qc:1564
float MapInfo_FilterGametype(Gametype pGametype, int pFeatures, int pFlagsRequired, int pFlagsForbidden, bool pAbortOnGenerate)
Definition mapinfo.qc:176
string _MapInfo_CheckArenaFile(string pFilename, string pMapname)
Definition mapinfo.qc:972
bool MapInfo_isRedundant(string fn, string t)
Definition mapinfo.qc:798
float MapInfo_FindName(string s)
Definition mapinfo.qc:1418
bool MapInfo_Get_ByID(int i)
Definition mapinfo.qc:274
void MapInfo_Shutdown()
Definition mapinfo.qc:1642
int MapInfo_RequiredFlags()
Definition mapinfo.qc:1670
string MapInfo_Type_ToString(Gametype t)
Definition mapinfo.qc:655
string MapInfo_BSPName_ByID(float i)
Definition mapinfo.qc:249
void _MapInfo_FilterList_swap(float i, float j, entity pass)
Definition mapinfo.qc:161
bool autocvar_g_mapinfo_ignore_warnings
Definition mapinfo.qc:14
float _MapInfo_FilterGametype(vector pGametype, int pFeatures, int pFlagsRequired, int pFlagsForbidden, bool pAbortOnGenerate)
Definition mapinfo.qc:180
string MapInfo_title_sans_author(string title)
Removes author string from title (if found) and copies it to MapInfo_Map_author if that wasn't set.
Definition mapinfo.qc:772
string _MapInfo_GlobItem(float i)
Definition mapinfo.qc:125
void MapInfo_FilterString(string sf)
Definition mapinfo.qc:212
void MapInfo_Filter_Free()
Definition mapinfo.qc:239
void MapInfo_LoadMap(string s, float reinit)
Definition mapinfo.qc:1514
float _MapInfo_globhandle
Definition mapinfo.qc:124
vector _GametypeFlags_FromGametype(int a)
Definition mapinfo.qc:20
string MapInfo_Type_Description(Gametype t)
Definition mapinfo.qc:650
float _MapInfo_Generate(string pFilename)
Definition mapinfo.qc:281
string MapInfo_ListAllAllowedMaps(float pRequiredFlags, float pForbiddenFlags)
Definition mapinfo.qc:1547
int autocvar_g_mapinfo_q3compat
Definition mapinfo.qc:11
Gametype MapInfo_CurrentGametype()
Definition mapinfo.qc:1482
void MapInfo_Cache_Destroy()
Definition mapinfo.qc:43
string _MapInfo_GetDefault(Gametype t)
Definition mapinfo.qc:458
void MapInfo_Cache_Store()
Definition mapinfo.qc:69
void MapInfo_Cache_Create()
Definition mapinfo.qc:53
string _MapInfo_GetDefaultEx(Gametype t)
Definition mapinfo.qc:533
string unquote(string s)
Definition mapinfo.qc:254
string _MapInfo_FindArenaFile(string pFilename, string extension)
Definition mapinfo.qc:1002
#define WARN_COND
Definition mapinfo.qc:15
float _MapInfo_FilterList_cmp(float i, float j, entity pass)
Definition mapinfo.qc:168
string _MapInfo_Map_worldspawn_music
Definition mapinfo.qc:279
int MapInfo_Get_ByName(string pFilename, float pAllowGenerate, Gametype pGametypeToSet)
Definition mapinfo.qc:1382
int MapInfo_ForbiddenFlags()
Definition mapinfo.qc:1655
int MapInfo_CurrentFeatures()
Definition mapinfo.qc:1472
int _MapInfo_Cache_Buf_IndexToMapData
Definition mapinfo.qc:41
float MapInfo_Cache_Retrieve(string map)
Definition mapinfo.qc:96
string MapInfo_Type_ToText(Gametype t)
Definition mapinfo.qc:660
string MapInfo_ListAllowedMaps(Gametype type, float pRequiredFlags, float pForbiddenFlags)
Definition mapinfo.qc:1533
int _MapInfo_Cache_Active
Definition mapinfo.qc:39
float _MapInfo_CheckMap(string s, bool gametype_only)
Definition mapinfo.qc:1489
void _MapInfo_Map_ApplyGametype(string s, Gametype pWantedType, Gametype pThisType, int load_default)
Definition mapinfo.qc:463
void MapInfo_SwitchGameType(Gametype t)
Definition mapinfo.qc:1509
void MapInfo_LoadMapSettings(string s)
Definition mapinfo.qc:1571
void MapInfo_ClearTemps()
Definition mapinfo.qc:1630
float MapInfo_FilterList_Lookup(float i)
Definition mapinfo.qc:156
float _MapInfo_GetTeamPlayBool(Gametype t)
Definition mapinfo.qc:538
Gametype MapInfo_Type_FromString(string gtype, bool dowarn, bool is_q3compat)
Definition mapinfo.qc:620
void MapInfo_Cache_Invalidate()
Definition mapinfo.qc:61
void MapInfo_Enumerate()
Definition mapinfo.qc:134
float _MapInfo_filtered_allocated
Definition mapinfo.qc:155
float MapInfo_Get_ByName_NoFallbacks(string pFilename, int pAllowGenerate, Gametype pGametypeToSet)
Definition mapinfo.qc:1035
float _MapInfo_filtered
Definition mapinfo.qc:154
void _MapInfo_Map_Reset()
Definition mapinfo.qc:443
bool _MapInfo_ParseArena(string arena_filename, int fh, string pFilename, Gametype pGametypeToSet, bool isdefi, bool isgenerator)
Definition mapinfo.qc:820
string MapInfo_FixName(string s)
Definition mapinfo.qc:1466
const int MAPINFO_FEATURE_MONSTERS
Definition mapinfo.qh:159
vector MapInfo_Map_supportedGametypes
Definition mapinfo.qh:13
vector MapInfo_Map_maxs
Definition mapinfo.qh:17
vector MAPINFO_TYPE_ALL
Definition mapinfo.qh:27
#define MAPINFO_SETTEMP_ACL_USER
Definition mapinfo.qh:236
vector MapInfo_Map_mins
Definition mapinfo.qh:16
int map_minplayers
Definition mapinfo.qh:190
string MapInfo_FindName_match
Definition mapinfo.qh:195
float MapInfo_FindName_firstResult
Definition mapinfo.qh:196
Gametype MapInfo_LoadedGametype
Definition mapinfo.qh:220
int map_maxplayers
Definition mapinfo.qh:191
float MapInfo_count
Definition mapinfo.qh:166
const int MAPINFO_FLAG_FORBIDDEN
Definition mapinfo.qh:162
string MapInfo_Map_bspname
Definition mapinfo.qh:6
string MapInfo_Map_fog
Definition mapinfo.qh:12
int MapInfo_Map_supportedFeatures
Definition mapinfo.qh:14
float MapInfo_progress
Definition mapinfo.qh:173
string MapInfo_Map_title
Definition mapinfo.qh:7
string MapInfo_Map_author
Definition mapinfo.qh:10
const int MAPINFO_FEATURE_WEAPONS
Definition mapinfo.qh:156
const int MAPINFO_FLAG_HIDDEN
Definition mapinfo.qh:161
int MapInfo_Map_flags
Definition mapinfo.qh:15
const int MAPINFO_FEATURE_VEHICLES
Definition mapinfo.qh:157
#define MAPINFO_SETTEMP_ACL_SYSTEM
Definition mapinfo.qh:237
string MapInfo_Map_titlestring
Definition mapinfo.qh:8
const int MAPINFO_FEATURE_TURRETS
Definition mapinfo.qh:158
string MapInfo_Map_clientstuff
Definition mapinfo.qh:11
string MapInfo_Map_description
Definition mapinfo.qh:9
const int MAPINFO_FLAG_FRUSTRATING
Definition mapinfo.qh:163
vector gametype_flags
Definition mapinfo.qh:28
const int MAPINFO_FLAG_DONOTWANT
Definition mapinfo.qh:164
void localcmd(string command,...)
void cvar_set(string name, string value)
string fgets(float fhandle)
void fclose(float fhandle)
float stof(string val,...)
void fputs(float fhandle, string s)
string substring(string s, float start, float length)
float cvar(string name)
float fopen(string filename, float mode)
string search_getfilename(float handle, float num)
vector stov(string s)
float random(void)
const string cvar_string(string name)
float search_getsize(float handle)
float vlen(vector v)
float search_begin(string pattern, float caseinsensitive, float quiet)
string vtos(vector v)
float min(float f,...)
float checkextension(string ext)
string ftos(float f)
float floor(float f)
const string cvar_defstring(string name)
string argv(float n)
void search_end(float handle)
float max(float f,...)
string string_null
Definition nil.qh:9
strcat(_("^F4Countdown stopped!"), "\n^BG", _("Teams are too unbalanced."))
#define NULL
Definition post.qh:14
#define world
Definition post.qh:15
#define error
Definition pre.qh:6
ERASEABLE void RandomSelection_Init()
Definition random.qc:4
entity RandomSelection_chosen_ent
Definition random.qh:5
#define REGISTRY_MAX(id)
Definition registry.qh:17
vector
Definition self.qh:92
string GetField_fullspawndata(entity e, string fieldname, bool vfspath)
Retrieves the value of a map entity field from fullspawndata.
Definition main.qc:451
ERASEABLE void heapsort(int n, swapfunc_t swap, comparefunc_t cmp, entity pass)
Definition sort.qh:9
ERASEABLE string car(string s)
returns first word
Definition string.qh:259
ERASEABLE string cdr(string s)
returns all but first word
Definition string.qh:268
#define startsWith(haystack, needle)
Definition string.qh:236
ERASEABLE bool startsWithNocase(string haystack, string needle)
Definition string.qh:239
ERASEABLE string cons(string a, string b)
Definition string.qh:276