// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2004-2024 by Sonic Team Junior.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file  y_inter.c
/// \brief Tally screens, or "Intermissions" as they were formally called in Doom

#include "doomdef.h"
#include "doomstat.h"
#include "d_main.h"
#include "f_finale.h"
#include "g_game.h"
#include "hu_stuff.h"
#include "netcode/i_net.h"
#include "i_video.h"
#include "p_tick.h"
#include "r_defs.h"
#include "r_skins.h"
#include "s_sound.h"
#include "st_stuff.h"
#include "v_video.h"
#include "w_wad.h"
#include "y_inter.h"
#include "z_zone.h"
#include "m_menu.h"
#include "m_misc.h"
#include "i_system.h"
#include "p_setup.h"

#include "r_local.h"
#include "p_local.h"

#include "m_cond.h" // condition sets
#include "lua_hook.h" // IntermissionThinker hook

#include "lua_hud.h"
#include "lua_hudlib_drawlist.h"

#ifdef HWRENDER
#include "hardware/hw_main.h"
#endif

typedef struct
{
	char patch[9];
	 INT32 points;
	UINT8 display;
} y_bonus_t;

typedef union
{
	struct
	{
		char passed1[21]; // KNUCKLES GOT    / CRAWLA HONCHO
		char passed2[16]; // THROUGH THE ACT / PASSED THE ACT
		INT32 passedx1;
		INT32 passedx2;

		y_bonus_t bonuses[4];
		patch_t *bonuspatches[4];

		SINT8 gotperfbonus; // Used for visitation flags.

		UINT32 score, total; // fake score, total
		UINT32 tics; // time

		UINT8 actnum; // act number being displayed
		patch_t *ptotal; // TOTAL
		UINT8 gotlife; // Number of extra lives obtained
	} coop;

	struct
	{
		char passed1[29];             // KNUCKLES GOT     / CRAWLA HONCHO
		char passed2[17];             // A CHAOS EMERALD? / GOT THEM ALL!
		char passed3[15];             //                   CAN NOW BECOME
		char passed4[SKINNAMESIZE+7]; //                   SUPER CRAWLA HONCHO
		INT32 passedx1;
		INT32 passedx2;
		INT32 passedx3;
		INT32 passedx4;

		INT32 emeraldbounces;
		INT32 emeraldmomy;
		INT32 emeraldy;

		y_bonus_t bonuses[2];
		patch_t *bonuspatches[2];

		patch_t *pscore; // SCORE
		UINT32 score; // fake score

		// Continues
		UINT8 continues;
		patch_t *pcontinues;
		UINT8 *playerchar; // Continue HUD
		UINT16 *playercolor;

		UINT8 gotlife; // Number of extra lives obtained
	} spec;

	struct
	{
		UINT32 scores[MAXPLAYERS]; // Winner's score
		UINT16 *color[MAXPLAYERS]; // Winner's color #
		boolean spectator[MAXPLAYERS]; // Spectator list
		UINT8 *character[MAXPLAYERS]; // Winner's character #
		INT32 ctfteam[MAXPLAYERS]; // Winner's ctfteam #
		INT32 num[MAXPLAYERS]; // Winner's player #
		char *name[MAXPLAYERS]; // Winner's name
		patch_t *result; // RESULT
		patch_t *blueflag;
		patch_t *redflag; // int_ctf uses this struct too.
		INT32 numplayers; // Number of players being displayed
		char levelstring[40]; // holds levelnames up to 32 characters
	} match;

	struct
	{
		UINT16 *color[MAXPLAYERS]; // Winner's color #
		UINT8 *character[MAXPLAYERS]; // Winner's character #
		INT32 num[MAXPLAYERS]; // Winner's player #
		char name[MAXPLAYERS][9]; // Winner's name
		UINT32 times[MAXPLAYERS];
		UINT32 rings[MAXPLAYERS];
		UINT32 maxrings[MAXPLAYERS];
		UINT32 monitors[MAXPLAYERS];
		UINT32 scores[MAXPLAYERS];
		UINT32 points[MAXPLAYERS];
		INT32 numplayers; // Number of players being displayed
		char levelstring[40]; // holds levelnames up to 32 characters
	} competition;

} y_data;

static y_data data;

// graphics
static patch_t *bgpatch = NULL;     // INTERSCR
static patch_t *bgtile = NULL;      // SPECTILE/SRB2BACK
static patch_t *interpic = NULL;    // custom picture defined in map header
static boolean usetile;
static INT32 timer;

typedef struct
{
	INT32 source_width, source_height;
	INT32 source_bpp, source_rowbytes;
	UINT8 *source_picture;
	INT32 target_width, target_height;
	INT32 target_bpp, target_rowbytes;
	UINT8 *target_picture;
} y_buffer_t;

boolean usebuffer = false;
static boolean useinterpic;
static y_buffer_t *y_buffer;

static INT32 intertic;
static INT32 tallydonetic = -1;
static INT32 endtic = -1;

intertype_t intertype = int_none;
intertype_t intermissiontypes[NUMGAMETYPES];

static huddrawlist_h luahuddrawlist_intermission;

static void Y_RescaleScreenBuffer(void);
static void Y_AwardCoopBonuses(void);
static void Y_AwardSpecialStageBonus(void);
static void Y_CalculateCompetitionWinners(void);
static void Y_CalculateTimeRaceWinners(void);
static void Y_CalculateMatchWinners(void);
static void Y_UnloadData(void);

// Stuff copy+pasted from st_stuff.c
#define ST_DrawNumFromHud(h,n)        V_DrawTallNum(hudinfo[h].x, hudinfo[h].y, hudinfo[h].f, n)
#define ST_DrawPadNumFromHud(h,n,q)   V_DrawPaddedTallNum(hudinfo[h].x, hudinfo[h].y, hudinfo[h].f, n, q)
#define ST_DrawPatchFromHud(h,p)      V_DrawScaledPatch(hudinfo[h].x, hudinfo[h].y, hudinfo[h].f, p)

static void Y_IntermissionTokenDrawer(void)
{
	INT32 y, offs, lowy, calc;
	UINT32 tokencount;
	INT16 temp;
	UINT8 em;

	offs = 0;
	lowy = BASEVIDHEIGHT - 32 - 8;
	temp = tokenicon->height / 2;

	em = 0;
	while (emeralds & (1 << em))
		if (++em == 7)
			return;

	if (tallydonetic != -1)
	{
		offs = (intertic - tallydonetic)*2;
		if (offs > 10)
			offs = 8;
	}

	V_DrawSmallScaledPatch(32, lowy-1, 0, emeraldpics[2][em]); // coinbox

	y = (lowy + offs + 1) - (temp + (token + 1)*8);

	for (tokencount = token; tokencount; tokencount--)
	{
		if (y >= -temp)
			V_DrawSmallScaledPatch(32, y, 0, tokenicon);
		y += 8;
	}

	y += (offs*(temp - 1)/8);
	calc = (lowy - y)*2;

	if (calc > 0)
		V_DrawCroppedPatch(32<<FRACBITS, y<<FRACBITS, FRACUNIT/2, FRACUNIT/2, 0, tokenicon, NULL, 0, 0, tokenicon->width<<FRACBITS, calc<<FRACBITS);
}


//
// Y_LoadIntermissionData
//
// Load patches for drawing the intermission, if acceptable
//
void Y_LoadIntermissionData(void)
{
	INT32 i;

	if (dedicated)
		return;

	switch (intertype)
	{
		case int_coop:
		{
			for (i = 0; i < 4; ++i)
			{
				if (strlen(data.coop.bonuses[i].patch))
					data.coop.bonuspatches[i] = W_CachePatchName(data.coop.bonuses[i].patch, PU_PATCH);
			}
			data.coop.ptotal = W_CachePatchName("YB_TOTAL", PU_PATCH);


			// grab an interscreen if appropriate
			if (mapheaderinfo[gamemap-1]->interscreen[0] != '#')
				interpic = W_CachePatchName(mapheaderinfo[gamemap-1]->interscreen, PU_PATCH);
			else // no interscreen? use default background
				bgpatch = W_CachePatchName("INTERSCR", PU_PATCH);
			break;
		}
		case int_spec:
		{
			for (i = 0; i < 2; ++i)
				data.spec.bonuspatches[i] = W_CachePatchName(data.spec.bonuses[i].patch, PU_PATCH);

			data.spec.pscore = W_CachePatchName("YB_SCORE", PU_PATCH);
			data.spec.pcontinues = W_CachePatchName("YB_CONTI", PU_PATCH);

			// grab an interscreen if appropriate
			if (mapheaderinfo[gamemap-1]->interscreen[0] != '#')
				interpic = W_CachePatchName(mapheaderinfo[gamemap-1]->interscreen, PU_PATCH);
			else // no interscreen? use default background
				bgtile = W_CachePatchName("SPECTILE", PU_PATCH);
			break;
		}
		case int_ctf:
		case int_teammatch:
		{
			if (!rflagico) //prevent a crash if we haven't cached our team graphics yet
			{
				rflagico = W_CachePatchName("RFLAGICO", PU_HUDGFX);
				bflagico = W_CachePatchName("BFLAGICO", PU_HUDGFX);
				rmatcico = W_CachePatchName("RMATCICO", PU_HUDGFX);
				bmatcico = W_CachePatchName("BMATCICO", PU_HUDGFX);
			}

			data.match.redflag = (intertype == int_ctf) ? rflagico : rmatcico;
			data.match.blueflag = (intertype == int_ctf) ? bflagico : bmatcico;
		}
		/* FALLTHRU */
		case int_match:
		case int_race:
		case int_comp:
		{
			if (intertype == int_match || intertype == int_race)
			{
				// get RESULT header
				data.match.result = W_CachePatchName("RESULT", PU_PATCH);
			}

			// get background tile
			bgtile = W_CachePatchName("SRB2BACK", PU_PATCH);
			break;
		}
		case int_none:
		default:
			break;
	}
}

