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