mirror of
				https://github.com/KartKrewDev/RingRacers.git
				synced 2025-10-30 08:01:28 +00:00 
			
		
		
		
	Make default Emerald pickup in extra Sealed Star a fake SA2 yellow-gold Chaos Emerald See merge request KartKrew/Kart!2268
		
			
				
	
	
		
			1455 lines
		
	
	
	
		
			32 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1455 lines
		
	
	
	
		
			32 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
// DR. ROBOTNIK'S RING RACERS
 | 
						|
//-----------------------------------------------------------------------------
 | 
						|
// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour
 | 
						|
// Copyright (C) 2024 by Kart Krew
 | 
						|
//
 | 
						|
// 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  k_podium.c
 | 
						|
/// \brief Grand Prix podium cutscene
 | 
						|
 | 
						|
#include "k_podium.h"
 | 
						|
 | 
						|
#include "doomdef.h"
 | 
						|
#include "d_main.h"
 | 
						|
#include "d_netcmd.h"
 | 
						|
#include "f_finale.h"
 | 
						|
#include "g_game.h"
 | 
						|
#include "hu_stuff.h"
 | 
						|
#include "r_local.h"
 | 
						|
#include "s_sound.h"
 | 
						|
#include "i_time.h"
 | 
						|
#include "i_video.h"
 | 
						|
#include "v_video.h"
 | 
						|
#include "w_wad.h"
 | 
						|
#include "z_zone.h"
 | 
						|
#include "i_system.h"
 | 
						|
#include "i_threads.h"
 | 
						|
#include "dehacked.h"
 | 
						|
#include "g_input.h"
 | 
						|
#include "console.h"
 | 
						|
#include "m_random.h"
 | 
						|
#include "m_misc.h" // moviemode functionality
 | 
						|
#include "y_inter.h"
 | 
						|
#include "m_cond.h"
 | 
						|
#include "p_local.h"
 | 
						|
#include "p_saveg.h"
 | 
						|
#include "p_setup.h"
 | 
						|
#include "st_stuff.h" // hud hiding
 | 
						|
#include "fastcmp.h"
 | 
						|
 | 
						|
#include "lua_hud.h"
 | 
						|
#include "lua_hook.h"
 | 
						|
 | 
						|
#include "k_menu.h"
 | 
						|
#include "k_grandprix.h"
 | 
						|
#include "k_rank.h"
 | 
						|
 | 
						|
#include "v_draw.hpp"
 | 
						|
 | 
						|
#include "k_hud.h"
 | 
						|
 | 
						|
#include <string>
 | 
						|
 | 
						|
typedef enum
 | 
						|
{
 | 
						|
	PODIUM_ST_CONGRATS_SLIDEIN,
 | 
						|
	PODIUM_ST_CONGRATS_SLIDEUP,
 | 
						|
	PODIUM_ST_DATA_SLIDEIN,
 | 
						|
	PODIUM_ST_DATA_PAUSE,
 | 
						|
	PODIUM_ST_LEVEL_APPEAR,
 | 
						|
	PODIUM_ST_LEVEL_PAUSE,
 | 
						|
	PODIUM_ST_TOTALS_SLIDEIN,
 | 
						|
	PODIUM_ST_TOTALS_PAUSE,
 | 
						|
	PODIUM_ST_GRADE_APPEAR,
 | 
						|
	PODIUM_ST_GRADE_VOICE,
 | 
						|
	PODIUM_ST_DONE,
 | 
						|
	PODIUM_ST_EXIT,
 | 
						|
} podium_state_e;
 | 
						|
 | 
						|
static struct podiumData_s
 | 
						|
{
 | 
						|
	boolean ranking;
 | 
						|
	gpRank_t rank;
 | 
						|
	gp_rank_e grade;
 | 
						|
 | 
						|
	podium_state_e state;
 | 
						|
	INT32 delay;
 | 
						|
	INT32 transition, transitionTime;
 | 
						|
 | 
						|
	UINT8 displayLevels;
 | 
						|
	sfxenum_t gradeVoice;
 | 
						|
 | 
						|
	cupheader_t *cup;
 | 
						|
	UINT8 emeraldnum;
 | 
						|
 | 
						|
	boolean fastForward;
 | 
						|
 | 
						|
	char header[64];
 | 
						|
 | 
						|
	void Init(void);
 | 
						|
	void NextLevel(void);
 | 
						|
	void Tick(void);
 | 
						|
	void Draw(void);
 | 
						|
} g_podiumData;
 | 
						|
 | 
						|