//
// Y_ConsiderScreenBuffer
//
// Can we copy the current screen to a buffer?
//
void Y_ConsiderScreenBuffer(void)
{
	if (gameaction != ga_completed)
		return;

	if (y_buffer == NULL)
		y_buffer = Z_Calloc(sizeof(y_buffer_t), PU_STATIC, NULL);
	else
		return;

	y_buffer->source_width = vid.width;
	y_buffer->source_height = vid.height;
	y_buffer->source_bpp = vid.bpp;
	y_buffer->source_rowbytes = vid.rowbytes;
	y_buffer->source_picture = ZZ_Alloc(y_buffer->source_width*vid.bpp * y_buffer->source_height);
	VID_BlitLinearScreen(screens[1], y_buffer->source_picture, vid.width*vid.bpp, vid.height, vid.width*vid.bpp, vid.rowbytes);

	// Make the rescaled screen buffer
	Y_RescaleScreenBuffer();
}

//
// Y_RescaleScreenBuffer
//
// Write the rescaled source picture, to the destination picture that has the current screen's resolutions.
//
static void Y_RescaleScreenBuffer(void)
{
	INT32 sx, sy; // source
	INT32 dx, dy; // dest
	fixed_t scalefac, yscalefac;
	fixed_t rowfrac, colfrac;
	UINT8 *dest;

	// Who knows?
	if (y_buffer == NULL)
		return;

	if (y_buffer->target_picture)
		Z_Free(y_buffer->target_picture);

	y_buffer->target_width = vid.width;
	y_buffer->target_height = vid.height;
	y_buffer->target_rowbytes = vid.rowbytes;
	y_buffer->target_bpp = vid.bpp;
	y_buffer->target_picture = ZZ_Alloc(y_buffer->target_width*vid.bpp * y_buffer->target_height);
	dest = y_buffer->target_picture;

	scalefac = FixedDiv(y_buffer->target_width*FRACUNIT, y_buffer->source_width*FRACUNIT);
	yscalefac = FixedDiv(y_buffer->target_height*FRACUNIT, y_buffer->source_height*FRACUNIT);

	rowfrac = FixedDiv(FRACUNIT, yscalefac);
	colfrac = FixedDiv(FRACUNIT, scalefac);

	for (sy = 0, dy = 0; sy < (y_buffer->source_height << FRACBITS) && dy < y_buffer->target_height; sy += rowfrac, dy++)
		for (sx = 0, dx = 0; sx < (y_buffer->source_width << FRACBITS) && dx < y_buffer->target_width; sx += colfrac, dx += y_buffer->target_bpp)
			dest[(dy * y_buffer->target_rowbytes) + dx] = y_buffer->source_picture[((sy>>FRACBITS) * y_buffer->source_width) + (sx>>FRACBITS)];
}

//
// Y_CleanupScreenBuffer
//
// Free all related memory.
//
void Y_CleanupScreenBuffer(void)
{
	// Who knows?
	if (y_buffer == NULL)
		return;

	if (y_buffer->target_picture)
		Z_Free(y_buffer->target_picture);

	if (y_buffer->source_picture)
		Z_Free(y_buffer->source_picture);

	Z_Free(y_buffer);
	y_buffer = NULL;
}

