1 /*
2 ** g_mapinfo.cpp
3 **
4 **---------------------------------------------------------------------------
5 ** Copyright 2011 Braden Obrzut
6 ** All rights reserved.
7 **
8 ** Redistribution and use in source and binary forms, with or without
9 ** modification, are permitted provided that the following conditions
10 ** are met:
11 **
12 ** 1. Redistributions of source code must retain the above copyright
13 **    notice, this list of conditions and the following disclaimer.
14 ** 2. Redistributions in binary form must reproduce the above copyright
15 **    notice, this list of conditions and the following disclaimer in the
16 **    documentation and/or other materials provided with the distribution.
17 ** 3. The name of the author may not be used to endorse or promote products
18 **    derived from this software without specific prior written permission.
19 **
20 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
24 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 **---------------------------------------------------------------------------
31 **
32 **
33 */
34 
35 #include "gamemap.h"
36 #include "g_intermission.h"
37 #include "g_mapinfo.h"
38 #include "language.h"
39 #include "lnspec.h"
40 #include "tarray.h"
41 #include "scanner.h"
42 #include "w_wad.h"
43 #include "wl_iwad.h"
44 #include "wl_shade.h"
45 #include "v_video.h"
46 #include "g_shared/a_inventory.h"
47 #include "g_shared/a_playerpawn.h"
48 #include "thingdef/thingdef.h"
49 
50 ////////////////////////////////////////////////////////////////////////////////
51 // MapInfoBlockParser
52 //
53 // Base class to handle syntax of mapinfo lump for common types.
54 
55 class MapInfoBlockParser
56 {
57 public:
MapInfoBlockParser(Scanner & sc,const char * block)58 	MapInfoBlockParser(Scanner &sc, const char* block) : sc(sc), block(block)
59 	{
60 	}
61 
Parse()62 	void Parse()
63 	{
64 		ParseHeader();
65 		ParseBlock(block);
66 	}
67 
68 protected:
69 	virtual bool CheckKey(FString key)=0;
ParseHeader()70 	virtual void ParseHeader() {};
71 
ParseBoolAssignment(bool & dest)72 	void ParseBoolAssignment(bool &dest)
73 	{
74 		sc.MustGetToken('=');
75 		sc.MustGetToken(TK_BoolConst);
76 		dest = sc->boolean;
77 	}
78 
ParseColorAssignment(int & dest)79 	void ParseColorAssignment(int &dest)
80 	{
81 		sc.MustGetToken('=');
82 		sc.MustGetToken(TK_StringConst);
83 		dest = V_GetColorFromString(NULL, sc->str);
84 	}
85 
ParseColorArrayAssignment(TArray<int> & dest)86 	void ParseColorArrayAssignment(TArray<int> &dest)
87 	{
88 		sc.MustGetToken('=');
89 		do
90 		{
91 			sc.MustGetToken(TK_StringConst);
92 			dest.Push(V_GetColorFromString(NULL, sc->str));
93 		}
94 		while(sc.CheckToken(','));
95 	}
96 
ParseColorArrayAssignment(int * dest,unsigned int max)97 	void ParseColorArrayAssignment(int *dest, unsigned int max)
98 	{
99 		sc.MustGetToken('=');
100 		do
101 		{
102 			sc.MustGetToken(TK_StringConst);
103 			*dest++ = V_GetColorFromString(NULL, sc->str);
104 
105 			if(--max == 0)
106 				break;
107 		}
108 		while(sc.CheckToken(','));
109 	}
110 
ParseFixedAssignment(fixed & dest)111 	void ParseFixedAssignment(fixed &dest)
112 	{
113 		sc.MustGetToken('=');
114 		bool negative = sc.CheckToken('-');
115 		sc.MustGetToken(TK_FloatConst);
116 		dest = negative ? -FLOAT2FIXED(sc->decimal) : FLOAT2FIXED(sc->decimal);
117 	}
118 
ParseIntAssignment(int & dest)119 	void ParseIntAssignment(int &dest)
120 	{
121 		sc.MustGetToken('=');
122 		bool negative = sc.CheckToken('-');
123 		sc.MustGetToken(TK_IntConst);
124 		dest = negative ? -sc->number : sc->number;
125 	}
126 
ParseIntAssignment(unsigned int & dest)127 	void ParseIntAssignment(unsigned int &dest)
128 	{
129 		sc.MustGetToken('=');
130 		sc.MustGetToken(TK_IntConst);
131 		dest = sc->number;
132 	}
133 
ParseTicAssignment(unsigned int & tics)134 	void ParseTicAssignment(unsigned int &tics)
135 	{
136 		sc.MustGetToken('=');
137 		sc.MustGetToken(TK_FloatConst);
138 		if(!CheckTicsValid(sc->decimal))
139 			sc.ScriptMessage(Scanner::ERROR, "Invalid tic duration.");
140 
141 		tics = static_cast<unsigned int>(sc->decimal*2);
142 	}
143 
ParseIntArrayAssignment(TArray<int> & dest)144 	void ParseIntArrayAssignment(TArray<int> &dest)
145 	{
146 		sc.MustGetToken('=');
147 		do
148 		{
149 			bool negative = sc.CheckToken('-');
150 			sc.MustGetToken(TK_IntConst);
151 			dest.Push(negative ? -sc->number : sc->number);
152 		}
153 		while(sc.CheckToken(','));
154 	}
155 
ParseIntArrayAssignment(TArray<unsigned int> & dest)156 	void ParseIntArrayAssignment(TArray<unsigned int> &dest)
157 	{
158 		sc.MustGetToken('=');
159 		do
160 		{
161 			sc.MustGetToken(TK_IntConst);
162 			dest.Push(sc->number);
163 		}
164 		while(sc.CheckToken(','));
165 	}
166 
ParseIntArrayAssignment(int * dest,unsigned int max)167 	void ParseIntArrayAssignment(int *dest, unsigned int max)
168 	{
169 		sc.MustGetToken('=');
170 		do
171 		{
172 			bool negative = sc.CheckToken('-');
173 			sc.MustGetToken(TK_IntConst);
174 			*dest++ = negative ? -sc->number : sc->number;
175 
176 			if(--max == 0)
177 				break;
178 		}
179 		while(sc.CheckToken(','));
180 	}
181 
ParseIntArrayAssignment(unsigned int * dest,unsigned int max)182 	void ParseIntArrayAssignment(unsigned int *dest, unsigned int max)
183 	{
184 		sc.MustGetToken('=');
185 		do
186 		{
187 			sc.MustGetToken(TK_IntConst);
188 			*dest++ = sc->number;
189 
190 			if(--max == 0)
191 				break;
192 		}
193 		while(sc.CheckToken(','));
194 	}
195 
ParseStringAssignment(FString & dest)196 	void ParseStringAssignment(FString &dest)
197 	{
198 		sc.MustGetToken('=');
199 		sc.MustGetToken(TK_StringConst);
200 		dest = sc->str;
201 	}
202 
ParseNameAssignment(FName & dest)203 	void ParseNameAssignment(FName &dest)
204 	{
205 		sc.MustGetToken('=');
206 		sc.MustGetToken(TK_StringConst);
207 		dest = sc->str;
208 	}
209 
ParseStringArrayAssignment(TArray<FString> & dest)210 	void ParseStringArrayAssignment(TArray<FString> &dest)
211 	{
212 		sc.MustGetToken('=');
213 		do
214 		{
215 			sc.MustGetToken(TK_StringConst);
216 			dest.Push(sc->str);
217 		}
218 		while(sc.CheckToken(','));
219 	}
220 
ParseStringArrayAssignment(FString * dest,unsigned int max)221 	void ParseStringArrayAssignment(FString *dest, unsigned int max)
222 	{
223 		sc.MustGetToken('=');
224 		do
225 		{
226 			sc.MustGetToken(TK_StringConst);
227 			*dest++ = sc->str;
228 
229 			if(--max == 0)
230 				break;
231 		}
232 		while(sc.CheckToken(','));
233 	}
234 
ParseFontColorAssignment(EColorRange & dest)235 	void ParseFontColorAssignment(EColorRange &dest)
236 	{
237 		sc.MustGetToken('=');
238 		sc.MustGetToken(TK_StringConst);
239 		dest = V_FindFontColor(sc->str);
240 	}
241 
242 	Scanner &sc;
243 	const char* const block;
244 
245 private:
ParseBlock(const char * block)246 	void ParseBlock(const char* block)
247 	{
248 		sc.MustGetToken('{');
249 		while(!sc.CheckToken('}'))
250 		{
251 			sc.MustGetToken(TK_Identifier);
252 			if(!CheckKey(sc->str))
253 			{
254 				sc.ScriptMessage(Scanner::WARNING, "Unknown %s property '%s'.", block, sc->str.GetChars());
255 				if(sc.CheckToken('='))
256 				{
257 					do
258 					{
259 						sc.GetNextToken();
260 					}
261 					while(sc.CheckToken(','));
262 				}
263 			}
264 		}
265 	}
266 };
267 
268 ////////////////////////////////////////////////////////////////////////////////
269 
270 static LevelInfo defaultMap;
271 static TArray<LevelInfo> levelInfos;
272 
LevelInfo()273 LevelInfo::LevelInfo() : UseMapInfoName(false)
274 {
275 	MapName[0] = 0;
276 	TitlePatch.SetInvalid();
277 	BorderTexture.SetInvalid();
278 	DefaultTexture[0].SetInvalid();
279 	DefaultTexture[1].SetInvalid();
280 	DefaultLighting = LIGHTLEVEL_DEFAULT;
281 	DefaultVisibility = VISIBILITY_DEFAULT;
282 	DefaultMaxLightVis = MAXLIGHTVIS_DEFAULT;
283 	DeathCam = false;
284 	ExitFadeColor = 0;
285 	ExitFadeDuration = 30;
286 	FloorNumber = "1";
287 	Par = 0;
288 	LevelBonus = -1;
289 	LevelNumber = 0;
290 	Cluster = 0;
291 	NoIntermission = false;
292 	SecretDeathSounds = false;
293 	SpawnWithWeaponRaised = false;
294 	ForceTally = false;
295 	HighScoresGraphic.SetInvalid();
296 }
297 
GetBorderTexture() const298 FTextureID LevelInfo::GetBorderTexture() const
299 {
300 	static FTextureID BorderFlat = TexMan.GetTexture(gameinfo.BorderFlat, FTexture::TEX_Flat);
301 	if(!BorderTexture.isValid())
302 		return BorderFlat;
303 	return BorderTexture;
304 }
305 
GetName(const GameMap * gm) const306 FString LevelInfo::GetName(const GameMap *gm) const
307 {
308 	if(UseMapInfoName)
309 		return Name;
310 	return gm->GetHeader().name;
311 }
312 
Find(const char * level)313 LevelInfo &LevelInfo::Find(const char* level)
314 {
315 	for(unsigned int i = 0;i < levelInfos.Size();++i)
316 	{
317 		if(stricmp(levelInfos[i].MapName, level) == 0)
318 			return levelInfos[i];
319 	}
320 	return defaultMap;
321 }
322 
FindByNumber(unsigned int num)323 LevelInfo &LevelInfo::FindByNumber(unsigned int num)
324 {
325 	for(unsigned int i = 0;i < levelInfos.Size();++i)
326 	{
327 		if(levelInfos[i].LevelNumber == num)
328 			return levelInfos[i];
329 	}
330 	return defaultMap;
331 }
332 
333 class LevelInfoBlockParser : public MapInfoBlockParser
334 {
335 private:
336 	bool parseHeader;
337 	LevelInfo &mapInfo;
338 
339 public:
LevelInfoBlockParser(Scanner & sc,LevelInfo & mapInfo,bool parseHeader)340 	LevelInfoBlockParser(Scanner &sc, LevelInfo &mapInfo, bool parseHeader) :
341 		MapInfoBlockParser(sc, "map"), parseHeader(parseHeader), mapInfo(mapInfo) {}
342 
343 protected:
ParseHeader()344 	void ParseHeader()
345 	{
346 		if(!parseHeader)
347 			return;
348 
349 		sc.MustGetToken(TK_StringConst);
350 		strncpy(mapInfo.MapName, sc->str, 8);
351 		mapInfo.MapName[8] = 0;
352 
353 		if(strnicmp(mapInfo.MapName, "MAP", 3) == 0)
354 		{
355 			int num = atoi(mapInfo.MapName+3);
356 			if(num > 0) // Zero is the default so no need to do anything.
357 			{
358 				ClearLevelNumber(num);
359 				mapInfo.LevelNumber = num;
360 			}
361 		}
362 
363 		bool useLanguage = false;
364 		if(sc.CheckToken(TK_Identifier))
365 		{
366 			if(sc->str.CompareNoCase("lookup") != 0)
367 				sc.ScriptMessage(Scanner::ERROR, "Expected lookup keyword but got '%s' instead.", sc->str.GetChars());
368 			else
369 				useLanguage = true;
370 		}
371 		if(sc.CheckToken(TK_StringConst))
372 		{
373 			mapInfo.UseMapInfoName = true;
374 			if(useLanguage)
375 				mapInfo.Name = language[sc->str];
376 			else
377 				mapInfo.Name = sc->str;
378 		}
379 	}
380 
ClearLevelNumber(unsigned int num)381 	void ClearLevelNumber(unsigned int num)
382 	{
383 		LevelInfo &other = LevelInfo::FindByNumber(num);
384 		if(other.MapName[0] != 0)
385 			other.LevelNumber = 0;
386 	}
387 
ParseNext(FString & next)388 	void ParseNext(FString &next)
389 	{
390 		sc.MustGetToken('=');
391 		if(sc.CheckToken(TK_Identifier))
392 		{
393 			if(sc->str.CompareNoCase("EndSequence") == 0)
394 			{
395 				sc.MustGetToken(',');
396 				sc.MustGetToken(TK_StringConst);
397 				next.Format("EndSequence:%s", sc->str.GetChars());
398 			}
399 			else
400 				sc.ScriptMessage(Scanner::ERROR, "Expected EndSequence.");
401 		}
402 		else
403 		{
404 			sc.MustGetToken(TK_StringConst);
405 			next = sc->str;
406 		}
407 	}
408 
CheckKey(FString key)409 	bool CheckKey(FString key)
410 	{
411 		if(key.CompareNoCase("next") == 0)
412 			ParseNext(mapInfo.NextMap);
413 		else if(key.CompareNoCase("secretnext") == 0)
414 			ParseNext(mapInfo.NextSecret);
415 		else if(key.CompareNoCase("victorynext") == 0)
416 			ParseNext(mapInfo.NextVictory);
417 		else if(key.CompareNoCase("bordertexture") == 0)
418 		{
419 			FString textureName;
420 			ParseStringAssignment(textureName);
421 			mapInfo.BorderTexture = TexMan.GetTexture(textureName, FTexture::TEX_Flat);
422 		}
423 		else if(key.CompareNoCase("defaultfloor") == 0)
424 		{
425 			FString textureName;
426 			ParseStringAssignment(textureName);
427 			mapInfo.DefaultTexture[MapSector::Floor] = TexMan.GetTexture(textureName, FTexture::TEX_Flat);
428 		}
429 		else if(key.CompareNoCase("defaultceiling") == 0)
430 		{
431 			FString textureName;
432 			ParseStringAssignment(textureName);
433 			mapInfo.DefaultTexture[MapSector::Ceiling] = TexMan.GetTexture(textureName, FTexture::TEX_Flat);
434 		}
435 		else if(key.CompareNoCase("DefaultLighting") == 0)
436 			ParseIntAssignment(mapInfo.DefaultLighting);
437 		else if(key.CompareNoCase("DefaultVisibility") == 0)
438 		{
439 			sc.MustGetToken('=');
440 			sc.MustGetToken(TK_FloatConst);
441 			mapInfo.DefaultVisibility = static_cast<fixed>(sc->decimal*LIGHTVISIBILITY_FACTOR*65536.);
442 		}
443 		else if(key.CompareNoCase("DefaultMaxLightVis") == 0)
444 		{
445 			sc.MustGetToken('=');
446 			sc.MustGetToken(TK_FloatConst);
447 			mapInfo.DefaultMaxLightVis = static_cast<fixed>(sc->decimal*LIGHTVISIBILITY_FACTOR*65536.);
448 		}
449 		else if(key.CompareNoCase("SpecialAction") == 0)
450 		{
451 			LevelInfo::SpecialAction action;
452 			sc.MustGetToken('=');
453 			sc.MustGetToken(TK_StringConst);
454 			action.Class = ClassDef::FindClassTentative(sc->str, NATIVE_CLASS(Actor));
455 			sc.MustGetToken(',');
456 			sc.MustGetToken(TK_StringConst);
457 			action.Special = Specials::LookupFunctionNum(sc->str);
458 
459 			unsigned int i;
460 			for(i = 0;i < 5 && sc.CheckToken(',');++i)
461 			{
462 				sc.MustGetToken(TK_IntConst);
463 				action.Args[i] = sc->number;
464 			}
465 			while(i < 5)
466 				action.Args[i++] = 0;
467 
468 			mapInfo.SpecialActions.Push(action);
469 		}
470 		else if(key.CompareNoCase("Cluster") == 0)
471 			ParseIntAssignment(mapInfo.Cluster);
472 		else if(key.CompareNoCase("CompletionString") == 0)
473 			ParseStringAssignment(mapInfo.CompletionString);
474 		else if(key.CompareNoCase("EnsureInventory") == 0)
475 		{
476 			TArray<FString> classNames;
477 			ParseStringArrayAssignment(classNames);
478 
479 			for(unsigned int i = 0;i < classNames.Size();++i)
480 			{
481 				const ClassDef *cls = ClassDef::FindClass(classNames[i]);
482 				if(!cls || !cls->IsDescendantOf(NATIVE_CLASS(Inventory)))
483 					sc.ScriptMessage(Scanner::ERROR, "Class %s doesn't appear to be a kind of Inventory.", classNames[i].GetChars());
484 				mapInfo.EnsureInventory.Push(cls);
485 			}
486 		}
487 		else if(key.CompareNoCase("ExitFade") == 0)
488 		{
489 			ParseColorAssignment(mapInfo.ExitFadeColor);
490 			sc.MustGetToken(',');
491 			sc.MustGetToken(TK_FloatConst);
492 			if(!CheckTicsValid(sc->decimal))
493 				sc.ScriptMessage(Scanner::ERROR, "Invalid tic duration.");
494 			mapInfo.ExitFadeDuration = static_cast<unsigned int>(sc->decimal*2);
495 		}
496 		else if(key.CompareNoCase("DeathCam") == 0)
497 			ParseBoolAssignment(mapInfo.DeathCam);
498 		else if(key.CompareNoCase("FloorNumber") == 0)
499 		{
500 			sc.MustGetToken('=');
501 			if(sc.CheckToken(TK_StringConst))
502 				mapInfo.FloorNumber = sc->str;
503 			else
504 			{
505 				sc.MustGetToken(TK_IntConst);
506 				mapInfo.FloorNumber.Format("%d", sc->number);
507 			}
508 		}
509 		else if(key.CompareNoCase("ForceTally") == 0)
510 			ParseBoolAssignment(mapInfo.ForceTally);
511 		else if(key.CompareNoCase("HighScoresGraphic") == 0)
512 		{
513 			FString texName;
514 			ParseStringAssignment(texName);
515 			mapInfo.HighScoresGraphic = TexMan.CheckForTexture(texName, FTexture::TEX_Any);
516 		}
517 		else if(key.CompareNoCase("LevelBonus") == 0)
518 			ParseIntAssignment(mapInfo.LevelBonus);
519 		else if(key.CompareNoCase("LevelNum") == 0)
520 		{
521 			// Get the number and then remove any other map from this slot.
522 			unsigned int num;
523 			ParseIntAssignment(num);
524 			ClearLevelNumber(num);
525 			mapInfo.LevelNumber = num;
526 		}
527 		else if(key.CompareNoCase("Music") == 0)
528 			ParseStringAssignment(mapInfo.Music);
529 		else if(key.CompareNoCase("NoIntermission") == 0)
530 			mapInfo.NoIntermission = true;
531 		else if(key.CompareNoCase("Par") == 0)
532 			ParseIntAssignment(mapInfo.Par);
533 		else if(key.CompareNoCase("SecretDeathSounds") == 0)
534 			ParseBoolAssignment(mapInfo.SecretDeathSounds);
535 		else if(key.CompareNoCase("SpawnWithWeaponRaised") == 0)
536 			mapInfo.SpawnWithWeaponRaised = true;
537 		else if(key.CompareNoCase("TitlePatch") == 0)
538 		{
539 			FString textureName;
540 			ParseStringAssignment(textureName);
541 			mapInfo.TitlePatch = TexMan.GetTexture(textureName, FTexture::TEX_Any);
542 		}
543 		else if(key.CompareNoCase("Translator") == 0)
544 			ParseStringAssignment(mapInfo.Translator);
545 		else
546 			return false;
547 		return true;
548 	}
549 };
550 
551 ////////////////////////////////////////////////////////////////////////////////
552 
553 GameInfo gameinfo;
554 
555 class GameInfoBlockParser : public MapInfoBlockParser
556 {
557 public:
GameInfoBlockParser(Scanner & sc)558 	GameInfoBlockParser(Scanner &sc) : MapInfoBlockParser(sc, "gameinfo") {}
559 
560 protected:
CheckKey(FString key)561 	bool CheckKey(FString key)
562 	{
563 		if(key.CompareNoCase("advisorycolor") == 0)
564 			ParseColorAssignment(gameinfo.AdvisoryColor);
565 		else if(key.CompareNoCase("advisorypic") == 0)
566 			ParseStringAssignment(gameinfo.AdvisoryPic);
567 		else if(key.CompareNoCase("border") == 0)
568 		{
569 			sc.MustGetToken('=');
570 			if(sc.CheckToken(TK_Identifier))
571 			{
572 				gameinfo.Border.issolid = true;
573 				if(!sc->str.CompareNoCase("inset") == 0)
574 					sc.ScriptMessage(Scanner::ERROR, "Expected 'inset' got '%s' instead.", sc->str.GetChars());
575 				sc.MustGetToken(',');
576 				sc.MustGetToken(TK_StringConst);
577 				gameinfo.Border.topcolor = V_GetColorFromString(NULL, sc->str);
578 				sc.MustGetToken(',');
579 				sc.MustGetToken(TK_StringConst);
580 				gameinfo.Border.bottomcolor = V_GetColorFromString(NULL, sc->str);
581 				sc.MustGetToken(',');
582 				sc.MustGetToken(TK_StringConst);
583 				gameinfo.Border.highlightcolor = V_GetColorFromString(NULL, sc->str);
584 			}
585 			else
586 			{
587 				gameinfo.Border.issolid = false;
588 				sc.MustGetToken(TK_IntConst);
589 				gameinfo.Border.offset = sc->number;
590 				sc.MustGetToken(',');
591 				sc.MustGetToken(TK_IntConst); // Unused
592 				sc.MustGetToken(',');
593 				sc.MustGetToken(TK_StringConst);
594 				gameinfo.Border.tl = sc->str;
595 				sc.MustGetToken(',');
596 				sc.MustGetToken(TK_StringConst);
597 				gameinfo.Border.t = sc->str;
598 				sc.MustGetToken(',');
599 				sc.MustGetToken(TK_StringConst);
600 				gameinfo.Border.tr = sc->str;
601 				sc.MustGetToken(',');
602 				sc.MustGetToken(TK_StringConst);
603 				gameinfo.Border.l = sc->str;
604 				sc.MustGetToken(',');
605 				sc.MustGetToken(TK_StringConst);
606 				gameinfo.Border.r = sc->str;
607 				sc.MustGetToken(',');
608 				sc.MustGetToken(TK_StringConst);
609 				gameinfo.Border.bl = sc->str;
610 				sc.MustGetToken(',');
611 				sc.MustGetToken(TK_StringConst);
612 				gameinfo.Border.b = sc->str;
613 				sc.MustGetToken(',');
614 				sc.MustGetToken(TK_StringConst);
615 				gameinfo.Border.br = sc->str;
616 			}
617 		}
618 		else if(key.CompareNoCase("borderflat") == 0)
619 			ParseStringAssignment(gameinfo.BorderFlat);
620 		else if(key.CompareNoCase("deathtransition") == 0)
621 		{
622 			sc.MustGetToken('=');
623 			sc.MustGetToken(TK_StringConst);
624 			if(sc->str.CompareNoCase("fizzle") == 0)
625 				gameinfo.DeathTransition = GameInfo::TRANSITION_Fizzle;
626 			else if(sc->str.CompareNoCase("fade") == 0)
627 				gameinfo.DeathTransition = GameInfo::TRANSITION_Fade;
628 			else
629 				sc.ScriptMessage(Scanner::ERROR, "Unknown transition type '%s'.", sc->str.GetChars());
630 		}
631 		else if(key.CompareNoCase("dialogcolor") == 0)
632 			ParseFontColorAssignment(gameinfo.FontColors[GameInfo::DIALOG]);
633 		else if(key.CompareNoCase("doorsoundsequence") == 0)
634 			ParseNameAssignment(gameinfo.DoorSoundSequence);
635 		else if(key.CompareNoCase("drawreadthis") == 0)
636 			ParseBoolAssignment(gameinfo.DrawReadThis);
637 		else if(key.CompareNoCase("gamecolormap") == 0)
638 			ParseStringAssignment(gameinfo.GameColormap);
639 		else if(key.CompareNoCase("gameoverpic") == 0)
640 			ParseStringAssignment(gameinfo.GameOverPic);
641 		else if(key.CompareNoCase("victorypic") == 0)
642 			ParseStringAssignment(gameinfo.VictoryPic);
643 		else if(key.CompareNoCase("gamepalette") == 0)
644 			ParseStringAssignment(gameinfo.GamePalette);
645 		else if(key.CompareNoCase("gibfactor") == 0)
646 		{
647 			sc.MustGetToken('=');
648 			sc.MustGetToken(TK_FloatConst);
649 			gameinfo.GibFactor = static_cast<fixed>(sc->decimal*FRACUNIT);
650 		}
651 		else if(key.CompareNoCase("signon") == 0)
652 			ParseStringAssignment(gameinfo.SignonLump);
653 		else if(key.CompareNoCase("menufade") == 0)
654 			ParseColorAssignment(gameinfo.MenuFadeColor);
655 		else if(key.CompareNoCase("menucolors") == 0)
656 			// Border1, Border2, Border3, Background, Stripe, StripeBG
657 			ParseColorArrayAssignment(gameinfo.MenuColors, 6);
658 		else if(key.CompareNoCase("messagecolors") == 0)
659 			// Background, Top Color, Bottom Color
660 			ParseColorArrayAssignment(gameinfo.MessageColors, 3);
661 		else if(key.CompareNoCase("messagefontcolor") == 0)
662 			ParseFontColorAssignment(gameinfo.FontColors[GameInfo::MESSAGEFONT]);
663 		else if(key.CompareNoCase("titlemusic") == 0)
664 			ParseStringAssignment(gameinfo.TitleMusic);
665 		else if(key.CompareNoCase("titlepalette") == 0)
666 			ParseStringAssignment(gameinfo.TitlePalette);
667 		else if(key.CompareNoCase("titlepage") == 0)
668 			ParseStringAssignment(gameinfo.TitlePage);
669 		else if(key.CompareNoCase("titletime") == 0)
670 			ParseIntAssignment(gameinfo.TitleTime);
671 		else if(key.CompareNoCase("translator") == 0)
672 		{
673 			sc.MustGetToken('=');
674 			sc.MustGetToken(TK_StringConst);
675 			gameinfo.Translator.Push(sc->str);
676 		}
677 		else if(key.CompareNoCase("menumusic") == 0)
678 			ParseStringAssignment(gameinfo.MenuMusic);
679 		else if(key.CompareNoCase("pushwallsoundsequence") == 0)
680 			ParseNameAssignment(gameinfo.PushwallSoundSequence);
681 		else if(key.CompareNoCase("scoresmusic") == 0)
682 			ParseStringAssignment(gameinfo.ScoresMusic);
683 		else if(key.CompareNoCase("menuwindowcolors") == 0)
684 			// Background, Top color, bottom color, Index background, Index top, Index bottom
685 			ParseColorArrayAssignment(gameinfo.MenuWindowColors, 6);
686 		else if(key.CompareNoCase("finaleflat") == 0)
687 			ParseStringAssignment(gameinfo.FinaleFlat);
688 		else if(key.CompareNoCase("finalemusic") == 0)
689 			ParseStringAssignment(gameinfo.FinaleMusic);
690 		else if(key.CompareNoCase("victorymusic") == 0)
691 			ParseStringAssignment(gameinfo.VictoryMusic);
692 		else if(key.CompareNoCase("intermissionmusic") == 0)
693 			ParseStringAssignment(gameinfo.IntermissionMusic);
694 		else if(key.CompareNoCase("menufontcolor_title") == 0)
695 			ParseFontColorAssignment(gameinfo.FontColors[GameInfo::MENU_TITLE]);
696 		else if(key.CompareNoCase("menufontcolor_label") == 0)
697 			ParseFontColorAssignment(gameinfo.FontColors[GameInfo::MENU_LABEL]);
698 		else if(key.CompareNoCase("menufontcolor_selection") == 0)
699 			ParseFontColorAssignment(gameinfo.FontColors[GameInfo::MENU_SELECTION]);
700 		else if(key.CompareNoCase("menufontcolor_disabled") == 0)
701 			ParseFontColorAssignment(gameinfo.FontColors[GameInfo::MENU_DISABLED]);
702 		else if(key.CompareNoCase("menufontcolor_invalid") == 0)
703 			ParseFontColorAssignment(gameinfo.FontColors[GameInfo::MENU_INVALID]);
704 		else if(key.CompareNoCase("menufontcolor_invalidselection") == 0)
705 			ParseFontColorAssignment(gameinfo.FontColors[GameInfo::MENU_INVALIDSELECTION]);
706 		else if(key.CompareNoCase("menufontcolor_highlight") == 0)
707 			ParseFontColorAssignment(gameinfo.FontColors[GameInfo::MENU_HIGHLIGHT]);
708 		else if(key.CompareNoCase("menufontcolor_highlightselection") == 0)
709 			ParseFontColorAssignment(gameinfo.FontColors[GameInfo::MENU_HIGHLIGHTSELECTION]);
710 		else if(key.CompareNoCase("highscoresfont") == 0)
711 			ParseStringAssignment(gameinfo.HighScoresFont);
712 		else if(key.CompareNoCase("highscoresfontcolor") == 0)
713 			ParseFontColorAssignment(gameinfo.FontColors[GameInfo::HIGHSCORES]);
714 		else if(key.CompareNoCase("pageindexfontcolor") == 0)
715 			ParseFontColorAssignment(gameinfo.FontColors[GameInfo::PAGEINDEX]);
716 		else if(key.CompareNoCase("psyched") == 0)
717 		{
718 			ParseColorArrayAssignment(gameinfo.PsychedColors, 2);
719 			if(sc.CheckToken(','))
720 			{
721 				bool negative = sc.CheckToken('-');
722 				sc.MustGetToken(TK_IntConst);
723 				gameinfo.PsychedOffset = negative ? -sc->number : sc->number;
724 			}
725 			else
726 				gameinfo.PsychedOffset = 0;
727 		}
728 		else if(key.CompareNoCase("playerclasses") == 0)
729 		{
730 			sc.MustGetToken('=');
731 			gameinfo.PlayerClasses.Clear();
732 			do
733 			{
734 				sc.MustGetToken(TK_StringConst);
735 				gameinfo.PlayerClasses.Push(sc->str);
736 
737 			}
738 			while(sc.CheckToken(','));
739 		}
740 		else if(key.CompareNoCase("quitmessages") == 0)
741 			ParseStringArrayAssignment(gameinfo.QuitMessages);
742 		else
743 			return false;
744 		return true;
745 	}
746 };
747 
748 class AutomapBlockParser : public MapInfoBlockParser
749 {
750 public:
AutomapBlockParser(Scanner & sc)751 	AutomapBlockParser(Scanner &sc) : MapInfoBlockParser(sc, "automap") {}
752 
753 protected:
CheckKey(FString key)754 	bool CheckKey(FString key)
755 	{
756 		if(key.CompareNoCase("Background") == 0)
757 			ParseColorAssignment(gameinfo.automap.Background);
758 		else if(key.CompareNoCase("DoorColor") == 0)
759 			ParseColorAssignment(gameinfo.automap.DoorColor);
760 		else if(key.CompareNoCase("FloorColor") == 0)
761 			ParseColorAssignment(gameinfo.automap.FloorColor);
762 		else if(key.CompareNoCase("FontColor") == 0)
763 			ParseFontColorAssignment(gameinfo.automap.FontColor);
764 		else if(key.CompareNoCase("WallColor") == 0)
765 			ParseColorAssignment(gameinfo.automap.WallColor);
766 		else if(key.CompareNoCase("YourColor") == 0)
767 			ParseColorAssignment(gameinfo.automap.YourColor);
768 		else
769 			return false;
770 		return true;
771 	}
772 };
773 
774 ////////////////////////////////////////////////////////////////////////////////
775 
776 static TArray<EpisodeInfo> episodes;
777 
EpisodeInfo()778 EpisodeInfo::EpisodeInfo() : Shortcut(0), NoSkill(false)
779 {
780 }
781 
GetNumEpisodes()782 unsigned int EpisodeInfo::GetNumEpisodes()
783 {
784 	return episodes.Size();
785 }
786 
GetEpisode(unsigned int index)787 EpisodeInfo &EpisodeInfo::GetEpisode(unsigned int index)
788 {
789 	return episodes[index];
790 }
791 
792 class EpisodeBlockParser : public MapInfoBlockParser
793 {
794 private:
795 	EpisodeInfo &episode;
796 	bool useEpisode;
797 
798 public:
EpisodeBlockParser(Scanner & sc,EpisodeInfo & episode)799 	EpisodeBlockParser(Scanner &sc, EpisodeInfo &episode) :
800 		MapInfoBlockParser(sc, "episode"), episode(episode), useEpisode(true)
801 		{}
802 
UseEpisode() const803 	bool UseEpisode() const { return useEpisode; }
804 
805 protected:
ParseHeader()806 	void ParseHeader()
807 	{
808 		sc.MustGetToken(TK_StringConst);
809 		episode.StartMap = sc->str;
810 	}
811 
CheckKey(FString key)812 	bool CheckKey(FString key)
813 	{
814 		if(key.CompareNoCase("name") == 0)
815 			ParseStringAssignment(episode.EpisodeName);
816 		else if(key.CompareNoCase("lookup") == 0)
817 		{
818 			ParseStringAssignment(episode.EpisodeName);
819 			episode.EpisodeName = language[episode.EpisodeName];
820 		}
821 		else if(key.CompareNoCase("picname") == 0)
822 			ParseStringAssignment(episode.EpisodePicture);
823 		else if(key.CompareNoCase("key") == 0)
824 		{
825 			FString tmp;
826 			ParseStringAssignment(tmp);
827 			episode.Shortcut = tmp[0];
828 		}
829 		else if(key.CompareNoCase("remove") == 0)
830 			useEpisode = false;
831 		else if(key.CompareNoCase("noskillmenu") == 0)
832 			episode.NoSkill = true;
833 		else if(key.CompareNoCase("optional") == 0)
834 		{
835 			if(Wads.CheckNumForName(episode.StartMap) == -1)
836 				useEpisode = false;
837 		}
838 		else
839 			return false;
840 		return true;
841 	}
842 };
843 
844 ////////////////////////////////////////////////////////////////////////////////
845 
846 static TMap<unsigned int, ClusterInfo> clusters;
847 
ClusterInfo()848 ClusterInfo::ClusterInfo() : ExitTextType(ClusterInfo::EXIT_STRING),
849 	TextFont(SmallFont), TextAlignment(TS_Left), TextAnchor(TS_Middle),
850 	TextColor(CR_UNTRANSLATED)
851 {
852 }
853 
Find(unsigned int index)854 ClusterInfo &ClusterInfo::Find(unsigned int index)
855 {
856 	return clusters[index];
857 }
858 
859 class ClusterBlockParser : public MapInfoBlockParser
860 {
861 private:
862 	ClusterInfo *cluster;
863 
864 public:
ClusterBlockParser(Scanner & sc)865 	ClusterBlockParser(Scanner &sc) :
866 		MapInfoBlockParser(sc, "cluster")
867 		{}
868 protected:
ParseHeader()869 	void ParseHeader()
870 	{
871 		sc.MustGetToken(TK_IntConst);
872 		cluster = &ClusterInfo::Find(sc->number);
873 	}
874 
CheckKey(FString key)875 	bool CheckKey(FString key)
876 	{
877 		if(key.CompareNoCase("enterslideshow") == 0)
878 			ParseStringAssignment(cluster->EnterSlideshow);
879 		else if(key.CompareNoCase("exitslideshow") == 0)
880 			ParseStringAssignment(cluster->ExitSlideshow);
881 		else if(key.CompareNoCase("exittext") == 0 || key.CompareNoCase("entertext") == 0)
882 		{
883 			sc.MustGetToken('=');
884 			bool lookup = false;
885 			if(sc.CheckToken(TK_Identifier))
886 			{
887 				if(sc->str.CompareNoCase("lookup") != 0)
888 					sc.ScriptMessage(Scanner::ERROR, "Expected lookup but got '%s' instead.", sc->str.GetChars());
889 				sc.MustGetToken(',');
890 				lookup = true;
891 			}
892 			sc.MustGetToken(TK_StringConst);
893 
894 			FString &text = key.CompareNoCase("exittext") == 0 ? cluster->ExitText : cluster->EnterText;
895 			text = lookup ? FString(language[sc->str]) : sc->str;
896 		}
897 		else if(key.CompareNoCase("entertextislump") == 0)
898 			cluster->EnterTextType = ClusterInfo::EXIT_LUMP;
899 		else if(key.CompareNoCase("exittextislump") == 0)
900 			cluster->ExitTextType = ClusterInfo::EXIT_LUMP;
901 		else if(key.CompareNoCase("entertextismessage") == 0)
902 			cluster->EnterTextType = ClusterInfo::EXIT_MESSAGE;
903 		else if(key.CompareNoCase("exittextismessage") == 0)
904 			cluster->ExitTextType = ClusterInfo::EXIT_MESSAGE;
905 		else if(key.CompareNoCase("flat") == 0)
906 			ParseStringAssignment(cluster->Flat);
907 		else if(key.CompareNoCase("music") == 0)
908 			ParseStringAssignment(cluster->Music);
909 		else if(sc->str.CompareNoCase("textalignment") == 0)
910 		{
911 			sc.MustGetToken('=');
912 			sc.MustGetToken(TK_Identifier);
913 			if(sc->str.CompareNoCase("left") == 0)
914 				cluster->TextAlignment = TS_Left;
915 			else if(sc->str.CompareNoCase("center") == 0)
916 				cluster->TextAlignment = TS_Center;
917 			else if(sc->str.CompareNoCase("right") == 0)
918 				cluster->TextAlignment = TS_Right;
919 			else
920 				sc.ScriptMessage(Scanner::ERROR, "Unknown alignment.");
921 		}
922 		else if(sc->str.CompareNoCase("textanchor") == 0)
923 		{
924 			sc.MustGetToken('=');
925 			sc.MustGetToken(TK_Identifier);
926 			if(sc->str.CompareNoCase("top") == 0)
927 				cluster->TextAnchor = TS_Top;
928 			else if(sc->str.CompareNoCase("middle") == 0)
929 				cluster->TextAnchor = TS_Middle;
930 			else if(sc->str.CompareNoCase("bottom") == 0)
931 				cluster->TextAnchor = TS_Bottom;
932 			else
933 				sc.ScriptMessage(Scanner::ERROR, "Unknown anchor.");
934 		}
935 		else if(sc->str.CompareNoCase("textcolor") == 0)
936 			ParseFontColorAssignment(cluster->TextColor);
937 		else if(sc->str.CompareNoCase("textfont") == 0)
938 		{
939 			FString fontName;
940 			ParseStringAssignment(fontName);
941 			cluster->TextFont = V_GetFont(fontName);
942 		}
943 		else
944 			return false;
945 		return true;
946 	}
947 };
948 
949 ////////////////////////////////////////////////////////////////////////////////
950 
951 static TArray<SkillInfo> skills;
952 static TMap<FName, unsigned int> skillIds;
953 
SkillInfo()954 SkillInfo::SkillInfo() : DamageFactor(FRACUNIT), PlayerDamageFactor(FRACUNIT),
955 	SpawnFilter(0), MapFilter(0), FastMonsters(false), QuizHints(false)
956 {
957 }
958 
GetNumSkills()959 unsigned int SkillInfo::GetNumSkills()
960 {
961 	return skills.Size();
962 }
963 
GetSkillIndex(const SkillInfo & skill)964 unsigned int SkillInfo::GetSkillIndex(const SkillInfo &skill)
965 {
966 	return (unsigned int)(&skill - &skills[0]);
967 }
968 
GetSkill(unsigned int index)969 SkillInfo &SkillInfo::GetSkill(unsigned int index)
970 {
971 	return skills[index];
972 }
973 
974 class SkillInfoBlockParser : public MapInfoBlockParser
975 {
976 private:
977 	SkillInfo *skill;
978 
979 public:
SkillInfoBlockParser(Scanner & sc)980 	SkillInfoBlockParser(Scanner &sc) : MapInfoBlockParser(sc, "skill")
981 	{
982 	}
983 
984 protected:
ParseHeader()985 	void ParseHeader()
986 	{
987 		sc.MustGetToken(TK_Identifier);
988 		const unsigned int *id = skillIds.CheckKey(sc->str);
989 		if(id)
990 			skill = &skills[*id];
991 		else
992 		{
993 			unsigned int newId = skills.Push(SkillInfo());
994 			skill = &skills[newId];
995 			skillIds.Insert(sc->str, newId);
996 		}
997 	}
998 
CheckKey(FString key)999 	bool CheckKey(FString key)
1000 	{
1001 		if(key.CompareNoCase("damagefactor") == 0)
1002 			ParseFixedAssignment(skill->DamageFactor);
1003 		else if(key.CompareNoCase("fastmonsters") == 0)
1004 			skill->FastMonsters = true;
1005 		else if(key.CompareNoCase("name") == 0)
1006 		{
1007 			ParseStringAssignment(skill->Name);
1008 			if(skill->Name[0] == '$')
1009 				skill->Name = language[skill->Name.Mid(1)];
1010 		}
1011 		else if(key.CompareNoCase("picname") == 0)
1012 			ParseStringAssignment(skill->SkillPicture);
1013 		else if(key.CompareNoCase("playerdamagefactor") == 0)
1014 			ParseFixedAssignment(skill->PlayerDamageFactor);
1015 		else if(key.CompareNoCase("spawnfilter") == 0)
1016 		{
1017 			ParseIntAssignment(skill->SpawnFilter);
1018 			--skill->SpawnFilter;
1019 		}
1020 		else if(key.CompareNoCase("mapfilter") == 0)
1021 			ParseIntAssignment(skill->MapFilter);
1022 		else if(key.CompareNoCase("mustconfirm") == 0)
1023 		{
1024 			ParseStringAssignment(skill->MustConfirm);
1025 			if(skill->MustConfirm[0] == '$')
1026 				skill->MustConfirm = language[skill->MustConfirm.Mid(1)];
1027 		}
1028 		else if(key.CompareNoCase("quizhints") == 0)
1029 			ParseBoolAssignment(skill->QuizHints);
1030 		else
1031 			return false;
1032 		return true;
1033 	}
1034 };
1035 
1036 ////////////////////////////////////////////////////////////////////////////////
1037 
1038 class IntermissionBlockParser : public MapInfoBlockParser
1039 {
1040 private:
1041 	IntermissionInfo *intermission;
1042 
1043 public:
IntermissionBlockParser(Scanner & sc)1044 	IntermissionBlockParser(Scanner &sc) : MapInfoBlockParser(sc, "intermission")
1045 	{
1046 	}
1047 
1048 protected:
ParseHeader()1049 	void ParseHeader()
1050 	{
1051 		sc.MustGetToken(TK_Identifier);
1052 
1053 		intermission = IntermissionInfo::Find(sc->str);
1054 		intermission->Clear();
1055 	}
1056 
ParseTimeAssignment(unsigned int & time)1057 	void ParseTimeAssignment(unsigned int &time)
1058 	{
1059 		sc.MustGetToken('=');
1060 
1061 		if(sc.CheckToken(TK_Identifier))
1062 		{
1063 			if(sc->str.CompareNoCase("titletime") == 0)
1064 				time = gameinfo.TitleTime*TICRATE;
1065 			else
1066 				sc.ScriptMessage(Scanner::ERROR, "Invalid special time %s.\n", sc->str.GetChars());
1067 		}
1068 		else
1069 		{
1070 			bool inSeconds = sc.CheckToken('-');
1071 			sc.MustGetToken(TK_FloatConst);
1072 			if(!CheckTicsValid(sc->decimal))
1073 				sc.ScriptMessage(Scanner::ERROR, "Invalid tic duration.");
1074 
1075 			time = static_cast<unsigned int>(sc->decimal*2);
1076 			if(inSeconds)
1077 				time *= 35;
1078 		}
1079 	}
1080 
CheckKey(FString key)1081 	bool CheckKey(FString key)
1082 	{
1083 		IntermissionInfo::Action action;
1084 		if(key.CompareNoCase("Cast") == 0)
1085 		{
1086 			CastIntermissionAction *cast = new CastIntermissionAction();
1087 
1088 			action.type = IntermissionInfo::CAST;
1089 			action.action = cast;
1090 
1091 			if(!ParseCast(cast))
1092 			{
1093 				delete cast;
1094 				return false;
1095 			}
1096 		}
1097 		else if(key.CompareNoCase("Fader") == 0)
1098 		{
1099 			FaderIntermissionAction *fader = new FaderIntermissionAction();
1100 
1101 			action.type = IntermissionInfo::FADER;
1102 			action.action = fader;
1103 
1104 			if(!ParseFader(fader))
1105 			{
1106 				delete fader;
1107 				return false;
1108 			}
1109 		}
1110 		else if(key.CompareNoCase("GotoTitle") == 0)
1111 		{
1112 			action.type = IntermissionInfo::GOTOTITLE;
1113 			action.action = new IntermissionAction();
1114 			sc.MustGetToken('{');
1115 			sc.MustGetToken('}');
1116 		}
1117 		else if(key.CompareNoCase("Image") == 0)
1118 		{
1119 			action.type = IntermissionInfo::IMAGE;
1120 			action.action = new IntermissionAction();
1121 
1122 			sc.MustGetToken('{');
1123 			while(!sc.CheckToken('}'))
1124 			{
1125 				sc.MustGetToken(TK_Identifier);
1126 				if(!CheckStandardKey(action.action, sc->str))
1127 				{
1128 					delete action.action;
1129 					return false;
1130 				}
1131 			}
1132 		}
1133 		else if(key.CompareNoCase("Link") == 0)
1134 		{
1135 			ParseNameAssignment(intermission->Link);
1136 			return true;
1137 		}
1138 		else if(key.CompareNoCase("TextScreen") == 0)
1139 		{
1140 			TextScreenIntermissionAction *textscreen = new TextScreenIntermissionAction();
1141 
1142 			action.type = IntermissionInfo::TEXTSCREEN;
1143 			action.action = textscreen;
1144 
1145 			if(!ParseTextScreen(textscreen))
1146 			{
1147 				delete textscreen;
1148 				return false;
1149 			}
1150 		}
1151 		else if(key.CompareNoCase("VictoryStats") == 0)
1152 		{
1153 			action.type = IntermissionInfo::VICTORYSTATS;
1154 			action.action = new IntermissionAction();
1155 			sc.MustGetToken('{');
1156 			sc.MustGetToken('}');
1157 		}
1158 		else
1159 			return false;
1160 
1161 		intermission->Actions.Push(action);
1162 		return true;
1163 	}
1164 
CheckStandardKey(IntermissionAction * action,const FString & key)1165 	bool CheckStandardKey(IntermissionAction *action, const FString &key)
1166 	{
1167 		if(key.CompareNoCase("Draw") == 0)
1168 		{
1169 			IntermissionAction::DrawData data;
1170 
1171 			sc.MustGetToken('=');
1172 			sc.MustGetToken(TK_StringConst);
1173 			data.Image = TexMan.CheckForTexture(sc->str, FTexture::TEX_Any);
1174 			sc.MustGetToken(',');
1175 			sc.MustGetToken(TK_IntConst);
1176 			data.X = sc->number;
1177 			sc.MustGetToken(',');
1178 			sc.MustGetToken(TK_IntConst);
1179 			data.Y = sc->number;
1180 
1181 			action->Draw.Push(data);
1182 		}
1183 		else if(key.CompareNoCase("Background") == 0)
1184 		{
1185 			sc.MustGetToken('=');
1186 			if(sc.CheckToken(TK_Identifier))
1187 			{
1188 				if(sc->str.CompareNoCase("HighScores") == 0)
1189 				{
1190 					action->Type = IntermissionAction::HIGHSCORES;
1191 				}
1192 				else if(sc->str.CompareNoCase("TitlePage") == 0)
1193 				{
1194 					action->Type = IntermissionAction::TITLEPAGE;
1195 				}
1196 				else if(sc->str.CompareNoCase("LoadMap") == 0)
1197 				{
1198 					action->Type = IntermissionAction::LOADMAP;
1199 					sc.MustGetToken(',');
1200 					sc.MustGetToken(TK_StringConst);
1201 					action->MapName = sc->str;
1202 				}
1203 				else
1204 				{
1205 					sc.ScriptMessage(Scanner::ERROR, "Unknown background type %s. Use quotes for static image.", sc->str.GetChars());
1206 				}
1207 			}
1208 			else
1209 			{
1210 				sc.MustGetToken(TK_StringConst);
1211 				FString tex = sc->str;
1212 				action->Background = TexMan.CheckForTexture(tex, FTexture::TEX_Any);
1213 				action->Type = IntermissionAction::NORMAL;
1214 				if(sc.CheckToken(','))
1215 				{
1216 					if(!sc.CheckToken(TK_BoolConst))
1217 						sc.MustGetToken(TK_IntConst);
1218 					action->BackgroundTile = sc->boolean;
1219 					if(sc.CheckToken(','))
1220 					{
1221 						sc.MustGetToken(TK_StringConst);
1222 						action->Palette = sc->str;
1223 					}
1224 				}
1225 			}
1226 		}
1227 		else if(key.CompareNoCase("Music") == 0)
1228 			ParseStringAssignment(action->Music);
1229 		else if(key.CompareNoCase("Time") == 0)
1230 			ParseTimeAssignment(action->Time);
1231 		else
1232 			return false;
1233 		return true;
1234 	}
1235 
ParseCast(CastIntermissionAction * cast)1236 	bool ParseCast(CastIntermissionAction *cast)
1237 	{
1238 		sc.MustGetToken('{');
1239 		while(!sc.CheckToken('}'))
1240 		{
1241 			sc.MustGetToken(TK_Identifier);
1242 			if(CheckStandardKey(cast, sc->str))
1243 				continue;
1244 
1245 			if(sc->str.CompareNoCase("CastClass") == 0)
1246 			{
1247 				sc.MustGetToken('=');
1248 				sc.MustGetToken(TK_StringConst);
1249 				cast->Class = ClassDef::FindClass(sc->str);
1250 			}
1251 			else if(sc->str.CompareNoCase("CastName") == 0)
1252 			{
1253 				ParseStringAssignment(cast->Name);
1254 				if(cast->Name[0] == '$')
1255 					cast->Name = language[cast->Name.Mid(1)];
1256 			}
1257 			else
1258 				return false;
1259 		}
1260 		return true;
1261 	}
1262 
ParseFader(FaderIntermissionAction * fader)1263 	bool ParseFader(FaderIntermissionAction *fader)
1264 	{
1265 		sc.MustGetToken('{');
1266 		while(!sc.CheckToken('}'))
1267 		{
1268 			sc.MustGetToken(TK_Identifier);
1269 			if(CheckStandardKey(fader, sc->str))
1270 				continue;
1271 
1272 			if(sc->str.CompareNoCase("FadeType") == 0)
1273 			{
1274 				sc.MustGetToken('=');
1275 				sc.MustGetToken(TK_Identifier);
1276 				if(sc->str.CompareNoCase("FadeIn") == 0)
1277 					fader->Fade = FaderIntermissionAction::FADEIN;
1278 				else if(sc->str.CompareNoCase("FadeOut") == 0)
1279 					fader->Fade = FaderIntermissionAction::FADEOUT;
1280 				else
1281 					sc.ScriptMessage(Scanner::ERROR, "Unknown fade type.");
1282 			}
1283 			else
1284 				return false;
1285 		}
1286 		return true;
1287 	}
1288 
ParseTextScreen(TextScreenIntermissionAction * textscreen)1289 	bool ParseTextScreen(TextScreenIntermissionAction *textscreen)
1290 	{
1291 		sc.MustGetToken('{');
1292 		while(!sc.CheckToken('}'))
1293 		{
1294 			sc.MustGetToken(TK_Identifier);
1295 			if(CheckStandardKey(textscreen, sc->str))
1296 				continue;
1297 
1298 			if(sc->str.CompareNoCase("FadeTime") == 0)
1299 				ParseTimeAssignment(textscreen->FadeTime);
1300 			else if(sc->str.CompareNoCase("Text") == 0)
1301 				ParseStringArrayAssignment(textscreen->Text);
1302 			else if(sc->str.CompareNoCase("TextAlignment") == 0)
1303 			{
1304 				sc.MustGetToken('=');
1305 				sc.MustGetToken(TK_Identifier);
1306 				if(sc->str.CompareNoCase("left") == 0)
1307 					textscreen->Alignment = TS_Left;
1308 				else if(sc->str.CompareNoCase("center") == 0)
1309 					textscreen->Alignment = TS_Center;
1310 				else if(sc->str.CompareNoCase("right") == 0)
1311 					textscreen->Alignment = TS_Right;
1312 				else
1313 					sc.ScriptMessage(Scanner::ERROR, "Unknown alignment.");
1314 			}
1315 			else if(sc->str.CompareNoCase("TextAnchor") == 0)
1316 			{
1317 				sc.MustGetToken('=');
1318 				sc.MustGetToken(TK_Identifier);
1319 				if(sc->str.CompareNoCase("top") == 0)
1320 					textscreen->Anchor = TS_Top;
1321 				else if(sc->str.CompareNoCase("middle") == 0)
1322 					textscreen->Anchor = TS_Middle;
1323 				else if(sc->str.CompareNoCase("bottom") == 0)
1324 					textscreen->Anchor = TS_Bottom;
1325 				else
1326 					sc.ScriptMessage(Scanner::ERROR, "Unknown anchor.");
1327 			}
1328 			else if(sc->str.CompareNoCase("TextColor") == 0)
1329 				ParseFontColorAssignment(textscreen->TextColor);
1330 			else if(sc->str.CompareNoCase("TextDelay") == 0)
1331 				ParseTicAssignment(textscreen->TextDelay);
1332 			else if(sc->str.CompareNoCase("TextFont") == 0)
1333 			{
1334 				FString fontName;
1335 				ParseStringAssignment(fontName);
1336 				textscreen->TextFont = V_GetFont(fontName);
1337 			}
1338 			else if(sc->str.CompareNoCase("TextSpeed") == 0)
1339 				ParseTicAssignment(textscreen->TextSpeed);
1340 			else if(sc->str.CompareNoCase("Position") == 0)
1341 			{
1342 				sc.MustGetToken('=');
1343 				sc.MustGetToken(TK_IntConst);
1344 				textscreen->PrintX = sc->number;
1345 				sc.MustGetToken(',');
1346 				sc.MustGetToken(TK_IntConst);
1347 				textscreen->PrintY = sc->number;
1348 			}
1349 			else
1350 				return false;
1351 		}
1352 		return true;
1353 	}
1354 };
1355 
1356 ////////////////////////////////////////////////////////////////////////////////
1357 
SkipBlock(Scanner & sc)1358 static void SkipBlock(Scanner &sc)
1359 {
1360 	// Skip header
1361 	while(sc.GetNextToken() && sc->token != '{');
1362 	// Skip content
1363 	unsigned int level = 0;
1364 	while(sc.GetNextToken() && (level != 0 || sc->token != '}'))
1365 	{
1366 		if(sc->token == '{')
1367 			++level;
1368 		else if(sc->token == '}')
1369 			--level;
1370 	}
1371 }
1372 
ParseMapInfoLump(int lump,bool gameinfoPass)1373 static void ParseMapInfoLump(int lump, bool gameinfoPass)
1374 {
1375 	FMemLump data = Wads.ReadLump(lump);
1376 	Scanner sc((const char*)data.GetMem(), data.GetSize());
1377 	sc.SetScriptIdentifier(Wads.GetLumpFullName(lump));
1378 
1379 	while(sc.TokensLeft())
1380 	{
1381 		sc.MustGetToken(TK_Identifier);
1382 		if(sc->str.CompareNoCase("include") == 0)
1383 		{
1384 			sc.MustGetToken(TK_StringConst);
1385 			int includeLump = Wads.GetNumForFullName(sc->str);
1386 			if(includeLump != -1)
1387 				ParseMapInfoLump(includeLump, gameinfoPass);
1388 
1389 			continue;
1390 		}
1391 
1392 		if(!gameinfoPass)
1393 		{
1394 			if(sc->str.CompareNoCase("defaultmap") == 0)
1395 			{
1396 				defaultMap = LevelInfo();
1397 				LevelInfoBlockParser(sc, defaultMap, false).Parse();
1398 			}
1399 			else if(sc->str.CompareNoCase("adddefaultmap") == 0)
1400 			{
1401 				LevelInfoBlockParser(sc, defaultMap, false).Parse();
1402 			}
1403 			else if(sc->str.CompareNoCase("automap") == 0)
1404 			{
1405 				AutomapBlockParser(sc).Parse();
1406 			}
1407 			else if(sc->str.CompareNoCase("clearepisodes") == 0)
1408 			{
1409 				episodes.Clear();
1410 			}
1411 			else if(sc->str.CompareNoCase("clearskills") == 0)
1412 			{
1413 				skills.Clear();
1414 				skillIds.Clear();
1415 			}
1416 			else if(sc->str.CompareNoCase("cluster") == 0)
1417 			{
1418 				ClusterBlockParser(sc).Parse();
1419 			}
1420 			else if(sc->str.CompareNoCase("episode") == 0)
1421 			{
1422 				EpisodeInfo episode;
1423 				EpisodeBlockParser parser(sc, episode);
1424 				parser.Parse();
1425 				if(parser.UseEpisode())
1426 					episodes.Push(episode);
1427 			}
1428 			else if(sc->str.CompareNoCase("intermission") == 0)
1429 			{
1430 				IntermissionBlockParser(sc).Parse();
1431 			}
1432 			else if(sc->str.CompareNoCase("map") == 0)
1433 			{
1434 				LevelInfo newMap = defaultMap;
1435 				LevelInfoBlockParser(sc, newMap, true).Parse();
1436 
1437 				LevelInfo &existing = LevelInfo::Find(newMap.MapName);
1438 				if(&existing != &defaultMap)
1439 					existing = newMap;
1440 				else
1441 					levelInfos.Push(newMap);
1442 			}
1443 			else if(sc->str.CompareNoCase("skill") == 0)
1444 			{
1445 				SkillInfoBlockParser(sc).Parse();
1446 			}
1447 			else
1448 				SkipBlock(sc);
1449 		}
1450 		else
1451 		{
1452 			if(sc->str.CompareNoCase("gameinfo") == 0)
1453 			{
1454 				GameInfoBlockParser(sc).Parse();
1455 			}
1456 			// Regular key words
1457 			else if(sc->str.CompareNoCase("clearepisodes") == 0) {}
1458 			else
1459 				SkipBlock(sc);
1460 		}
1461 	}
1462 }
1463 
G_ParseMapInfo(bool gameinfoPass)1464 void G_ParseMapInfo(bool gameinfoPass)
1465 {
1466 	int lastlump = 0;
1467 	int lump;
1468 
1469 	if((lump = Wads.GetNumForFullName(IWad::GetGame().Mapinfo)) != -1)
1470 		ParseMapInfoLump(lump, gameinfoPass);
1471 
1472 	while((lump = Wads.FindLump("MAPINFO", &lastlump)) != -1)
1473 		ParseMapInfoLump(lump, gameinfoPass);
1474 
1475 	while((lump = Wads.FindLump("ZMAPINFO", &lastlump)) != -1)
1476 		ParseMapInfoLump(lump, gameinfoPass);
1477 
1478 	// Sanity checks!
1479 	if(!gameinfoPass)
1480 	{
1481 		if(episodes.Size() == 0)
1482 			Quit("At least 1 episode must be defined.");
1483 
1484 		for(unsigned int i = 0;i < gameinfo.PlayerClasses.Size();++i)
1485 		{
1486 			const ClassDef *cls = ClassDef::FindClass(gameinfo.PlayerClasses[i]);
1487 			if(!cls || !cls->IsDescendantOf(NATIVE_CLASS(PlayerPawn)))
1488 				Quit("'%s' is not a valid player class!", gameinfo.PlayerClasses[i].GetChars());
1489 		}
1490 	}
1491 }
1492