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 ≻
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