//
// Y_IntermissionDrawer
//
// Called by D_Display. Nothing is modified here; all it does is draw.
// Neat concept, huh?
//
void Y_IntermissionDrawer(void)
{
	// Bonus loops
	INT32 i;

	if (intertype == int_none || rendermode == render_none)
		return;

	if (useinterpic)
		V_DrawScaledPatch(0, 0, 0, interpic);
	else if (!usetile)
	{
		if (rendermode == render_soft && usebuffer)
		{
			// no y_buffer
			if (y_buffer == NULL)
				VID_BlitLinearScreen(screens[1], screens[0], vid.width*vid.bpp, vid.height, vid.width*vid.bpp, vid.rowbytes);
			else
			{
				// Maybe the resolution changed?
				if ((y_buffer->target_width != vid.width) || (y_buffer->target_height != vid.height))
					Y_RescaleScreenBuffer();

				// Blit the already-scaled screen buffer to the current screen
				VID_BlitLinearScreen(y_buffer->target_picture, screens[0], vid.width*vid.bpp, vid.height, vid.width*vid.bpp, vid.rowbytes);
			}
		}
#ifdef HWRENDER
		else if (rendermode != render_soft && usebuffer)
			HWR_DrawIntermissionBG();
#endif
		else if (bgpatch)
		{
			fixed_t hs = vid.width  * FRACUNIT / BASEVIDWIDTH;
			fixed_t vs = vid.height * FRACUNIT / BASEVIDHEIGHT;
			V_DrawStretchyFixedPatch(0, 0, hs, vs, V_NOSCALEPATCH, bgpatch, NULL);
		}
	}
	else if (bgtile)
		V_DrawPatchFill(bgtile);

	if (renderisnewtic)
	{
		LUA_HUD_ClearDrawList(luahuddrawlist_intermission);
		LUA_HUDHOOK(intermission, luahuddrawlist_intermission);
	}
	LUA_HUD_DrawList(luahuddrawlist_intermission);

	if (!LUA_HudEnabled(hud_intermissiontally))
		goto skiptallydrawer;

	if (intertype == int_coop)
	{
		INT32 bonusy;

		if (gottoken) // first to be behind everything else
			Y_IntermissionTokenDrawer();

		if (!splitscreen)  // there's not enough room in splitscreen, don't even bother trying!
		{
			// draw score
			ST_DrawPatchFromHud(HUD_SCORE, sboscore);
			ST_DrawNumFromHud(HUD_SCORENUM, data.coop.score);

			// draw time
			ST_DrawPatchFromHud(HUD_TIME, sbotime);
			if (cv_timetic.value == 3)
				ST_DrawNumFromHud(HUD_SECONDS, data.coop.tics);
			else
			{
				INT32 seconds, minutes, tictrn;

				seconds = G_TicsToSeconds(data.coop.tics);
				minutes = G_TicsToMinutes(data.coop.tics, true);
				tictrn  = G_TicsToCentiseconds(data.coop.tics);

				ST_DrawNumFromHud(HUD_MINUTES, minutes); // Minutes
				ST_DrawPatchFromHud(HUD_TIMECOLON, sbocolon); // Colon
				ST_DrawPadNumFromHud(HUD_SECONDS, seconds, 2); // Seconds

				if (cv_timetic.value == 1 || cv_timetic.value == 2 || modeattacking || marathonmode)
				{
					ST_DrawPatchFromHud(HUD_TIMETICCOLON, sboperiod); // Period
					ST_DrawPadNumFromHud(HUD_TICS, tictrn, 2); // Tics
				}
			}
		}

		if (LUA_HudEnabled(hud_intermissiontitletext))
		{
			// draw the "got through act" lines and act number
			V_DrawLevelTitle(data.coop.passedx1, 49, 0, data.coop.passed1);
			{
				INT32 h = V_LevelNameHeight(data.coop.passed2);
				V_DrawLevelTitle(data.coop.passedx2, 49+h+2, 0, data.coop.passed2);

				if (data.coop.actnum)
					V_DrawLevelActNum(244, 42+h, 0, data.coop.actnum);
			}
		}

		bonusy = 150;
		// Total
		V_DrawScaledPatch(152, bonusy, 0, data.coop.ptotal);
		V_DrawTallNum(BASEVIDWIDTH - 68, bonusy + 1, 0, data.coop.total);
		bonusy -= (3*(tallnum[0]->height)/2) + 1;

		// Draw bonuses
		for (i = 3; i >= 0; --i)
		{
			if (data.coop.bonuses[i].display)
			{
				V_DrawScaledPatch(152, bonusy, 0, data.coop.bonuspatches[i]);
				V_DrawTallNum(BASEVIDWIDTH - 68, bonusy + 1, 0, data.coop.bonuses[i].points);
			}
			bonusy -= (3*(tallnum[0]->height)/2) + 1;
		}
	}
	else if (intertype == int_spec)
	{
		static tic_t animatetic = 0;
		INT32 ttheight = 16;
		INT32 xoffset1 = 0; // Line 1 x offset
		INT32 xoffset2 = 0; // Line 2 x offset
		INT32 xoffset3 = 0; // Line 3 x offset
		INT32 xoffset4 = 0; // Line 4 x offset
		INT32 xoffset5 = 0; // Line 5 x offset
		INT32 xoffset6 = 0; // Line 6 x offset
		UINT8 drawsection = 0;

		if (gottoken) // first to be behind everything else
			Y_IntermissionTokenDrawer();

		// draw the header
		if (intertic <= 2*TICRATE)
			animatetic = 0;
		else if (!animatetic && data.spec.bonuses[0].points == 0 && data.spec.bonuses[1].points == 0 && data.spec.passed3[0] != '\0')
			animatetic = intertic + TICRATE;

		if (animatetic && (tic_t)intertic >= animatetic)
		{
			const INT32 scradjust = (vid.width/vid.dup)>>3; // 40 for BASEVIDWIDTH
			INT32 animatetimer = (intertic - animatetic);
			if (animatetimer <= 16)
			{
				xoffset1 = -(animatetimer      * scradjust);
				xoffset2 = -((animatetimer- 2) * scradjust);
				xoffset3 = -((animatetimer- 4) * scradjust);
				xoffset4 = -((animatetimer- 6) * scradjust);
				xoffset5 = -((animatetimer- 8) * scradjust);
				xoffset6 = -((animatetimer-10) * scradjust);
				if (xoffset2 > 0) xoffset2 = 0;
				if (xoffset3 > 0) xoffset3 = 0;
				if (xoffset4 > 0) xoffset4 = 0;
				if (xoffset5 > 0) xoffset5 = 0;
				if (xoffset6 > 0) xoffset6 = 0;
			}
			else if (animatetimer < 34)
			{
				drawsection = 1;
				xoffset1 = (24-animatetimer) * scradjust;
				xoffset2 = (26-animatetimer) * scradjust;
				xoffset3 = (28-animatetimer) * scradjust;
				xoffset4 = (30-animatetimer) * scradjust;
				xoffset5 = (32-animatetimer) * scradjust;
				xoffset6 = (34-animatetimer) * scradjust;
				if (xoffset1 < 0) xoffset1 = 0;
				if (xoffset2 < 0) xoffset2 = 0;
				if (xoffset3 < 0) xoffset3 = 0;
				if (xoffset4 < 0) xoffset4 = 0;
				if (xoffset5 < 0) xoffset5 = 0;
			}
			else
			{
				drawsection = 1;
				if (animatetimer == 32)
					S_StartSound(NULL, sfx_s3k68);
			}
		}

		if (drawsection == 1)
		{
			if (LUA_HudEnabled(hud_intermissiontitletext))
			{
				const char *ringtext = "\x82" "50 rings, no shield";
				const char *tut1text = "\x82" "press " "\x80" "spin";
				const char *tut2text = "\x82" "mid-" "\x80" "jump";
				ttheight = 8;
				V_DrawLevelTitle(data.spec.passedx1 + xoffset1, ttheight, 0, data.spec.passed1);
				ttheight += V_LevelNameHeight(data.spec.passed3) + 2;
				V_DrawLevelTitle(data.spec.passedx3 + xoffset2, ttheight, 0, data.spec.passed3);
				ttheight += V_LevelNameHeight(data.spec.passed4) + 2;
				V_DrawLevelTitle(data.spec.passedx4 + xoffset3, ttheight, 0, data.spec.passed4);

				ttheight = 108;
				V_DrawLevelTitle(BASEVIDWIDTH/2 + xoffset4 - (V_LevelNameWidth(ringtext)/2), ttheight, 0, ringtext);
				ttheight += V_LevelNameHeight(tut1text) + 2;
				V_DrawLevelTitle(BASEVIDWIDTH/2 + xoffset5 - (V_LevelNameWidth(tut1text)/2), ttheight, 0, tut1text);
				ttheight += V_LevelNameHeight(tut2text) + 2;
				V_DrawLevelTitle(BASEVIDWIDTH/2 + xoffset6 - (V_LevelNameWidth(tut2text)/2), ttheight, 0, tut2text);
			}
		}
		else
		{
			INT32 yoffset = 0;

			if (LUA_HudEnabled(hud_intermissiontitletext))
			{
				if (data.spec.passed1[0] != '\0')
				{
					ttheight = 24;
					V_DrawLevelTitle(data.spec.passedx1 + xoffset1, ttheight, 0, data.spec.passed1);
					ttheight += V_LevelNameHeight(data.spec.passed2) + 2;
					V_DrawLevelTitle(data.spec.passedx2 + xoffset2, ttheight, 0, data.spec.passed2);
				}
				else
				{
					ttheight = 24 + (V_LevelNameHeight(data.spec.passed2)/2) + 2;
					V_DrawLevelTitle(data.spec.passedx2 + xoffset1, ttheight, 0, data.spec.passed2);
				}
			}

			V_DrawScaledPatch(152 + xoffset3, 108, 0, data.spec.bonuspatches[0]);
			V_DrawTallNum(BASEVIDWIDTH + xoffset3 - 68, 109, 0, data.spec.bonuses[0].points);
			if (data.spec.bonuses[1].display)
			{
				V_DrawScaledPatch(152 + xoffset4, 124, 0, data.spec.bonuspatches[1]);
				V_DrawTallNum(BASEVIDWIDTH + xoffset4 - 68, 125, 0, data.spec.bonuses[1].points);
				yoffset = 16;
				// hack; pass the buck along...
				xoffset4 = xoffset5;
				xoffset5 = xoffset6;
			}
			V_DrawScaledPatch(152 + xoffset4, 124+yoffset, 0, data.spec.pscore);
			V_DrawTallNum(BASEVIDWIDTH + xoffset4 - 68, 125+yoffset, 0, data.spec.score);

			// Draw continues!
			if (continuesInSession /* && (data.spec.continues & 0x80) */) // Always draw when continues are a thing
			{
				UINT8 continues = data.spec.continues & 0x7F;

				V_DrawScaledPatch(152 + xoffset5, 150+yoffset, 0, data.spec.pcontinues);
				if (continues > 5)
				{
					INT32 leftx = (continues >= 10) ? 216 : 224;
					V_DrawContinueIcon(leftx + xoffset5, 162+yoffset, 0, *data.spec.playerchar, *data.spec.playercolor);
					V_DrawScaledPatch(leftx + xoffset5 + 12, 160+yoffset, 0, stlivex);
					if (!((data.spec.continues & 0x80) && !(endtic < 0 || intertic%20 < 10)))
						V_DrawRightAlignedString(252 + xoffset5, 158+yoffset, 0,
							va("%d",(((data.spec.continues & 0x80) && (endtic < 0)) ? continues-1 : continues)));
				}
				else
				{
					for (i = 0; i < continues; ++i)
					{
						if ((data.spec.continues & 0x80) && i == continues-1 && (endtic < 0 || intertic%20 < 10))
							break;
						V_DrawContinueIcon(246 + xoffset5 - (i*20), 162+yoffset, 0, *data.spec.playerchar, *data.spec.playercolor);
					}
				}
			}
		}

		// draw the emeralds
		if (LUA_HudEnabled(hud_intermissionemeralds))
		{
			boolean drawthistic = !(ALL7EMERALDS(emeralds) && (intertic & 1));
			INT32 emeraldx = 152 - 3*28;
			INT32 em = P_GetNextEmerald();

			if (em == 7)
			{
				if (!stagefailed)
				{
					fixed_t adjust = 2*(FINESINE(FixedAngle((intertic + 1)<<(FRACBITS-4)) & FINEMASK));
					V_DrawFixedPatch(152<<FRACBITS, (74<<FRACBITS) - adjust, FRACUNIT, 0, emeraldpics[0][em], NULL);
				}
			}
			else if (em < 7)
			{
				if (drawthistic)
					for (i = 0; i < 7; ++i)
					{
						if ((i != em) && (emeralds & (1 << i)))
							V_DrawScaledPatch(emeraldx, 74, 0, emeraldpics[0][i]);
						emeraldx += 28;
					}

				emeraldx = 152 + (em-3)*28;

				if (intertic > 1)
				{
					if (stagefailed && data.spec.emeraldy < (vid.height/vid.dup)+16)
					{
						emeraldx += intertic - 6;
					}

					if (drawthistic)
						V_DrawScaledPatch(emeraldx, data.spec.emeraldy, 0, emeraldpics[0][em]);
				}
			}
		}
	}
	else if (intertype == int_match || intertype == int_race)
	{
		INT32 j = 0;
		INT32 x = 4;
		INT32 y = 48;
		char name[MAXPLAYERNAME+1];
		char strtime[10];

		// draw the header
		V_DrawScaledPatch(112, 2, 0, data.match.result);

		// draw the level name
		V_DrawCenteredString(BASEVIDWIDTH/2, 20, 0, data.match.levelstring);
		V_DrawFill(4, 42, 312, 1, 0);

		if (data.match.numplayers > 9)
		{
			V_DrawFill(160, 32, 1, 152, 0);

			if (intertype == int_race)
				V_DrawRightAlignedString(x+152, 32, V_YELLOWMAP, "TIME");
			else
				V_DrawRightAlignedString(x+152, 32, V_YELLOWMAP, "SCORE");

			V_DrawCenteredString(x+(BASEVIDWIDTH/2)+6, 32, V_YELLOWMAP, "#");
			V_DrawString(x+(BASEVIDWIDTH/2)+36, 32, V_YELLOWMAP, "NAME");
		}

		V_DrawCenteredString(x+6, 32, V_YELLOWMAP, "#");
		V_DrawString(x+36, 32, V_YELLOWMAP, "NAME");

		if (intertype == int_race)
			V_DrawRightAlignedString(x+(BASEVIDWIDTH/2)+152, 32, V_YELLOWMAP, "TIME");
		else
			V_DrawRightAlignedString(x+(BASEVIDWIDTH/2)+152, 32, V_YELLOWMAP, "SCORE");

		for (i = 0; i < data.match.numplayers; i++)
		{
			if (data.match.spectator[i])
				continue; //Ignore spectators.

			V_DrawCenteredString(x+6, y, 0, va("%d", j+1));
			j++; //We skip spectators, but not their number.

			if (playeringame[data.match.num[i]])
			{
				// Draw the back sprite, it looks ugly if we don't
				V_DrawSmallScaledPatch(x+16, y-4, 0, livesback);

				if (data.match.color[i] == 0)
					V_DrawSmallScaledPatch(x+16, y-4, 0,faceprefix[*data.match.character[i]]);
				else
				{
					UINT8 *colormap = R_GetTranslationColormap(*data.match.character[i], *data.match.color[i], GTC_CACHE);
					V_DrawSmallMappedPatch(x+16, y-4, 0,faceprefix[*data.match.character[i]], colormap);
				}

				if (data.match.numplayers > 9)
				{
					if (intertype == int_race)
						strlcpy(name, data.match.name[i], 8);
					else
						strlcpy(name, data.match.name[i], 9);
				}
				else
					STRBUFCPY(name, data.match.name[i]);

				V_DrawString(x+36, y, V_ALLOWLOWERCASE, name);

				if (data.match.numplayers > 9)
				{
					if (intertype == int_match)
						V_DrawRightAlignedString(x+152, y, 0, va("%i", data.match.scores[i]));
					else if (intertype == int_race)
					{
						if (players[data.match.num[i]].pflags & PF_GAMETYPEOVER)
							snprintf(strtime, sizeof strtime, "DNF");
						else
							snprintf(strtime, sizeof strtime,
								"%i:%02i.%02i",
								G_TicsToMinutes(data.match.scores[i], true),
								G_TicsToSeconds(data.match.scores[i]), G_TicsToCentiseconds(data.match.scores[i]));

						strtime[sizeof strtime - 1] = '\0';
						V_DrawRightAlignedString(x+152, y, 0, strtime);
					}
				}
				else
				{
					if (intertype == int_match)
						V_DrawRightAlignedString(x+152+BASEVIDWIDTH/2, y, 0, va("%u", data.match.scores[i]));
					else if (intertype == int_race)
					{
						if (players[data.match.num[i]].pflags & PF_GAMETYPEOVER)
							snprintf(strtime, sizeof strtime, "DNF");
						else
							snprintf(strtime, sizeof strtime, "%i:%02i.%02i", G_TicsToMinutes(data.match.scores[i], true),
									G_TicsToSeconds(data.match.scores[i]), G_TicsToCentiseconds(data.match.scores[i]));

						strtime[sizeof strtime - 1] = '\0';

						V_DrawRightAlignedString(x+152+BASEVIDWIDTH/2, y, 0, strtime);
					}
				}
			}

			y += 16;

			if (y > 176)
			{
				y = 48;
				x += BASEVIDWIDTH/2;
			}
		}
	}
	else if (intertype == int_ctf || intertype == int_teammatch)
	{
		INT32 x = 4, y = 0;
		INT32 redplayers = 0, blueplayers = 0;
		char name[MAXPLAYERNAME+1];

		// Show the team flags and the team score at the top instead of "RESULTS"
		V_DrawSmallScaledPatch(128 - (data.match.blueflag->width / 4), 2, 0, data.match.blueflag);
		V_DrawCenteredString(128, 16, 0, va("%u", bluescore));

		V_DrawSmallScaledPatch(192 - (data.match.redflag->width / 4), 2, 0, data.match.redflag);
		V_DrawCenteredString(192, 16, 0, va("%u", redscore));

		// draw the level name
		V_DrawCenteredString(BASEVIDWIDTH/2, 24, 0, data.match.levelstring);
		V_DrawFill(4, 42, 312, 1, 0);

		//vert. line
		V_DrawFill(160, 32, 1, 152, 0);

		//strings at the top of the list
		V_DrawCenteredString(x+6, 32, V_YELLOWMAP, "#");
		V_DrawCenteredString(x+(BASEVIDWIDTH/2)+6, 32, V_YELLOWMAP, "#");

		V_DrawString(x+36, 32, V_YELLOWMAP, "NAME");
		V_DrawString(x+(BASEVIDWIDTH/2)+36, 32, V_YELLOWMAP, "NAME");

		V_DrawRightAlignedString(x+152, 32, V_YELLOWMAP, "SCORE");
		V_DrawRightAlignedString(x+(BASEVIDWIDTH/2)+152, 32, V_YELLOWMAP, "SCORE");

		for (i = 0; i < data.match.numplayers; i++)
		{
			if (playeringame[data.match.num[i]] && !(data.match.spectator[i]))
			{
				UINT8 *colormap = R_GetTranslationColormap(*data.match.character[i], *data.match.color[i], GTC_CACHE);

				if (data.match.ctfteam[i] == 1) //red
				{
					if (redplayers++ > 9)
						continue;
					x = 4 + (BASEVIDWIDTH/2);
					y = (redplayers * 16) + 32;
					V_DrawCenteredString(x+6, y, 0, va("%d", redplayers));
				}
				else if (data.match.ctfteam[i] == 2) //blue
				{
					if (blueplayers++ > 9)
						continue;
					x = 4;
					y = (blueplayers * 16) + 32;
					V_DrawCenteredString(x+6, y, 0, va("%d", blueplayers));
				}
				else
					continue;

				// Draw the back sprite, it looks ugly if we don't
				V_DrawSmallScaledPatch(x+16, y-4, 0, livesback);

				//color is ALWAYS going to be 6/7 here, no need to check if it's nonzero.
				V_DrawSmallMappedPatch(x+16, y-4, 0,faceprefix[*data.match.character[i]], colormap);

				strlcpy(name, data.match.name[i], 9);

				V_DrawString(x+36, y, V_ALLOWLOWERCASE, name);

				V_DrawRightAlignedString(x+152, y, 0, va("%u", data.match.scores[i]));
			}
		}
	}
	else if (intertype == int_comp)
	{
		INT32 x = 4;
		INT32 y = 48;
		UINT32 ptime, pring, pmaxring, pmonitor, pscore;
		char sstrtime[10];

		// draw the level name
		V_DrawCenteredString(BASEVIDWIDTH/2, 8, 0, data.competition.levelstring);
		V_DrawFill(4, 42, 312, 1, 0);

		V_DrawCenteredString(x+6, 32, V_YELLOWMAP, "#");
		V_DrawString(x+36, 32, V_YELLOWMAP, "NAME");
		// Time
		V_DrawRightAlignedString(x+160, 32, V_YELLOWMAP, "TIME");

		// Rings
		V_DrawThinString(x+168, 32, V_YELLOWMAP, "RING");

		// Total rings
		V_DrawThinString(x+191, 24, V_YELLOWMAP, "TOTAL");
		V_DrawThinString(x+196, 32, V_YELLOWMAP, "RING");

		// Monitors
		V_DrawThinString(x+223, 24, V_YELLOWMAP, "ITEM");
		V_DrawThinString(x+229, 32, V_YELLOWMAP, "BOX");

		// Score
		V_DrawRightAlignedString(x+288, 32, V_YELLOWMAP, "SCORE");

		// Points
		V_DrawRightAlignedString(x+312, 32, V_YELLOWMAP, "PT");

		for (i = 0; i < data.competition.numplayers; i++)
		{
			ptime = (data.competition.times[i] & ~0x80000000);
			pring = (data.competition.rings[i] & ~0x80000000);
			pmaxring = (data.competition.maxrings[i] & ~0x80000000);
			pmonitor = (data.competition.monitors[i] & ~0x80000000);
			pscore = (data.competition.scores[i] & ~0x80000000);

			V_DrawCenteredString(x+6, y, 0, va("%d", i+1));

			if (playeringame[data.competition.num[i]])
			{
				// Draw the back sprite, it looks ugly if we don't
				V_DrawSmallScaledPatch(x+16, y-4, 0, livesback);

				if (data.competition.color[i] == 0)
					V_DrawSmallScaledPatch(x+16, y-4, 0,faceprefix[*data.competition.character[i]]);
				else
				{
					UINT8 *colormap = R_GetTranslationColormap(*data.competition.character[i], *data.competition.color[i], GTC_CACHE);
					V_DrawSmallMappedPatch(x+16, y-4, 0,faceprefix[*data.competition.character[i]], colormap);
				}

				// already constrained to 8 characters
				V_DrawString(x+36, y, V_ALLOWLOWERCASE, data.competition.name[i]);

				if (players[data.competition.num[i]].pflags & PF_GAMETYPEOVER)
					snprintf(sstrtime, sizeof sstrtime, "Time Over");
				else if (players[data.competition.num[i]].lives <= 0)
					snprintf(sstrtime, sizeof sstrtime, "Game Over");
				else
					snprintf(sstrtime, sizeof sstrtime, "%i:%02i.%02i", G_TicsToMinutes(ptime, true),
							G_TicsToSeconds(ptime), G_TicsToCentiseconds(ptime));

				sstrtime[sizeof sstrtime - 1] = '\0';
				// Time
				V_DrawRightAlignedThinString(x+160, y, ((data.competition.times[i] & 0x80000000) ? V_YELLOWMAP : 0), sstrtime);
				// Rings
				V_DrawRightAlignedThinString(x+188, y, V_MONOSPACE|((data.competition.rings[i] & 0x80000000) ? V_YELLOWMAP : 0), va("%u", pring));
				// Total rings
				V_DrawRightAlignedThinString(x+216, y, V_MONOSPACE|((data.competition.maxrings[i] & 0x80000000) ? V_YELLOWMAP : 0), va("%u", pmaxring));
				// Monitors
				V_DrawRightAlignedThinString(x+244, y, V_MONOSPACE|((data.competition.monitors[i] & 0x80000000) ? V_YELLOWMAP : 0), va("%u", pmonitor));
				// Score
				V_DrawRightAlignedThinString(x+288, y, V_MONOSPACE|((data.competition.scores[i] & 0x80000000) ? V_YELLOWMAP : 0), va("%u", pscore));
				// Final Points
				V_DrawRightAlignedString(x+312, y, V_YELLOWMAP, va("%d", data.competition.points[i]));
			}

			y += 16;

			if (y > 176)
				break;
		}
	}

skiptallydrawer:
	if (!LUA_HudEnabled(hud_intermissionmessages))
		return;

	if (timer)
		V_DrawCenteredString(BASEVIDWIDTH/2, 188, V_YELLOWMAP,
			va("start in %d seconds", timer/TICRATE));

	// Make it obvious that scrambling is happening next round.
	if (cv_scrambleonchange.value && cv_teamscramble.value && (intertic/TICRATE % 2 == 0))
		V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, V_YELLOWMAP, M_GetText("Teams will be scrambled next round!"));
}

