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