1 /* Emacs style mode select -*- C++ -*-
2 *-----------------------------------------------------------------------------
3 *
4 *
5 * PrBoom: a Doom port merged with LxDoom and LSDLDoom
6 * based on BOOM, a modified and improved DOOM engine
7 * Copyright (C) 1999 by
8 * id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman
9 * Copyright (C) 1999-2000 by
10 * Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze
11 * Copyright 2005, 2006 by
12 * Florian Schulze, Colin Phipps, Neil Stevens, Andrey Budko
13 *
14 * This program is free software; you can redistribute it and/or
15 * modify it under the terms of the GNU General Public License
16 * as published by the Free Software Foundation; either version 2
17 * of the License, or (at your option) any later version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with this program; if not, write to the Free Software
26 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
27 * 02111-1307, USA.
28 *
29 * DESCRIPTION:
30 * Game completion, final screen animation.
31 *
32 *-----------------------------------------------------------------------------
33 */
34
35 #include "doomstat.h"
36 #include "d_event.h"
37 #include "v_video.h"
38 #include "w_wad.h"
39 #include "s_sound.h"
40 #include "sounds.h"
41 #include "d_deh.h" // Ty 03/22/98 - externalizations
42 #include "f_finale.h" // CPhipps - hmm...
43
44 // Stage of animation:
45 // 0 = text, 1 = art screen, 2 = character cast
46 static int finalestage; // cph -
47 static int finalecount; // made static
48 static const char* finaletext; // cph -
49 static const char* finaleflat; // made static const
50
51 // defines for the end mission display text // phares
52
53 #define TEXTSPEED 3 // original value // phares
54 #define TEXTWAIT 250 // original value // phares
55 #define NEWTEXTSPEED 0.01f // new value // phares
56 #define NEWTEXTWAIT 1000 // new value // phares
57
58 // CPhipps - removed the old finale screen text message strings;
59 // they were commented out for ages already
60 // Ty 03/22/98 - ... the new s_WHATEVER extern variables are used
61 // in the code below instead.
62
63 void F_StartCast (void);
64 void F_CastTicker (void);
65 dboolean F_CastResponder (event_t *ev);
66 void F_CastDrawer (void);
67
68 void WI_checkForAccelerate(void); // killough 3/28/98: used to
69 extern int acceleratestage; // accelerate intermission screens
70 static int midstage; // whether we're in "mid-stage"
71
72 //
73 // F_StartFinale
74 //
F_StartFinale(void)75 void F_StartFinale (void)
76 {
77 gameaction = ga_nothing;
78 gamestate = GS_FINALE;
79 automapmode &= ~am_active;
80
81 // killough 3/28/98: clear accelerative text flags
82 acceleratestage = midstage = 0;
83
84 // Okay - IWAD dependend stuff.
85 // This has been changed severly, and
86 // some stuff might have changed in the process.
87 switch ( gamemode )
88 {
89 // DOOM 1 - E1, E3 or E4, but each nine missions
90 case shareware:
91 case registered:
92 case retail:
93 {
94 S_ChangeMusic(mus_victor, true);
95
96 switch (gameepisode)
97 {
98 case 1:
99 finaleflat = bgflatE1; // Ty 03/30/98 - new externalized bg flats
100 finaletext = s_E1TEXT; // Ty 03/23/98 - Was e1text variable.
101 break;
102 case 2:
103 finaleflat = bgflatE2;
104 finaletext = s_E2TEXT; // Ty 03/23/98 - Same stuff for each
105 break;
106 case 3:
107 finaleflat = bgflatE3;
108 finaletext = s_E3TEXT;
109 break;
110 case 4:
111 finaleflat = bgflatE4;
112 finaletext = s_E4TEXT;
113 break;
114 default:
115 // Ouch.
116 break;
117 }
118 break;
119 }
120
121 // DOOM II and missions packs with E1, M34
122 case commercial:
123 {
124 S_ChangeMusic(mus_read_m, true);
125
126 // Ty 08/27/98 - added the gamemission logic
127 switch (gamemap)
128 {
129 case 6:
130 finaleflat = bgflat06;
131 finaletext = (gamemission==pack_tnt) ? s_T1TEXT :
132 (gamemission==pack_plut) ? s_P1TEXT : s_C1TEXT;
133 break;
134 case 11:
135 finaleflat = bgflat11;
136 finaletext = (gamemission==pack_tnt) ? s_T2TEXT :
137 (gamemission==pack_plut) ? s_P2TEXT : s_C2TEXT;
138 break;
139 case 20:
140 finaleflat = bgflat20;
141 finaletext = (gamemission==pack_tnt) ? s_T3TEXT :
142 (gamemission==pack_plut) ? s_P3TEXT : s_C3TEXT;
143 break;
144 case 30:
145 finaleflat = bgflat30;
146 finaletext = (gamemission==pack_tnt) ? s_T4TEXT :
147 (gamemission==pack_plut) ? s_P4TEXT : s_C4TEXT;
148 break;
149 case 15:
150 finaleflat = bgflat15;
151 finaletext = (gamemission==pack_tnt) ? s_T5TEXT :
152 (gamemission==pack_plut) ? s_P5TEXT : s_C5TEXT;
153 break;
154 case 31:
155 finaleflat = bgflat31;
156 finaletext = (gamemission==pack_tnt) ? s_T6TEXT :
157 (gamemission==pack_plut) ? s_P6TEXT : s_C6TEXT;
158 break;
159 default:
160 // Ouch.
161 break;
162 }
163 break;
164 // Ty 08/27/98 - end gamemission logic
165 }
166
167 // Indeterminate.
168 default: // Ty 03/30/98 - not externalized
169 S_ChangeMusic(mus_read_m, true);
170 finaleflat = "F_SKY1"; // Not used anywhere else.
171 finaletext = s_C1TEXT; // FIXME - other text, music?
172 break;
173 }
174
175 finalestage = 0;
176 finalecount = 0;
177 }
178
179
180
F_Responder(event_t * event)181 dboolean F_Responder (event_t *event)
182 {
183 if (finalestage == 2)
184 return F_CastResponder (event);
185
186 return false;
187 }
188
189 // Get_TextSpeed() returns the value of the text display speed // phares
190 // Rewritten to allow user-directed acceleration -- killough 3/28/98
191
Get_TextSpeed(void)192 static float Get_TextSpeed(void)
193 {
194 return midstage ? NEWTEXTSPEED : (midstage=acceleratestage) ?
195 acceleratestage=0, NEWTEXTSPEED : TEXTSPEED;
196 }
197
198
199 //
200 // F_Ticker
201 //
202 // killough 3/28/98: almost totally rewritten, to use
203 // player-directed acceleration instead of constant delays.
204 // Now the player can accelerate the text display by using
205 // the fire/use keys while it is being printed. The delay
206 // automatically responds to the user, and gives enough
207 // time to read.
208 //
209 // killough 5/10/98: add back v1.9 demo compatibility
210 //
211
F_Ticker(void)212 void F_Ticker(void)
213 {
214 int i;
215 if (!demo_compatibility)
216 WI_checkForAccelerate(); // killough 3/28/98: check for acceleration
217 else
218 if (gamemode == commercial && finalecount > 50) // check for skipping
219 for (i=0; i<MAXPLAYERS; i++)
220 if (players[i].cmd.buttons)
221 goto next_level; // go on to the next level
222
223 // advance animation
224 finalecount++;
225
226 if (finalestage == 2)
227 F_CastTicker();
228
229 if (!finalestage)
230 {
231 float speed = demo_compatibility ? TEXTSPEED : Get_TextSpeed();
232 /* killough 2/28/98: changed to allow acceleration */
233 if (finalecount > strlen(finaletext)*speed +
234 (midstage ? NEWTEXTWAIT : TEXTWAIT) ||
235 (midstage && acceleratestage)) {
236 if (gamemode != commercial) // Doom 1 / Ultimate Doom episode end
237 { // with enough time, it's automatic
238 finalecount = 0;
239 finalestage = 1;
240 wipegamestate = -1; // force a wipe
241 if (gameepisode == 3)
242 S_StartMusic(mus_bunny);
243 }
244 else // you must press a button to continue in Doom 2
245 if (!demo_compatibility && midstage)
246 {
247 next_level:
248 if (gamemap == 30)
249 F_StartCast(); // cast of Doom 2 characters
250 else
251 gameaction = ga_worlddone; // next level, e.g. MAP07
252 }
253 }
254 }
255 }
256
257 //
258 // F_TextWrite
259 //
260 // This program displays the background and text at end-mission // phares
261 // text time. It draws both repeatedly so that other displays, // |
262 // like the main menu, can be drawn over it dynamically and // V
263 // erased dynamically. The TEXTSPEED constant is changed into
264 // the Get_TextSpeed function so that the speed of writing the // ^
265 // text can be increased, and there's still time to read what's // |
266 // written. // phares
267 // CPhipps - reformatted
268
269 #include "hu_stuff.h"
270 extern patchnum_t hu_font[HU_FONTSIZE];
271
272
F_TextWrite(void)273 static void F_TextWrite (void)
274 {
275 V_DrawBackground(finaleflat, 0);
276 { // draw some of the text onto the screen
277 int cx = 10;
278 int cy = 10;
279 const char* ch = finaletext; // CPhipps - const
280 int count = (int)((float)(finalecount - 10)/Get_TextSpeed()); // phares
281 int w;
282
283 if (count < 0)
284 count = 0;
285
286 for ( ; count ; count-- ) {
287 int c = *ch++;
288
289 if (!c)
290 break;
291 if (c == '\n') {
292 cx = 10;
293 cy += 11;
294 continue;
295 }
296
297 c = toupper(c) - HU_FONTSTART;
298 if (c < 0 || c> HU_FONTSIZE) {
299 cx += 4;
300 continue;
301 }
302
303 w = hu_font[c].width;
304 if (cx+w > SCREENWIDTH)
305 break;
306 // CPhipps - patch drawing updated
307 V_DrawNumPatch(cx, cy, 0, hu_font[c].lumpnum, CR_DEFAULT, VPT_STRETCH);
308 cx+=w;
309 }
310 }
311 }
312
313 //
314 // Final DOOM 2 animation
315 // Casting by id Software.
316 // in order of appearance
317 //
318 typedef struct
319 {
320 const char **name; // CPhipps - const**
321 mobjtype_t type;
322 } castinfo_t;
323
324 static const castinfo_t castorder[] = { // CPhipps - static const, initialised here
325 { &s_CC_ZOMBIE, MT_POSSESSED },
326 { &s_CC_SHOTGUN, MT_SHOTGUY },
327 { &s_CC_HEAVY, MT_CHAINGUY },
328 { &s_CC_IMP, MT_TROOP },
329 { &s_CC_DEMON, MT_SERGEANT },
330 { &s_CC_LOST, MT_SKULL },
331 { &s_CC_CACO, MT_HEAD },
332 { &s_CC_HELL, MT_KNIGHT },
333 { &s_CC_BARON, MT_BRUISER },
334 { &s_CC_ARACH, MT_BABY },
335 { &s_CC_PAIN, MT_PAIN },
336 { &s_CC_REVEN, MT_UNDEAD },
337 { &s_CC_MANCU, MT_FATSO },
338 { &s_CC_ARCH, MT_VILE },
339 { &s_CC_SPIDER, MT_SPIDER },
340 { &s_CC_CYBER, MT_CYBORG },
341 { &s_CC_HERO, MT_PLAYER },
342 { NULL, 0}
343 };
344
345 int castnum;
346 int casttics;
347 state_t* caststate;
348 dboolean castdeath;
349 int castframes;
350 int castonmelee;
351 dboolean castattacking;
352
353
354 //
355 // F_StartCast
356 //
357
F_StartCast(void)358 void F_StartCast (void)
359 {
360 wipegamestate = -1; // force a screen wipe
361 castnum = 0;
362 caststate = &states[mobjinfo[castorder[castnum].type].seestate];
363 casttics = caststate->tics;
364 castdeath = false;
365 finalestage = 2;
366 castframes = 0;
367 castonmelee = 0;
368 castattacking = false;
369 S_ChangeMusic(mus_evil, true);
370 }
371
372
373 //
374 // F_CastTicker
375 //
F_CastTicker(void)376 void F_CastTicker (void)
377 {
378 int st;
379 int sfx;
380
381 if (--casttics > 0)
382 return; // not time to change state yet
383
384 if (caststate->tics == -1 || caststate->nextstate == S_NULL)
385 {
386 // switch from deathstate to next monster
387 castnum++;
388 castdeath = false;
389 if (castorder[castnum].name == NULL)
390 castnum = 0;
391 if (mobjinfo[castorder[castnum].type].seesound)
392 S_StartSound (NULL, mobjinfo[castorder[castnum].type].seesound);
393 caststate = &states[mobjinfo[castorder[castnum].type].seestate];
394 castframes = 0;
395 }
396 else
397 {
398 // just advance to next state in animation
399 if (caststate == &states[S_PLAY_ATK1])
400 goto stopattack; // Oh, gross hack!
401 st = caststate->nextstate;
402 caststate = &states[st];
403 castframes++;
404
405 // sound hacks....
406 switch (st)
407 {
408 case S_PLAY_ATK1: sfx = sfx_dshtgn; break;
409 case S_POSS_ATK2: sfx = sfx_pistol; break;
410 case S_SPOS_ATK2: sfx = sfx_shotgn; break;
411 case S_VILE_ATK2: sfx = sfx_vilatk; break;
412 case S_SKEL_FIST2: sfx = sfx_skeswg; break;
413 case S_SKEL_FIST4: sfx = sfx_skepch; break;
414 case S_SKEL_MISS2: sfx = sfx_skeatk; break;
415 case S_FATT_ATK8:
416 case S_FATT_ATK5:
417 case S_FATT_ATK2: sfx = sfx_firsht; break;
418 case S_CPOS_ATK2:
419 case S_CPOS_ATK3:
420 case S_CPOS_ATK4: sfx = sfx_shotgn; break;
421 case S_TROO_ATK3: sfx = sfx_claw; break;
422 case S_SARG_ATK2: sfx = sfx_sgtatk; break;
423 case S_BOSS_ATK2:
424 case S_BOS2_ATK2:
425 case S_HEAD_ATK2: sfx = sfx_firsht; break;
426 case S_SKULL_ATK2: sfx = sfx_sklatk; break;
427 case S_SPID_ATK2:
428 case S_SPID_ATK3: sfx = sfx_shotgn; break;
429 case S_BSPI_ATK2: sfx = sfx_plasma; break;
430 case S_CYBER_ATK2:
431 case S_CYBER_ATK4:
432 case S_CYBER_ATK6: sfx = sfx_rlaunc; break;
433 case S_PAIN_ATK3: sfx = sfx_sklatk; break;
434 default: sfx = 0; break;
435 }
436
437 if (sfx)
438 S_StartSound (NULL, sfx);
439 }
440
441 if (castframes == 12)
442 {
443 // go into attack frame
444 castattacking = true;
445 if (castonmelee)
446 caststate=&states[mobjinfo[castorder[castnum].type].meleestate];
447 else
448 caststate=&states[mobjinfo[castorder[castnum].type].missilestate];
449 castonmelee ^= 1;
450 if (caststate == &states[S_NULL])
451 {
452 if (castonmelee)
453 caststate=
454 &states[mobjinfo[castorder[castnum].type].meleestate];
455 else
456 caststate=
457 &states[mobjinfo[castorder[castnum].type].missilestate];
458 }
459 }
460
461 if (castattacking)
462 {
463 if (castframes == 24
464 || caststate == &states[mobjinfo[castorder[castnum].type].seestate] )
465 {
466 stopattack:
467 castattacking = false;
468 castframes = 0;
469 caststate = &states[mobjinfo[castorder[castnum].type].seestate];
470 }
471 }
472
473 casttics = caststate->tics;
474 if (casttics == -1)
475 casttics = 15;
476 }
477
478
479 //
480 // F_CastResponder
481 //
482
F_CastResponder(event_t * ev)483 dboolean F_CastResponder (event_t* ev)
484 {
485 if (ev->type != ev_keydown)
486 return false;
487
488 if (castdeath)
489 return true; // already in dying frames
490
491 // go into death frame
492 castdeath = true;
493 caststate = &states[mobjinfo[castorder[castnum].type].deathstate];
494 casttics = caststate->tics;
495 castframes = 0;
496 castattacking = false;
497 if (mobjinfo[castorder[castnum].type].deathsound)
498 S_StartSound (NULL, mobjinfo[castorder[castnum].type].deathsound);
499
500 return true;
501 }
502
503
F_CastPrint(const char * text)504 static void F_CastPrint (const char* text) // CPhipps - static, const char*
505 {
506 const char* ch; // CPhipps - const
507 int c;
508 int cx;
509 int w;
510 int width;
511
512 // find width
513 ch = text;
514 width = 0;
515
516 while (ch)
517 {
518 c = *ch++;
519 if (!c)
520 break;
521 c = toupper(c) - HU_FONTSTART;
522 if (c < 0 || c> HU_FONTSIZE)
523 {
524 width += 4;
525 continue;
526 }
527
528 w = hu_font[c].width;
529 width += w;
530 }
531
532 // draw it
533 cx = 160-width/2;
534 ch = text;
535 while (ch)
536 {
537 c = *ch++;
538 if (!c)
539 break;
540 c = toupper(c) - HU_FONTSTART;
541 if (c < 0 || c> HU_FONTSIZE)
542 {
543 cx += 4;
544 continue;
545 }
546
547 w = hu_font[c].width;
548 // CPhipps - patch drawing updated
549 V_DrawNumPatch(cx, 180, 0, hu_font[c].lumpnum, CR_DEFAULT, VPT_STRETCH);
550 cx+=w;
551 }
552 }
553
554
555 //
556 // F_CastDrawer
557 //
558
F_CastDrawer(void)559 void F_CastDrawer (void)
560 {
561 spritedef_t* sprdef;
562 spriteframe_t* sprframe;
563 int lump;
564 dboolean flip;
565
566 // erase the entire screen to a background
567 // CPhipps - patch drawing updated
568 V_DrawNamePatch(0,0,0, bgcastcall, CR_DEFAULT, VPT_STRETCH); // Ty 03/30/98 bg texture extern
569 // e6y: wide-res
570 V_FillBorder(-1, 0);
571
572 F_CastPrint (*(castorder[castnum].name));
573
574 // draw the current frame in the middle of the screen
575 sprdef = &sprites[caststate->sprite];
576 sprframe = &sprdef->spriteframes[ caststate->frame & FF_FRAMEMASK];
577 lump = sprframe->lump[0];
578 flip = (dboolean)(sprframe->flip & 1);
579
580 // CPhipps - patch drawing updated
581 V_DrawNumPatch(160, 170, 0, lump+firstspritelump, CR_DEFAULT,
582 VPT_STRETCH | (flip ? VPT_FLIP : 0));
583 }
584
585 //
586 // F_BunnyScroll
587 //
588 static const char pfub2[] = { "PFUB2" };
589 static const char pfub1[] = { "PFUB1" };
590
F_BunnyScroll(void)591 static void F_BunnyScroll (void)
592 {
593 char name[10];
594 int stage;
595 static int laststage;
596
597 {
598 int scrolled = 320 - (finalecount-230)/2;
599 if (scrolled <= 0) {
600 V_DrawNamePatch(0, 0, 0, pfub2, CR_DEFAULT, VPT_STRETCH);
601 } else if (scrolled >= 320) {
602 V_DrawNamePatch(0, 0, 0, pfub1, CR_DEFAULT, VPT_STRETCH);
603 } else {
604 V_DrawNamePatch(320-scrolled, 0, 0, pfub1, CR_DEFAULT, VPT_STRETCH);
605 V_DrawNamePatch(-scrolled, 0, 0, pfub2, CR_DEFAULT, VPT_STRETCH);
606 }
607 }
608
609 if (finalecount < 1130)
610 return;
611 if (finalecount < 1180)
612 {
613 // CPhipps - patch drawing updated
614 V_DrawNamePatch((320-13*8)/2, (200-8*8)/2,0, "END0", CR_DEFAULT, VPT_STRETCH);
615 laststage = 0;
616 return;
617 }
618
619 stage = (finalecount-1180) / 5;
620 if (stage > 6)
621 stage = 6;
622 if (stage > laststage)
623 {
624 S_StartSound (NULL, sfx_pistol);
625 laststage = stage;
626 }
627
628 sprintf (name,"END%i",stage);
629 // CPhipps - patch drawing updated
630 V_DrawNamePatch((320-13*8)/2, (200-8*8)/2, 0, name, CR_DEFAULT, VPT_STRETCH);
631 }
632
633
634 //
635 // F_Drawer
636 //
F_Drawer(void)637 void F_Drawer (void)
638 {
639 if (finalestage == 2)
640 {
641 F_CastDrawer ();
642 return;
643 }
644
645 if (!finalestage)
646 F_TextWrite ();
647 else
648 {
649 switch (gameepisode)
650 {
651 // CPhipps - patch drawing updated
652 case 1:
653 if ( gamemode == retail )
654 V_DrawNamePatch(0, 0, 0, "CREDIT", CR_DEFAULT, VPT_STRETCH);
655 else
656 V_DrawNamePatch(0, 0, 0, "HELP2", CR_DEFAULT, VPT_STRETCH);
657 break;
658 case 2:
659 V_DrawNamePatch(0, 0, 0, "VICTORY2", CR_DEFAULT, VPT_STRETCH);
660 break;
661 case 3:
662 F_BunnyScroll ();
663 break;
664 case 4:
665 V_DrawNamePatch(0, 0, 0, "ENDPIC", CR_DEFAULT, VPT_STRETCH);
666 break;
667 }
668 // e6y: wide-res
669 V_FillBorder(-1, 0);
670 }
671 }
672