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