//
// Y_Ticker
//
// Manages fake score tally for single player end of act, and decides when intermission is over.
//
void Y_Ticker(void)
{
	if (intertype == int_none)
		return;

	// Check for pause or menu up in single player
	if (paused || P_AutoPause())
		return;

	LUA_HookBool(stagefailed, HOOK(IntermissionThinker));

	intertic++;

	// Team scramble code for team match and CTF.
	// Don't do this if we're going to automatically scramble teams next round.
	if (G_GametypeHasTeams() && cv_teamscramble.value && !cv_scrambleonchange.value && server)
	{
		// If we run out of time in intermission, the beauty is that
		// the P_Ticker() team scramble code will pick it up.
		if ((intertic % (TICRATE/7)) == 0)
			P_DoTeamscrambling();
	}

	// multiplayer uses timer (based on cv_inttime)
	if (timer)
	{
		if (!--timer)
		{
			Y_EndIntermission();
			G_AfterIntermission();
			return;
		}
	}
	// single player is hardcoded to go away after awhile
	else if (intertic == endtic)
	{
		Y_EndIntermission();
		G_AfterIntermission();
		return;
	}

	if (endtic != -1)
		return; // tally is done

	if (intertype == int_coop) // coop or single player, normal level
	{
		INT32 i;
		UINT32 oldscore = data.coop.score;
		boolean skip = (marathonmode) ? true : false;
		boolean anybonuses = false;

		if (!intertic) // first time only
		{
			if (mapheaderinfo[gamemap-1]->musinterfadeout
#ifdef _WIN32
				// can't fade midi due to win32 volume hack
				&& S_MusicType() != MU_MID
#endif
			)
				S_FadeOutStopMusic(mapheaderinfo[gamemap-1]->musinterfadeout);
			else if (mapheaderinfo[gamemap-1]->musintername[0] && S_MusicExists(mapheaderinfo[gamemap-1]->musintername, !midi_disabled, !digital_disabled))
				S_ChangeMusicInternal(mapheaderinfo[gamemap-1]->musintername, false); // don't loop it
			else
				S_ChangeMusicInternal("_clear", false); // don't loop it
			tallydonetic = -1;
		}

		if (intertic < TICRATE) // one second pause before tally begins
			return;

		for (i = 0; i < MAXPLAYERS; i++)
			if (playeringame[i] && (players[i].cmd.buttons & BT_SPIN))
				skip = true;

		// bonuses count down by 222 each tic
		for (i = 0; i < 4; ++i)
		{
			if (!data.coop.bonuses[i].points)
				continue;

			data.coop.bonuses[i].points -= 222;
			data.coop.total += 222;
			data.coop.score += 222;
			if (data.coop.bonuses[i].points < 0 || skip == true) // too far?
			{
				data.coop.score += data.coop.bonuses[i].points;
				data.coop.total += data.coop.bonuses[i].points;
				data.coop.bonuses[i].points = 0;
			}
			if (data.coop.score > MAXSCORE)
				data.coop.score = MAXSCORE;
			if (data.coop.bonuses[i].points > 0)
				anybonuses = true;
		}

		if (!anybonuses)
		{
			tallydonetic = intertic;
			endtic = intertic + 3*TICRATE; // 3 second pause after end of tally
			S_StartSound(NULL, (gottoken ? sfx_token : sfx_chchng)); // cha-ching!

			// Update when done with tally
			if (!demoplayback)
			{
				M_SilentUpdateUnlockablesAndEmblems(serverGamedata);

				if (M_UpdateUnlockablesAndExtraEmblems(clientGamedata))
					S_StartSound(NULL, sfx_s3k68);

				G_SaveGameData(clientGamedata);
			}
		}
		else if (!(intertic & 1))
			S_StartSound(NULL, sfx_ptally); // tally sound effect

		if (data.coop.gotlife > 0 && (skip == true || data.coop.score % 50000 < oldscore % 50000)) // just passed a 50000 point mark
		{
			// lives are already added since tally is fake, but play the music
			P_PlayLivesJingle(NULL);
			--data.coop.gotlife;
		}
	}
	else if (intertype == int_spec) // coop or single player, special stage
	{
		INT32 i;
		UINT32 oldscore = data.spec.score;
		boolean skip = (marathonmode) ? true : false, super = false, anybonuses = false;

		if (!intertic) // first time only
		{
			if (mapheaderinfo[gamemap-1]->musinterfadeout
#ifdef _WIN32
				// can't fade midi due to win32 volume hack
				&& S_MusicType() != MU_MID
#endif
			)
				S_FadeOutStopMusic(mapheaderinfo[gamemap-1]->musinterfadeout);
			else if (mapheaderinfo[gamemap-1]->musintername[0] && S_MusicExists(mapheaderinfo[gamemap-1]->musintername, !midi_disabled, !digital_disabled))
				S_ChangeMusicInternal(mapheaderinfo[gamemap-1]->musintername, false); // don't loop it
			else
				S_ChangeMusicInternal(stagefailed ? "CHFAIL" : "CHPASS", false); // don't loop it
			tallydonetic = -1;
		}

		// emerald bounce
		if (dedicated || !LUA_HudEnabled(hud_intermissionemeralds))
		{
			// dedicated servers don't need this, especially since it crashes when stagefailed
			// also skip this if Lua disabled intermission emeralds, so it doesn't play sounds
		}
		else if (intertic <= 1)
		{
			data.spec.emeraldbounces = 0;
			data.spec.emeraldmomy = 20;
			data.spec.emeraldy = -40;
		}
		else if (P_GetNextEmerald() < 7)
		{
			if (!stagefailed)
			{
				if (data.spec.emeraldbounces < 3)
				{
					data.spec.emeraldy += (++data.spec.emeraldmomy);
					if (data.spec.emeraldy > 74)
					{
						S_StartSound(NULL, sfx_tink); // tink
						data.spec.emeraldbounces++;
						data.spec.emeraldmomy = -(data.spec.emeraldmomy/2);
						data.spec.emeraldy = 74;
					}
				}
			}
			else
			{
				if (data.spec.emeraldy < (vid.height/vid.dup)+16)
				{
					data.spec.emeraldy += (++data.spec.emeraldmomy);
				}
				if (data.spec.emeraldbounces < 1 && data.spec.emeraldy > 74)
				{
					S_StartSound(NULL, sfx_shldls); // nope
					data.spec.emeraldbounces++;
					data.spec.emeraldmomy = -(data.spec.emeraldmomy/2);
					data.spec.emeraldy = 74;
				}
			}
		}

		if (intertic < 2*TICRATE) // TWO second pause before tally begins, thank you mazmazz
			return;

		for (i = 0; i < MAXPLAYERS; i++)
			if (playeringame[i])
			{
				if (players[i].cmd.buttons & BT_SPIN)
					skip = true;
				if (players[i].charflags & SF_SUPER)
					super = true;
			}

		if (tallydonetic != -1 && ((data.spec.continues & 0x80) || (super && ALL7EMERALDS(emeralds))))
		{
			if ((intertic - tallydonetic) > (3*TICRATE)/2)
			{
				endtic = intertic + 4*TICRATE; // 4 second pause after end of tally
				if (data.spec.continues & 0x80)
					S_StartSound(NULL, sfx_s3kac); // bingly-bingly-bing!

			}
			return;
		}

		// bonuses count down by 222 each tic
		for (i = 0; i < 2; ++i)
		{
			if (!data.spec.bonuses[i].points)
				continue;

			data.spec.bonuses[i].points -= 222;
			data.spec.score += 222;
			if (data.spec.bonuses[i].points < 0 || skip == true) // too far?
			{
				data.spec.score += data.spec.bonuses[i].points;
				data.spec.bonuses[i].points = 0;
			}
			if (data.spec.score > MAXSCORE)
				data.spec.score = MAXSCORE;
			if (data.spec.bonuses[i].points > 0)
				anybonuses = true;
		}

		if (!anybonuses)
		{
			tallydonetic = intertic;
			if (!((data.spec.continues & 0x80) || (super && ALL7EMERALDS(emeralds)))) // don't set endtic yet!
				endtic = intertic + 4*TICRATE; // 4 second pause after end of tally

			S_StartSound(NULL, (gottoken ? sfx_token : sfx_chchng)); // cha-ching!

			// Update when done with tally
			if (!demoplayback)
			{
				M_SilentUpdateUnlockablesAndEmblems(serverGamedata);

				if (M_UpdateUnlockablesAndExtraEmblems(clientGamedata))
					S_StartSound(NULL, sfx_s3k68);

				G_SaveGameData(clientGamedata);
			}
		}
		else if (!(intertic & 1))
			S_StartSound(NULL, sfx_ptally); // tally sound effect

		if (data.spec.gotlife > 0 && (skip == true || data.spec.score % 50000 < oldscore % 50000)) // just passed a 50000 point mark
		{
			// lives are already added since tally is fake, but play the music
			P_PlayLivesJingle(NULL);
			--data.spec.gotlife;
		}
	}
	else if (intertype == int_match || intertype == int_ctf || intertype == int_teammatch) // match
	{
		if (!intertic) // first time only
			S_ChangeMusicInternal("_inter", true); // loop it

		// If a player has left or joined, recalculate scores.
		if (data.match.numplayers != D_NumPlayers())
			Y_CalculateMatchWinners();
	}
	else if (intertype == int_race || intertype == int_comp) // race
	{
		if (!intertic) // first time only
			S_ChangeMusicInternal("_inter", true); // loop it

		// Don't bother recalcing for race. It doesn't make as much sense.
	}
}

