1 /*
2 ** intermission.cpp
3 ** Framework for intermissions (text screens, slideshows, etc)
4 **
5 **---------------------------------------------------------------------------
6 ** Copyright 2010 Christoph Oelckers
7 ** All rights reserved.
8 **
9 ** Redistribution and use in source and binary forms, with or without
10 ** modification, are permitted provided that the following conditions
11 ** are met:
12 **
13 ** 1. Redistributions of source code must retain the above copyright
14 **    notice, this list of conditions and the following disclaimer.
15 ** 2. Redistributions in binary form must reproduce the above copyright
16 **    notice, this list of conditions and the following disclaimer in the
17 **    documentation and/or other materials provided with the distribution.
18 ** 3. The name of the author may not be used to endorse or promote products
19 **    derived from this software without specific prior written permission.
20 **
21 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 **---------------------------------------------------------------------------
32 **
33 */
34 
35 #include "doomtype.h"
36 #include "doomstat.h"
37 #include "d_event.h"
38 #include "w_wad.h"
39 #include "gi.h"
40 #include "v_video.h"
41 #include "v_palette.h"
42 #include "d_main.h"
43 #include "gstrings.h"
44 #include "intermission/intermission.h"
45 #include "actor.h"
46 #include "d_player.h"
47 #include "r_state.h"
48 #include "r_data/r_translate.h"
49 #include "c_bind.h"
50 #include "g_level.h"
51 #include "p_conversation.h"
52 #include "menu/menu.h"
53 #include "d_net.h"
54 
55 FIntermissionDescriptorList IntermissionDescriptors;
56 
57 IMPLEMENT_CLASS(DIntermissionScreen)
58 IMPLEMENT_CLASS(DIntermissionScreenFader)
59 IMPLEMENT_CLASS(DIntermissionScreenText)
60 IMPLEMENT_CLASS(DIntermissionScreenCast)
61 IMPLEMENT_CLASS(DIntermissionScreenScroller)
62 IMPLEMENT_POINTY_CLASS(DIntermissionController)
63 	DECLARE_POINTER(mScreen)
64 END_POINTERS
65 
66 extern int		NoWipe;
67 
68 //==========================================================================
69 //
70 //
71 //
72 //==========================================================================
73 
Init(FIntermissionAction * desc,bool first)74 void DIntermissionScreen::Init(FIntermissionAction *desc, bool first)
75 {
76 	int lumpnum;
77 
78 	if (desc->mCdTrack == 0 || !S_ChangeCDMusic (desc->mCdTrack, desc->mCdId))
79 	{
80 		if (desc->mMusic.IsEmpty())
81 		{
82 			// only start the default music if this is the first action in an intermission
83 			if (first) S_ChangeMusic (gameinfo.finaleMusic, gameinfo.finaleOrder, desc->mMusicLooping);
84 		}
85 		else
86 		{
87 			S_ChangeMusic (desc->mMusic, desc->mMusicOrder, desc->mMusicLooping);
88 		}
89 	}
90 	mDuration = desc->mDuration;
91 
92 	const char *texname = desc->mBackground;
93 	if (*texname == '@')
94 	{
95 		char *pp;
96 		unsigned int v = strtoul(texname+1, &pp, 10) - 1;
97 		if (*pp == 0 && v < gameinfo.finalePages.Size())
98 		{
99 			texname = gameinfo.finalePages[v].GetChars();
100 		}
101 		else if (gameinfo.finalePages.Size() > 0)
102 		{
103 			texname = gameinfo.finalePages[0].GetChars();
104 		}
105 		else
106 		{
107 			texname = gameinfo.TitlePage.GetChars();
108 		}
109 	}
110 	else if (*texname == '$')
111 	{
112 		texname = GStrings[texname+1];
113 	}
114 	if (texname[0] != 0)
115 	{
116 		mBackground = TexMan.CheckForTexture(texname, FTexture::TEX_MiscPatch);
117 		mFlatfill = desc->mFlatfill;
118 	}
119 	S_Sound (CHAN_VOICE | CHAN_UI, desc->mSound, 1.0f, ATTN_NONE);
120 	if (desc->mPalette.IsNotEmpty() && (lumpnum = Wads.CheckNumForFullName(desc->mPalette, true)) > 0)
121 	{
122 		PalEntry *palette;
123 		const BYTE *orgpal;
124 		FMemLump lump;
125 		int i;
126 
127 		lump = Wads.ReadLump (lumpnum);
128 		orgpal = (BYTE *)lump.GetMem();
129 		palette = screen->GetPalette ();
130 		for (i = 256; i > 0; i--, orgpal += 3)
131 		{
132 			*palette++ = PalEntry (orgpal[0], orgpal[1], orgpal[2]);
133 		}
134 		screen->UpdatePalette ();
135 		mPaletteChanged = true;
136 		NoWipe = 1;
137 		M_EnableMenu(false);
138 	}
139 	mOverlays.Resize(desc->mOverlays.Size());
140 	for (unsigned i=0; i < mOverlays.Size(); i++)
141 	{
142 		mOverlays[i].x = desc->mOverlays[i].x;
143 		mOverlays[i].y = desc->mOverlays[i].y;
144 		mOverlays[i].mCondition = desc->mOverlays[i].mCondition;
145 		mOverlays[i].mPic = TexMan.CheckForTexture(desc->mOverlays[i].mName, FTexture::TEX_MiscPatch);
146 	}
147 	mTicker = 0;
148 }
149 
150 
Responder(event_t * ev)151 int DIntermissionScreen::Responder (event_t *ev)
152 {
153 	if (ev->type == EV_KeyDown)
154 	{
155 		return -1;
156 	}
157 	return 0;
158 }
159 
Ticker()160 int DIntermissionScreen::Ticker ()
161 {
162 	if (++mTicker >= mDuration && mDuration > 0) return -1;
163 	return 0;
164 }
165 
CheckOverlay(int i)166 bool DIntermissionScreen::CheckOverlay(int i)
167 {
168 	if (mOverlays[i].mCondition == NAME_Multiplayer && !multiplayer) return false;
169 	else if (mOverlays[i].mCondition != NAME_None)
170 	{
171 		if (multiplayer || players[0].mo == NULL) return false;
172 		const PClass *cls = PClass::FindClass(mOverlays[i].mCondition);
173 		if (cls == NULL) return false;
174 		if (!players[0].mo->IsKindOf(cls)) return false;
175 	}
176 	return true;
177 }
178 
Drawer()179 void DIntermissionScreen::Drawer ()
180 {
181 	if (mBackground.isValid())
182 	{
183 		if (!mFlatfill)
184 		{
185 			screen->DrawTexture (TexMan[mBackground], 0, 0, DTA_Fullscreen, true, TAG_DONE);
186 		}
187 		else
188 		{
189 			screen->FlatFill (0,0, SCREENWIDTH, SCREENHEIGHT, TexMan[mBackground]);
190 		}
191 	}
192 	else
193 	{
194 		screen->Clear (0, 0, SCREENWIDTH, SCREENHEIGHT, 0, 0);
195 	}
196 	for (unsigned i=0; i < mOverlays.Size(); i++)
197 	{
198 		if (CheckOverlay(i))
199 			screen->DrawTexture (TexMan[mOverlays[i].mPic], mOverlays[i].x, mOverlays[i].y, DTA_320x200, true, TAG_DONE);
200 	}
201 	if (!mFlatfill) screen->FillBorder (NULL);
202 }
203 
Destroy()204 void DIntermissionScreen::Destroy()
205 {
206 	if (mPaletteChanged)
207 	{
208 		PalEntry *palette;
209 		int i;
210 
211 		palette = screen->GetPalette ();
212 		for (i = 0; i < 256; ++i)
213 		{
214 			palette[i] = GPalette.BaseColors[i];
215 		}
216 		screen->UpdatePalette ();
217 		NoWipe = 5;
218 		mPaletteChanged = false;
219 		M_EnableMenu(true);
220 	}
221 	S_StopSound(CHAN_VOICE);
222 	Super::Destroy();
223 }
224 
225 //==========================================================================
226 //
227 //
228 //
229 //==========================================================================
230 
Init(FIntermissionAction * desc,bool first)231 void DIntermissionScreenFader::Init(FIntermissionAction *desc, bool first)
232 {
233 	Super::Init(desc, first);
234 	mType = static_cast<FIntermissionActionFader*>(desc)->mFadeType;
235 }
236 
237 //===========================================================================
238 //
239 // FadePic
240 //
241 //===========================================================================
242 
Responder(event_t * ev)243 int DIntermissionScreenFader::Responder (event_t *ev)
244 {
245 	if (ev->type == EV_KeyDown)
246 	{
247 		V_SetBlend(0,0,0,0);
248 		return -1;
249 	}
250 	return Super::Responder(ev);
251 }
252 
Ticker()253 int DIntermissionScreenFader::Ticker ()
254 {
255 	if (mFlatfill || !mBackground.isValid()) return -1;
256 	return Super::Ticker();
257 }
258 
Drawer()259 void DIntermissionScreenFader::Drawer ()
260 {
261 	if (!mFlatfill && mBackground.isValid())
262 	{
263 		double factor = clamp(double(mTicker) / mDuration, 0., 1.);
264 		if (mType == FADE_In) factor = 1.0 - factor;
265 		int color = MAKEARGB(xs_RoundToInt(factor*255), 0,0,0);
266 
267 		if (screen->Begin2D(false))
268 		{
269 			screen->DrawTexture (TexMan[mBackground], 0, 0, DTA_Fullscreen, true, DTA_ColorOverlay, color, TAG_DONE);
270 			for (unsigned i=0; i < mOverlays.Size(); i++)
271 			{
272 				if (CheckOverlay(i))
273 					screen->DrawTexture (TexMan[mOverlays[i].mPic], mOverlays[i].x, mOverlays[i].y, DTA_320x200, true, DTA_ColorOverlay, color, TAG_DONE);
274 			}
275 			screen->FillBorder (NULL);
276 		}
277 		else
278 		{
279 			V_SetBlend (0,0,0,int(256*factor));
280 			Super::Drawer();
281 		}
282 	}
283 }
284 
285 //==========================================================================
286 //
287 //
288 //
289 //==========================================================================
290 
Init(FIntermissionAction * desc,bool first)291 void DIntermissionScreenText::Init(FIntermissionAction *desc, bool first)
292 {
293 	Super::Init(desc, first);
294 	mText = static_cast<FIntermissionActionTextscreen*>(desc)->mText;
295 	if (mText[0] == '$') mText = GStrings(&mText[1]);
296 	mTextSpeed = static_cast<FIntermissionActionTextscreen*>(desc)->mTextSpeed;
297 	mTextX = static_cast<FIntermissionActionTextscreen*>(desc)->mTextX;
298 	if (mTextX < 0) mTextX =gameinfo.TextScreenX;
299 	mTextY = static_cast<FIntermissionActionTextscreen*>(desc)->mTextY;
300 	if (mTextY < 0) mTextY =gameinfo.TextScreenY;
301 	mTextLen = (int)strlen(mText);
302 	mTextDelay = static_cast<FIntermissionActionTextscreen*>(desc)->mTextDelay;
303 	mTextColor = static_cast<FIntermissionActionTextscreen*>(desc)->mTextColor;
304 	// For text screens, the duration only counts when the text is complete.
305 	if (mDuration > 0) mDuration += mTextDelay + mTextSpeed * mTextLen;
306 }
307 
Responder(event_t * ev)308 int DIntermissionScreenText::Responder (event_t *ev)
309 {
310 	if (ev->type == EV_KeyDown)
311 	{
312 		if (mTicker < mTextDelay + (mTextLen * mTextSpeed))
313 		{
314 			mTicker = mTextDelay + (mTextLen * mTextSpeed);
315 			return 1;
316 		}
317 	}
318 	return Super::Responder(ev);
319 }
320 
Drawer()321 void DIntermissionScreenText::Drawer ()
322 {
323 	Super::Drawer();
324 	if (mTicker >= mTextDelay)
325 	{
326 		FTexture *pic;
327 		int w;
328 		size_t count;
329 		int c;
330 		const FRemapTable *range;
331 		const char *ch = mText;
332 		const int kerning = SmallFont->GetDefaultKerning();
333 
334 		// Count number of rows in this text. Since it does not word-wrap, we just count
335 		// line feed characters.
336 		int numrows;
337 
338 		for (numrows = 1, c = 0; ch[c] != '\0'; ++c)
339 		{
340 			numrows += (ch[c] == '\n');
341 		}
342 
343 		int rowheight = SmallFont->GetHeight() * CleanYfac;
344 		int rowpadding = (gameinfo.gametype & (GAME_DoomStrifeChex) ? 3 : -1) * CleanYfac;
345 
346 		int cx = (mTextX - 160)*CleanXfac + screen->GetWidth() / 2;
347 		int cy = (mTextY - 100)*CleanYfac + screen->GetHeight() / 2;
348 		int startx = cx;
349 
350 		// Does this text fall off the end of the screen? If so, try to eliminate some margins first.
351 		while (rowpadding > 0 && cy + numrows * (rowheight + rowpadding) - rowpadding > screen->GetHeight())
352 		{
353 			rowpadding--;
354 		}
355 		// If it's still off the bottom, try to center it vertically.
356 		if (cy + numrows * (rowheight + rowpadding) - rowpadding > screen->GetHeight())
357 		{
358 			cy = (screen->GetHeight() - (numrows * (rowheight + rowpadding) - rowpadding)) / 2;
359 			// If it's off the top now, you're screwed. It's too tall to fit.
360 			if (cy < 0)
361 			{
362 				cy = 0;
363 			}
364 		}
365 		rowheight += rowpadding;
366 
367 		// draw some of the text onto the screen
368 		count = (mTicker - mTextDelay) / mTextSpeed;
369 		range = SmallFont->GetColorTranslation (mTextColor);
370 
371 		for ( ; count > 0 ; count-- )
372 		{
373 			c = *ch++;
374 			if (!c)
375 				break;
376 			if (c == '\n')
377 			{
378 				cx = startx;
379 				cy += rowheight;
380 				continue;
381 			}
382 
383 			pic = SmallFont->GetChar (c, &w);
384 			w += kerning;
385 			w *= CleanXfac;
386 			if (cx + w > SCREENWIDTH)
387 				continue;
388 			if (pic != NULL)
389 			{
390 				screen->DrawTexture (pic,
391 					cx,
392 					cy,
393 					DTA_Translation, range,
394 					DTA_CleanNoMove, true,
395 					TAG_DONE);
396 			}
397 			cx += w;
398 		}
399 	}
400 }
401 
402 //==========================================================================
403 //
404 //
405 //
406 //==========================================================================
407 
Init(FIntermissionAction * desc,bool first)408 void DIntermissionScreenCast::Init(FIntermissionAction *desc, bool first)
409 {
410 	Super::Init(desc, first);
411 	mName = static_cast<FIntermissionActionCast*>(desc)->mName;
412 	mClass = PClass::FindClass(static_cast<FIntermissionActionCast*>(desc)->mCastClass);
413 	if (mClass != NULL) mDefaults = GetDefaultByType(mClass);
414 	else
415 	{
416 		mDefaults = NULL;
417 		caststate = NULL;
418 		return;
419 	}
420 
421 	mCastSounds.Resize(static_cast<FIntermissionActionCast*>(desc)->mCastSounds.Size());
422 	for (unsigned i=0; i < mCastSounds.Size(); i++)
423 	{
424 		mCastSounds[i].mSequence = static_cast<FIntermissionActionCast*>(desc)->mCastSounds[i].mSequence;
425 		mCastSounds[i].mIndex = static_cast<FIntermissionActionCast*>(desc)->mCastSounds[i].mIndex;
426 		mCastSounds[i].mSound = static_cast<FIntermissionActionCast*>(desc)->mCastSounds[i].mSound;
427 	}
428 	caststate = mDefaults->SeeState;
429 	if (mClass->IsDescendantOf(RUNTIME_CLASS(APlayerPawn)))
430 	{
431 		advplayerstate = mDefaults->MissileState;
432 		casttranslation = translationtables[TRANSLATION_Players][consoleplayer];
433 	}
434 	else
435 	{
436 		advplayerstate = NULL;
437 		casttranslation = NULL;
438 		if (mDefaults->Translation != 0)
439 		{
440 			casttranslation = translationtables[GetTranslationType(mDefaults->Translation)]
441 												[GetTranslationIndex(mDefaults->Translation)];
442 		}
443 	}
444 	castdeath = false;
445 	castframes = 0;
446 	castonmelee = 0;
447 	castattacking = false;
448 	if (mDefaults->SeeSound)
449 	{
450 		S_Sound (CHAN_VOICE | CHAN_UI, mDefaults->SeeSound, 1, ATTN_NONE);
451 	}
452 }
453 
Responder(event_t * ev)454 int DIntermissionScreenCast::Responder (event_t *ev)
455 {
456 	if (ev->type != EV_KeyDown) return 0;
457 
458 	if (castdeath)
459 		return 1;					// already in dying frames
460 
461 	castdeath = true;
462 
463 	if (mClass != NULL)
464 	{
465 		FName label[] = {NAME_Death, NAME_Cast};
466 		caststate = mClass->ActorInfo->FindState(2, label);
467 		if (caststate == NULL) return -1;
468 
469 		casttics = caststate->GetTics();
470 		castframes = 0;
471 		castattacking = false;
472 
473 		if (mClass->IsDescendantOf(RUNTIME_CLASS(APlayerPawn)))
474 		{
475 			int snd = S_FindSkinnedSound(players[consoleplayer].mo, "*death");
476 			if (snd != 0) S_Sound (CHAN_VOICE | CHAN_UI, snd, 1, ATTN_NONE);
477 		}
478 		else if (mDefaults->DeathSound)
479 		{
480 			S_Sound (CHAN_VOICE | CHAN_UI, mDefaults->DeathSound, 1, ATTN_NONE);
481 		}
482 	}
483 	return true;
484 }
485 
PlayAttackSound()486 void DIntermissionScreenCast::PlayAttackSound()
487 {
488 	// sound hacks....
489 	if (caststate != NULL && castattacking)
490 	{
491 		for (unsigned i = 0; i < mCastSounds.Size(); i++)
492 		{
493 			if ((!!mCastSounds[i].mSequence) == (basestate != mDefaults->MissileState) &&
494 				(caststate == basestate + mCastSounds[i].mIndex))
495 			{
496 				S_StopAllChannels ();
497 				S_Sound (CHAN_WEAPON | CHAN_UI, mCastSounds[i].mSound, 1, ATTN_NONE);
498 				return;
499 			}
500 		}
501 	}
502 
503 }
504 
Ticker()505 int DIntermissionScreenCast::Ticker ()
506 {
507 	Super::Ticker();
508 
509 	if (--casttics > 0 && caststate != NULL)
510 		return 0; 				// not time to change state yet
511 
512 	if (caststate == NULL || caststate->GetTics() == -1 || caststate->GetNextState() == NULL ||
513 		(caststate->GetNextState() == caststate && castdeath))
514 	{
515 		return -1;
516 	}
517 	else
518 	{
519 		// just advance to next state in animation
520 		if (caststate == advplayerstate)
521 			goto stopattack;	// Oh, gross hack!
522 
523 		caststate = caststate->GetNextState();
524 
525 		PlayAttackSound();
526 		castframes++;
527 	}
528 
529 	if (castframes == 12 && !castdeath)
530 	{
531 		// go into attack frame
532 		castattacking = true;
533 		if (!mClass->IsDescendantOf(RUNTIME_CLASS(APlayerPawn)))
534 		{
535 			if (castonmelee)
536 				basestate = caststate = mDefaults->MeleeState;
537 			else
538 				basestate = caststate = mDefaults->MissileState;
539 			castonmelee ^= 1;
540 			if (caststate == NULL)
541 			{
542 				if (castonmelee)
543 					basestate = caststate = mDefaults->MeleeState;
544 				else
545 					basestate = caststate = mDefaults->MissileState;
546 			}
547 		}
548 		else
549 		{
550 			// The players use the melee state differently so it can't be used here
551 			basestate = caststate = mDefaults->MissileState;
552 		}
553 		PlayAttackSound();
554 	}
555 
556 	if (castattacking)
557 	{
558 		if (castframes == 24 || caststate == mDefaults->SeeState )
559 		{
560 		  stopattack:
561 			castattacking = false;
562 			castframes = 0;
563 			caststate = mDefaults->SeeState;
564 		}
565 	}
566 
567 	casttics = caststate->GetTics();
568 	if (casttics == -1)
569 		casttics = 15;
570 	return 0;
571 }
572 
Drawer()573 void DIntermissionScreenCast::Drawer ()
574 {
575 	spriteframe_t*		sprframe;
576 	FTexture*			pic;
577 
578 	Super::Drawer();
579 
580 	const char *name = mName;
581 	if (name != NULL)
582 	{
583 		if (*name == '$') name = GStrings(name+1);
584 		screen->DrawText (SmallFont, CR_UNTRANSLATED,
585 			(SCREENWIDTH - SmallFont->StringWidth (name) * CleanXfac)/2,
586 			(SCREENHEIGHT * 180) / 200,
587 			name,
588 			DTA_CleanNoMove, true, TAG_DONE);
589 	}
590 
591 	// draw the current frame in the middle of the screen
592 	if (caststate != NULL)
593 	{
594 		double castscalex = FIXED2DBL(mDefaults->scaleX);
595 		double castscaley = FIXED2DBL(mDefaults->scaleY);
596 
597 		int castsprite = caststate->sprite;
598 
599 		if (!(mDefaults->flags4 & MF4_NOSKIN) &&
600 			mDefaults->SpawnState != NULL && caststate->sprite == mDefaults->SpawnState->sprite &&
601 			mClass->IsDescendantOf(RUNTIME_CLASS(APlayerPawn)) &&
602 			skins != NULL)
603 		{
604 			// Only use the skin sprite if this class has not been removed from the
605 			// PlayerClasses list.
606 			for (unsigned i = 0; i < PlayerClasses.Size(); ++i)
607 			{
608 				if (PlayerClasses[i].Type == mClass)
609 				{
610 					FPlayerSkin *skin = &skins[players[consoleplayer].userinfo.GetSkin()];
611 					castsprite = skin->sprite;
612 
613 					if (!(mDefaults->flags4 & MF4_NOSKIN))
614 					{
615 						castscaley = FIXED2DBL(skin->ScaleY);
616 						castscalex = FIXED2DBL(skin->ScaleX);
617 					}
618 
619 				}
620 			}
621 		}
622 
623 		sprframe = &SpriteFrames[sprites[castsprite].spriteframes + caststate->GetFrame()];
624 		pic = TexMan(sprframe->Texture[0]);
625 
626 		screen->DrawTexture (pic, 160, 170,
627 			DTA_320x200, true,
628 			DTA_FlipX, sprframe->Flip & 1,
629 			DTA_DestHeightF, pic->GetScaledHeightDouble() * castscaley,
630 			DTA_DestWidthF, pic->GetScaledWidthDouble() * castscalex,
631 			DTA_RenderStyle, mDefaults->RenderStyle,
632 			DTA_Alpha, mDefaults->alpha,
633 			DTA_Translation, casttranslation,
634 			TAG_DONE);
635 	}
636 }
637 
638 //==========================================================================
639 //
640 //
641 //
642 //==========================================================================
643 
Init(FIntermissionAction * desc,bool first)644 void DIntermissionScreenScroller::Init(FIntermissionAction *desc, bool first)
645 {
646 	Super::Init(desc, first);
647 	mFirstPic = mBackground;
648 	mSecondPic = TexMan.CheckForTexture(static_cast<FIntermissionActionScroller*>(desc)->mSecondPic, FTexture::TEX_MiscPatch);
649 	mScrollDelay = static_cast<FIntermissionActionScroller*>(desc)->mScrollDelay;
650 	mScrollTime = static_cast<FIntermissionActionScroller*>(desc)->mScrollTime;
651 	mScrollDir = static_cast<FIntermissionActionScroller*>(desc)->mScrollDir;
652 }
653 
Responder(event_t * ev)654 int DIntermissionScreenScroller::Responder (event_t *ev)
655 {
656 	int res = Super::Responder(ev);
657 	if (res == -1)
658 	{
659 		mBackground = mSecondPic;
660 		mTicker = mScrollDelay + mScrollTime;
661 	}
662 	return res;
663 }
664 
Drawer()665 void DIntermissionScreenScroller::Drawer ()
666 {
667 	FTexture *tex = TexMan[mFirstPic];
668 	FTexture *tex2 = TexMan[mSecondPic];
669 	if (mTicker >= mScrollDelay && mTicker < mScrollDelay + mScrollTime && tex != NULL && tex2 != NULL)
670 	{
671 
672 		int fwidth = tex->GetScaledWidth();
673 		int fheight = tex->GetScaledHeight();
674 
675 		double xpos1 = 0, ypos1 = 0, xpos2 = 0, ypos2 = 0;
676 
677 		switch (mScrollDir)
678 		{
679 		case SCROLL_Up:
680 			ypos1 = double(mTicker - mScrollDelay) * fheight / mScrollTime;
681 			ypos2 = ypos1 - fheight;
682 			break;
683 
684 		case SCROLL_Down:
685 			ypos1 = -double(mTicker - mScrollDelay) * fheight / mScrollTime;
686 			ypos2 = ypos1 + fheight;
687 			break;
688 
689 		case SCROLL_Left:
690 		default:
691 			xpos1 = double(mTicker - mScrollDelay) * fwidth / mScrollTime;
692 			xpos2 = xpos1 - fwidth;
693 			break;
694 
695 		case SCROLL_Right:
696 			xpos1 = -double(mTicker - mScrollDelay) * fwidth / mScrollTime;
697 			xpos2 = xpos1 + fwidth;
698 			break;
699 		}
700 
701 		screen->DrawTexture (tex, xpos1, ypos1,
702 			DTA_VirtualWidth, fwidth,
703 			DTA_VirtualHeight, fheight,
704 			DTA_Masked, false,
705 			TAG_DONE);
706 		screen->DrawTexture (tex2, xpos2, ypos2,
707 			DTA_VirtualWidth, fwidth,
708 			DTA_VirtualHeight, fheight,
709 			DTA_Masked, false,
710 			TAG_DONE);
711 
712 		screen->FillBorder (NULL);
713 		mBackground = mSecondPic;
714 	}
715 	else
716 	{
717 		Super::Drawer();
718 	}
719 }
720 
721 
722 //==========================================================================
723 //
724 //
725 //
726 //==========================================================================
727 
728 DIntermissionController *DIntermissionController::CurrentIntermission;
729 
DIntermissionController(FIntermissionDescriptor * Desc,bool DeleteDesc,BYTE state)730 DIntermissionController::DIntermissionController(FIntermissionDescriptor *Desc, bool DeleteDesc, BYTE state)
731 {
732 	mDesc = Desc;
733 	mDeleteDesc = DeleteDesc;
734 	mIndex = 0;
735 	mAdvance = false;
736 	mSentAdvance = false;
737 	mScreen = NULL;
738 	mFirst = true;
739 	mGameState = state;
740 
741 	// If the intermission finishes straight away then cancel the wipe.
742 	if(!NextPage())
743 		wipegamestate = GS_FINALE;
744 }
745 
NextPage()746 bool DIntermissionController::NextPage ()
747 {
748 	FTextureID bg;
749 	bool fill = false;
750 
751 	if (mIndex == (int)mDesc->mActions.Size() && mDesc->mLink == NAME_None)
752 	{
753 		// last page
754 		return false;
755 	}
756 
757 	if (mScreen != NULL)
758 	{
759 		bg = mScreen->GetBackground(&fill);
760 		mScreen->Destroy();
761 	}
762 again:
763 	while ((unsigned)mIndex < mDesc->mActions.Size())
764 	{
765 		FIntermissionAction *action = mDesc->mActions[mIndex++];
766 		if (action->mClass == WIPER_ID)
767 		{
768 			wipegamestate = static_cast<FIntermissionActionWiper*>(action)->mWipeType;
769 		}
770 		else if (action->mClass == TITLE_ID)
771 		{
772 			Destroy();
773 			D_StartTitle ();
774 			return false;
775 		}
776 		else
777 		{
778 			// create page here
779 			mScreen = (DIntermissionScreen*)action->mClass->CreateNew();
780 			mScreen->SetBackground(bg, fill);	// copy last screen's background before initializing
781 			mScreen->Init(action, mFirst);
782 			mFirst = false;
783 			return true;
784 		}
785 	}
786 	if (mDesc->mLink != NAME_None)
787 	{
788 		FIntermissionDescriptor **pDesc = IntermissionDescriptors.CheckKey(mDesc->mLink);
789 		if (pDesc != NULL)
790 		{
791 			if (mDeleteDesc) delete mDesc;
792 			mDeleteDesc = false;
793 			mIndex = 0;
794 			mDesc = *pDesc;
795 			goto again;
796 		}
797 	}
798 	return false;
799 }
800 
Responder(event_t * ev)801 bool DIntermissionController::Responder (event_t *ev)
802 {
803 	if (mScreen != NULL)
804 	{
805 		if (!mScreen->mPaletteChanged && ev->type == EV_KeyDown)
806 		{
807 			const char *cmd = Bindings.GetBind (ev->data1);
808 
809 			if (cmd != NULL &&
810 				(!stricmp(cmd, "toggleconsole") ||
811 				 !stricmp(cmd, "screenshot")))
812 			{
813 				return false;
814 			}
815 		}
816 
817 		if (mScreen->mTicker < 2) return false;	// prevent some leftover events from auto-advancing
818 		int res = mScreen->Responder(ev);
819 		if (res == -1 && !mSentAdvance)
820 		{
821 			Net_WriteByte(DEM_ADVANCEINTER);
822 			mSentAdvance = true;
823 		}
824 		return !!res;
825 	}
826 	return false;
827 }
828 
Ticker()829 void DIntermissionController::Ticker ()
830 {
831 	if (mAdvance)
832 	{
833 		mSentAdvance = false;
834 	}
835 	if (mScreen != NULL)
836 	{
837 		mAdvance |= (mScreen->Ticker() == -1);
838 	}
839 	if (mAdvance)
840 	{
841 		mAdvance = false;
842 		if (!NextPage())
843 		{
844 			switch (mGameState)
845 			{
846 			case FSTATE_InLevel:
847 				if (level.cdtrack == 0 || !S_ChangeCDMusic (level.cdtrack, level.cdid))
848 					S_ChangeMusic (level.Music, level.musicorder);
849 				gamestate = GS_LEVEL;
850 				wipegamestate = GS_LEVEL;
851 				P_ResumeConversation ();
852 				viewactive = true;
853 				Destroy();
854 				break;
855 
856 			case FSTATE_ChangingLevel:
857 				gameaction = ga_worlddone;
858 				Destroy();
859 				break;
860 
861 			default:
862 				break;
863 			}
864 		}
865 	}
866 }
867 
Drawer()868 void DIntermissionController::Drawer ()
869 {
870 	if (mScreen != NULL)
871 	{
872 		mScreen->Drawer();
873 	}
874 }
875 
Destroy()876 void DIntermissionController::Destroy ()
877 {
878 	Super::Destroy();
879 	if (mScreen != NULL) mScreen->Destroy();
880 	if (mDeleteDesc) delete mDesc;
881 	mDesc = NULL;
882 	if (CurrentIntermission == this) CurrentIntermission = NULL;
883 }
884 
885 
886 //==========================================================================
887 //
888 // starts a new intermission
889 //
890 //==========================================================================
891 
F_StartIntermission(FIntermissionDescriptor * desc,bool deleteme,BYTE state)892 void F_StartIntermission(FIntermissionDescriptor *desc, bool deleteme, BYTE state)
893 {
894 	if (DIntermissionController::CurrentIntermission != NULL)
895 	{
896 		DIntermissionController::CurrentIntermission->Destroy();
897 	}
898 	V_SetBlend (0,0,0,0);
899 	S_StopAllChannels ();
900 	gameaction = ga_nothing;
901 	gamestate = GS_FINALE;
902 	if (state == FSTATE_InLevel) wipegamestate = GS_FINALE;	// don't wipe when within a level.
903 	viewactive = false;
904 	automapactive = false;
905 	DIntermissionController::CurrentIntermission = new DIntermissionController(desc, deleteme, state);
906 	GC::WriteBarrier(DIntermissionController::CurrentIntermission);
907 }
908 
909 
910 //==========================================================================
911 //
912 // starts a new intermission
913 //
914 //==========================================================================
915 
F_StartIntermission(FName seq,BYTE state)916 void F_StartIntermission(FName seq, BYTE state)
917 {
918 	FIntermissionDescriptor **pdesc = IntermissionDescriptors.CheckKey(seq);
919 	if (pdesc != NULL)
920 	{
921 		F_StartIntermission(*pdesc, false, state);
922 	}
923 }
924 
925 
926 //==========================================================================
927 //
928 // Called by main loop.
929 //
930 //==========================================================================
931 
F_Responder(event_t * ev)932 bool F_Responder (event_t* ev)
933 {
934 	if (DIntermissionController::CurrentIntermission != NULL)
935 	{
936 		return DIntermissionController::CurrentIntermission->Responder(ev);
937 	}
938 	return false;
939 }
940 
941 //==========================================================================
942 //
943 // Called by main loop.
944 //
945 //==========================================================================
946 
F_Ticker()947 void F_Ticker ()
948 {
949 	if (DIntermissionController::CurrentIntermission != NULL)
950 	{
951 		DIntermissionController::CurrentIntermission->Ticker();
952 	}
953 }
954 
955 //==========================================================================
956 //
957 // Called by main loop.
958 //
959 //==========================================================================
960 
F_Drawer()961 void F_Drawer ()
962 {
963 	if (DIntermissionController::CurrentIntermission != NULL)
964 	{
965 		DIntermissionController::CurrentIntermission->Drawer();
966 	}
967 }
968 
969 
970 //==========================================================================
971 //
972 // Called by main loop.
973 //
974 //==========================================================================
975 
F_EndFinale()976 void F_EndFinale ()
977 {
978 	if (DIntermissionController::CurrentIntermission != NULL)
979 	{
980 		DIntermissionController::CurrentIntermission->Destroy();
981 		DIntermissionController::CurrentIntermission = NULL;
982 	}
983 }
984 
985 //==========================================================================
986 //
987 // Called by net loop.
988 //
989 //==========================================================================
990 
F_AdvanceIntermission()991 void F_AdvanceIntermission()
992 {
993 	if (DIntermissionController::CurrentIntermission != NULL)
994 	{
995 		DIntermissionController::CurrentIntermission->mAdvance = true;
996 	}
997 }
998 
999