1 // Emacs style mode select   -*- C++ -*-
2 //-----------------------------------------------------------------------------
3 //
4 // $Id: f_finale.cpp 4542 2014-02-09 17:39:42Z dr_sean $
5 //
6 // Copyright (C) 1998-2006 by Randy Heit (ZDoom).
7 // Copyright (C) 2006-2014 by The Odamex Team.
8 //
9 // This program is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU General Public License
11 // as published by the Free Software Foundation; either version 2
12 // of the License, or (at your option) any later version.
13 //
14 // This program is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 // GNU General Public License for more details.
18 //
19 // DESCRIPTION:
20 //		Game completion, final screen animation.
21 //
22 //-----------------------------------------------------------------------------
23 
24 
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <ctype.h>
28 #include <math.h>
29 
30 #include "i_system.h"
31 #include "m_swap.h"
32 #include "z_zone.h"
33 #include "v_video.h"
34 #include "v_text.h"
35 #include "w_wad.h"
36 #include "s_sound.h"
37 #include "gstrings.h"
38 #include "doomstat.h"
39 #include "r_state.h"
40 #include "hu_stuff.h"
41 
42 #include "gi.h"
43 
44 // Stage of animation:
45 //	0 = text, 1 = art screen, 2 = character cast
46 unsigned int	finalestage;
47 
48 int	finalecount;
49 
50 #define TEXTSPEED		2
51 #define TEXTWAIT		250
52 
53 static const char*	finaletext;
54 static const char*	finaleflat;
55 
56 void	F_StartCast (void);
57 void	F_CastTicker (void);
58 BOOL	F_CastResponder (event_t *ev);
59 void	F_CastDrawer (void);
60 
61 //
62 // F_StartFinale
63 //
F_StartFinale(char * music,char * flat,const char * text)64 void F_StartFinale (char *music, char *flat, const char *text)
65 {
66 	gameaction = ga_nothing;
67 	gamestate = GS_FINALE;
68 	viewactive = false;
69 
70 	// Okay - IWAD dependend stuff.
71 	// This has been changed severly, and
72 	//	some stuff might have changed in the process.
73 	// [RH] More flexible now (even more severe changes)
74 	//  finaleflat, finaletext, and music are now
75 	//  determined in G_WorldDone() based on data in
76 	//  a level_info_t and a cluster_info_t.
77 
78 	if (*music == 0)
79 		S_ChangeMusic (std::string(gameinfo.finaleMusic, 8),
80 			!(gameinfo.flags & GI_NOLOOPFINALEMUSIC));
81 	else
82 		S_ChangeMusic (std::string(music, 8), !(gameinfo.flags & GI_NOLOOPFINALEMUSIC));
83 
84 	if (*flat == 0)
85 		finaleflat = gameinfo.finaleFlat;
86 	else
87 		finaleflat = flat;
88 
89 	if (text)
90 		finaletext = text;
91 	else
92 		finaletext = "Empty message";
93 
94 	finalestage = 0;
95 	finalecount = 0;
96 	V_SetBlend (0,0,0,0);
97 	S_StopAllChannels ();
98 }
99 
100 
101 
F_Responder(event_t * event)102 BOOL F_Responder (event_t *event)
103 {
104 	if (finalestage == 2)
105 		return F_CastResponder (event);
106 
107 	return false;
108 }
109 
110 
111 //
112 // F_Ticker
113 //
F_Ticker(void)114 void F_Ticker (void)
115 {
116 	// denis - do this serverside only
117 	// check for skipping
118 	// [RH] Non-commercial can be skipped now, too
119 	if (serverside && finalecount > 50 && finalestage != 1) {
120 		// go on to the next level
121 		// [RH] or just reveal the entire message if we're still ticking it
122 		Players::iterator it = players.begin();
123 		for (;it != players.end();++it)
124 			if (it->cmd.buttons)
125 				break;
126 
127 		if (it != players.end())
128 		{
129 			/*if (finalecount < (signed)(strlen (finaletext)*TEXTSPEED))
130 			{
131 				finalecount = strlen (finaletext)*TEXTSPEED;
132 			}
133 			else
134 			{*/
135 				if (!strncmp (level.nextmap, "EndGame", 7) ||
136 				(gamemode == retail_chex && !strncmp (level.nextmap, "E1M6", 4)))  	// [ML] Chex mode: game is over
137 				{																	// after E1M5
138 					if (level.nextmap[7] == 'C')
139 					{
140 						F_StartCast ();
141 					}
142 					else
143 					{
144 						finalecount = 0;
145 						finalestage = 1;
146 						wipegamestate = GS_FORCEWIPE;
147 						if (level.nextmap[7] == '3')
148 							S_StartMusic ("d_bunny");
149 					}
150 				}
151 				else
152 				{
153 					gameaction = ga_worlddone;
154 				}
155 			//}
156 		}
157 	}
158 
159 	// advance animation
160 	finalecount++;
161 
162 	if (finalestage == 2)
163 	{
164 		F_CastTicker ();
165 		return;
166 	}
167 }
168 
169 
170 
171 //
172 // F_TextWrite
173 //
174 extern patch_t *hu_font[HU_FONTSIZE];
175 
176 
F_TextWrite(void)177 void F_TextWrite (void)
178 {
179 	int 		w;
180 	int 		count;
181 	const char*		ch;
182 	int 		c;
183 	int 		cx;
184 	int 		cy;
185 
186 	// erase the entire screen to a tiled background
187 	{
188 		int lump = W_CheckNumForName (finaleflat, ns_flats);
189 		if (lump >= 0)
190 		{
191 			screen->FlatFill (0,0, screen->width, screen->height,
192 						(byte *)W_CacheLumpNum (lump, PU_CACHE));
193 		}
194 		else
195 		{
196 			screen->Clear (0, 0, screen->width, screen->height, 0);
197 		}
198 	}
199 	V_MarkRect (0, 0, screen->width, screen->height);
200 
201 	// draw some of the text onto the screen
202 	cx = 10;
203 	cy = 10;
204 	ch = finaletext;
205 
206 	if (finalecount < 11)
207 		return;
208 
209 	count = (finalecount - 10)/TEXTSPEED;
210 	for ( ; count ; count-- )
211 	{
212 		c = *ch++;
213 		if (!c)
214 			break;
215 		if (c == '\n')
216 		{
217 			cx = 10;
218 			cy += 11;
219 			continue;
220 		}
221 
222 		c = toupper(c) - HU_FONTSTART;
223 		if (c < 0 || c> HU_FONTSIZE)
224 		{
225 			cx += 4;
226 			continue;
227 		}
228 
229 		w = hu_font[c]->width();
230 		if (cx+w > screen->width)
231 			break;
232 		screen->DrawPatchClean (hu_font[c], cx, cy);
233 		cx+=w;
234 	}
235 
236 }
237 
238 //
239 // Final DOOM 2 animation
240 // Casting by id Software.
241 //	 in order of appearance
242 //
243 typedef struct
244 {
245 	const char		*name;
246 	mobjtype_t	type;
247 } castinfo_t;
248 
249 castinfo_t		castorder[] = {
250 	{NULL, MT_POSSESSED},
251 	{NULL, MT_SHOTGUY},
252 	{NULL, MT_CHAINGUY},
253 	{NULL, MT_TROOP},
254 	{NULL, MT_SERGEANT},
255 	{NULL, MT_SKULL},
256 	{NULL, MT_HEAD},
257 	{NULL, MT_KNIGHT},
258 	{NULL, MT_BRUISER},
259 	{NULL, MT_BABY},
260 	{NULL, MT_PAIN},
261 	{NULL, MT_UNDEAD},
262 	{NULL, MT_FATSO},
263 	{NULL, MT_VILE},
264 	{NULL, MT_SPIDER},
265 	{NULL, MT_CYBORG},
266 	{NULL, MT_PLAYER},
267 
268 	{NULL, MT_UNKNOWNTHING}
269 };
270 
271 static int 			castnum;
272 static int 			casttics;
273 static int			castsprite;
274 static state_t*		caststate;
275 static BOOL	 		castdeath;
276 static int 			castframes;
277 static int 			castonmelee;
278 static BOOL	 		castattacking;
279 
280 
281 //
282 // F_StartCast
283 //
284 extern	gamestate_t 	wipegamestate;
285 
286 
F_StartCast(void)287 void F_StartCast (void)
288 {
289 	// [RH] Set the names for the cast
290 	castorder[0].name = GStrings(CC_ZOMBIE);
291 	castorder[1].name = GStrings(CC_SHOTGUN);
292 	castorder[2].name = GStrings(CC_HEAVY);
293 	castorder[3].name = GStrings(CC_IMP);
294 	castorder[4].name = GStrings(CC_DEMON);
295 	castorder[5].name = GStrings(CC_LOST);
296 	castorder[6].name = GStrings(CC_CACO);
297 	castorder[7].name = GStrings(CC_HELL);
298 	castorder[8].name = GStrings(CC_BARON);
299 	castorder[9].name = GStrings(CC_ARACH);
300 	castorder[10].name = GStrings(CC_PAIN);
301 	castorder[11].name = GStrings(CC_REVEN);
302 	castorder[12].name = GStrings(CC_MANCU);
303 	castorder[13].name = GStrings(CC_ARCH);
304 	castorder[14].name = GStrings(CC_SPIDER);
305 	castorder[15].name = GStrings(CC_CYBER);
306 	castorder[16].name = GStrings(CC_HERO);
307 
308 	wipegamestate = GS_FORCEWIPE;
309 	castnum = 0;
310 	caststate = &states[mobjinfo[castorder[castnum].type].seestate];
311 	castsprite = caststate->sprite;
312 	casttics = caststate->tics;
313 	castdeath = false;
314 	finalestage = 2;
315 	castframes = 0;
316 	castonmelee = 0;
317 	castattacking = false;
318 	S_ChangeMusic("d_evil", true);
319 }
320 
321 
322 //
323 // F_CastTicker
324 //
F_CastTicker(void)325 void F_CastTicker (void)
326 {
327 	int st;
328 	int atten;
329 
330 	if (--casttics > 0)
331 		return; 				// not time to change state yet
332 
333 	if (caststate->tics == -1 || caststate->nextstate == S_NULL)
334 	{
335 		// switch from deathstate to next monster
336 		castnum++;
337 		castdeath = false;
338 		if (castorder[castnum].name == NULL)
339 			castnum = 0;
340 		if (mobjinfo[castorder[castnum].type].seesound) {
341 			atten = ATTN_NONE;
342 			S_Sound (CHAN_VOICE, mobjinfo[castorder[castnum].type].seesound, 1, atten);
343 		}
344 		caststate = &states[mobjinfo[castorder[castnum].type].seestate];
345 		castsprite = caststate->sprite;
346 		castframes = 0;
347 	}
348 	else
349 	{
350 		const char *sfx;
351 
352 		// just advance to next state in animation
353 		if (caststate == &states[S_PLAY_ATK1])
354 			goto stopattack;	// Oh, gross hack!
355 		st = caststate->nextstate;
356 		caststate = &states[st];
357 		castframes++;
358 
359 		// sound hacks....
360 		switch (st)
361 		{
362 		  case S_PLAY_ATK1: 	sfx = "weapons/sshotf"; break;
363 		  case S_POSS_ATK2: 	sfx = "grunt/attack"; break;
364 		  case S_SPOS_ATK2: 	sfx = "shotguy/attack"; break;
365 		  case S_VILE_ATK2: 	sfx = "vile/start"; break;
366 		  case S_SKEL_FIST2:	sfx = "skeleton/swing"; break;
367 		  case S_SKEL_FIST4:	sfx = "skeleton/melee"; break;
368 		  case S_SKEL_MISS2:	sfx = "skeleton/attack"; break;
369 		  case S_FATT_ATK8:
370 		  case S_FATT_ATK5:
371 		  case S_FATT_ATK2: 	sfx = "fatso/attack"; break;
372 		  case S_CPOS_ATK2:
373 		  case S_CPOS_ATK3:
374 		  case S_CPOS_ATK4: 	sfx = "chainguy/attack"; break;
375 		  case S_TROO_ATK3: 	sfx = "imp/attack"; break;
376 		  case S_SARG_ATK2: 	sfx = "demon/melee"; break;
377 		  case S_BOSS_ATK2:
378 		  case S_BOS2_ATK2:
379 		  case S_HEAD_ATK2: 	sfx = "caco/attack"; break;
380 		  case S_SKULL_ATK2:	sfx = "skull/melee"; break;
381 		  case S_SPID_ATK2:
382 		  case S_SPID_ATK3: 	sfx = "spider/attack"; break;
383 		  case S_BSPI_ATK2: 	sfx = "baby/attack"; break;
384 		  case S_CYBER_ATK2:
385 		  case S_CYBER_ATK4:
386 		  case S_CYBER_ATK6:	sfx = "weapons/rocklf"; break;
387 		  case S_PAIN_ATK3: 	sfx = "skull/melee"; break;
388 		  default: sfx = 0; break;
389 		}
390 
391 		if (sfx) {
392 			S_StopAllChannels ();
393 			S_Sound (CHAN_WEAPON, sfx, 1, ATTN_NONE);
394 		}
395 	}
396 
397 	if (castframes == 12)
398 	{
399 		// go into attack frame
400 		castattacking = true;
401 		if (castonmelee)
402 			caststate=&states[mobjinfo[castorder[castnum].type].meleestate];
403 		else
404 			caststate=&states[mobjinfo[castorder[castnum].type].missilestate];
405 		castonmelee ^= 1;
406 		if (caststate == &states[S_NULL])
407 		{
408 			if (castonmelee)
409 				caststate=
410 					&states[mobjinfo[castorder[castnum].type].meleestate];
411 			else
412 				caststate=
413 					&states[mobjinfo[castorder[castnum].type].missilestate];
414 		}
415 	}
416 
417 	if (castattacking)
418 	{
419 		if (castframes == 24
420 			||	caststate == &states[mobjinfo[castorder[castnum].type].seestate] )
421 		{
422 		  stopattack:
423 			castattacking = false;
424 			castframes = 0;
425 			caststate = &states[mobjinfo[castorder[castnum].type].seestate];
426 		}
427 	}
428 
429 	casttics = caststate->tics;
430 	if (casttics == -1)
431 		casttics = 15;
432 }
433 
434 
435 //
436 // F_CastResponder
437 //
438 
F_CastResponder(event_t * ev)439 BOOL F_CastResponder (event_t* ev)
440 {
441 	if (ev->type != ev_keydown)
442 		return false;
443 
444 	if (castdeath)
445 		return true;					// already in dying frames
446 
447 	// go into death frame
448 	castdeath = true;
449 	caststate = &states[mobjinfo[castorder[castnum].type].deathstate];
450 	casttics = caststate->tics;
451 	castframes = 0;
452 	castattacking = false;
453 	if (mobjinfo[castorder[castnum].type].deathsound)
454 		S_Sound (CHAN_VOICE, mobjinfo[castorder[castnum].type].deathsound, 1, ATTN_NONE);
455 
456 	return true;
457 }
458 
459 //
460 // F_CastDrawer
461 //
F_CastDrawer(void)462 void F_CastDrawer (void)
463 {
464 	spritedef_t*		sprdef;
465 	spriteframe_t*		sprframe;
466 	int 				lump;
467 	BOOL	 			flip;
468 	patch_t*			patch;
469 
470 	// erase the entire screen to a background
471 	screen->DrawPatchIndirect (W_CachePatch ("BOSSBACK"), 0, 0);
472 
473 	screen->DrawTextClean (CR_RED,
474 		(screen->width - V_StringWidth (castorder[castnum].name) * CleanXfac)/2,
475 		(screen->height * 180) / 200, castorder[castnum].name);
476 
477 	// draw the current frame in the middle of the screen
478 	sprdef = &sprites[castsprite];
479 	sprframe = &sprdef->spriteframes[caststate->frame & FF_FRAMEMASK];
480 	lump = sprframe->lump[0];
481 	flip = (BOOL)sprframe->flip[0];
482 
483 	patch = W_CachePatch (lump);
484 	if (flip)
485 		screen->DrawPatchFlipped (patch, 160, 170);
486 	else
487 		screen->DrawPatchIndirect (patch, 160, 170);
488 }
489 
490 
491 //
492 // F_DrawPatchCol
493 //
494 
495 // Palettized version 8bpp
496 
F_DrawPatchColP(int x,const patch_t * patch,int col,const DCanvas * scrn)497 void F_DrawPatchColP (int x, const patch_t *patch, int col, const DCanvas *scrn)
498 {
499 	byte*		source;
500 	byte*		dest;
501 	byte*		desttop;
502 	unsigned	count;
503 	int			repeat;
504 	int			c;
505 	unsigned	step;
506 	unsigned	invstep;
507 	float		mul;
508 	float		fx;
509 	byte		p;
510 	int			pitch;
511 
512 	// [RH] figure out how many times to repeat this column
513 	// (for screens wider than 320 pixels)
514 	mul = scrn->width / (float)320;
515 	fx = (float)x;
516 	repeat = (int)(floor (mul*(fx+1)) - floor(mul*fx));
517 	if (repeat == 0)
518 		return;
519 
520 	// [RH] Remap virtual-x to real-x
521 	x = (int)floor (mul*x);
522 
523 	// [RH] Figure out per-row fixed-point step
524 	step = (200<<16) / scrn->height;
525 	invstep = (scrn->height<<16) / 200;
526 
527 	tallpost_t *post = (tallpost_t *)((byte *)patch + LELONG(patch->columnofs[col]));
528 	desttop = scrn->buffer + x;
529 	pitch = scrn->pitch;
530 
531 	// step through the posts in a column
532 	while (!post->end())
533 	{
534 		source = post->data();
535 		dest = desttop + ((post->topdelta*invstep)>>16)*pitch;
536 		count = (post->length * invstep) >> 16;
537 		c = 0;
538 
539 		switch (repeat) {
540 			case 1:
541 				do {
542 					*dest = source[c>>16];
543 					dest += pitch;
544 					c += step;
545 				} while (--count);
546 				break;
547 			case 2:
548 				do {
549 					p = source[c>>16];
550 					dest[0] = p;
551 					dest[1] = p;
552 					dest += pitch;
553 					c += step;
554 				} while (--count);
555 				break;
556 			case 3:
557 				do {
558 					p = source[c>>16];
559 					dest[0] = p;
560 					dest[1] = p;
561 					dest[2] = p;
562 					dest += pitch;
563 					c += step;
564 				} while (--count);
565 				break;
566 			case 4:
567 				do {
568 					p = source[c>>16];
569 					dest[0] = p;
570 					dest[1] = p;
571 					dest[2] = p;
572 					dest[3] = p;
573 					dest += pitch;
574 					c += step;
575 				} while (--count);
576 				break;
577 			default:
578 				{
579 					int count2;
580 
581 					do {
582 						p = source[c>>16];
583 						for (count2 = repeat; count2; count2--) {
584 							dest[count2] = p;
585 						}
586 						dest += pitch;
587 						c += step;
588 					} while (--count);
589 				}
590 				break;
591 		}
592 
593 		post = post->next();
594 	}
595 }
596 
597 // Direct version 32bpp:
598 
F_DrawPatchColD(int x,const patch_t * patch,int col,const DCanvas * scrn)599 void F_DrawPatchColD (int x, const patch_t *patch, int col, const DCanvas *scrn)
600 {
601 	byte*		source;
602 	argb_t*		dest;
603 	argb_t*		desttop;
604 	unsigned	count;
605 	int			repeat;
606 	int			c;
607 	unsigned	step;
608 	unsigned	invstep;
609 	float		mul;
610 	float		fx;
611 	argb_t		p;
612 	int			pitch;
613 
614 	// [RH] figure out how many times to repeat this column
615 	// (for screens wider than 320 pixels)
616 	mul = scrn->width / (float)320;
617 	fx = (float)x;
618 	repeat = (int)(floor (mul*(fx+1)) - floor(mul*fx));
619 	if (repeat == 0)
620 		return;
621 
622 	// [RH] Remap virtual-x to real-x
623 	x = (int)floor (mul*x);
624 
625 	// [RH] Figure out per-row fixed-point step
626 	step = (200<<16) / scrn->height;
627 	invstep = (scrn->height<<16) / 200;
628 
629 	tallpost_t *post = (tallpost_t *)((byte *)patch + LELONG(patch->columnofs[col]));
630 	desttop = (argb_t *)scrn->buffer + x;
631 	pitch = scrn->pitch / sizeof(argb_t);
632 
633 	shaderef_t pal = shaderef_t(&GetDefaultPalette()->maps, 0);
634 
635 	// step through the posts in a column
636 	while (!post->end())
637 	{
638 		source = post->data();
639 		dest = desttop + ((post->topdelta*invstep)>>16)*pitch;
640 		count = (post->length * invstep) >> 16;
641 		c = 0;
642 
643 		switch (repeat) {
644 			case 1:
645 				do {
646 					*dest = pal.shade(source[c>>16]);
647 					dest += pitch;
648 					c += step;
649 				} while (--count);
650 				break;
651 			case 2:
652 				do {
653 					p = pal.shade(source[c>>16]);
654 					dest[0] = p;
655 					dest[1] = p;
656 					dest += pitch;
657 					c += step;
658 				} while (--count);
659 				break;
660 			case 3:
661 				do {
662 					p = pal.shade(source[c>>16]);
663 					dest[0] = p;
664 					dest[1] = p;
665 					dest[2] = p;
666 					dest += pitch;
667 					c += step;
668 				} while (--count);
669 				break;
670 			case 4:
671 				do {
672 					p = pal.shade(source[c>>16]);
673 					dest[0] = p;
674 					dest[1] = p;
675 					dest[2] = p;
676 					dest[3] = p;
677 					dest += pitch;
678 					c += step;
679 				} while (--count);
680 				break;
681 			default:
682 				{
683 					int count2;
684 
685 					do {
686 						p = pal.shade(source[c>>16]);
687 						for (count2 = repeat; count2; count2--) {
688 							dest[count2] = p;
689 						}
690 						dest += pitch;
691 						c += step;
692 					} while (--count);
693 				}
694 				break;
695 		}
696 
697 		post = post->next();
698 	}
699 }
700 
701 
702 //
703 // F_BunnyScroll
704 //
F_BunnyScroll(void)705 void F_BunnyScroll (void)
706 {
707 	int 		scrolled;
708 	int 		x;
709 	patch_t*	p1;
710 	patch_t*	p2;
711 	char		name[10];
712 	int 		stage;
713 	static int	laststage;
714 
715 	p1 = W_CachePatch ("PFUB2");
716 	p2 = W_CachePatch ("PFUB1");
717 
718 	V_MarkRect (0, 0, screen->width, screen->height);
719 
720 	scrolled = 320 - (finalecount-230)/2;
721 	if (scrolled > 320)
722 		scrolled = 320;
723 	if (scrolled < 0)
724 		scrolled = 0;
725 
726 	if (screen->is8bit())
727 	{
728 		for ( x=0 ; x<320 ; x++)
729 		{
730 			if (x+scrolled < 320)
731 				F_DrawPatchColP (x, p1, x+scrolled, screen);
732 			else
733 				F_DrawPatchColP (x, p2, x+scrolled - 320, screen);
734 		}
735 	}
736 	else
737 	{
738 	for ( x=0 ; x<320 ; x++)
739 	{
740 		if (x+scrolled < 320)
741 				F_DrawPatchColD (x, p1, x+scrolled, screen);
742 		else
743 				F_DrawPatchColD (x, p2, x+scrolled - 320, screen);
744 		}
745 	}
746 
747 	if (finalecount < 1130)
748 		return;
749 	if (finalecount < 1180)
750 	{
751 		screen->DrawPatchIndirect (W_CachePatch ("END0"),
752 			(320-13*8)/2, (200-8*8)/2);
753 		laststage = 0;
754 		return;
755 	}
756 
757 	stage = (finalecount-1180) / 5;
758 	if (stage > 6)
759 		stage = 6;
760 	if (stage > laststage)
761 	{
762 		S_Sound (CHAN_WEAPON, "weapons/pistol", 1, ATTN_NONE);
763 		laststage = stage;
764 	}
765 
766 	sprintf (name,"END%i",stage);
767 	screen->DrawPatchIndirect (W_CachePatch (name),
768 		(320-13*8)/2, (200-8*8)/2);
769 }
770 
771 
772 //
773 // F_Drawer
774 //
F_Drawer(void)775 void F_Drawer (void)
776 {
777 	switch (finalestage)
778 	{
779 		case 0:
780 			F_TextWrite ();
781 			break;
782 
783 		case 1:
784 			switch (level.nextmap[7])
785 			{
786 				default:
787 				case '1':
788 					screen->DrawPatchIndirect (W_CachePatch (gameinfo.finalePage1), 0, 0);
789 					break;
790 				case '2':
791 					screen->DrawPatchIndirect (W_CachePatch (gameinfo.finalePage2), 0, 0);
792 					break;
793 				case '3':
794 					F_BunnyScroll ();
795 					break;
796 				case '4':
797 					screen->DrawPatchIndirect (W_CachePatch (gameinfo.finalePage3), 0, 0);
798 					break;
799 			}
800 			break;
801 
802 		case 2:
803 			F_CastDrawer ();
804 			break;
805 	}
806 }
807 
808 VERSION_CONTROL (f_finale_cpp, "$Id: f_finale.cpp 4542 2014-02-09 17:39:42Z dr_sean $")
809 
810