1 /*
2 ** g_level.cpp
3 ** Parses MAPINFO
4 **
5 **---------------------------------------------------------------------------
6 ** Copyright 1998-2006 Randy Heit
7 ** Copyright 2009 Christoph Oelckers
8 ** All rights reserved.
9 **
10 ** Redistribution and use in source and binary forms, with or without
11 ** modification, are permitted provided that the following conditions
12 ** are met:
13 **
14 ** 1. Redistributions of source code must retain the above copyright
15 **    notice, this list of conditions and the following disclaimer.
16 ** 2. Redistributions in binary form must reproduce the above copyright
17 **    notice, this list of conditions and the following disclaimer in the
18 **    documentation and/or other materials provided with the distribution.
19 ** 3. The name of the author may not be used to endorse or promote products
20 **    derived from this software without specific prior written permission.
21 **
22 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
23 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
26 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
27 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
31 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 **---------------------------------------------------------------------------
33 **
34 */
35 
36 #include <assert.h>
37 #include "templates.h"
38 #include "g_level.h"
39 #include "sc_man.h"
40 #include "w_wad.h"
41 #include "cmdlib.h"
42 #include "v_video.h"
43 #include "p_lnspec.h"
44 #include "p_setup.h"
45 #include "i_system.h"
46 #include "gi.h"
47 #include "gstrings.h"
48 #include "farchive.h"
49 #include "p_acs.h"
50 #include "doomstat.h"
51 #include "d_player.h"
52 #include "autosegs.h"
53 #include "version.h"
54 #include "v_text.h"
55 
56 TArray<cluster_info_t> wadclusterinfos;
57 TArray<level_info_t> wadlevelinfos;
58 
59 level_info_t TheDefaultLevelInfo;
60 static cluster_info_t TheDefaultClusterInfo;
61 
62 TArray<FEpisode> AllEpisodes;
63 
64 extern TMap<int, FString> HexenMusic;
65 
66 //==========================================================================
67 //
68 //
69 //==========================================================================
70 
FindWadLevelInfo(const char * name)71 static int FindWadLevelInfo (const char *name)
72 {
73 	for (unsigned int i = 0; i < wadlevelinfos.Size(); i++)
74 	{
75 		if (!wadlevelinfos[i].MapName.CompareNoCase(name))
76 			return i;
77 	}
78 	return -1;
79 }
80 
81 //==========================================================================
82 //
83 //
84 //==========================================================================
85 
FindLevelInfo(const char * mapname,bool allowdefault)86 level_info_t *FindLevelInfo (const char *mapname, bool allowdefault)
87 {
88 	int i;
89 
90 	if ((i = FindWadLevelInfo (mapname)) > -1)
91 	{
92 		return &wadlevelinfos[i];
93 	}
94 	else if (allowdefault)
95 	{
96 		if (TheDefaultLevelInfo.LevelName.IsEmpty())
97 		{
98 			TheDefaultLevelInfo.SkyPic2 = TheDefaultLevelInfo.SkyPic1 = "SKY1";
99 			TheDefaultLevelInfo.LevelName = "Unnamed";
100 		}
101 		return &TheDefaultLevelInfo;
102 	}
103 	return NULL;
104 }
105 
106 //==========================================================================
107 //
108 //
109 //==========================================================================
110 
FindLevelByNum(int num)111 level_info_t *FindLevelByNum (int num)
112 {
113 	for (unsigned int i = 0; i < wadlevelinfos.Size(); i++)
114 		if (wadlevelinfos[i].levelnum == num)
115 			return &wadlevelinfos[i];
116 
117 	return NULL;
118 }
119 
120 
121 //==========================================================================
122 //
123 //
124 //==========================================================================
125 
FindLevelByWarpTrans(int num)126 static level_info_t *FindLevelByWarpTrans (int num)
127 {
128 	for (unsigned i = wadlevelinfos.Size(); i-- != 0; )
129 		if (wadlevelinfos[i].WarpTrans == num)
130 			return &wadlevelinfos[i];
131 
132 	return NULL;
133 }
134 
135 //==========================================================================
136 //
137 //
138 //==========================================================================
139 
CheckWarpTransMap(FString & mapname,bool substitute)140 bool CheckWarpTransMap (FString &mapname, bool substitute)
141 {
142 	if (mapname[0] == '&' && (mapname[1] & 0xDF) == 'W' &&
143 		(mapname[2] & 0xDF) == 'T' && mapname[3] == '@')
144 	{
145 		level_info_t *lev = FindLevelByWarpTrans (atoi (&mapname[4]));
146 		if (lev != NULL)
147 		{
148 			mapname = lev->MapName;
149 			return true;
150 		}
151 		else if (substitute)
152 		{
153 			char a = mapname[4], b = mapname[5];
154 			mapname = "MAP";
155 			mapname << a << b;
156 		}
157 	}
158 	return false;
159 }
160 
161 //==========================================================================
162 //
163 //
164 //==========================================================================
165 
FindWadClusterInfo(int cluster)166 static int FindWadClusterInfo (int cluster)
167 {
168 	for (unsigned int i = 0; i < wadclusterinfos.Size(); i++)
169 		if (wadclusterinfos[i].cluster == cluster)
170 			return i;
171 
172 	return -1;
173 }
174 
175 //==========================================================================
176 //
177 //
178 //==========================================================================
179 
FindClusterInfo(int cluster)180 cluster_info_t *FindClusterInfo (int cluster)
181 {
182 	int i;
183 
184 	if ((i = FindWadClusterInfo (cluster)) > -1)
185 		return &wadclusterinfos[i];
186 	else
187 		return &TheDefaultClusterInfo;
188 }
189 
190 //==========================================================================
191 //
192 //
193 //==========================================================================
194 
G_ClearSnapshots(void)195 void G_ClearSnapshots (void)
196 {
197 	for (unsigned int i = 0; i < wadlevelinfos.Size(); i++)
198 	{
199 		wadlevelinfos[i].ClearSnapshot();
200 	}
201 	// Since strings are only locked when snapshotting a level, unlock them
202 	// all now, since we got rid of all the snapshots that cared about them.
203 	GlobalACSStrings.UnlockAll();
204 }
205 
206 //==========================================================================
207 //
208 // Remove any existing defereds
209 //
210 //==========================================================================
211 
P_RemoveDefereds(void)212 void P_RemoveDefereds (void)
213 {
214 	for (unsigned int i = 0; i < wadlevelinfos.Size(); i++)
215 	{
216 		wadlevelinfos[i].ClearDefered();
217 	}
218 }
219 
220 
221 //==========================================================================
222 //
223 //
224 //==========================================================================
225 
Reset()226 void level_info_t::Reset()
227 {
228 	MapName = "";
229 	MapBackground = "";
230 	levelnum = 0;
231 	PName = "";
232 	NextMap = "";
233 	NextSecretMap = "";
234 	SkyPic1 = SkyPic2 = "-NOFLAT-";
235 	cluster = 0;
236 	partime = 0;
237 	sucktime = 0;
238 	flags = 0;
239 	if (gameinfo.gametype == GAME_Hexen)
240 		flags2 = 0;
241 	else
242 		flags2 = LEVEL2_LAXMONSTERACTIVATION;
243 	flags3 = 0;
244 	Music = "";
245 	LevelName = "";
246 	FadeTable = "COLORMAP";
247 	WallHorizLight = -8;
248 	WallVertLight = +8;
249 	F1Pic = "";
250 	musicorder = 0;
251 	snapshot = NULL;
252 	snapshotVer = 0;
253 	defered = 0;
254 	skyspeed1 = skyspeed2 = 0.f;
255 	fadeto = 0;
256 	outsidefog = 0xff000000;
257 	cdtrack = 0;
258 	cdid = 0;
259 	gravity = 0.f;
260 	aircontrol = 0.f;
261 	WarpTrans = 0;
262 	airsupply = 20;
263 	compatflags = compatflags2 = 0;
264 	compatmask = compatmask2 = 0;
265 	Translator = "";
266 	RedirectType = 0;
267 	RedirectMapName = "";
268 	EnterPic = "";
269 	ExitPic = "";
270 	InterMusic = "";
271 	intermusicorder = 0;
272 	SoundInfo = "";
273 	SndSeq = "";
274 	BorderTexture = "";
275 	teamdamage = 0.f;
276 	specialactions.Clear();
277 	DefaultEnvironment = 0;
278 	PrecacheSounds.Clear();
279 }
280 
281 
282 //==========================================================================
283 //
284 //
285 //==========================================================================
286 
LookupLevelName()287 FString level_info_t::LookupLevelName()
288 {
289 	if (flags & LEVEL_LOOKUPLEVELNAME)
290 	{
291 		const char *thename;
292 		const char *lookedup;
293 
294 		lookedup = GStrings[LevelName];
295 		if (lookedup == NULL)
296 		{
297 			thename = LevelName;
298 		}
299 		else
300 		{
301 			char checkstring[32];
302 
303 			// Strip out the header from the localized string
304 			if (MapName.Len() > 3 && MapName[0] == 'E' && MapName[2] == 'M')
305 			{
306 				mysnprintf (checkstring, countof(checkstring), "%s: ", MapName.GetChars());
307 			}
308 			else if (MapName.Len() > 3 && MapName[0] == 'M' && MapName[1] == 'A' && MapName[2] == 'P')
309 			{
310 				mysnprintf (checkstring, countof(checkstring), "%d: ", atoi(&MapName[3]));
311 			}
312 			else if (MapName.Len() > 5 && MapName[0] == 'L' && MapName[1] == 'E' && MapName[2] == 'V' && MapName[3] == 'E' && MapName[4] == 'L')
313 			{
314 				mysnprintf (checkstring, countof(checkstring), "%d: ", atoi(&MapName[5]));
315 			}
316 			else
317 			{
318 				// make sure nothing is stripped.
319 				checkstring[0] = '\0';
320 			}
321 			thename = strstr (lookedup, checkstring);
322 			if (thename == NULL)
323 			{
324 				thename = lookedup;
325 			}
326 			else
327 			{
328 				thename += strlen (checkstring);
329 			}
330 		}
331 		return thename;
332 	}
333 	else return LevelName;
334 }
335 
336 
337 //==========================================================================
338 //
339 //
340 //==========================================================================
341 
ClearSnapshot()342 void level_info_t::ClearSnapshot()
343 {
344 	if (snapshot != NULL) delete snapshot;
345 	snapshot = NULL;
346 }
347 
348 //==========================================================================
349 //
350 //
351 //==========================================================================
352 
ClearDefered()353 void level_info_t::ClearDefered()
354 {
355 	acsdefered_t *def = defered;
356 	while (def)
357 	{
358 		acsdefered_t *next = def->next;
359 		delete def;
360 		def = next;
361 	}
362 	defered = NULL;
363 }
364 
365 //==========================================================================
366 //
367 //
368 //==========================================================================
369 
CheckLevelRedirect()370 level_info_t *level_info_t::CheckLevelRedirect ()
371 {
372 	if (RedirectType != NAME_None)
373 	{
374 		const PClass *type = PClass::FindClass(RedirectType);
375 		if (type != NULL)
376 		{
377 			for (int i = 0; i < MAXPLAYERS; ++i)
378 			{
379 				if (playeringame[i] && players[i].mo->FindInventory (type))
380 				{
381 					// check for actual presence of the map.
382 					if (P_CheckMapData(RedirectMapName))
383 					{
384 						return FindLevelInfo(RedirectMapName);
385 					}
386 					break;
387 				}
388 			}
389 		}
390 	}
391 	return NULL;
392 }
393 
394 //==========================================================================
395 //
396 //
397 //==========================================================================
398 
isValid()399 bool level_info_t::isValid()
400 {
401 	return MapName.Len() != 0 || this == &TheDefaultLevelInfo;
402 }
403 
404 //==========================================================================
405 //
406 //
407 //==========================================================================
408 
Reset()409 void cluster_info_t::Reset()
410 {
411 	cluster = 0;
412 	FinaleFlat = "";
413 	ExitText = "";
414 	EnterText = "";
415 	MessageMusic = "";
416 	musicorder = 0;
417 	flags = 0;
418 	cdtrack = 0;
419 	ClusterName = "";
420 	cdid = 0;
421 }
422 
423 
424 
425 //==========================================================================
426 //
427 //
428 //==========================================================================
429 
ParseOpenBrace()430 void FMapInfoParser::ParseOpenBrace()
431 {
432 	switch(format_type)
433 	{
434 	default:
435 		format_type = sc.CheckString("{") ? FMT_New : FMT_Old;
436 		if (format_type == FMT_New)
437 			sc.SetCMode(true);
438 		break;
439 
440 	case FMT_Old:
441 		break;
442 
443 	case FMT_New:
444 		sc.MustGetStringName("{");
445 		sc.SetCMode(true);
446 		break;
447 	}
448 }
449 
450 //==========================================================================
451 //
452 //
453 //==========================================================================
454 
ParseCloseBrace()455 bool FMapInfoParser::ParseCloseBrace()
456 {
457 	if (format_type == FMT_New)
458 	{
459 		return sc.Compare("}");
460 	}
461 	else
462 	{
463 		// We have to assume that the next keyword
464 		// starts a new top level block
465 		sc.UnGet();
466 		return true;
467 	}
468 }
469 
470 //==========================================================================
471 //
472 //
473 //==========================================================================
474 
CheckAssign()475 bool FMapInfoParser::CheckAssign()
476 {
477 	if (format_type == FMT_New) return sc.CheckString("=");
478 	else return false;	// force explicit handling
479 }
480 
481 //==========================================================================
482 //
483 //
484 //==========================================================================
485 
ParseAssign()486 void FMapInfoParser::ParseAssign()
487 {
488 	if (format_type == FMT_New) sc.MustGetStringName("=");
489 }
490 
491 //==========================================================================
492 //
493 //
494 //==========================================================================
495 
MustParseAssign()496 void FMapInfoParser::MustParseAssign()
497 {
498 	if (format_type == FMT_New) sc.MustGetStringName("=");
499 	else sc.ScriptError(NULL);
500 }
501 
502 //==========================================================================
503 //
504 //
505 //==========================================================================
506 
ParseComma()507 void FMapInfoParser::ParseComma()
508 {
509 	if (format_type == FMT_New) sc.MustGetStringName(",");
510 }
511 
512 //==========================================================================
513 //
514 //
515 //==========================================================================
516 
CheckNumber()517 bool FMapInfoParser::CheckNumber()
518 {
519 	if (format_type == FMT_New)
520 	{
521 		if (sc.CheckString(","))
522 		{
523 			sc.MustGetNumber();
524 			return true;
525 		}
526 		return false;
527 	}
528 	else return sc.CheckNumber();
529 }
530 
531 //==========================================================================
532 //
533 //
534 //==========================================================================
535 
CheckFloat()536 bool FMapInfoParser::CheckFloat()
537 {
538 	if (format_type == FMT_New)
539 	{
540 		if (sc.CheckString(","))
541 		{
542 			sc.MustGetFloat();
543 			return true;
544 		}
545 		return false;
546 	}
547 	else return sc.CheckFloat();
548 }
549 
550 //==========================================================================
551 //
552 // skips an entire parameter list that's separated by commas
553 //
554 //==========================================================================
555 
SkipToNext()556 void FMapInfoParser::SkipToNext()
557 {
558 	if (sc.CheckString("="))
559 	{
560 		do
561 		{
562 			sc.MustGetString();
563 		}
564 		while (sc.CheckString(","));
565 	}
566 }
567 
568 
569 //==========================================================================
570 //
571 // checks if the current block was properly terminated
572 //
573 //==========================================================================
574 
CheckEndOfFile(const char * block)575 void FMapInfoParser::CheckEndOfFile(const char *block)
576 {
577 	if (format_type == FMT_New && !sc.Compare("}"))
578 	{
579 		sc.ScriptError("Unexpected end of file in %s definition", block);
580 	}
581 }
582 
583 //==========================================================================
584 //
585 // ParseLookupname
586 // Parses a string that may reference the string table
587 //
588 //==========================================================================
589 
ParseLookupName(FString & dest)590 bool FMapInfoParser::ParseLookupName(FString &dest)
591 {
592 	sc.MustGetString();
593 	if (sc.Compare("lookup"))
594 	{
595 		ParseComma();
596 		sc.MustGetString();
597 		dest = sc.String;
598 		return true;
599 	}
600 	else if (sc.String[0] == '$')
601 	{
602 		dest = sc.String+1;
603 		return true;
604 	}
605 	else if (format_type == FMT_Old)
606 	{
607 		dest = sc.String;
608 		return false;
609 	}
610 	else
611 	{
612 		sc.UnGet();
613 		dest = "";
614 		do
615 		{
616 			sc.MustGetString();
617 			dest << sc.String << '\n';
618 		}
619 		while (sc.CheckString(","));
620 		// strip off the last newline
621 		dest.Truncate(long(dest.Len()-1));
622 		return false;
623 	}
624 }
625 
626 //==========================================================================
627 //
628 //
629 //==========================================================================
630 
631 /*
632 void FMapInfoParser::ParseLumpOrTextureName(char *name)
633 {
634 	sc.MustGetString();
635 	uppercopy(name, sc.String);
636 	name[8]=0;
637 }
638 */
639 
ParseLumpOrTextureName(FString & name)640 void FMapInfoParser::ParseLumpOrTextureName(FString &name)
641 {
642 	sc.MustGetString();
643 	name = sc.String;
644 }
645 
646 
647 //==========================================================================
648 //
649 //
650 //==========================================================================
651 
ParseMusic(FString & name,int & order)652 void FMapInfoParser::ParseMusic(FString &name, int &order)
653 {
654 	sc.MustGetString();
655 
656 	order = 0;
657 	char *colon = strchr (sc.String, ':');
658 	if (colon)
659 	{
660 		order = atoi(colon+1);
661 		*colon = 0;
662 	}
663 	name = sc.String;
664 	if (!colon && CheckNumber())
665 	{
666 		order = sc.Number;
667 	}
668 }
669 
670 //==========================================================================
671 //
672 // ParseCluster
673 // Parses a cluster definition
674 //
675 //==========================================================================
676 
ParseCluster()677 void FMapInfoParser::ParseCluster()
678 {
679 	sc.MustGetNumber ();
680 	int clusterindex = FindWadClusterInfo (sc.Number);
681 	if (clusterindex == -1)
682 	{
683 		clusterindex = wadclusterinfos.Reserve(1);
684 	}
685 
686 	cluster_info_t *clusterinfo = &wadclusterinfos[clusterindex];
687 	clusterinfo->Reset();
688 	clusterinfo->cluster = sc.Number;
689 
690 	ParseOpenBrace();
691 
692 	while (sc.GetString())
693 	{
694 		if (sc.Compare("name"))
695 		{
696 			ParseAssign();
697 			if (ParseLookupName(clusterinfo->ClusterName))
698 				clusterinfo->flags |= CLUSTER_LOOKUPCLUSTERNAME;
699 		}
700 		else if (sc.Compare("entertext"))
701 		{
702 			ParseAssign();
703 			if (ParseLookupName(clusterinfo->EnterText))
704 				clusterinfo->flags |= CLUSTER_LOOKUPENTERTEXT;
705 		}
706 		else if (sc.Compare("exittext"))
707 		{
708 			ParseAssign();
709 			if (ParseLookupName(clusterinfo->ExitText))
710 				clusterinfo->flags |= CLUSTER_LOOKUPEXITTEXT;
711 		}
712 		else if (sc.Compare("music"))
713 		{
714 			ParseAssign();
715 			ParseMusic(clusterinfo->MessageMusic, clusterinfo->musicorder);
716 		}
717 		else if (sc.Compare("flat"))
718 		{
719 			ParseAssign();
720 			ParseLumpOrTextureName(clusterinfo->FinaleFlat);
721 		}
722 		else if (sc.Compare("pic"))
723 		{
724 			ParseAssign();
725 			ParseLumpOrTextureName(clusterinfo->FinaleFlat);
726 			clusterinfo->flags |= CLUSTER_FINALEPIC;
727 		}
728 		else if (sc.Compare("hub"))
729 		{
730 			clusterinfo->flags |= CLUSTER_HUB;
731 		}
732 		else if (sc.Compare("cdtrack"))
733 		{
734 			ParseAssign();
735 			sc.MustGetNumber();
736 			clusterinfo->cdtrack = sc.Number;
737 		}
738 		else if (sc.Compare("cdid"))
739 		{
740 			ParseAssign();
741 			sc.MustGetString();
742 			clusterinfo->cdid = strtoul (sc.String, NULL, 16);
743 		}
744 		else if (sc.Compare("entertextislump"))
745 		{
746 			clusterinfo->flags |= CLUSTER_ENTERTEXTINLUMP;
747 		}
748 		else if (sc.Compare("exittextislump"))
749 		{
750 			clusterinfo->flags |= CLUSTER_EXITTEXTINLUMP;
751 		}
752 		else if (!ParseCloseBrace())
753 		{
754 			// Unknown
755 			sc.ScriptMessage("Unknown property '%s' found in map definition\n", sc.String);
756 			SkipToNext();
757 		}
758 		else
759 		{
760 			break;
761 		}
762 	}
763 	CheckEndOfFile("cluster");
764 }
765 
766 
767 //==========================================================================
768 //
769 // ParseNextMap
770 // Parses a next map field
771 //
772 //==========================================================================
773 
ParseNextMap(FString & mapname)774 void FMapInfoParser::ParseNextMap(FString &mapname)
775 {
776 	if (sc.CheckNumber())
777 	{
778 		if (HexenHack)
779 		{
780 			mapname.Format("&wt@%02d", sc.Number);
781 		}
782 		else
783 		{
784 			mapname.Format("MAP%02d", sc.Number);
785 		}
786 	}
787 	else
788 	{
789 		sc.MustGetString();
790 		mapname = sc.String;
791 		FName seq = CheckEndSequence();
792 		if (seq != NAME_None)
793 		{
794 			mapname.Format("enDSeQ%04x", int(seq));
795 		}
796 	}
797 }
798 
799 //==========================================================================
800 //
801 // Map options
802 //
803 //==========================================================================
804 
DEFINE_MAP_OPTION(levelnum,true)805 DEFINE_MAP_OPTION(levelnum, true)
806 {
807 	parse.ParseAssign();
808 	parse.sc.MustGetNumber();
809 	info->levelnum = parse.sc.Number;
810 }
811 
DEFINE_MAP_OPTION(next,true)812 DEFINE_MAP_OPTION(next, true)
813 {
814 	parse.ParseAssign();
815 	parse.ParseNextMap(info->NextMap);
816 }
817 
DEFINE_MAP_OPTION(secretnext,true)818 DEFINE_MAP_OPTION(secretnext, true)
819 {
820 	parse.ParseAssign();
821 	parse.ParseNextMap(info->NextSecretMap);
822 }
823 
DEFINE_MAP_OPTION(secret,true)824 DEFINE_MAP_OPTION(secret, true) // Just an alias for secretnext, for Vavoom compatibility
825 {
826 	parse.ParseAssign();
827 	parse.ParseNextMap(info->NextSecretMap);
828 }
829 
DEFINE_MAP_OPTION(cluster,true)830 DEFINE_MAP_OPTION(cluster, true)
831 {
832 	parse.ParseAssign();
833 	parse.sc.MustGetNumber();
834 	info->cluster = parse.sc.Number;
835 
836 	// If this cluster hasn't been defined yet, add it. This is especially needed
837 	// for Hexen, because it doesn't have clusterdefs. If we don't do this, every
838 	// level on Hexen will sometimes be considered as being on the same hub,
839 	// depending on the check done.
840 	if (FindWadClusterInfo (parse.sc.Number) == -1)
841 	{
842 		unsigned int clusterindex = wadclusterinfos.Reserve(1);
843 		cluster_info_t *clusterinfo = &wadclusterinfos[clusterindex];
844 		clusterinfo->Reset();
845 		clusterinfo->cluster = parse.sc.Number;
846 		if (parse.HexenHack)
847 		{
848 			clusterinfo->flags |= CLUSTER_HUB;
849 		}
850 	}
851 }
852 
DEFINE_MAP_OPTION(sky1,true)853 DEFINE_MAP_OPTION(sky1, true)
854 {
855 	parse.ParseAssign();
856 	parse.ParseLumpOrTextureName(info->SkyPic1);
857 	if (parse.CheckFloat())
858 	{
859 		if (parse.HexenHack)
860 		{
861 			parse.sc.Float /= 256;
862 		}
863 		info->skyspeed1 = float(parse.sc.Float * (35. / 1000.));
864 	}
865 }
866 
DEFINE_MAP_OPTION(sky2,true)867 DEFINE_MAP_OPTION(sky2, true)
868 {
869 	parse.ParseAssign();
870 	parse.ParseLumpOrTextureName(info->SkyPic2);
871 	if (parse.CheckFloat())
872 	{
873 		if (parse.HexenHack)
874 		{
875 			parse.sc.Float /= 256;
876 		}
877 		info->skyspeed2 = float(parse.sc.Float * (35. / 1000.));
878 	}
879 }
880 
881 // Vavoom compatibility
DEFINE_MAP_OPTION(skybox,true)882 DEFINE_MAP_OPTION(skybox, true)
883 {
884 	parse.ParseAssign();
885 	parse.ParseLumpOrTextureName(info->SkyPic1);
886 	info->skyspeed1 = 0;
887 }
888 
DEFINE_MAP_OPTION(fade,true)889 DEFINE_MAP_OPTION(fade, true)
890 {
891 	parse.ParseAssign();
892 	parse.sc.MustGetString();
893 	info->fadeto = V_GetColor(NULL, parse.sc.String);
894 }
895 
DEFINE_MAP_OPTION(outsidefog,true)896 DEFINE_MAP_OPTION(outsidefog, true)
897 {
898 	parse.ParseAssign();
899 	parse.sc.MustGetString();
900 	info->outsidefog = V_GetColor(NULL, parse.sc.String);
901 }
902 
DEFINE_MAP_OPTION(titlepatch,true)903 DEFINE_MAP_OPTION(titlepatch, true)
904 {
905 	parse.ParseAssign();
906 	parse.ParseLumpOrTextureName(info->PName);
907 }
908 
DEFINE_MAP_OPTION(partime,true)909 DEFINE_MAP_OPTION(partime, true)
910 {
911 	parse.ParseAssign();
912 	parse.sc.MustGetNumber();
913 	info->partime = parse.sc.Number;
914 }
915 
DEFINE_MAP_OPTION(par,true)916 DEFINE_MAP_OPTION(par, true)
917 {
918 	parse.ParseAssign();
919 	parse.sc.MustGetNumber();
920 	info->partime = parse.sc.Number;
921 }
922 
DEFINE_MAP_OPTION(sucktime,true)923 DEFINE_MAP_OPTION(sucktime, true)
924 {
925 	parse.ParseAssign();
926 	parse.sc.MustGetNumber();
927 	info->sucktime = parse.sc.Number;
928 }
929 
DEFINE_MAP_OPTION(music,true)930 DEFINE_MAP_OPTION(music, true)
931 {
932 	parse.ParseAssign();
933 	parse.ParseMusic(info->Music, info->musicorder);
934 }
935 
DEFINE_MAP_OPTION(intermusic,true)936 DEFINE_MAP_OPTION(intermusic, true)
937 {
938 	parse.ParseAssign();
939 	parse.ParseMusic(info->InterMusic, info->intermusicorder);
940 }
941 
DEFINE_MAP_OPTION(fadetable,true)942 DEFINE_MAP_OPTION(fadetable, true)
943 {
944 	parse.ParseAssign();
945 	parse.ParseLumpOrTextureName(info->FadeTable);
946 }
947 
DEFINE_MAP_OPTION(evenlighting,true)948 DEFINE_MAP_OPTION(evenlighting, true)
949 {
950 	info->WallVertLight = info->WallHorizLight = 0;
951 }
952 
DEFINE_MAP_OPTION(cdtrack,true)953 DEFINE_MAP_OPTION(cdtrack, true)
954 {
955 	parse.ParseAssign();
956 	parse.sc.MustGetNumber();
957 	info->cdtrack = parse.sc.Number;
958 }
959 
DEFINE_MAP_OPTION(cdid,true)960 DEFINE_MAP_OPTION(cdid, true)
961 {
962 	parse.ParseAssign();
963 	parse.sc.MustGetString();
964 	info->cdid = strtoul (parse.sc.String, NULL, 16);
965 }
966 
DEFINE_MAP_OPTION(warptrans,true)967 DEFINE_MAP_OPTION(warptrans, true)
968 {
969 	parse.ParseAssign();
970 	parse.sc.MustGetNumber();
971 	info->WarpTrans = parse.sc.Number;
972 }
973 
DEFINE_MAP_OPTION(vertwallshade,true)974 DEFINE_MAP_OPTION(vertwallshade, true)
975 {
976 	parse.ParseAssign();
977 	parse.sc.MustGetNumber();
978 	info->WallVertLight = (SBYTE)clamp (parse.sc.Number / 2, -128, 127);
979 }
980 
DEFINE_MAP_OPTION(horizwallshade,true)981 DEFINE_MAP_OPTION(horizwallshade, true)
982 {
983 	parse.ParseAssign();
984 	parse.sc.MustGetNumber();
985 	info->WallHorizLight = (SBYTE)clamp (parse.sc.Number / 2, -128, 127);
986 }
987 
DEFINE_MAP_OPTION(gravity,true)988 DEFINE_MAP_OPTION(gravity, true)
989 {
990 	parse.ParseAssign();
991 	parse.sc.MustGetFloat();
992 	info->gravity = float(parse.sc.Float);
993 }
994 
DEFINE_MAP_OPTION(aircontrol,true)995 DEFINE_MAP_OPTION(aircontrol, true)
996 {
997 	parse.ParseAssign();
998 	parse.sc.MustGetFloat();
999 	info->aircontrol = float(parse.sc.Float);
1000 }
1001 
DEFINE_MAP_OPTION(airsupply,true)1002 DEFINE_MAP_OPTION(airsupply, true)
1003 {
1004 	parse.ParseAssign();
1005 	parse.sc.MustGetNumber();
1006 	info->airsupply = parse.sc.Number;
1007 }
1008 
DEFINE_MAP_OPTION(interpic,true)1009 DEFINE_MAP_OPTION(interpic, true)
1010 {
1011 	parse.ParseAssign();
1012 	parse.sc.MustGetString();
1013 	info->ExitPic = parse.sc.String;
1014 }
1015 
DEFINE_MAP_OPTION(exitpic,true)1016 DEFINE_MAP_OPTION(exitpic, true)
1017 {
1018 	parse.ParseAssign();
1019 	parse.sc.MustGetString();
1020 	info->ExitPic = parse.sc.String;
1021 }
1022 
DEFINE_MAP_OPTION(enterpic,true)1023 DEFINE_MAP_OPTION(enterpic, true)
1024 {
1025 	parse.ParseAssign();
1026 	parse.sc.MustGetString();
1027 	info->EnterPic = parse.sc.String;
1028 }
1029 
DEFINE_MAP_OPTION(specialaction,true)1030 DEFINE_MAP_OPTION(specialaction, true)
1031 {
1032 	parse.ParseAssign();
1033 
1034 	FSpecialAction *sa = &info->specialactions[info->specialactions.Reserve(1)];
1035 	int min_arg, max_arg;
1036 	if (parse.format_type == parse.FMT_Old) parse.sc.SetCMode(true);
1037 	parse.sc.MustGetString();
1038 	sa->Type = FName(parse.sc.String);
1039 	parse.sc.CheckString(",");
1040 	parse.sc.MustGetString();
1041 	sa->Action = P_FindLineSpecial(parse.sc.String, &min_arg, &max_arg);
1042 	if (sa->Action == 0 || min_arg < 0)
1043 	{
1044 		parse.sc.ScriptError("Unknown specialaction '%s'", parse.sc.String);
1045 	}
1046 	int j = 0;
1047 	while (j < 5 && parse.sc.CheckString(","))
1048 	{
1049 		parse.sc.MustGetNumber();
1050 		sa->Args[j++] = parse.sc.Number;
1051 	}
1052 	if (parse.format_type == parse.FMT_Old) parse.sc.SetCMode(false);
1053 }
1054 
DEFINE_MAP_OPTION(PrecacheSounds,true)1055 DEFINE_MAP_OPTION(PrecacheSounds, true)
1056 {
1057 	parse.ParseAssign();
1058 
1059 	do
1060 	{
1061 		parse.sc.MustGetString();
1062 		FSoundID snd = parse.sc.String;
1063 		if (snd == 0)
1064 		{
1065 			parse.sc.ScriptMessage("Unknown sound \"%s\"", parse.sc.String);
1066 		}
1067 		else
1068 		{
1069 			info->PrecacheSounds.Push(snd);
1070 		}
1071 	} while (parse.sc.CheckString(","));
1072 }
1073 
DEFINE_MAP_OPTION(PrecacheTextures,true)1074 DEFINE_MAP_OPTION(PrecacheTextures, true)
1075 {
1076 	parse.ParseAssign();
1077 
1078 	do
1079 	{
1080 		parse.sc.MustGetString();
1081 		//the texture manager is not initialized here so all we can do is store the texture's name.
1082 		info->PrecacheTextures.Push(parse.sc.String);
1083 	} while (parse.sc.CheckString(","));
1084 }
1085 
DEFINE_MAP_OPTION(redirect,true)1086 DEFINE_MAP_OPTION(redirect, true)
1087 {
1088 	parse.ParseAssign();
1089 	parse.sc.MustGetString();
1090 	info->RedirectType = parse.sc.String;
1091 	parse.ParseComma();
1092 	parse.ParseNextMap(info->RedirectMapName);
1093 }
1094 
DEFINE_MAP_OPTION(sndseq,true)1095 DEFINE_MAP_OPTION(sndseq, true)
1096 {
1097 	parse.ParseAssign();
1098 	parse.sc.MustGetString();
1099 	info->SndSeq = parse.sc.String;
1100 }
1101 
DEFINE_MAP_OPTION(sndinfo,true)1102 DEFINE_MAP_OPTION(sndinfo, true)
1103 {
1104 	parse.ParseAssign();
1105 	parse.sc.MustGetString();
1106 	info->SoundInfo = parse.sc.String;
1107 }
1108 
DEFINE_MAP_OPTION(soundinfo,true)1109 DEFINE_MAP_OPTION(soundinfo, true)
1110 {
1111 	parse.ParseAssign();
1112 	parse.sc.MustGetString();
1113 	info->SoundInfo = parse.sc.String;
1114 }
1115 
DEFINE_MAP_OPTION(translator,true)1116 DEFINE_MAP_OPTION(translator, true)
1117 {
1118 	parse.ParseAssign();
1119 	parse.sc.MustGetString();
1120 	info->Translator = parse.sc.String;
1121 }
1122 
DEFINE_MAP_OPTION(deathsequence,false)1123 DEFINE_MAP_OPTION(deathsequence, false)
1124 {
1125 	parse.ParseAssign();
1126 	parse.sc.MustGetString();
1127 	info->deathsequence = parse.sc.String;
1128 }
1129 
DEFINE_MAP_OPTION(slideshow,false)1130 DEFINE_MAP_OPTION(slideshow, false)
1131 {
1132 	parse.ParseAssign();
1133 	parse.sc.MustGetString();
1134 	info->slideshow = parse.sc.String;
1135 }
1136 
DEFINE_MAP_OPTION(bordertexture,true)1137 DEFINE_MAP_OPTION(bordertexture, true)
1138 {
1139 	parse.ParseAssign();
1140 	parse.ParseLumpOrTextureName(info->BorderTexture);
1141 }
1142 
DEFINE_MAP_OPTION(f1,true)1143 DEFINE_MAP_OPTION(f1, true)
1144 {
1145 	parse.ParseAssign();
1146 	parse.ParseLumpOrTextureName(info->F1Pic);
1147 }
1148 
DEFINE_MAP_OPTION(teamdamage,true)1149 DEFINE_MAP_OPTION(teamdamage, true)
1150 {
1151 	parse.ParseAssign();
1152 	parse.sc.MustGetFloat();
1153 	info->teamdamage = float(parse.sc.Float);
1154 }
1155 
DEFINE_MAP_OPTION(mapbackground,true)1156 DEFINE_MAP_OPTION(mapbackground, true)
1157 {
1158 	parse.ParseAssign();
1159 	parse.ParseLumpOrTextureName(info->MapBackground);
1160 }
1161 
DEFINE_MAP_OPTION(defaultenvironment,false)1162 DEFINE_MAP_OPTION(defaultenvironment, false)
1163 {
1164 	int id;
1165 
1166 	parse.ParseAssign();
1167 	if (parse.sc.CheckNumber())
1168 	{ // Numeric ID XXX [, YYY]
1169 		id = parse.sc.Number << 8;
1170 		if (parse.CheckNumber())
1171 		{
1172 			id |= parse.sc.Number;
1173 		}
1174 	}
1175 	else
1176 	{ // Named environment
1177 		parse.sc.MustGetString();
1178 		ReverbContainer *reverb = S_FindEnvironment(parse.sc.String);
1179 		if (reverb == NULL)
1180 		{
1181 			parse.sc.ScriptMessage("Unknown sound environment '%s'\n", parse.sc.String);
1182 			id = 0;
1183 		}
1184 		else
1185 		{
1186 			id = reverb->ID;
1187 		}
1188 	}
1189 	info->DefaultEnvironment = id;
1190 }
1191 
1192 
1193 //==========================================================================
1194 //
1195 // All flag based map options
1196 //
1197 //==========================================================================
1198 
1199 enum EMIType
1200 {
1201 	MITYPE_IGNORE,
1202 	MITYPE_EATNEXT,
1203 	MITYPE_SETFLAG,
1204 	MITYPE_CLRFLAG,
1205 	MITYPE_SCFLAGS,
1206 	MITYPE_SETFLAG2,
1207 	MITYPE_CLRFLAG2,
1208 	MITYPE_SCFLAGS2,
1209 	MITYPE_SETFLAG3,
1210 	MITYPE_CLRFLAG3,
1211 	MITYPE_SCFLAGS3,
1212 	MITYPE_COMPATFLAG,
1213 };
1214 
1215 struct MapInfoFlagHandler
1216 {
1217 	const char *name;
1218 	EMIType type;
1219 	DWORD data1, data2;
1220 }
1221 MapFlagHandlers[] =
1222 {
1223 	{ "nointermission",					MITYPE_SETFLAG,	LEVEL_NOINTERMISSION, 0 },
1224 	{ "intermission",					MITYPE_CLRFLAG,	LEVEL_NOINTERMISSION, 0 },
1225 	{ "doublesky",						MITYPE_SETFLAG,	LEVEL_DOUBLESKY, 0 },
1226 	{ "nosoundclipping",				MITYPE_IGNORE,	0, 0 },	// was nosoundclipping
1227 	{ "allowmonstertelefrags",			MITYPE_SETFLAG,	LEVEL_MONSTERSTELEFRAG, 0 },
1228 	{ "map07special",					MITYPE_SETFLAG,	LEVEL_MAP07SPECIAL, 0 },
1229 	{ "baronspecial",					MITYPE_SETFLAG,	LEVEL_BRUISERSPECIAL, 0 },
1230 	{ "cyberdemonspecial",				MITYPE_SETFLAG,	LEVEL_CYBORGSPECIAL, 0 },
1231 	{ "spidermastermindspecial",		MITYPE_SETFLAG,	LEVEL_SPIDERSPECIAL, 0 },
1232 	{ "minotaurspecial",				MITYPE_SETFLAG,	LEVEL_MINOTAURSPECIAL, 0 },
1233 	{ "dsparilspecial",					MITYPE_SETFLAG,	LEVEL_SORCERER2SPECIAL, 0 },
1234 	{ "ironlichspecial",				MITYPE_SETFLAG,	LEVEL_HEADSPECIAL, 0 },
1235 	{ "specialaction_exitlevel",		MITYPE_SCFLAGS,	0, ~LEVEL_SPECACTIONSMASK },
1236 	{ "specialaction_opendoor",			MITYPE_SCFLAGS,	LEVEL_SPECOPENDOOR, ~LEVEL_SPECACTIONSMASK },
1237 	{ "specialaction_lowerfloor",		MITYPE_SCFLAGS,	LEVEL_SPECLOWERFLOOR, ~LEVEL_SPECACTIONSMASK },
1238 	{ "specialaction_lowerfloortohighest",MITYPE_SCFLAGS,LEVEL_SPECLOWERFLOORTOHIGHEST, ~LEVEL_SPECACTIONSMASK },
1239 	{ "specialaction_killmonsters",		MITYPE_SETFLAG,	LEVEL_SPECKILLMONSTERS, 0 },
1240 	{ "lightning",						MITYPE_SETFLAG,	LEVEL_STARTLIGHTNING, 0 },
1241 	{ "smoothlighting",					MITYPE_SETFLAG2,	LEVEL2_SMOOTHLIGHTING, 0 },
1242 	{ "noautosequences",				MITYPE_SETFLAG,	LEVEL_SNDSEQTOTALCTRL, 0 },
1243 	{ "autosequences",					MITYPE_CLRFLAG,	LEVEL_SNDSEQTOTALCTRL, 0 },
1244 	{ "forcenoskystretch",				MITYPE_SETFLAG,	LEVEL_FORCENOSKYSTRETCH, 0 },
1245 	{ "skystretch",						MITYPE_CLRFLAG,	LEVEL_FORCENOSKYSTRETCH, 0 },
1246 	{ "allowfreelook",					MITYPE_SCFLAGS,	LEVEL_FREELOOK_YES, ~LEVEL_FREELOOK_NO },
1247 	{ "nofreelook",						MITYPE_SCFLAGS,	LEVEL_FREELOOK_NO, ~LEVEL_FREELOOK_YES },
1248 	{ "allowjump",						MITYPE_CLRFLAG,	LEVEL_JUMP_NO, 0 },
1249 	{ "nojump",							MITYPE_SETFLAG,	LEVEL_JUMP_NO, 0 },
1250 	{ "fallingdamage",					MITYPE_SCFLAGS,	LEVEL_FALLDMG_HX, ~LEVEL_FALLDMG_ZD },
1251 	{ "oldfallingdamage",				MITYPE_SCFLAGS,	LEVEL_FALLDMG_ZD, ~LEVEL_FALLDMG_HX },
1252 	{ "forcefallingdamage",				MITYPE_SCFLAGS,	LEVEL_FALLDMG_ZD, ~LEVEL_FALLDMG_HX },
1253 	{ "strifefallingdamage",			MITYPE_SETFLAG,	LEVEL_FALLDMG_ZD|LEVEL_FALLDMG_HX, 0 },
1254 	{ "nofallingdamage",				MITYPE_SCFLAGS,	0, ~(LEVEL_FALLDMG_ZD|LEVEL_FALLDMG_HX) },
1255 	{ "noallies",						MITYPE_SETFLAG,	LEVEL_NOALLIES, 0 },
1256 	{ "filterstarts",					MITYPE_SETFLAG,	LEVEL_FILTERSTARTS, 0 },
1257 	{ "useplayerstartz",				MITYPE_SETFLAG, LEVEL_USEPLAYERSTARTZ, 0 },
1258 	{ "randomplayerstarts",				MITYPE_SETFLAG2, LEVEL2_RANDOMPLAYERSTARTS, 0 },
1259 	{ "activateowndeathspecials",		MITYPE_SETFLAG,	LEVEL_ACTOWNSPECIAL, 0 },
1260 	{ "killeractivatesdeathspecials",	MITYPE_CLRFLAG,	LEVEL_ACTOWNSPECIAL, 0 },
1261 	{ "missilesactivateimpactlines",	MITYPE_SETFLAG2,	LEVEL2_MISSILESACTIVATEIMPACT, 0 },
1262 	{ "missileshootersactivetimpactlines",MITYPE_CLRFLAG2,	LEVEL2_MISSILESACTIVATEIMPACT, 0 },
1263 	{ "noinventorybar",					MITYPE_SETFLAG,	LEVEL_NOINVENTORYBAR, 0 },
1264 	{ "deathslideshow",					MITYPE_IGNORE,		0, 0 },
1265 	{ "strictmonsteractivation",		MITYPE_CLRFLAG2,	LEVEL2_LAXMONSTERACTIVATION, LEVEL2_LAXACTIVATIONMAPINFO },
1266 	{ "laxmonsteractivation",			MITYPE_SETFLAG2,	LEVEL2_LAXMONSTERACTIVATION, LEVEL2_LAXACTIVATIONMAPINFO },
1267 	{ "additive_scrollers",				MITYPE_COMPATFLAG, COMPATF_BOOMSCROLL, 0 },
1268 	{ "keepfullinventory",				MITYPE_SETFLAG2,	LEVEL2_KEEPFULLINVENTORY, 0 },
1269 	{ "monsterfallingdamage",			MITYPE_SETFLAG2,	LEVEL2_MONSTERFALLINGDAMAGE, 0 },
1270 	{ "nomonsterfallingdamage",			MITYPE_CLRFLAG2,	LEVEL2_MONSTERFALLINGDAMAGE, 0 },
1271 	{ "clipmidtextures",				MITYPE_SETFLAG2,	LEVEL2_CLIPMIDTEX, 0 },
1272 	{ "wrapmidtextures",				MITYPE_SETFLAG2,	LEVEL2_WRAPMIDTEX, 0 },
1273 	{ "allowcrouch",					MITYPE_CLRFLAG,	LEVEL_CROUCH_NO, 0 },
1274 	{ "nocrouch",						MITYPE_SETFLAG,	LEVEL_CROUCH_NO, 0 },
1275 	{ "pausemusicinmenus",				MITYPE_SCFLAGS2,	LEVEL2_PAUSE_MUSIC_IN_MENUS, 0 },
1276 	{ "noinfighting",					MITYPE_SCFLAGS2,	LEVEL2_NOINFIGHTING, ~LEVEL2_TOTALINFIGHTING },
1277 	{ "normalinfighting",				MITYPE_SCFLAGS2,	0, ~(LEVEL2_NOINFIGHTING|LEVEL2_TOTALINFIGHTING)},
1278 	{ "totalinfighting",				MITYPE_SCFLAGS2,	LEVEL2_TOTALINFIGHTING, ~LEVEL2_NOINFIGHTING },
1279 	{ "infiniteflightpowerup",			MITYPE_SETFLAG2,	LEVEL2_INFINITE_FLIGHT, 0 },
1280 	{ "noinfiniteflightpowerup",		MITYPE_CLRFLAG2,	LEVEL2_INFINITE_FLIGHT, 0 },
1281 	{ "allowrespawn",					MITYPE_SETFLAG2,	LEVEL2_ALLOWRESPAWN, 0 },
1282 	{ "teamplayon",						MITYPE_SCFLAGS2,	LEVEL2_FORCETEAMPLAYON, ~LEVEL2_FORCETEAMPLAYOFF },
1283 	{ "teamplayoff",					MITYPE_SCFLAGS2,	LEVEL2_FORCETEAMPLAYOFF, ~LEVEL2_FORCETEAMPLAYON },
1284 	{ "checkswitchrange",				MITYPE_SETFLAG2,	LEVEL2_CHECKSWITCHRANGE, 0 },
1285 	{ "nocheckswitchrange",				MITYPE_CLRFLAG2,	LEVEL2_CHECKSWITCHRANGE, 0 },
1286 	{ "grinding_polyobj",				MITYPE_SETFLAG2,	LEVEL2_POLYGRIND, 0 },
1287 	{ "no_grinding_polyobj",			MITYPE_CLRFLAG2,	LEVEL2_POLYGRIND, 0 },
1288 	{ "resetinventory",					MITYPE_SETFLAG2,	LEVEL2_RESETINVENTORY, 0 },
1289 	{ "resethealth",					MITYPE_SETFLAG2,	LEVEL2_RESETHEALTH, 0 },
1290 	{ "endofgame",						MITYPE_SETFLAG2,	LEVEL2_ENDGAME, 0 },
1291 	{ "nostatistics",					MITYPE_SETFLAG2,	LEVEL2_NOSTATISTICS, 0 },
1292 	{ "noautosavehint",					MITYPE_SETFLAG2,	LEVEL2_NOAUTOSAVEHINT, 0 },
1293 	{ "forgetstate",					MITYPE_SETFLAG2,	LEVEL2_FORGETSTATE, 0 },
1294 	{ "rememberstate",					MITYPE_CLRFLAG2,	LEVEL2_FORGETSTATE, 0 },
1295 	{ "unfreezesingleplayerconversations",MITYPE_SETFLAG2,	LEVEL2_CONV_SINGLE_UNFREEZE, 0 },
1296 	{ "spawnwithweaponraised",			MITYPE_SETFLAG2,	LEVEL2_PRERAISEWEAPON, 0 },
1297 	{ "forcefakecontrast",				MITYPE_SETFLAG3,	LEVEL3_FORCEFAKECONTRAST, 0 },
1298 	{ "nobotnodes",						MITYPE_IGNORE,	0, 0 },		// Skulltag option: nobotnodes
1299 	{ "compat_shorttex",				MITYPE_COMPATFLAG, COMPATF_SHORTTEX, 0 },
1300 	{ "compat_stairs",					MITYPE_COMPATFLAG, COMPATF_STAIRINDEX, 0 },
1301 	{ "compat_limitpain",				MITYPE_COMPATFLAG, COMPATF_LIMITPAIN, 0 },
1302 	{ "compat_nopassover",				MITYPE_COMPATFLAG, COMPATF_NO_PASSMOBJ, 0 },
1303 	{ "compat_notossdrops",				MITYPE_COMPATFLAG, COMPATF_NOTOSSDROPS, 0 },
1304 	{ "compat_useblocking", 			MITYPE_COMPATFLAG, COMPATF_USEBLOCKING, 0 },
1305 	{ "compat_nodoorlight",				MITYPE_COMPATFLAG, COMPATF_NODOORLIGHT, 0 },
1306 	{ "compat_ravenscroll",				MITYPE_COMPATFLAG, COMPATF_RAVENSCROLL, 0 },
1307 	{ "compat_soundtarget",				MITYPE_COMPATFLAG, COMPATF_SOUNDTARGET, 0 },
1308 	{ "compat_dehhealth",				MITYPE_COMPATFLAG, COMPATF_DEHHEALTH, 0 },
1309 	{ "compat_trace",					MITYPE_COMPATFLAG, COMPATF_TRACE, 0 },
1310 	{ "compat_dropoff",					MITYPE_COMPATFLAG, COMPATF_DROPOFF, 0 },
1311 	{ "compat_boomscroll",				MITYPE_COMPATFLAG, COMPATF_BOOMSCROLL, 0 },
1312 	{ "compat_invisibility",			MITYPE_COMPATFLAG, COMPATF_INVISIBILITY, 0 },
1313 	{ "compat_silent_instant_floors",	MITYPE_COMPATFLAG, COMPATF_SILENT_INSTANT_FLOORS, 0 },
1314 	{ "compat_sectorsounds",			MITYPE_COMPATFLAG, COMPATF_SECTORSOUNDS, 0 },
1315 	{ "compat_missileclip",				MITYPE_COMPATFLAG, COMPATF_MISSILECLIP, 0 },
1316 	{ "compat_crossdropoff",			MITYPE_COMPATFLAG, COMPATF_CROSSDROPOFF, 0 },
1317 	{ "compat_anybossdeath",			MITYPE_COMPATFLAG, COMPATF_ANYBOSSDEATH, 0 },
1318 	{ "compat_minotaur",				MITYPE_COMPATFLAG, COMPATF_MINOTAUR, 0 },
1319 	{ "compat_mushroom",				MITYPE_COMPATFLAG, COMPATF_MUSHROOM, 0 },
1320 	{ "compat_mbfmonstermove",			MITYPE_COMPATFLAG, COMPATF_MBFMONSTERMOVE, 0 },
1321 	{ "compat_corpsegibs",				MITYPE_COMPATFLAG, COMPATF_CORPSEGIBS, 0 },
1322 	{ "compat_noblockfriends",			MITYPE_COMPATFLAG, COMPATF_NOBLOCKFRIENDS, 0 },
1323 	{ "compat_spritesort",				MITYPE_COMPATFLAG, COMPATF_SPRITESORT, 0 },
1324 	{ "compat_light",					MITYPE_COMPATFLAG, COMPATF_LIGHT, 0 },
1325 	{ "compat_polyobj",					MITYPE_COMPATFLAG, COMPATF_POLYOBJ, 0 },
1326 	{ "compat_maskedmidtex",			MITYPE_COMPATFLAG, COMPATF_MASKEDMIDTEX, 0 },
1327 	{ "compat_badangles",				MITYPE_COMPATFLAG, 0, COMPATF2_BADANGLES },
1328 	{ "compat_floormove",				MITYPE_COMPATFLAG, 0, COMPATF2_FLOORMOVE },
1329 	{ "compat_soundcutoff",				MITYPE_COMPATFLAG, 0, COMPATF2_SOUNDCUTOFF },
1330 	{ "compat_pointonline",				MITYPE_COMPATFLAG, 0, COMPATF2_POINTONLINE },
1331 	{ "cd_start_track",					MITYPE_EATNEXT,	0, 0 },
1332 	{ "cd_end1_track",					MITYPE_EATNEXT,	0, 0 },
1333 	{ "cd_end2_track",					MITYPE_EATNEXT,	0, 0 },
1334 	{ "cd_end3_track",					MITYPE_EATNEXT,	0, 0 },
1335 	{ "cd_intermission_track",			MITYPE_EATNEXT,	0, 0 },
1336 	{ "cd_title_track",					MITYPE_EATNEXT,	0, 0 },
1337 	{ NULL, MITYPE_IGNORE, 0, 0}
1338 };
1339 
1340 //==========================================================================
1341 //
1342 // ParseMapDefinition
1343 // Parses the body of a map definition, including defaultmap etc.
1344 //
1345 //==========================================================================
1346 
ParseMapDefinition(level_info_t & info)1347 void FMapInfoParser::ParseMapDefinition(level_info_t &info)
1348 {
1349 	int index;
1350 
1351 	ParseOpenBrace();
1352 
1353 	while (sc.GetString())
1354 	{
1355 		if ((index = sc.MatchString(&MapFlagHandlers->name, sizeof(*MapFlagHandlers))) >= 0)
1356 		{
1357 			MapInfoFlagHandler *handler = &MapFlagHandlers[index];
1358 			switch (handler->type)
1359 			{
1360 			case MITYPE_EATNEXT:
1361 				ParseAssign();
1362 				sc.MustGetString();
1363 				break;
1364 
1365 			case MITYPE_IGNORE:
1366 				break;
1367 
1368 			case MITYPE_SETFLAG:
1369 				info.flags |= handler->data1;
1370 				info.flags |= handler->data2;
1371 				break;
1372 
1373 			case MITYPE_CLRFLAG:
1374 				info.flags &= ~handler->data1;
1375 				info.flags |= handler->data2;
1376 				break;
1377 
1378 			case MITYPE_SCFLAGS:
1379 				info.flags = (info.flags & handler->data2) | handler->data1;
1380 				break;
1381 
1382 			case MITYPE_SETFLAG2:
1383 				info.flags2 |= handler->data1;
1384 				info.flags2 |= handler->data2;
1385 				break;
1386 
1387 			case MITYPE_CLRFLAG2:
1388 				info.flags2 &= ~handler->data1;
1389 				info.flags2 |= handler->data2;
1390 				break;
1391 
1392 			case MITYPE_SCFLAGS2:
1393 				info.flags2 = (info.flags2 & handler->data2) | handler->data1;
1394 				break;
1395 
1396 			case MITYPE_SETFLAG3:
1397 				info.flags3 |= handler->data1;
1398 				info.flags3 |= handler->data2;
1399 				break;
1400 
1401 			case MITYPE_CLRFLAG3:
1402 				info.flags3 &= ~handler->data1;
1403 				info.flags3 |= handler->data2;
1404 				break;
1405 
1406 			case MITYPE_SCFLAGS3:
1407 				info.flags3 = (info.flags3 & handler->data2) | handler->data1;
1408 				break;
1409 
1410 			case MITYPE_COMPATFLAG:
1411 			{
1412 				int set = 1;
1413 				if (format_type == FMT_New)
1414 				{
1415 					if (CheckAssign())
1416 					{
1417 						sc.MustGetNumber();
1418 						set = sc.Number;
1419 					}
1420 				}
1421 				else
1422 				{
1423 					if (sc.CheckNumber()) set = sc.Number;
1424 				}
1425 
1426 				if (set)
1427 				{
1428 					info.compatflags |= handler->data1;
1429 					info.compatflags2 |= handler->data2;
1430 				}
1431 				else
1432 				{
1433 					info.compatflags &= ~handler->data1;
1434 					info.compatflags2 &= ~handler->data2;
1435 				}
1436 				info.compatmask |= handler->data1;
1437 				info.compatmask2 |= handler->data2;
1438 			}
1439 			break;
1440 
1441 			default:
1442 				// should never happen
1443 				assert(false);
1444 				break;
1445 			}
1446 		}
1447 		else
1448 		{
1449 			FAutoSegIterator probe(YRegHead, YRegTail);
1450 			bool success = false;
1451 
1452 			while (*++probe != NULL)
1453 			{
1454 				if (sc.Compare(((FMapOptInfo *)(*probe))->name))
1455 				{
1456 					if (!((FMapOptInfo *)(*probe))->old && format_type != FMT_New)
1457 					{
1458 						sc.ScriptError("MAPINFO option '%s' requires the new MAPINFO format", sc.String);
1459 					}
1460 					((FMapOptInfo *)(*probe))->handler(*this, &info);
1461 					success = true;
1462 					break;
1463 				}
1464 			}
1465 
1466 			if (!success)
1467 			{
1468 				if (!ParseCloseBrace())
1469 				{
1470 					// Unknown
1471 					sc.ScriptMessage("Unknown property '%s' found in map definition\n", sc.String);
1472 					SkipToNext();
1473 				}
1474 				else
1475 				{
1476 					break;
1477 				}
1478 			}
1479 		}
1480 	}
1481 	CheckEndOfFile("map");
1482 }
1483 
1484 
1485 //==========================================================================
1486 //
1487 // GetDefaultLevelNum
1488 // Gets a default level num from a map name.
1489 //
1490 //==========================================================================
1491 
GetDefaultLevelNum(const char * mapname)1492 static int GetDefaultLevelNum(const char *mapname)
1493 {
1494 	if (!strnicmp (mapname, "MAP", 3) && strlen(mapname) <= 5)
1495 	{
1496 		int mapnum = atoi (mapname + 3);
1497 
1498 		if (mapnum >= 1 && mapnum <= 99)
1499 			return mapnum;
1500 	}
1501 	else if (mapname[0] == 'E' &&
1502 			mapname[1] >= '0' && mapname[1] <= '9' &&
1503 			mapname[2] == 'M' &&
1504 			mapname[3] >= '0' && mapname[3] <= '9')
1505 	{
1506 		int epinum = mapname[1] - '1';
1507 		int mapnum = mapname[3] - '0';
1508 		return epinum*10 + mapnum;
1509 	}
1510 	return 0;
1511 }
1512 
1513 //==========================================================================
1514 //
1515 // ParseMapHeader
1516 // Parses the header of a map definition ('map mapxx mapname')
1517 //
1518 //==========================================================================
1519 
ParseMapHeader(level_info_t & defaultinfo)1520 level_info_t *FMapInfoParser::ParseMapHeader(level_info_t &defaultinfo)
1521 {
1522 	FName mapname;
1523 
1524 	if (sc.CheckNumber())
1525 	{	// MAPNAME is a number; assume a Hexen wad
1526 		char maptemp[8];
1527 		mysnprintf (maptemp, countof(maptemp), "MAP%02d", sc.Number);
1528 		mapname = maptemp;
1529 		HexenHack = true;
1530 	}
1531 	else
1532 	{
1533 		sc.MustGetString();
1534 		mapname = sc.String;
1535 	}
1536 	int levelindex = FindWadLevelInfo (mapname);
1537 	if (levelindex == -1)
1538 	{
1539 		levelindex = wadlevelinfos.Reserve(1);
1540 	}
1541 	level_info_t *levelinfo = &wadlevelinfos[levelindex];
1542 	*levelinfo = defaultinfo;
1543 	if (HexenHack)
1544 	{
1545 		levelinfo->WallHorizLight = levelinfo->WallVertLight = 0;
1546 
1547 		// Hexen levels are automatically nointermission,
1548 		// no auto sound sequences, falling damage,
1549 		// monsters activate their own specials, and missiles
1550 		// are always the activators of impact lines.
1551 		levelinfo->flags |= LEVEL_NOINTERMISSION
1552 						 | LEVEL_SNDSEQTOTALCTRL
1553 						 | LEVEL_FALLDMG_HX
1554 						 | LEVEL_ACTOWNSPECIAL;
1555 		levelinfo->flags2|= LEVEL2_HEXENHACK
1556 						 | LEVEL2_INFINITE_FLIGHT
1557 						 | LEVEL2_MISSILESACTIVATEIMPACT
1558 						 | LEVEL2_MONSTERFALLINGDAMAGE;
1559 
1560 	}
1561 
1562 	levelinfo->MapName = mapname;
1563 	levelinfo->MapName.ToUpper();
1564 	sc.MustGetString ();
1565 	if (sc.String[0] == '$')
1566 	{
1567 		// For consistency with other definitions allow $Stringtablename here, too.
1568 		levelinfo->flags |= LEVEL_LOOKUPLEVELNAME;
1569 		levelinfo->LevelName = sc.String + 1;
1570 	}
1571 	else
1572 	{
1573 		if (sc.Compare ("lookup"))
1574 		{
1575 			sc.MustGetString ();
1576 			levelinfo->flags |= LEVEL_LOOKUPLEVELNAME;
1577 		}
1578 		levelinfo->LevelName = sc.String;
1579 	}
1580 
1581 	// Set up levelnum now so that you can use Teleport_NewMap specials
1582 	// to teleport to maps with standard names without needing a levelnum.
1583 	levelinfo->levelnum = GetDefaultLevelNum(levelinfo->MapName);
1584 
1585 	// Does this map have a song defined via SNDINFO's $map command?
1586 	// Set that as this map's default music if it does.
1587 	FString *song;
1588 	if ((song = HexenMusic.CheckKey(levelinfo->levelnum)) != NULL)
1589 	{
1590 		levelinfo->Music = *song;
1591 	}
1592 
1593 	return levelinfo;
1594 }
1595 
1596 
1597 //==========================================================================
1598 //
1599 // Episode definitions start with the header "episode <start-map>"
1600 // and then can be followed by any of the following:
1601 //
1602 // name "Episode name as text"
1603 // picname "Picture to display the episode name"
1604 // key "Shortcut key for the menu"
1605 // noskillmenu
1606 // remove
1607 //
1608 //==========================================================================
1609 
ParseEpisodeInfo()1610 void FMapInfoParser::ParseEpisodeInfo ()
1611 {
1612 	unsigned int i;
1613 	FString map;
1614 	FString pic;
1615 	FString name;
1616 	bool remove = false;
1617 	char key = 0;
1618 	bool noskill = false;
1619 	bool optional = false;
1620 	bool extended = false;
1621 
1622 	// Get map name
1623 	sc.MustGetString ();
1624 	map = sc.String;
1625 
1626 	if (sc.CheckString ("teaser"))
1627 	{
1628 		sc.MustGetString ();
1629 		if (gameinfo.flags & GI_SHAREWARE)
1630 		{
1631 			map = sc.String;
1632 		}
1633 	}
1634 
1635 	ParseOpenBrace();
1636 
1637 	while (sc.GetString())
1638 	{
1639 		if (sc.Compare ("optional"))
1640 		{
1641 			// For M4 in Doom
1642 			optional = true;
1643 		}
1644 		else if (sc.Compare ("extended"))
1645 		{
1646 			// For M4 and M5 in Heretic
1647 			extended = true;
1648 		}
1649 		else if (sc.Compare ("name"))
1650 		{
1651 			ParseAssign();
1652 			sc.MustGetString ();
1653 			name = sc.String;
1654 		}
1655 		else if (sc.Compare ("picname"))
1656 		{
1657 			ParseAssign();
1658 			sc.MustGetString ();
1659 			pic = sc.String;
1660 		}
1661 		else if (sc.Compare ("remove"))
1662 		{
1663 			remove = true;
1664 		}
1665 		else if (sc.Compare ("key"))
1666 		{
1667 			ParseAssign();
1668 			sc.MustGetString ();
1669 			key = sc.String[0];
1670 		}
1671 		else if (sc.Compare("noskillmenu"))
1672 		{
1673 			noskill = true;
1674 		}
1675 		else if (!ParseCloseBrace())
1676 		{
1677 			// Unknown
1678 			sc.ScriptMessage("Unknown property '%s' found in episode definition\n", sc.String);
1679 			SkipToNext();
1680 		}
1681 		else
1682 		{
1683 			break;
1684 		}
1685 	}
1686 	CheckEndOfFile("episode");
1687 
1688 	if (extended && !(gameinfo.flags & GI_MENUHACK_EXTENDED))
1689 	{ // If the episode is for the extended Heretic, but this is
1690 	  // not the extended Heretic, ignore it.
1691 		return;
1692 	}
1693 
1694 	if (optional && !remove)
1695 	{
1696 		if (!P_CheckMapData(map))
1697 		{
1698 			// If the episode is optional and the map does not exist
1699 			// just ignore this episode definition.
1700 			return;
1701 		}
1702 	}
1703 
1704 
1705 	for (i = 0; i < AllEpisodes.Size(); i++)
1706 	{
1707 		if (AllEpisodes[i].mEpisodeMap.CompareNoCase(map) == 0)
1708 		{
1709 			break;
1710 		}
1711 	}
1712 
1713 	if (remove)
1714 	{
1715 		// If the remove property is given for an episode, remove it.
1716 		AllEpisodes.Delete(i);
1717 	}
1718 	else
1719 	{
1720 		// Only allocate a new entry if this doesn't replace an existing episode.
1721 		if (i >= AllEpisodes.Size())
1722 		{
1723 			i = AllEpisodes.Reserve(1);
1724 		}
1725 
1726 		FEpisode *epi = &AllEpisodes[i];
1727 
1728 		epi->mEpisodeMap = map;
1729 		epi->mEpisodeName = name;
1730 		epi->mPicName = pic;
1731 		epi->mShortcut = tolower(key);
1732 		epi->mNoSkill = noskill;
1733 	}
1734 }
1735 
1736 
1737 //==========================================================================
1738 //
1739 // Clears episode definitions
1740 //
1741 //==========================================================================
1742 
ClearEpisodes()1743 void ClearEpisodes()
1744 {
1745 	AllEpisodes.Clear();
1746 }
1747 
1748 //==========================================================================
1749 //
1750 // SetLevelNum
1751 // Avoid duplicate levelnums. The level being set always has precedence.
1752 //
1753 //==========================================================================
1754 
SetLevelNum(level_info_t * info,int num)1755 static void SetLevelNum (level_info_t *info, int num)
1756 {
1757 	for (unsigned int i = 0; i < wadlevelinfos.Size(); ++i)
1758 	{
1759 		if (wadlevelinfos[i].levelnum == num)
1760 			wadlevelinfos[i].levelnum = 0;
1761 	}
1762 	info->levelnum = num;
1763 }
1764 
1765 //==========================================================================
1766 //
1767 // G_DoParseMapInfo
1768 // Parses a single MAPINFO lump
1769 // data for wadlevelinfos and wadclusterinfos.
1770 //
1771 //==========================================================================
1772 
ParseMapInfo(int lump,level_info_t & gamedefaults,level_info_t & defaultinfo)1773 void FMapInfoParser::ParseMapInfo (int lump, level_info_t &gamedefaults, level_info_t &defaultinfo)
1774 {
1775 	sc.OpenLumpNum(lump);
1776 
1777 	defaultinfo = gamedefaults;
1778 	HexenHack = false;
1779 
1780 	while (sc.GetString ())
1781 	{
1782 		if (sc.Compare("include"))
1783 		{
1784 			sc.MustGetString();
1785 			int inclump = Wads.CheckNumForFullName(sc.String, true);
1786 			if (inclump < 0)
1787 			{
1788 				sc.ScriptError("include file '%s' not found", sc.String);
1789 			}
1790 			if (Wads.GetLumpFile(sc.LumpNum) != Wads.GetLumpFile(inclump))
1791 			{
1792 				// Do not allow overriding includes from the default MAPINFO
1793 				if (Wads.GetLumpFile(sc.LumpNum) == 0)
1794 				{
1795 					I_FatalError("File %s is overriding core lump %s.",
1796 						Wads.GetWadFullName(Wads.GetLumpFile(inclump)), sc.String);
1797 				}
1798 			}
1799 			FScanner saved_sc = sc;
1800 			ParseMapInfo(inclump, gamedefaults, defaultinfo);
1801 			sc = saved_sc;
1802 		}
1803 		else if (sc.Compare("gamedefaults"))
1804 		{
1805 			gamedefaults.Reset();
1806 			ParseMapDefinition(gamedefaults);
1807 			defaultinfo = gamedefaults;
1808 		}
1809 		else if (sc.Compare("defaultmap"))
1810 		{
1811 			defaultinfo = gamedefaults;
1812 			ParseMapDefinition(defaultinfo);
1813 		}
1814 		else if (sc.Compare("adddefaultmap"))
1815 		{
1816 			// Same as above but adds to the existing definitions instead of replacing them completely
1817 			ParseMapDefinition(defaultinfo);
1818 		}
1819 		else if (sc.Compare("map"))
1820 		{
1821 			level_info_t *levelinfo = ParseMapHeader(defaultinfo);
1822 
1823 			ParseMapDefinition(*levelinfo);
1824 
1825 			// When the second sky is -NOFLAT-, make it a copy of the first sky
1826 			if (!levelinfo->SkyPic2.CompareNoCase("-NOFLAT-"))
1827 			{
1828 				levelinfo->SkyPic2 = levelinfo->SkyPic1;
1829 			}
1830 			SetLevelNum (levelinfo, levelinfo->levelnum);	// Wipe out matching levelnums from other maps.
1831 		}
1832 		// clusterdef is the old keyword but the new format has enough
1833 		// structuring that 'cluster' can be handled, too. The old format does not.
1834 		else if (sc.Compare("clusterdef") || (format_type != FMT_Old && sc.Compare("cluster")))
1835 		{
1836 			ParseCluster();
1837 		}
1838 		else if (sc.Compare("episode"))
1839 		{
1840 			ParseEpisodeInfo();
1841 		}
1842 		else if (sc.Compare("clearepisodes"))
1843 		{
1844 			ClearEpisodes();
1845 		}
1846 		else if (sc.Compare("skill"))
1847 		{
1848 			ParseSkill();
1849 		}
1850 		else if (sc.Compare("clearskills"))
1851 		{
1852 			AllSkills.Clear();
1853 			DefaultSkill = -1;
1854 		}
1855 		else if (sc.Compare("gameinfo"))
1856 		{
1857 			if (format_type != FMT_Old)
1858 			{
1859 				format_type = FMT_New;
1860 				ParseGameInfo();
1861 			}
1862 			else
1863 			{
1864 				sc.ScriptError("gameinfo definitions not supported with old MAPINFO syntax");
1865 			}
1866 		}
1867 		else if (sc.Compare("intermission"))
1868 		{
1869 			if (format_type != FMT_Old)
1870 			{
1871 				format_type = FMT_New;
1872 				ParseIntermission();
1873 			}
1874 			else
1875 			{
1876 				sc.ScriptError("intermission definitions not supported with old MAPINFO syntax");
1877 			}
1878 		}
1879 		else if (sc.Compare("doomednums"))
1880 		{
1881 			if (format_type != FMT_Old)
1882 			{
1883 				format_type = FMT_New;
1884 				ParseDoomEdNums();
1885 			}
1886 			else
1887 			{
1888 				sc.ScriptError("doomednums definitions not supported with old MAPINFO syntax");
1889 			}
1890 		}
1891 		else if (sc.Compare("spawnnums"))
1892 		{
1893 			if (format_type != FMT_Old)
1894 			{
1895 				format_type = FMT_New;
1896 				ParseSpawnNums();
1897 			}
1898 			else
1899 			{
1900 				sc.ScriptError("spawnnums definitions not supported with old MAPINFO syntax");
1901 			}
1902 		}
1903 		else if (sc.Compare("conversationids"))
1904 		{
1905 			if (format_type != FMT_Old)
1906 			{
1907 				format_type = FMT_New;
1908 				ParseConversationIDs();
1909 			}
1910 			else
1911 			{
1912 				sc.ScriptError("conversationids definitions not supported with old MAPINFO syntax");
1913 			}
1914 		}
1915 		else if (sc.Compare("automap") || sc.Compare("automap_overlay"))
1916 		{
1917 			if (format_type != FMT_Old)
1918 			{
1919 				format_type = FMT_New;
1920 				ParseAMColors(sc.Compare("automap_overlay"));
1921 			}
1922 			else
1923 			{
1924 				sc.ScriptError("automap colorset definitions not supported with old MAPINFO syntax");
1925 			}
1926 		}
1927 		else
1928 		{
1929 			sc.ScriptError("%s: Unknown top level keyword", sc.String);
1930 		}
1931 	}
1932 }
1933 
1934 //==========================================================================
1935 //
1936 //
1937 //
1938 //==========================================================================
1939 
1940 void DeinitIntermissions();
1941 
ClearMapinfo()1942 static void ClearMapinfo()
1943 {
1944 	wadclusterinfos.Clear();
1945 	wadlevelinfos.Clear();
1946 	ClearEpisodes();
1947 	AllSkills.Clear();
1948 	DefaultSkill = -1;
1949 	DeinitIntermissions();
1950 	level.info = NULL;
1951 }
1952 
1953 //==========================================================================
1954 //
1955 // G_ParseMapInfo
1956 // Parses the MAPINFO lumps of all loaded WADs and generates
1957 // data for wadlevelinfos and wadclusterinfos.
1958 //
1959 //==========================================================================
1960 
G_ParseMapInfo(FString basemapinfo)1961 void G_ParseMapInfo (FString basemapinfo)
1962 {
1963 	int lump, lastlump = 0;
1964 	level_info_t gamedefaults;
1965 
1966 	ClearMapinfo();
1967 	atterm(ClearMapinfo);
1968 
1969 	// Parse the default MAPINFO for the current game. This lump *MUST* come from zdoom.pk3.
1970 	if (basemapinfo.IsNotEmpty())
1971 	{
1972 		FMapInfoParser parse;
1973 		level_info_t defaultinfo;
1974 		int baselump = Wads.GetNumForFullName(basemapinfo);
1975 		if (Wads.GetLumpFile(baselump) > 0)
1976 		{
1977 			I_FatalError("File %s is overriding core lump %s.",
1978 				Wads.GetWadFullName(Wads.GetLumpFile(baselump)), basemapinfo.GetChars());
1979 		}
1980 		parse.ParseMapInfo(baselump, gamedefaults, defaultinfo);
1981 	}
1982 
1983 	static const char *mapinfonames[] = { "MAPINFO", "ZMAPINFO", NULL };
1984 	int nindex;
1985 
1986 	// Parse any extra MAPINFOs.
1987 	while ((lump = Wads.FindLumpMulti (mapinfonames, &lastlump, false, &nindex)) != -1)
1988 	{
1989 		if (nindex == 0)
1990 		{
1991 			// If this lump is named MAPINFO we need to check if the same WAD contains a ZMAPINFO lump.
1992 			// If that exists we need to skip this one.
1993 
1994 			int wad = Wads.GetLumpFile(lump);
1995 			int altlump = Wads.CheckNumForName("ZMAPINFO", ns_global, wad, true);
1996 
1997 			if (altlump >= 0) continue;
1998 		}
1999 		FMapInfoParser parse(nindex == 1? FMapInfoParser::FMT_New : FMapInfoParser::FMT_Unknown);
2000 		level_info_t defaultinfo;
2001 		parse.ParseMapInfo(lump, gamedefaults, defaultinfo);
2002 	}
2003 
2004 	if (AllEpisodes.Size() == 0)
2005 	{
2006 		I_FatalError ("You cannot use clearepisodes in a MAPINFO if you do not define any new episodes after it.");
2007 	}
2008 	if (AllSkills.Size() == 0)
2009 	{
2010 		I_FatalError ("You cannot use clearskills in a MAPINFO if you do not define any new skills after it.");
2011 	}
2012 }
2013 
2014