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  *  Intermission screens.
31  *
32  *-----------------------------------------------------------------------------
33  */
34 
35 #include "doomstat.h"
36 #include "m_random.h"
37 #include "w_wad.h"
38 #include "g_game.h"
39 #include "r_main.h"
40 #include "v_video.h"
41 #include "wi_stuff.h"
42 #include "s_sound.h"
43 #include "sounds.h"
44 #include "lprintf.h"  // jff 08/03/98 - declaration of lprintf
45 #include "r_draw.h"
46 
47 // Ty 03/17/98: flag that new par times have been loaded in d_deh
48 extern dboolean deh_pars;
49 
50 //
51 // Data needed to add patches to full screen intermission pics.
52 // Patches are statistics messages, and animations.
53 // Loads of by-pixel layout and placement, offsets etc.
54 //
55 
56 //
57 // Different vetween registered DOOM (1994) and
58 //  Ultimate DOOM - Final edition (retail, 1995?).
59 // This is supposedly ignored for commercial
60 //  release (aka DOOM II), which had 34 maps
61 //  in one episode. So there.
62 #define NUMEPISODES 4
63 #define NUMMAPS     9
64 
65 
66 // Not used
67 // in tics
68 //U #define PAUSELEN    (TICRATE*2)
69 //U #define SCORESTEP    100
70 //U #define ANIMPERIOD    32
71 // pixel distance from "(YOU)" to "PLAYER N"
72 //U #define STARDIST  10
73 //U #define WK 1
74 
75 
76 // GLOBAL LOCATIONS
77 #define WI_TITLEY      2
78 #define WI_SPACINGY   33
79 
80 // SINGLE-PLAYER STUFF
81 #define SP_STATSX     50
82 #define SP_STATSY     50
83 
84 #define SP_TIMEX      8
85 // proff/nicolas 09/20/98 -- changed for hi-res
86 #define SP_TIMEY      160
87 //#define SP_TIMEY      (SCREENHEIGHT-32)
88 
89 
90 // NET GAME STUFF
91 #define NG_STATSY     50
92 #define NG_STATSX     (32 + V_NamePatchWidth(star)/2 + 32*!dofrags)
93 
94 #define NG_SPACINGX   64
95 
96 
97 // Used to display the frags matrix at endgame
98 // DEATHMATCH STUFF
99 #define DM_MATRIXX    42
100 #define DM_MATRIXY    68
101 
102 #define DM_SPACINGX   40
103 
104 #define DM_TOTALSX   269
105 
106 #define DM_KILLERSX   10
107 #define DM_KILLERSY  100
108 #define DM_VICTIMSX    5
109 #define DM_VICTIMSY   50
110 
111 
112 // These animation variables, structures, etc. are used for the
113 // DOOM/Ultimate DOOM intermission screen animations.  This is
114 // totally different from any sprite or texture/flat animations
115 typedef enum
116 {
117   ANIM_ALWAYS,   // determined by patch entry
118   ANIM_RANDOM,   // occasional
119   ANIM_LEVEL     // continuous
120 } animenum_t;
121 
122 typedef struct
123 {
124   int   x;       // x/y coordinate pair structure
125   int   y;
126 } point_t;
127 
128 
129 //
130 // Animation.
131 // There is another anim_t used in p_spec.
132 //
133 typedef struct
134 {
135   animenum_t  type;
136 
137   // period in tics between animations
138   int   period;
139 
140   // number of animation frames
141   int   nanims;
142 
143   // location of animation
144   point_t loc;
145 
146   // ALWAYS: n/a,
147   // RANDOM: period deviation (<256),
148   // LEVEL: level
149   int   data1;
150 
151   // ALWAYS: n/a,
152   // RANDOM: random base period,
153   // LEVEL: n/a
154   int   data2;
155 
156   /* actual graphics for frames of animations
157    * cphipps - const
158    */
159   patchnum_t p[3];
160 
161   // following must be initialized to zero before use!
162 
163   // next value of bcnt (used in conjunction with period)
164   int   nexttic;
165 
166   // last drawn animation frame
167   int   lastdrawn;
168 
169   // next frame number to animate
170   int   ctr;
171 
172   // used by RANDOM and LEVEL when animating
173   int   state;
174 } anim_t;
175 
176 
177 static point_t lnodes[NUMEPISODES][NUMMAPS] =
178 {
179   // Episode 0 World Map
180   {
181     { 185, 164 }, // location of level 0 (CJ)
182     { 148, 143 }, // location of level 1 (CJ)
183     { 69, 122 },  // location of level 2 (CJ)
184     { 209, 102 }, // location of level 3 (CJ)
185     { 116, 89 },  // location of level 4 (CJ)
186     { 166, 55 },  // location of level 5 (CJ)
187     { 71, 56 },   // location of level 6 (CJ)
188     { 135, 29 },  // location of level 7 (CJ)
189     { 71, 24 }    // location of level 8 (CJ)
190   },
191 
192   // Episode 1 World Map should go here
193   {
194     { 254, 25 },  // location of level 0 (CJ)
195     { 97, 50 },   // location of level 1 (CJ)
196     { 188, 64 },  // location of level 2 (CJ)
197     { 128, 78 },  // location of level 3 (CJ)
198     { 214, 92 },  // location of level 4 (CJ)
199     { 133, 130 }, // location of level 5 (CJ)
200     { 208, 136 }, // location of level 6 (CJ)
201     { 148, 140 }, // location of level 7 (CJ)
202     { 235, 158 }  // location of level 8 (CJ)
203   },
204 
205   // Episode 2 World Map should go here
206   {
207     { 156, 168 }, // location of level 0 (CJ)
208     { 48, 154 },  // location of level 1 (CJ)
209     { 174, 95 },  // location of level 2 (CJ)
210     { 265, 75 },  // location of level 3 (CJ)
211     { 130, 48 },  // location of level 4 (CJ)
212     { 279, 23 },  // location of level 5 (CJ)
213     { 198, 48 },  // location of level 6 (CJ)
214     { 140, 25 },  // location of level 7 (CJ)
215     { 281, 136 }  // location of level 8 (CJ)
216   }
217 };
218 
219 
220 //
221 // Animation locations for episode 0 (1).
222 // Using patches saves a lot of space,
223 //  as they replace 320x200 full screen frames.
224 //
225 static anim_t epsd0animinfo[] =
226 {
227   { ANIM_ALWAYS, TICRATE/3, 3, { 224, 104 } },
228   { ANIM_ALWAYS, TICRATE/3, 3, { 184, 160 } },
229   { ANIM_ALWAYS, TICRATE/3, 3, { 112, 136 } },
230   { ANIM_ALWAYS, TICRATE/3, 3, { 72, 112 } },
231   { ANIM_ALWAYS, TICRATE/3, 3, { 88, 96 } },
232   { ANIM_ALWAYS, TICRATE/3, 3, { 64, 48 } },
233   { ANIM_ALWAYS, TICRATE/3, 3, { 192, 40 } },
234   { ANIM_ALWAYS, TICRATE/3, 3, { 136, 16 } },
235   { ANIM_ALWAYS, TICRATE/3, 3, { 80, 16 } },
236   { ANIM_ALWAYS, TICRATE/3, 3, { 64, 24 } }
237 };
238 
239 static anim_t epsd1animinfo[] =
240 {
241   { ANIM_LEVEL,  TICRATE/3, 1, { 128, 136 }, 1 },
242   { ANIM_LEVEL,  TICRATE/3, 1, { 128, 136 }, 2 },
243   { ANIM_LEVEL,  TICRATE/3, 1, { 128, 136 }, 3 },
244   { ANIM_LEVEL,  TICRATE/3, 1, { 128, 136 }, 4 },
245   { ANIM_LEVEL,  TICRATE/3, 1, { 128, 136 }, 5 },
246   { ANIM_LEVEL,  TICRATE/3, 1, { 128, 136 }, 6 },
247   { ANIM_LEVEL,  TICRATE/3, 1, { 128, 136 }, 7 },
248   { ANIM_LEVEL,  TICRATE/3, 3, { 192, 144 }, 8 },
249   { ANIM_LEVEL,  TICRATE/3, 1, { 128, 136 }, 8 }
250 };
251 
252 static anim_t epsd2animinfo[] =
253 {
254   { ANIM_ALWAYS, TICRATE/3, 3, { 104, 168 } },
255   { ANIM_ALWAYS, TICRATE/3, 3, { 40, 136 } },
256   { ANIM_ALWAYS, TICRATE/3, 3, { 160, 96 } },
257   { ANIM_ALWAYS, TICRATE/3, 3, { 104, 80 } },
258   { ANIM_ALWAYS, TICRATE/3, 3, { 120, 32 } },
259   { ANIM_ALWAYS, TICRATE/4, 3, { 40, 0 } }
260 };
261 
262 static int NUMANIMS[NUMEPISODES] =
263 {
264   sizeof(epsd0animinfo)/sizeof(anim_t),
265   sizeof(epsd1animinfo)/sizeof(anim_t),
266   sizeof(epsd2animinfo)/sizeof(anim_t)
267 };
268 
269 static anim_t *anims[NUMEPISODES] =
270 {
271   epsd0animinfo,
272   epsd1animinfo,
273   epsd2animinfo
274 };
275 
276 
277 //
278 // GENERAL DATA
279 //
280 
281 //
282 // Locally used stuff.
283 //
284 #define FB 0
285 
286 
287 // States for single-player
288 #define SP_KILLS    0
289 #define SP_ITEMS    2
290 #define SP_SECRET   4
291 #define SP_FRAGS    6
292 #define SP_TIME     8
293 #define SP_PAR      ST_TIME
294 
295 #define SP_PAUSE    1
296 
297 // in seconds
298 #define SHOWNEXTLOCDELAY  4
299 //#define SHOWLASTLOCDELAY  SHOWNEXTLOCDELAY
300 
301 
302 // used to accelerate or skip a stage
303 int   acceleratestage;           // killough 3/28/98: made global
304 
305 // wbs->pnum
306 static int    me;
307 
308  // specifies current state
309 static stateenum_t  state;
310 
311 // contains information passed into intermission
312 static wbstartstruct_t* wbs;
313 
314 static wbplayerstruct_t* plrs;  // wbs->plyr[]
315 
316 // used for general timing
317 static int    cnt;
318 
319 // used for timing of background animation
320 static int    bcnt;
321 
322 // signals to refresh everything for one frame
323 static int    firstrefresh;
324 
325 static int    cnt_time;
326 static int    cnt_total_time;
327 static int    cnt_par;
328 static int    cnt_pause;
329 
330 //
331 //  GRAPHICS
332 //
333 
334 // You Are Here graphic
335 static const char* yah[3] = { "WIURH0", "WIURH1", 0 };
336 
337 // splat
338 static const char* splat[2] = {"WISPLAT", 0};
339 
340 // %, : graphics
341 static const char percent[] = {"WIPCNT"};
342 static const char colon[] = {"WICOLON"};
343 
344 // 0-9 graphic
345 static patchnum_t num[10];
346 
347 // minus sign
348 static const char wiminus[] = {"WIMINUS"};
349 
350 // "Finished!" graphics
351 static const char finished[] = {"WIF"};
352 
353 // "Entering" graphic
354 static const char entering[] = {"WIENTER"};
355 
356 // "secret"
357 static const char sp_secret[] = {"WISCRT2"};
358 
359 // "Kills", "Scrt", "Items", "Frags"
360 static const char kills[] = {"WIOSTK"};
361 static const char secret[] = {"WIOSTS"};
362 static const char items[] = {"WIOSTI"};
363 static const char frags[] = {"WIFRGS"};
364 
365 // Time sucks.
366 static const char time1[] = {"WITIME"};
367 static const char par[] = {"WIPAR"};
368 static const char sucks[] = {"WISUCKS"};
369 
370 // "killers", "victims"
371 static const char killers[] = {"WIKILRS"};
372 static const char victims[] = {"WIVCTMS"};
373 
374 // "Total", your face, your dead face
375 static const char total[] = {"WIMSTT"};
376 static const char star[] = {"STFST01"};
377 static const char bstar[] = {"STFDEAD0"};
378 
379 // "red P[1..MAXPLAYERS]"
380 static const char facebackp[] = {"STPB0"};
381 
382 //
383 // CODE
384 //
385 
386 static void WI_endDeathmatchStats(void);
387 static void WI_endNetgameStats(void);
388 #define WI_endStats WI_endNetgameStats
389 
390 /* ====================================================================
391  * WI_levelNameLump
392  * Purpore: Returns the name of the graphic lump containing the name of
393  *          the given level.
394  * Args:    Episode and level, and buffer (must by 9 chars) to write to
395  * Returns: void
396  */
WI_levelNameLump(int epis,int map,char * buf)397 void WI_levelNameLump(int epis, int map, char* buf)
398 {
399   if (gamemode == commercial) {
400     sprintf(buf, "CWILV%2.2d", map);
401   } else {
402     sprintf(buf, "WILV%d%d", epis, map);
403   }
404 }
405 
406 // ====================================================================
407 // WI_slamBackground
408 // Purpose: Put the full-screen background up prior to patches
409 // Args:    none
410 // Returns: void
411 //
WI_slamBackground(void)412 static void WI_slamBackground(void)
413 {
414   char  name[9];  // limited to 8 characters
415 
416   if (gamemode == commercial || (gamemode == retail && wbs->epsd == 3))
417     strcpy(name, "INTERPIC");
418   else
419     sprintf(name, "WIMAP%d", wbs->epsd);
420 
421   // background
422   V_DrawNamePatch(0, 0, FB, name, CR_DEFAULT, VPT_STRETCH);
423 
424   // e6y: wide-res
425   V_FillBorder(-1, 0);
426 }
427 
428 
429 // ====================================================================
430 // WI_Responder
431 // Purpose: Draw animations on intermission background screen
432 // Args:    ev    -- event pointer, not actually used here.
433 // Returns: False -- dummy routine
434 //
435 // The ticker is used to detect keys
436 //  because of timing issues in netgames.
WI_Responder(event_t * ev)437 dboolean WI_Responder(event_t* ev)
438 {
439   return false;
440 }
441 
442 
443 // ====================================================================
444 // WI_drawLF
445 // Purpose: Draw the "Finished" level name before showing stats
446 // Args:    none
447 // Returns: void
448 //
WI_drawLF(void)449 void WI_drawLF(void)
450 {
451   int y = WI_TITLEY;
452   char lname[9];
453 
454   // draw <LevelName>
455   /* cph - get the graphic lump name and use it */
456   WI_levelNameLump(wbs->epsd, wbs->last, lname);
457   // CPhipps - patch drawing updated
458   V_DrawNamePatch((320 - V_NamePatchWidth(lname))/2, y,
459      FB, lname, CR_DEFAULT, VPT_STRETCH);
460 
461   // draw "Finished!"
462   y += (5*V_NamePatchHeight(lname))/4;
463 
464   // CPhipps - patch drawing updated
465   V_DrawNamePatch((320 - V_NamePatchWidth(finished))/2, y,
466      FB, finished, CR_DEFAULT, VPT_STRETCH);
467 }
468 
469 
470 // ====================================================================
471 // WI_drawEL
472 // Purpose: Draw introductory "Entering" and level name
473 // Args:    none
474 // Returns: void
475 //
WI_drawEL(void)476 void WI_drawEL(void)
477 {
478   int y = WI_TITLEY;
479   char lname[9];
480 
481   /* cph - get the graphic lump name */
482   WI_levelNameLump(wbs->epsd, wbs->next, lname);
483 
484   // draw "Entering"
485   // CPhipps - patch drawing updated
486   V_DrawNamePatch((320 - V_NamePatchWidth(entering))/2,
487       y, FB, entering, CR_DEFAULT, VPT_STRETCH);
488 
489   // draw level
490   y += (5*V_NamePatchHeight(lname))/4;
491 
492   // CPhipps - patch drawing updated
493   V_DrawNamePatch((320 - V_NamePatchWidth(lname))/2, y, FB,
494      lname, CR_DEFAULT, VPT_STRETCH);
495 }
496 
497 
498 /* ====================================================================
499  * WI_drawOnLnode
500  * Purpose: Draw patches at a location based on episode/map
501  * Args:    n   -- index to map# within episode
502  *          c[] -- array of names of patches to be drawn
503  * Returns: void
504  */
505 void
WI_drawOnLnode(int n,const char * const c[])506 WI_drawOnLnode  // draw stuff at a location by episode/map#
507 ( int   n,
508   const char* const c[] )
509 {
510   int   i;
511   dboolean fits = false;
512 
513   i = 0;
514   do
515   {
516     int            left;
517     int            top;
518     int            right;
519     int            bottom;
520     const rpatch_t* patch = R_CachePatchName(c[i]);
521 
522     left = lnodes[wbs->epsd][n].x - patch->leftoffset;
523     top = lnodes[wbs->epsd][n].y - patch->topoffset;
524     right = left + patch->width;
525     bottom = top + patch->height;
526     R_UnlockPatchName(c[i]);
527 
528     if (left >= 0
529        && right < 320
530        && top >= 0
531        && bottom < 200)
532     {
533       fits = true;
534     }
535     else
536     {
537       i++;
538     }
539   } while (!fits && i!=2 && c[i]);
540 
541   if (fits && i<2)
542   {
543     // CPhipps - patch drawing updated
544     V_DrawNamePatch(lnodes[wbs->epsd][n].x, lnodes[wbs->epsd][n].y,
545        FB, c[i], CR_DEFAULT, VPT_STRETCH);
546   }
547   else
548   {
549     // DEBUG
550     //jff 8/3/98 use logical output routine
551     lprintf(LO_DEBUG,"Could not place patch on level %d\n", n+1);
552   }
553 }
554 
555 
556 // ====================================================================
557 // WI_initAnimatedBack
558 // Purpose: Initialize pointers and styles for background animation
559 // Args:    none
560 // Returns: void
561 //
WI_initAnimatedBack(void)562 void WI_initAnimatedBack(void)
563 {
564   int   i;
565   anim_t* a;
566 
567   if (gamemode == commercial)  // no animation for DOOM2
568     return;
569 
570   if (wbs->epsd > 2)
571     return;
572 
573   for (i=0;i<NUMANIMS[wbs->epsd];i++)
574   {
575     a = &anims[wbs->epsd][i];
576 
577     // init variables
578     a->ctr = -1;
579 
580     // specify the next time to draw it
581     if (a->type == ANIM_ALWAYS)
582       a->nexttic = bcnt + 1 + (M_Random()%a->period);
583     else
584       if (a->type == ANIM_RANDOM)
585         a->nexttic = bcnt + 1 + a->data2+(M_Random()%a->data1);
586       else
587         if (a->type == ANIM_LEVEL)
588           a->nexttic = bcnt + 1;
589   }
590 }
591 
592 
593 // ====================================================================
594 // WI_updateAnimatedBack
595 // Purpose: Figure out what animation we do on this iteration
596 // Args:    none
597 // Returns: void
598 //
WI_updateAnimatedBack(void)599 void WI_updateAnimatedBack(void)
600 {
601   int   i;
602   anim_t* a;
603 
604   if (gamemode == commercial)
605     return;
606 
607   if (wbs->epsd > 2)
608     return;
609 
610   for (i=0;i<NUMANIMS[wbs->epsd];i++)
611   {
612     a = &anims[wbs->epsd][i];
613 
614     if (bcnt == a->nexttic)
615     {
616       switch (a->type)
617       {
618         case ANIM_ALWAYS:
619              if (++a->ctr >= a->nanims) a->ctr = 0;
620              a->nexttic = bcnt + a->period;
621              break;
622 
623         case ANIM_RANDOM:
624              a->ctr++;
625              if (a->ctr == a->nanims)
626              {
627                a->ctr = -1;
628                a->nexttic = bcnt+a->data2+(M_Random()%a->data1);
629              }
630              else
631                a->nexttic = bcnt + a->period;
632              break;
633 
634         case ANIM_LEVEL:
635              // gawd-awful hack for level anims
636              if (!(state == StatCount && i == 7)
637                 && wbs->next == a->data1)
638              {
639                a->ctr++;
640                if (a->ctr == a->nanims) a->ctr--;
641                a->nexttic = bcnt + a->period;
642              }
643              break;
644       }
645     }
646   }
647 }
648 
649 
650 // ====================================================================
651 // WI_drawAnimatedBack
652 // Purpose: Actually do the animation (whew!)
653 // Args:    none
654 // Returns: void
655 //
WI_drawAnimatedBack(void)656 void WI_drawAnimatedBack(void)
657 {
658   int     i;
659   anim_t*   a;
660 
661   if (gamemode==commercial) //jff 4/25/98 Someone forgot commercial an enum
662     return;
663 
664   if (wbs->epsd > 2)
665     return;
666 
667   for (i=0 ; i<NUMANIMS[wbs->epsd] ; i++)
668   {
669     a = &anims[wbs->epsd][i];
670 
671     if (a->ctr >= 0)
672       // CPhipps - patch drawing updated
673       V_DrawNumPatch(a->loc.x, a->loc.y, FB, a->p[a->ctr].lumpnum, CR_DEFAULT, VPT_STRETCH);
674   }
675 }
676 
677 
678 // ====================================================================
679 // WI_drawNum
680 // Purpose: Draws a number.  If digits > 0, then use that many digits
681 //          minimum, otherwise only use as many as necessary
682 // Args:    x, y   -- location
683 //          n      -- the number to be drawn
684 //          digits -- number of digits minimum or zero
685 // Returns: new x position after drawing (note we are going to the left)
686 // CPhipps - static
WI_drawNum(int x,int y,int n,int digits)687 static int WI_drawNum (int x, int y, int n, int digits)
688 {
689   int   fontwidth = num[0].width;
690   int   neg;
691   int   temp;
692 
693   if (digits < 0)
694   {
695     if (!n)
696     {
697       // make variable-length zeros 1 digit long
698       digits = 1;
699     }
700     else
701     {
702       // figure out # of digits in #
703       digits = 0;
704       temp = n;
705 
706       while (temp)
707       {
708         temp /= 10;
709         digits++;
710       }
711     }
712   }
713 
714   neg = n < 0;
715   if (neg)
716     n = -n;
717 
718   // if non-number, do not draw it
719   if (n == 1994)
720     return 0;
721 
722   // draw the new number
723   while (digits--)
724   {
725     x -= fontwidth;
726     // CPhipps - patch drawing updated
727     V_DrawNumPatch(x, y, FB, num[ n % 10 ].lumpnum, CR_DEFAULT, VPT_STRETCH);
728     n /= 10;
729   }
730 
731   // draw a minus sign if necessary
732   if (neg)
733     // CPhipps - patch drawing updated
734     V_DrawNamePatch(x-=8, y, FB, wiminus, CR_DEFAULT, VPT_STRETCH);
735 
736   return x;
737 }
738 
739 
740 // ====================================================================
741 // WI_drawPercent
742 // Purpose: Draws a percentage, really just a call to WI_drawNum
743 //          after putting a percent sign out there
744 // Args:    x, y   -- location
745 //          p      -- the percentage value to be drawn, no negatives
746 // Returns: void
747 // CPhipps - static
WI_drawPercent(int x,int y,int p)748 static void WI_drawPercent(int x, int y, int p)
749 {
750   if (p < 0)
751     return;
752 
753   // CPhipps - patch drawing updated
754   V_DrawNamePatch(x, y, FB, percent, CR_DEFAULT, VPT_STRETCH);
755   WI_drawNum(x, y, p, -1);
756 }
757 
758 
759 // ====================================================================
760 // WI_drawTime
761 // Purpose: Draws the level completion time or par time, or "Sucks"
762 //          if 1 hour or more
763 // Args:    x, y   -- location
764 //          t      -- the time value to be drawn
765 // Returns: void
766 //
767 // CPhipps - static
768 //         - largely rewritten to display hours and use slightly better algorithm
769 
WI_drawTime(int x,int y,int t)770 static void WI_drawTime(int x, int y, int t)
771 {
772   int   n;
773 
774   if (t<0)
775     return;
776 
777   if (t < 100*60*60)
778     for(;;) {
779       n = t % 60;
780       t /= 60;
781       x = WI_drawNum(x, y, n, (t || n>9) ? 2 : 1) - V_NamePatchWidth(colon);
782 
783       // draw
784       if (t)
785   // CPhipps - patch drawing updated
786         V_DrawNamePatch(x, y, FB, colon, CR_DEFAULT, VPT_STRETCH);
787       else break;
788     }
789   else // "sucks" (maybe should be "addicted", even I've never had a 100 hour game ;)
790     V_DrawNamePatch(x - V_NamePatchWidth(sucks),
791         y, FB, sucks, CR_DEFAULT, VPT_STRETCH);
792 }
793 
794 
795 // ====================================================================
796 // WI_End
797 // Purpose: Unloads data structures (inverse of WI_Start)
798 // Args:    none
799 // Returns: void
800 //
WI_End(void)801 void WI_End(void)
802 {
803   if (deathmatch)
804     WI_endDeathmatchStats();
805   else if (netgame)
806     WI_endNetgameStats();
807   else
808     WI_endStats();
809 }
810 
811 
812 // ====================================================================
813 // WI_initNoState
814 // Purpose: Clear state, ready for end of level activity
815 // Args:    none
816 // Returns: void
817 //
WI_initNoState(void)818 void WI_initNoState(void)
819 {
820   state = NoState;
821   acceleratestage = 0;
822   cnt = 10;
823 }
824 
825 
826 // ====================================================================
827 // WI_drawTimeStats
828 // Purpose: Put the times on the screen
829 // Args:    time, total time, par time, in seconds
830 // Returns: void
831 //
832 // cph - pulled from WI_drawStats below
833 
WI_drawTimeStats(int cnt_time,int cnt_total_time,int cnt_par)834 static void WI_drawTimeStats(int cnt_time, int cnt_total_time, int cnt_par)
835 {
836   V_DrawNamePatch(SP_TIMEX, SP_TIMEY, FB, time1, CR_DEFAULT, VPT_STRETCH);
837   WI_drawTime(320/2 - SP_TIMEX, SP_TIMEY, cnt_time);
838 
839   V_DrawNamePatch(SP_TIMEX, (SP_TIMEY+200)/2, FB, total, CR_DEFAULT, VPT_STRETCH);
840   WI_drawTime(320/2 - SP_TIMEX, (SP_TIMEY+200)/2, cnt_total_time);
841 
842   // Ty 04/11/98: redid logic: should skip only if with pwad but
843   // without deh patch
844   // killough 2/22/98: skip drawing par times on pwads
845   // Ty 03/17/98: unless pars changed with deh patch
846 
847   if (!(modifiedgame && !deh_pars))
848   {
849     if (wbs->epsd < 3)
850     {
851       V_DrawNamePatch(320/2 + SP_TIMEX, SP_TIMEY, FB, par, CR_DEFAULT, VPT_STRETCH);
852       WI_drawTime(320 - SP_TIMEX, SP_TIMEY, cnt_par);
853     }
854   }
855 }
856 
857 // ====================================================================
858 // WI_updateNoState
859 // Purpose: Cycle until end of level activity is done
860 // Args:    none
861 // Returns: void
862 //
WI_updateNoState(void)863 void WI_updateNoState(void)
864 {
865 
866   WI_updateAnimatedBack();
867 
868   if (!--cnt)
869     G_WorldDone();
870 }
871 
872 static dboolean    snl_pointeron = false;
873 
874 
875 // ====================================================================
876 // WI_initShowNextLoc
877 // Purpose: Prepare to show the next level's location
878 // Args:    none
879 // Returns: void
880 //
WI_initShowNextLoc(void)881 void WI_initShowNextLoc(void)
882 {
883   if ((gamemode != commercial) && (gamemap == 8)) {
884     G_WorldDone();
885     return;
886   }
887 
888   state = ShowNextLoc;
889   acceleratestage = 0;
890 
891   // e6y: That was pretty easy - only a HEX editor and luck
892   // There is no more desync on ddt-tas.zip\e4tux231.lmp
893   // --------- tasdoom.idb ---------
894   // .text:00031194 loc_31194:      ; CODE XREF: WI_updateStats+3A9j
895   // .text:00031194                 mov     ds:state, 1
896   // .text:0003119E                 mov     ds:acceleratestage, 0
897   // .text:000311A8                 mov     ds:cnt, 3Ch
898   // nowhere no hide
899   if (compatibility_level == tasdoom_compatibility)
900     cnt = 60;
901   else
902     cnt = SHOWNEXTLOCDELAY * TICRATE;
903 
904   WI_initAnimatedBack();
905 }
906 
907 
908 // ====================================================================
909 // WI_updateShowNextLoc
910 // Purpose: Prepare to show the next level's location
911 // Args:    none
912 // Returns: void
913 //
WI_updateShowNextLoc(void)914 void WI_updateShowNextLoc(void)
915 {
916   WI_updateAnimatedBack();
917 
918   if (!--cnt || acceleratestage)
919     WI_initNoState();
920   else
921     snl_pointeron = (cnt & 31) < 20;
922 }
923 
924 
925 // ====================================================================
926 // WI_drawShowNextLoc
927 // Purpose: Show the next level's location on animated backgrounds
928 // Args:    none
929 // Returns: void
930 //
WI_drawShowNextLoc(void)931 void WI_drawShowNextLoc(void)
932 {
933   int   i;
934   int   last;
935 
936   WI_slamBackground();
937 
938   // draw animated background
939   WI_drawAnimatedBack();
940 
941   if ( gamemode != commercial)
942   {
943     if (wbs->epsd > 2)
944     {
945       WI_drawEL();  // "Entering..." if not E1 or E2
946       return;
947     }
948 
949     last = (wbs->last == 8) ? wbs->next - 1 : wbs->last;
950 
951     // draw a splat on taken cities.
952     for (i=0 ; i<=last ; i++)
953       WI_drawOnLnode(i, splat);
954 
955     // splat the secret level?
956     if (wbs->didsecret)
957       WI_drawOnLnode(8, splat);
958 
959     // draw flashing ptr
960     if (snl_pointeron)
961       WI_drawOnLnode(wbs->next, yah);
962   }
963 
964   // draws which level you are entering..
965   if ( (gamemode != commercial)
966      || wbs->next != 30)  // check for MAP30 end game
967   WI_drawEL();
968 }
969 
970 // ====================================================================
971 // WI_drawNoState
972 // Purpose: Draw the pointer and next location
973 // Args:    none
974 // Returns: void
975 //
WI_drawNoState(void)976 void WI_drawNoState(void)
977 {
978   snl_pointeron = true;
979   WI_drawShowNextLoc();
980 }
981 
982 // ====================================================================
983 // WI_fragSum
984 // Purpose: Calculate frags for this player based on the current totals
985 //          of all the other players.  Subtract self-frags.
986 // Args:    playernum -- the player to be calculated
987 // Returns: the total frags for this player
988 //
WI_fragSum(int playernum)989 int WI_fragSum(int playernum)
990 {
991   int   i;
992   int   frags = 0;
993 
994   for (i=0 ; i<MAXPLAYERS ; i++)
995   {
996     if (playeringame[i]  // is this player playing?
997        && i!=playernum) // and it's not the player we're calculating
998     {
999       frags += plrs[playernum].frags[i];
1000     }
1001   }
1002 
1003 
1004   // JDC hack - negative frags.
1005   frags -= plrs[playernum].frags[playernum];
1006 
1007   return frags;
1008 }
1009 
1010 static int          dm_state;
1011 // CPhipps - short, dynamically allocated
1012 static short int  **dm_frags;  // frags matrix
1013 static short int   *dm_totals;  // totals by player
1014 
1015 // ====================================================================
1016 // WI_initDeathmatchStats
1017 // Purpose: Set up to display DM stats at end of level.  Calculate
1018 //          frags for all players.
1019 // Args:    none
1020 // Returns: void
1021 //
WI_initDeathmatchStats(void)1022 void WI_initDeathmatchStats(void)
1023 {
1024   int   i; // looping variables
1025 
1026   // CPhipps - allocate data structures needed
1027   dm_frags  = calloc(MAXPLAYERS, sizeof(*dm_frags));
1028   dm_totals = calloc(MAXPLAYERS, sizeof(*dm_totals));
1029 
1030   state = StatCount;  // We're doing stats
1031   acceleratestage = 0;
1032   dm_state = 1;  // count how many times we've done a complete stat
1033 
1034   cnt_pause = TICRATE;
1035 
1036   for (i=0 ; i<MAXPLAYERS ; i++)
1037   {
1038     if (playeringame[i])
1039     {
1040       // CPhipps - allocate frags line
1041       dm_frags[i] = calloc(MAXPLAYERS, sizeof(**dm_frags)); // set all counts to zero
1042 
1043       dm_totals[i] = 0;
1044     }
1045   }
1046   WI_initAnimatedBack();
1047 }
1048 
1049 // ====================================================================
1050 // CPhipps - WI_endDeathmatchStats
1051 // Purpose: Deallocate dynamically allocated DM stats data
1052 // Args:    none
1053 // Returns: void
1054 //
1055 
WI_endDeathmatchStats(void)1056 void WI_endDeathmatchStats(void)
1057 {
1058   int i;
1059   for (i=0; i<MAXPLAYERS; i++)
1060     free(dm_frags[i]);
1061 
1062   free(dm_frags); free(dm_totals);
1063 }
1064 
1065 // ====================================================================
1066 // WI_updateDeathmatchStats
1067 // Purpose: Advance Deathmatch stats screen animation.  Calculate
1068 //          frags for all players.  Lots of noise and drama around
1069 //          the presentation.
1070 // Args:    none
1071 // Returns: void
1072 //
WI_updateDeathmatchStats(void)1073 void WI_updateDeathmatchStats(void)
1074 {
1075   int   i;
1076   int   j;
1077 
1078   dboolean stillticking;
1079 
1080   WI_updateAnimatedBack();
1081 
1082   if (acceleratestage && dm_state != 4)  // still ticking
1083   {
1084     acceleratestage = 0;
1085 
1086     for (i=0 ; i<MAXPLAYERS ; i++)
1087     {
1088       if (playeringame[i])
1089       {
1090         for (j=0 ; j<MAXPLAYERS ; j++)
1091           if (playeringame[j])
1092             dm_frags[i][j] = plrs[i].frags[j];
1093 
1094         dm_totals[i] = WI_fragSum(i);
1095       }
1096     }
1097 
1098 
1099     S_StartSound(0, sfx_barexp);  // bang
1100     dm_state = 4;  // we're done with all 4 (or all we have to do)
1101   }
1102 
1103 
1104   if (dm_state == 2)
1105   {
1106     if (!(bcnt&3))
1107       S_StartSound(0, sfx_pistol);  // noise while counting
1108 
1109     stillticking = false;
1110 
1111     for (i=0 ; i<MAXPLAYERS ; i++)
1112     {
1113       if (playeringame[i])
1114       {
1115         for (j=0 ; j<MAXPLAYERS ; j++)
1116         {
1117           if (playeringame[j]
1118              && dm_frags[i][j] != plrs[i].frags[j])
1119           {
1120             if (plrs[i].frags[j] < 0)
1121               dm_frags[i][j]--;
1122             else
1123               dm_frags[i][j]++;
1124 
1125             if (dm_frags[i][j] > 999) // Ty 03/17/98 3-digit frag count
1126               dm_frags[i][j] = 999;
1127 
1128             if (dm_frags[i][j] < -999)
1129               dm_frags[i][j] = -999;
1130 
1131             stillticking = true;
1132           }
1133         }
1134         dm_totals[i] = WI_fragSum(i);
1135 
1136         if (dm_totals[i] > 999)
1137           dm_totals[i] = 999;
1138 
1139         if (dm_totals[i] < -999)
1140           dm_totals[i] = -999;  // Ty 03/17/98 end 3-digit frag count
1141       }
1142     }
1143 
1144     if (!stillticking)
1145     {
1146       S_StartSound(0, sfx_barexp);
1147       dm_state++;
1148     }
1149   }
1150   else if (dm_state == 4)
1151   {
1152     if (acceleratestage)
1153     {
1154       S_StartSound(0, sfx_slop);
1155 
1156       if ( gamemode == commercial)
1157         WI_initNoState();
1158       else
1159         WI_initShowNextLoc();
1160     }
1161   }
1162   else if (dm_state & 1)
1163   {
1164     if (!--cnt_pause)
1165     {
1166       dm_state++;
1167       cnt_pause = TICRATE;
1168     }
1169   }
1170 }
1171 
1172 
1173 // ====================================================================
1174 // WI_drawDeathmatchStats
1175 // Purpose: Draw the stats on the screen in a matrix
1176 // Args:    none
1177 // Returns: void
1178 //
1179 // proff/nicolas 09/20/98 -- changed for hi-res
1180 // CPhipps - patch drawing updated
WI_drawDeathmatchStats(void)1181 void WI_drawDeathmatchStats(void)
1182 {
1183   int   i;
1184   int   j;
1185   int   x;
1186   int   y;
1187   int   w;
1188 
1189   int   lh; // line height
1190   int   halfface = V_NamePatchWidth(facebackp)/2;
1191 
1192   lh = WI_SPACINGY;
1193 
1194   WI_slamBackground();
1195 
1196   // draw animated background
1197   WI_drawAnimatedBack();
1198   WI_drawLF();
1199 
1200   // draw stat titles (top line)
1201   V_DrawNamePatch(DM_TOTALSX-V_NamePatchWidth(total)/2,
1202      DM_MATRIXY-WI_SPACINGY+10, FB, total, CR_DEFAULT, VPT_STRETCH);
1203 
1204   V_DrawNamePatch(DM_KILLERSX, DM_KILLERSY, FB, killers, CR_DEFAULT, VPT_STRETCH);
1205   V_DrawNamePatch(DM_VICTIMSX, DM_VICTIMSY, FB, victims, CR_DEFAULT, VPT_STRETCH);
1206 
1207   // draw P?
1208   x = DM_MATRIXX + DM_SPACINGX;
1209   y = DM_MATRIXY;
1210 
1211   for (i=0 ; i<MAXPLAYERS ; i++)
1212   {
1213     if (playeringame[i]) {
1214       //int trans = playernumtotrans[i];
1215       V_DrawNamePatch(x-halfface, DM_MATRIXY - WI_SPACINGY,
1216          FB, facebackp, i ? CR_LIMIT+i : CR_DEFAULT,
1217          VPT_STRETCH | (i ? VPT_TRANS : 0));
1218       V_DrawNamePatch(DM_MATRIXX-halfface, y,
1219          FB, facebackp, i ? CR_LIMIT+i : CR_DEFAULT,
1220          VPT_STRETCH | (i ? VPT_TRANS : 0));
1221 
1222       if (i == me)
1223       {
1224         V_DrawNamePatch(x-halfface, DM_MATRIXY - WI_SPACINGY,
1225            FB, bstar, CR_DEFAULT, VPT_STRETCH);
1226         V_DrawNamePatch(DM_MATRIXX-halfface, y,
1227            FB, star, CR_DEFAULT, VPT_STRETCH);
1228       }
1229     }
1230     x += DM_SPACINGX;
1231     y += WI_SPACINGY;
1232   }
1233 
1234   // draw stats
1235   y = DM_MATRIXY+10;
1236   w = num[0].width;
1237 
1238   for (i=0 ; i<MAXPLAYERS ; i++)
1239   {
1240     x = DM_MATRIXX + DM_SPACINGX;
1241 
1242     if (playeringame[i])
1243     {
1244       for (j=0 ; j<MAXPLAYERS ; j++)
1245       {
1246         if (playeringame[j])
1247           WI_drawNum(x+w, y, dm_frags[i][j], 2);
1248 
1249         x += DM_SPACINGX;
1250       }
1251       WI_drawNum(DM_TOTALSX+w, y, dm_totals[i], 2);
1252     }
1253     y += WI_SPACINGY;
1254   }
1255 }
1256 
1257 
1258 //
1259 // Note: The term "Netgame" means a coop game
1260 //
1261 
1262 // e6y
1263 // 'short' => 'int' for cnt_kills, cnt_items and cnt_secret
1264 //
1265 // Original sources use 'int' type for cnt_kills instead of 'short'
1266 // I don't know who have made change of type, but this change
1267 // leads to desynch  if 'kills' percentage is more than 32767.
1268 // Actually PrBoom will be in an infinite cycle at calculation of
1269 // percentage if the player will not press <Use> for acceleration, because
1270 // the condition (cnt_kills[0] >= (plrs[me].skills * 100) / wbs->maxkills)
1271 // will be always false in this case.
1272 //
1273 // If you will kill 800 monsters on MAP30 on Ultra-Violence skill and
1274 // will not press <Use>, vanilla will count up to 80000%, but PrBoom
1275 // will be in infinite cycle of counting:
1276 // (0, 1, 2, ..., 32766, 32767, -32768, -32767, ..., -1, 0, 1, ...)
1277 // Negative numbers will not be displayed.
1278 
1279 static int *cnt_kills;
1280 static int *cnt_items;
1281 static int *cnt_secret;
1282 static int *cnt_frags;
1283 static int    dofrags;
1284 static int    ng_state;
1285 
1286 // ====================================================================
1287 // CPhipps - WI_endNetgameStats
1288 // Purpose: Clean up coop game stats
1289 // Args:    none
1290 // Returns: void
1291 //
WI_endNetgameStats(void)1292 static void WI_endNetgameStats(void)
1293 {
1294   free(cnt_frags); cnt_frags = NULL;
1295   free(cnt_secret); cnt_secret = NULL;
1296   free(cnt_items); cnt_items = NULL;
1297   free(cnt_kills); cnt_kills = NULL;
1298 }
1299 
1300 // ====================================================================
1301 // WI_initNetgameStats
1302 // Purpose: Prepare for coop game stats
1303 // Args:    none
1304 // Returns: void
1305 //
WI_initNetgameStats(void)1306 void WI_initNetgameStats(void)
1307 {
1308   int i;
1309 
1310   state = StatCount;
1311   acceleratestage = 0;
1312   ng_state = 1;
1313 
1314   cnt_pause = TICRATE;
1315 
1316   // CPhipps - allocate these dynamically, blank with calloc
1317   cnt_kills = calloc(MAXPLAYERS, sizeof(*cnt_kills));
1318   cnt_items = calloc(MAXPLAYERS, sizeof(*cnt_items));
1319   cnt_secret= calloc(MAXPLAYERS, sizeof(*cnt_secret));
1320   cnt_frags = calloc(MAXPLAYERS, sizeof(*cnt_frags));
1321 
1322   for (i=0 ; i<MAXPLAYERS ; i++)
1323     if (playeringame[i])
1324       dofrags += WI_fragSum(i);
1325 
1326   dofrags = !!dofrags; // set to true or false - did we have frags?
1327 
1328   WI_initAnimatedBack();
1329 }
1330 
1331 
1332 // ====================================================================
1333 // WI_updateNetgameStats
1334 // Purpose: Calculate coop stats as we display them with noise and fury
1335 // Args:    none
1336 // Returns: void
1337 // Comment: This stuff sure is complicated for what it does
1338 //
WI_updateNetgameStats(void)1339 void WI_updateNetgameStats(void)
1340 {
1341   int   i;
1342   int   fsum;
1343 
1344   dboolean stillticking;
1345 
1346   WI_updateAnimatedBack();
1347 
1348   if (acceleratestage && ng_state != 10)
1349   {
1350     acceleratestage = 0;
1351 
1352     for (i=0 ; i<MAXPLAYERS ; i++)
1353     {
1354       if (!playeringame[i])
1355         continue;
1356 
1357       cnt_kills[i] = (plrs[i].skills * 100) / wbs->maxkills;
1358       cnt_items[i] = (plrs[i].sitems * 100) / wbs->maxitems;
1359 
1360       // killough 2/22/98: Make secrets = 100% if maxsecret = 0:
1361       cnt_secret[i] = wbs->maxsecret ?
1362                       (plrs[i].ssecret * 100) / wbs->maxsecret : 100;
1363       if (dofrags)
1364         cnt_frags[i] = WI_fragSum(i);  // we had frags
1365     }
1366     S_StartSound(0, sfx_barexp);  // bang
1367     ng_state = 10;
1368   }
1369 
1370   if (ng_state == 2)
1371   {
1372     if (!(bcnt&3))
1373       S_StartSound(0, sfx_pistol);  // pop
1374 
1375     stillticking = false;
1376 
1377     for (i=0 ; i<MAXPLAYERS ; i++)
1378     {
1379       if (!playeringame[i])
1380         continue;
1381 
1382       cnt_kills[i] += 2;
1383 
1384       if (cnt_kills[i] >= (plrs[i].skills * 100) / wbs->maxkills)
1385         cnt_kills[i] = (plrs[i].skills * 100) / wbs->maxkills;
1386       else
1387         stillticking = true; // still got stuff to tally
1388     }
1389 
1390     if (!stillticking)
1391     {
1392       S_StartSound(0, sfx_barexp);
1393       ng_state++;
1394     }
1395   }
1396   else if (ng_state == 4)
1397   {
1398     if (!(bcnt&3))
1399       S_StartSound(0, sfx_pistol);
1400 
1401     stillticking = false;
1402 
1403     for (i=0 ; i<MAXPLAYERS ; i++)
1404     {
1405       if (!playeringame[i])
1406         continue;
1407 
1408       cnt_items[i] += 2;
1409       if (cnt_items[i] >= (plrs[i].sitems * 100) / wbs->maxitems)
1410         cnt_items[i] = (plrs[i].sitems * 100) / wbs->maxitems;
1411       else
1412         stillticking = true;
1413     }
1414 
1415     if (!stillticking)
1416     {
1417       S_StartSound(0, sfx_barexp);
1418       ng_state++;
1419     }
1420   }
1421   else if (ng_state == 6)
1422   {
1423     if (!(bcnt&3))
1424       S_StartSound(0, sfx_pistol);
1425 
1426     stillticking = false;
1427 
1428     for (i=0 ; i<MAXPLAYERS ; i++)
1429     {
1430       if (!playeringame[i])
1431         continue;
1432 
1433       cnt_secret[i] += 2;
1434 
1435       // killough 2/22/98: Make secrets = 100% if maxsecret = 0:
1436 
1437       if (cnt_secret[i] >= (wbs->maxsecret ? (plrs[i].ssecret * 100) / wbs->maxsecret : compatibility_level < lxdoom_1_compatibility ? 0 : 100))
1438         cnt_secret[i] = wbs->maxsecret ? (plrs[i].ssecret * 100) / wbs->maxsecret : 100;
1439       else
1440         stillticking = true;
1441     }
1442 
1443     if (!stillticking)
1444     {
1445       S_StartSound(0, sfx_barexp);
1446       ng_state += 1 + 2*!dofrags;
1447     }
1448   }
1449   else if (ng_state == 8)
1450   {
1451     if (!(bcnt&3))
1452       S_StartSound(0, sfx_pistol);
1453 
1454     stillticking = false;
1455 
1456     for (i=0 ; i<MAXPLAYERS ; i++)
1457     {
1458       if (!playeringame[i])
1459         continue;
1460 
1461       cnt_frags[i] += 1;
1462 
1463       if (cnt_frags[i] >= (fsum = WI_fragSum(i)))
1464         cnt_frags[i] = fsum;
1465       else
1466         stillticking = true;
1467     }
1468 
1469     if (!stillticking)
1470     {
1471       S_StartSound(0, sfx_pldeth);
1472       ng_state++;
1473     }
1474   }
1475   else if (ng_state == 10)
1476   {
1477     if (acceleratestage)
1478     {
1479       S_StartSound(0, sfx_sgcock);
1480       if ( gamemode == commercial )
1481         WI_initNoState();
1482       else
1483         WI_initShowNextLoc();
1484     }
1485   }
1486   else if (ng_state & 1)
1487   {
1488     if (!--cnt_pause)
1489     {
1490       ng_state++;
1491       cnt_pause = TICRATE;
1492     }
1493   }
1494 }
1495 
1496 
1497 // ====================================================================
1498 // WI_drawNetgameStats
1499 // Purpose: Put the coop stats on the screen
1500 // Args:    none
1501 // Returns: void
1502 //
1503 // proff/nicolas 09/20/98 -- changed for hi-res
1504 // CPhipps - patch drawing updated
WI_drawNetgameStats(void)1505 void WI_drawNetgameStats(void)
1506 {
1507   int   i;
1508   int   x;
1509   int   y;
1510   int   pwidth = V_NamePatchWidth(percent);
1511   int   fwidth = V_NamePatchWidth(facebackp);
1512 
1513   WI_slamBackground();
1514 
1515   // draw animated background
1516   WI_drawAnimatedBack();
1517 
1518   WI_drawLF();
1519 
1520   // draw stat titles (top line)
1521   V_DrawNamePatch(NG_STATSX+NG_SPACINGX-V_NamePatchWidth(kills),
1522      NG_STATSY, FB, kills, CR_DEFAULT, VPT_STRETCH);
1523 
1524   V_DrawNamePatch(NG_STATSX+2*NG_SPACINGX-V_NamePatchWidth(items),
1525      NG_STATSY, FB, items, CR_DEFAULT, VPT_STRETCH);
1526 
1527   V_DrawNamePatch(NG_STATSX+3*NG_SPACINGX-V_NamePatchWidth(secret),
1528      NG_STATSY, FB, secret, CR_DEFAULT, VPT_STRETCH);
1529 
1530   if (dofrags)
1531     V_DrawNamePatch(NG_STATSX+4*NG_SPACINGX-V_NamePatchWidth(frags),
1532        NG_STATSY, FB, frags, CR_DEFAULT, VPT_STRETCH);
1533 
1534   // draw stats
1535   y = NG_STATSY + V_NamePatchHeight(kills);
1536 
1537   for (i=0 ; i<MAXPLAYERS ; i++)
1538   {
1539     //int trans = playernumtotrans[i];
1540     if (!playeringame[i])
1541       continue;
1542 
1543     x = NG_STATSX;
1544     V_DrawNamePatch(x-fwidth, y, FB, facebackp,
1545        i ? CR_LIMIT+i : CR_DEFAULT,
1546        VPT_STRETCH | (i ? VPT_TRANS : 0));
1547 
1548     if (i == me)
1549       V_DrawNamePatch(x-fwidth, y, FB, star, CR_DEFAULT, VPT_STRETCH);
1550 
1551     x += NG_SPACINGX;
1552     if (cnt_kills)
1553       WI_drawPercent(x-pwidth, y+10, cnt_kills[i]);
1554     x += NG_SPACINGX;
1555     if (cnt_items)
1556       WI_drawPercent(x-pwidth, y+10, cnt_items[i]);
1557     x += NG_SPACINGX;
1558     if (cnt_secret)
1559       WI_drawPercent(x-pwidth, y+10, cnt_secret[i]);
1560     x += NG_SPACINGX;
1561 
1562     if (dofrags && cnt_frags)
1563       WI_drawNum(x, y+10, cnt_frags[i], -1);
1564 
1565     y += WI_SPACINGY;
1566   }
1567 
1568   if (y <= SP_TIMEY)
1569     // cph - show times in coop on the entering screen
1570     WI_drawTimeStats(plrs[me].stime / TICRATE, wbs->totaltimes / TICRATE, wbs->partime / TICRATE);
1571 }
1572 
1573 static int  sp_state;
1574 
1575 // ====================================================================
1576 // WI_initStats
1577 // Purpose: Get ready for single player stats
1578 // Args:    none
1579 // Returns: void
1580 // Comment: Seems like we could do all these stats in a more generic
1581 //          set of routines that weren't duplicated for dm, coop, sp
1582 //
WI_initStats(void)1583 void WI_initStats(void)
1584 {
1585   state = StatCount;
1586   acceleratestage = 0;
1587   sp_state = 1;
1588 
1589   // CPhipps - allocate (awful code, I know, but saves changing it all) and initialise
1590   *(cnt_kills = malloc(sizeof(*cnt_kills))) =
1591   *(cnt_items = malloc(sizeof(*cnt_items))) =
1592   *(cnt_secret= malloc(sizeof(*cnt_secret))) = -1;
1593   cnt_time = cnt_par = cnt_total_time = -1;
1594   cnt_pause = TICRATE;
1595 
1596   WI_initAnimatedBack();
1597 }
1598 
1599 // ====================================================================
1600 // WI_updateStats
1601 // Purpose: Calculate solo stats
1602 // Args:    none
1603 // Returns: void
1604 //
WI_updateStats(void)1605 void WI_updateStats(void)
1606 {
1607   //e6y
1608   static dboolean play_early_explosion = true;
1609 
1610   WI_updateAnimatedBack();
1611 
1612   if (acceleratestage && sp_state != 10)
1613   {
1614     acceleratestage = 0;
1615     cnt_kills[0] = (plrs[me].skills * 100) / wbs->maxkills;
1616     cnt_items[0] = (plrs[me].sitems * 100) / wbs->maxitems;
1617 
1618     // killough 2/22/98: Make secrets = 100% if maxsecret = 0:
1619     cnt_secret[0] = (wbs->maxsecret ?
1620       (plrs[me].ssecret * 100) / wbs->maxsecret : 100);
1621 
1622     cnt_total_time = wbs->totaltimes / TICRATE;
1623     cnt_time = plrs[me].stime / TICRATE;
1624     cnt_par = wbs->partime / TICRATE;
1625     S_StartSound(0, sfx_barexp);
1626     sp_state = 10;
1627   }
1628 
1629   if (sp_state == 2)
1630   {
1631     cnt_kills[0] += 2;
1632 
1633     if (!(bcnt&3))
1634       S_StartSound(0, sfx_pistol);
1635 
1636     if (cnt_kills[0] >= (plrs[me].skills * 100) / wbs->maxkills)
1637     {
1638       cnt_kills[0] = (plrs[me].skills * 100) / wbs->maxkills;
1639       S_StartSound(0, sfx_barexp);
1640       sp_state++;
1641     }
1642   }
1643   else if (sp_state == 4)
1644   {
1645     cnt_items[0] += 2;
1646 
1647     if (!(bcnt&3))
1648       S_StartSound(0, sfx_pistol);
1649 
1650     if (cnt_items[0] >= (plrs[me].sitems * 100) / wbs->maxitems)
1651     {
1652       cnt_items[0] = (plrs[me].sitems * 100) / wbs->maxitems;
1653       S_StartSound(0, sfx_barexp);
1654       sp_state++;
1655     }
1656   }
1657   else if (sp_state == 6)
1658   {
1659     cnt_secret[0] += 2;
1660 
1661     if (!(bcnt&3))
1662       S_StartSound(0, sfx_pistol);
1663 
1664     // killough 2/22/98: Make secrets = 100% if maxsecret = 0:
1665     if ((!wbs->maxsecret && compatibility_level < lxdoom_1_compatibility) ||
1666 	cnt_secret[0] >= (wbs->maxsecret ?
1667       (plrs[me].ssecret * 100) / wbs->maxsecret : 100))
1668     {
1669       cnt_secret[0] = (wbs->maxsecret ?
1670         (plrs[me].ssecret * 100) / wbs->maxsecret : 100);
1671       S_StartSound(0, sfx_barexp);
1672       sp_state++;
1673     }
1674   }
1675   else if (sp_state == 8)
1676   {
1677     if (!(bcnt&3) && play_early_explosion) //e6y: do not play count sound after explosion sound
1678       S_StartSound(0, sfx_pistol);
1679 
1680     cnt_time += 3;
1681 
1682     if (cnt_time >= plrs[me].stime / TICRATE)
1683       cnt_time = plrs[me].stime / TICRATE;
1684 
1685     cnt_total_time += 3;
1686 
1687     if (cnt_total_time >= wbs->totaltimes / TICRATE)
1688       cnt_total_time = wbs->totaltimes / TICRATE;
1689 
1690     cnt_par += 3;
1691 
1692     // e6y
1693     // if par time is hidden (if modifiedgame is true)
1694     // the game should play explosion sound immediately after
1695     // the counter will reach level time instead of par time
1696     if (modifiedgame && play_early_explosion)
1697     {
1698       if ((cnt_time >= plrs[me].stime / TICRATE) && (compatibility_level < lxdoom_1_compatibility || cnt_total_time >= wbs->totaltimes / TICRATE))
1699       {
1700         // for ExM8 levels if the player won't have pressed <Use>
1701         if (compatibility_level < lxdoom_1_compatibility)
1702           cnt_total_time = wbs->totaltimes / TICRATE;
1703 
1704         S_StartSound(0, sfx_barexp);
1705         play_early_explosion = false; // do not play it twice or more
1706       }
1707     }
1708 
1709     if (cnt_par >= wbs->partime / TICRATE)
1710     {
1711       cnt_par = wbs->partime / TICRATE;
1712 
1713       if ((cnt_time >= plrs[me].stime / TICRATE) && (compatibility_level < lxdoom_1_compatibility || cnt_total_time >= wbs->totaltimes / TICRATE))
1714       {
1715         //e6y: for ExM8 levels
1716         if (compatibility_level < lxdoom_1_compatibility)
1717           cnt_total_time = wbs->totaltimes / TICRATE;
1718 
1719         if (!modifiedgame) //e6y: do not play explosion sound if it was already played
1720           S_StartSound(0, sfx_barexp);
1721         sp_state++;
1722       }
1723     }
1724   }
1725   else if (sp_state == 10)
1726   {
1727     if (acceleratestage)
1728     {
1729       S_StartSound(0, sfx_sgcock);
1730 
1731       if (gamemode == commercial)
1732         WI_initNoState();
1733       else
1734         WI_initShowNextLoc();
1735     }
1736   }
1737   else if (sp_state & 1)
1738   {
1739     play_early_explosion = true; //e6y
1740     if (!--cnt_pause)
1741     {
1742       sp_state++;
1743       cnt_pause = TICRATE;
1744     }
1745   }
1746 }
1747 
1748 // ====================================================================
1749 // WI_drawStats
1750 // Purpose: Put the solo stats on the screen
1751 // Args:    none
1752 // Returns: void
1753 //
1754 // proff/nicolas 09/20/98 -- changed for hi-res
1755 // CPhipps - patch drawing updated
WI_drawStats(void)1756 void WI_drawStats(void)
1757 {
1758   // line height
1759   int lh;
1760 
1761   lh = (3*num[0].height)/2;
1762 
1763   WI_slamBackground();
1764 
1765   // draw animated background
1766   WI_drawAnimatedBack();
1767 
1768   WI_drawLF();
1769 
1770   V_DrawNamePatch(SP_STATSX, SP_STATSY, FB, kills, CR_DEFAULT, VPT_STRETCH);
1771   if (cnt_kills)
1772     WI_drawPercent(320 - SP_STATSX, SP_STATSY, cnt_kills[0]);
1773 
1774   V_DrawNamePatch(SP_STATSX, SP_STATSY+lh, FB, items, CR_DEFAULT, VPT_STRETCH);
1775   if (cnt_items)
1776     WI_drawPercent(320 - SP_STATSX, SP_STATSY+lh, cnt_items[0]);
1777 
1778   V_DrawNamePatch(SP_STATSX, SP_STATSY+2*lh, FB, sp_secret, CR_DEFAULT, VPT_STRETCH);
1779   if (cnt_secret)
1780     WI_drawPercent(320 - SP_STATSX, SP_STATSY+2*lh, cnt_secret[0]);
1781 
1782   WI_drawTimeStats(cnt_time, cnt_total_time, cnt_par);
1783 }
1784 
1785 // ====================================================================
1786 // WI_checkForAccelerate
1787 // Purpose: See if the player has hit either the attack or use key
1788 //          or mouse button.  If so we set acceleratestage to 1 and
1789 //          all those display routines above jump right to the end.
1790 // Args:    none
1791 // Returns: void
1792 //
WI_checkForAccelerate(void)1793 void WI_checkForAccelerate(void)
1794 {
1795   int   i;
1796   player_t  *player;
1797 
1798   // check for button presses to skip delays
1799   for (i=0, player = players ; i<MAXPLAYERS ; i++, player++)
1800   {
1801     if (playeringame[i])
1802     {
1803       if (player->cmd.buttons & BT_ATTACK)
1804       {
1805         if (!player->attackdown)
1806           acceleratestage = 1;
1807         player->attackdown = true;
1808       }
1809       else
1810         player->attackdown = false;
1811 
1812       if (player->cmd.buttons & BT_USE)
1813       {
1814         if (!player->usedown)
1815           acceleratestage = 1;
1816         player->usedown = true;
1817       }
1818       else
1819         player->usedown = false;
1820     }
1821   }
1822 }
1823 
1824 // ====================================================================
1825 // WI_Ticker
1826 // Purpose: Do various updates every gametic, for stats, animation,
1827 //          checking that intermission music is running, etc.
1828 // Args:    none
1829 // Returns: void
1830 //
WI_Ticker(void)1831 void WI_Ticker(void)
1832 {
1833   // counter for general background animation
1834   bcnt++;
1835 
1836   if (bcnt == 1)
1837   {
1838     // intermission music
1839     if ( gamemode == commercial )
1840       S_ChangeMusic(mus_dm2int, true);
1841     else
1842       S_ChangeMusic(mus_inter, true);
1843   }
1844 
1845   WI_checkForAccelerate();
1846 
1847   switch (state)
1848   {
1849     case StatCount:
1850          if (deathmatch) WI_updateDeathmatchStats();
1851          else if (netgame) WI_updateNetgameStats();
1852          else WI_updateStats();
1853          break;
1854 
1855     case ShowNextLoc:
1856          WI_updateShowNextLoc();
1857          break;
1858 
1859     case NoState:
1860          WI_updateNoState();
1861          break;
1862   }
1863 }
1864 
1865 /* ====================================================================
1866  * WI_loadData
1867  * Purpose: Initialize intermission data such as background graphics,
1868  *          patches, map names, etc.
1869  * Args:    none
1870  * Returns: void
1871  *
1872  * CPhipps - modified for new wad lump handling.
1873  *         - no longer preload most graphics, other funcs can use
1874  *           them by name
1875  */
1876 
WI_loadData(void)1877 void WI_loadData(void)
1878 {
1879   int   i;
1880   int   j;
1881   char  name[9];  // limited to 8 characters
1882   anim_t* a;
1883 
1884   if (gamemode != commercial)
1885   {
1886     if (wbs->epsd < 3)
1887     {
1888       for (j=0;j<NUMANIMS[wbs->epsd];j++)
1889       {
1890         a = &anims[wbs->epsd][j];
1891         for (i=0;i<a->nanims;i++)
1892         {
1893           // MONDO HACK!
1894           if (wbs->epsd != 1 || j != 8)
1895           {
1896             // animations
1897             sprintf(name, "WIA%d%.2d%.2d", wbs->epsd, j, i);
1898             R_SetPatchNum(&a->p[i], name);
1899           }
1900           else
1901           {
1902             // HACK ALERT!
1903             a->p[i] = anims[1][4].p[i];
1904           }
1905         }
1906       }
1907     }
1908   }
1909 
1910   for (i=0;i<10;i++)
1911   {
1912     // numbers 0-9
1913     sprintf(name, "WINUM%d", i);
1914     R_SetPatchNum(&num[i], name);
1915   }
1916 }
1917 
1918 
1919 // ====================================================================
1920 // WI_Drawer
1921 // Purpose: Call the appropriate stats drawing routine depending on
1922 //          what kind of game is being played (DM, coop, solo)
1923 // Args:    none
1924 // Returns: void
1925 //
WI_Drawer(void)1926 void WI_Drawer (void)
1927 {
1928   switch (state)
1929   {
1930     case StatCount:
1931          if (deathmatch)
1932            WI_drawDeathmatchStats();
1933          else if (netgame)
1934            WI_drawNetgameStats();
1935          else
1936            WI_drawStats();
1937          break;
1938 
1939     case ShowNextLoc:
1940          WI_drawShowNextLoc();
1941          break;
1942 
1943     case NoState:
1944          WI_drawNoState();
1945          break;
1946   }
1947 }
1948 
1949 
1950 // ====================================================================
1951 // WI_initVariables
1952 // Purpose: Initialize the intermission information structure
1953 //          Note: wbstartstruct_t is defined in d_player.h
1954 // Args:    wbstartstruct -- pointer to the structure with the data
1955 // Returns: void
1956 //
WI_initVariables(wbstartstruct_t * wbstartstruct)1957 void WI_initVariables(wbstartstruct_t* wbstartstruct)
1958 {
1959 
1960   wbs = wbstartstruct;
1961 
1962 #ifdef RANGECHECKING
1963   if (gamemode != commercial)
1964   {
1965     if ( gamemode == retail )
1966       RNGCHECK(wbs->epsd, 0, 3);
1967     else
1968       RNGCHECK(wbs->epsd, 0, 2);
1969   }
1970   else
1971   {
1972     RNGCHECK(wbs->last, 0, 8);
1973     RNGCHECK(wbs->next, 0, 8);
1974   }
1975   RNGCHECK(wbs->pnum, 0, MAXPLAYERS);
1976   RNGCHECK(wbs->pnum, 0, MAXPLAYERS);
1977 #endif
1978 
1979   acceleratestage = 0;
1980   cnt = bcnt = 0;
1981   firstrefresh = 1;
1982   me = wbs->pnum;
1983   plrs = wbs->plyr;
1984 
1985   if (!wbs->maxkills)
1986     wbs->maxkills = 1;  // probably only useful in MAP30
1987 
1988   if (!wbs->maxitems)
1989     wbs->maxitems = 1;
1990 
1991   if ( gamemode != retail )
1992     if (wbs->epsd > 2)
1993       wbs->epsd -= 3;
1994 }
1995 
1996 // ====================================================================
1997 // WI_Start
1998 // Purpose: Call the various init routines
1999 //          Note: wbstartstruct_t is defined in d_player.h
2000 // Args:    wbstartstruct -- pointer to the structure with the
2001 //          intermission data
2002 // Returns: void
2003 //
WI_Start(wbstartstruct_t * wbstartstruct)2004 void WI_Start(wbstartstruct_t* wbstartstruct)
2005 {
2006   WI_initVariables(wbstartstruct);
2007   WI_loadData();
2008 
2009   if (deathmatch)
2010     WI_initDeathmatchStats();
2011   else if (netgame)
2012     WI_initNetgameStats();
2013   else
2014     WI_initStats();
2015 }
2016