1 //
2 // Copyright(C) 1993-1996 Id Software, Inc.
3 // Copyright(C) 2005-2014 Simon Howard
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // DESCRIPTION:
16 //	Game completion, final screen animation.
17 //
18 
19 
20 #include <stdio.h>
21 #include <ctype.h>
22 #include <stdlib.h>
23 
24 // Functions.
25 #include "deh_main.h"
26 #include "i_system.h"
27 #include "i_swap.h"
28 #include "z_zone.h"
29 #include "v_video.h"
30 #include "w_wad.h"
31 #include "s_sound.h"
32 
33 // Data.
34 #include "d_main.h"
35 #include "dstrings.h"
36 #include "sounds.h"
37 
38 #include "doomstat.h"
39 #include "r_state.h"
40 #include "m_controls.h" // [crispy] key_*
41 #include "m_misc.h" // [crispy] M_StringDuplicate()
42 #include "m_random.h" // [crispy] Crispy_Random()
43 
44 typedef enum
45 {
46     F_STAGE_TEXT,
47     F_STAGE_ARTSCREEN,
48     F_STAGE_CAST,
49 } finalestage_t;
50 
51 // ?
52 //#include "doomstat.h"
53 //#include "r_local.h"
54 //#include "f_finale.h"
55 
56 // Stage of animation:
57 finalestage_t finalestage;
58 
59 unsigned int finalecount;
60 
61 #define	TEXTSPEED	3
62 #define	TEXTWAIT	250
63 
64 typedef struct
65 {
66     GameMission_t mission;
67     int episode, level;
68     const char *background;
69     const char *text;
70 } textscreen_t;
71 
72 static textscreen_t textscreens[] =
73 {
74     { doom,      1, 8,  "FLOOR4_8",  E1TEXT},
75     { doom,      2, 8,  "SFLR6_1",   E2TEXT},
76     { doom,      3, 8,  "MFLR8_4",   E3TEXT},
77     { doom,      4, 8,  "MFLR8_3",   E4TEXT},
78     { doom,      5, 8,  "FLOOR7_2",  E5TEXT}, // [crispy] Sigil
79 
80     { doom2,     1, 6,  "SLIME16",   C1TEXT},
81     { doom2,     1, 11, "RROCK14",   C2TEXT},
82     { doom2,     1, 20, "RROCK07",   C3TEXT},
83     { doom2,     1, 30, "RROCK17",   C4TEXT},
84     { doom2,     1, 15, "RROCK13",   C5TEXT},
85     { doom2,     1, 31, "RROCK19",   C6TEXT},
86 
87     { pack_tnt,  1, 6,  "SLIME16",   T1TEXT},
88     { pack_tnt,  1, 11, "RROCK14",   T2TEXT},
89     { pack_tnt,  1, 20, "RROCK07",   T3TEXT},
90     { pack_tnt,  1, 30, "RROCK17",   T4TEXT},
91     { pack_tnt,  1, 15, "RROCK13",   T5TEXT},
92     { pack_tnt,  1, 31, "RROCK19",   T6TEXT},
93 
94     { pack_plut, 1, 6,  "SLIME16",   P1TEXT},
95     { pack_plut, 1, 11, "RROCK14",   P2TEXT},
96     { pack_plut, 1, 20, "RROCK07",   P3TEXT},
97     { pack_plut, 1, 30, "RROCK17",   P4TEXT},
98     { pack_plut, 1, 15, "RROCK13",   P5TEXT},
99     { pack_plut, 1, 31, "RROCK19",   P6TEXT},
100 
101     { pack_nerve, 1, 8, "SLIME16",   N1TEXT},
102     { pack_master, 1, 20, "SLIME16",   M1TEXT},
103     { pack_master, 1, 21, "SLIME16",   M2TEXT},
104 };
105 
106 const char *finaletext;
107 const char *finaleflat;
108 static char *finaletext_rw;
109 
110 void	F_StartCast (void);
111 void	F_CastTicker (void);
112 boolean F_CastResponder (event_t *ev);
113 void	F_CastDrawer (void);
114 
115 extern void A_RandomJump();
116 
117 //
118 // F_StartFinale
119 //
F_StartFinale(void)120 void F_StartFinale (void)
121 {
122     size_t i;
123 
124     gameaction = ga_nothing;
125     gamestate = GS_FINALE;
126     viewactive = false;
127     automapactive = false;
128 
129     if (logical_gamemission == doom)
130     {
131         S_ChangeMusic(mus_victor, true);
132     }
133     else
134     {
135         S_ChangeMusic(mus_read_m, true);
136     }
137 
138     // Find the right screen and set the text and background
139 
140     for (i=0; i<arrlen(textscreens); ++i)
141     {
142         textscreen_t *screen = &textscreens[i];
143 
144         // Hack for Chex Quest
145 
146         if (gameversion == exe_chex && screen->mission == doom)
147         {
148             screen->level = 5;
149         }
150 
151         if (logical_gamemission == screen->mission
152          && (logical_gamemission != doom || gameepisode == screen->episode)
153          && gamemap == screen->level)
154         {
155             finaletext = screen->text;
156             finaleflat = screen->background;
157         }
158     }
159 
160     // Do dehacked substitutions of strings
161 
162     finaletext = DEH_String(finaletext);
163     finaleflat = DEH_String(finaleflat);
164     // [crispy] do the "char* vs. const char*" dance
165     if (finaletext_rw)
166     {
167 	free(finaletext_rw);
168 	finaletext_rw = NULL;
169     }
170     finaletext_rw = M_StringDuplicate(finaletext);
171 
172     finalestage = F_STAGE_TEXT;
173     finalecount = 0;
174 
175 }
176 
177 
178 
F_Responder(event_t * event)179 boolean F_Responder (event_t *event)
180 {
181     if (finalestage == F_STAGE_CAST)
182 	return F_CastResponder (event);
183 
184     return false;
185 }
186 
187 
188 //
189 // F_Ticker
190 //
F_Ticker(void)191 void F_Ticker (void)
192 {
193     size_t		i;
194 
195     // check for skipping
196     if ( (gamemode == commercial)
197       && ( finalecount > 50) )
198     {
199       // go on to the next level
200       for (i=0 ; i<MAXPLAYERS ; i++)
201 	if (players[i].cmd.buttons)
202 	  break;
203 
204       if (i < MAXPLAYERS)
205       {
206 	if (gamemission == pack_nerve && gamemap == 8)
207 	  F_StartCast ();
208 	else
209 	if (gamemission == pack_master && (gamemap == 20 || gamemap == 21))
210 	  F_StartCast ();
211 	else
212 	if (gamemap == 30)
213 	  F_StartCast ();
214 	else
215 	  gameaction = ga_worlddone;
216       }
217     }
218 
219     // advance animation
220     finalecount++;
221 
222     if (finalestage == F_STAGE_CAST)
223     {
224 	F_CastTicker ();
225 	return;
226     }
227 
228     if ( gamemode == commercial)
229 	return;
230 
231     if (finalestage == F_STAGE_TEXT
232      && finalecount>strlen (finaletext)*TEXTSPEED + TEXTWAIT)
233     {
234 	finalecount = 0;
235 	finalestage = F_STAGE_ARTSCREEN;
236 	wipegamestate = -1;		// force a wipe
237 	if (gameepisode == 3)
238 	    S_StartMusic (mus_bunny);
239     }
240 }
241 
242 
243 
244 //
245 // F_TextWrite
246 //
247 
248 #include "hu_stuff.h"
249 extern	patch_t *hu_font[HU_FONTSIZE];
250 
251 // [crispy] add line breaks for lines exceeding screenwidth
F_AddLineBreak(char * c)252 static inline boolean F_AddLineBreak (char *c)
253 {
254     while (c-- > finaletext_rw)
255     {
256 	if (*c == '\n')
257 	{
258 	    return false;
259 	}
260 	else
261 	if (*c == ' ')
262 	{
263 	    *c = '\n';
264 	    return true;
265 	}
266     }
267 
268     return false;
269 }
270 
F_TextWrite(void)271 void F_TextWrite (void)
272 {
273     byte*	src;
274     pixel_t*	dest;
275 
276     int		x,y,w;
277     signed int	count;
278     char *ch; // [crispy] un-const
279     int		c;
280     int		cx;
281     int		cy;
282 
283     // erase the entire screen to a tiled background
284     src = W_CacheLumpName ( finaleflat , PU_CACHE);
285     dest = I_VideoBuffer;
286 
287     for (y=0 ; y<SCREENHEIGHT ; y++)
288     {
289 #ifndef CRISPY_TRUECOLOR
290 	for (x=0 ; x<SCREENWIDTH/64 ; x++)
291 	{
292 	    memcpy (dest, src+((y&63)<<6), 64);
293 	    dest += 64;
294 	}
295 	if (SCREENWIDTH&63)
296 	{
297 	    memcpy (dest, src+((y&63)<<6), SCREENWIDTH&63);
298 	    dest += (SCREENWIDTH&63);
299 	}
300 #else
301 	for (x=0 ; x<SCREENWIDTH ; x++)
302 	{
303 		*dest++ = colormaps[src[((y&63)<<6) + (x&63)]];
304 	}
305 #endif
306     }
307 
308     V_MarkRect (0, 0, SCREENWIDTH, SCREENHEIGHT);
309 
310     // draw some of the text onto the screen
311     cx = 10;
312     cy = 10;
313     ch = finaletext_rw;
314 
315     count = ((signed int) finalecount - 10) / TEXTSPEED;
316     if (count < 0)
317 	count = 0;
318     for ( ; count ; count-- )
319     {
320 	c = *ch++;
321 	if (!c)
322 	    break;
323 	if (c == '\n')
324 	{
325 	    cx = 10;
326 	    cy += 11;
327 	    continue;
328 	}
329 
330 	c = toupper(c) - HU_FONTSTART;
331 	if (c < 0 || c> HU_FONTSIZE)
332 	{
333 	    cx += 4;
334 	    continue;
335 	}
336 
337 	w = SHORT (hu_font[c]->width);
338 	if (cx+w > ORIGWIDTH)
339 	{
340 	    // [crispy] add line breaks for lines exceeding screenwidth
341 	    if (F_AddLineBreak(ch))
342 	    {
343 		continue;
344 	    }
345 	    else
346 	    break;
347 	}
348 	// [cispy] prevent text from being drawn off-screen vertically
349 	if (cy + SHORT(hu_font[c]->height) > ORIGHEIGHT)
350 	{
351 	    break;
352 	}
353 	V_DrawPatch(cx, cy, hu_font[c]);
354 	cx+=w;
355     }
356 
357 }
358 
359 //
360 // Final DOOM 2 animation
361 // Casting by id Software.
362 //   in order of appearance
363 //
364 typedef struct
365 {
366     const char	*name;
367     mobjtype_t	type;
368 } castinfo_t;
369 
370 castinfo_t	castorder[] = {
371     {CC_ZOMBIE, MT_POSSESSED},
372     {CC_SHOTGUN, MT_SHOTGUY},
373     {CC_HEAVY, MT_CHAINGUY},
374     {CC_IMP, MT_TROOP},
375     {CC_DEMON, MT_SERGEANT},
376     {CC_LOST, MT_SKULL},
377     {CC_CACO, MT_HEAD},
378     {CC_HELL, MT_KNIGHT},
379     {CC_BARON, MT_BRUISER},
380     {CC_ARACH, MT_BABY},
381     {CC_PAIN, MT_PAIN},
382     {CC_REVEN, MT_UNDEAD},
383     {CC_MANCU, MT_FATSO},
384     {CC_ARCH, MT_VILE},
385     {CC_SPIDER, MT_SPIDER},
386     {CC_CYBER, MT_CYBORG},
387     {CC_HERO, MT_PLAYER},
388 
389     {NULL,0}
390 };
391 
392 int		castnum;
393 int		casttics;
394 state_t*	caststate;
395 boolean		castdeath;
396 int		castframes;
397 int		castonmelee;
398 boolean		castattacking;
399 static signed char	castangle; // [crispy] turnable cast
400 static signed char	castskip; // [crispy] skippable cast
401 static boolean	castflip; // [crispy] flippable death sequence
402 
403 // [crispy] randomize seestate and deathstate sounds in the cast
F_RandomizeSound(int sound)404 static int F_RandomizeSound (int sound)
405 {
406 	if (!crispy->soundfix)
407 		return sound;
408 
409 	switch (sound)
410 	{
411 		// [crispy] actor->info->seesound, from p_enemy.c:A_Look()
412 		case sfx_posit1:
413 		case sfx_posit2:
414 		case sfx_posit3:
415 			return sfx_posit1 + Crispy_Random()%3;
416 			break;
417 
418 		case sfx_bgsit1:
419 		case sfx_bgsit2:
420 			return sfx_bgsit1 + Crispy_Random()%2;
421 			break;
422 
423 		// [crispy] actor->info->deathsound, from p_enemy.c:A_Scream()
424 		case sfx_podth1:
425 		case sfx_podth2:
426 		case sfx_podth3:
427 			return sfx_podth1 + Crispy_Random()%3;
428 			break;
429 
430 		case sfx_bgdth1:
431 		case sfx_bgdth2:
432 			return sfx_bgdth1 + Crispy_Random()%2;
433 			break;
434 
435 		default:
436 			return sound;
437 			break;
438 	}
439 }
440 
441 extern void A_BruisAttack();
442 extern void A_BspiAttack();
443 extern void A_CPosAttack();
444 extern void A_CPosRefire();
445 extern void A_CyberAttack();
446 extern void A_FatAttack1();
447 extern void A_FatAttack2();
448 extern void A_FatAttack3();
449 extern void A_HeadAttack();
450 extern void A_PainAttack();
451 extern void A_PosAttack();
452 extern void A_SargAttack();
453 extern void A_SkelFist();
454 extern void A_SkelMissile();
455 extern void A_SkelWhoosh();
456 extern void A_SkullAttack();
457 extern void A_SPosAttack();
458 extern void A_TroopAttack();
459 extern void A_VileTarget();
460 
461 typedef struct
462 {
463 	void *const action;
464 	const int sound;
465 	const boolean early;
466 } actionsound_t;
467 
468 static const actionsound_t actionsounds[] =
469 {
470 	{A_PosAttack,   sfx_pistol, false},
471 	{A_SPosAttack,  sfx_shotgn, false},
472 	{A_CPosAttack,  sfx_shotgn, false},
473 	{A_CPosRefire,  sfx_shotgn, false},
474 	{A_VileTarget,  sfx_vilatk, true},
475 	{A_SkelWhoosh,  sfx_skeswg, false},
476 	{A_SkelFist,    sfx_skepch, false},
477 	{A_SkelMissile, sfx_skeatk, true},
478 	{A_FatAttack1,  sfx_firsht, false},
479 	{A_FatAttack2,  sfx_firsht, false},
480 	{A_FatAttack3,  sfx_firsht, false},
481 	{A_HeadAttack,  sfx_firsht, true},
482 	{A_BruisAttack, sfx_firsht, true},
483 	{A_TroopAttack, sfx_claw,   false},
484 	{A_SargAttack,  sfx_sgtatk, true},
485 	{A_SkullAttack, sfx_sklatk, false},
486 	{A_PainAttack,  sfx_sklatk, true},
487 	{A_BspiAttack,  sfx_plasma, false},
488 	{A_CyberAttack, sfx_rlaunc, false},
489 };
490 
491 // [crispy] play attack sound based on state action function (instead of state number)
F_SoundForState(int st)492 static int F_SoundForState (int st)
493 {
494 	void *const castaction = (void *) caststate->action.acv;
495 	void *const nextaction = (void *) (&states[caststate->nextstate])->action.acv;
496 
497 	// [crispy] fix Doomguy in casting sequence
498 	if (castaction == NULL)
499 	{
500 		if (st == S_PLAY_ATK2)
501 			return sfx_dshtgn;
502 		else
503 			return 0;
504 	}
505 	else
506 	{
507 		int i;
508 
509 		for (i = 0; i < arrlen(actionsounds); i++)
510 		{
511 			const actionsound_t *const as = &actionsounds[i];
512 
513 			if ((!as->early && castaction == as->action) ||
514 			    (as->early && nextaction == as->action))
515 			{
516 				return as->sound;
517 			}
518 		}
519 	}
520 
521 	return 0;
522 }
523 
524 //
525 // F_StartCast
526 //
F_StartCast(void)527 void F_StartCast (void)
528 {
529     wipegamestate = -1;		// force a screen wipe
530     castnum = 0;
531     caststate = &states[mobjinfo[castorder[castnum].type].seestate];
532     casttics = caststate->tics;
533     castdeath = false;
534     finalestage = F_STAGE_CAST;
535     castframes = 0;
536     castonmelee = 0;
537     castattacking = false;
538     S_ChangeMusic(mus_evil, true);
539 }
540 
541 
542 //
543 // F_CastTicker
544 //
F_CastTicker(void)545 void F_CastTicker (void)
546 {
547     int		st;
548     int		sfx;
549 
550     if (--casttics > 0)
551 	return;			// not time to change state yet
552 
553     if (caststate->tics == -1 || caststate->nextstate == S_NULL || castskip) // [crispy] skippable cast
554     {
555 	if (castskip)
556 	{
557 	    castnum += castskip;
558 	    castskip = 0;
559 	}
560 	else
561 	// switch from deathstate to next monster
562 	castnum++;
563 	castdeath = false;
564 	if (castorder[castnum].name == NULL)
565 	    castnum = 0;
566 	if (mobjinfo[castorder[castnum].type].seesound)
567 	    S_StartSound (NULL, F_RandomizeSound(mobjinfo[castorder[castnum].type].seesound));
568 	caststate = &states[mobjinfo[castorder[castnum].type].seestate];
569 	castframes = 0;
570 	castangle = 0; // [crispy] turnable cast
571 	castflip = false; // [crispy] flippable death sequence
572     }
573     else
574     {
575 	// just advance to next state in animation
576 	// [crispy] fix Doomguy in casting sequence
577 	/*
578 	if (!castdeath && caststate == &states[S_PLAY_ATK1])
579 	    goto stopattack;	// Oh, gross hack!
580 	*/
581 	// [crispy] Allow A_RandomJump() in deaths in cast sequence
582 	if (caststate->action.acp1 == A_RandomJump && Crispy_Random() < caststate->misc2)
583 	{
584 	    st = caststate->misc1;
585 	}
586 	else
587 	{
588 	// [crispy] fix Doomguy in casting sequence
589 	if (!castdeath && caststate == &states[S_PLAY_ATK1])
590 	    st = S_PLAY_ATK2;
591 	else
592 	if (!castdeath && caststate == &states[S_PLAY_ATK2])
593 	    goto stopattack;	// Oh, gross hack!
594 	else
595 	st = caststate->nextstate;
596 	}
597 	caststate = &states[st];
598 	castframes++;
599 
600 	sfx = F_SoundForState(st);
601 /*
602 	// sound hacks....
603 	switch (st)
604 	{
605 	  case S_PLAY_ATK2:	sfx = sfx_dshtgn; break; // [crispy] fix Doomguy in casting sequence
606 	  case S_POSS_ATK2:	sfx = sfx_pistol; break;
607 	  case S_SPOS_ATK2:	sfx = sfx_shotgn; break;
608 	  case S_VILE_ATK2:	sfx = sfx_vilatk; break;
609 	  case S_SKEL_FIST2:	sfx = sfx_skeswg; break;
610 	  case S_SKEL_FIST4:	sfx = sfx_skepch; break;
611 	  case S_SKEL_MISS2:	sfx = sfx_skeatk; break;
612 	  case S_FATT_ATK8:
613 	  case S_FATT_ATK5:
614 	  case S_FATT_ATK2:	sfx = sfx_firsht; break;
615 	  case S_CPOS_ATK2:
616 	  case S_CPOS_ATK3:
617 	  case S_CPOS_ATK4:	sfx = sfx_shotgn; break;
618 	  case S_TROO_ATK3:	sfx = sfx_claw; break;
619 	  case S_SARG_ATK2:	sfx = sfx_sgtatk; break;
620 	  case S_BOSS_ATK2:
621 	  case S_BOS2_ATK2:
622 	  case S_HEAD_ATK2:	sfx = sfx_firsht; break;
623 	  case S_SKULL_ATK2:	sfx = sfx_sklatk; break;
624 	  case S_SPID_ATK2:
625 	  case S_SPID_ATK3:	sfx = sfx_shotgn; break;
626 	  case S_BSPI_ATK2:	sfx = sfx_plasma; break;
627 	  case S_CYBER_ATK2:
628 	  case S_CYBER_ATK4:
629 	  case S_CYBER_ATK6:	sfx = sfx_rlaunc; break;
630 	  case S_PAIN_ATK3:	sfx = sfx_sklatk; break;
631 	  default: sfx = 0; break;
632 	}
633 
634 */
635 	if (sfx)
636 	    S_StartSound (NULL, sfx);
637     }
638 
639     if (!castdeath && castframes == 12)
640     {
641 	// go into attack frame
642 	castattacking = true;
643 	if (castonmelee)
644 	    caststate=&states[mobjinfo[castorder[castnum].type].meleestate];
645 	else
646 	    caststate=&states[mobjinfo[castorder[castnum].type].missilestate];
647 	castonmelee ^= 1;
648 	if (caststate == &states[S_NULL])
649 	{
650 	    if (castonmelee)
651 		caststate=
652 		    &states[mobjinfo[castorder[castnum].type].meleestate];
653 	    else
654 		caststate=
655 		    &states[mobjinfo[castorder[castnum].type].missilestate];
656 	}
657     }
658 
659     if (castattacking)
660     {
661 	if (castframes == 24
662 	    ||	caststate == &states[mobjinfo[castorder[castnum].type].seestate] )
663 	{
664 	  stopattack:
665 	    castattacking = false;
666 	    castframes = 0;
667 	    caststate = &states[mobjinfo[castorder[castnum].type].seestate];
668 	}
669     }
670 
671     casttics = caststate->tics;
672     if (casttics == -1)
673     {
674 	// [crispy] Allow A_RandomJump() in deaths in cast sequence
675 	if (caststate->action.acp1 == A_RandomJump)
676 	{
677 	    if (Crispy_Random() < caststate->misc2)
678 	    {
679 		caststate = &states[caststate->misc1];
680 	    }
681 	    else
682 	    {
683 		caststate = &states[caststate->nextstate];
684 	    }
685 
686 	    casttics = caststate->tics;
687 	}
688 
689 	if (casttics == -1)
690 	{
691 	casttics = 15;
692 	}
693     }
694 }
695 
696 
697 //
698 // F_CastResponder
699 //
700 
F_CastResponder(event_t * ev)701 boolean F_CastResponder (event_t* ev)
702 {
703     boolean xdeath = false;
704 
705     if (ev->type != ev_keydown)
706 	return false;
707 
708     // [crispy] make monsters turnable in cast ...
709     if (ev->data1 == key_left)
710     {
711 	if (++castangle > 7)
712 	    castangle = 0;
713 	return false;
714     }
715     else
716     if (ev->data1 == key_right)
717     {
718 	if (--castangle < 0)
719 	    castangle = 7;
720 	return false;
721     }
722     else
723     // [crispy] ... and allow to skip through them ..
724     if (ev->data1 == key_strafeleft || ev->data1 == key_alt_strafeleft)
725     {
726 	castskip = castnum ? -1 : arrlen(castorder)-2;
727 	return false;
728     }
729     else
730     if (ev->data1 == key_straferight || ev->data1 == key_alt_straferight)
731     {
732 	castskip = +1;
733 	return false;
734     }
735     // [crispy] ... and finally turn them into gibbs
736     if (ev->data1 == key_speed)
737 	xdeath = true;
738 
739     if (castdeath)
740 	return true;			// already in dying frames
741 
742     // go into death frame
743     castdeath = true;
744     if (xdeath && mobjinfo[castorder[castnum].type].xdeathstate)
745 	caststate = &states[mobjinfo[castorder[castnum].type].xdeathstate];
746     else
747     caststate = &states[mobjinfo[castorder[castnum].type].deathstate];
748     casttics = caststate->tics;
749     // [crispy] Allow A_RandomJump() in deaths in cast sequence
750     if (casttics == -1 && caststate->action.acp1 == A_RandomJump)
751     {
752         if (Crispy_Random() < caststate->misc2)
753         {
754             caststate = &states [caststate->misc1];
755         }
756         else
757         {
758             caststate = &states [caststate->nextstate];
759         }
760         casttics = caststate->tics;
761     }
762     castframes = 0;
763     castattacking = false;
764     if (xdeath && mobjinfo[castorder[castnum].type].xdeathstate)
765         S_StartSound (NULL, sfx_slop);
766     else
767     if (mobjinfo[castorder[castnum].type].deathsound)
768 	S_StartSound (NULL, F_RandomizeSound(mobjinfo[castorder[castnum].type].deathsound));
769 
770     // [crispy] flippable death sequence
771     castflip = crispy->flipcorpses &&
772 	castdeath &&
773 	(mobjinfo[castorder[castnum].type].flags & MF_FLIPPABLE) &&
774 	(Crispy_Random() & 1);
775 
776     return true;
777 }
778 
779 
F_CastPrint(const char * text)780 void F_CastPrint (const char *text)
781 {
782     const char *ch;
783     int		c;
784     int		cx;
785     int		w;
786     int		width;
787 
788     // find width
789     ch = text;
790     width = 0;
791 
792     while (ch)
793     {
794 	c = *ch++;
795 	if (!c)
796 	    break;
797 	c = toupper(c) - HU_FONTSTART;
798 	if (c < 0 || c> HU_FONTSIZE)
799 	{
800 	    width += 4;
801 	    continue;
802 	}
803 
804 	w = SHORT (hu_font[c]->width);
805 	width += w;
806     }
807 
808     // draw it
809     cx = ORIGWIDTH/2-width/2;
810     ch = text;
811     while (ch)
812     {
813 	c = *ch++;
814 	if (!c)
815 	    break;
816 	c = toupper(c) - HU_FONTSTART;
817 	if (c < 0 || c> HU_FONTSIZE)
818 	{
819 	    cx += 4;
820 	    continue;
821 	}
822 
823 	w = SHORT (hu_font[c]->width);
824 	V_DrawPatch(cx, 180, hu_font[c]);
825 	cx+=w;
826     }
827 
828 }
829 
830 
831 //
832 // F_CastDrawer
833 //
834 
F_CastDrawer(void)835 void F_CastDrawer (void)
836 {
837     spritedef_t*	sprdef;
838     spriteframe_t*	sprframe;
839     int			lump;
840     boolean		flip;
841     patch_t*		patch;
842 
843     // erase the entire screen to a background
844     V_DrawPatchFullScreen (W_CacheLumpName (DEH_String("BOSSBACK"), PU_CACHE), false);
845 
846     F_CastPrint (DEH_String(castorder[castnum].name));
847 
848     // draw the current frame in the middle of the screen
849     sprdef = &sprites[caststate->sprite];
850     // [crispy] the TNT1 sprite is not supposed to be rendered anyway
851     if (!sprdef->numframes && caststate->sprite == SPR_TNT1)
852     {
853 	return;
854     }
855     sprframe = &sprdef->spriteframes[ caststate->frame & FF_FRAMEMASK];
856     lump = sprframe->lump[castangle]; // [crispy] turnable cast
857     flip = (boolean)sprframe->flip[castangle] ^ castflip; // [crispy] turnable cast, flippable death sequence
858 
859     patch = W_CacheLumpNum (lump+firstspritelump, PU_CACHE);
860     if (flip)
861 	V_DrawPatchFlipped(ORIGWIDTH/2, 170, patch);
862     else
863 	V_DrawPatch(ORIGWIDTH/2, 170, patch);
864 }
865 
866 
867 //
868 // F_DrawPatchCol
869 //
870 static fixed_t dxi, dy, dyi;
871 
872 void
F_DrawPatchCol(int x,patch_t * patch,int col)873 F_DrawPatchCol
874 ( int		x,
875   patch_t*	patch,
876   int		col )
877 {
878     column_t*	column;
879     byte*	source;
880     pixel_t*	dest;
881     pixel_t*	desttop;
882     int		count;
883 
884     column = (column_t *)((byte *)patch + LONG(patch->columnofs[col]));
885     desttop = I_VideoBuffer + x;
886 
887     // step through the posts in a column
888     while (column->topdelta != 0xff )
889     {
890 	int srccol = 0;
891 	source = (byte *)column + 3;
892 	dest = desttop + ((column->topdelta * dy) >> FRACBITS)*SCREENWIDTH;
893 	count = (column->length * dy) >> FRACBITS;
894 
895 	while (count--)
896 	{
897 	    *dest = source[srccol >> FRACBITS];
898 	    srccol += dyi;
899 	    dest += SCREENWIDTH;
900 	}
901 	column = (column_t *)(  (byte *)column + column->length + 4 );
902     }
903 }
904 
905 
906 //
907 // F_BunnyScroll
908 //
F_BunnyScroll(void)909 void F_BunnyScroll (void)
910 {
911     signed int  scrolled;
912     int		x;
913     patch_t*	p1;
914     patch_t*	p2;
915     char	name[10];
916     int		stage;
917     static int	laststage;
918     int         p2offset, p1offset, pillar_width;
919 
920     dxi = (ORIGWIDTH << FRACBITS) / NONWIDEWIDTH;
921     dy = (SCREENHEIGHT << FRACBITS) / ORIGHEIGHT;
922     dyi = (ORIGHEIGHT << FRACBITS) / SCREENHEIGHT;
923 
924     p1 = W_CacheLumpName (DEH_String("PFUB2"), PU_LEVEL);
925     p2 = W_CacheLumpName (DEH_String("PFUB1"), PU_LEVEL);
926 
927     // [crispy] fill pillarboxes in widescreen mode
928     pillar_width = (SCREENWIDTH - (SHORT(p1->width) << FRACBITS) / dxi) / 2;
929 
930     if (pillar_width > 0)
931     {
932         V_DrawFilledBox(0, 0, pillar_width, SCREENHEIGHT, 0);
933         V_DrawFilledBox(SCREENWIDTH - pillar_width, 0, pillar_width, SCREENHEIGHT, 0);
934     }
935     else
936     {
937         pillar_width = 0;
938     }
939 
940     // Calculate the portion of PFUB2 that would be offscreen at original res.
941     p1offset = (ORIGWIDTH - SHORT(p1->width)) / 2;
942 
943     if (SHORT(p2->width) == ORIGWIDTH)
944     {
945         // Unity or original PFUBs.
946         // PFUB1 only contains the pixels that scroll off.
947         p2offset = ORIGWIDTH - p1offset;
948     }
949     else
950     {
951         // Widescreen mod PFUBs.
952         // Right side of PFUB2 and left side of PFUB1 are identical.
953         p2offset = ORIGWIDTH + p1offset;
954     }
955 
956     V_MarkRect (0, 0, SCREENWIDTH, SCREENHEIGHT);
957 
958     scrolled = (ORIGWIDTH - ((signed int) finalecount-230)/2);
959     if (scrolled > ORIGWIDTH)
960 	scrolled = ORIGWIDTH;
961     if (scrolled < 0)
962 	scrolled = 0;
963 
964     for (x = pillar_width; x < SCREENWIDTH - pillar_width; x++)
965     {
966         int x2 = ((x * dxi) >> FRACBITS) - WIDESCREENDELTA + scrolled;
967 
968         if (x2 < p2offset)
969             F_DrawPatchCol (x, p1, x2 - p1offset);
970         else
971             F_DrawPatchCol (x, p2, x2 - p2offset);
972     }
973 
974     if (finalecount < 1130)
975 	return;
976     if (finalecount < 1180)
977     {
978         V_DrawPatch((ORIGWIDTH - 13 * 8) / 2,
979                     (ORIGHEIGHT - 8 * 8) / 2,
980                     W_CacheLumpName(DEH_String("END0"), PU_CACHE));
981 	laststage = 0;
982 	return;
983     }
984 
985     stage = (finalecount-1180) / 5;
986     if (stage > 6)
987 	stage = 6;
988     if (stage > laststage)
989     {
990 	S_StartSound (NULL, sfx_pistol);
991 	laststage = stage;
992     }
993 
994     DEH_snprintf(name, 10, "END%i", stage);
995     V_DrawPatch((ORIGWIDTH - 13 * 8) / 2,
996                 (ORIGHEIGHT - 8 * 8) / 2,
997                 W_CacheLumpName (name,PU_CACHE));
998 }
999 
F_ArtScreenDrawer(void)1000 static void F_ArtScreenDrawer(void)
1001 {
1002     const char *lumpname;
1003 
1004     if (gameepisode == 3)
1005     {
1006         F_BunnyScroll();
1007     }
1008     else
1009     {
1010         switch (gameepisode)
1011         {
1012             case 1:
1013                 if (gameversion >= exe_ultimate)
1014                 {
1015                     lumpname = "CREDIT";
1016                 }
1017                 else
1018                 {
1019                     lumpname = "HELP2";
1020                 }
1021                 break;
1022             case 2:
1023                 lumpname = "VICTORY2";
1024                 break;
1025             case 4:
1026                 lumpname = "ENDPIC";
1027                 break;
1028             // [crispy] Sigil
1029             case 5:
1030                 lumpname = "SIGILEND";
1031                 if (W_CheckNumForName(DEH_String(lumpname)) == -1)
1032                 {
1033                     return;
1034                 }
1035                 break;
1036             default:
1037                 return;
1038         }
1039 
1040         lumpname = DEH_String(lumpname);
1041 
1042         V_DrawPatchFullScreen (W_CacheLumpName(lumpname, PU_CACHE), false);
1043     }
1044 }
1045 
1046 //
1047 // F_Drawer
1048 //
F_Drawer(void)1049 void F_Drawer (void)
1050 {
1051     switch (finalestage)
1052     {
1053         case F_STAGE_CAST:
1054             F_CastDrawer();
1055             break;
1056         case F_STAGE_TEXT:
1057             F_TextWrite();
1058             break;
1059         case F_STAGE_ARTSCREEN:
1060             F_ArtScreenDrawer();
1061             break;
1062     }
1063 }
1064 
1065 
1066