//
// Y_DetermineIntermissionType
//
// Determines the intermission type from the current gametype.
//
void Y_DetermineIntermissionType(void)
{
	// set to int_none initially
	intertype = int_none;

	if (intermissiontypes[gametype] != int_none)
		intertype = intermissiontypes[gametype];
	else if (gametype == GT_COOP)
		intertype = (G_IsSpecialStage(gamemap)) ? int_spec : int_coop;
	else if (gametype == GT_TEAMMATCH)
		intertype = int_teammatch;
	else if (gametype == GT_MATCH
	 || gametype == GT_TAG
	 || gametype == GT_HIDEANDSEEK)
		intertype = int_match;
	else if (gametype == GT_RACE)
		intertype = int_race;
	else if (gametype == GT_COMPETITION)
		intertype = int_comp;
	else if (gametype == GT_CTF)
		intertype = int_ctf;
}

//
// Y_StartIntermission
//
// Called by G_DoCompleted. Sets up data for intermission drawer/ticker.
//
//
void Y_StartIntermission(void)
{
	intertic = -1;

#ifdef PARANOIA
	if (endtic != -1)
		I_Error("endtic is dirty");
#endif

	if (!multiplayer)
	{
		timer = 0;
		intertype = (G_IsSpecialStage(gamemap)) ? int_spec : int_coop;
	}
	else
	{
		if (cv_inttime.value == 0 && ((intertype == int_coop) || (intertype == int_spec)))
			timer = 0;
		else
		{
			timer = cv_inttime.value*TICRATE;

			if (!timer)
				timer = 1;
		}
	}

	// We couldn't display the intermission even if we wanted to.
	// But we still need to give the players their score bonuses, dummy.
	//if (dedicated) return;

	// This should always exist, but just in case...
	if(!mapheaderinfo[prevmap])
		P_AllocMapHeader(prevmap);

	switch (intertype)
	{
		case int_coop: // coop or single player, normal level
		{
			// award time and ring bonuses
			Y_AwardCoopBonuses();

			// setup time data
			data.coop.tics = players[consoleplayer].realtime;

			// get act number
			data.coop.actnum = mapheaderinfo[gamemap-1]->actnum;

			// grab an interscreen if appropriate
			if (mapheaderinfo[gamemap-1]->interscreen[0] != '#')
			{
				useinterpic = true;
				usebuffer = false;
			}
			else
			{
				useinterpic = false;
#ifdef HWRENDER
				if (rendermode == render_opengl)
					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
#endif
			}
			usetile = false;

			// set up the "got through act" message according to skin name
			if (stagefailed)
			{
				strcpy(data.coop.passed1, mapheaderinfo[gamemap-1]->lvlttl);

				if (mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE)
				{
					data.spec.passed2[0] = '\0';
				}
				else
				{
					strcpy(data.coop.passed2, "Zone");
				}
			}
			else
			{
				// too long so just show "YOU GOT THROUGH THE ACT"
				if (strlen(skins[players[consoleplayer].skin]->realname) > 13)
				{
					strcpy(data.coop.passed1, "you got");
					strcpy(data.coop.passed2, (mapheaderinfo[gamemap-1]->actnum) ? "through act" : "through the act");
				}
				// long enough that "X GOT" won't fit so use "X PASSED THE ACT"
				else if (strlen(skins[players[consoleplayer].skin]->realname) > 8)
				{
					strcpy(data.coop.passed1, skins[players[consoleplayer].skin]->realname);
					strcpy(data.coop.passed2, (mapheaderinfo[gamemap-1]->actnum) ? "passed act" : "passed the act");
				}
				// length is okay for normal use
				else
				{
					snprintf(data.coop.passed1, sizeof data.coop.passed1, "%s got",
						skins[players[consoleplayer].skin]->realname);
					strcpy(data.coop.passed2, (mapheaderinfo[gamemap-1]->actnum) ? "through act" : "through the act");
				}
			}

			// set X positions
			if (mapheaderinfo[gamemap-1]->actnum)
			{
				data.coop.passedx1 = 62 + (176 - V_LevelNameWidth(data.coop.passed1))/2;
				data.coop.passedx2 = 62 + (176 - V_LevelNameWidth(data.coop.passed2))/2;
			}
			else
			{
				data.coop.passedx1 = (BASEVIDWIDTH - V_LevelNameWidth(data.coop.passed1))/2;
				data.coop.passedx2 = (BASEVIDWIDTH - V_LevelNameWidth(data.coop.passed2))/2;
			}
			// The above value is not precalculated because it needs only be computed once
			// at the start of intermission, and precalculating it would preclude mods
			// changing the font to one of a slightly different width.

			if ((stagefailed) && !(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE))
			{
				// Bit of a hack, offset so that the "Zone" text is right aligned like title cards.
				data.coop.passedx2 = (data.coop.passedx1 + V_LevelNameWidth(data.coop.passed1)) - V_LevelNameWidth(data.coop.passed2);
			}

			break;
		}

		case int_spec: // coop or single player, special stage
		{
			// give out ring bonuses
			Y_AwardSpecialStageBonus();

			// grab an interscreen if appropriate
			if (mapheaderinfo[gamemap-1]->interscreen[0] != '#')
				useinterpic = true;
			else
				useinterpic = false;

			// tile if using the default background
			usetile = !useinterpic;

			// get special stage specific patches
/*			if (!stagefailed && ALL7EMERALDS(emeralds))
			{
				data.spec.cemerald = W_CachePatchName("GOTEMALL", PU_PATCH);
				data.spec.headx = 70;
				data.spec.nowsuper = players[consoleplayer].skin
					? NULL : W_CachePatchName("NOWSUPER", PU_PATCH);
			}
			else
			{
				data.spec.cemerald = W_CachePatchName("CEMERALD", PU_PATCH);
				data.spec.headx = 48;
				data.spec.nowsuper = NULL;
			} */

			// Super form stuff (normally blank)
			data.spec.passed3[0] = '\0';
			data.spec.passed4[0] = '\0';

			// set up the "got through act" message according to skin name
			if (stagefailed)
			{
				strcpy(data.spec.passed2, "Special Stage");
				data.spec.passed1[0] = '\0';
			}
			else if (ALL7EMERALDS(emeralds))
			{
				snprintf(data.spec.passed1,
					sizeof data.spec.passed1, "%s",
					skins[players[consoleplayer].skin]->realname);
				data.spec.passed1[sizeof data.spec.passed1 - 1] = '\0';
				strcpy(data.spec.passed2, "got them all!");

				if (players[consoleplayer].charflags & SF_SUPER)
				{
					strcpy(data.spec.passed3, "can now become");
					if (strlen(skins[players[consoleplayer].skin]->supername) > 20) //too long, use generic
						strcpy(data.spec.passed4, "their super form");
					else
						strcpy(data.spec.passed4, skins[players[consoleplayer].skin]->supername);
				}
			}
			else
			{
				if (strlen(skins[players[consoleplayer].skin]->realname) <= SKINNAMESIZE-5)
				{
					snprintf(data.spec.passed1,
						sizeof data.spec.passed1, "%s got",
						skins[players[consoleplayer].skin]->realname);
					data.spec.passed1[sizeof data.spec.passed1 - 1] = '\0';
				}
				else
					strcpy(data.spec.passed1, "You got");
				strcpy(data.spec.passed2, "a Chaos Emerald");
				if (P_GetNextEmerald() > 6)
				{
					data.spec.passed2[15] = '?';
					data.spec.passed2[16] = '\0';
				}
			}
			data.spec.passedx1 = (BASEVIDWIDTH - V_LevelNameWidth(data.spec.passed1))/2;
			data.spec.passedx2 = (BASEVIDWIDTH - V_LevelNameWidth(data.spec.passed2))/2;
			data.spec.passedx3 = (BASEVIDWIDTH - V_LevelNameWidth(data.spec.passed3))/2;
			data.spec.passedx4 = (BASEVIDWIDTH - V_LevelNameWidth(data.spec.passed4))/2;
			break;
		}

		case int_match:
		{
			// Calculate who won
			Y_CalculateMatchWinners();

			// set up the levelstring
			if (mapheaderinfo[prevmap]->actnum)
				snprintf(data.match.levelstring,
					sizeof data.match.levelstring,
					"%.32s * %d *",
					mapheaderinfo[prevmap]->lvlttl, mapheaderinfo[prevmap]->actnum);
			else
				snprintf(data.match.levelstring,
					sizeof data.match.levelstring,
					"* %.32s *",
					mapheaderinfo[prevmap]->lvlttl);

			data.match.levelstring[sizeof data.match.levelstring - 1] = '\0';

			usetile = true;
			useinterpic = false;
			break;
		}

		case int_race: // (time-only race)
		{
			// Calculate who won
			Y_CalculateTimeRaceWinners();

			// set up the levelstring
			if (mapheaderinfo[prevmap]->actnum)
				snprintf(data.match.levelstring,
					sizeof data.match.levelstring,
					"%.32s * %d *",
					mapheaderinfo[prevmap]->lvlttl, mapheaderinfo[prevmap]->actnum);
			else
				snprintf(data.match.levelstring,
					sizeof data.match.levelstring,
					"* %.32s *",
					mapheaderinfo[prevmap]->lvlttl);

			data.match.levelstring[sizeof data.match.levelstring - 1] = '\0';

			usetile = true;
			useinterpic = false;
			break;
		}

		case int_teammatch:
		case int_ctf:
		{
			// Calculate who won
			Y_CalculateMatchWinners();

			// set up the levelstring
			if (mapheaderinfo[prevmap]->actnum)
				snprintf(data.match.levelstring,
					sizeof data.match.levelstring,
					"%.32s * %d *",
					mapheaderinfo[prevmap]->lvlttl, mapheaderinfo[prevmap]->actnum);
			else
				snprintf(data.match.levelstring,
					sizeof data.match.levelstring,
					"* %.32s *",
					mapheaderinfo[prevmap]->lvlttl);

			data.match.levelstring[sizeof data.match.levelstring - 1] = '\0';

			usetile = true;
			useinterpic = false;
			break;
		}

		case int_comp: // classic (full race)
		{
			// find out who won
			Y_CalculateCompetitionWinners();

			// set up the levelstring
			if (mapheaderinfo[prevmap]->actnum)
				snprintf(data.competition.levelstring,
					sizeof data.competition.levelstring,
					"%.32s * %d *",
					mapheaderinfo[prevmap]->lvlttl, mapheaderinfo[prevmap]->actnum);
			else
				snprintf(data.competition.levelstring,
					sizeof data.competition.levelstring,
					"* %.32s *",
					mapheaderinfo[prevmap]->lvlttl);

			data.competition.levelstring[sizeof data.competition.levelstring - 1] = '\0';

			usetile = true;
			useinterpic = false;
			break;
		}

		case int_none:
		default:
			break;
	}

	LUA_HUD_DestroyDrawList(luahuddrawlist_intermission);
	luahuddrawlist_intermission = LUA_HUD_CreateDrawList();
}