void podiumData_s::Init(void)
 | 
						|
{
 | 
						|
	fastForward = false;
 | 
						|
 | 
						|
	if (grandprixinfo.cup != nullptr)
 | 
						|
	{
 | 
						|
		rank = grandprixinfo.rank;
 | 
						|
		cup = grandprixinfo.cup;
 | 
						|
		emeraldnum = cup->emeraldnum;
 | 
						|
	}
 | 
						|
	else
 | 
						|
	{
 | 
						|
		// construct fake rank for testing podium
 | 
						|
		// directly from the editor
 | 
						|
 | 
						|
		size_t cupID = M_RandomRange(0, numkartcupheaders-1);
 | 
						|
		cup = kartcupheaders;
 | 
						|
		for (size_t i = 0; i < cupID; i++)
 | 
						|
		{
 | 
						|
			if (cup == nullptr)
 | 
						|
			{
 | 
						|
				break;
 | 
						|
			}
 | 
						|
 | 
						|
			cup = cup->next;
 | 
						|
		}
 | 
						|
		emeraldnum = 0;
 | 
						|
 | 
						|
		memset(&rank, 0, sizeof(gpRank_t));
 | 
						|
		rank.skin = players[consoleplayer].skin;
 | 
						|
 | 
						|
		rank.numPlayers = std::clamp<UINT8>(M_RandomRange(0, MAXSPLITSCREENPLAYERS + 1), 1, MAXSPLITSCREENPLAYERS);
 | 
						|
		rank.totalPlayers = K_GetGPPlayerCount(rank.numPlayers);
 | 
						|
 | 
						|
		rank.position = M_RandomRange(1, 4);
 | 
						|
 | 
						|
		rank.continuesUsed = M_RandomRange(0, 3);
 | 
						|
 | 
						|
		// Fake totals
 | 
						|
		rank.numLevels = 8;
 | 
						|
 | 
						|
		constexpr INT32 numRaces = 5;
 | 
						|
		for (INT32 i = 0; i < rank.numPlayers; i++)
 | 
						|
		{
 | 
						|
			rank.totalPoints += numRaces * K_CalculateGPRankPoints(i + 1, rank.totalPlayers);
 | 
						|
		}
 | 
						|
		rank.totalRings = numRaces * rank.numPlayers * 20;
 | 
						|
 | 
						|
		// Randomized winnings
 | 
						|
		INT32 rgs = 0;
 | 
						|
		INT32 laps = 0;
 | 
						|
		INT32 tlaps = 0;
 | 
						|
		INT32 prs = 0;
 | 
						|
		INT32 tprs = 0;
 | 
						|
 | 
						|
		rank.winPoints = M_RandomRange(0, rank.totalPoints);
 | 
						|
 | 
						|
		for (INT32 i = 0; i < rank.numLevels; i++)
 | 
						|
		{
 | 
						|
			gpRank_level_t *const lvl = &rank.levels[i];
 | 
						|
			UINT8 specialWinner = 0;
 | 
						|
			UINT16 pprs = 0;
 | 
						|
			UINT16 plaps = 0;
 | 
						|
 | 
						|
			lvl->id = M_RandomRange(4, nummapheaders);
 | 
						|
 | 
						|
			lvl->event = GPEVENT_NONE;
 | 
						|
			switch (i)
 | 
						|
			{
 | 
						|
				case 2:
 | 
						|
				case 5:
 | 
						|
				{
 | 
						|
					lvl->event = GPEVENT_BONUS;
 | 
						|
					lvl->totalPrisons = M_RandomRange(1, 10);
 | 
						|
					tprs += lvl->totalPrisons;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
				case 7:
 | 
						|
				{
 | 
						|
					lvl->event = GPEVENT_SPECIAL;
 | 
						|
					specialWinner = M_RandomRange(0, rank.numPlayers);
 | 
						|
					break;
 | 
						|
				}
 | 
						|
				default:
 | 
						|
				{
 | 
						|
					lvl->totalLapPoints = M_RandomRange(2, 5) * 2;
 | 
						|
					tlaps += lvl->totalLapPoints;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			lvl->time = M_RandomRange(50*TICRATE, 210*TICRATE);
 | 
						|
 | 
						|
			for (INT32 j = 0; j < rank.numPlayers; j++)
 | 
						|
			{
 | 
						|
				gpRank_level_perplayer_t *const dta = &lvl->perPlayer[j];
 | 
						|
 | 
						|
				dta->position = M_RandomRange(1, rank.totalPlayers);
 | 
						|
 | 
						|
				if (lvl->event == GPEVENT_NONE)
 | 
						|
				{
 | 
						|
					dta->rings = M_RandomRange(0, 20);
 | 
						|
					rgs += dta->rings;
 | 
						|
 | 
						|
					dta->lapPoints = M_RandomRange(0, lvl->totalLapPoints);
 | 
						|
					plaps = std::max(plaps, dta->lapPoints);
 | 
						|
				}
 | 
						|
 | 
						|
				if (lvl->event == GPEVENT_BONUS)
 | 
						|
				{
 | 
						|
					dta->prisons = M_RandomRange(0, lvl->totalPrisons);
 | 
						|
					pprs = std::max(pprs, dta->prisons);
 | 
						|
				}
 | 
						|
 | 
						|
				if (lvl->event == GPEVENT_SPECIAL)
 | 
						|
				{
 | 
						|
					dta->gotSpecialPrize = (j+1 == specialWinner);
 | 
						|
					dta->grade = GRADE_E;
 | 
						|
					if (dta->gotSpecialPrize)
 | 
						|
					{
 | 
						|
						rank.specialWon = true;
 | 
						|
					}
 | 
						|
				}
 | 
						|
				else
 | 
						|
				{
 | 
						|
					dta->grade = static_cast<gp_rank_e>(M_RandomRange(static_cast<INT32>(GRADE_E), static_cast<INT32>(GRADE_A)));
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			laps += plaps;
 | 
						|
			prs += pprs;
 | 
						|
		}
 | 
						|
 | 
						|
		rank.rings = rgs;
 | 
						|
		rank.laps = laps;
 | 
						|
		rank.totalLaps = tlaps;
 | 
						|
		rank.prisons = prs;
 | 
						|
		rank.totalPrisons = tprs;
 | 
						|
	}
 | 
						|
 | 
						|
	grade = K_CalculateGPGrade(&rank);
 | 
						|
 | 
						|
	delay = TICRATE/2;
 | 
						|
 | 
						|
	transition = 0;
 | 
						|
	transitionTime = TICRATE/2;
 | 
						|
 | 
						|
	header[0] = '\0';
 | 
						|
 | 
						|
	if (rank.position > RANK_NEUTRAL_POSITION)
 | 
						|
	{
 | 
						|
		snprintf(
 | 
						|
			header, sizeof header,
 | 
						|
			"NO GOOD..."
 | 
						|
		);
 | 
						|
	}
 | 
						|
	else
 | 
						|
	{
 | 
						|
		snprintf(
 | 
						|
			header, sizeof header,
 | 
						|
			"CONGRATULATIONS"
 | 
						|
		);
 | 
						|
	}
 | 
						|
 | 
						|
	header[sizeof header - 1] = '\0';
 | 
						|
 | 
						|
	displayLevels = 0;
 | 
						|
 | 
						|
	gradeVoice = sfx_None;
 | 
						|
 | 
						|
	// It'd be neat to add all of the grade sounds,
 | 
						|
	// but not this close to release
 | 
						|
	if (rank.position > RANK_NEUTRAL_POSITION || grade < GRADE_C)
 | 
						|
	{
 | 
						|
		gradeVoice = skins[rank.skin].soundsid[S_sfx[sfx_klose].skinsound];
 | 
						|
	}
 | 
						|
	else
 | 
						|
	{
 | 
						|
		gradeVoice = skins[rank.skin].soundsid[S_sfx[sfx_kwin].skinsound];
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void podiumData_s::NextLevel(void)
 | 
						|
{
 | 
						|
	state = PODIUM_ST_LEVEL_APPEAR;
 | 
						|
	displayLevels++;
 | 
						|
	delay = TICRATE/7;
 | 
						|
}
 | 
						|
 | 
						|
void podiumData_s::Tick(void)
 | 
						|
{
 | 
						|
	if (delay > 0)
 | 
						|
	{
 | 
						|
		delay--;
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	if (transition < FRACUNIT)
 | 
						|
	{
 | 
						|
		if (transitionTime <= 0)
 | 
						|
		{
 | 
						|
			transition = FRACUNIT;
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		transition += FRACUNIT / transitionTime;
 | 
						|
		if (transition > FRACUNIT)
 | 
						|
		{
 | 
						|
			transition = FRACUNIT;
 | 
						|
		}
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	switch (state)
 | 
						|
	{
 | 
						|
		case PODIUM_ST_CONGRATS_SLIDEIN:
 | 
						|
		{
 | 
						|
			state = PODIUM_ST_CONGRATS_SLIDEUP;
 | 
						|
			transition = 0;
 | 
						|
			transitionTime = TICRATE/2;
 | 
						|
			delay = TICRATE/2;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		case PODIUM_ST_CONGRATS_SLIDEUP:
 | 
						|
		{
 | 
						|
			state = PODIUM_ST_DATA_SLIDEIN;
 | 
						|
			transition = 0;
 | 
						|
			transitionTime = TICRATE/2;
 | 
						|
			delay = TICRATE/5;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		case PODIUM_ST_DATA_SLIDEIN:
 | 
						|
		{
 | 
						|
			state = PODIUM_ST_DATA_PAUSE;
 | 
						|
			delay = TICRATE/5;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		case PODIUM_ST_DATA_PAUSE:
 | 
						|
		{
 | 
						|
			NextLevel();
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		case PODIUM_ST_LEVEL_APPEAR:
 | 
						|
		{
 | 
						|
			S_StopSoundByNum(sfx_mbs5b);
 | 
						|
			S_StartSound(nullptr, (displayLevels >= rank.numLevels) ? sfx_mbs70 : sfx_mbs5b);
 | 
						|
 | 
						|
			state = PODIUM_ST_LEVEL_PAUSE;
 | 
						|
			delay = TICRATE/2;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		case PODIUM_ST_LEVEL_PAUSE:
 | 
						|
		{
 | 
						|
			if (displayLevels < rank.numLevels)
 | 
						|
			{
 | 
						|
				NextLevel();
 | 
						|
			}
 | 
						|
			else
 | 
						|
			{
 | 
						|
				state = PODIUM_ST_TOTALS_SLIDEIN;
 | 
						|
				transition = 0;
 | 
						|
				transitionTime = TICRATE/2;
 | 
						|
				delay = TICRATE/5;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		case PODIUM_ST_TOTALS_SLIDEIN:
 | 
						|
		{
 | 
						|
			state = PODIUM_ST_TOTALS_PAUSE;
 | 
						|
			delay = TICRATE/5;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		case PODIUM_ST_TOTALS_PAUSE:
 | 
						|
		{
 | 
						|
			state = PODIUM_ST_GRADE_APPEAR;
 | 
						|
			transition = 0;
 | 
						|
			transitionTime = TICRATE/7;
 | 
						|
			delay = TICRATE/2;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		case PODIUM_ST_GRADE_APPEAR:
 | 
						|
		{
 | 
						|
			S_StartSound(nullptr, sfx_rank);
 | 
						|
			if (K_CalculateGPGrade(&rank) >= GRADE_S)
 | 
						|
				S_StartSoundAtVolume(nullptr, sfx_srank, 200);
 | 
						|
			state = PODIUM_ST_GRADE_VOICE;
 | 
						|
			delay = TICRATE/2;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		case PODIUM_ST_GRADE_VOICE:
 | 
						|
		{
 | 
						|
			if (cv_kartvoices.value)
 | 
						|
			{
 | 
						|
				S_StartSound(nullptr, gradeVoice);
 | 
						|
			}
 | 
						|
			state = PODIUM_ST_DONE;
 | 
						|
			delay = 5*TICRATE;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		case PODIUM_ST_DONE:
 | 
						|
		{
 | 
						|
			if (menuactive == false && M_MenuConfirmPressed(0) == true)
 | 
						|
			{
 | 
						|
				state = PODIUM_ST_EXIT;
 | 
						|
				delay = 2*TICRATE;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		case PODIUM_ST_EXIT:
 | 
						|
		{
 | 
						|
			if (grandprixinfo.gp == true
 | 
						|
				&& grandprixinfo.cup != nullptr
 | 
						|
				&& grandprixinfo.cup->playcredits == true)
 | 
						|
			{
 | 
						|
				nextmap = NEXTMAP_CREDITS;
 | 
						|
			}
 | 
						|
			else
 | 
						|
			{
 | 
						|
				nextmap = NEXTMAP_TITLE;
 | 
						|
			}
 | 
						|
 | 
						|
			G_EndGame();
 | 
						|
			return;
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void podiumData_s::Draw(void)
 | 
						|
{
 | 
						|
	INT32 i;
 | 
						|
 | 
						|
	const float transition_f = FixedToFloat(transition);
 | 
						|
	const float transition_i = 1.0 - transition_f;
 | 
						|
 | 
						|
	srb2::Draw drawer = srb2::Draw(0, 0);
 | 
						|
 | 
						|
	INT32 fade = 5;
 | 
						|
	if (state == PODIUM_ST_CONGRATS_SLIDEIN)
 | 
						|
	{
 | 
						|
		fade = (5 * transition_f);
 | 
						|
	}
 | 
						|
 | 
						|
	V_DrawFadeFill(
 | 
						|
		0, 0,
 | 
						|
		vid.width, vid.height,
 | 
						|
		V_NOSCALESTART,
 | 
						|
		31, fade
 | 
						|
	);
 | 
						|
 | 
						|
	constexpr INT32 header_height = 36;
 | 
						|
	constexpr INT32 header_offset = -16;
 | 
						|
	constexpr INT32 header_centered = (BASEVIDHEIGHT * 0.5) - header_height - header_offset;
 | 
						|
 | 
						|
	switch (state)
 | 
						|
	{
 | 
						|
		case PODIUM_ST_CONGRATS_SLIDEIN:
 | 
						|
			Y_DrawIntermissionHeader(
 | 
						|
				(BASEVIDWIDTH * transition_i * FRACUNIT),
 | 
						|
				(header_centered + header_offset) * FRACUNIT,
 | 
						|
				false, header, false, false
 | 
						|
			);
 | 
						|
			break;
 | 
						|
 | 
						|
		case PODIUM_ST_CONGRATS_SLIDEUP:
 | 
						|
			Y_DrawIntermissionHeader(
 | 
						|
				0,
 | 
						|
				((header_centered * transition_i) + header_offset) * FRACUNIT,
 | 
						|
				false, header, false, false
 | 
						|
			);
 | 
						|
			break;
 | 
						|
 | 
						|
		default:
 | 
						|
			Y_DrawIntermissionHeader(
 | 
						|
				0,
 | 
						|
				header_offset * FRACUNIT,
 | 
						|
				false, header, false, false
 | 
						|
			);
 | 
						|
			break;
 | 
						|
	}
 | 
						|
 | 
						|
	const boolean singlePlayer = (rank.numPlayers == 1);
 | 
						|
	player_t *bestHuman = &players[consoleplayer];
 | 
						|
 | 
						|
	if (singlePlayer == false)
 | 
						|
	{
 | 
						|
		UINT8 bestPos = UINT8_MAX;
 | 
						|
 | 
						|
		for (i = 0; i < rank.numPlayers; i++)
 | 
						|
		{
 | 
						|
			// BLEH BLEH, skincolor isn't saved to GP results, so I can't use the same values that get set. ANNOYING.
 | 
						|
			if (players[i].position < bestPos)
 | 
						|
			{
 | 
						|
				bestHuman = &players[i];
 | 
						|
				bestPos = players[i].position;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	srb2::Draw drawer_winner = drawer.xy(16, 16);
 | 
						|
	if (state >= PODIUM_ST_DATA_SLIDEIN)
 | 
						|
	{
 | 
						|
		if (state == PODIUM_ST_DATA_SLIDEIN)
 | 
						|
		{
 | 
						|
			drawer_winner = drawer_winner.x( transition_i * -BASEVIDWIDTH );
 | 
						|
		}
 | 
						|
 | 
						|
		drawer_winner
 | 
						|
			.colormap(bestHuman->skin, static_cast<skincolornum_t>(bestHuman->skincolor))
 | 
						|
			.patch(faceprefix[bestHuman->skin][FACE_WANTED]);
 | 
						|
 | 
						|
		drawer_winner
 | 
						|
			.xy(44, 31)
 | 
						|
			.align(srb2::Draw::Align::kCenter)
 | 
						|
			.font(srb2::Draw::Font::kZVote)
 | 
						|
			.text(va("%c%d", (rank.scorePosition > 0 ? '+' : ' '), rank.scorePosition));
 | 
						|
 | 
						|
		drawer_winner
 | 
						|
			.xy(64, 19)
 | 
						|
			.patch("K_POINT4");
 | 
						|
 | 
						|
		drawer_winner
 | 
						|
			.xy(88, 21)
 | 
						|
			.align(srb2::Draw::Align::kLeft)
 | 
						|
			.font(srb2::Draw::Font::kPing)
 | 
						|
			.colormap(TC_RAINBOW, SKINCOLOR_GOLD)
 | 
						|
			.text(va("%d", rank.winPoints));
 | 
						|
 | 
						|
		drawer_winner
 | 
						|
			.xy(75, 31)
 | 
						|
			.align(srb2::Draw::Align::kCenter)
 | 
						|
			.font(srb2::Draw::Font::kZVote)
 | 
						|
			.text(va("%c%d", (rank.scoreGPPoints > 0 ? '+' : ' '), rank.scoreGPPoints));
 | 
						|
 | 
						|
 | 
						|
		srb2::Draw drawer_trophy = drawer.xy(272, 10);
 | 
						|
		if (state == PODIUM_ST_DATA_SLIDEIN)
 | 
						|
		{
 | 
						|
			drawer_trophy = drawer_trophy.x( transition_i * BASEVIDWIDTH );
 | 
						|
		}
 | 
						|
 | 
						|
		if (cup != nullptr)
 | 
						|
		{
 | 
						|
			M_DrawCup(
 | 
						|
				cup, drawer_trophy.x() * FRACUNIT, drawer_trophy.y() * FRACUNIT,
 | 
						|
				0, true,
 | 
						|
				(rank.position >= 1 && rank.position <= 3) ? rank.position : 0
 | 
						|
			);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (state >= PODIUM_ST_LEVEL_APPEAR)
 | 
						|
	{
 | 
						|
		srb2::Draw drawer_line = drawer_winner.xy(80, 28);
 | 
						|
 | 
						|
		for (i = 0; i <= displayLevels; i++)
 | 
						|
		{
 | 
						|
			srb2::Draw drawer_perplayer = drawer_line;
 | 
						|
			gpRank_level_t *lvl = nullptr;
 | 
						|
 | 
						|
			if (i > 0)
 | 
						|
			{
 | 
						|
				drawer_line
 | 
						|
					.xy(-88, 6)
 | 
						|
					.width(304)
 | 
						|
					.height(2)
 | 
						|
					.fill(31);
 | 
						|
 | 
						|
				lvl = &rank.levels[i - 1];
 | 
						|
 | 
						|
				if (lvl->id > 0)
 | 
						|
				{
 | 
						|
					char *title = G_BuildMapTitle(lvl->id);
 | 
						|
					if (title)
 | 
						|
					{
 | 
						|
						drawer_perplayer
 | 
						|
							.align(srb2::Draw::Align::kRight)
 | 
						|
							.font(srb2::Draw::Font::kThin)
 | 
						|
							.text(title);
 | 
						|
 | 
						|
						Z_Free(title);
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			INT32 p;
 | 
						|
			for (p = 0; p < rank.numPlayers; p++)
 | 
						|
			{
 | 
						|
				player_t *const player = &players[displayplayers[p]];
 | 
						|
 | 
						|
				if (lvl == nullptr)
 | 
						|
				{
 | 
						|
					if (singlePlayer == false)
 | 
						|
					{
 | 
						|
						drawer_perplayer
 | 
						|
							.xy(12, -2)
 | 
						|
							.colormap(player->skin, static_cast<skincolornum_t>(player->skincolor))
 | 
						|
							.patch(faceprefix[player->skin][FACE_MINIMAP]);
 | 
						|
 | 
						|
						drawer_perplayer
 | 
						|
							.xy(26, 0)
 | 
						|
							.font(srb2::Draw::Font::kConsole)
 | 
						|
							.text(va("%c", ('A' + p)));
 | 
						|
					}
 | 
						|
				}
 | 
						|
				// Do not draw any stats for GAME OVERed player
 | 
						|
				else if (gpRank_level_perplayer_t *const dta = &lvl->perPlayer[p]; dta->grade != GRADE_INVALID)
 | 
						|
				{
 | 
						|
					srb2::Draw drawer_rank = drawer_perplayer.xy(2, 0);
 | 
						|
 | 
						|
					if (lvl->event != GPEVENT_SPECIAL)
 | 
						|
					{
 | 
						|
						drawer_rank
 | 
						|
							.xy(0, -1)
 | 
						|
							.colormap( static_cast<skincolornum_t>(K_GetGradeColor(dta->grade)) )
 | 
						|
							.patch(va("R_CUPRN%c", K_GetGradeChar(dta->grade)));
 | 
						|
					}
 | 
						|
 | 
						|
					srb2::Draw drawer_gametype = drawer_rank.xy(18, 0);
 | 
						|
 | 
						|
					switch (lvl->event)
 | 
						|
					{
 | 
						|
						case GPEVENT_BONUS:
 | 
						|
						{
 | 
						|
							drawer_gametype
 | 
						|
								.xy(0, 1)
 | 
						|
								.patch("K_CAPICO");
 | 
						|
 | 
						|
							drawer_gametype
 | 
						|
								.xy(22, 1)
 | 
						|
								.align(srb2::Draw::Align::kCenter)
 | 
						|
								.font(srb2::Draw::Font::kPing)
 | 
						|
								.text(va("%d/%d", dta->prisons, lvl->totalPrisons));
 | 
						|
							break;
 | 
						|
						}
 | 
						|
						case GPEVENT_SPECIAL:
 | 
						|
						{
 | 
						|
							srb2::Draw drawer_emerald = drawer_gametype;
 | 
						|
							UINT8 emeraldNum = g_podiumData.emeraldnum;
 | 
						|
 | 
						|
							boolean useWhiteFrame = ((leveltime & 1) || !dta->gotSpecialPrize);
 | 
						|
							patch_t *emeraldPatch = nullptr;
 | 
						|
							skincolornum_t emeraldColor = SKINCOLOR_NONE;
 | 
						|
 | 
						|
							if (emeraldNum == 0)
 | 
						|
							{
 | 
						|
								// Prize -- todo, currently using fake Emerald
 | 
						|
								emeraldColor = SKINCOLOR_GOLD;
 | 
						|
							}
 | 
						|
							else
 | 
						|
							{
 | 
						|
								emeraldColor = static_cast<skincolornum_t>( SKINCOLOR_CHAOSEMERALD1 + ((emeraldNum - 1) % 7) );
 | 
						|
							}
 | 
						|
 | 
						|
							{
 | 
						|
								std::string emeraldName;
 | 
						|
								if (emeraldNum > 7)
 | 
						|
								{
 | 
						|
									emeraldName = (useWhiteFrame ? "K_SUPER2" : "K_SUPER1");
 | 
						|
								}
 | 
						|
								else
 | 
						|
								{
 | 
						|
									emeraldName = (useWhiteFrame ? "K_EMERC" : "K_EMERW");
 | 
						|
								}
 | 
						|
 | 
						|
								emeraldPatch = static_cast<patch_t*>( W_CachePatchName(emeraldName.c_str(), PU_CACHE) );
 | 
						|
							}
 | 
						|
 | 
						|
							if (dta->gotSpecialPrize)
 | 
						|
							{
 | 
						|
								if (emeraldColor != SKINCOLOR_NONE)
 | 
						|
								{
 | 
						|
									drawer_emerald = drawer_emerald.colormap( emeraldColor );
 | 
						|
								}
 | 
						|
							}
 | 
						|
							else
 | 
						|
							{
 | 
						|
								drawer_emerald = drawer_emerald.colormap( TC_BLINK, SKINCOLOR_BLACK );
 | 
						|
							}
 | 
						|
 | 
						|
							drawer_emerald
 | 
						|
								.xy(6 - (emeraldPatch->width * 0.5), 0)
 | 
						|
								.patch(emeraldPatch);
 | 
						|
							break;
 | 
						|
						}
 | 
						|
						default:
 | 
						|
						{
 | 
						|
							drawer_gametype
 | 
						|
								.xy(0, 1)
 | 
						|
								.patch("K_SPTLAP");
 | 
						|
 | 
						|
							drawer_gametype
 | 
						|
								.xy(22, 1)
 | 
						|
								.align(srb2::Draw::Align::kCenter)
 | 
						|
								.font(srb2::Draw::Font::kPing)
 | 
						|
								.text(va("%d/%d", dta->lapPoints, lvl->totalLapPoints));
 | 
						|
							break;
 | 
						|
						}
 | 
						|
					}
 | 
						|
 | 
						|
					if (singlePlayer)
 | 
						|
					{
 | 
						|
						srb2::Draw drawer_rings = drawer_gametype.xy(36, 0);
 | 
						|
 | 
						|
						if (lvl->event == GPEVENT_NONE)
 | 
						|
						{
 | 
						|
							drawer_rings
 | 
						|
								.xy(0, -1)
 | 
						|
								.patch("K_SRING1");
 | 
						|
 | 
						|
							drawer_rings
 | 
						|
								.xy(22, 1)
 | 
						|
								.colormap(TC_RAINBOW, SKINCOLOR_YELLOW)
 | 
						|
								.align(srb2::Draw::Align::kCenter)
 | 
						|
								.font(srb2::Draw::Font::kPing)
 | 
						|
								.text(va("%d", dta->rings));
 | 
						|
						}
 | 
						|
 | 
						|
						srb2::Draw drawer_timer = drawer_rings.xy(36, 0);
 | 
						|
 | 
						|
						drawer_timer
 | 
						|
							.xy(0, 0)
 | 
						|
							.patch("K_STTIMS");
 | 
						|
 | 
						|
						drawer_timer
 | 
						|
							.xy(32, 1)
 | 
						|
							.align(srb2::Draw::Align::kCenter)
 | 
						|
							.font(srb2::Draw::Font::kPing)
 | 
						|
							.text(lvl->time == UINT32_MAX ?
 | 
						|
								"--'--\"--" : va(
 | 
						|
								"%i'%02i\"%02i",
 | 
						|
								G_TicsToMinutes(lvl->time, true),
 | 
						|
								G_TicsToSeconds(lvl->time),
 | 
						|
								G_TicsToCentiseconds(lvl->time)
 | 
						|
							));
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				drawer_perplayer = drawer_perplayer.x(56);
 | 
						|
			}
 | 
						|
 | 
						|
			drawer_line = drawer_line.y(12);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (state >= PODIUM_ST_TOTALS_SLIDEIN)
 | 
						|
	{
 | 
						|
		srb2::Draw drawer_totals = drawer
 | 
						|
			.xy(BASEVIDWIDTH * 0.5, BASEVIDHEIGHT - 48.0);
 | 
						|
 | 
						|
		srb2::Draw drawer_totals_left = drawer_totals
 | 
						|
			.x(-144.0);
 | 
						|
 | 
						|
		srb2::Draw drawer_totals_right = drawer_totals
 | 
						|
			.x(78.0);
 | 
						|
 | 
						|
		if (state == PODIUM_ST_TOTALS_SLIDEIN)
 | 
						|
		{
 | 
						|
			drawer_totals_left = drawer_totals_left.x( transition_i * -BASEVIDWIDTH );
 | 
						|
			drawer_totals_right = drawer_totals_right.x( transition_i * BASEVIDWIDTH );
 | 
						|
		}
 | 
						|
 | 
						|
		drawer_totals_left
 | 
						|
			.xy(8.0, 8.0)
 | 
						|
			.patch("R_RTPBR");
 | 
						|
 | 
						|
		skincolornum_t continuesColor = SKINCOLOR_NONE;
 | 
						|
		if (rank.continuesUsed == 0)
 | 
						|
		{
 | 
						|
			continuesColor = SKINCOLOR_GOLD;
 | 
						|
		}
 | 
						|
		else if (rank.scoreContinues < 0)
 | 
						|
		{
 | 
						|
			continuesColor = SKINCOLOR_RED;
 | 
						|
		}
 | 
						|
 | 
						|
		drawer_totals_left
 | 
						|
			.y(24.0)
 | 
						|
			.patch("RANKCONT");
 | 
						|
 | 
						|
		drawer_totals_left
 | 
						|
			.xy(44.0, 24.0)
 | 
						|
			.align(srb2::Draw::Align::kCenter)
 | 
						|
			.font(srb2::Draw::Font::kTimer)
 | 
						|
			.colormap( TC_RAINBOW, continuesColor )
 | 
						|
			.text(va("%d", rank.continuesUsed));
 | 
						|
 | 
						|
		drawer_totals_left
 | 
						|
			.xy(44.0, 38.0)
 | 
						|
			.align(srb2::Draw::Align::kCenter)
 | 
						|
			.font(srb2::Draw::Font::kZVote)
 | 
						|
			.colormap( TC_RAINBOW, continuesColor )
 | 
						|
			.text(va("%c%d", (rank.scoreContinues >= 0 ? '+' : ' '), rank.scoreContinues));
 | 
						|
 | 
						|
		drawer_totals_left
 | 
						|
			.patch("RANKRING");
 | 
						|
 | 
						|
		drawer_totals_left
 | 
						|
			.xy(44.0, 0.0)
 | 
						|
			.align(srb2::Draw::Align::kCenter)
 | 
						|
			.font(srb2::Draw::Font::kThinTimer)
 | 
						|
			.text(va("%d / %d", rank.rings, rank.totalRings));
 | 
						|
 | 
						|
		drawer_totals_left
 | 
						|
			.xy(44.0, 14.0)
 | 
						|
			.align(srb2::Draw::Align::kCenter)
 | 
						|
			.font(srb2::Draw::Font::kZVote)
 | 
						|
			.text(va("%c%d", (rank.scoreRings > 0 ? '+' : ' '), rank.scoreRings));
 | 
						|
 | 
						|
		drawer_totals_right
 | 
						|
			.xy(10.0, 46.0)
 | 
						|
			.patch("CAPS_ZB");
 | 
						|
 | 
						|
		drawer_totals_right
 | 
						|
			.xy(44.0, 24.0)
 | 
						|
			.align(srb2::Draw::Align::kCenter)
 | 
						|
			.font(srb2::Draw::Font::kThinTimer)
 | 
						|
			.text(va("%d / %d", rank.prisons, rank.totalPrisons));
 | 
						|
 | 
						|
		drawer_totals_right
 | 
						|
			.xy(44.0, 38.0)
 | 
						|
			.align(srb2::Draw::Align::kCenter)
 | 
						|
			.font(srb2::Draw::Font::kZVote)
 | 
						|
			.text(va("%c%d", (rank.scorePrisons > 0 ? '+' : ' '), rank.scorePrisons));
 | 
						|
 | 
						|
		drawer_totals_right
 | 
						|
			.patch("RANKLAPS");
 | 
						|
 | 
						|
		drawer_totals_right
 | 
						|
			.xy(44.0, 0.0)
 | 
						|
			.align(srb2::Draw::Align::kCenter)
 | 
						|
			.font(srb2::Draw::Font::kThinTimer)
 | 
						|
			.text(va("%d / %d", rank.laps, rank.totalLaps));
 | 
						|
 | 
						|
		drawer_totals_right
 | 
						|
			.xy(44.0, 14.0)
 | 
						|
			.align(srb2::Draw::Align::kCenter)
 | 
						|
			.font(srb2::Draw::Font::kZVote)
 | 
						|
			.text(va("%c%d", (rank.scoreLaps > 0 ? '+' : ' '), rank.scoreLaps));
 | 
						|
	}
 | 
						|
 | 
						|
	if ((state == PODIUM_ST_GRADE_APPEAR && delay == 0)
 | 
						|
		|| state >= PODIUM_ST_GRADE_VOICE)
 | 
						|
	{
 | 
						|
		char grade_letter = K_GetGradeChar( static_cast<gp_rank_e>(grade) );
 | 
						|
 | 
						|
		patch_t *grade_img = static_cast<patch_t*>( W_CachePatchName(va("R_FINRN%c", grade_letter), PU_CACHE) );
 | 
						|
		srb2::Draw grade_drawer = drawer
 | 
						|
			.xy(BASEVIDWIDTH * 0.5, BASEVIDHEIGHT - 2.0 - (grade_img->height * 0.5))
 | 
						|
			.colormap( static_cast<skincolornum_t>(K_GetGradeColor( static_cast<gp_rank_e>(grade) )) );
 | 
						|
 | 
						|
		if (rank.specialWon == true)
 | 
						|
		{
 | 
						|
			UINT8 emeraldNum = g_podiumData.emeraldnum;
 | 
						|
 | 
						|
			const boolean emeraldBlink = (leveltime & 1);
 | 
						|
			patch_t *emeraldOverlay = nullptr;
 | 
						|
			patch_t *emeraldUnderlay = nullptr;
 | 
						|
			skincolornum_t emeraldColor = SKINCOLOR_NONE;
 | 
						|
 | 
						|
			if (emeraldNum == 0)
 | 
						|
			{
 | 
						|
				// Prize -- todo, currently using fake Emerald
 | 
						|
				emeraldColor = SKINCOLOR_GOLD;
 | 
						|
			}
 | 
						|
			else
 | 
						|
			{
 | 
						|
				emeraldColor = static_cast<skincolornum_t>( SKINCOLOR_CHAOSEMERALD1 + ((emeraldNum - 1) % 7) );
 | 
						|
			}
 | 
						|
 | 
						|
			{
 | 
						|
				if (emeraldNum > 7)
 | 
						|
				{
 | 
						|
					emeraldOverlay = static_cast<patch_t*>( W_CachePatchName("SEMRA0", PU_CACHE) );
 | 
						|
					emeraldUnderlay = static_cast<patch_t*>( W_CachePatchName("SEMRB0", PU_CACHE) );
 | 
						|
				}
 | 
						|
				else
 | 
						|
				{
 | 
						|
					emeraldOverlay = static_cast<patch_t*>( W_CachePatchName("EMRCA0", PU_CACHE) );
 | 
						|
					emeraldUnderlay = static_cast<patch_t*>( W_CachePatchName("EMRCB0", PU_CACHE) );
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			srb2::Draw emerald_drawer = grade_drawer
 | 
						|
				.xy(-48, 20)
 | 
						|
				.colormap(emeraldColor)
 | 
						|
				.scale(0.5);
 | 
						|
 | 
						|
			if (emeraldBlink)
 | 
						|
			{
 | 
						|
				emerald_drawer
 | 
						|
					.flags(V_ADD)
 | 
						|
					.patch(emeraldOverlay);
 | 
						|
 | 
						|
				if (emeraldUnderlay != nullptr)
 | 
						|
				{
 | 
						|
					emerald_drawer
 | 
						|
						.patch(emeraldUnderlay);
 | 
						|
				}
 | 
						|
			}
 | 
						|
			else
 | 
						|
			{
 | 
						|
				emerald_drawer
 | 
						|
					.patch(emeraldOverlay);
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		grade_drawer
 | 
						|
			.xy(48, -2)
 | 
						|
			.align(srb2::Draw::Align::kCenter)
 | 
						|
			.font(srb2::Draw::Font::kMenu)
 | 
						|
			.text("TOTAL");
 | 
						|
 | 
						|
		grade_drawer
 | 
						|
			.xy(48, 8)
 | 
						|
			.align(srb2::Draw::Align::kCenter)
 | 
						|
			.font(srb2::Draw::Font::kThinTimer)
 | 
						|
			.text(va("%d", rank.scoreTotal));
 | 
						|
 | 
						|
		float sc = 1.0;
 | 
						|
		if (state == PODIUM_ST_GRADE_APPEAR)
 | 
						|
		{
 | 
						|
			sc += transition_i * 8.0;
 | 
						|
		}
 | 
						|
 | 
						|
		grade_drawer
 | 
						|
			.xy(-grade_img->width * 0.5 * sc, -grade_img->height * 0.5 * sc)
 | 
						|
			.scale(sc)
 | 
						|
			.patch(grade_img);
 | 
						|
	}
 | 
						|
 | 
						|
	if (state >= PODIUM_ST_DATA_SLIDEIN)
 | 
						|
	{
 | 
						|
		K_DrawKartPositionNumXY(
 | 
						|
			rank.position, 1,
 | 
						|
			(drawer_winner.x() + 36) * FRACUNIT, (drawer_winner.y() + 2) * FRACUNIT,
 | 
						|
			FRACUNIT, drawer_winner.flags(),
 | 
						|
			leveltime,
 | 
						|
			((mapheaderinfo[gamemap - 1]->levelflags & LF_SUBTRACTNUM) == LF_SUBTRACTNUM),
 | 
						|
			true,
 | 
						|
			true,
 | 
						|
			(rank.position > 3)
 | 
						|
		);
 | 
						|
 | 
						|
		if (state == PODIUM_ST_DONE)
 | 
						|
		{
 | 
						|
			Y_DrawIntermissionButton(delay, 0, true);
 | 
						|
		}
 | 
						|
		else if (state == PODIUM_ST_EXIT)
 | 
						|
		{
 | 
						|
			Y_DrawIntermissionButton(-1, (2*TICRATE) - delay, true);
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/*--------------------------------------------------
 | 
						|
	boolean K_PodiumSequence(void)
 | 
						|
 | 
						|
		See header file for description.
 | 
						|
--------------------------------------------------*/
 | 
						|
boolean K_PodiumSequence(void)
 | 
						|
{
 | 
						|
	return (gamestate == GS_CEREMONY);
 | 
						|
}
 | 
						|
 | 
						|
/*--------------------------------------------------
 | 
						|
	boolean K_PodiumRanking(void)
 | 
						|
 | 
						|
		See header file for description.
 | 
						|
--------------------------------------------------*/
 | 
						|
boolean K_PodiumRanking(void)
 | 
						|
{
 | 
						|
	return (gamestate == GS_CEREMONY && g_podiumData.ranking == true);
 | 
						|
}
 | 
						|
 | 
						|
/*--------------------------------------------------
 | 
						|
	boolean K_PodiumGrade(void)
 | 
						|
 | 
						|
		See header file for description.
 | 
						|
--------------------------------------------------*/
 | 
						|
gp_rank_e K_PodiumGrade(void)
 | 
						|
{
 | 
						|
	if (K_PodiumSequence() == false)
 | 
						|
	{
 | 
						|
		return GRADE_E;
 | 
						|
	}
 | 
						|
 | 
						|
	return g_podiumData.grade;
 | 
						|
}
 | 
						|
 | 
						|
/*--------------------------------------------------
 | 
						|
	boolean K_PodiumHasEmerald(void)
 | 
						|
 | 
						|
		See header file for description.
 | 
						|
--------------------------------------------------*/
 | 
						|
boolean K_PodiumHasEmerald(void)
 | 
						|
{
 | 
						|
	if (K_PodiumSequence() == false)
 | 
						|
	{
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	return g_podiumData.rank.specialWon;
 | 
						|
}
 | 
						|
 | 
						|
/*--------------------------------------------------
 | 
						|
	UINT8 K_GetPodiumPosition(player_t *player)
 | 
						|
 | 
						|
		See header file for description.
 | 
						|
--------------------------------------------------*/
 | 
						|
UINT8 K_GetPodiumPosition(player_t *player)
 | 
						|
{
 | 
						|
	UINT8 position = 1;
 | 
						|
	INT32 i;
 | 
						|
 | 
						|
	for (i = 0; i < MAXPLAYERS; i++)
 | 
						|
	{
 | 
						|
		player_t *other = NULL;
 | 
						|
		if (playeringame[i] == false)
 | 
						|
		{
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		other = &players[i];
 | 
						|
		if (other->bot == false && other->spectator == true)
 | 
						|
		{
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		if (other->score > player->score)
 | 
						|
		{
 | 
						|
			// Final score is the important part.
 | 
						|
			position++;
 | 
						|
		}
 | 
						|
		else if (other->score == player->score)
 | 
						|
		{
 | 
						|
			if (other->bot == false && player->bot == true)
 | 
						|
			{
 | 
						|
				// Bots are never as important as players.
 | 
						|
				position++;
 | 
						|
			}
 | 
						|
			else if (i < player - players)
 | 
						|
			{
 | 
						|
				// Port priority is the final tie breaker.
 | 
						|
				position++;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return position;
 | 
						|
}
 | 
						|
 | 
						|
/*--------------------------------------------------
 | 
						|
	static void K_SetPodiumWaypoint(player_t *const player, waypoint_t *const waypoint)
 | 
						|
 | 
						|
		Changes the player's current and next waypoints, for
 | 
						|
		use during the podium sequence.
 | 
						|
 | 
						|
	Input Arguments:-
 | 
						|
		player - The player to update the waypoints of.
 | 
						|
		waypoint - The new current waypoint.
 | 
						|
 | 
						|
	Return:-
 | 
						|
		None
 | 
						|
--------------------------------------------------*/
 | 
						|
static void K_SetPodiumWaypoint(player_t *const player, waypoint_t *const waypoint)
 | 
						|
{
 | 
						|
	// Set the new waypoint.
 | 
						|
	player->currentwaypoint = waypoint;
 | 
						|
 | 
						|
	if ((waypoint == NULL)
 | 
						|
		|| (waypoint->nextwaypoints == NULL)
 | 
						|
		|| (waypoint->numnextwaypoints == 0U))
 | 
						|
	{
 | 
						|
		// No waypoint, or no next waypoint.
 | 
						|
		player->nextwaypoint = NULL;
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	// Simply use the first available next waypoint.
 | 
						|
	// No need for split paths in these cutscenes.
 | 
						|
	player->nextwaypoint = waypoint->nextwaypoints[0];
 | 
						|
}
 | 
						|
 | 
						|
/*--------------------------------------------------
 | 
						|
	void K_InitializePodiumWaypoint(player_t *const player)
 | 
						|
 | 
						|
		See header file for description.
 | 
						|
--------------------------------------------------*/
 | 
						|
void K_InitializePodiumWaypoint(player_t *const player)
 | 
						|
{
 | 
						|
	if ((player != NULL) && (player->mo != NULL))
 | 
						|
	{
 | 
						|
		if (player->position == 0)
 | 
						|
		{
 | 
						|
			// Just in case a netgame scenario with a late joiner ocurrs.
 | 
						|
			player->position = K_GetPodiumPosition(player);
 | 
						|
		}
 | 
						|
 | 
						|
		if (player->position > 0 && player->position <= MAXPLAYERS)
 | 
						|
		{
 | 
						|
			// Initialize our first waypoint to the one that
 | 
						|
			// matches our position.
 | 
						|
			K_SetPodiumWaypoint(player, K_GetWaypointFromID(player->position));
 | 
						|
		}
 | 
						|
		else
 | 
						|
		{
 | 
						|
			// None does, so remove it if we happen to have one.
 | 
						|
			K_SetPodiumWaypoint(player, NULL);
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/*--------------------------------------------------
 | 
						|
	void K_UpdatePodiumWaypoints(player_t *const player)
 | 
						|
 | 
						|
		See header file for description.
 | 
						|
--------------------------------------------------*/
 | 
						|
void K_UpdatePodiumWaypoints(player_t *const player)
 | 
						|
{
 | 
						|
	if ((player != NULL) && (player->mo != NULL))
 | 
						|
	{
 | 
						|
		if (player->currentwaypoint != NULL)
 | 
						|
		{
 | 
						|
			const fixed_t xydist = P_AproxDistance(
 | 
						|
				player->mo->x - player->currentwaypoint->mobj->x,
 | 
						|
				player->mo->y - player->currentwaypoint->mobj->y
 | 
						|
			);
 | 
						|
			const fixed_t xyzdist = P_AproxDistance(
 | 
						|
				xydist,
 | 
						|
				player->mo->z - player->currentwaypoint->mobj->z
 | 
						|
			);
 | 
						|
			//const fixed_t speed = P_AproxDistance(player->mo->momx, player->mo->momy);
 | 
						|
 | 
						|
			if (xyzdist <= player->mo->radius + player->currentwaypoint->mobj->radius)
 | 
						|
			{
 | 
						|
				// Reached waypoint, go to the next waypoint.
 | 
						|
				K_SetPodiumWaypoint(player, player->nextwaypoint);
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/*--------------------------------------------------
 | 
						|
	boolean K_StartCeremony(void)
 | 
						|
 | 
						|
		See header file for description.
 | 
						|
--------------------------------------------------*/
 | 
						|
boolean K_StartCeremony(void)
 | 
						|
{
 | 
						|
	if (grandprixinfo.gp == false)
 | 
						|
	{
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	INT32 i;
 | 
						|
	INT32 podiumMapNum = NEXTMAP_INVALID;
 | 
						|
 | 
						|
	if (grandprixinfo.cup != NULL
 | 
						|
	&& grandprixinfo.cup->cachedlevels[CUPCACHE_PODIUM] != NEXTMAP_INVALID)
 | 
						|
	{
 | 
						|
		podiumMapNum = grandprixinfo.cup->cachedlevels[CUPCACHE_PODIUM];
 | 
						|
	}
 | 
						|
	else if (podiummap)
 | 
						|
	{
 | 
						|
		podiumMapNum = G_MapNumber(podiummap);
 | 
						|
	}
 | 
						|
 | 
						|
	if (podiumMapNum < nummapheaders
 | 
						|
		&& mapheaderinfo[podiumMapNum]
 | 
						|
		&& mapheaderinfo[podiumMapNum]->lumpnum != LUMPERROR)
 | 
						|
	{
 | 
						|
		gamemap = podiumMapNum+1;
 | 
						|
		g_reloadingMap = false;
 | 
						|
 | 
						|
		encoremode = grandprixinfo.encore;
 | 
						|
 | 
						|
		if (savedata.lives > 0)
 | 
						|
		{
 | 
						|
			K_LoadGrandPrixSaveGame();
 | 
						|
			savedata.lives = 0;
 | 
						|
		}
 | 
						|
 | 
						|
		// Make sure all of the GAME OVER'd players can spawn
 | 
						|
		// and be present for the podium
 | 
						|
		for (i = 0; i < MAXPLAYERS; i++)
 | 
						|
		{
 | 
						|
			if (playeringame[i])
 | 
						|
			{
 | 
						|
				if (players[i].lives < 1)
 | 
						|
					players[i].lives = 1;
 | 
						|
 | 
						|
				if (players[i].bot)
 | 
						|
					players[i].spectator = false;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		G_SetGametype(GT_RACE);
 | 
						|
		G_DoLoadLevelEx(false, GS_CEREMONY);
 | 
						|
		wipegamestate = GS_CEREMONY; // I don't know what else to do here
 | 
						|
 | 
						|
		r_splitscreen = 0; // Only one screen for the ceremony
 | 
						|
		R_ExecuteSetViewSize();
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
/*--------------------------------------------------
 | 
						|
	void K_FinishCeremony(void)
 | 
						|
 | 
						|
		See header file for description.
 | 
						|
--------------------------------------------------*/
 | 
						|
void K_FinishCeremony(void)
 | 
						|
{
 | 
						|
	if (K_PodiumSequence() == false)
 | 
						|
	{
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	g_podiumData.ranking = true;
 | 
						|
	g_fast_forward = 0;
 | 
						|
}
 | 
						|
 | 
						|
/*--------------------------------------------------
 | 
						|
	void K_ResetCeremony(void)
 | 
						|
 | 
						|
		See header file for description.
 | 
						|
--------------------------------------------------*/
 | 
						|
void K_ResetCeremony(void)
 | 
						|
{
 | 
						|
	SINT8 i;
 | 
						|
 | 
						|
	memset(&g_podiumData, 0, sizeof(struct podiumData_s));
 | 
						|
 | 
						|
	if (K_PodiumSequence() == false)
 | 
						|
	{
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	// Establish rank and grade for this play session.
 | 
						|
	g_podiumData.Init();
 | 
						|
 | 
						|
	// Set up music for podium.
 | 
						|
	{
 | 
						|
		if (g_podiumData.rank.position == 1)
 | 
						|
		{
 | 
						|
			mapmusrng = 2;
 | 
						|
		}
 | 
						|
		else if (g_podiumData.rank.position <= RANK_NEUTRAL_POSITION)
 | 
						|
		{
 | 
						|
			mapmusrng = 1;
 | 
						|
		}
 | 
						|
		else
 | 
						|
		{
 | 
						|
			mapmusrng = 0;
 | 
						|
		}
 | 
						|
 | 
						|
		UINT8 limit = (encoremode && mapheaderinfo[gamemap-1]->encoremusname_size)
 | 
						|
			? mapheaderinfo[gamemap-1]->encoremusname_size
 | 
						|
			: mapheaderinfo[gamemap-1]->musname_size;
 | 
						|
 | 
						|
		if (limit < 1)
 | 
						|
			limit = 1;
 | 
						|
 | 
						|
		while (mapmusrng >= limit)
 | 
						|
		{
 | 
						|
			mapmusrng--;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (!grandprixinfo.cup)
 | 
						|
	{
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	cupheader_t *emeraldcup = NULL;
 | 
						|
 | 
						|
	if (gamedata->sealedswaps[GDMAX_SEALEDSWAPS-1] != NULL // all found
 | 
						|
	|| grandprixinfo.cup->id >= basenumkartcupheaders // custom content
 | 
						|
	|| M_SecretUnlocked(SECRET_SPECIALATTACK, false)) // true order
 | 
						|
	{
 | 
						|
		// Standard order.
 | 
						|
		emeraldcup = grandprixinfo.cup;
 | 
						|
	}
 | 
						|
	else
 | 
						|
	{
 | 
						|
		// Determine order from sealedswaps.
 | 
						|
		for (i = 0; i < GDMAX_SEALEDSWAPS; i++)
 | 
						|
		{
 | 
						|
			if (gamedata->sealedswaps[i] == NULL)
 | 
						|
			{
 | 
						|
				if (g_podiumData.rank.specialWon == true)
 | 
						|
				{
 | 
						|
					// First visit! Mark it off.
 | 
						|
					gamedata->sealedswaps[i] = grandprixinfo.cup;
 | 
						|
				}
 | 
						|
 | 
						|
				break;
 | 
						|
			}
 | 
						|
 | 
						|
			if (gamedata->sealedswaps[i] != grandprixinfo.cup)
 | 
						|
				continue;
 | 
						|
 | 
						|
			// Repeat visit, grab the same ID.
 | 
						|
			break;
 | 
						|
		}
 | 
						|
 | 
						|
		// If there's pending stars, apply them to the new cup order.
 | 
						|
		if (i < GDMAX_SEALEDSWAPS)
 | 
						|
		{
 | 
						|
			emeraldcup = kartcupheaders;
 | 
						|
			while (emeraldcup)
 | 
						|
			{
 | 
						|
				if (emeraldcup->id >= basenumkartcupheaders)
 | 
						|
				{
 | 
						|
					emeraldcup = NULL;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				if (emeraldcup->emeraldnum == i+1)
 | 
						|
					break;
 | 
						|
 | 
						|
				emeraldcup = emeraldcup->next;
 | 
						|
			}
 | 
						|
 | 
						|
			g_podiumData.emeraldnum = i+1;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Write grade, position, and emerald-having-ness for later sessions!
 | 
						|
	i = (grandprixinfo.masterbots) ? KARTGP_MASTER : grandprixinfo.gamespeed;
 | 
						|
 | 
						|
	// All results populate downwards in difficulty. This prevents someone
 | 
						|
	// who's just won on Normal from feeling obligated to complete Easy too.
 | 
						|
	for (; i >= 0; i--)
 | 
						|
	{
 | 
						|
		boolean anymerit = false;
 | 
						|
 | 
						|
		if ((grandprixinfo.cup->windata[i].best_placement == 0) // First run
 | 
						|
			|| (g_podiumData.rank.position <= grandprixinfo.cup->windata[i].best_placement)) // Later, better run
 | 
						|
		{
 | 
						|
			grandprixinfo.cup->windata[i].best_placement = g_podiumData.rank.position;
 | 
						|
 | 
						|
			// The following will not occur in unmodified builds, but pre-emptively sanitise gamedata if someone just changes MAXPLAYERS and calls it a day
 | 
						|
			if (grandprixinfo.cup->windata[i].best_placement > 0x0F)
 | 
						|
			{
 | 
						|
				grandprixinfo.cup->windata[i].best_placement = 0x0F;
 | 
						|
			}
 | 
						|
 | 
						|
			anymerit = true;
 | 
						|
		}
 | 
						|
 | 
						|
		if (g_podiumData.grade >= grandprixinfo.cup->windata[i].best_grade)
 | 
						|
		{
 | 
						|
			grandprixinfo.cup->windata[i].best_grade = g_podiumData.grade;
 | 
						|
			anymerit = true;
 | 
						|
		}
 | 
						|
 | 
						|
		if (g_podiumData.rank.specialWon == true && emeraldcup)
 | 
						|
		{
 | 
						|
			emeraldcup->windata[i].got_emerald = true;
 | 
						|
			anymerit = true;
 | 
						|
		}
 | 
						|
 | 
						|
		if (anymerit == true)
 | 
						|
		{
 | 
						|
			grandprixinfo.cup->windata[i].best_skin.id = g_podiumData.rank.skin;
 | 
						|
			grandprixinfo.cup->windata[i].best_skin.unloaded = NULL;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Update visitation.
 | 
						|
	prevmap = gamemap-1;
 | 
						|
	G_UpdateVisited();
 | 
						|
 | 
						|
	// will subsequently save in P_LoadLevel
 | 
						|
}
 | 
						|
 | 
						|
/*--------------------------------------------------
 | 
						|
	void K_CeremonyTicker(boolean run)
 | 
						|
 | 
						|
		See header file for description.
 | 
						|
--------------------------------------------------*/
 | 
						|
void K_CeremonyTicker(boolean run)
 | 
						|
{
 | 
						|
	// don't trigger if doing anything besides idling
 | 
						|
	if (gameaction != ga_nothing || gamestate != GS_CEREMONY)
 | 
						|
	{
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	P_TickAltView(&titlemapcam);
 | 
						|
 | 
						|
	if (titlemapcam.mobj != NULL)
 | 
						|
	{
 | 
						|
		camera[0].x = titlemapcam.mobj->x;
 | 
						|
		camera[0].y = titlemapcam.mobj->y;
 | 
						|
		camera[0].z = titlemapcam.mobj->z;
 | 
						|
		camera[0].angle = titlemapcam.mobj->angle;
 | 
						|
		camera[0].aiming = titlemapcam.mobj->pitch;
 | 
						|
		camera[0].subsector = titlemapcam.mobj->subsector;
 | 
						|
	}
 | 
						|
 | 
						|
	if (g_podiumData.ranking == false)
 | 
						|
	{
 | 
						|
		if (run == true)
 | 
						|
		{
 | 
						|
			if (g_podiumData.fastForward == true)
 | 
						|
			{
 | 
						|
				if (g_fast_forward == 0)
 | 
						|
				{
 | 
						|
					// Possibly an infinite loop, finalize even if we're still in the middle of the cutscene.
 | 
						|
					K_FinishCeremony();
 | 
						|
				}
 | 
						|
			}
 | 
						|
			else
 | 
						|
			{
 | 
						|
				if (menuactive == false && M_MenuConfirmPressed(0) == true)
 | 
						|
				{
 | 
						|
					if (!netgame)
 | 
						|
					{
 | 
						|
						constexpr tic_t kSkipToTime = 60 * TICRATE;
 | 
						|
						if (kSkipToTime > leveltime)
 | 
						|
						{
 | 
						|
							g_fast_forward = kSkipToTime - leveltime;
 | 
						|
						}
 | 
						|
					}
 | 
						|
 | 
						|
					g_podiumData.fastForward = true;
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	if (run == true)
 | 
						|
	{
 | 
						|
		g_podiumData.Tick();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/*--------------------------------------------------
 | 
						|
	void K_CeremonyDrawer(void)
 | 
						|
 | 
						|
		See header file for description.
 | 
						|
--------------------------------------------------*/
 | 
						|
void K_CeremonyDrawer(void)
 | 
						|
{
 | 
						|
	if (g_podiumData.ranking == false)
 | 
						|
	{
 | 
						|
		// not ready to draw.
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	g_podiumData.Draw();
 | 
						|
}
 |