1 // SONIC ROBO BLAST 2
2 //-----------------------------------------------------------------------------
3 // Copyright (C) 2004-2020 by Sonic Team Junior.
4 //
5 // This program is free software distributed under the
6 // terms of the GNU General Public License, version 2.
7 // See the 'LICENSE' file for more details.
8 //-----------------------------------------------------------------------------
9 /// \file y_inter.c
10 /// \brief Tally screens, or "Intermissions" as they were formally called in Doom
11
12 #include "doomdef.h"
13 #include "doomstat.h"
14 #include "d_main.h"
15 #include "f_finale.h"
16 #include "g_game.h"
17 #include "hu_stuff.h"
18 #include "i_net.h"
19 #include "i_video.h"
20 #include "p_tick.h"
21 #include "r_defs.h"
22 #include "r_skins.h"
23 #include "s_sound.h"
24 #include "st_stuff.h"
25 #include "v_video.h"
26 #include "w_wad.h"
27 #include "y_inter.h"
28 #include "z_zone.h"
29 #include "m_menu.h"
30 #include "m_misc.h"
31 #include "i_system.h"
32 #include "p_setup.h"
33
34 #include "r_local.h"
35 #include "p_local.h"
36
37 #include "m_cond.h" // condition sets
38 #include "lua_hook.h" // IntermissionThinker hook
39
40 #include "lua_hud.h"
41
42 #ifdef HWRENDER
43 #include "hardware/hw_main.h"
44 #endif
45
46 typedef struct
47 {
48 char patch[9];
49 INT32 points;
50 UINT8 display;
51 } y_bonus_t;
52
53 typedef union
54 {
55 struct
56 {
57 char passed1[21]; // KNUCKLES GOT / CRAWLA HONCHO
58 char passed2[16]; // THROUGH THE ACT / PASSED THE ACT
59 INT32 passedx1;
60 INT32 passedx2;
61
62 y_bonus_t bonuses[4];
63 patch_t *bonuspatches[4];
64
65 SINT8 gotperfbonus; // Used for visitation flags.
66
67 UINT32 score, total; // fake score, total
68 UINT32 tics; // time
69
70 UINT8 actnum; // act number being displayed
71 patch_t *ptotal; // TOTAL
72 UINT8 gotlife; // Number of extra lives obtained
73 } coop;
74
75 struct
76 {
77 char passed1[29]; // KNUCKLES GOT / CRAWLA HONCHO
78 char passed2[17]; // A CHAOS EMERALD? / GOT THEM ALL!
79 char passed3[15]; // CAN NOW BECOME
80 char passed4[SKINNAMESIZE+7]; // SUPER CRAWLA HONCHO
81 INT32 passedx1;
82 INT32 passedx2;
83 INT32 passedx3;
84 INT32 passedx4;
85
86 y_bonus_t bonuses[2];
87 patch_t *bonuspatches[2];
88
89 patch_t *pscore; // SCORE
90 UINT32 score; // fake score
91
92 // Continues
93 UINT8 continues;
94 patch_t *pcontinues;
95 INT32 *playerchar; // Continue HUD
96 UINT16 *playercolor;
97
98 UINT8 gotlife; // Number of extra lives obtained
99 } spec;
100
101 struct
102 {
103 UINT32 scores[MAXPLAYERS]; // Winner's score
104 UINT16 *color[MAXPLAYERS]; // Winner's color #
105 boolean spectator[MAXPLAYERS]; // Spectator list
106 INT32 *character[MAXPLAYERS]; // Winner's character #
107 INT32 num[MAXPLAYERS]; // Winner's player #
108 char *name[MAXPLAYERS]; // Winner's name
109 patch_t *result; // RESULT
110 patch_t *blueflag;
111 patch_t *redflag; // int_ctf uses this struct too.
112 INT32 numplayers; // Number of players being displayed
113 char levelstring[40]; // holds levelnames up to 32 characters
114 } match;
115
116 struct
117 {
118 UINT16 *color[MAXPLAYERS]; // Winner's color #
119 INT32 *character[MAXPLAYERS]; // Winner's character #
120 INT32 num[MAXPLAYERS]; // Winner's player #
121 char name[MAXPLAYERS][9]; // Winner's name
122 UINT32 times[MAXPLAYERS];
123 UINT32 rings[MAXPLAYERS];
124 UINT32 maxrings[MAXPLAYERS];
125 UINT32 monitors[MAXPLAYERS];
126 UINT32 scores[MAXPLAYERS];
127 UINT32 points[MAXPLAYERS];
128 INT32 numplayers; // Number of players being displayed
129 char levelstring[40]; // holds levelnames up to 32 characters
130 } competition;
131
132 } y_data;
133
134 static y_data data;
135
136 // graphics
137 static patch_t *bgpatch = NULL; // INTERSCR
138 static patch_t *bgtile = NULL; // SPECTILE/SRB2BACK
139 static patch_t *interpic = NULL; // custom picture defined in map header
140 static boolean usetile;
141 static INT32 timer;
142
143 typedef struct
144 {
145 INT32 source_width, source_height;
146 INT32 source_bpp, source_rowbytes;
147 UINT8 *source_picture;
148 INT32 target_width, target_height;
149 INT32 target_bpp, target_rowbytes;
150 UINT8 *target_picture;
151 } y_buffer_t;
152
153 boolean usebuffer = false;
154 static boolean useinterpic;
155 static y_buffer_t *y_buffer;
156
157 static INT32 intertic;
158 static INT32 tallydonetic = -1;
159 static INT32 endtic = -1;
160
161 intertype_t intertype = int_none;
162 intertype_t intermissiontypes[NUMGAMETYPES];
163
164 static void Y_RescaleScreenBuffer(void);
165 static void Y_AwardCoopBonuses(void);
166 static void Y_AwardSpecialStageBonus(void);
167 static void Y_CalculateCompetitionWinners(void);
168 static void Y_CalculateTimeRaceWinners(void);
169 static void Y_CalculateMatchWinners(void);
170 static void Y_UnloadData(void);
171
172 // Stuff copy+pasted from st_stuff.c
173 #define ST_DrawNumFromHud(h,n) V_DrawTallNum(hudinfo[h].x, hudinfo[h].y, hudinfo[h].f, n)
174 #define ST_DrawPadNumFromHud(h,n,q) V_DrawPaddedTallNum(hudinfo[h].x, hudinfo[h].y, hudinfo[h].f, n, q)
175 #define ST_DrawPatchFromHud(h,p) V_DrawScaledPatch(hudinfo[h].x, hudinfo[h].y, hudinfo[h].f, p)
176
Y_IntermissionTokenDrawer(void)177 static void Y_IntermissionTokenDrawer(void)
178 {
179 INT32 y, offs, lowy, calc;
180 UINT32 tokencount;
181 INT16 temp;
182 UINT8 em;
183
184 offs = 0;
185 lowy = BASEVIDHEIGHT - 32 - 8;
186 temp = tokenicon->height / 2;
187
188 em = 0;
189 while (emeralds & (1 << em))
190 if (++em == 7)
191 return;
192
193 if (tallydonetic != -1)
194 {
195 offs = (intertic - tallydonetic)*2;
196 if (offs > 10)
197 offs = 8;
198 }
199
200 V_DrawSmallScaledPatch(32, lowy-1, 0, emeraldpics[2][em]); // coinbox
201
202 y = (lowy + offs + 1) - (temp + (token + 1)*8);
203
204 for (tokencount = token; tokencount; tokencount--)
205 {
206 if (y >= -temp)
207 V_DrawSmallScaledPatch(32, y, 0, tokenicon);
208 y += 8;
209 }
210
211 y += (offs*(temp - 1)/8);
212 calc = (lowy - y)*2;
213
214 if (calc > 0)
215 V_DrawCroppedPatch(32<<FRACBITS, y<<FRACBITS, FRACUNIT/2, 0, tokenicon, 0, 0, tokenicon->width, calc);
216 }
217
218
219 //
220 // Y_LoadIntermissionData
221 //
222 // Load patches for drawing the intermission, if acceptable
223 //
Y_LoadIntermissionData(void)224 void Y_LoadIntermissionData(void)
225 {
226 INT32 i;
227
228 if (dedicated)
229 return;
230
231 switch (intertype)
232 {
233 case int_coop:
234 {
235 for (i = 0; i < 4; ++i)
236 {
237 if (strlen(data.coop.bonuses[i].patch))
238 data.coop.bonuspatches[i] = W_CachePatchName(data.coop.bonuses[i].patch, PU_PATCH);
239 }
240 data.coop.ptotal = W_CachePatchName("YB_TOTAL", PU_PATCH);
241
242 // get background patches
243 bgpatch = W_CachePatchName("INTERSCR", PU_PATCH);
244
245 // grab an interscreen if appropriate
246 if (mapheaderinfo[gamemap-1]->interscreen[0] != '#')
247 interpic = W_CachePatchName(mapheaderinfo[gamemap-1]->interscreen, PU_PATCH);
248 break;
249 }
250 case int_spec:
251 {
252 for (i = 0; i < 2; ++i)
253 data.spec.bonuspatches[i] = W_CachePatchName(data.spec.bonuses[i].patch, PU_PATCH);
254
255 data.spec.pscore = W_CachePatchName("YB_SCORE", PU_PATCH);
256 data.spec.pcontinues = W_CachePatchName("YB_CONTI", PU_PATCH);
257
258 // get background tile
259 bgtile = W_CachePatchName("SPECTILE", PU_PATCH);
260
261 // grab an interscreen if appropriate
262 if (mapheaderinfo[gamemap-1]->interscreen[0] != '#')
263 interpic = W_CachePatchName(mapheaderinfo[gamemap-1]->interscreen, PU_PATCH);
264 break;
265 }
266 case int_ctf:
267 case int_teammatch:
268 {
269 if (!rflagico) //prevent a crash if we haven't cached our team graphics yet
270 {
271 rflagico = W_CachePatchName("RFLAGICO", PU_HUDGFX);
272 bflagico = W_CachePatchName("BFLAGICO", PU_HUDGFX);
273 rmatcico = W_CachePatchName("RMATCICO", PU_HUDGFX);
274 bmatcico = W_CachePatchName("BMATCICO", PU_HUDGFX);
275 }
276
277 data.match.redflag = (intertype == int_ctf) ? rflagico : rmatcico;
278 data.match.blueflag = (intertype == int_ctf) ? bflagico : bmatcico;
279 }
280 /* FALLTHRU */
281 case int_match:
282 case int_race:
283 case int_comp:
284 {
285 if (intertype == int_match || intertype == int_race)
286 {
287 // get RESULT header
288 data.match.result = W_CachePatchName("RESULT", PU_PATCH);
289 }
290
291 // get background tile
292 bgtile = W_CachePatchName("SRB2BACK", PU_PATCH);
293 break;
294 }
295 case int_none:
296 default:
297 break;
298 }
299 }
300
301 //
302 // Y_ConsiderScreenBuffer
303 //
304 // Can we copy the current screen to a buffer?
305 //
Y_ConsiderScreenBuffer(void)306 void Y_ConsiderScreenBuffer(void)
307 {
308 if (gameaction != ga_completed)
309 return;
310
311 if (y_buffer == NULL)
312 y_buffer = Z_Calloc(sizeof(y_buffer_t), PU_STATIC, NULL);
313 else
314 return;
315
316 y_buffer->source_width = vid.width;
317 y_buffer->source_height = vid.height;
318 y_buffer->source_bpp = vid.bpp;
319 y_buffer->source_rowbytes = vid.rowbytes;
320 y_buffer->source_picture = ZZ_Alloc(y_buffer->source_width*vid.bpp * y_buffer->source_height);
321 VID_BlitLinearScreen(screens[1], y_buffer->source_picture, vid.width*vid.bpp, vid.height, vid.width*vid.bpp, vid.rowbytes);
322
323 // Make the rescaled screen buffer
324 Y_RescaleScreenBuffer();
325 }
326
327 //
328 // Y_RescaleScreenBuffer
329 //
330 // Write the rescaled source picture, to the destination picture that has the current screen's resolutions.
331 //
Y_RescaleScreenBuffer(void)332 static void Y_RescaleScreenBuffer(void)
333 {
334 INT32 sx, sy; // source
335 INT32 dx, dy; // dest
336 fixed_t scalefac, yscalefac;
337 fixed_t rowfrac, colfrac;
338 UINT8 *dest;
339
340 // Who knows?
341 if (y_buffer == NULL)
342 return;
343
344 if (y_buffer->target_picture)
345 Z_Free(y_buffer->target_picture);
346
347 y_buffer->target_width = vid.width;
348 y_buffer->target_height = vid.height;
349 y_buffer->target_rowbytes = vid.rowbytes;
350 y_buffer->target_bpp = vid.bpp;
351 y_buffer->target_picture = ZZ_Alloc(y_buffer->target_width*vid.bpp * y_buffer->target_height);
352 dest = y_buffer->target_picture;
353
354 scalefac = FixedDiv(y_buffer->target_width*FRACUNIT, y_buffer->source_width*FRACUNIT);
355 yscalefac = FixedDiv(y_buffer->target_height*FRACUNIT, y_buffer->source_height*FRACUNIT);
356
357 rowfrac = FixedDiv(FRACUNIT, yscalefac);
358 colfrac = FixedDiv(FRACUNIT, scalefac);
359
360 for (sy = 0, dy = 0; sy < (y_buffer->source_height << FRACBITS) && dy < y_buffer->target_height; sy += rowfrac, dy++)
361 for (sx = 0, dx = 0; sx < (y_buffer->source_width << FRACBITS) && dx < y_buffer->target_width; sx += colfrac, dx += y_buffer->target_bpp)
362 dest[(dy * y_buffer->target_rowbytes) + dx] = y_buffer->source_picture[((sy>>FRACBITS) * y_buffer->source_width) + (sx>>FRACBITS)];
363 }
364
365 //
366 // Y_CleanupScreenBuffer
367 //
368 // Free all related memory.
369 //
Y_CleanupScreenBuffer(void)370 void Y_CleanupScreenBuffer(void)
371 {
372 // Who knows?
373 if (y_buffer == NULL)
374 return;
375
376 if (y_buffer->target_picture)
377 Z_Free(y_buffer->target_picture);
378
379 if (y_buffer->source_picture)
380 Z_Free(y_buffer->source_picture);
381
382 Z_Free(y_buffer);
383 y_buffer = NULL;
384 }
385
386 //
387 // Y_IntermissionDrawer
388 //
389 // Called by D_Display. Nothing is modified here; all it does is draw.
390 // Neat concept, huh?
391 //
Y_IntermissionDrawer(void)392 void Y_IntermissionDrawer(void)
393 {
394 // Bonus loops
395 INT32 i;
396
397 if (intertype == int_none || rendermode == render_none)
398 return;
399
400 if (useinterpic)
401 V_DrawScaledPatch(0, 0, 0, interpic);
402 else if (!usetile)
403 {
404 if (rendermode == render_soft && usebuffer)
405 {
406 // no y_buffer
407 if (y_buffer == NULL)
408 VID_BlitLinearScreen(screens[1], screens[0], vid.width*vid.bpp, vid.height, vid.width*vid.bpp, vid.rowbytes);
409 else
410 {
411 // Maybe the resolution changed?
412 if ((y_buffer->target_width != vid.width) || (y_buffer->target_height != vid.height))
413 Y_RescaleScreenBuffer();
414
415 // Blit the already-scaled screen buffer to the current screen
416 VID_BlitLinearScreen(y_buffer->target_picture, screens[0], vid.width*vid.bpp, vid.height, vid.width*vid.bpp, vid.rowbytes);
417 }
418 }
419 #ifdef HWRENDER
420 else if (rendermode != render_soft && usebuffer)
421 HWR_DrawIntermissionBG();
422 #endif
423 else if (bgpatch)
424 {
425 fixed_t hs = vid.width * FRACUNIT / BASEVIDWIDTH;
426 fixed_t vs = vid.height * FRACUNIT / BASEVIDHEIGHT;
427 V_DrawStretchyFixedPatch(0, 0, hs, vs, V_NOSCALEPATCH, bgpatch, NULL);
428 }
429 }
430 else if (bgtile)
431 V_DrawPatchFill(bgtile);
432
433 LUAh_IntermissionHUD();
434 if (!LUA_HudEnabled(hud_intermissiontally))
435 goto skiptallydrawer;
436
437 if (intertype == int_coop)
438 {
439 INT32 bonusy;
440
441 if (gottoken) // first to be behind everything else
442 Y_IntermissionTokenDrawer();
443
444 if (!splitscreen) // there's not enough room in splitscreen, don't even bother trying!
445 {
446 // draw score
447 ST_DrawPatchFromHud(HUD_SCORE, sboscore);
448 ST_DrawNumFromHud(HUD_SCORENUM, data.coop.score);
449
450 // draw time
451 ST_DrawPatchFromHud(HUD_TIME, sbotime);
452 if (cv_timetic.value == 3)
453 ST_DrawNumFromHud(HUD_SECONDS, data.coop.tics);
454 else
455 {
456 INT32 seconds, minutes, tictrn;
457
458 seconds = G_TicsToSeconds(data.coop.tics);
459 minutes = G_TicsToMinutes(data.coop.tics, true);
460 tictrn = G_TicsToCentiseconds(data.coop.tics);
461
462 ST_DrawNumFromHud(HUD_MINUTES, minutes); // Minutes
463 ST_DrawPatchFromHud(HUD_TIMECOLON, sbocolon); // Colon
464 ST_DrawPadNumFromHud(HUD_SECONDS, seconds, 2); // Seconds
465
466 if (cv_timetic.value == 1 || cv_timetic.value == 2 || modeattacking || marathonmode)
467 {
468 ST_DrawPatchFromHud(HUD_TIMETICCOLON, sboperiod); // Period
469 ST_DrawPadNumFromHud(HUD_TICS, tictrn, 2); // Tics
470 }
471 }
472 }
473
474 // draw the "got through act" lines and act number
475 V_DrawLevelTitle(data.coop.passedx1, 49, 0, data.coop.passed1);
476 {
477 INT32 h = V_LevelNameHeight(data.coop.passed2);
478 V_DrawLevelTitle(data.coop.passedx2, 49+h+2, 0, data.coop.passed2);
479
480 if (data.coop.actnum)
481 V_DrawLevelActNum(244, 42+h, 0, data.coop.actnum);
482 }
483
484 bonusy = 150;
485 // Total
486 V_DrawScaledPatch(152, bonusy, 0, data.coop.ptotal);
487 V_DrawTallNum(BASEVIDWIDTH - 68, bonusy + 1, 0, data.coop.total);
488 bonusy -= (3*(tallnum[0]->height)/2) + 1;
489
490 // Draw bonuses
491 for (i = 3; i >= 0; --i)
492 {
493 if (data.coop.bonuses[i].display)
494 {
495 V_DrawScaledPatch(152, bonusy, 0, data.coop.bonuspatches[i]);
496 V_DrawTallNum(BASEVIDWIDTH - 68, bonusy + 1, 0, data.coop.bonuses[i].points);
497 }
498 bonusy -= (3*(tallnum[0]->height)/2) + 1;
499 }
500 }
501 else if (intertype == int_spec)
502 {
503 static tic_t animatetic = 0;
504 INT32 ttheight = 16;
505 INT32 xoffset1 = 0; // Line 1 x offset
506 INT32 xoffset2 = 0; // Line 2 x offset
507 INT32 xoffset3 = 0; // Line 3 x offset
508 INT32 xoffset4 = 0; // Line 4 x offset
509 INT32 xoffset5 = 0; // Line 5 x offset
510 INT32 xoffset6 = 0; // Line 6 x offset
511 UINT8 drawsection = 0;
512
513 if (gottoken) // first to be behind everything else
514 Y_IntermissionTokenDrawer();
515
516 // draw the header
517 if (intertic <= 2*TICRATE)
518 animatetic = 0;
519 else if (!animatetic && data.spec.bonuses[0].points == 0 && data.spec.bonuses[1].points == 0 && data.spec.passed3[0] != '\0')
520 animatetic = intertic + TICRATE;
521
522 if (animatetic && (tic_t)intertic >= animatetic)
523 {
524 const INT32 scradjust = (vid.width/vid.dupx)>>3; // 40 for BASEVIDWIDTH
525 INT32 animatetimer = (intertic - animatetic);
526 if (animatetimer <= 16)
527 {
528 xoffset1 = -(animatetimer * scradjust);
529 xoffset2 = -((animatetimer- 2) * scradjust);
530 xoffset3 = -((animatetimer- 4) * scradjust);
531 xoffset4 = -((animatetimer- 6) * scradjust);
532 xoffset5 = -((animatetimer- 8) * scradjust);
533 xoffset6 = -((animatetimer-10) * scradjust);
534 if (xoffset2 > 0) xoffset2 = 0;
535 if (xoffset3 > 0) xoffset3 = 0;
536 if (xoffset4 > 0) xoffset4 = 0;
537 if (xoffset5 > 0) xoffset5 = 0;
538 if (xoffset6 > 0) xoffset6 = 0;
539 }
540 else if (animatetimer < 34)
541 {
542 drawsection = 1;
543 xoffset1 = (24-animatetimer) * scradjust;
544 xoffset2 = (26-animatetimer) * scradjust;
545 xoffset3 = (28-animatetimer) * scradjust;
546 xoffset4 = (30-animatetimer) * scradjust;
547 xoffset5 = (32-animatetimer) * scradjust;
548 xoffset6 = (34-animatetimer) * scradjust;
549 if (xoffset1 < 0) xoffset1 = 0;
550 if (xoffset2 < 0) xoffset2 = 0;
551 if (xoffset3 < 0) xoffset3 = 0;
552 if (xoffset4 < 0) xoffset4 = 0;
553 if (xoffset5 < 0) xoffset5 = 0;
554 }
555 else
556 {
557 drawsection = 1;
558 if (animatetimer == 32)
559 S_StartSound(NULL, sfx_s3k68);
560 }
561 }
562
563 if (drawsection == 1)
564 {
565 const char *ringtext = "\x82" "50 rings, no shield";
566 const char *tut1text = "\x82" "press " "\x80" "spin";
567 const char *tut2text = "\x82" "mid-" "\x80" "jump";
568 ttheight = 8;
569 V_DrawLevelTitle(data.spec.passedx1 + xoffset1, ttheight, 0, data.spec.passed1);
570 ttheight += V_LevelNameHeight(data.spec.passed3) + 2;
571 V_DrawLevelTitle(data.spec.passedx3 + xoffset2, ttheight, 0, data.spec.passed3);
572 ttheight += V_LevelNameHeight(data.spec.passed4) + 2;
573 V_DrawLevelTitle(data.spec.passedx4 + xoffset3, ttheight, 0, data.spec.passed4);
574
575 ttheight = 108;
576 V_DrawLevelTitle(BASEVIDWIDTH/2 + xoffset4 - (V_LevelNameWidth(ringtext)/2), ttheight, 0, ringtext);
577 ttheight += V_LevelNameHeight(tut1text) + 2;
578 V_DrawLevelTitle(BASEVIDWIDTH/2 + xoffset5 - (V_LevelNameWidth(tut1text)/2), ttheight, 0, tut1text);
579 ttheight += V_LevelNameHeight(tut2text) + 2;
580 V_DrawLevelTitle(BASEVIDWIDTH/2 + xoffset6 - (V_LevelNameWidth(tut2text)/2), ttheight, 0, tut2text);
581 }
582 else
583 {
584 INT32 yoffset = 0;
585 if (data.spec.passed1[0] != '\0')
586 {
587 ttheight = 24;
588 V_DrawLevelTitle(data.spec.passedx1 + xoffset1, ttheight, 0, data.spec.passed1);
589 ttheight += V_LevelNameHeight(data.spec.passed2) + 2;
590 V_DrawLevelTitle(data.spec.passedx2 + xoffset2, ttheight, 0, data.spec.passed2);
591 }
592 else
593 {
594 ttheight = 24 + (V_LevelNameHeight(data.spec.passed2)/2) + 2;
595 V_DrawLevelTitle(data.spec.passedx2 + xoffset1, ttheight, 0, data.spec.passed2);
596 }
597
598 V_DrawScaledPatch(152 + xoffset3, 108, 0, data.spec.bonuspatches[0]);
599 V_DrawTallNum(BASEVIDWIDTH + xoffset3 - 68, 109, 0, data.spec.bonuses[0].points);
600 if (data.spec.bonuses[1].display)
601 {
602 V_DrawScaledPatch(152 + xoffset4, 124, 0, data.spec.bonuspatches[1]);
603 V_DrawTallNum(BASEVIDWIDTH + xoffset4 - 68, 125, 0, data.spec.bonuses[1].points);
604 yoffset = 16;
605 // hack; pass the buck along...
606 xoffset4 = xoffset5;
607 xoffset5 = xoffset6;
608 }
609 V_DrawScaledPatch(152 + xoffset4, 124+yoffset, 0, data.spec.pscore);
610 V_DrawTallNum(BASEVIDWIDTH + xoffset4 - 68, 125+yoffset, 0, data.spec.score);
611
612 // Draw continues!
613 if (continuesInSession /* && (data.spec.continues & 0x80) */) // Always draw when continues are a thing
614 {
615 UINT8 continues = data.spec.continues & 0x7F;
616
617 V_DrawScaledPatch(152 + xoffset5, 150+yoffset, 0, data.spec.pcontinues);
618 if (continues > 5)
619 {
620 INT32 leftx = (continues >= 10) ? 216 : 224;
621 V_DrawContinueIcon(leftx + xoffset5, 162+yoffset, 0, *data.spec.playerchar, *data.spec.playercolor);
622 V_DrawScaledPatch(leftx + xoffset5 + 12, 160+yoffset, 0, stlivex);
623 if (!((data.spec.continues & 0x80) && !(endtic < 0 || intertic%20 < 10)))
624 V_DrawRightAlignedString(252 + xoffset5, 158+yoffset, 0,
625 va("%d",(((data.spec.continues & 0x80) && (endtic < 0)) ? continues-1 : continues)));
626 }
627 else
628 {
629 for (i = 0; i < continues; ++i)
630 {
631 if ((data.spec.continues & 0x80) && i == continues-1 && (endtic < 0 || intertic%20 < 10))
632 break;
633 V_DrawContinueIcon(246 + xoffset5 - (i*20), 162+yoffset, 0, *data.spec.playerchar, *data.spec.playercolor);
634 }
635 }
636 }
637 }
638
639 // draw the emeralds
640 //if (intertic & 1)
641 {
642 boolean drawthistic = !(ALL7EMERALDS(emeralds) && (intertic & 1));
643 INT32 emeraldx = 152 - 3*28;
644 INT32 em = P_GetNextEmerald();
645
646 if (em == 7)
647 {
648 if (!stagefailed)
649 {
650 fixed_t adjust = 2*(FINESINE(FixedAngle((intertic + 1)<<(FRACBITS-4)) & FINEMASK));
651 V_DrawFixedPatch(152<<FRACBITS, (74<<FRACBITS) - adjust, FRACUNIT, 0, emeraldpics[0][em], NULL);
652 }
653 }
654 else if (em < 7)
655 {
656 static UINT8 emeraldbounces = 0;
657 static INT32 emeraldmomy = 20;
658 static INT32 emeraldy = -40;
659
660 if (drawthistic)
661 for (i = 0; i < 7; ++i)
662 {
663 if ((i != em) && (emeralds & (1 << i)))
664 V_DrawScaledPatch(emeraldx, 74, 0, emeraldpics[0][i]);
665 emeraldx += 28;
666 }
667
668 emeraldx = 152 + (em-3)*28;
669
670 if (intertic <= 1)
671 {
672 emeraldbounces = 0;
673 emeraldmomy = 20;
674 emeraldy = -40;
675 }
676 else
677 {
678 if (!stagefailed)
679 {
680 if (emeraldbounces < 3)
681 {
682 emeraldy += (++emeraldmomy);
683 if (emeraldy > 74)
684 {
685 S_StartSound(NULL, sfx_tink); // tink
686 emeraldbounces++;
687 emeraldmomy = -(emeraldmomy/2);
688 emeraldy = 74;
689 }
690 }
691 }
692 else
693 {
694 if (emeraldy < (vid.height/vid.dupy)+16)
695 {
696 emeraldy += (++emeraldmomy);
697 emeraldx += intertic - 6;
698 }
699 if (emeraldbounces < 1 && emeraldy > 74)
700 {
701 S_StartSound(NULL, sfx_shldls); // nope
702 emeraldbounces++;
703 emeraldmomy = -(emeraldmomy/2);
704 emeraldy = 74;
705 }
706 }
707 if (drawthistic)
708 V_DrawScaledPatch(emeraldx, emeraldy, 0, emeraldpics[0][em]);
709 }
710 }
711 }
712 }
713 else if (intertype == int_match || intertype == int_race)
714 {
715 INT32 j = 0;
716 INT32 x = 4;
717 INT32 y = 48;
718 char name[MAXPLAYERNAME+1];
719 char strtime[10];
720
721 // draw the header
722 V_DrawScaledPatch(112, 2, 0, data.match.result);
723
724 // draw the level name
725 V_DrawCenteredString(BASEVIDWIDTH/2, 20, 0, data.match.levelstring);
726 V_DrawFill(4, 42, 312, 1, 0);
727
728 if (data.match.numplayers > 9)
729 {
730 V_DrawFill(160, 32, 1, 152, 0);
731
732 if (intertype == int_race)
733 V_DrawRightAlignedString(x+152, 32, V_YELLOWMAP, "TIME");
734 else
735 V_DrawRightAlignedString(x+152, 32, V_YELLOWMAP, "SCORE");
736
737 V_DrawCenteredString(x+(BASEVIDWIDTH/2)+6, 32, V_YELLOWMAP, "#");
738 V_DrawString(x+(BASEVIDWIDTH/2)+36, 32, V_YELLOWMAP, "NAME");
739 }
740
741 V_DrawCenteredString(x+6, 32, V_YELLOWMAP, "#");
742 V_DrawString(x+36, 32, V_YELLOWMAP, "NAME");
743
744 if (intertype == int_race)
745 V_DrawRightAlignedString(x+(BASEVIDWIDTH/2)+152, 32, V_YELLOWMAP, "TIME");
746 else
747 V_DrawRightAlignedString(x+(BASEVIDWIDTH/2)+152, 32, V_YELLOWMAP, "SCORE");
748
749 for (i = 0; i < data.match.numplayers; i++)
750 {
751 if (data.match.spectator[i])
752 continue; //Ignore spectators.
753
754 V_DrawCenteredString(x+6, y, 0, va("%d", j+1));
755 j++; //We skip spectators, but not their number.
756
757 if (playeringame[data.match.num[i]])
758 {
759 // Draw the back sprite, it looks ugly if we don't
760 V_DrawSmallScaledPatch(x+16, y-4, 0, livesback);
761
762 if (data.match.color[i] == 0)
763 V_DrawSmallScaledPatch(x+16, y-4, 0,faceprefix[*data.match.character[i]]);
764 else
765 {
766 UINT8 *colormap = R_GetTranslationColormap(*data.match.character[i], *data.match.color[i], GTC_CACHE);
767 V_DrawSmallMappedPatch(x+16, y-4, 0,faceprefix[*data.match.character[i]], colormap);
768 }
769
770 if (data.match.numplayers > 9)
771 {
772 if (intertype == int_race)
773 strlcpy(name, data.match.name[i], 8);
774 else
775 strlcpy(name, data.match.name[i], 9);
776 }
777 else
778 STRBUFCPY(name, data.match.name[i]);
779
780 V_DrawString(x+36, y, V_ALLOWLOWERCASE, name);
781
782 if (data.match.numplayers > 9)
783 {
784 if (intertype == int_match)
785 V_DrawRightAlignedString(x+152, y, 0, va("%i", data.match.scores[i]));
786 else if (intertype == int_race)
787 {
788 if (players[data.match.num[i]].pflags & PF_GAMETYPEOVER)
789 snprintf(strtime, sizeof strtime, "DNF");
790 else
791 snprintf(strtime, sizeof strtime,
792 "%i:%02i.%02i",
793 G_TicsToMinutes(data.match.scores[i], true),
794 G_TicsToSeconds(data.match.scores[i]), G_TicsToCentiseconds(data.match.scores[i]));
795
796 strtime[sizeof strtime - 1] = '\0';
797 V_DrawRightAlignedString(x+152, y, 0, strtime);
798 }
799 }
800 else
801 {
802 if (intertype == int_match)
803 V_DrawRightAlignedString(x+152+BASEVIDWIDTH/2, y, 0, va("%u", data.match.scores[i]));
804 else if (intertype == int_race)
805 {
806 if (players[data.match.num[i]].pflags & PF_GAMETYPEOVER)
807 snprintf(strtime, sizeof strtime, "DNF");
808 else
809 snprintf(strtime, sizeof strtime, "%i:%02i.%02i", G_TicsToMinutes(data.match.scores[i], true),
810 G_TicsToSeconds(data.match.scores[i]), G_TicsToCentiseconds(data.match.scores[i]));
811
812 strtime[sizeof strtime - 1] = '\0';
813
814 V_DrawRightAlignedString(x+152+BASEVIDWIDTH/2, y, 0, strtime);
815 }
816 }
817 }
818
819 y += 16;
820
821 if (y > 176)
822 {
823 y = 48;
824 x += BASEVIDWIDTH/2;
825 }
826 }
827 }
828 else if (intertype == int_ctf || intertype == int_teammatch)
829 {
830 INT32 x = 4, y = 0;
831 INT32 redplayers = 0, blueplayers = 0;
832 char name[MAXPLAYERNAME+1];
833
834 // Show the team flags and the team score at the top instead of "RESULTS"
835 V_DrawSmallScaledPatch(128 - (data.match.blueflag->width / 4), 2, 0, data.match.blueflag);
836 V_DrawCenteredString(128, 16, 0, va("%u", bluescore));
837
838 V_DrawSmallScaledPatch(192 - (data.match.redflag->width / 4), 2, 0, data.match.redflag);
839 V_DrawCenteredString(192, 16, 0, va("%u", redscore));
840
841 // draw the level name
842 V_DrawCenteredString(BASEVIDWIDTH/2, 24, 0, data.match.levelstring);
843 V_DrawFill(4, 42, 312, 1, 0);
844
845 //vert. line
846 V_DrawFill(160, 32, 1, 152, 0);
847
848 //strings at the top of the list
849 V_DrawCenteredString(x+6, 32, V_YELLOWMAP, "#");
850 V_DrawCenteredString(x+(BASEVIDWIDTH/2)+6, 32, V_YELLOWMAP, "#");
851
852 V_DrawString(x+36, 32, V_YELLOWMAP, "NAME");
853 V_DrawString(x+(BASEVIDWIDTH/2)+36, 32, V_YELLOWMAP, "NAME");
854
855 V_DrawRightAlignedString(x+152, 32, V_YELLOWMAP, "SCORE");
856 V_DrawRightAlignedString(x+(BASEVIDWIDTH/2)+152, 32, V_YELLOWMAP, "SCORE");
857
858 for (i = 0; i < data.match.numplayers; i++)
859 {
860 if (playeringame[data.match.num[i]] && !(data.match.spectator[i]))
861 {
862 UINT8 *colormap = R_GetTranslationColormap(*data.match.character[i], *data.match.color[i], GTC_CACHE);
863
864 if (*data.match.color[i] == SKINCOLOR_RED) //red
865 {
866 if (redplayers++ > 9)
867 continue;
868 x = 4 + (BASEVIDWIDTH/2);
869 y = (redplayers * 16) + 32;
870 V_DrawCenteredString(x+6, y, 0, va("%d", redplayers));
871 }
872 else if (*data.match.color[i] == SKINCOLOR_BLUE) //blue
873 {
874 if (blueplayers++ > 9)
875 continue;
876 x = 4;
877 y = (blueplayers * 16) + 32;
878 V_DrawCenteredString(x+6, y, 0, va("%d", blueplayers));
879 }
880 else
881 continue;
882
883 // Draw the back sprite, it looks ugly if we don't
884 V_DrawSmallScaledPatch(x+16, y-4, 0, livesback);
885
886 //color is ALWAYS going to be 6/7 here, no need to check if it's nonzero.
887 V_DrawSmallMappedPatch(x+16, y-4, 0,faceprefix[*data.match.character[i]], colormap);
888
889 strlcpy(name, data.match.name[i], 9);
890
891 V_DrawString(x+36, y, V_ALLOWLOWERCASE, name);
892
893 V_DrawRightAlignedString(x+152, y, 0, va("%u", data.match.scores[i]));
894 }
895 }
896 }
897 else if (intertype == int_comp)
898 {
899 INT32 x = 4;
900 INT32 y = 48;
901 UINT32 ptime, pring, pmaxring, pmonitor, pscore;
902 char sstrtime[10];
903
904 // draw the level name
905 V_DrawCenteredString(BASEVIDWIDTH/2, 8, 0, data.competition.levelstring);
906 V_DrawFill(4, 42, 312, 1, 0);
907
908 V_DrawCenteredString(x+6, 32, V_YELLOWMAP, "#");
909 V_DrawString(x+36, 32, V_YELLOWMAP, "NAME");
910 // Time
911 V_DrawRightAlignedString(x+160, 32, V_YELLOWMAP, "TIME");
912
913 // Rings
914 V_DrawThinString(x+168, 32, V_YELLOWMAP, "RING");
915
916 // Total rings
917 V_DrawThinString(x+191, 24, V_YELLOWMAP, "TOTAL");
918 V_DrawThinString(x+196, 32, V_YELLOWMAP, "RING");
919
920 // Monitors
921 V_DrawThinString(x+223, 24, V_YELLOWMAP, "ITEM");
922 V_DrawThinString(x+229, 32, V_YELLOWMAP, "BOX");
923
924 // Score
925 V_DrawRightAlignedString(x+288, 32, V_YELLOWMAP, "SCORE");
926
927 // Points
928 V_DrawRightAlignedString(x+312, 32, V_YELLOWMAP, "PT");
929
930 for (i = 0; i < data.competition.numplayers; i++)
931 {
932 ptime = (data.competition.times[i] & ~0x80000000);
933 pring = (data.competition.rings[i] & ~0x80000000);
934 pmaxring = (data.competition.maxrings[i] & ~0x80000000);
935 pmonitor = (data.competition.monitors[i] & ~0x80000000);
936 pscore = (data.competition.scores[i] & ~0x80000000);
937
938 V_DrawCenteredString(x+6, y, 0, va("%d", i+1));
939
940 if (playeringame[data.competition.num[i]])
941 {
942 // Draw the back sprite, it looks ugly if we don't
943 V_DrawSmallScaledPatch(x+16, y-4, 0, livesback);
944
945 if (data.competition.color[i] == 0)
946 V_DrawSmallScaledPatch(x+16, y-4, 0,faceprefix[*data.competition.character[i]]);
947 else
948 {
949 UINT8 *colormap = R_GetTranslationColormap(*data.competition.character[i], *data.competition.color[i], GTC_CACHE);
950 V_DrawSmallMappedPatch(x+16, y-4, 0,faceprefix[*data.competition.character[i]], colormap);
951 }
952
953 // already constrained to 8 characters
954 V_DrawString(x+36, y, V_ALLOWLOWERCASE, data.competition.name[i]);
955
956 if (players[data.competition.num[i]].pflags & PF_GAMETYPEOVER)
957 snprintf(sstrtime, sizeof sstrtime, "Time Over");
958 else if (players[data.competition.num[i]].lives <= 0)
959 snprintf(sstrtime, sizeof sstrtime, "Game Over");
960 else
961 snprintf(sstrtime, sizeof sstrtime, "%i:%02i.%02i", G_TicsToMinutes(ptime, true),
962 G_TicsToSeconds(ptime), G_TicsToCentiseconds(ptime));
963
964 sstrtime[sizeof sstrtime - 1] = '\0';
965 // Time
966 V_DrawRightAlignedThinString(x+160, y, ((data.competition.times[i] & 0x80000000) ? V_YELLOWMAP : 0), sstrtime);
967 // Rings
968 V_DrawRightAlignedThinString(x+188, y, V_MONOSPACE|((data.competition.rings[i] & 0x80000000) ? V_YELLOWMAP : 0), va("%u", pring));
969 // Total rings
970 V_DrawRightAlignedThinString(x+216, y, V_MONOSPACE|((data.competition.maxrings[i] & 0x80000000) ? V_YELLOWMAP : 0), va("%u", pmaxring));
971 // Monitors
972 V_DrawRightAlignedThinString(x+244, y, V_MONOSPACE|((data.competition.monitors[i] & 0x80000000) ? V_YELLOWMAP : 0), va("%u", pmonitor));
973 // Score
974 V_DrawRightAlignedThinString(x+288, y, V_MONOSPACE|((data.competition.scores[i] & 0x80000000) ? V_YELLOWMAP : 0), va("%u", pscore));
975 // Final Points
976 V_DrawRightAlignedString(x+312, y, V_YELLOWMAP, va("%d", data.competition.points[i]));
977 }
978
979 y += 16;
980
981 if (y > 176)
982 break;
983 }
984 }
985
986 skiptallydrawer:
987 if (!LUA_HudEnabled(hud_intermissionmessages))
988 return;
989
990 if (timer)
991 V_DrawCenteredString(BASEVIDWIDTH/2, 188, V_YELLOWMAP,
992 va("start in %d seconds", timer/TICRATE));
993
994 // Make it obvious that scrambling is happening next round.
995 if (cv_scrambleonchange.value && cv_teamscramble.value && (intertic/TICRATE % 2 == 0))
996 V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, V_YELLOWMAP, M_GetText("Teams will be scrambled next round!"));
997 }
998
999 //
1000 // Y_Ticker
1001 //
1002 // Manages fake score tally for single player end of act, and decides when intermission is over.
1003 //
Y_Ticker(void)1004 void Y_Ticker(void)
1005 {
1006 if (intertype == int_none)
1007 return;
1008
1009 // Check for pause or menu up in single player
1010 if (paused || P_AutoPause())
1011 return;
1012
1013 LUAh_IntermissionThinker();
1014
1015 intertic++;
1016
1017 // Team scramble code for team match and CTF.
1018 // Don't do this if we're going to automatically scramble teams next round.
1019 if (G_GametypeHasTeams() && cv_teamscramble.value && !cv_scrambleonchange.value && server)
1020 {
1021 // If we run out of time in intermission, the beauty is that
1022 // the P_Ticker() team scramble code will pick it up.
1023 if ((intertic % (TICRATE/7)) == 0)
1024 P_DoTeamscrambling();
1025 }
1026
1027 // multiplayer uses timer (based on cv_inttime)
1028 if (timer)
1029 {
1030 if (!--timer)
1031 {
1032 Y_EndIntermission();
1033 G_AfterIntermission();
1034 return;
1035 }
1036 }
1037 // single player is hardcoded to go away after awhile
1038 else if (intertic == endtic)
1039 {
1040 Y_EndIntermission();
1041 G_AfterIntermission();
1042 return;
1043 }
1044
1045 if (endtic != -1)
1046 return; // tally is done
1047
1048 if (intertype == int_coop) // coop or single player, normal level
1049 {
1050 INT32 i;
1051 UINT32 oldscore = data.coop.score;
1052 boolean skip = (marathonmode) ? true : false;
1053 boolean anybonuses = false;
1054
1055 if (!intertic) // first time only
1056 {
1057 if (mapheaderinfo[gamemap-1]->musinterfadeout
1058 #ifdef _WIN32
1059 // can't fade midi due to win32 volume hack
1060 && S_MusicType() != MU_MID
1061 #endif
1062 )
1063 S_FadeOutStopMusic(mapheaderinfo[gamemap-1]->musinterfadeout);
1064 else if (mapheaderinfo[gamemap-1]->musintername[0] && S_MusicExists(mapheaderinfo[gamemap-1]->musintername, !midi_disabled, !digital_disabled))
1065 S_ChangeMusicInternal(mapheaderinfo[gamemap-1]->musintername, false); // don't loop it
1066 else
1067 S_ChangeMusicInternal("_clear", false); // don't loop it
1068 tallydonetic = -1;
1069 }
1070
1071 if (intertic < TICRATE) // one second pause before tally begins
1072 return;
1073
1074 for (i = 0; i < MAXPLAYERS; i++)
1075 if (playeringame[i] && (players[i].cmd.buttons & BT_SPIN))
1076 skip = true;
1077
1078 // bonuses count down by 222 each tic
1079 for (i = 0; i < 4; ++i)
1080 {
1081 if (!data.coop.bonuses[i].points)
1082 continue;
1083
1084 data.coop.bonuses[i].points -= 222;
1085 data.coop.total += 222;
1086 data.coop.score += 222;
1087 if (data.coop.bonuses[i].points < 0 || skip == true) // too far?
1088 {
1089 data.coop.score += data.coop.bonuses[i].points;
1090 data.coop.total += data.coop.bonuses[i].points;
1091 data.coop.bonuses[i].points = 0;
1092 }
1093 if (data.coop.score > MAXSCORE)
1094 data.coop.score = MAXSCORE;
1095 if (data.coop.bonuses[i].points > 0)
1096 anybonuses = true;
1097 }
1098
1099 if (!anybonuses)
1100 {
1101 tallydonetic = intertic;
1102 endtic = intertic + 3*TICRATE; // 3 second pause after end of tally
1103 S_StartSound(NULL, (gottoken ? sfx_token : sfx_chchng)); // cha-ching!
1104
1105 // Update when done with tally
1106 if ((!modifiedgame || savemoddata) && !(netgame || multiplayer) && !demoplayback)
1107 {
1108 if (M_UpdateUnlockablesAndExtraEmblems())
1109 S_StartSound(NULL, sfx_s3k68);
1110
1111 G_SaveGameData();
1112 }
1113 }
1114 else if (!(intertic & 1))
1115 S_StartSound(NULL, sfx_ptally); // tally sound effect
1116
1117 if (data.coop.gotlife > 0 && (skip == true || data.coop.score % 50000 < oldscore % 50000)) // just passed a 50000 point mark
1118 {
1119 // lives are already added since tally is fake, but play the music
1120 P_PlayLivesJingle(NULL);
1121 --data.coop.gotlife;
1122 }
1123 }
1124 else if (intertype == int_spec) // coop or single player, special stage
1125 {
1126 INT32 i;
1127 UINT32 oldscore = data.spec.score;
1128 boolean skip = (marathonmode) ? true : false, super = false, anybonuses = false;
1129
1130 if (!intertic) // first time only
1131 {
1132 if (mapheaderinfo[gamemap-1]->musinterfadeout
1133 #ifdef _WIN32
1134 // can't fade midi due to win32 volume hack
1135 && S_MusicType() != MU_MID
1136 #endif
1137 )
1138 S_FadeOutStopMusic(mapheaderinfo[gamemap-1]->musinterfadeout);
1139 else if (mapheaderinfo[gamemap-1]->musintername[0] && S_MusicExists(mapheaderinfo[gamemap-1]->musintername, !midi_disabled, !digital_disabled))
1140 S_ChangeMusicInternal(mapheaderinfo[gamemap-1]->musintername, false); // don't loop it
1141 else
1142 S_ChangeMusicInternal("_clear", false); // don't loop it
1143 tallydonetic = -1;
1144 }
1145
1146 if (intertic < 2*TICRATE) // TWO second pause before tally begins, thank you mazmazz
1147 return;
1148
1149 for (i = 0; i < MAXPLAYERS; i++)
1150 if (playeringame[i])
1151 {
1152 if (players[i].cmd.buttons & BT_SPIN)
1153 skip = true;
1154 if (players[i].charflags & SF_SUPER)
1155 super = true;
1156 }
1157
1158 if (tallydonetic != -1 && ((data.spec.continues & 0x80) || (super && ALL7EMERALDS(emeralds))))
1159 {
1160 if ((intertic - tallydonetic) > (3*TICRATE)/2)
1161 {
1162 endtic = intertic + 4*TICRATE; // 4 second pause after end of tally
1163 if (data.spec.continues & 0x80)
1164 S_StartSound(NULL, sfx_s3kac); // bingly-bingly-bing!
1165
1166 }
1167 return;
1168 }
1169
1170 // bonuses count down by 222 each tic
1171 for (i = 0; i < 2; ++i)
1172 {
1173 if (!data.spec.bonuses[i].points)
1174 continue;
1175
1176 data.spec.bonuses[i].points -= 222;
1177 data.spec.score += 222;
1178 if (data.spec.bonuses[i].points < 0 || skip == true) // too far?
1179 {
1180 data.spec.score += data.spec.bonuses[i].points;
1181 data.spec.bonuses[i].points = 0;
1182 }
1183 if (data.spec.score > MAXSCORE)
1184 data.spec.score = MAXSCORE;
1185 if (data.spec.bonuses[i].points > 0)
1186 anybonuses = true;
1187 }
1188
1189 if (!anybonuses)
1190 {
1191 tallydonetic = intertic;
1192 if (!((data.spec.continues & 0x80) || (super && ALL7EMERALDS(emeralds)))) // don't set endtic yet!
1193 endtic = intertic + 4*TICRATE; // 4 second pause after end of tally
1194
1195 S_StartSound(NULL, (gottoken ? sfx_token : sfx_chchng)); // cha-ching!
1196
1197 // Update when done with tally
1198 if ((!modifiedgame || savemoddata) && !(netgame || multiplayer) && !demoplayback)
1199 {
1200 if (M_UpdateUnlockablesAndExtraEmblems())
1201 S_StartSound(NULL, sfx_s3k68);
1202
1203 G_SaveGameData();
1204 }
1205 }
1206 else if (!(intertic & 1))
1207 S_StartSound(NULL, sfx_ptally); // tally sound effect
1208
1209 if (data.spec.gotlife > 0 && (skip == true || data.spec.score % 50000 < oldscore % 50000)) // just passed a 50000 point mark
1210 {
1211 // lives are already added since tally is fake, but play the music
1212 P_PlayLivesJingle(NULL);
1213 --data.spec.gotlife;
1214 }
1215 }
1216 else if (intertype == int_match || intertype == int_ctf || intertype == int_teammatch) // match
1217 {
1218 if (!intertic) // first time only
1219 S_ChangeMusicInternal("_inter", true); // loop it
1220
1221 // If a player has left or joined, recalculate scores.
1222 if (data.match.numplayers != D_NumPlayers())
1223 Y_CalculateMatchWinners();
1224 }
1225 else if (intertype == int_race || intertype == int_comp) // race
1226 {
1227 if (!intertic) // first time only
1228 S_ChangeMusicInternal("_inter", true); // loop it
1229
1230 // Don't bother recalcing for race. It doesn't make as much sense.
1231 }
1232 }
1233
1234 //
1235 // Y_DetermineIntermissionType
1236 //
1237 // Determines the intermission type from the current gametype.
1238 //
Y_DetermineIntermissionType(void)1239 void Y_DetermineIntermissionType(void)
1240 {
1241 // set to int_none initially
1242 intertype = int_none;
1243
1244 if (intermissiontypes[gametype] != int_none)
1245 intertype = intermissiontypes[gametype];
1246 else if (gametype == GT_COOP)
1247 intertype = (G_IsSpecialStage(gamemap)) ? int_spec : int_coop;
1248 else if (gametype == GT_TEAMMATCH)
1249 intertype = int_teammatch;
1250 else if (gametype == GT_MATCH
1251 || gametype == GT_TAG
1252 || gametype == GT_HIDEANDSEEK)
1253 intertype = int_match;
1254 else if (gametype == GT_RACE)
1255 intertype = int_race;
1256 else if (gametype == GT_COMPETITION)
1257 intertype = int_comp;
1258 else if (gametype == GT_CTF)
1259 intertype = int_ctf;
1260 }
1261
1262 //
1263 // Y_StartIntermission
1264 //
1265 // Called by G_DoCompleted. Sets up data for intermission drawer/ticker.
1266 //
1267 //
Y_StartIntermission(void)1268 void Y_StartIntermission(void)
1269 {
1270 intertic = -1;
1271
1272 #ifdef PARANOIA
1273 if (endtic != -1)
1274 I_Error("endtic is dirty");
1275 #endif
1276
1277 if (!multiplayer)
1278 {
1279 timer = 0;
1280 intertype = (G_IsSpecialStage(gamemap)) ? int_spec : int_coop;
1281 }
1282 else
1283 {
1284 if (cv_inttime.value == 0 && ((intertype == int_coop) || (intertype == int_spec)))
1285 timer = 0;
1286 else
1287 {
1288 timer = cv_inttime.value*TICRATE;
1289
1290 if (!timer)
1291 timer = 1;
1292 }
1293 }
1294
1295 // We couldn't display the intermission even if we wanted to.
1296 // But we still need to give the players their score bonuses, dummy.
1297 //if (dedicated) return;
1298
1299 // This should always exist, but just in case...
1300 if(!mapheaderinfo[prevmap])
1301 P_AllocMapHeader(prevmap);
1302
1303 switch (intertype)
1304 {
1305 case int_coop: // coop or single player, normal level
1306 {
1307 // award time and ring bonuses
1308 Y_AwardCoopBonuses();
1309
1310 // setup time data
1311 data.coop.tics = players[consoleplayer].realtime;
1312
1313 // get act number
1314 data.coop.actnum = mapheaderinfo[gamemap-1]->actnum;
1315
1316 // grab an interscreen if appropriate
1317 if (mapheaderinfo[gamemap-1]->interscreen[0] != '#')
1318 {
1319 useinterpic = true;
1320 usebuffer = false;
1321 }
1322 else
1323 {
1324 useinterpic = false;
1325 #ifdef HWRENDER
1326 if (rendermode == render_opengl)
1327 usebuffer = true; // This needs to be here for OpenGL, otherwise usebuffer is never set to true for it, and thus there's no screenshot in the intermission
1328 #endif
1329 }
1330 usetile = false;
1331
1332 // set up the "got through act" message according to skin name
1333 // too long so just show "YOU GOT THROUGH THE ACT"
1334 if (strlen(skins[players[consoleplayer].skin].realname) > 13)
1335 {
1336 strcpy(data.coop.passed1, "you got");
1337 strcpy(data.coop.passed2, (mapheaderinfo[gamemap-1]->actnum) ? "through act" : "through the act");
1338 }
1339 // long enough that "X GOT" won't fit so use "X PASSED THE ACT"
1340 else if (strlen(skins[players[consoleplayer].skin].realname) > 8)
1341 {
1342 strcpy(data.coop.passed1, skins[players[consoleplayer].skin].realname);
1343 strcpy(data.coop.passed2, (mapheaderinfo[gamemap-1]->actnum) ? "passed act" : "passed the act");
1344 }
1345 // length is okay for normal use
1346 else
1347 {
1348 snprintf(data.coop.passed1, sizeof data.coop.passed1, "%s got",
1349 skins[players[consoleplayer].skin].realname);
1350 strcpy(data.coop.passed2, (mapheaderinfo[gamemap-1]->actnum) ? "through act" : "through the act");
1351 }
1352
1353 // set X positions
1354 if (mapheaderinfo[gamemap-1]->actnum)
1355 {
1356 data.coop.passedx1 = 62 + (176 - V_LevelNameWidth(data.coop.passed1))/2;
1357 data.coop.passedx2 = 62 + (176 - V_LevelNameWidth(data.coop.passed2))/2;
1358 }
1359 else
1360 {
1361 data.coop.passedx1 = (BASEVIDWIDTH - V_LevelNameWidth(data.coop.passed1))/2;
1362 data.coop.passedx2 = (BASEVIDWIDTH - V_LevelNameWidth(data.coop.passed2))/2;
1363 }
1364 // The above value is not precalculated because it needs only be computed once
1365 // at the start of intermission, and precalculating it would preclude mods
1366 // changing the font to one of a slightly different width.
1367 break;
1368 }
1369
1370 case int_spec: // coop or single player, special stage
1371 {
1372 // give out ring bonuses
1373 Y_AwardSpecialStageBonus();
1374
1375 // grab an interscreen if appropriate
1376 if (mapheaderinfo[gamemap-1]->interscreen[0] != '#')
1377 useinterpic = true;
1378 else
1379 useinterpic = false;
1380
1381 // tile if using the default background
1382 usetile = !useinterpic;
1383
1384 // get special stage specific patches
1385 /* if (!stagefailed && ALL7EMERALDS(emeralds))
1386 {
1387 data.spec.cemerald = W_CachePatchName("GOTEMALL", PU_PATCH);
1388 data.spec.headx = 70;
1389 data.spec.nowsuper = players[consoleplayer].skin
1390 ? NULL : W_CachePatchName("NOWSUPER", PU_PATCH);
1391 }
1392 else
1393 {
1394 data.spec.cemerald = W_CachePatchName("CEMERALD", PU_PATCH);
1395 data.spec.headx = 48;
1396 data.spec.nowsuper = NULL;
1397 } */
1398
1399 // Super form stuff (normally blank)
1400 data.spec.passed3[0] = '\0';
1401 data.spec.passed4[0] = '\0';
1402
1403 // set up the "got through act" message according to skin name
1404 if (stagefailed)
1405 {
1406 strcpy(data.spec.passed2, "Special Stage");
1407 data.spec.passed1[0] = '\0';
1408 }
1409 else if (ALL7EMERALDS(emeralds))
1410 {
1411 snprintf(data.spec.passed1,
1412 sizeof data.spec.passed1, "%s",
1413 skins[players[consoleplayer].skin].realname);
1414 data.spec.passed1[sizeof data.spec.passed1 - 1] = '\0';
1415 strcpy(data.spec.passed2, "got them all!");
1416
1417 if (players[consoleplayer].charflags & SF_SUPER)
1418 {
1419 strcpy(data.spec.passed3, "can now become");
1420 snprintf(data.spec.passed4,
1421 sizeof data.spec.passed4, "Super %s",
1422 skins[players[consoleplayer].skin].realname);
1423 data.spec.passed4[sizeof data.spec.passed4 - 1] = '\0';
1424 }
1425 }
1426 else
1427 {
1428 if (strlen(skins[players[consoleplayer].skin].realname) <= SKINNAMESIZE-5)
1429 {
1430 snprintf(data.spec.passed1,
1431 sizeof data.spec.passed1, "%s got",
1432 skins[players[consoleplayer].skin].realname);
1433 data.spec.passed1[sizeof data.spec.passed1 - 1] = '\0';
1434 }
1435 else
1436 strcpy(data.spec.passed1, "You got");
1437 strcpy(data.spec.passed2, "a Chaos Emerald");
1438 if (P_GetNextEmerald() > 6)
1439 {
1440 data.spec.passed2[15] = '?';
1441 data.spec.passed2[16] = '\0';
1442 }
1443 }
1444 data.spec.passedx1 = (BASEVIDWIDTH - V_LevelNameWidth(data.spec.passed1))/2;
1445 data.spec.passedx2 = (BASEVIDWIDTH - V_LevelNameWidth(data.spec.passed2))/2;
1446 data.spec.passedx3 = (BASEVIDWIDTH - V_LevelNameWidth(data.spec.passed3))/2;
1447 data.spec.passedx4 = (BASEVIDWIDTH - V_LevelNameWidth(data.spec.passed4))/2;
1448 break;
1449 }
1450
1451 case int_match:
1452 {
1453 // Calculate who won
1454 Y_CalculateMatchWinners();
1455
1456 // set up the levelstring
1457 if (mapheaderinfo[prevmap]->actnum)
1458 snprintf(data.match.levelstring,
1459 sizeof data.match.levelstring,
1460 "%.32s * %d *",
1461 mapheaderinfo[prevmap]->lvlttl, mapheaderinfo[prevmap]->actnum);
1462 else
1463 snprintf(data.match.levelstring,
1464 sizeof data.match.levelstring,
1465 "* %.32s *",
1466 mapheaderinfo[prevmap]->lvlttl);
1467
1468 data.match.levelstring[sizeof data.match.levelstring - 1] = '\0';
1469
1470 usetile = true;
1471 useinterpic = false;
1472 break;
1473 }
1474
1475 case int_race: // (time-only race)
1476 {
1477 // Calculate who won
1478 Y_CalculateTimeRaceWinners();
1479
1480 // set up the levelstring
1481 if (mapheaderinfo[prevmap]->actnum)
1482 snprintf(data.match.levelstring,
1483 sizeof data.match.levelstring,
1484 "%.32s * %d *",
1485 mapheaderinfo[prevmap]->lvlttl, mapheaderinfo[prevmap]->actnum);
1486 else
1487 snprintf(data.match.levelstring,
1488 sizeof data.match.levelstring,
1489 "* %.32s *",
1490 mapheaderinfo[prevmap]->lvlttl);
1491
1492 data.match.levelstring[sizeof data.match.levelstring - 1] = '\0';
1493
1494 usetile = true;
1495 useinterpic = false;
1496 break;
1497 }
1498
1499 case int_teammatch:
1500 case int_ctf:
1501 {
1502 // Calculate who won
1503 Y_CalculateMatchWinners();
1504
1505 // set up the levelstring
1506 if (mapheaderinfo[prevmap]->actnum)
1507 snprintf(data.match.levelstring,
1508 sizeof data.match.levelstring,
1509 "%.32s * %d *",
1510 mapheaderinfo[prevmap]->lvlttl, mapheaderinfo[prevmap]->actnum);
1511 else
1512 snprintf(data.match.levelstring,
1513 sizeof data.match.levelstring,
1514 "* %.32s *",
1515 mapheaderinfo[prevmap]->lvlttl);
1516
1517 data.match.levelstring[sizeof data.match.levelstring - 1] = '\0';
1518
1519 usetile = true;
1520 useinterpic = false;
1521 break;
1522 }
1523
1524 case int_comp: // classic (full race)
1525 {
1526 // find out who won
1527 Y_CalculateCompetitionWinners();
1528
1529 // set up the levelstring
1530 if (mapheaderinfo[prevmap]->actnum)
1531 snprintf(data.competition.levelstring,
1532 sizeof data.competition.levelstring,
1533 "%.32s * %d *",
1534 mapheaderinfo[prevmap]->lvlttl, mapheaderinfo[prevmap]->actnum);
1535 else
1536 snprintf(data.competition.levelstring,
1537 sizeof data.competition.levelstring,
1538 "* %.32s *",
1539 mapheaderinfo[prevmap]->lvlttl);
1540
1541 data.competition.levelstring[sizeof data.competition.levelstring - 1] = '\0';
1542
1543 usetile = true;
1544 useinterpic = false;
1545 break;
1546 }
1547
1548 case int_none:
1549 default:
1550 break;
1551 }
1552 }
1553
1554 //
1555 // Y_CalculateMatchWinners
1556 //
Y_CalculateMatchWinners(void)1557 static void Y_CalculateMatchWinners(void)
1558 {
1559 INT32 i, j;
1560 boolean completed[MAXPLAYERS];
1561
1562 // Initialize variables
1563 memset(data.match.scores, 0, sizeof (data.match.scores));
1564 memset(data.match.color, 0, sizeof (data.match.color));
1565 memset(data.match.character, 0, sizeof (data.match.character));
1566 memset(data.match.spectator, 0, sizeof (data.match.spectator));
1567 memset(completed, 0, sizeof (completed));
1568 data.match.numplayers = 0;
1569 i = j = 0;
1570
1571 for (j = 0; j < MAXPLAYERS; j++)
1572 {
1573 if (!playeringame[j])
1574 continue;
1575
1576 for (i = 0; i < MAXPLAYERS; i++)
1577 {
1578 if (!playeringame[i])
1579 continue;
1580
1581 if (players[i].score >= data.match.scores[data.match.numplayers] && completed[i] == false)
1582 {
1583 data.match.scores[data.match.numplayers] = players[i].score;
1584 data.match.color[data.match.numplayers] = &players[i].skincolor;
1585 data.match.character[data.match.numplayers] = &players[i].skin;
1586 data.match.name[data.match.numplayers] = player_names[i];
1587 data.match.spectator[data.match.numplayers] = players[i].spectator;
1588 data.match.num[data.match.numplayers] = i;
1589 }
1590 }
1591 completed[data.match.num[data.match.numplayers]] = true;
1592 data.match.numplayers++;
1593 }
1594 }
1595
1596 //
1597 // Y_CalculateTimeRaceWinners
1598 //
Y_CalculateTimeRaceWinners(void)1599 static void Y_CalculateTimeRaceWinners(void)
1600 {
1601 INT32 i, j;
1602 boolean completed[MAXPLAYERS];
1603
1604 // Initialize variables
1605
1606 for (i = 0; i < MAXPLAYERS; i++)
1607 data.match.scores[i] = INT32_MAX;
1608
1609 memset(data.match.color, 0, sizeof (data.match.color));
1610 memset(data.match.character, 0, sizeof (data.match.character));
1611 memset(data.match.spectator, 0, sizeof (data.match.spectator));
1612 memset(completed, 0, sizeof (completed));
1613 data.match.numplayers = 0;
1614 i = j = 0;
1615
1616 for (j = 0; j < MAXPLAYERS; j++)
1617 {
1618 if (!playeringame[j])
1619 continue;
1620
1621 for (i = 0; i < MAXPLAYERS; i++)
1622 {
1623 if (!playeringame[i])
1624 continue;
1625
1626 if (players[i].realtime <= data.match.scores[data.match.numplayers] && completed[i] == false)
1627 {
1628 data.match.scores[data.match.numplayers] = players[i].realtime;
1629 data.match.color[data.match.numplayers] = &players[i].skincolor;
1630 data.match.character[data.match.numplayers] = &players[i].skin;
1631 data.match.name[data.match.numplayers] = player_names[i];
1632 data.match.num[data.match.numplayers] = i;
1633 }
1634 }
1635 completed[data.match.num[data.match.numplayers]] = true;
1636 data.match.numplayers++;
1637 }
1638 }
1639
1640 //
1641 // Y_CalculateCompetitionWinners
1642 //
Y_CalculateCompetitionWinners(void)1643 static void Y_CalculateCompetitionWinners(void)
1644 {
1645 INT32 i, j;
1646 boolean bestat[5];
1647 boolean completed[MAXPLAYERS];
1648 INT32 winner; // shortcut
1649
1650 UINT32 points[MAXPLAYERS];
1651 UINT32 times[MAXPLAYERS];
1652 UINT32 rings[MAXPLAYERS];
1653 UINT32 maxrings[MAXPLAYERS];
1654 UINT32 monitors[MAXPLAYERS];
1655 UINT32 scores[MAXPLAYERS];
1656 char tempname[9];
1657
1658 memset(data.competition.points, 0, sizeof (data.competition.points));
1659 memset(points, 0, sizeof (points));
1660 memset(completed, 0, sizeof (completed));
1661
1662 // Award points.
1663 for (i = 0; i < MAXPLAYERS; i++)
1664 {
1665 if (!playeringame[i])
1666 continue;
1667
1668 for (j = 0; j < 5; j++)
1669 bestat[j] = true;
1670
1671 if ((players[i].pflags & PF_GAMETYPEOVER) || players[i].lives <= 0)
1672 players[i].rings = 0;
1673
1674 times[i] = players[i].realtime;
1675 rings[i] = (UINT32)max(players[i].rings, 0);
1676 maxrings[i] = (UINT32)players[i].totalring;
1677 monitors[i] = (UINT32)players[i].numboxes;
1678 scores[i] = (UINT32)min(players[i].score, MAXSCORE);
1679
1680 for (j = 0; j < MAXPLAYERS; j++)
1681 {
1682 if (!playeringame[j] || j == i)
1683 continue;
1684
1685 if (players[i].realtime <= players[j].realtime)
1686 points[i]++;
1687 else
1688 bestat[0] = false;
1689
1690 if (max(players[i].rings, 0) >= max(players[j].rings, 0))
1691 points[i]++;
1692 else
1693 bestat[1] = false;
1694
1695 if (players[i].totalring >= players[j].totalring)
1696 points[i]++;
1697 else
1698 bestat[2] = false;
1699
1700 if (players[i].numboxes >= players[j].numboxes)
1701 points[i]++;
1702 else
1703 bestat[3] = false;
1704
1705 if (players[i].score >= players[j].score)
1706 points[i]++;
1707 else
1708 bestat[4] = false;
1709 }
1710
1711 // Highlight best scores
1712 if (bestat[0])
1713 times[i] |= 0x80000000;
1714 if (bestat[1])
1715 rings[i] |= 0x80000000;
1716 if (bestat[2])
1717 maxrings[i] |= 0x80000000;
1718 if (bestat[3])
1719 monitors[i] |= 0x80000000;
1720 if (bestat[4])
1721 scores[i] |= 0x80000000;
1722 }
1723
1724 // Now we go through and set the data.competition struct properly
1725 data.competition.numplayers = 0;
1726 for (i = 0; i < MAXPLAYERS; i++)
1727 {
1728 if (!playeringame[i])
1729 continue;
1730
1731 winner = 0;
1732
1733 for (j = 0; j < MAXPLAYERS; j++)
1734 {
1735 if (!playeringame[j])
1736 continue;
1737
1738 if (points[j] >= data.competition.points[data.competition.numplayers] && completed[j] == false)
1739 {
1740 data.competition.points[data.competition.numplayers] = points[j];
1741 data.competition.num[data.competition.numplayers] = winner = j;
1742 }
1743 }
1744 // We know this person won this spot, now let's set everything appropriately
1745 data.competition.times[data.competition.numplayers] = times[winner];
1746 data.competition.rings[data.competition.numplayers] = rings[winner];
1747 data.competition.maxrings[data.competition.numplayers] = maxrings[winner];
1748 data.competition.monitors[data.competition.numplayers] = monitors[winner];
1749 data.competition.scores[data.competition.numplayers] = scores[winner];
1750
1751 strncpy(tempname, player_names[winner], 8);
1752 tempname[8] = '\0';
1753 strncpy(data.competition.name[data.competition.numplayers], tempname, 9);
1754
1755 data.competition.color[data.competition.numplayers] = &players[winner].skincolor;
1756 data.competition.character[data.competition.numplayers] = &players[winner].skin;
1757
1758 completed[winner] = true;
1759 data.competition.numplayers++;
1760 }
1761 }
1762
1763 // ============
1764 // COOP BONUSES
1765 // ============
1766
1767 //
1768 // Y_SetNullBonus
1769 // No bonus in this slot, but we need to set some things anyway.
1770 //
Y_SetNullBonus(player_t * player,y_bonus_t * bstruct)1771 static void Y_SetNullBonus(player_t *player, y_bonus_t *bstruct)
1772 {
1773 (void)player;
1774 memset(bstruct, 0, sizeof(y_bonus_t));
1775 }
1776
1777 //
1778 // Y_SetTimeBonus
1779 //
Y_SetTimeBonus(player_t * player,y_bonus_t * bstruct)1780 static void Y_SetTimeBonus(player_t *player, y_bonus_t *bstruct)
1781 {
1782 INT32 secs, bonus;
1783
1784 strncpy(bstruct->patch, "YB_TIME", sizeof(bstruct->patch));
1785 bstruct->display = true;
1786
1787 // calculate time bonus
1788 secs = player->realtime / TICRATE;
1789 if (secs < 30) /* :30 */ bonus = 50000;
1790 else if (secs < 60) /* 1:00 */ bonus = 10000;
1791 else if (secs < 90) /* 1:30 */ bonus = 5000;
1792 else if (secs < 120) /* 2:00 */ bonus = 4000;
1793 else if (secs < 180) /* 3:00 */ bonus = 3000;
1794 else if (secs < 240) /* 4:00 */ bonus = 2000;
1795 else if (secs < 300) /* 5:00 */ bonus = 1000;
1796 else if (secs < 360) /* 6:00 */ bonus = 500;
1797 else if (secs < 420) /* 7:00 */ bonus = 400;
1798 else if (secs < 480) /* 8:00 */ bonus = 300;
1799 else if (secs < 540) /* 9:00 */ bonus = 200;
1800 else if (secs < 600) /* 10:00 */ bonus = 100;
1801 else /* TIME TAKEN: TOO LONG */ bonus = 0;
1802 bstruct->points = bonus;
1803 }
1804
1805 //
1806 // Y_SetRingBonus
1807 //
Y_SetRingBonus(player_t * player,y_bonus_t * bstruct)1808 static void Y_SetRingBonus(player_t *player, y_bonus_t *bstruct)
1809 {
1810 strncpy(bstruct->patch, "YB_RING", sizeof(bstruct->patch));
1811 bstruct->display = true;
1812 bstruct->points = max(0, (player->rings) * 100);
1813 }
1814
1815 //
1816 // Y_SetNightsBonus
1817 //
Y_SetNightsBonus(player_t * player,y_bonus_t * bstruct)1818 static void Y_SetNightsBonus(player_t *player, y_bonus_t *bstruct)
1819 {
1820 strncpy(bstruct->patch, "YB_NIGHT", sizeof(bstruct->patch));
1821 bstruct->display = true;
1822 bstruct->points = player->totalmarescore;
1823 }
1824
1825 //
1826 // Y_SetLapBonus
1827 //
Y_SetLapBonus(player_t * player,y_bonus_t * bstruct)1828 static void Y_SetLapBonus(player_t *player, y_bonus_t *bstruct)
1829 {
1830 strncpy(bstruct->patch, "YB_LAP", sizeof(bstruct->patch));
1831 bstruct->display = true;
1832 bstruct->points = max(0, player->totalmarebonuslap * 1000);
1833 }
1834
1835 //
1836 // Y_SetLinkBonus
1837 //
Y_SetLinkBonus(player_t * player,y_bonus_t * bstruct)1838 static void Y_SetLinkBonus(player_t *player, y_bonus_t *bstruct)
1839 {
1840 strncpy(bstruct->patch, "YB_LINK", sizeof(bstruct->patch));
1841 bstruct->display = true;
1842 bstruct->points = max(0, (player->maxlink - 1) * 100);
1843 }
1844
1845 //
1846 // Y_SetGuardBonus
1847 //
Y_SetGuardBonus(player_t * player,y_bonus_t * bstruct)1848 static void Y_SetGuardBonus(player_t *player, y_bonus_t *bstruct)
1849 {
1850 INT32 bonus;
1851 strncpy(bstruct->patch, "YB_GUARD", sizeof(bstruct->patch));
1852 bstruct->display = true;
1853
1854 if (player->timeshit == 0) bonus = 10000;
1855 else if (player->timeshit == 1) bonus = 5000;
1856 else if (player->timeshit == 2) bonus = 1000;
1857 else if (player->timeshit == 3) bonus = 500;
1858 else if (player->timeshit == 4) bonus = 100;
1859 else bonus = 0;
1860 bstruct->points = bonus;
1861 }
1862
1863 //
1864 // Y_SetPerfectBonus
1865 //
Y_SetPerfectBonus(player_t * player,y_bonus_t * bstruct)1866 static void Y_SetPerfectBonus(player_t *player, y_bonus_t *bstruct)
1867 {
1868 INT32 i;
1869
1870 (void)player;
1871 memset(bstruct, 0, sizeof(y_bonus_t));
1872 strncpy(bstruct->patch, "YB_PERFE", sizeof(bstruct->patch));
1873
1874 if (intertype != int_coop || data.coop.gotperfbonus == -1)
1875 {
1876 INT32 sharedringtotal = 0;
1877 for (i = 0; i < MAXPLAYERS; i++)
1878 {
1879 if (!playeringame[i]) continue;
1880 sharedringtotal += players[i].rings;
1881 }
1882 if (!sharedringtotal || nummaprings == -1 || sharedringtotal < nummaprings)
1883 bstruct->display = false;
1884 else
1885 {
1886 bstruct->display = true;
1887 bstruct->points = 50000;
1888 }
1889 }
1890 if (intertype != int_coop)
1891 return;
1892
1893 data.coop.gotperfbonus = (bstruct->display ? 1 : 0);
1894 }
1895
Y_SetSpecialRingBonus(player_t * player,y_bonus_t * bstruct)1896 static void Y_SetSpecialRingBonus(player_t *player, y_bonus_t *bstruct)
1897 {
1898 INT32 i, sharedringtotal = 0;
1899
1900 (void)player;
1901 strncpy(bstruct->patch, "YB_RING", sizeof(bstruct->patch));
1902 bstruct->display = true;
1903
1904 for (i = 0; i < MAXPLAYERS; i++)
1905 {
1906 if (!playeringame[i]) continue;
1907 sharedringtotal += players[i].rings;
1908 }
1909 bstruct->points = max(0, (sharedringtotal) * 100);
1910 }
1911
1912 // This list can be extended in the future with SOC/Lua, perhaps.
1913 typedef void (*bonus_f)(player_t *, y_bonus_t *);
1914 bonus_f bonuses_list[6][4] = {
1915 {
1916 Y_SetNullBonus,
1917 Y_SetNullBonus,
1918 Y_SetNullBonus,
1919 Y_SetNullBonus,
1920 },
1921 {
1922 Y_SetNullBonus,
1923 Y_SetTimeBonus,
1924 Y_SetRingBonus,
1925 Y_SetPerfectBonus,
1926 },
1927 {
1928 Y_SetNullBonus,
1929 Y_SetGuardBonus,
1930 Y_SetRingBonus,
1931 Y_SetNullBonus,
1932 },
1933 {
1934 Y_SetNullBonus,
1935 Y_SetGuardBonus,
1936 Y_SetRingBonus,
1937 Y_SetPerfectBonus,
1938 },
1939 {
1940 Y_SetNullBonus,
1941 Y_SetNightsBonus,
1942 Y_SetLapBonus,
1943 Y_SetNullBonus,
1944 },
1945 {
1946 Y_SetNullBonus,
1947 Y_SetLinkBonus,
1948 Y_SetLapBonus,
1949 Y_SetNullBonus,
1950 },
1951 };
1952
1953
1954
1955 //
1956 // Y_AwardCoopBonuses
1957 //
1958 // Awards the time and ring bonuses.
1959 //
Y_AwardCoopBonuses(void)1960 static void Y_AwardCoopBonuses(void)
1961 {
1962 INT32 i, j, bonusnum, oldscore, ptlives;
1963 y_bonus_t localbonuses[4];
1964
1965 // set score/total first
1966 data.coop.total = 0;
1967 data.coop.score = players[consoleplayer].score;
1968 data.coop.gotperfbonus = -1;
1969 memset(data.coop.bonuses, 0, sizeof(data.coop.bonuses));
1970 memset(data.coop.bonuspatches, 0, sizeof(data.coop.bonuspatches));
1971
1972 for (i = 0; i < MAXPLAYERS; ++i)
1973 {
1974 if (!playeringame[i] || players[i].lives < 1) // not active or game over
1975 bonusnum = 0; // all null
1976 else
1977 bonusnum = mapheaderinfo[prevmap]->bonustype + 1; // -1 is none
1978
1979 oldscore = players[i].score;
1980
1981 for (j = 0; j < 4; ++j) // Set bonuses
1982 {
1983 (bonuses_list[bonusnum][j])(&players[i], &localbonuses[j]);
1984 players[i].score += localbonuses[j].points;
1985 if (players[i].score > MAXSCORE)
1986 players[i].score = MAXSCORE;
1987 }
1988
1989 ptlives = min(
1990 (INT32)((!ultimatemode && !modeattacking && players[i].lives != INFLIVES) ? max((INT32)((players[i].score/50000) - (oldscore/50000)), (INT32)0) : 0),
1991 (INT32)(mapheaderinfo[prevmap]->maxbonuslives < 0 ? INT32_MAX : mapheaderinfo[prevmap]->maxbonuslives));
1992 if (ptlives)
1993 P_GivePlayerLives(&players[i], ptlives);
1994
1995 if (i == consoleplayer)
1996 {
1997 data.coop.gotlife = (((netgame || multiplayer) && G_GametypeUsesCoopLives() && cv_cooplives.value == 0) ? 0 : ptlives);
1998 M_Memcpy(&data.coop.bonuses, &localbonuses, sizeof(data.coop.bonuses));
1999 }
2000 }
2001
2002 // Just in case the perfect bonus wasn't checked.
2003 if (data.coop.gotperfbonus < 0)
2004 data.coop.gotperfbonus = 0;
2005 }
2006
2007 //
2008 // Y_AwardSpecialStageBonus
2009 //
2010 // Gives a ring bonus only.
Y_AwardSpecialStageBonus(void)2011 static void Y_AwardSpecialStageBonus(void)
2012 {
2013 INT32 i, oldscore, ptlives;
2014 y_bonus_t localbonuses[2];
2015
2016 data.spec.score = players[consoleplayer].score;
2017 memset(data.spec.bonuses, 0, sizeof(data.spec.bonuses));
2018 memset(data.spec.bonuspatches, 0, sizeof(data.spec.bonuspatches));
2019
2020 for (i = 0; i < MAXPLAYERS; i++)
2021 {
2022 oldscore = players[i].score;
2023
2024 if (!playeringame[i] || players[i].lives < 1) // not active or game over
2025 {
2026 Y_SetNullBonus(&players[i], &localbonuses[0]);
2027 Y_SetNullBonus(&players[i], &localbonuses[1]);
2028 }
2029 else if (maptol & TOL_NIGHTS) // NiGHTS bonus score instead of Rings
2030 {
2031 Y_SetNightsBonus(&players[i], &localbonuses[0]);
2032 Y_SetNullBonus(&players[i], &localbonuses[1]);
2033 }
2034 else
2035 {
2036 Y_SetSpecialRingBonus(&players[i], &localbonuses[0]);
2037 Y_SetPerfectBonus(&players[i], &localbonuses[1]);
2038 }
2039 players[i].score += localbonuses[0].points;
2040 players[i].score += localbonuses[1].points;
2041 if (players[i].score > MAXSCORE)
2042 players[i].score = MAXSCORE;
2043
2044 // grant extra lives right away since tally is faked
2045 ptlives = min(
2046 (INT32)((!ultimatemode && !modeattacking && players[i].lives != INFLIVES) ? max((INT32)((players[i].score/50000) - (oldscore/50000)), (INT32)0) : 0),
2047 (INT32)(mapheaderinfo[prevmap]->maxbonuslives < 0 ? INT32_MAX : mapheaderinfo[prevmap]->maxbonuslives));
2048 P_GivePlayerLives(&players[i], ptlives);
2049
2050 if (i == consoleplayer)
2051 {
2052 data.spec.gotlife = (((netgame || multiplayer) && G_GametypeUsesCoopLives() && cv_cooplives.value == 0) ? 0 : ptlives);
2053 M_Memcpy(&data.spec.bonuses, &localbonuses, sizeof(data.spec.bonuses));
2054
2055 // Continues related
2056 data.spec.continues = min(players[i].continues, 8);
2057 if (players[i].gotcontinue)
2058 data.spec.continues |= 0x80;
2059 data.spec.playercolor = &players[i].skincolor;
2060 data.spec.playerchar = &players[i].skin;
2061 }
2062 }
2063 }
2064
2065 // ======
2066
2067 //
2068 // Y_EndIntermission
2069 //
Y_EndIntermission(void)2070 void Y_EndIntermission(void)
2071 {
2072 if (!dedicated)
2073 Y_UnloadData();
2074
2075 endtic = -1;
2076 intertype = int_none;
2077 usebuffer = false;
2078 }
2079
2080 #define UNLOAD(x) if (x) {Patch_Free(x);} x = NULL;
2081
2082 //
2083 // Y_UnloadData
2084 //
Y_UnloadData(void)2085 static void Y_UnloadData(void)
2086 {
2087 // unload the background patches
2088 UNLOAD(bgpatch);
2089 UNLOAD(bgtile);
2090 UNLOAD(interpic);
2091
2092 switch (intertype)
2093 {
2094 case int_coop:
2095 // unload the coop and single player patches
2096 UNLOAD(data.coop.bonuspatches[3]);
2097 UNLOAD(data.coop.bonuspatches[2]);
2098 UNLOAD(data.coop.bonuspatches[1]);
2099 UNLOAD(data.coop.bonuspatches[0]);
2100 UNLOAD(data.coop.ptotal);
2101 break;
2102 case int_spec:
2103 // unload the special stage patches
2104 //UNLOAD(data.spec.cemerald);
2105 //UNLOAD(data.spec.nowsuper);
2106 UNLOAD(data.spec.bonuspatches[1]);
2107 UNLOAD(data.spec.bonuspatches[0]);
2108 UNLOAD(data.spec.pscore);
2109 UNLOAD(data.spec.pcontinues);
2110 break;
2111 case int_match:
2112 case int_race:
2113 UNLOAD(data.match.result);
2114 break;
2115 case int_ctf:
2116 UNLOAD(data.match.blueflag);
2117 UNLOAD(data.match.redflag);
2118 break;
2119 default:
2120 //without this default,
2121 //int_none, int_tag, int_chaos, and int_comp
2122 //are not handled
2123 break;
2124 }
2125 }
2126