//
// Y_CalculateMatchWinners
//
static void Y_CalculateMatchWinners(void)
{
	INT32 i, j;
	boolean completed[MAXPLAYERS];

	// Initialize variables
	memset(data.match.ctfteam, 0, sizeof (data.match.ctfteam));
	memset(data.match.scores, 0, sizeof (data.match.scores));
	memset(data.match.color, 0, sizeof (data.match.color));
	memset(data.match.character, 0, sizeof (data.match.character));
	memset(data.match.spectator, 0, sizeof (data.match.spectator));
	memset(completed, 0, sizeof (completed));
	data.match.numplayers = 0;
	i = j = 0;

	for (j = 0; j < MAXPLAYERS; j++)
	{
		if (!playeringame[j])
			continue;

		for (i = 0; i < MAXPLAYERS; i++)
		{
			if (!playeringame[i])
				continue;

			if (players[i].score >= data.match.scores[data.match.numplayers] && completed[i] == false)
			{
				data.match.ctfteam[data.match.numplayers] = players[i].ctfteam;
				data.match.scores[data.match.numplayers] = players[i].score;
				data.match.color[data.match.numplayers] = &players[i].skincolor;
				if (data.match.ctfteam[data.match.numplayers] == 1) // red team
					data.match.color[data.match.numplayers] = &skincolor_redteam;

				if (data.match.ctfteam[data.match.numplayers] == 2) // blue team
					data.match.color[data.match.numplayers] = &skincolor_blueteam;

				data.match.character[data.match.numplayers] = &players[i].skin;
				data.match.name[data.match.numplayers] = player_names[i];
				data.match.spectator[data.match.numplayers] = players[i].spectator;
				data.match.num[data.match.numplayers] = i;
			}
		}
		completed[data.match.num[data.match.numplayers]] = true;
		data.match.numplayers++;
	}
}

