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 if(isdefi) // some maps omit the required "style" key and don't appear in defrag's menu
872 // but can still be played in defrag's default gametype with eg `map kool_simple5`.
873 stored_supportedGametypes |= MAPINFO_TYPE_CTS.gametype_flags;
874 // might have .arena AND .defi for the same map so these bitfields are OR'd
875 if(isgenerator)
876 MapInfo_Map_supportedGametypes |= stored_supportedGametypes;
877 else
878 {
879 FOREACH(Gametypes, it.gametype_flags & stored_supportedGametypes,
880 {
881 _MapInfo_Map_ApplyGametype ("", pGametypeToSet, it, true);
882 });
883 }
884 MapInfo_Map_supportedFeatures |= stored_supportedFeatures;
885 MapInfo_Map_flags |= stored_flags;
886 return true; // no need to continue through the file, we have our map!
887 }
888 else
889 {
890 // discard any gathered locals, we're not using the correct map!
891 stored_Map_description = "";
892 stored_Map_title = "";
893 stored_Map_author = "";
894 stored_supportedGametypes = '0 0 0';
895 stored_supportedFeatures = 0;
896 stored_flags = 0;
897 continue;
898 }
899 }
900
901 s = strreplace("\t", " ", s);
902
903 float p = strstrofs(s, "//", 0);
904 if(p >= 0)
905 s = substring(s, 0, p);
906
907 // perform an initial trim to ensure the first argument is properly obtained
908 // remove leading spaces
909 while(substring(s, 0, 1) == " ")
910 s = substring(s, 1, -1);
911
912 t = car(s); s = cdr(s);
913 t = strtolower(t); // apparently some q3 maps use capitalized parameters
914
915 // remove trailing spaces
916 while(substring(t, -1, 1) == " ")
917 t = substring(t, 0, -2);
918
919 // remove trailing spaces
920 while(substring(s, -1, 1) == " ")
921 s = substring(s, 0, -2);
922 // remove leading spaces
923 while(substring(s, 0, 1) == " ")
924 s = substring(s, 1, -1);
925 // limited support of ""
926 // remove trailing and leading " of s
927 if(substring(s, 0, 1) == "\"")
928 {
929 if(substring(s, -1, 1) == "\"")
930 s = substring(s, 1, -2);
931 }
932 if(t == "longname")
933 stored_Map_title = s;
934 else if(t == "author")
935 stored_Map_author = s;
936 else if(t == "type")
937 {
938 // if there is a valid gametype in this .arena file, include it in the menu
939 stored_supportedFeatures |= MAPINFO_FEATURE_WEAPONS;
940 // type in quake 3 holds all the supported gametypes, so we must loop through all of them
941 // TODO: handle support here better to include more Xonotic teamplay modes
942 string types = s;
943 types = strreplace("team", "tdm ft", types);
944 types = strreplace("ffa", "dm lms ka", types);
945 types = strreplace("tourney", "duel", types); // QL used duel so the following check must support it
946 if(strstrofs(types, "duel", 0) < 0 && strstrofs(types, "tdm", 0) >= 0) // larger team map, support additional gametypes!
947 types = cons(types, "ca kh");
948 FOREACH_WORD(types, true,
949 {
950 Gametype f = MapInfo_Type_FromString(it, false, true);
951 if(f)
952 stored_supportedGametypes |= f.gametype_flags;
953 });
954 }
955 else if(t == "style" && isdefi)
956 {
957 // we have a defrag map on our hands, add CTS!
958 // TODO: styles, see https://ws.q3df.org/level_design/defi/?dark=1
959 stored_supportedGametypes |= MAPINFO_TYPE_CTS.gametype_flags;
960 }
961 else if(t == "map")
962 {
963 if(strtolower(s) == strtolower(pFilename))
964 dosave = true; // yay, found our map!
965 }
966 else if(t == "quote")
967 stored_Map_description = s;
968 // TODO: fraglimit
969 }
970
971 // if the map wasn't found in the .arena, fall back to generated .mapinfo
972 return false;
973}
974
975string _MapInfo_CheckArenaFile(string pFilename, string pMapname)
976{
977 // returns the file name if valid, otherwise returns ""
978 // a string is returned to optimise the use cases where a filename is also returned
979 int fh = fopen(pFilename, FILE_READ);
980 if(fh < 0)
981 return "";
982 for(string s; (s = fgets(fh)); )
983 {
984 s = strreplace("\t", "", s);
985 while(substring(s, 0, 1) == " ")
986 s = substring(s, 1, -1);
987 if(substring(s, 0, 2) == "//")
988 continue;
989 if(s == "")
990 continue;
991 int offset = strstrofs(s, "map", 0);
992 if(offset >= 0)
993 {
994 if(strstrofs(strtolower(s), strcat("\"", strtolower(pMapname), "\""), offset) >= 0) // quake 3 is case insensitive
995 {
996 fclose(fh);
997 return pFilename; // FOUND IT!
998 }
999 }
1000 }
1001 fclose(fh);
1002 return ""; // file did not contain a "map" field matching our map name
1003}
1004
1005string _MapInfo_FindArenaFile(string pFilename, string extension)
1006{
1007 // A valid arena file with the "default" name may have been created as a map-specific
1008 // override of an arena file with a different name (possibly multi-map) in the pk3
1009 // (occasionally in the wild such file exists in the pk3 but isn't valid for a map in the pk3).
1010 // Therefore we try the "default" name first.
1011 string afile = _MapInfo_CheckArenaFile(strcat("scripts/", pFilename, extension), pFilename);
1012 if (afile != ""
1013 || !checkextension("DP_QC_FS_SEARCH_PACKFILE"))
1014 return afile;
1015
1016 string base_pack = whichpack(strcat("maps/", pFilename, ".bsp"));
1017 if(base_pack == "") // this map isn't packaged!
1018 return "";
1019 int glob = search_packfile_begin(strcat("scripts/*", extension), true, true, base_pack);
1020 if(glob < 0) // there's no matching file in the pack
1021 return "";
1022 int n = search_getsize(glob);
1023 for(int j = 0; j < n; ++j)
1024 {
1025 afile = search_getfilename(glob, j);
1026 if(_MapInfo_CheckArenaFile(afile, pFilename) != "")
1027 {
1028 search_end(glob);
1029 return afile;
1030 }
1031 }
1032 search_end(glob);
1033
1034 return ""; // if we get here, a valid .arena file could not be found
1035}
1036
1037// load info about a map by name into the MapInfo_Map_* globals
1038float MapInfo_Get_ByName_NoFallbacks(string pFilename, int pAllowGenerate, Gametype pGametypeToSet)
1039{
1040 string fn;
1041 string s, t;
1042 float fh;
1043 int f, i;
1044 float r, n;
1045 string acl = MAPINFO_SETTEMP_ACL_USER;
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(p > 0 && (t == "" || t == "\t")) // indented comment line
1182 continue;
1183 if(t == "title")
1185 else if(t == "description")
1187 else if(t == "author")
1189 else if(t == "has")
1190 {
1191 t = car(s); // s = cdr(s);
1193 else if(t == "turrets") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_TURRETS;
1194 else if(t == "vehicles") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_VEHICLES;
1195 else if(t == "monsters") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_MONSTERS;
1196 else if(t == "new_toys") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_WEAPONS;
1197 else if(WARN_COND)
1198 LOG_WARN("Map ", pFilename, " supports unknown feature ", t, ", ignored");
1199 }
1200 else if(t == "hidden")
1201 {
1203 }
1204 else if(t == "forbidden")
1205 {
1207 }
1208 else if(t == "frustrating")
1209 {
1211 }
1212 else if(t == "donotwant" || t == "noautomaplist")
1213 {
1215 }
1216 else if(t == "gameversion_min")
1217 {
1218 if (cvar("gameversion") < stof(s))
1220 }
1221 else if(t == "type")
1222 {
1223 t = car(s); s = cdr(s);
1224 Gametype f = MapInfo_Type_FromString(t, true, false);
1225 //if(WARN_COND)
1226 //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'.");
1227 if(f)
1228 _MapInfo_Map_ApplyGametype (s, pGametypeToSet, f, true);
1229 else if(WARN_COND)
1230 LOG_DEBUG("Map ", pFilename, " supports unknown gametype ", t, ", ignored");
1231 }
1232 else if(t == "gametype")
1233 {
1234 t = car(s); s = cdr(s);
1235 Gametype f = MapInfo_Type_FromString(t, true, false);
1236 if(f)
1237 _MapInfo_Map_ApplyGametypeEx (s, pGametypeToSet, f);
1238 else if(WARN_COND)
1239 LOG_DEBUG("Map ", pFilename, " supports unknown gametype ", t, ", ignored");
1240 }
1241 else if(t == "size")
1242 {
1243 float a, b, c, d, e;
1244 t = car(s); s = cdr(s); a = stof(t);
1245 t = car(s); s = cdr(s); b = stof(t);
1246 t = car(s); s = cdr(s); c = stof(t);
1247 t = car(s); s = cdr(s); d = stof(t);
1248 t = car(s); s = cdr(s); e = stof(t);
1249 if(s == "")
1250 {
1251 if(WARN_COND)
1252 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");
1253 }
1254 else
1255 {
1256 t = car(s); s = cdr(s); f = stof(t);
1257 if(s != "")
1258 {
1259 if(WARN_COND)
1260 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");
1261 }
1262 else
1263 {
1264 if(a >= d || b >= e || c >= f)
1265 {
1266 if(WARN_COND)
1267 LOG_WARN("Map ", pFilename, " contains an incorrect size line, mins have to be < maxs");
1268 }
1269 else
1270 {
1271 MapInfo_Map_mins.x = a;
1272 MapInfo_Map_mins.y = b;
1273 MapInfo_Map_mins.z = c;
1274 MapInfo_Map_maxs.x = d;
1275 MapInfo_Map_maxs.y = e;
1276 MapInfo_Map_maxs.z = f;
1277 }
1278 }
1279 }
1280 }
1281 else if(t == "settemp_for_type")
1282 {
1283 t = car(s); s = cdr(s);
1284 bool all = t == "all";
1285 Gametype f = NULL;
1286 if(all || (f = MapInfo_Type_FromString(t, true, false)))
1287 {
1288 if((all ? MAPINFO_TYPE_ALL : f.gametype_flags) & pGametypeToSet.gametype_flags)
1289 {
1290 _MapInfo_Parse_Settemp(pFilename, acl, 0, s, 1);
1291 }
1292 }
1293 else
1294 {
1295 LOG_DEBUG("Map ", pFilename, " has a setting for unknown gametype ", t, ", ignored");
1296 }
1297 }
1298 else if(t == "clientsettemp_for_type")
1299 {
1300 t = car(s); s = cdr(s);
1301 bool all = t == "all";
1302 Gametype f = NULL;
1303 if(all || (f = MapInfo_Type_FromString(t, true, false)))
1304 {
1305 if((all ? MAPINFO_TYPE_ALL : f.gametype_flags) & pGametypeToSet.gametype_flags)
1306 {
1307 _MapInfo_Parse_Settemp(pFilename, acl, 1, s, 1);
1308 }
1309 }
1310 else
1311 {
1312 LOG_DEBUG("Map ", pFilename, " has a client setting for unknown gametype ", t, ", ignored");
1313 }
1314 }
1315 else if(t == "fog")
1316 {
1317 if (!cvar_value_issafe(s))
1318 {
1319 if(WARN_COND)
1320 LOG_WARN("Map ", pFilename, " contains a potentially harmful fog setting, ignored");
1321 }
1322 else
1323 MapInfo_Map_fog = s;
1324 }
1325 else if(t == "cdtrack")
1326 {
1327 t = car(s); s = cdr(s);
1328 // We do this only if pGametypeToSet even though this
1329 // content is theoretically gametype independent,
1330 // because MapInfo_Map_clientstuff contains otherwise
1331 // gametype dependent stuff. That way this value stays
1332 // empty when not setting a gametype to not set any
1333 // false expectations.
1334 if(pGametypeToSet)
1335 {
1336 if (!cvar_value_issafe(t))
1337 {
1338 if(WARN_COND)
1339 LOG_WARN("Map ", pFilename, " contains a potentially harmful cdtrack, ignored");
1340 }
1341 else
1343 MapInfo_Map_clientstuff, "cd loop \"", t, "\"\n"
1344 );
1345 }
1346 }
1347 else if(WARN_COND)
1348 LOG_WARN("Map ", pFilename, " provides unknown info item \"", t, "\", ignored");
1349 }
1350 fclose(fh);
1351
1352LABEL(mapinfo_handled)
1353#ifdef SVQC
1354 // if the map is currently loaded we can read worldspawn fields directly
1355 if (pFilename == mi_shortname)
1356 {
1357 if (MapInfo_Map_title == "<TITLE>")
1358 if (world.message != "")
1359 MapInfo_Map_title = world.message;
1360 if (MapInfo_Map_author == "<AUTHOR>")
1361 if ((s = GetField_fullspawndata(world, "author", false)) != "")
1363 }
1364#endif
1365 // Could skip removing author from title when it's source is .mapinfo
1366 // but must always do it for world.message and .arena/.defi as VQ3 didn't support author
1367 // so mappers tended to put it in world.message and/or longname.
1368 MapInfo_Map_title = MapInfo_title_sans_author(MapInfo_Map_title); // may set author if not set
1369
1370 if(MapInfo_Map_title == "<TITLE>")
1374 else
1376
1377 if (MapInfo_Map_author == "<AUTHOR>")
1378 MapInfo_Map_author = ""; // don't display "<AUTHOR>" in the UI (we do write it to .mapinfo files)
1379
1381 if(MapInfo_Map_supportedGametypes != '0 0 0')
1382 return r;
1383 if (WARN_COND)
1384 LOG_WARN("Map ", pFilename, " supports no gametypes, ignored");
1385 return 0;
1386}
1387int MapInfo_Get_ByName(string pFilename, float pAllowGenerate, Gametype pGametypeToSet)
1388{
1389 int r = MapInfo_Get_ByName_NoFallbacks(pFilename, pAllowGenerate, pGametypeToSet);
1390
1391 FOREACH(Gametypes, it.m_isForcedSupported(it), _MapInfo_Map_ApplyGametypeEx("", pGametypeToSet, it));
1392
1393 if(pGametypeToSet)
1394 {
1395 if(!(MapInfo_Map_supportedGametypes & pGametypeToSet.gametype_flags))
1396 {
1397 error("Can't select the requested gametype. This should never happen as the caller should prevent it!\n");
1398 //_MapInfo_Map_ApplyGametypeEx("", pGametypeToSet, MAPINFO_TYPE_DEATHMATCH);
1399 //return;
1400 }
1401 }
1402
1403 return r;
1404}
1405
1406bool MapReadSizes(string map)
1407{
1408 // TODO: implement xonotic#28 / xonvote 172 (sizes in mapinfo)
1409 string readsize_msg = strcat("MapReadSizes ", map);
1410 float fh = fopen(strcat("maps/", map, ".sizes"), FILE_READ);
1411 if(fh >= 0)
1412 {
1413 map_minplayers = stoi(fgets(fh));
1414 map_maxplayers = stoi(fgets(fh));
1415 fclose(fh);
1416 LOG_TRACEF(readsize_msg, ": ok, min %d max %d", map_minplayers, map_maxplayers);
1417 return true;
1418 }
1419 LOG_TRACE(readsize_msg, ": not found");
1420 return false;
1421}
1422
1423float MapInfo_FindName(string s)
1424{
1425 // if there is exactly one map of prefix s, return it
1426 // if not, return the null string
1427 // note that DP sorts glob results... so I can use a binary search
1428 float l, r, m, cmp;
1429 l = 0;
1430 r = MapInfo_count;
1431 // invariants: r is behind s, l-1 is equal or before
1432 while(l != r)
1433 {
1434 m = floor((l + r) / 2);
1437 if(cmp == 0)
1438 return m; // found and good
1439 if(cmp < 0)
1440 l = m + 1; // l-1 is before s
1441 else
1442 r = m; // behind s
1443 }
1446 // r == l, so: l is behind s, l-1 is before
1447 // SO: if there is any, l is the one with the right prefix
1448 // and l+1 may be one too
1449 if(l == MapInfo_count)
1450 {
1453 return -1; // no MapInfo_FindName_match, behind last item
1454 }
1456 {
1459 return -1; // wrong prefix
1460 }
1461 if(l == MapInfo_count - 1)
1462 return l; // last one, nothing can follow => unique
1464 {
1466 return -1; // ambigous MapInfo_FindName_match
1467 }
1468 return l;
1469}
1470
1471string MapInfo_FixName(string s)
1472{
1475}
1476
1478{
1479 int req = 0;
1480 // TODO: find a better way to check if weapons are required on the map
1481 if(!(cvar("g_instagib") || cvar("g_overkill") || cvar("g_nix") || cvar("g_weaponarena") || !cvar("g_pickup_items") || !cvar("g_melee_only")
1482 || cvar("g_race") || cvar("g_cts") || cvar("g_nexball") || cvar("g_ca") || cvar("g_freezetag") || cvar("g_lms")))
1484 return req;
1485}
1486
1488{
1489 Gametype prev = MapInfo_Type_FromString(cvar_string("gamecfg"), false, false);
1490 FOREACH(Gametypes, cvar(it.netname) && it != prev, return it);
1491 return prev ? prev : MAPINFO_TYPE_DEATHMATCH;
1492}
1493
1494float _MapInfo_CheckMap(string s, bool gametype_only) // returns 0 if the map can't be played with the current settings, 1 otherwise
1495{
1496 if(!MapInfo_Get_ByName(s, 1, NULL))
1497 return 0;
1499 return 0;
1500 if (gametype_only)
1501 return 1;
1503 return 0;
1504 return 1;
1505}
1506
1507float MapInfo_CheckMap(string s) // returns 0 if the map can't be played with the current settings, 1 otherwise
1508{
1509 float r = _MapInfo_CheckMap(s, false);
1511 return r;
1512}
1513
1515{
1516 FOREACH(Gametypes, true, cvar_set(it.netname, (it == t) ? "1" : "0"));
1517}
1518
1519void MapInfo_LoadMap(string s, float reinit)
1520{
1522 // we shouldn't need this, as LoadMapSettings already fixes the gametype
1523 //if(!MapInfo_CheckMap(s))
1524 //{
1525 // print("EMERGENCY: can't play the selected map in the given gametype. Falling back to DM.\n");
1526 // MapInfo_SwitchGameType(MAPINFO_TYPE_DEATHMATCH.gametype_flags);
1527 //}
1528
1529 LOG_INFO("Switching to map ", s);
1530
1532 if (reinit)
1533 localcmd(strcat("\nmap ", s, "\n"));
1534 else
1535 localcmd(strcat("\nchangelevel ", s, "\n"));
1536}
1537
1538string MapInfo_ListAllowedMaps(Gametype type, float pRequiredFlags, float pForbiddenFlags)
1539{
1540 string out;
1541
1542 // to make absolutely sure:
1544 MapInfo_FilterGametype(type, MapInfo_CurrentFeatures(), pRequiredFlags, pForbiddenFlags, 0);
1545
1546 out = "";
1547 for(float i = 0; i < MapInfo_count; ++i)
1549 return substring(out, 1, strlen(out) - 1);
1550}
1551
1552string MapInfo_ListAllAllowedMaps(float pRequiredFlags, float pForbiddenFlags)
1553{
1554 string out;
1555
1556 // to make absolutely sure:
1558 _MapInfo_FilterGametype(MAPINFO_TYPE_ALL, 0, pRequiredFlags, pForbiddenFlags, 0);
1559
1560 out = "";
1561 for(float i = 0; i < MapInfo_count; ++i)
1563
1564 MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), pRequiredFlags, pForbiddenFlags, 0);
1565
1566 return substring(out, 1, strlen(out) - 1);
1567}
1568
1570{
1572 cvar_set("gamecfg", t.mdl);
1574}
1575
1576void MapInfo_LoadMapSettings(string s) // to be called from worldspawn
1577{
1580
1581 if(!_MapInfo_CheckMap(s, true)) // with underscore, it keeps temps
1582 {
1583 if(cvar("g_mapinfo_allow_unsupported_modes_and_let_stuff_break"))
1584 {
1585 LOG_SEVERE("can't play the selected map in the given gametype. Working with only the override settings.");
1587 return; // do not call Get_ByName!
1588 }
1589
1590 if(MapInfo_Map_supportedGametypes == '0 0 0')
1591 {
1593 FOREACH(Gametypes, it.m_priority == 2,
1594 {
1595 MapInfo_Map_supportedGametypes |= it.gametype_flags;
1596 RandomSelection_AddEnt(it, 1, 1);
1597 });
1600 LOG_SEVEREF("Mapinfo system is not functional at all. Falling back to a preferred mode (%s).", t.mdl);
1603 return; // do not call Get_ByName!
1604 }
1605
1606#if 0
1607 // find the lowest bit in the supported gametypes
1608 // unnecessary now that we select one at random
1609 int _t = 1;
1610 while(!(MapInfo_Map_supportedGametypes & 1))
1611 {
1612 _t <<= 1;
1614 }
1615#endif
1617 Gametype t_prev = t;
1618 FOREACH(Gametypes, MapInfo_Map_supportedGametypes & it.gametype_flags,
1619 {
1620 RandomSelection_AddEnt(it, 1, it.m_priority);
1621 });
1624
1625 // t is now a supported mode!
1626 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);
1628 }
1629 if(!_MapInfo_CheckMap(s, false)) { // with underscore, it keeps temps
1630 LOG_WARNF("the selected map lacks features required by current settings; playing anyway.");
1631 }
1632 MapInfo_Get_ByName(s, 1, t);
1633}
1634
1646
1659
1661{
1662 int f = MAPINFO_FLAG_FORBIDDEN;
1663
1664#ifdef GAMEQC
1665 if (!cvar("g_maplist_allow_hidden"))
1666#endif
1668
1669 if (!cvar("g_maplist_allow_frustrating"))
1671
1672 return f;
1673}
1674
1676{
1677 int f = 0;
1678
1679 if(cvar("g_maplist_allow_frustrating") > 1)
1681
1682 return f;
1683}
#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:1439
int cvar_settemp_restore()
Definition util.qc:847
float cvar_settemp(string tmp_cvar, string tmp_value)
Definition util.qc:809
string mi_shortname
Definition util.qh:133
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:53
#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:59
#define LOG_TRACEF(...)
Definition log.qh:75
#define LOG_INFO(...)
Definition log.qh:62
#define LOG_TRACE(...)
Definition log.qh:74
#define LOG_SEVEREF(...)
Definition log.qh:55
#define LOG_DEBUG(...)
Definition log.qh:78
#define LOG_SEVERE(...)
Definition log.qh:54
#define LOG_WARN(...)
Definition log.qh:58
ERASEABLE void db_close(int db)
Definition map.qh:86
ERASEABLE int db_create()
Definition map.qh:25
ERASEABLE string db_get(int db, string key)
Definition map.qh:93
ERASEABLE void db_put(int db, string key, string value)
Definition map.qh:103
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:1406
float MapInfo_CheckMap(string s)
Definition mapinfo.qc:1507
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:1569
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:975
bool MapInfo_isRedundant(string fn, string t)
Definition mapinfo.qc:798
float MapInfo_FindName(string s)
Definition mapinfo.qc:1423
bool MapInfo_Get_ByID(int i)
Definition mapinfo.qc:274
void MapInfo_Shutdown()
Definition mapinfo.qc:1647
int MapInfo_RequiredFlags()
Definition mapinfo.qc:1675
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:1519
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:1552
int autocvar_g_mapinfo_q3compat
Definition mapinfo.qc:11
Gametype MapInfo_CurrentGametype()
Definition mapinfo.qc:1487
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:1005
#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:1387
int MapInfo_ForbiddenFlags()
Definition mapinfo.qc:1660
int MapInfo_CurrentFeatures()
Definition mapinfo.qc:1477
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:1538
int _MapInfo_Cache_Active
Definition mapinfo.qc:39
float _MapInfo_CheckMap(string s, bool gametype_only)
Definition mapinfo.qc:1494
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:1514
void MapInfo_LoadMapSettings(string s)
Definition mapinfo.qc:1576
void MapInfo_ClearTemps()
Definition mapinfo.qc:1635
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:1038
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:1471
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:96
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:258
ERASEABLE string cdr(string s)
Returns all but first word.
Definition string.qh:268
#define startsWith(haystack, needle)
Definition string.qh:234
ERASEABLE bool startsWithNocase(string haystack, string needle)
Definition string.qh:237
ERASEABLE string cons(string a, string b)
Definition string.qh:277