1 /*
2 ** intermission_parser.cpp
3 ** Parser for intermission definitions in MAPINFO
4 ** (both new style and old style 'ENDGAME' blocks)
5 **
6 **---------------------------------------------------------------------------
7 ** Copyright 2010 Christoph Oelckers
8 ** All rights reserved.
9 **
10 ** Redistribution and use in source and binary forms, with or without
11 ** modification, are permitted provided that the following conditions
12 ** are met:
13 **
14 ** 1. Redistributions of source code must retain the above copyright
15 **    notice, this list of conditions and the following disclaimer.
16 ** 2. Redistributions in binary form must reproduce the above copyright
17 **    notice, this list of conditions and the following disclaimer in the
18 **    documentation and/or other materials provided with the distribution.
19 ** 3. The name of the author may not be used to endorse or promote products
20 **    derived from this software without specific prior written permission.
21 **
22 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
23 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
26 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
27 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
31 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 **---------------------------------------------------------------------------
33 **
34 */
35 
36 
37 #include "intermission/intermission.h"
38 #include "g_level.h"
39 #include "w_wad.h"
40 #include "gi.h"
41 
42 
ReplaceIntermission(FName intname,FIntermissionDescriptor * desc)43 static void ReplaceIntermission(FName intname,FIntermissionDescriptor *desc)
44 {
45 	FIntermissionDescriptor ** pDesc = IntermissionDescriptors.CheckKey(intname);
46 	if (pDesc != NULL && *pDesc != NULL) delete *pDesc;
47 	IntermissionDescriptors[intname] = desc;
48 }
49 
DeinitIntermissions()50 void DeinitIntermissions()
51 {
52 	FIntermissionDescriptorList::Iterator it(IntermissionDescriptors);
53 
54 	FIntermissionDescriptorList::Pair *pair;
55 
56 	while (it.NextPair(pair))
57 	{
58 		delete pair->Value;
59 		pair->Value = NULL;
60 	}
61 	IntermissionDescriptors.Clear();
62 }
63 
64 //==========================================================================
65 //
66 // FIntermissionAction
67 //
68 //==========================================================================
69 
FIntermissionAction()70 FIntermissionAction::FIntermissionAction()
71 {
72 	mSize = sizeof(FIntermissionAction);
73 	mClass = RUNTIME_CLASS(DIntermissionScreen);
74 	mMusicOrder =
75 	mCdId =
76 	mCdTrack =
77 	mDuration = 0;
78 	mFlatfill = false;
79 	mMusicLooping = true;
80 }
81 
ParseKey(FScanner & sc)82 bool FIntermissionAction::ParseKey(FScanner &sc)
83 {
84 	if (sc.Compare("music"))
85 	{
86 		sc.MustGetToken('=');
87 		sc.MustGetToken(TK_StringConst);
88 		mMusic = sc.String;
89 		mMusicOrder = 0;
90 		if (sc.CheckToken(','))
91 		{
92 			sc.MustGetToken(TK_IntConst);
93 			mMusicOrder = sc.Number;
94 		}
95 		return true;
96 	}
97 	else if (sc.Compare("cdmusic"))
98 	{
99 		sc.MustGetToken('=');
100 		sc.MustGetToken(TK_IntConst);
101 		mCdTrack = sc.Number;
102 		mCdId = 0;
103 		if (sc.CheckToken(','))
104 		{
105 			sc.MustGetToken(TK_IntConst);
106 			mCdId = sc.Number;
107 		}
108 		return true;
109 	}
110 	else if (sc.Compare("Time"))
111 	{
112 		sc.MustGetToken('=');
113 		if (!sc.CheckToken('-'))
114 		{
115 			sc.MustGetFloat();
116 			mDuration = xs_RoundToInt(sc.Float*TICRATE);
117 		}
118 		else
119 		{
120 			sc.MustGetToken(TK_IntConst);
121 			mDuration = sc.Number;
122 		}
123 		return true;
124 	}
125 	else if (sc.Compare("Background"))
126 	{
127 		sc.MustGetToken('=');
128 		sc.MustGetToken(TK_StringConst);
129 		mBackground = sc.String;
130 		mFlatfill = 0;
131 		if (sc.CheckToken(','))
132 		{
133 			sc.MustGetToken(TK_IntConst);
134 			mFlatfill = !!sc.Number;
135 			if (sc.CheckToken(','))
136 			{
137 				sc.MustGetToken(TK_StringConst);
138 				mPalette = sc.String;
139 			}
140 		}
141 		return true;
142 	}
143 	else if (sc.Compare("Sound"))
144 	{
145 		sc.MustGetToken('=');
146 		sc.MustGetToken(TK_StringConst);
147 		mSound = sc.String;
148 		return true;
149 	}
150 	else if (sc.Compare("Draw"))
151 	{
152 		FIntermissionPatch *pat = &mOverlays[mOverlays.Reserve(1)];
153 		sc.MustGetToken('=');
154 		sc.MustGetToken(TK_StringConst);
155 		pat->mName = sc.String;
156 		sc.MustGetToken(',');
157 		sc.MustGetToken(TK_IntConst);
158 		pat->x = sc.Number;
159 		sc.MustGetToken(',');
160 		sc.MustGetToken(TK_IntConst);
161 		pat->y = sc.Number;
162 		pat->mCondition = NAME_None;
163 		return true;
164 	}
165 	else if (sc.Compare("DrawConditional"))
166 	{
167 		FIntermissionPatch *pat = &mOverlays[mOverlays.Reserve(1)];
168 		sc.MustGetToken('=');
169 		sc.MustGetToken(TK_StringConst);
170 		pat->mCondition = sc.String;
171 		sc.MustGetToken(',');
172 		sc.MustGetToken(TK_StringConst);
173 		pat->mName = sc.String;
174 		sc.MustGetToken(',');
175 		sc.MustGetToken(TK_IntConst);
176 		pat->x = sc.Number;
177 		sc.MustGetToken(',');
178 		sc.MustGetToken(TK_IntConst);
179 		pat->y = sc.Number;
180 		return true;
181 	}
182 	else return false;
183 }
184 
185 //==========================================================================
186 //
187 // FIntermissionActionFader
188 //
189 //==========================================================================
190 
FIntermissionActionFader()191 FIntermissionActionFader::FIntermissionActionFader()
192 {
193 	mSize = sizeof(FIntermissionActionFader);
194 	mClass = RUNTIME_CLASS(DIntermissionScreenFader);
195 	mFadeType = FADE_In;
196 }
197 
ParseKey(FScanner & sc)198 bool FIntermissionActionFader::ParseKey(FScanner &sc)
199 {
200 	struct FadeType
201 	{
202 		const char *Name;
203 		EFadeType Type;
204 	}
205 	const FT[] = {
206 		{ "FadeIn", FADE_In },
207 		{ "FadeOut", FADE_Out },
208 		{ NULL, FADE_In }
209 	};
210 
211 	if (sc.Compare("FadeType"))
212 	{
213 		sc.MustGetToken('=');
214 		sc.MustGetToken(TK_Identifier);
215 		int v = sc.MatchString(&FT[0].Name, sizeof(FT[0]));
216 		if (v != -1) mFadeType = FT[v].Type;
217 		return true;
218 	}
219 	else return Super::ParseKey(sc);
220 }
221 
222 //==========================================================================
223 //
224 // FIntermissionActionWiper
225 //
226 //==========================================================================
227 
FIntermissionActionWiper()228 FIntermissionActionWiper::FIntermissionActionWiper()
229 {
230 	mSize = sizeof(FIntermissionActionWiper);
231 	mClass = WIPER_ID;
232 	mWipeType = GS_FORCEWIPE;
233 }
234 
ParseKey(FScanner & sc)235 bool FIntermissionActionWiper::ParseKey(FScanner &sc)
236 {
237 	struct WipeType
238 	{
239 		const char *Name;
240 		gamestate_t Type;
241 	}
242 	const FT[] = {
243 		{ "Crossfade", GS_FORCEWIPEFADE },
244 		{ "Melt", GS_FORCEWIPEMELT },
245 		{ "Burn", GS_FORCEWIPEBURN },
246 		{ "Default", GS_FORCEWIPE },
247 		{ NULL, GS_FORCEWIPE }
248 	};
249 
250 	if (sc.Compare("WipeType"))
251 	{
252 		sc.MustGetToken('=');
253 		sc.MustGetToken(TK_Identifier);
254 		int v = sc.MatchString(&FT[0].Name, sizeof(FT[0]));
255 		if (v != -1) mWipeType = FT[v].Type;
256 		return true;
257 	}
258 	else return Super::ParseKey(sc);
259 }
260 
261 //==========================================================================
262 //
263 // FIntermissionActionFader
264 //
265 //==========================================================================
266 
FIntermissionActionTextscreen()267 FIntermissionActionTextscreen::FIntermissionActionTextscreen()
268 {
269 	mSize = sizeof(FIntermissionActionTextscreen);
270 	mClass = RUNTIME_CLASS(DIntermissionScreenText);
271 	mTextSpeed = 2;
272 	mTextX = -1;	// use gameinfo defaults
273 	mTextY = -1;
274 	mTextColor = CR_UNTRANSLATED;
275 	mTextDelay = 10;
276 }
277 
ParseKey(FScanner & sc)278 bool FIntermissionActionTextscreen::ParseKey(FScanner &sc)
279 {
280 	if (sc.Compare("Position"))
281 	{
282 		sc.MustGetToken('=');
283 		sc.MustGetToken(TK_IntConst);
284 		mTextX = sc.Number;
285 		sc.MustGetToken(',');
286 		sc.MustGetToken(TK_IntConst);
287 		mTextY = sc.Number;
288 		return true;
289 	}
290 	else if (sc.Compare("TextLump"))
291 	{
292 		sc.MustGetToken('=');
293 		sc.MustGetToken(TK_StringConst);
294 		int lump = Wads.CheckNumForFullName(sc.String, true);
295 		if (lump > 0)
296 		{
297 			mText = Wads.ReadLump(lump).GetString();
298 		}
299 		else
300 		{
301 			// only print an error if coming from a PWAD
302 			if (Wads.GetLumpFile(sc.LumpNum) > 1)
303 				sc.ScriptMessage("Unknown text lump '%s'", sc.String);
304 			mText.Format("Unknown text lump '%s'", sc.String);
305 		}
306 		return true;
307 	}
308 	else if (sc.Compare("Text"))
309 	{
310 		sc.MustGetToken('=');
311 		do
312 		{
313 			sc.MustGetToken(TK_StringConst);
314 			mText << sc.String << '\n';
315 		}
316 		while (sc.CheckToken(','));
317 		return true;
318 	}
319 	else if (sc.Compare("TextColor"))
320 	{
321 		sc.MustGetToken('=');
322 		sc.MustGetToken(TK_StringConst);
323 		mTextColor = V_FindFontColor(sc.String);
324 		return true;
325 	}
326 	else if (sc.Compare("TextDelay"))
327 	{
328 		sc.MustGetToken('=');
329 		if (!sc.CheckToken('-'))
330 		{
331 			sc.MustGetFloat();
332 			mTextDelay = xs_RoundToInt(sc.Float*TICRATE);
333 		}
334 		else
335 		{
336 			sc.MustGetToken(TK_IntConst);
337 			mTextDelay = sc.Number;
338 		}
339 		return true;
340 	}
341 	else if (sc.Compare("textspeed"))
342 	{
343 		sc.MustGetToken('=');
344 		sc.MustGetToken(TK_IntConst);
345 		mTextSpeed = sc.Number;
346 		return true;
347 	}
348 	else return Super::ParseKey(sc);
349 }
350 
351 //==========================================================================
352 //
353 // FIntermissionAction
354 //
355 //==========================================================================
356 
FIntermissionActionCast()357 FIntermissionActionCast::FIntermissionActionCast()
358 {
359 	mSize = sizeof(FIntermissionActionCast);
360 	mClass = RUNTIME_CLASS(DIntermissionScreenCast);
361 }
362 
ParseKey(FScanner & sc)363 bool FIntermissionActionCast::ParseKey(FScanner &sc)
364 {
365 	if (sc.Compare("CastName"))
366 	{
367 		sc.MustGetToken('=');
368 		sc.MustGetToken(TK_StringConst);
369 		mName = sc.String;
370 		return true;
371 	}
372 	else if (sc.Compare("CastClass"))
373 	{
374 		sc.MustGetToken('=');
375 		sc.MustGetToken(TK_StringConst);
376 		mCastClass = sc.String;
377 		return true;
378 	}
379 	else if (sc.Compare("AttackSound"))
380 	{
381 		static const char *const seqs[] = {"Missile", "Melee", NULL};
382 		FCastSound *cs = &mCastSounds[mCastSounds.Reserve(1)];
383 		sc.MustGetToken('=');
384 		sc.MustGetToken(TK_StringConst);
385 		cs->mSequence = (BYTE)sc.MatchString(seqs);
386 		sc.MustGetToken(',');
387 		sc.MustGetToken(TK_IntConst);
388 		cs->mIndex = (BYTE)sc.Number;
389 		sc.MustGetToken(',');
390 		sc.MustGetToken(TK_StringConst);
391 		cs->mSound = sc.String;
392 		return true;
393 	}
394 	else return Super::ParseKey(sc);
395 }
396 
397 //==========================================================================
398 //
399 // FIntermissionActionScroller
400 //
401 //==========================================================================
402 
FIntermissionActionScroller()403 FIntermissionActionScroller::FIntermissionActionScroller()
404 {
405 	mSize = sizeof(FIntermissionActionScroller);
406 	mClass = RUNTIME_CLASS(DIntermissionScreenScroller);
407 	mScrollDelay = 0;
408 	mScrollTime = 640;
409 	mScrollDir = SCROLL_Right;
410 }
411 
ParseKey(FScanner & sc)412 bool FIntermissionActionScroller::ParseKey(FScanner &sc)
413 {
414 	struct ScrollType
415 	{
416 		const char *Name;
417 		EScrollDir Type;
418 	}
419 	const ST[] = {
420 		{ "Left", SCROLL_Left },
421 		{ "Right", SCROLL_Right },
422 		{ "Up", SCROLL_Up },
423 		{ "Down", SCROLL_Down },
424 		{ NULL, SCROLL_Left }
425 	};
426 
427 	if (sc.Compare("ScrollDirection"))
428 	{
429 		sc.MustGetToken('=');
430 		sc.MustGetToken(TK_Identifier);
431 		int v = sc.MatchString(&ST[0].Name, sizeof(ST[0]));
432 		if (v != -1) mScrollDir = ST[v].Type;
433 		return true;
434 	}
435 	else if (sc.Compare("InitialDelay"))
436 	{
437 		sc.MustGetToken('=');
438 		if (!sc.CheckToken('-'))
439 		{
440 			sc.MustGetFloat();
441 			mScrollDelay = xs_RoundToInt(sc.Float*TICRATE);
442 		}
443 		else
444 		{
445 			sc.MustGetToken(TK_IntConst);
446 			mScrollDelay = sc.Number;
447 		}
448 		return true;
449 	}
450 	else if (sc.Compare("ScrollTime"))
451 	{
452 		sc.MustGetToken('=');
453 		if (!sc.CheckToken('-'))
454 		{
455 			sc.MustGetFloat();
456 			mScrollTime = xs_RoundToInt(sc.Float*TICRATE);
457 		}
458 		else
459 		{
460 			sc.MustGetToken(TK_IntConst);
461 			mScrollTime = sc.Number;
462 		}
463 		return true;
464 	}
465 	else if (sc.Compare("Background2"))
466 	{
467 		sc.MustGetToken('=');
468 		sc.MustGetToken(TK_StringConst);
469 		mSecondPic = sc.String;
470 		return true;
471 	}
472 	else return Super::ParseKey(sc);
473 }
474 
475 //==========================================================================
476 //
477 // ParseIntermission
478 //
479 //==========================================================================
480 
ParseIntermissionAction(FIntermissionDescriptor * desc)481 void FMapInfoParser::ParseIntermissionAction(FIntermissionDescriptor *desc)
482 {
483 	FIntermissionAction *ac = NULL;
484 
485 	sc.MustGetToken(TK_Identifier);
486 	if (sc.Compare("image"))
487 	{
488 		ac = new FIntermissionAction;
489 	}
490 	else if (sc.Compare("scroller"))
491 	{
492 		ac = new FIntermissionActionScroller;
493 	}
494 	else if (sc.Compare("cast"))
495 	{
496 		ac = new FIntermissionActionCast;
497 	}
498 	else if (sc.Compare("Fader"))
499 	{
500 		ac = new FIntermissionActionFader;
501 	}
502 	else if (sc.Compare("Wiper"))
503 	{
504 		ac = new FIntermissionActionWiper;
505 	}
506 	else if (sc.Compare("TextScreen"))
507 	{
508 		ac = new FIntermissionActionTextscreen;
509 	}
510 	else if (sc.Compare("GotoTitle"))
511 	{
512 		ac = new FIntermissionAction;
513 		ac->mClass = TITLE_ID;
514 	}
515 	else if (sc.Compare("Link"))
516 	{
517 		sc.MustGetToken('=');
518 		sc.MustGetToken(TK_Identifier);
519 		desc->mLink = sc.String;
520 		return;
521 	}
522 	else
523 	{
524 		sc.ScriptMessage("Unknown intermission type '%s'", sc.String);
525 	}
526 
527 	sc.MustGetToken('{');
528 	while (!sc.CheckToken('}'))
529 	{
530 		bool success = false;
531 		if (!sc.CheckToken(TK_Sound))
532 		{
533 			sc.MustGetToken(TK_Identifier);
534 		}
535 		if (ac != NULL)
536 		{
537 			success = ac->ParseKey(sc);
538 			if (!success)
539 			{
540 				sc.ScriptMessage("Unknown key name '%s'\n", sc.String);
541 			}
542 		}
543 		if (!success) SkipToNext();
544 	}
545 	if (ac != NULL) desc->mActions.Push(ac);
546 }
547 
548 //==========================================================================
549 //
550 // ParseIntermission
551 //
552 //==========================================================================
553 
ParseIntermission()554 void FMapInfoParser::ParseIntermission()
555 {
556 	sc.MustGetString();
557 	FName intname = sc.String;
558 	FIntermissionDescriptor *desc = new FIntermissionDescriptor();
559 
560 	ReplaceIntermission(intname, desc);
561 	sc.MustGetToken('{');
562 	while (!sc.CheckToken('}'))
563 	{
564 		ParseIntermissionAction(desc);
565 	}
566 }
567 
568 //==========================================================================
569 //
570 // Parse old style endsequence
571 //
572 //==========================================================================
573 
574 struct EndSequence
575 {
576 	SBYTE EndType;
577 	bool MusicLooping;
578 	bool PlayTheEnd;
579 	FString PicName;
580 	FString PicName2;
581 	FString Music;
582 };
583 
584 enum EndTypes
585 {
586 	END_Pic,
587 	END_Bunny,
588 	END_Cast,
589 	END_Demon
590 };
591 
ParseEndGame()592 FName FMapInfoParser::ParseEndGame()
593 {
594 	EndSequence newSeq;
595 	static int generated = 0;
596 
597 	newSeq.EndType = -1;
598 	newSeq.PlayTheEnd = false;
599 	newSeq.MusicLooping = true;
600 	while (!sc.CheckString("}"))
601 	{
602 		sc.MustGetString();
603 		if (sc.Compare("pic"))
604 		{
605 			ParseAssign();
606 			sc.MustGetString();
607 			newSeq.EndType = END_Pic;
608 			newSeq.PicName = sc.String;
609 		}
610 		else if (sc.Compare("hscroll"))
611 		{
612 			ParseAssign();
613 			newSeq.EndType = END_Bunny;
614 			sc.MustGetString();
615 			newSeq.PicName = sc.String;
616 			ParseComma();
617 			sc.MustGetString();
618 			newSeq.PicName2 = sc.String;
619 			if (CheckNumber())
620 				newSeq.PlayTheEnd = !!sc.Number;
621 		}
622 		else if (sc.Compare("vscroll"))
623 		{
624 			ParseAssign();
625 			newSeq.EndType = END_Demon;
626 			sc.MustGetString();
627 			newSeq.PicName = sc.String;
628 			ParseComma();
629 			sc.MustGetString();
630 			newSeq.PicName2 = sc.String;
631 		}
632 		else if (sc.Compare("cast"))
633 		{
634 			newSeq.EndType = END_Cast;
635 			if (newSeq.PicName.IsEmpty()) newSeq.PicName = "$bgcastcall";
636 		}
637 		else if (sc.Compare("music"))
638 		{
639 			ParseAssign();
640 			sc.MustGetString();
641 			newSeq.Music = sc.String;
642 			if (CheckNumber())
643 			{
644 				newSeq.MusicLooping = !!sc.Number;
645 			}
646 		}
647 		else
648 		{
649 			if (format_type == FMT_New)
650 			{
651 				// Unknown
652 				sc.ScriptMessage("Unknown property '%s' found in endgame definition\n", sc.String);
653 				SkipToNext();
654 			}
655 			else
656 			{
657 				sc.ScriptError("Unknown property '%s' found in endgame definition\n", sc.String);
658 			}
659 
660 		}
661 	}
662 	FIntermissionDescriptor *desc = new FIntermissionDescriptor;
663 	FIntermissionAction *action = NULL;
664 
665 	switch (newSeq.EndType)
666 	{
667 	case END_Pic:
668 		action = new FIntermissionAction;
669 		break;
670 
671 	case END_Bunny:
672 	{
673 		FIntermissionActionScroller *bunny = new FIntermissionActionScroller;
674 		bunny->mSecondPic = newSeq.PicName2;
675 		bunny->mScrollDir = SCROLL_Left;
676 		bunny->mScrollDelay = 230;
677 		bunny->mScrollTime = 640;
678 		bunny->mDuration = 1130;
679 		action = bunny;
680 		if (newSeq.PlayTheEnd) desc->mLink = "TheEnd";
681 		break;
682 	}
683 
684 	case END_Demon:
685 	{
686 		FIntermissionActionScroller *demon = new FIntermissionActionScroller;
687 		demon->mSecondPic = newSeq.PicName2;
688 		demon->mScrollDir = SCROLL_Up;
689 		demon->mScrollDelay = 70;
690 		demon->mScrollTime = 600;
691 		action = demon;
692 		break;
693 	}
694 
695 	case END_Cast:
696 		action = new FIntermissionAction;
697 		action->mDuration = 1;
698 		desc->mLink = "Doom2Cast";
699 		break;
700 	}
701 
702 	if (action == NULL)
703 	{
704 		sc.ScriptError("Endgame type was not defined");
705 		return NAME_None;	// We won't really get here.
706 	}
707 	else
708 	{
709 		action->mBackground = newSeq.PicName;
710 		action->mMusic = newSeq.Music;
711 		action->mMusicLooping = newSeq.MusicLooping;
712 		desc->mActions.Push(action);
713 
714 		FString seq;
715 		seq.Format("@EndSequence_%d_", generated++);
716 		ReplaceIntermission(seq, desc);
717 		return FName(seq);
718 	}
719 }
720 
721 //==========================================================================
722 //
723 // Checks map name for end sequence
724 //
725 //==========================================================================
726 
CheckEndSequence()727 FName FMapInfoParser::CheckEndSequence()
728 {
729 	const char *seqname = NULL;
730 
731 	if (sc.Compare("endgame"))
732 	{
733 		if (!sc.CheckString("{"))
734 		{
735 			// Make Demon Eclipse work again
736 			sc.UnGet();
737 			goto standard_endgame;
738 		}
739 		return ParseEndGame();
740 	}
741 	else if (strnicmp (sc.String, "EndGame", 7) == 0)
742 	{
743 		switch (sc.String[7])
744 		{
745 		case '1':	seqname = "Inter_Pic1";		break;
746 		case '2':	seqname = "Inter_Pic2";		break;
747 		case '3':	seqname = "Inter_Bunny";	break;
748 		case 'C':	seqname = "Inter_Cast";		break;
749 		case 'W':	seqname = "Inter_Underwater";	break;
750 		case 'S':	seqname = "Inter_Strife";	break;
751 	standard_endgame:
752 		default:	seqname = "Inter_Pic3";		break;
753 		}
754 	}
755 	else if (sc.Compare("endpic"))
756 	{
757 		ParseComma();
758 		sc.MustGetString ();
759 		FString seqname;
760 		seqname << "@EndPic_" << sc.String;
761 		FIntermissionDescriptor *desc = new FIntermissionDescriptor;
762 		FIntermissionAction *action = new FIntermissionAction;
763 		action->mBackground = sc.String;
764 		desc->mActions.Push(action);
765 		ReplaceIntermission(seqname, desc);
766 		return FName(seqname);
767 	}
768 	else if (sc.Compare("endbunny"))
769 	{
770 		seqname = "Inter_Bunny";
771 	}
772 	else if (sc.Compare("endcast"))
773 	{
774 		seqname = "Inter_Cast";
775 	}
776 	else if (sc.Compare("enddemon"))
777 	{
778 		seqname = "Inter_Demonscroll";
779 	}
780 	else if (sc.Compare("endchess"))
781 	{
782 		seqname = "Inter_Chess";
783 	}
784 	else if (sc.Compare("endunderwater"))
785 	{
786 		seqname = "Inter_Underwater";
787 	}
788 	else if (sc.Compare("endbuystrife"))
789 	{
790 		seqname = "Inter_BuyStrife";
791 	}
792 	else if (sc.Compare("endtitle"))
793 	{
794 		seqname = "Inter_Titlescreen";
795 	}
796 	else if (sc.Compare("endsequence"))
797 	{
798 		ParseComma();
799 		sc.MustGetString();
800 		seqname = sc.String;
801 	}
802 
803 	if (seqname != NULL)
804 	{
805 		return FName(seqname);
806 	}
807 	return NAME_None;
808 }
809 
810 
811 //==========================================================================
812 //
813 // Creates an intermission from the cluster's finale info
814 //
815 //==========================================================================
816 
F_StartFinale(const char * music,int musicorder,int cdtrack,unsigned int cdid,const char * flat,const char * text,INTBOOL textInLump,INTBOOL finalePic,INTBOOL lookupText,bool ending,FName endsequence)817 void F_StartFinale (const char *music, int musicorder, int cdtrack, unsigned int cdid, const char *flat,
818 					const char *text, INTBOOL textInLump, INTBOOL finalePic, INTBOOL lookupText,
819 					bool ending, FName endsequence)
820 {
821 	// Hexen's chess ending doesn't have a text screen, even if the cluster has a message defined.
822 	if (text != NULL && *text != 0 && endsequence != NAME_Inter_Chess)
823 	{
824 		FIntermissionActionTextscreen *textscreen = new FIntermissionActionTextscreen;
825 		if (textInLump)
826 		{
827 			int lump = Wads.CheckNumForFullName(text, true);
828 			if (lump > 0)
829 			{
830 				textscreen->mText = Wads.ReadLump(lump).GetString();
831 			}
832 			else
833 			{
834 				textscreen->mText.Format("Unknown text lump '%s'", text);
835 			}
836 		}
837 		else if (!lookupText)
838 		{
839 			textscreen->mText = text;
840 		}
841 		else
842 		{
843 			textscreen->mText << '$' << text;
844 		}
845 		textscreen->mTextDelay = 10;
846 		if (flat != NULL && *flat != 0)
847 		{
848 			textscreen->mBackground = flat;
849 		}
850 		else
851 		{
852 			// force a black screen if no texture is set.
853 			textscreen->mBackground = "-";
854 		}
855 		textscreen->mFlatfill = !finalePic;
856 
857 		if (music != NULL && *music != 0)
858 		{
859 			textscreen->mMusic = music;
860 			textscreen->mMusicOrder = musicorder;
861 		}
862 		if (cdtrack > 0)
863 		{
864 			textscreen->mCdTrack = cdtrack;
865 			textscreen->mCdId = cdid;
866 		}
867 		FIntermissionDescriptor *desc = new FIntermissionDescriptor;
868 		desc->mActions.Push(textscreen);
869 
870 		if (ending)
871 		{
872 			desc->mLink = endsequence;
873 			FIntermissionActionWiper *wiper = new FIntermissionActionWiper;
874 			desc->mActions.Push(wiper);
875 		}
876 
877 		F_StartIntermission(desc, true, ending? FSTATE_EndingGame : FSTATE_ChangingLevel);
878 	}
879 	else if (ending)
880 	{
881 		FIntermissionDescriptor **pdesc = IntermissionDescriptors.CheckKey(endsequence);
882 		if (pdesc != NULL)
883 		{
884 			F_StartIntermission(*pdesc, false, ending? FSTATE_EndingGame : FSTATE_ChangingLevel);
885 		}
886 	}
887 }
888