//
// Y_CalculateTimeRaceWinners
//
static void Y_CalculateTimeRaceWinners(void)
{
	INT32 i, j;
	boolean completed[MAXPLAYERS];

	// Initialize variables

	for (i = 0; i < MAXPLAYERS; i++)
		data.match.scores[i] = INT32_MAX;

	memset(data.match.color, 0, sizeof (data.match.color));
	memset(data.match.character, 0, sizeof (data.match.character));
	memset(data.match.spectator, 0, sizeof (data.match.spectator));
	memset(completed, 0, sizeof (completed));
	data.match.numplayers = 0;
	i = j = 0;

	for (j = 0; j < MAXPLAYERS; j++)
	{
		if (!playeringame[j])
			continue;

		for (i = 0; i < MAXPLAYERS; i++)
		{
			if (!playeringame[i])
				continue;

			if (players[i].realtime <= data.match.scores[data.match.numplayers] && completed[i] == false)
			{
				data.match.scores[data.match.numplayers] = players[i].realtime;
				data.match.color[data.match.numplayers] = &players[i].skincolor;
				data.match.character[data.match.numplayers] = &players[i].skin;
				data.match.name[data.match.numplayers] = player_names[i];
				data.match.num[data.match.numplayers] = i;
			}
		}
		completed[data.match.num[data.match.numplayers]] = true;
		data.match.numplayers++;
	}
}

//
// Y_CalculateCompetitionWinners
//
static void Y_CalculateCompetitionWinners(void)
{
	INT32 i, j;
	boolean bestat[5];
	boolean completed[MAXPLAYERS];
	INT32 winner; // shortcut

	UINT32 points[MAXPLAYERS];
	UINT32 times[MAXPLAYERS];
	UINT32 rings[MAXPLAYERS];
	UINT32 maxrings[MAXPLAYERS];
	UINT32 monitors[MAXPLAYERS];
	UINT32 scores[MAXPLAYERS];
	char tempname[9];

	memset(data.competition.points, 0, sizeof (data.competition.points));
	memset(points, 0, sizeof (points));
	memset(completed, 0, sizeof (completed));

	// Award points.
	for (i = 0; i < MAXPLAYERS; i++)
	{
		if (!playeringame[i])
			continue;

		for (j = 0; j < 5; j++)
			bestat[j] = true;

		if ((players[i].pflags & PF_GAMETYPEOVER) || players[i].lives <= 0)
			players[i].rings = 0;

		times[i]    = players[i].realtime;
		rings[i]    = (UINT32)max(players[i].rings, 0);
		maxrings[i] = (UINT32)players[i].totalring;
		monitors[i] = (UINT32)players[i].numboxes;
		scores[i]   = (UINT32)min(players[i].score, MAXSCORE);

		for (j = 0; j < MAXPLAYERS; j++)
		{
			if (!playeringame[j] || j == i)
				continue;

			if (players[i].realtime <= players[j].realtime)
				points[i]++;
			else
				bestat[0] = false;

			if (max(players[i].rings, 0) >= max(players[j].rings, 0))
				points[i]++;
			else
				bestat[1] = false;

			if (players[i].totalring >= players[j].totalring)
				points[i]++;
			else
				bestat[2] = false;

			if (players[i].numboxes >= players[j].numboxes)
				points[i]++;
			else
				bestat[3] = false;

			if (players[i].score >= players[j].score)
				points[i]++;
			else
				bestat[4] = false;
		}

		// Highlight best scores
		if (bestat[0])
			times[i] |= 0x80000000;
		if (bestat[1])
			rings[i] |= 0x80000000;
		if (bestat[2])
			maxrings[i] |= 0x80000000;
		if (bestat[3])
			monitors[i] |= 0x80000000;
		if (bestat[4])
			scores[i] |= 0x80000000;
	}

	// Now we go through and set the data.competition struct properly
	data.competition.numplayers = 0;
	for (i = 0; i < MAXPLAYERS; i++)
	{
		if (!playeringame[i])
			continue;

		winner = 0;

		for (j = 0; j < MAXPLAYERS; j++)
		{
			if (!playeringame[j])
				continue;

			if (points[j] >= data.competition.points[data.competition.numplayers] && completed[j] == false)
			{
				data.competition.points[data.competition.numplayers] = points[j];
				data.competition.num[data.competition.numplayers] = winner = j;
			}
		}
		// We know this person won this spot, now let's set everything appropriately
		data.competition.times[data.competition.numplayers] = times[winner];
		data.competition.rings[data.competition.numplayers] = rings[winner];
		data.competition.maxrings[data.competition.numplayers] = maxrings[winner];
		data.competition.monitors[data.competition.numplayers] = monitors[winner];
		data.competition.scores[data.competition.numplayers] = scores[winner];

		strncpy(tempname, player_names[winner], 8);
		tempname[8] = '\0';
		strncpy(data.competition.name[data.competition.numplayers], tempname, 9);

		data.competition.color[data.competition.numplayers] = &players[winner].skincolor;
		data.competition.character[data.competition.numplayers] = &players[winner].skin;

		completed[winner] = true;
		data.competition.numplayers++;
	}
}

// ============
// COOP BONUSES
// ============

//
// Y_SetNullBonus
// No bonus in this slot, but we need to set some things anyway.
//
static void Y_SetNullBonus(player_t *player, y_bonus_t *bstruct)
{
	(void)player;
	memset(bstruct, 0, sizeof(y_bonus_t));
}

//
// Y_SetTimeBonus
//
static void Y_SetTimeBonus(player_t *player, y_bonus_t *bstruct)
{
	INT32 secs, bonus;

	strncpy(bstruct->patch, "YB_TIME", sizeof(bstruct->patch));
	bstruct->display = true;

	if (stagefailed == true)
	{
		// Time Bonus would be very easy to cheese by failing immediately.
		bonus = 0;
	}
	else
	{
		// calculate time bonus
		secs = player->realtime / TICRATE;
		if      (secs <  30) /*   :30 */ bonus = 50000;
		else if (secs <  60) /*  1:00 */ bonus = 10000;
		else if (secs <  90) /*  1:30 */ bonus = 5000;
		else if (secs < 120) /*  2:00 */ bonus = 4000;
		else if (secs < 180) /*  3:00 */ bonus = 3000;
		else if (secs < 240) /*  4:00 */ bonus = 2000;
		else if (secs < 300) /*  5:00 */ bonus = 1000;
		else if (secs < 360) /*  6:00 */ bonus = 500;
		else if (secs < 420) /*  7:00 */ bonus = 400;
		else if (secs < 480) /*  8:00 */ bonus = 300;
		else if (secs < 540) /*  9:00 */ bonus = 200;
		else if (secs < 600) /* 10:00 */ bonus = 100;
		else  /* TIME TAKEN: TOO LONG */ bonus = 0;
	}

	bstruct->points = bonus;
}

//
// Y_SetRingBonus
//
static void Y_SetRingBonus(player_t *player, y_bonus_t *bstruct)
{
	strncpy(bstruct->patch, "YB_RING", sizeof(bstruct->patch));
	bstruct->display = true;
	bstruct->points = max(0, (player->rings) * 100);
}

//
// Y_SetNightsBonus
//
static void Y_SetNightsBonus(player_t *player, y_bonus_t *bstruct)
{
	strncpy(bstruct->patch, "YB_NIGHT", sizeof(bstruct->patch));
	bstruct->display = true;
	bstruct->points = player->totalmarescore;
}

//
// Y_SetLapBonus
//
static void Y_SetLapBonus(player_t *player, y_bonus_t *bstruct)
{
	strncpy(bstruct->patch, "YB_LAP", sizeof(bstruct->patch));
	bstruct->display = true;
	bstruct->points = max(0, player->totalmarebonuslap * 1000);
}

//
// Y_SetLinkBonus
//
static void Y_SetLinkBonus(player_t *player, y_bonus_t *bstruct)
{
	strncpy(bstruct->patch, "YB_LINK", sizeof(bstruct->patch));
	bstruct->display = true;
	bstruct->points = max(0, (player->maxlink - 1) * 100);
}

