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 #if 0
760 if(cvar("g_campaign"))
761 cvar_set(t, s); // this is a wrapper and is always temporary anyway; no need to backup old values then
762 else
763 #endif
764 cvar_settemp(t, s);
765 }
766 else
767 {
768 LOG_TRACE("Applying temporary client setting ", t, " := ", s);
770 MapInfo_Map_clientstuff, "cl_cmd settemp \"", t, "\" \"", s, "\"\n"
771 );
772 }
773 }
774}
775
778string MapInfo_title_sans_author(string title)
779{
780 int offset;
781
782 if ((offset = strstrofs(title, " by ", 0)) >= 0)
783 {
784 if (MapInfo_Map_author == "<AUTHOR>")
785 MapInfo_Map_author = substring(title, offset + 4, strlen(title) - (offset + 4));
786 title = substring(title, 0, offset);
787 }
788 else if ((offset = strstrofs(title, " (by ", 0)) >= 0 || (offset = strstrofs(title, " [by ", 0)) >= 0)
789 {
790 if (MapInfo_Map_author == "<AUTHOR>")
791 MapInfo_Map_author = substring(title, offset + 5, strlen(title) - (offset + 5) - 1);
792 title = substring(title, 0, offset);
793 }
794 else if ((offset = strstrofs(title, "Made By ", 0)) >= 0) // often at the start of the string
795 {
796 if (MapInfo_Map_author == "<AUTHOR>")
797 MapInfo_Map_author = substring(title, offset + 8, strlen(title) - (offset + 8));
798 title = substring(title, 0, offset);
799 }
800
801 return title != "" ? title : "<TITLE>";
802}
803
804bool MapInfo_isRedundant(string fn, string t)
805{
806 // normalize file name
807 fn = strreplace("_", "", fn);
808 fn = strreplace("-", "", fn);
809
810 // normalize visible title
811 t = strreplace(":", "", t);
812 t = strreplace(" ", "", t);
813 t = strreplace("_", "", t);
814 t = strreplace("-", "", t);
815 t = strreplace("'", "", t);
816 t = strdecolorize(t);
817
818 // we allow the visible title to have punctuation the file name does
819 // not, but not vice versa
820 if(!strcasecmp(fn, t))
821 return true;
822
823 return false;
824}
825
826bool _MapInfo_ParseArena(string arena_filename, int fh, string pFilename, Gametype pGametypeToSet, bool isdefi, bool isgenerator)
827{
828 // NOTE: .arena files can hold more than 1 map's information!
829 // to handle this, we're going to store gathered information in local variables and save it if we encounter the correct map name
830 bool in_brackets = false; // testing a potential mapinfo section (within brackets)
831 bool dosave = false;
832 string stored_Map_description = "";
833 string stored_Map_title = "";
834 string stored_Map_author = "";
835 vector stored_supportedGametypes = '0 0 0';
836 int stored_supportedFeatures = 0;
837 int stored_flags = 0;
838 string t, s;
839
840 if (isgenerator)
841 LOG_INFO("Generating ", pFilename, ".mapinfo: analyzing ", arena_filename);
842
843 for (;;)
844 {
845 if (!((s = fgets(fh))))
846 break;
847
848 // catch different sorts of comments
849 if(s == "") // empty lines
850 continue;
851 if(substring(s, 0, 2) == "//") // C++ style
852 continue;
853 if(strstrofs(s, "{", 0) >= 0)
854 {
855 if(in_brackets)
856 return false; // edge case? already in a bracketed section!
857 in_brackets = true;
858 continue;
859 }
860 else if(!in_brackets)
861 {
862 // if we're not inside a bracket, don't process map info
863 continue;
864 }
865 if(strstrofs(s, "}", 0) >= 0)
866 {
867 if(!in_brackets)
868 return false; // no starting bracket! let the mapinfo generation system handle it
869 in_brackets = false;
870 if(dosave)
871 {
872 MapInfo_Map_description = stored_Map_description;
873 if(stored_Map_title != "")
874 MapInfo_Map_title = stored_Map_title;
875 if(stored_Map_author != "") // write the usual "<AUTHOR>" if we have nothing better
876 MapInfo_Map_author = stored_Map_author;
877 // might have .arena AND .defi for the same map so these bitfields are OR'd
878 if(isgenerator)
879 MapInfo_Map_supportedGametypes |= stored_supportedGametypes;
880 else
881 {
882 FOREACH(Gametypes, it.gametype_flags & stored_supportedGametypes,
883 {
884 _MapInfo_Map_ApplyGametype ("", pGametypeToSet, it, true);
885 });
886 }
887 MapInfo_Map_supportedFeatures |= stored_supportedFeatures;
888 MapInfo_Map_flags |= stored_flags;
889 return true; // no need to continue through the file, we have our map!
890 }
891 else
892 {
893 // discard any gathered locals, we're not using the correct map!
894 stored_Map_description = "";
895 stored_Map_title = "";
896 stored_Map_author = "";
897 stored_supportedGametypes = '0 0 0';
898 stored_supportedFeatures = 0;
899 stored_flags = 0;
900 continue;
901 }
902 }
903
904 s = strreplace("\t", " ", s);
905
906 float p = strstrofs(s, "//", 0);
907 if(p >= 0)
908 s = substring(s, 0, p);
909
910 // perform an initial trim to ensure the first argument is properly obtained
911 // remove leading spaces
912 while(substring(s, 0, 1) == " ")
913 s = substring(s, 1, -1);
914
915 t = car(s); s = cdr(s);
916 t = strtolower(t); // apparently some q3 maps use capitalized parameters
917
918 // remove trailing spaces
919 while(substring(t, -1, 1) == " ")
920 t = substring(t, 0, -2);
921
922 // remove trailing spaces
923 while(substring(s, -1, 1) == " ")
924 s = substring(s, 0, -2);
925 // remove leading spaces
926 while(substring(s, 0, 1) == " ")
927 s = substring(s, 1, -1);
928 // limited support of ""
929 // remove trailing and leading " of s
930 if(substring(s, 0, 1) == "\"")
931 {
932 if(substring(s, -1, 1) == "\"")
933 s = substring(s, 1, -2);
934 }
935 if(t == "longname")
936 stored_Map_title = s;
937 else if(t == "author")
938 stored_Map_author = s;
939 else if(t == "type")
940 {
941 // if there is a valid gametype in this .arena file, include it in the menu
942 stored_supportedFeatures |= MAPINFO_FEATURE_WEAPONS;
943 // type in quake 3 holds all the supported gametypes, so we must loop through all of them
944 // TODO: handle support here better to include more Xonotic teamplay modes
945 string types = s;
946 types = strreplace("team", "tdm ft", types);
947 types = strreplace("ffa", "dm lms ka", types);
948 types = strreplace("tourney", "duel", types); // QL used duel so the following check must support it
949 if(strstrofs(types, "duel", 0) < 0 && strstrofs(types, "tdm", 0) >= 0) // larger team map, support additional gametypes!
950 types = cons(types, "ca kh");
951 FOREACH_WORD(types, true,
952 {
953 Gametype f = MapInfo_Type_FromString(it, false, true);
954 if(f)
955 stored_supportedGametypes |= f.gametype_flags;
956 });
957 }
958 else if(t == "style" && isdefi)
959 {
960 // we have a defrag map on our hands, add CTS!
961 // TODO: styles
962 stored_supportedGametypes |= MAPINFO_TYPE_CTS.gametype_flags;
963 }
964 else if(t == "map")
965 {
966 if(strtolower(s) == strtolower(pFilename))
967 dosave = true; // yay, found our map!
968 }
969 else if(t == "quote")
970 stored_Map_description = s;
971 // TODO: fraglimit
972 }
973
974 // if the map wasn't found in the .arena, fall back to generated .mapinfo
975 return false;
976}
977
978string _MapInfo_CheckArenaFile(string pFilename, string pMapname)
979{
980 // returns the file name if valid, otherwise returns ""
981 // a string is returned to optimise the use cases where a filename is also returned
982 int fh = fopen(pFilename, FILE_READ);
983 if(fh < 0)
984 return "";
985 for(string s; (s = fgets(fh)); )
986 {
987 s = strreplace("\t", "", s);
988 while(substring(s, 0, 1) == " ")
989 s = substring(s, 1, -1);
990 if(substring(s, 0, 2) == "//")
991 continue;
992 if(s == "")
993 continue;
994 int offset = strstrofs(s, "map", 0);
995 if(offset >= 0)
996 {
997 if(strstrofs(strtolower(s), strcat("\"", strtolower(pMapname), "\""), offset) >= 0) // quake 3 is case insensitive
998 {
999 fclose(fh);
1000 return pFilename; // FOUND IT!
1001 }
1002 }
1003 }
1004 fclose(fh);
1005 return ""; // file did not contain a "map" field matching our map name
1006}
1007
1008string _MapInfo_FindArenaFile(string pFilename, string extension)
1009{
1010 // A valid arena file with the "default" name may have been created as a map-specific
1011 // override of an arena file with a different name (possibly multi-map) in the pk3
1012 // (occasionally in the wild such file exists in the pk3 but isn't valid for a map in the pk3).
1013 // Therefore we try the "default" name first.
1014 string afile = _MapInfo_CheckArenaFile(strcat("scripts/", pFilename, extension), pFilename);
1015 if (afile != ""
1016 || !checkextension("DP_QC_FS_SEARCH_PACKFILE"))
1017 return afile;
1018
1019 string base_pack = whichpack(strcat("maps/", pFilename, ".bsp"));
1020 if(base_pack == "") // this map isn't packaged!
1021 return "";
1022 int glob = search_packfile_begin(strcat("scripts/*", extension), true, true, base_pack);
1023 if(glob < 0) // there's no matching file in the pack
1024 return "";
1025 int n = search_getsize(glob);
1026 for(int j = 0; j < n; ++j)
1027 {
1028 afile = search_getfilename(glob, j);
1029 if(_MapInfo_CheckArenaFile(afile, pFilename) != "")
1030 {
1031 search_end(glob);
1032 return afile;
1033 }
1034 }
1035 search_end(glob);
1036
1037 return ""; // if we get here, a valid .arena file could not be found
1038}
1039
1040// load info about a map by name into the MapInfo_Map_* globals
1041float MapInfo_Get_ByName_NoFallbacks(string pFilename, int pAllowGenerate, Gametype pGametypeToSet)
1042{
1043 string fn;
1044 string s, t;
1045 float fh;
1046 int f, i;
1047 float r, n;
1048 string acl;
1049
1051
1052 if(strstrofs(pFilename, "/", 0) >= 0)
1053 {
1054 LOG_WARN("Invalid character in map name, ignored");
1055 return 0;
1056 }
1057
1058 if(pGametypeToSet == NULL)
1059 if(MapInfo_Cache_Retrieve(pFilename))
1060 return 1;
1061
1062 r = 1;
1063
1064 MapInfo_Map_bspname = pFilename;
1065
1066 // default all generic fields so they have "good" values in case something fails
1067 fn = strcat("maps/", pFilename, ".mapinfo");
1068 fh = fopen(fn, FILE_READ);
1069 if(fh < 0)
1070 {
1071 if(autocvar_g_mapinfo_q3compat == 1) // use arena data instead of generating a mapinfo file
1072 {
1073 // supporting .arena AND .defi for the same map
1074 bool success = false;
1075 fn = _MapInfo_FindArenaFile(pFilename, ".arena");
1076 if(fn != "" && (fh = fopen(fn, FILE_READ)) >= 0)
1077 {
1079 success = _MapInfo_ParseArena(fn, fh, pFilename, pGametypeToSet, false, false);
1080 fclose(fh);
1081 }
1082 fn = _MapInfo_FindArenaFile(pFilename, ".defi");
1083 if(fn != "" && (fh = fopen(fn, FILE_READ)) >= 0)
1084 {
1085 if(!success)
1087 success |= _MapInfo_ParseArena(fn, fh, pFilename, pGametypeToSet, true, false);
1088 fclose(fh);
1089 }
1090 if(success)
1091 goto mapinfo_handled; // skip generation
1092 }
1093
1094 fn = strcat("maps/autogenerated/", pFilename, ".mapinfo");
1095 fh = fopen(fn, FILE_READ);
1096 if(fh < 0)
1097 {
1098 if(!pAllowGenerate)
1099 return 0;
1101 r = _MapInfo_Generate(pFilename);
1102 if(!r)
1103 return 0;
1105 fh = fopen(fn, FILE_WRITE);
1106 fputs(fh, strcat("title ", MapInfo_Map_title, "\n"));
1107 fputs(fh, strcat("description ", MapInfo_Map_description, "\n"));
1108 fputs(fh, strcat("author ", MapInfo_Map_author, "\n"));
1110 {
1112 fputs(fh, strcat("cdtrack ", substring(_MapInfo_Map_worldspawn_music, 0, -4), "\n"));
1113 else
1114 fputs(fh, strcat("cdtrack ", _MapInfo_Map_worldspawn_music, "\n"));
1115 }
1116 else
1117 {
1118 n = tokenize_console(cvar_string("g_cdtracks_remaplist"));
1119 s = strcat(" ", cvar_string("g_cdtracks_dontusebydefault"), " ");
1120 for (;;)
1121 {
1122 i = floor(random() * n);
1123 if(strstrofs(s, strcat(" ", argv(i), " "), 0) < 0)
1124 break;
1125 }
1126 fputs(fh, strcat("cdtrack ", ftos(i + 1), "\n"));
1127 }
1129 fputs(fh, "has weapons\n");
1130 else
1131 fputs(fh, "// uncomment this if you added weapon pickups: has weapons\n");
1133 fputs(fh, "has turrets\n");
1134 else
1135 fputs(fh, "// uncomment this if you added turrets: has turrets\n");
1137 fputs(fh, "has vehicles\n");
1138 else
1139 fputs(fh, "// uncomment this if you added vehicles: has vehicles\n");
1141 fputs(fh, "frustrating\n");
1142
1143 FOREACH(Gametypes, MapInfo_Map_supportedGametypes & it.gametype_flags, {
1144 fputs(fh, sprintf("gametype %s // defaults: %s\n", MapInfo_Type_ToString(it), _MapInfo_GetDefaultEx(it)));
1145 });
1146
1147 fputs(fh, "// optional: fog density red green blue alpha mindist maxdist\n");
1148 fputs(fh, "// optional: settemp_for_type (all|gametypename) cvarname value\n");
1149 fputs(fh, "// optional: clientsettemp_for_type (all|gametypename) cvarname value\n");
1150 fputs(fh, "// optional: size mins_x mins_y mins_z maxs_x maxs_y maxs_z\n");
1151 fputs(fh, "// optional: hidden\n");
1152
1153 fclose(fh);
1154 r = 2;
1155 // return r;
1156 fh = fopen(fn, FILE_READ);
1157 if(fh < 0)
1158 error("... but I just wrote it!");
1159 }
1160
1161 if(WARN_COND)
1162 LOG_WARN("autogenerated mapinfo file ", fn, " has been loaded; please edit that file and move it to maps/", pFilename, ".mapinfo");
1163 }
1164
1166 for (;;)
1167 {
1168 if (!((s = fgets(fh))))
1169 break;
1170
1171 // catch different sorts of comments
1172 if(s == "") // empty lines
1173 continue;
1174 if(substring(s, 0, 1) == "#") // UNIX style
1175 continue;
1176 if(substring(s, 0, 2) == "//") // C++ style
1177 continue;
1178 if(substring(s, 0, 1) == "_") // q3map style
1179 continue;
1180
1181 float p = strstrofs(s, "//", 0);
1182 if(p >= 0)
1183 s = substring(s, 0, p);
1184
1185 t = car(s); s = cdr(s);
1186 if(t == "title")
1188 else if(t == "description")
1190 else if(t == "author")
1192 else if(t == "has")
1193 {
1194 t = car(s); // s = cdr(s);
1196 else if(t == "turrets") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_TURRETS;
1197 else if(t == "vehicles") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_VEHICLES;
1198 else if(t == "monsters") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_MONSTERS;
1199 else if(t == "new_toys") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_WEAPONS;
1200 else if(WARN_COND)
1201 LOG_WARN("Map ", pFilename, " supports unknown feature ", t, ", ignored");
1202 }
1203 else if(t == "hidden")
1204 {
1206 }
1207 else if(t == "forbidden")
1208 {
1210 }
1211 else if(t == "frustrating")
1212 {
1214 }
1215 else if(t == "donotwant" || t == "noautomaplist")
1216 {
1218 }
1219 else if(t == "gameversion_min")
1220 {
1221 if (cvar("gameversion") < stof(s))
1223 }
1224 else if(t == "type")
1225 {
1226 t = car(s); s = cdr(s);
1227 Gametype f = MapInfo_Type_FromString(t, true, false);
1228 //if(WARN_COND)
1229 //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'.");
1230 if(f)
1231 _MapInfo_Map_ApplyGametype (s, pGametypeToSet, f, true);
1232 else if(WARN_COND)
1233 LOG_DEBUG("Map ", pFilename, " supports unknown gametype ", t, ", ignored");
1234 }
1235 else if(t == "gametype")
1236 {
1237 t = car(s); s = cdr(s);
1238 Gametype f = MapInfo_Type_FromString(t, true, false);
1239 if(f)
1240 _MapInfo_Map_ApplyGametypeEx (s, pGametypeToSet, f);
1241 else if(WARN_COND)
1242 LOG_DEBUG("Map ", pFilename, " supports unknown gametype ", t, ", ignored");
1243 }
1244 else if(t == "size")
1245 {
1246 float a, b, c, d, e;
1247 t = car(s); s = cdr(s); a = stof(t);
1248 t = car(s); s = cdr(s); b = stof(t);
1249 t = car(s); s = cdr(s); c = stof(t);
1250 t = car(s); s = cdr(s); d = stof(t);
1251 t = car(s); s = cdr(s); e = stof(t);
1252 if(s == "")
1253 {
1254 if(WARN_COND)
1255 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");
1256 }
1257 else
1258 {
1259 t = car(s); s = cdr(s); f = stof(t);
1260 if(s != "")
1261 {
1262 if(WARN_COND)
1263 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");
1264 }
1265 else
1266 {
1267 if(a >= d || b >= e || c >= f)
1268 {
1269 if(WARN_COND)
1270 LOG_WARN("Map ", pFilename, " contains an incorrect size line, mins have to be < maxs");
1271 }
1272 else
1273 {
1274 MapInfo_Map_mins.x = a;
1275 MapInfo_Map_mins.y = b;
1276 MapInfo_Map_mins.z = c;
1277 MapInfo_Map_maxs.x = d;
1278 MapInfo_Map_maxs.y = e;
1279 MapInfo_Map_maxs.z = f;
1280 }
1281 }
1282 }
1283 }
1284 else if(t == "settemp_for_type")
1285 {
1286 t = car(s); s = cdr(s);
1287 bool all = t == "all";
1288 Gametype f = NULL;
1289 if(all || (f = MapInfo_Type_FromString(t, true, false)))
1290 {
1291 if((all ? MAPINFO_TYPE_ALL : f.gametype_flags) & pGametypeToSet.gametype_flags)
1292 {
1293 _MapInfo_Parse_Settemp(pFilename, acl, 0, s, 1);
1294 }
1295 }
1296 else
1297 {
1298 LOG_DEBUG("Map ", pFilename, " has a setting for unknown gametype ", t, ", ignored");
1299 }
1300 }
1301 else if(t == "clientsettemp_for_type")
1302 {
1303 t = car(s); s = cdr(s);
1304 bool all = t == "all";
1305 Gametype f = NULL;
1306 if(all || (f = MapInfo_Type_FromString(t, true, false)))
1307 {
1308 if((all ? MAPINFO_TYPE_ALL : f.gametype_flags) & pGametypeToSet.gametype_flags)
1309 {
1310 _MapInfo_Parse_Settemp(pFilename, acl, 1, s, 1);
1311 }
1312 }
1313 else
1314 {
1315 LOG_DEBUG("Map ", pFilename, " has a client setting for unknown gametype ", t, ", ignored");
1316 }
1317 }
1318 else if(t == "fog")
1319 {
1320 if (!cvar_value_issafe(s))
1321 {
1322 if(WARN_COND)
1323 LOG_WARN("Map ", pFilename, " contains a potentially harmful fog setting, ignored");
1324 }
1325 else
1326 MapInfo_Map_fog = s;
1327 }
1328 else if(t == "cdtrack")
1329 {
1330 t = car(s); s = cdr(s);
1331 // We do this only if pGametypeToSet even though this
1332 // content is theoretically gametype independent,
1333 // because MapInfo_Map_clientstuff contains otherwise
1334 // gametype dependent stuff. That way this value stays
1335 // empty when not setting a gametype to not set any
1336 // false expectations.
1337 if(pGametypeToSet)
1338 {
1339 if (!cvar_value_issafe(t))
1340 {
1341 if(WARN_COND)
1342 LOG_WARN("Map ", pFilename, " contains a potentially harmful cdtrack, ignored");
1343 }
1344 else
1346 MapInfo_Map_clientstuff, "cd loop \"", t, "\"\n"
1347 );
1348 }
1349 }
1350 else if(WARN_COND)
1351 LOG_WARN("Map ", pFilename, " provides unknown info item ", t, ", ignored");
1352 }
1353 fclose(fh);
1354
1355LABEL(mapinfo_handled)
1356#ifdef SVQC
1357 // if the map is currently loaded we can read worldspawn fields directly
1358 if (pFilename == mi_shortname)
1359 {
1360 if (MapInfo_Map_title == "<TITLE>")
1361 if (world.message != "")
1362 MapInfo_Map_title = world.message;
1363 if (MapInfo_Map_author == "<AUTHOR>")
1364 if ((s = GetField_fullspawndata(world, "author", false)) != "")
1366 }
1367#endif
1368 // Could skip removing author from title when it's source is .mapinfo
1369 // but must always do it for world.message and .arena/.defi as VQ3 didn't support author
1370 // so mappers tended to put it in world.message and/or longname.
1371 MapInfo_Map_title = MapInfo_title_sans_author(MapInfo_Map_title); // may set author if not set
1372
1373 if(MapInfo_Map_title == "<TITLE>")
1377 else
1379
1380 if (MapInfo_Map_author == "<AUTHOR>")
1381 MapInfo_Map_author = ""; // don't display "<AUTHOR>" in the UI (we do write it to .mapinfo files)
1382
1384 if(MapInfo_Map_supportedGametypes != '0 0 0')
1385 return r;
1386 if (WARN_COND)
1387 LOG_WARN("Map ", pFilename, " supports no gametypes, ignored");
1388 return 0;
1389}
1390int MapInfo_Get_ByName(string pFilename, float pAllowGenerate, Gametype pGametypeToSet)
1391{
1392 int r = MapInfo_Get_ByName_NoFallbacks(pFilename, pAllowGenerate, pGametypeToSet);
1393
1394 FOREACH(Gametypes, it.m_isForcedSupported(it), _MapInfo_Map_ApplyGametypeEx("", pGametypeToSet, it));
1395
1396 if(pGametypeToSet)
1397 {
1398 if(!(MapInfo_Map_supportedGametypes & pGametypeToSet.gametype_flags))
1399 {
1400 error("Can't select the requested gametype. This should never happen as the caller should prevent it!\n");
1401 //_MapInfo_Map_ApplyGametypeEx("", pGametypeToSet, MAPINFO_TYPE_DEATHMATCH);
1402 //return;
1403 }
1404 }
1405
1406 return r;
1407}
1408
1409bool MapReadSizes(string map)
1410{
1411 // TODO: implement xonotic#28 / xonvote 172 (sizes in mapinfo)
1412 string readsize_msg = strcat("MapReadSizes ", map);
1413 float fh = fopen(strcat("maps/", map, ".sizes"), FILE_READ);
1414 if(fh >= 0)
1415 {
1416 map_minplayers = stoi(fgets(fh));
1417 map_maxplayers = stoi(fgets(fh));
1418 fclose(fh);
1419 LOG_TRACEF(readsize_msg, ": ok, min %d max %d", map_minplayers, map_maxplayers);
1420 return true;
1421 }
1422 LOG_TRACE(readsize_msg, ": not found");
1423 return false;
1424}
1425
1426float MapInfo_FindName(string s)
1427{
1428 // if there is exactly one map of prefix s, return it
1429 // if not, return the null string
1430 // note that DP sorts glob results... so I can use a binary search
1431 float l, r, m, cmp;
1432 l = 0;
1433 r = MapInfo_count;
1434 // invariants: r is behind s, l-1 is equal or before
1435 while(l != r)
1436 {
1437 m = floor((l + r) / 2);
1440 if(cmp == 0)
1441 return m; // found and good
1442 if(cmp < 0)
1443 l = m + 1; // l-1 is before s
1444 else
1445 r = m; // behind s
1446 }
1449 // r == l, so: l is behind s, l-1 is before
1450 // SO: if there is any, l is the one with the right prefix
1451 // and l+1 may be one too
1452 if(l == MapInfo_count)
1453 {
1456 return -1; // no MapInfo_FindName_match, behind last item
1457 }
1459 {
1462 return -1; // wrong prefix
1463 }
1464 if(l == MapInfo_count - 1)
1465 return l; // last one, nothing can follow => unique
1467 {
1469 return -1; // ambigous MapInfo_FindName_match
1470 }
1471 return l;
1472}
1473
1474string MapInfo_FixName(string s)
1475{
1478}
1479
1481{
1482 int req = 0;
1483 // TODO: find a better way to check if weapons are required on the map
1484 if(!(cvar("g_instagib") || cvar("g_overkill") || cvar("g_nix") || cvar("g_weaponarena") || !cvar("g_pickup_items") || !cvar("g_melee_only")
1485 || cvar("g_race") || cvar("g_cts") || cvar("g_nexball") || cvar("g_ca") || cvar("g_freezetag") || cvar("g_lms")))
1487 return req;
1488}
1489
1491{
1492 Gametype prev = MapInfo_Type_FromString(cvar_string("gamecfg"), false, false);
1493 FOREACH(Gametypes, cvar(it.netname) && it != prev, return it);
1494 return prev ? prev : MAPINFO_TYPE_DEATHMATCH;
1495}
1496
1497float _MapInfo_CheckMap(string s, bool gametype_only) // returns 0 if the map can't be played with the current settings, 1 otherwise
1498{
1499 if(!MapInfo_Get_ByName(s, 1, NULL))
1500 return 0;
1502 return 0;
1503 if (gametype_only)
1504 return 1;
1506 return 0;
1507 return 1;
1508}
1509
1510float MapInfo_CheckMap(string s) // returns 0 if the map can't be played with the current settings, 1 otherwise
1511{
1512 float r;
1513 r = _MapInfo_CheckMap(s, false);
1515 return r;
1516}
1517
1519{
1520 FOREACH(Gametypes, true, cvar_set(it.netname, (it == t) ? "1" : "0"));
1521}
1522
1523void MapInfo_LoadMap(string s, float reinit)
1524{
1526 // we shouldn't need this, as LoadMapSettings already fixes the gametype
1527 //if(!MapInfo_CheckMap(s))
1528 //{
1529 // print("EMERGENCY: can't play the selected map in the given gametype. Falling back to DM.\n");
1530 // MapInfo_SwitchGameType(MAPINFO_TYPE_DEATHMATCH.gametype_flags);
1531 //}
1532
1533 LOG_INFO("Switching to map ", s);
1534
1536 if(reinit)
1537 localcmd(strcat("\nmap ", s, "\n"));
1538 else
1539 localcmd(strcat("\nchangelevel ", s, "\n"));
1540}
1541
1542string MapInfo_ListAllowedMaps(Gametype type, float pRequiredFlags, float pForbiddenFlags)
1543{
1544 string out;
1545
1546 // to make absolutely sure:
1548 MapInfo_FilterGametype(type, MapInfo_CurrentFeatures(), pRequiredFlags, pForbiddenFlags, 0);
1549
1550 out = "";
1551 for(float i = 0; i < MapInfo_count; ++i)
1553 return substring(out, 1, strlen(out) - 1);
1554}
1555
1556string MapInfo_ListAllAllowedMaps(float pRequiredFlags, float pForbiddenFlags)
1557{
1558 string out;
1559
1560 // to make absolutely sure:
1562 _MapInfo_FilterGametype(MAPINFO_TYPE_ALL, 0, pRequiredFlags, pForbiddenFlags, 0);
1563
1564 out = "";
1565 for(float i = 0; i < MapInfo_count; ++i)
1567
1568 MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), pRequiredFlags, pForbiddenFlags, 0);
1569
1570 return substring(out, 1, strlen(out) - 1);
1571}
1572
1574{
1576 cvar_set("gamecfg", t.mdl);
1578}
1579
1580void MapInfo_LoadMapSettings(string s) // to be called from worldspawn
1581{
1584
1585 if(!_MapInfo_CheckMap(s, true)) // with underscore, it keeps temps
1586 {
1587 if(cvar("g_mapinfo_allow_unsupported_modes_and_let_stuff_break"))
1588 {
1589 LOG_SEVERE("can't play the selected map in the given gametype. Working with only the override settings.");
1591 return; // do not call Get_ByName!
1592 }
1593
1594 if(MapInfo_Map_supportedGametypes == '0 0 0')
1595 {
1597 FOREACH(Gametypes, it.m_priority == 2,
1598 {
1599 MapInfo_Map_supportedGametypes |= it.gametype_flags;
1600 RandomSelection_AddEnt(it, 1, 1);
1601 });
1604 LOG_SEVEREF("Mapinfo system is not functional at all. Falling back to a preferred mode (%s).", t.mdl);
1607 return; // do not call Get_ByName!
1608 }
1609
1610#if 0
1611 // find the lowest bit in the supported gametypes
1612 // unnecessary now that we select one at random
1613 int _t = 1;
1614 while(!(MapInfo_Map_supportedGametypes & 1))
1615 {
1616 _t <<= 1;
1618 }
1619#endif
1621 Gametype t_prev = t;
1622 FOREACH(Gametypes, MapInfo_Map_supportedGametypes & it.gametype_flags,
1623 {
1624 RandomSelection_AddEnt(it, 1, it.m_priority);
1625 });
1628
1629 // t is now a supported mode!
1630 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);
1632 }
1633 if(!_MapInfo_CheckMap(s, false)) { // with underscore, it keeps temps
1634 LOG_WARNF("the selected map lacks features required by current settings; playing anyway.");
1635 }
1636 MapInfo_Get_ByName(s, 1, t);
1637}
1638
1650
1663
1665{
1666 int f = MAPINFO_FLAG_FORBIDDEN;
1667
1668#ifdef GAMEQC
1669 if (!cvar("g_maplist_allow_hidden"))
1670#endif
1672
1673 if (!cvar("g_maplist_allow_frustrating"))
1675
1676 return f;
1677}
1678
1680{
1681 int f = 0;
1682
1683 if(cvar("g_maplist_allow_frustrating") > 1)
1685
1686 return f;
1687}
#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:1339
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:1409
float MapInfo_CheckMap(string s)
Definition mapinfo.qc:1510
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:1573
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:978
bool MapInfo_isRedundant(string fn, string t)
Definition mapinfo.qc:804
float MapInfo_FindName(string s)
Definition mapinfo.qc:1426
bool MapInfo_Get_ByID(int i)
Definition mapinfo.qc:275
void MapInfo_Shutdown()
Definition mapinfo.qc:1651
int MapInfo_RequiredFlags()
Definition mapinfo.qc:1679
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:778
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:1523
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:1556
int autocvar_g_mapinfo_q3compat
Definition mapinfo.qc:11
Gametype MapInfo_CurrentGametype()
Definition mapinfo.qc:1490
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:1008
#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:1390
int MapInfo_ForbiddenFlags()
Definition mapinfo.qc:1664
int MapInfo_CurrentFeatures()
Definition mapinfo.qc:1480
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:1542
int _MapInfo_Cache_Active
Definition mapinfo.qc:39
float _MapInfo_CheckMap(string s, bool gametype_only)
Definition mapinfo.qc:1497
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:1518
void MapInfo_LoadMapSettings(string s)
Definition mapinfo.qc:1580
void MapInfo_ClearTemps()
Definition mapinfo.qc:1639
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:1041
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:826
string MapInfo_FixName(string s)
Definition mapinfo.qc:1474
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