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