//
// Y_SetGuardBonus
//
static void Y_SetGuardBonus(player_t *player, y_bonus_t *bstruct)
{
	INT32 bonus;
	strncpy(bstruct->patch, "YB_GUARD", sizeof(bstruct->patch));
	bstruct->display = true;

	if (stagefailed == true)
	{
		// "No-hit" runs would be very easy to cheese by failing immediately.
		bonus = 0;
	}
	else
	{
		if      (player->timeshit == 0) bonus = 10000;
		else if (player->timeshit == 1) bonus = 5000;
		else if (player->timeshit == 2) bonus = 1000;
		else if (player->timeshit == 3) bonus = 500;
		else if (player->timeshit == 4) bonus = 100;
		else                            bonus = 0;
	}

	bstruct->points = bonus;
}

//
// Y_SetPerfectBonus
//
static void Y_SetPerfectBonus(player_t *player, y_bonus_t *bstruct)
{
	INT32 i;

	(void)player;
	memset(bstruct, 0, sizeof(y_bonus_t));
	strncpy(bstruct->patch, "YB_PERFE", sizeof(bstruct->patch));

	if (intertype != int_coop || data.coop.gotperfbonus == -1)
	{
		INT32 sharedringtotal = 0;
		for (i = 0; i < MAXPLAYERS; i++)
		{
			if (!playeringame[i]) continue;
			sharedringtotal += players[i].rings;
		}
		if (!sharedringtotal || nummaprings == -1 || sharedringtotal < nummaprings)
			bstruct->display = false;
		else
		{
			bstruct->display = true;
			bstruct->points = 50000;
		}
	}
	if (intertype != int_coop)
		return;

	data.coop.gotperfbonus = (bstruct->display ? 1 : 0);
}

static void Y_SetSpecialRingBonus(player_t *player, y_bonus_t *bstruct)
{
	INT32 i, sharedringtotal = 0;

	(void)player;
	strncpy(bstruct->patch, "YB_RING", sizeof(bstruct->patch));
	bstruct->display = true;

	for (i = 0; i < MAXPLAYERS; i++)
	{
		if (!playeringame[i]) continue;
		sharedringtotal += players[i].rings;
	}
	bstruct->points = max(0, (sharedringtotal) * 100);
}

// This list can be extended in the future with SOC/Lua, perhaps.
typedef void (*bonus_f)(player_t *, y_bonus_t *);
bonus_f bonuses_list[6][4] = {
	{
		Y_SetNullBonus,
		Y_SetNullBonus,
		Y_SetNullBonus,
		Y_SetNullBonus,
	},
	{
		Y_SetNullBonus,
		Y_SetTimeBonus,
		Y_SetRingBonus,
		Y_SetPerfectBonus,
	},
	{
		Y_SetNullBonus,
		Y_SetGuardBonus,
		Y_SetRingBonus,
		Y_SetNullBonus,
	},
	{
		Y_SetNullBonus,
		Y_SetGuardBonus,
		Y_SetRingBonus,
		Y_SetPerfectBonus,
	},
	{
		Y_SetNullBonus,
		Y_SetNightsBonus,
		Y_SetLapBonus,
		Y_SetNullBonus,
	},
	{
		Y_SetNullBonus,
		Y_SetLinkBonus,
		Y_SetLapBonus,
		Y_SetNullBonus,
	},
};



//
// Y_AwardCoopBonuses
//
// Awards the time and ring bonuses.
//
static void Y_AwardCoopBonuses(void)
{
	INT32 i, j, bonusnum, oldscore, ptlives;
	y_bonus_t localbonuses[4];

	// set score/total first
	data.coop.total = (players[consoleplayer].pflags & PF_FINISHED) ? players[consoleplayer].recordscore : 0;
	data.coop.score = players[consoleplayer].score;
	data.coop.gotperfbonus = -1;
	memset(data.coop.bonuses, 0, sizeof(data.coop.bonuses));
	memset(data.coop.bonuspatches, 0, sizeof(data.coop.bonuspatches));

	for (i = 0; i < MAXPLAYERS; ++i)
	{
		if (!playeringame[i] || players[i].lives < 1 || players[i].bot == BOT_2PAI || players[i].bot == BOT_2PHUMAN) // not active, game over or tails bot
			bonusnum = 0; // all null
		else
			bonusnum = mapheaderinfo[prevmap]->bonustype + 1; // -1 is none

		oldscore = players[i].score;

		for (j = 0; j < 4; ++j) // Set bonuses
		{
			//Set the bonus, but only if we actually finished
			if (players[i].pflags & PF_FINISHED)
				(bonuses_list[bonusnum][j])(&players[i], &localbonuses[j]);
			else
				Y_SetNullBonus(&players[i], &localbonuses[j]);

			players[i].score += localbonuses[j].points;
			if (players[i].score > MAXSCORE)
				players[i].score = MAXSCORE;
			players[i].recordscore += localbonuses[j].points;
			if (players[i].recordscore > MAXSCORE)
				players[i].recordscore = MAXSCORE;
		}

		ptlives = min(
			(INT32)((!ultimatemode && !modeattacking && players[i].lives != INFLIVES) ? max((INT32)((players[i].score/50000) - (oldscore/50000)), (INT32)0) : 0),
			(INT32)(mapheaderinfo[prevmap]->maxbonuslives < 0 ? INT32_MAX : mapheaderinfo[prevmap]->maxbonuslives));
		if (ptlives)
			P_GivePlayerLives(&players[i], ptlives);

		if (i == consoleplayer)
		{
			data.coop.gotlife = (((netgame || multiplayer) && G_GametypeUsesCoopLives() && cv_cooplives.value == 0) ? 0 : ptlives);
			M_Memcpy(&data.coop.bonuses, &localbonuses, sizeof(data.coop.bonuses));
		}
	}

	// Just in case the perfect bonus wasn't checked.
	if (data.coop.gotperfbonus < 0)
		data.coop.gotperfbonus = 0;
}

//
// Y_AwardSpecialStageBonus
//
// Gives a ring bonus only.
static void Y_AwardSpecialStageBonus(void)
{
	INT32 i, oldscore, ptlives;
	y_bonus_t localbonuses[2];

	data.spec.score = players[consoleplayer].score;
	memset(data.spec.bonuses, 0, sizeof(data.spec.bonuses));
	memset(data.spec.bonuspatches, 0, sizeof(data.spec.bonuspatches));

	for (i = 0; i < MAXPLAYERS; i++)
	{
		oldscore = players[i].score;

		if (!playeringame[i] || players[i].lives < 1 || players[i].bot == BOT_2PAI || players[i].bot == BOT_2PHUMAN) // not active, game over or tails bot
		{
			Y_SetNullBonus(&players[i], &localbonuses[0]);
			Y_SetNullBonus(&players[i], &localbonuses[1]);
		}
		else if (maptol & TOL_NIGHTS) // NiGHTS bonus score instead of Rings
		{
			Y_SetNightsBonus(&players[i], &localbonuses[0]);
			Y_SetNullBonus(&players[i], &localbonuses[1]);
		}
		else
		{
			Y_SetSpecialRingBonus(&players[i], &localbonuses[0]);
			Y_SetPerfectBonus(&players[i], &localbonuses[1]);
		}
		players[i].score += localbonuses[0].points;
		players[i].score += localbonuses[1].points;
		if (players[i].score > MAXSCORE)
			players[i].score = MAXSCORE;
		players[i].recordscore += localbonuses[0].points;
		players[i].recordscore += localbonuses[1].points;
		if (players[i].recordscore > MAXSCORE)
			players[i].recordscore = MAXSCORE;

		// grant extra lives right away since tally is faked
		ptlives = min(
			(INT32)((!ultimatemode && !modeattacking && players[i].lives != INFLIVES) ? max((INT32)((players[i].score/50000) - (oldscore/50000)), (INT32)0) : 0),
			(INT32)(mapheaderinfo[prevmap]->maxbonuslives < 0 ? INT32_MAX : mapheaderinfo[prevmap]->maxbonuslives));
		P_GivePlayerLives(&players[i], ptlives);

		if (i == consoleplayer)
		{
			data.spec.gotlife = (((netgame || multiplayer) && G_GametypeUsesCoopLives() && cv_cooplives.value == 0) ? 0 : ptlives);
			M_Memcpy(&data.spec.bonuses, &localbonuses, sizeof(data.spec.bonuses));

			// Continues related
			data.spec.continues = min(players[i].continues, 8);
			if (players[i].gotcontinue)
				data.spec.continues |= 0x80;
			data.spec.playercolor = &players[i].skincolor;
			data.spec.playerchar = &players[i].skin;
		}
	}
}

// ======

//
// Y_EndIntermission
//
void Y_EndIntermission(void)
{
	if (!dedicated)
		Y_UnloadData();

	endtic = -1;
	intertype = int_none;
	usebuffer = false;
}

#define UNLOAD(x) if (x) {Patch_Free(x);} x = NULL;

//
// Y_UnloadData
//
static void Y_UnloadData(void)
{
	// unload the background patches
	UNLOAD(bgpatch);
	UNLOAD(bgtile);
	UNLOAD(interpic);

	switch (intertype)
	{
		case int_coop:
			// unload the coop and single player patches
			UNLOAD(data.coop.bonuspatches[3]);
			UNLOAD(data.coop.bonuspatches[2]);
			UNLOAD(data.coop.bonuspatches[1]);
			UNLOAD(data.coop.bonuspatches[0]);
			UNLOAD(data.coop.ptotal);
			break;
		case int_spec:
			// unload the special stage patches
			//UNLOAD(data.spec.cemerald);
			//UNLOAD(data.spec.nowsuper);
			UNLOAD(data.spec.bonuspatches[1]);
			UNLOAD(data.spec.bonuspatches[0]);
			UNLOAD(data.spec.pscore);
			UNLOAD(data.spec.pcontinues);
			break;
		case int_match:
		case int_race:
			UNLOAD(data.match.result);
			break;
		case int_ctf:
			UNLOAD(data.match.blueflag);
			UNLOAD(data.match.redflag);
			break;
		default:
			//without this default,
			//int_none, int_tag, int_chaos, and int_comp
			//are not handled
			break;
	}
}
