mirror of
				https://github.com/KartKrewDev/RingRacers.git
				synced 2025-10-30 08:01:28 +00:00 
			
		
		
		
	 22b20b5877
			
		
	
	
		22b20b5877
		
	
	
	
	
		
			
			Implemented using libopus for the Opus codec, same as is used in Discord. This adds the following cvars: - `voice_chat` On/Off, triggers self-deafen state on server via weaponprefs - `voice_mode` Activity/PTT - `voice_selfmute` On/Off, triggers self-mute state on server via weaponprefs - `voice_inputamp` -30 to 30, scales input by value in decibels - `voice_activationthreshold` -30 to 0, if any peak in a frame is higher, activates voice - `voice_loopback` On/Off, plays back local transcoded voice - `voice_proximity` On/Off, enables proximity effects for server - `voice_distanceattenuation_distance` distance in fracunits to scale voice volume over - `voice_distanceattenuation_factor` distance in logarithmic factor to scale voice volume by distance to. e.g. 0.5 for "half as loud" at or above max distance - `voice_stereopanning_factor` at 1.0, player voices are panned to left or right speaker, scaling to no effect at 0.0 - `voice_concurrentattenuation_factor` the logarithmic factor to attenuate player voices with concurrent speakers - `voice_concurrentattenuation_min` the minimum concurrent speakers before global concurrent speaker attenuation - `voice_concurrentattenuation_max` the maximum concurrent speakers for full global concurrent speaker attenuation - `voice_servermute` whether voice chat is enabled on this server. visible from MS via bitflag - `voicevolume` local volume of all voice playback A Voice Options menu is added with a subset of these options, and Server Options has server mute.
		
			
				
	
	
		
			2534 lines
		
	
	
	
		
			56 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			2534 lines
		
	
	
	
		
			56 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // DR. ROBOTNIK'S RING RACERS
 | |
| //-----------------------------------------------------------------------------
 | |
| // Copyright (C) 2024 by Vivian "toastergrl" Grannell.
 | |
| // Copyright (C) 2024 by Kart Krew.
 | |
| // Copyright (C) 2020 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.cpp
 | |
| /// \brief Tally screens, or "Intermissions" as they were formally called in Doom
 | |
| 
 | |
| #include <algorithm>
 | |
| 
 | |
| #include "doomdef.h"
 | |
| #include "doomstat.h"
 | |
| #include "d_main.h"
 | |
| #include "f_finale.h"
 | |
| #include "g_game.h"
 | |
| #include "hu_stuff.h"
 | |
| #include "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 "k_menu.h"
 | |
| #include "m_misc.h"
 | |
| #include "i_system.h"
 | |
| #include "p_setup.h"
 | |
| 
 | |
| #include "r_local.h"
 | |
| #include "r_fps.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"
 | |
| 
 | |
| #include "m_random.h" // M_RandomKey
 | |
| #include "g_input.h" // G_PlayerInputDown
 | |
| #include "k_hud.h" // K_DrawMapThumbnail
 | |
| #include "k_battle.h"
 | |
| #include "k_boss.h"
 | |
| #include "k_pwrlv.h"
 | |
| #include "k_grandprix.h"
 | |
| #include "k_serverstats.h" // SV_BumpMatchStats
 | |
| #include "m_easing.h"
 | |
| #include "music.h"
 | |
| 
 | |
| #include "v_draw.hpp"
 | |
| 
 | |
| #ifdef HWRENDER
 | |
| #include "hardware/hw_main.h"
 | |
| #endif
 | |
| 
 | |
| typedef struct
 | |
| {
 | |
| 	char patch[9];
 | |
| 	 INT32 points;
 | |
| 	UINT8 display;
 | |
| } y_bonus_t;
 | |
| 
 | |
| static y_data_t data;
 | |
| 
 | |
| // graphics
 | |
| static patch_t *bgpatch = NULL;     // INTERSCR
 | |
| static patch_t *widebgpatch = NULL;
 | |
| static patch_t *bgtile = NULL;      // SPECTILE/SRB2BACK
 | |
| static patch_t *interpic = NULL;    // custom picture defined in map header
 | |
| 
 | |
| #define INFINITE_TIMER (INT16_MAX) // just some arbitrarily large value that won't easily overflow
 | |
| static INT32 timer;
 | |
| static INT32 powertype = PWRLV_DISABLED;
 | |
| 
 | |
| static INT32 intertic;
 | |
| static INT32 endtic = -1;
 | |
| static INT32 sorttic = -1;
 | |
| 
 | |
| static fixed_t mqscroll = 0;
 | |
| static fixed_t chkscroll = 0;
 | |
| static fixed_t ttlscroll = 0;
 | |
| 
 | |
| intertype_t intertype = int_none;
 | |
| 
 | |
| static huddrawlist_h luahuddrawlist_intermission;
 | |
| 
 | |
| static boolean Y_CanSkipIntermission(void)
 | |
| {
 | |
| 	if (!netgame)
 | |
| 	{
 | |
| 		return true;
 | |
| 	}
 | |
| 
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| boolean Y_IntermissionPlayerLock(void)
 | |
| {
 | |
| 	return (gamestate == GS_INTERMISSION && data.rankingsmode == false);
 | |
| }
 | |
| 
 | |
| static void Y_UnloadData(void);
 | |
| 
 | |
| //
 | |
| // SRB2Kart - Y_CalculateMatchData and ancillary functions
 | |
| //
 | |
| static void Y_CompareTime(INT32 i)
 | |
| {
 | |
| 	UINT32 val = ((players[i].pflags & PF_NOCONTEST || players[i].realtime == UINT32_MAX)
 | |
| 		? (UINT32_MAX-1) : players[i].realtime);
 | |
| 
 | |
| 	if (!(val < data.val[data.numplayers]))
 | |
| 		return;
 | |
| 
 | |
| 	data.val[data.numplayers] = val;
 | |
| 	data.num[data.numplayers] = i;
 | |
| }
 | |
| 
 | |
| static void Y_CompareScore(INT32 i)
 | |
| {
 | |
| 	UINT32 val = ((players[i].pflags & PF_NOCONTEST)
 | |
| 			? (UINT32_MAX-1) : players[i].roundscore);
 | |
| 
 | |
| 	if (!(data.val[data.numplayers] == UINT32_MAX
 | |
| 	|| (!(players[i].pflags & PF_NOCONTEST) && val > data.val[data.numplayers])))
 | |
| 		return;
 | |
| 
 | |
| 	data.val[data.numplayers] = val;
 | |
| 	data.num[data.numplayers] = i;
 | |
| }
 | |
| 
 | |
| static void Y_CompareRank(INT32 i)
 | |
| {
 | |
| 	INT16 increase = ((data.increase[i] == INT16_MIN) ? 0 : data.increase[i]);
 | |
| 	UINT32 score = players[i].score;
 | |
| 
 | |
| 	if (powertype != PWRLV_DISABLED)
 | |
| 	{
 | |
| 		score = clientpowerlevels[i][powertype];
 | |
| 	}
 | |
| 
 | |
| 	if (!(data.val[data.numplayers] == UINT32_MAX || (score - increase) > data.val[data.numplayers]))
 | |
| 		return;
 | |
| 
 | |
| 	data.val[data.numplayers] = (score - increase);
 | |
| 	data.num[data.numplayers] = i;
 | |
| }
 | |
| 
 | |
| static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32))
 | |
| {
 | |
| 	INT32 i, j;
 | |
| 	boolean completed[MAXPLAYERS];
 | |
| 	INT32 numplayersingame = 0;
 | |
| 	boolean getmainplayer = false;
 | |
| 
 | |
| 	// Initialize variables
 | |
| 	if (rankingsmode > 1)
 | |
| 		;
 | |
| 	else if ((data.rankingsmode = (boolean)rankingsmode))
 | |
| 	{
 | |
| 		sprintf(data.headerstring, "Total Rankings");
 | |
| 		data.gotthrough = false;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		getmainplayer = true;
 | |
| 
 | |
| 		data.encore = encoremode;
 | |
| 
 | |
| 		memset(data.jitter, 0, sizeof (data.jitter));
 | |
| 	}
 | |
| 
 | |
| 	for (i = 0; i < MAXPLAYERS; i++)
 | |
| 	{
 | |
| 		data.val[i] = UINT32_MAX;
 | |
| 		data.grade[i] = GRADE_INVALID;
 | |
| 
 | |
| 		if (!playeringame[i] || players[i].spectator)
 | |
| 		{
 | |
| 			data.increase[i] = INT16_MIN;
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		if (!rankingsmode)
 | |
| 			data.increase[i] = INT16_MIN;
 | |
| 
 | |
| 		numplayersingame++;
 | |
| 	}
 | |
| 
 | |
| 	memset(completed, 0, sizeof (completed));
 | |
| 	data.numplayers = 0;
 | |
| 	data.showroundnum = false;
 | |
| 
 | |
| 	data.isduel = (numplayersingame <= 2);
 | |
| 
 | |
| 	srb2::StandingsJson standings {};
 | |
| 	bool savestandings = (!rankingsmode && demo.recording);
 | |
| 
 | |
| 	// Team stratification (this code only barely supports more than 2 teams)
 | |
| 	data.winningteam = TEAM_UNASSIGNED;
 | |
| 	data.halfway = UINT8_MAX;
 | |
| 
 | |
| 	UINT8 countteam[TEAM__MAX];
 | |
| 	UINT8 smallestteam = UINT8_MAX;
 | |
| 	memset(countteam, 0, sizeof(countteam));
 | |
| 
 | |
| 	if (rankingsmode == 0 && G_GametypeHasTeams())
 | |
| 	{
 | |
| 		for (i = data.winningteam+1; i < TEAM__MAX; i++)
 | |
| 		{
 | |
| 			countteam[i] = G_CountTeam(i);
 | |
| 
 | |
| 			if (g_teamscores[data.winningteam] < g_teamscores[i])
 | |
| 			{
 | |
| 				data.winningteam = i;
 | |
| 			}
 | |
| 
 | |
| 			if (smallestteam > countteam[i])
 | |
| 			{
 | |
| 				smallestteam = countteam[i];
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if (countteam[data.winningteam])
 | |
| 		{
 | |
| 			data.halfway = countteam[data.winningteam] - 1;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for (j = 0; j < numplayersingame; j++)
 | |
| 	{
 | |
| 		i = 0;
 | |
| 
 | |
| 		if (data.winningteam != TEAM_UNASSIGNED)
 | |
| 		{
 | |
| 			for (; i < MAXPLAYERS; i++)
 | |
| 			{
 | |
| 				if (!playeringame[i] || players[i].spectator || completed[i])
 | |
| 					continue;
 | |
| 
 | |
| 				if (players[i].team != data.winningteam)
 | |
| 					continue;
 | |
| 
 | |
| 				comparison(i);
 | |
| 			}
 | |
| 
 | |
| 			if (data.val[data.numplayers] == UINT32_MAX)
 | |
| 			{
 | |
| 				// Only run the un-teamed loop if everybody
 | |
| 				// on the winning team was previously placed
 | |
| 				i = 0;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		for (; i < MAXPLAYERS; i++)
 | |
| 		{
 | |
| 			if (!playeringame[i] || players[i].spectator || completed[i])
 | |
| 				continue;
 | |
| 
 | |
| 			comparison(i);
 | |
| 		}
 | |
| 
 | |
| 		i = data.num[data.numplayers];
 | |
| 
 | |
| 		completed[i] = true;
 | |
| 		data.grade[i] = K_PlayerTallyActive(&players[i]) ? players[i].tally.rank : GRADE_INVALID;
 | |
| 
 | |
| 		if (data.numplayers && (data.val[data.numplayers] == data.val[data.numplayers-1]))
 | |
| 		{
 | |
| 			data.pos[data.numplayers] = data.pos[data.numplayers-1];
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			data.pos[data.numplayers] = data.numplayers+1;
 | |
| 		}
 | |
| 
 | |
| #define strtime data.strval[data.numplayers]
 | |
| 
 | |
| 		strtime[0] = '\0';
 | |
| 
 | |
| 		if (!rankingsmode)
 | |
| 		{
 | |
| 			// Online rank is handled further below in this file.
 | |
| 			if (powertype == PWRLV_DISABLED)
 | |
| 			{
 | |
| 				if (data.winningteam != TEAM_UNASSIGNED)
 | |
| 				{
 | |
| 					// TODO ASK TYRON
 | |
| 					if (smallestteam != 0
 | |
| 					&& players[i].team == data.winningteam)
 | |
| 					{
 | |
| 						data.increase[i] = 1;
 | |
| 					}
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					UINT8 pointgetters = numplayersingame + spectateGriefed;
 | |
| 
 | |
| 					if (data.pos[data.numplayers] < pointgetters
 | |
| 					&& !(players[i].pflags & PF_NOCONTEST))
 | |
| 					{
 | |
| 						data.increase[i] = K_CalculateGPRankPoints(data.pos[data.numplayers], pointgetters);
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				if (data.increase[i] > 0)
 | |
| 				{
 | |
| 					players[i].score += data.increase[i];
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			if (savestandings)
 | |
| 			{
 | |
| 				srb2::StandingJson standing {};
 | |
| 				standing.ranking = data.pos[data.numplayers];
 | |
| 				standing.name = std::string(player_names[i]);
 | |
| 				standing.demoskin = players[i].skin;
 | |
| 				standing.skincolor = std::string(skincolors[players[i].skincolor].name);
 | |
| 				standing.timeorscore = data.val[data.numplayers];
 | |
| 				standings.standings.emplace_back(std::move(standing));
 | |
| 			}
 | |
| 
 | |
| 			if (data.val[data.numplayers] == (UINT32_MAX-1))
 | |
| 				STRBUFCPY(strtime, "RETIRED.");
 | |
| 			else
 | |
| 			{
 | |
| 				if (intertype == int_time)
 | |
| 				{
 | |
| 					snprintf(strtime, sizeof strtime, "%i'%02i\"%02i", G_TicsToMinutes(data.val[data.numplayers], true),
 | |
| 					G_TicsToSeconds(data.val[data.numplayers]), G_TicsToCentiseconds(data.val[data.numplayers]));
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					snprintf(strtime, sizeof strtime, "%d", data.val[data.numplayers]);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			if (powertype != PWRLV_DISABLED && !clientpowerlevels[i][powertype])
 | |
| 			{
 | |
| 				// No power level (guests)
 | |
| 				STRBUFCPY(strtime, "----");
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 				snprintf(strtime, sizeof strtime, "%d", data.val[data.numplayers]);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		strtime[sizeof strtime - 1] = '\0';
 | |
| 
 | |
| #undef strtime
 | |
| 
 | |
| 		data.numplayers++;
 | |
| 	}
 | |
| 
 | |
| 	if (data.numplayers <= 2
 | |
| 		|| data.halfway == UINT8_MAX
 | |
| 		|| data.halfway >= 8
 | |
| 		|| (data.numplayers - data.halfway) >= 8)
 | |
| 	{
 | |
| 		data.halfway = (data.numplayers-1)/2;
 | |
| 	}
 | |
| 
 | |
| 	if (savestandings)
 | |
| 	{
 | |
| 		srb2::write_current_demo_end_marker();
 | |
| 		srb2::write_current_demo_standings(standings);
 | |
| 	}
 | |
| 
 | |
| 	if (getmainplayer == true)
 | |
| 	{
 | |
| 		// Okay, player scores have been set now - we can calculate GP-relevant material.
 | |
| 		{
 | |
| 			if (grandprixinfo.gp == true)
 | |
| 			{
 | |
| 				K_UpdateGPRank(&grandprixinfo.rank);
 | |
| 			}
 | |
| 
 | |
| 			// See also G_GetNextMap, M_DrawPause
 | |
| 			data.showrank = false;
 | |
| 			if (grandprixinfo.gp == true
 | |
| 				&& netgame == false // TODO netgame Special Mode support
 | |
| 				&& grandprixinfo.gamespeed >= KARTSPEED_NORMAL
 | |
| 				&& roundqueue.size > 1
 | |
| 				&& roundqueue.entries[roundqueue.size - 1].rankrestricted == true
 | |
| 			)
 | |
| 			{
 | |
| 				if (roundqueue.position == roundqueue.size-1)
 | |
| 				{
 | |
| 					// On A rank pace? Then you get a chance for S rank!
 | |
| 					gp_rank_e rankforline = K_CalculateGPGrade(&grandprixinfo.rank);
 | |
| 
 | |
| 					data.showrank = (rankforline >= GRADE_A);
 | |
| 
 | |
| 					data.linemeter =
 | |
| 						(std::min(rankforline, GRADE_A)
 | |
| 							* (2 * TICRATE)
 | |
| 						) / GRADE_A;
 | |
| 
 | |
| 					// G_NextMap will float you to rank-restricted stages on Master wins.
 | |
| 					// Fudge the rank display.
 | |
| 					if (grandprixinfo.masterbots && grandprixinfo.rank.position <= 1)
 | |
| 					{
 | |
| 						data.showrank = true;
 | |
| 						data.linemeter = 2*TICRATE;
 | |
| 					}
 | |
| 
 | |
| 					// A little extra time to take it all in
 | |
| 					timer += TICRATE;
 | |
| 				}
 | |
| 
 | |
| 				if (gamedata->everseenspecial == true
 | |
| 					|| roundqueue.position == roundqueue.size)
 | |
| 				{
 | |
| 					// Additional cases in which it should always be shown.
 | |
| 					data.showrank = true;
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		i = MAXPLAYERS;
 | |
| 
 | |
| 		for (j = 0; j < data.numplayers; j++)
 | |
| 		{
 | |
| 			i = data.num[j];
 | |
| 
 | |
| 			if (i >= MAXPLAYERS
 | |
| 				|| playeringame[i] == false
 | |
| 				|| players[i].spectator == true)
 | |
| 			{
 | |
| 				continue;
 | |
| 			}
 | |
| 
 | |
| 			if (demo.playback)
 | |
| 			{
 | |
| 				if (!P_IsDisplayPlayer(&players[i]))
 | |
| 				{
 | |
| 					continue;
 | |
| 				}
 | |
| 
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			if (!P_IsPartyPlayer(&players[i]))
 | |
| 			{
 | |
| 				continue;
 | |
| 			}
 | |
| 
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		data.headerstring[0] = '\0';
 | |
| 		data.gotthrough = false;
 | |
| 		data.mainplayer = MAXPLAYERS;
 | |
| 
 | |
| 		if (j < data.numplayers)
 | |
| 		{
 | |
| 			data.mainplayer = i;
 | |
| 
 | |
| 			if (data.winningteam != TEAM_UNASSIGNED
 | |
| 			&& players[i].team != TEAM_UNASSIGNED)
 | |
| 			{
 | |
| 				data.gotthrough = true;
 | |
| 
 | |
| 				snprintf(data.headerstring,
 | |
| 					sizeof data.headerstring,
 | |
| 					"%s TEAM",
 | |
| 					g_teaminfo[players[i].team].name);
 | |
| 
 | |
| 				data.showroundnum = true;
 | |
| 			}
 | |
| 			else if (!(players[i].pflags & PF_NOCONTEST))
 | |
| 			{
 | |
| 				data.gotthrough = true;
 | |
| 
 | |
| 				if (players[i].skin < numskins)
 | |
| 				{
 | |
| 					snprintf(data.headerstring,
 | |
| 						sizeof data.headerstring,
 | |
| 						"%s",
 | |
| 						R_CanShowSkinInDemo(players[i].skin) ? skins[players[i].skin].realname : "???");
 | |
| 				}
 | |
| 
 | |
| 				data.showroundnum = true;
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 				snprintf(data.headerstring,
 | |
| 					sizeof data.headerstring,
 | |
| 					"NO CONTEST...");
 | |
| 			}
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			if (roundqueue.position > 0 && roundqueue.position <= roundqueue.size
 | |
| 				&& (grandprixinfo.gp == false || grandprixinfo.eventmode == GPEVENT_NONE))
 | |
| 			{
 | |
| 				snprintf(data.headerstring,
 | |
| 					sizeof data.headerstring,
 | |
| 					"ROUND");
 | |
| 
 | |
| 				data.showroundnum = true;
 | |
| 			}
 | |
| 			else if (K_CheckBossIntro() == true && bossinfo.enemyname)
 | |
| 			{
 | |
| 				snprintf(data.headerstring,
 | |
| 					sizeof data.headerstring,
 | |
| 					"%s",
 | |
| 					bossinfo.enemyname);
 | |
| 			}
 | |
| 			else if (battleprisons == true)
 | |
| 			{
 | |
| 				snprintf(data.headerstring,
 | |
| 					sizeof data.headerstring,
 | |
| 					"PRISON BREAK");
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 				snprintf(data.headerstring,
 | |
| 					sizeof data.headerstring,
 | |
| 					"%s STAGE",
 | |
| 					gametypes[gametype]->name);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		data.headerstring[sizeof data.headerstring - 1] = '\0';
 | |
| 	}
 | |
| }
 | |
| 
 | |
| typedef enum
 | |
| {
 | |
| 	BPP_AHEAD,
 | |
| 	BPP_DONE,
 | |
| 	BPP_MAIN,
 | |
| 	BPP_SHADOW = BPP_MAIN,
 | |
| 	BPP_MAX
 | |
| } bottomprogressionpatch_t;
 | |
| 
 | |
| //
 | |
| // Y_PlayerStandingsDrawer
 | |
| //
 | |
| // Handles drawing the center-of-screen player standings.
 | |
| //
 | |
| void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset)
 | |
| {
 | |
| 	if (standings->numplayers == 0)
 | |
| 	{
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	UINT8 i;
 | |
| 
 | |
| 	SINT8 yspacing = 14;
 | |
| 	INT32 heightcount = (standings->numplayers - 1);
 | |
| 
 | |
| 	INT32 x, y;
 | |
| 	INT32 x2, returny, inwardshim = 0;
 | |
| 
 | |
| 	boolean verticalresults = (standings->numplayers < 4 && (standings->numplayers == 1 || standings->isduel == false));
 | |
| 	boolean datarightofcolumn = false;
 | |
| 	boolean drawping = (netgame && gamestate == GS_LEVEL);
 | |
| 
 | |
| 	INT32 hilicol = highlightflags;
 | |
| 
 | |
| 	patch_t *resbar = static_cast<patch_t*>(W_CachePatchName("R_RESBAR", PU_PATCH)); // Results bars for players
 | |
| 	patch_t *cpu = static_cast<patch_t*>(W_CachePatchName("K_CPU", PU_PATCH));
 | |
| 
 | |
| 	if (drawping || standings->rankingsmode != 0)
 | |
| 	{
 | |
| 		inwardshim = 8;
 | |
| 	}
 | |
| 
 | |
| 	if (verticalresults)
 | |
| 	{
 | |
| 		x = (BASEVIDWIDTH/2) - 61;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		x = 29;
 | |
| 		inwardshim /= 2;
 | |
| 		heightcount /= 2;
 | |
| 	}
 | |
| 
 | |
| 	x += xoffset + inwardshim;
 | |
| 	x2 = x;
 | |
| 
 | |
| 	if (drawping)
 | |
| 	{
 | |
| 		x2 -= 9;
 | |
| 	}
 | |
| 
 | |
| 	UINT8 halfway = standings->halfway;
 | |
| 
 | |
| 	if (halfway > 4)
 | |
| 	{
 | |
| 		yspacing--;
 | |
| 	}
 | |
| 	else if (halfway <= 2)
 | |
| 	{
 | |
| 		yspacing++;
 | |
| 		if (verticalresults)
 | |
| 		{
 | |
| 			yspacing++;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	y = 106 - (heightcount * yspacing)/2;
 | |
| 
 | |
| 	if (standings->isduel)
 | |
| 	{
 | |
| 		y += 38;
 | |
| 	}
 | |
| 	else if (y < 70)
 | |
| 	{
 | |
| 		// One sanity check.
 | |
| 		y = 70;
 | |
| 	}
 | |
| 
 | |
| 	returny = y;
 | |
| 
 | |
| 	boolean (*_isHighlightedPlayer)(const player_t *) =
 | |
| 		(demo.playback
 | |
| 			? P_IsDisplayPlayer
 | |
| 			: P_IsPartyPlayer
 | |
| 		);
 | |
| 
 | |
| 	boolean doreverse = (
 | |
| 		standings->isduel && standings->numplayers == 2
 | |
| 		&& standings->num[0] > standings->num[1]
 | |
| 	);
 | |
| 
 | |
| 	i = 0;
 | |
| 
 | |
| 	if (doreverse)
 | |
| 	{
 | |
| 		i = standings->numplayers-1;
 | |
| 		halfway++;
 | |
| 	}
 | |
| 
 | |
| 	do // don't use "continue" in this loop just for sanity's sake
 | |
| 	{
 | |
| 		const UINT8 pnum = standings->num[i];
 | |
| 
 | |
| 		if (pnum == MAXPLAYERS)
 | |
| 			;
 | |
| 		else if (!playeringame[pnum] || players[pnum].spectator == true)
 | |
| 			standings->num[i] = MAXPLAYERS; // this should be the only field setting in this function
 | |
| 		else
 | |
| 		{
 | |
| 			UINT8 *charcolormap = NULL;
 | |
| 			if (!R_CanShowSkinInDemo(players[pnum].skin))
 | |
| 			{
 | |
| 				charcolormap = R_GetTranslationColormap(TC_BLINK, static_cast<skincolornum_t>(players[pnum].skincolor), GTC_CACHE);
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 				charcolormap = R_GetTranslationColormap(players[pnum].skin, static_cast<skincolornum_t>(players[pnum].skincolor), GTC_CACHE);
 | |
| 			}
 | |
| 
 | |
| 			if (standings->isduel)
 | |
| 			{
 | |
| 				INT32 duelx = x + 22 + (datarightofcolumn ? inwardshim : -inwardshim);
 | |
| 				INT32 duely = y - 80;
 | |
| 
 | |
| 				V_DrawScaledPatch(duelx, duely, 0, static_cast<patch_t*>(W_CachePatchName("DUELGRPH", PU_CACHE)));
 | |
| 				V_DrawScaledPatch(duelx + 8, duely + 9, V_TRANSLUCENT, static_cast<patch_t*>(W_CachePatchName("PREVBACK", PU_CACHE)));
 | |
| 
 | |
| 				UINT8 spr2 = SPR2_STIN;
 | |
| 				if (standings->pos[i] == 2)
 | |
| 				{
 | |
| 					spr2 = (datarightofcolumn ? SPR2_STGR : SPR2_STGL);
 | |
| 				}
 | |
| 
 | |
| 				M_DrawCharacterSprite(
 | |
| 					duelx + 40, duely + 78,
 | |
| 					players[pnum].skin,
 | |
| 					spr2,
 | |
| 					(datarightofcolumn ? 1 : 7),
 | |
| 					0,
 | |
| 					0,
 | |
| 					charcolormap
 | |
| 				);
 | |
| 
 | |
| 				duelx += 8;
 | |
| 				duely += 5;
 | |
| 
 | |
| 				UINT8 j;
 | |
| 				for (j = 0; j <= splitscreen; j++)
 | |
| 				{
 | |
| 					if (pnum == g_localplayers[j])
 | |
| 						break;
 | |
| 				}
 | |
| 
 | |
| 				INT32 letterpos = duelx + (datarightofcolumn ? 44 : 0);
 | |
| 
 | |
| 				if (j > splitscreen || demo.playback)
 | |
| 				{
 | |
| 					// TODO: EGGA isn't strictly correct for demo playback since they're not really network players, but it's better than displaying local profile.
 | |
| 					V_DrawScaledPatch(letterpos, duely, 0, static_cast<patch_t*>(W_CachePatchName(va("CHAR%s", (players[pnum].bot ? "CPU" : "EGGA")), PU_CACHE)));
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					duelx += (datarightofcolumn ? -1 : 11);
 | |
| 
 | |
| 					UINT8 profilen = cv_lastprofile[j].value;
 | |
| 
 | |
| 					V_DrawScaledPatch(duelx, duely, 0, static_cast<patch_t*>(W_CachePatchName("FILEBACK", PU_CACHE)));
 | |
| 
 | |
| 					if (datarightofcolumn && j == 0)
 | |
| 						letterpos++; // A is one pixel thinner
 | |
| 
 | |
| 					V_DrawScaledPatch(letterpos, duely, 0, static_cast<patch_t*>(W_CachePatchName(va("CHARSEL%c", 'A' + j), PU_CACHE)));
 | |
| 
 | |
| 					profile_t *pr = PR_GetProfile(profilen);
 | |
| 
 | |
| 					V_DrawCenteredFileString(duelx+26, duely, 0, pr ? pr->profilename : "PLAYER");
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			// Apply the jitter offset (later reversed)
 | |
| 			if (standings->jitter[pnum] > 0)
 | |
| 				y--;
 | |
| 
 | |
| 			V_DrawMappedPatch(x, y, 0, resbar, NULL);
 | |
| 
 | |
| 			V_DrawRightAlignedThinString(x+13, y-2, 0, va("%d", standings->pos[i]));
 | |
| 
 | |
| 			//if (players[pnum].skincolor != SKINCOLOR_NONE)
 | |
| 			{
 | |
| 				if ((players[pnum].pflags & PF_NOCONTEST) && players[pnum].bot)
 | |
| 				{
 | |
| 					// RETIRED !!
 | |
| 					V_DrawMappedPatch(
 | |
| 						x+14, y-5,
 | |
| 						0,
 | |
| 						static_cast<patch_t*>(W_CachePatchName("MINIDEAD", PU_CACHE)),
 | |
| 						R_GetTranslationColormap(TC_DEFAULT, static_cast<skincolornum_t>(players[pnum].skincolor), GTC_CACHE)
 | |
| 					);
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					charcolormap = R_GetTranslationColormap(players[pnum].skin, static_cast<skincolornum_t>(players[pnum].skincolor), GTC_CACHE);
 | |
| 					V_DrawMappedPatch(x+14, y-5, 0,
 | |
| 						R_CanShowSkinInDemo(players[pnum].skin) ?
 | |
| 						faceprefix[players[pnum].skin][FACE_MINIMAP] : kp_unknownminimap,
 | |
| 						charcolormap);
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| /*			y2 = y;
 | |
| 
 | |
| 			if ((netgame || (demo.playback && demo.netgame)) && playerconsole[pnum] == 0 && server_lagless && !players[pnum].bot)
 | |
| 			{
 | |
| 				static UINT8 alagles_timer = 0;
 | |
| 				patch_t *alagles;
 | |
| 
 | |
| 				y2 = ( y - 4 );
 | |
| 
 | |
| 				V_DrawScaledPatch(x + 36, y2, 0, W_CachePatchName(va("BLAGLES%d", (intertic / 3) % 6), PU_CACHE));
 | |
| 				// every 70 tics
 | |
| 				if (( leveltime % 70 ) == 0)
 | |
| 				{
 | |
| 					alagles_timer = 9;
 | |
| 				}
 | |
| 				if (alagles_timer > 0)
 | |
| 				{
 | |
| 					alagles = W_CachePatchName(va("ALAGLES%d", alagles_timer), PU_CACHE);
 | |
| 					V_DrawScaledPatch(x + 36, y2, 0, alagles);
 | |
| 					if (( leveltime % 2 ) == 0)
 | |
| 						alagles_timer--;
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					alagles = W_CachePatchName("ALAGLES0", PU_CACHE);
 | |
| 					V_DrawScaledPatch(x + 36, y2, 0, alagles);
 | |
| 				}
 | |
| 
 | |
| 				y2 += SHORT (alagles->height) + 1;
 | |
| 			}*/
 | |
| 
 | |
| 			V_DrawThinString(
 | |
| 				x+27, y-2,
 | |
| 				(
 | |
| 					_isHighlightedPlayer(&players[pnum])
 | |
| 						? hilicol
 | |
| 						: 0
 | |
| 				),
 | |
| 				player_names[pnum]
 | |
| 			);
 | |
| 
 | |
| 			{
 | |
| 				patch_t *voxpat;
 | |
| 				int voxxoffs = 0;
 | |
| 				int voxyoffs = 0;
 | |
| 				if (players[pnum].pflags2 & (PF2_SELFDEAFEN | PF2_SERVERDEAFEN))
 | |
| 				{
 | |
| 					voxpat = (patch_t*) W_CachePatchName("VOXCRD", PU_HUDGFX);
 | |
| 					voxxoffs = 1;
 | |
| 					voxyoffs = -5;
 | |
| 				}
 | |
| 				else if (players[pnum].pflags2 & (PF2_SELFMUTE | PF2_SERVERMUTE))
 | |
| 				{
 | |
| 					voxpat = (patch_t*) W_CachePatchName("VOXCRM", PU_HUDGFX);
 | |
| 					voxxoffs = 1;
 | |
| 					voxyoffs = -6;
 | |
| 				}
 | |
| 				else if (S_IsPlayerVoiceActive(pnum))
 | |
| 				{
 | |
| 					voxpat = (patch_t*) W_CachePatchName("VOXCRA", PU_HUDGFX);
 | |
| 					voxyoffs = -4;
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					voxpat = NULL;
 | |
| 				}
 | |
| 
 | |
| 				if (voxpat)
 | |
| 				{
 | |
| 					int namewidth = V_ThinStringWidth(player_names[pnum], 0);
 | |
| 					V_DrawFixedPatch((x + 27 + namewidth + voxxoffs) * FRACUNIT, (y + voxyoffs) * FRACUNIT, FRACUNIT, 0, voxpat, NULL);
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			V_DrawRightAlignedThinString(
 | |
| 				x+118, y-2,
 | |
| 				0,
 | |
| 				standings->strval[i]
 | |
| 			);
 | |
| 
 | |
| 			if (drawping)
 | |
| 			{
 | |
| 				if (players[pnum].bot)
 | |
| 				{
 | |
| 					V_DrawScaledPatch(
 | |
| 						x2-2 + (datarightofcolumn ? 2 : -2), y-2,
 | |
| 						0,
 | |
| 						cpu
 | |
| 					);
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					HU_drawPing(
 | |
| 						(x2 - 2) * FRACUNIT, (y-2) * FRACUNIT,
 | |
| 						playerpingtable[pnum],
 | |
| 						playerdelaytable[pnum],
 | |
| 						playerpacketlosstable[pnum],
 | |
| 						0,
 | |
| 						(datarightofcolumn ? 1 : -1)
 | |
| 					);
 | |
| 				}
 | |
| 			}
 | |
| 			else if (gamestate == GS_LEVEL)
 | |
| 				;
 | |
| 			else if (standings->rankingsmode != 0)
 | |
| 			{
 | |
| 				char *increasenum = NULL;
 | |
| 
 | |
| 				if (standings->increase[pnum] != INT16_MIN)
 | |
| 				{
 | |
| 					increasenum = va(
 | |
| 						"(%d)",
 | |
| 						standings->increase[pnum]
 | |
| 					);
 | |
| 				}
 | |
| 
 | |
| 				if (increasenum)
 | |
| 				{
 | |
| 					if (datarightofcolumn)
 | |
| 					{
 | |
| 						V_DrawThinString(
 | |
| 							x2, y-2,
 | |
| 							0,
 | |
| 							increasenum
 | |
| 						);
 | |
| 					}
 | |
| 					else
 | |
| 					{
 | |
| 						V_DrawRightAlignedThinString(
 | |
| 							x2, y-2,
 | |
| 							0,
 | |
| 							increasenum
 | |
| 						);
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 			else if (standings->grade[pnum] != GRADE_INVALID)
 | |
| 			{
 | |
| 				patch_t *gradePtc = static_cast<patch_t*>(W_CachePatchName(va("R_INRNK%c", K_GetGradeChar(static_cast<gp_rank_e>(standings->grade[pnum]))), PU_PATCH));
 | |
| 				patch_t *gradeBG = NULL;
 | |
| 
 | |
| 				UINT16 gradeColor = SKINCOLOR_NONE;
 | |
| 				UINT8 *gradeClm = NULL;
 | |
| 
 | |
| 				gradeColor = K_GetGradeColor(static_cast<gp_rank_e>(standings->grade[pnum]));
 | |
| 				if (gradeColor != SKINCOLOR_NONE)
 | |
| 				{
 | |
| 					gradeClm = R_GetTranslationColormap(TC_DEFAULT, static_cast<skincolornum_t>(gradeColor), GTC_CACHE);
 | |
| 				}
 | |
| 
 | |
| 				if (datarightofcolumn)
 | |
| 				{
 | |
| 					gradeBG = static_cast<patch_t*>(W_CachePatchName("R_INRNKR", PU_PATCH));
 | |
| 					V_DrawMappedPatch(x + 118, y, 0, gradeBG, gradeClm);
 | |
| 					V_DrawMappedPatch(x + 118 + 4, y - 1, 0, gradePtc, gradeClm);
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					gradeBG = static_cast<patch_t*>(W_CachePatchName("R_INRNKL", PU_PATCH));
 | |
| 					V_DrawMappedPatch(x - 12, y, 0, gradeBG, gradeClm);
 | |
| 					V_DrawMappedPatch(x - 12 + 3, y - 1, 0, gradePtc, gradeClm);
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			// Reverse the jitter offset
 | |
| 			if (standings->jitter[pnum] > 0)
 | |
| 				y++;
 | |
| 		}
 | |
| 
 | |
| 		y += yspacing;
 | |
| 
 | |
| 		if (verticalresults == false && i == halfway)
 | |
| 		{
 | |
| 			x = 169 + xoffset - inwardshim;
 | |
| 			y = returny;
 | |
| 
 | |
| 			datarightofcolumn = true;
 | |
| 			x2 = x + 118 + 5;
 | |
| 		}
 | |
| 
 | |
| 		if (!doreverse)
 | |
| 		{
 | |
| 			if (++i < standings->numplayers)
 | |
| 				continue;
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		if (i == 0)
 | |
| 			break;
 | |
| 		i--;
 | |
| 	}
 | |
| 	while (true);
 | |
| }
 | |
| 
 | |
| //
 | |
| // Y_RoundQueueDrawer
 | |
| //
 | |
| // Handles drawing the bottom-of-screen progression.
 | |
| // Currently requires intermission y_data for animation only.
 | |
| //
 | |
| void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations, boolean widescreen)
 | |
| {
 | |
| 	if (roundqueue.size == 0)
 | |
| 	{
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	// The following is functionally a hack.
 | |
| 	// Due to how interpolation works, it's functionally one frame behind.
 | |
| 	// So we offset certain interpolated timers by this to make our lives easier!
 | |
| 	// This permits cues handled in the ticker and visuals to match up,
 | |
| 	// like the player pin reaching the Sealed Star the frame of the fade.
 | |
| 	// We also do this rather than doing extrapoleration because that would
 | |
| 	// still put 35fps in the future. ~toast 100523
 | |
| 	SINT8 interpoffs = (R_UsingFrameInterpolation() ? 1 : 0);
 | |
| 
 | |
| 	UINT8 i;
 | |
| 
 | |
| 	UINT8 *greymap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_GREY, GTC_CACHE);
 | |
| 
 | |
| 	INT32 baseflags = 0;
 | |
| 	INT32 bufferspace = 0;
 | |
| 
 | |
| 	if (widescreen)
 | |
| 	{
 | |
| 		baseflags |= V_SNAPTOBOTTOM;
 | |
| 		bufferspace = ((vid.width/vid.dupx) - BASEVIDWIDTH) / 2;
 | |
| 	}
 | |
| 
 | |
| 	// Background pieces
 | |
| 	patch_t *queuebg_flat = static_cast<patch_t*>(W_CachePatchName("R_RMBG1", PU_PATCH));
 | |
| 	patch_t *queuebg_upwa = static_cast<patch_t*>(W_CachePatchName("R_RMBG2", PU_PATCH));
 | |
| 	patch_t *queuebg_down = static_cast<patch_t*>(W_CachePatchName("R_RMBG3", PU_PATCH));
 | |
| 	patch_t *queuebg_prize = static_cast<patch_t*>(W_CachePatchName("R_RMBG4", PU_PATCH));
 | |
| 
 | |
| 	// Progression lines
 | |
| 	patch_t *line_upwa[BPP_MAX];
 | |
| 	patch_t *line_down[BPP_MAX];
 | |
| 	patch_t *line_flat[BPP_MAX];
 | |
| 
 | |
| 	line_upwa[BPP_AHEAD] = static_cast<patch_t*>(W_CachePatchName("R_RRMLN1", PU_PATCH));
 | |
| 	line_upwa[BPP_DONE] = static_cast<patch_t*>(W_CachePatchName("R_RRMLN3", PU_PATCH));
 | |
| 	line_upwa[BPP_SHADOW] = static_cast<patch_t*>(W_CachePatchName("R_RRMLS1", PU_PATCH));
 | |
| 
 | |
| 	line_down[BPP_AHEAD] = static_cast<patch_t*>(W_CachePatchName("R_RRMLN2", PU_PATCH));
 | |
| 	line_down[BPP_DONE] = static_cast<patch_t*>(W_CachePatchName("R_RRMLN4", PU_PATCH));
 | |
| 	line_down[BPP_SHADOW] = static_cast<patch_t*>(W_CachePatchName("R_RRMLS2", PU_PATCH));
 | |
| 
 | |
| 	line_flat[BPP_AHEAD] = static_cast<patch_t*>(W_CachePatchName("R_RRMLN5", PU_PATCH));
 | |
| 	line_flat[BPP_DONE] = static_cast<patch_t*>(W_CachePatchName("R_RRMLN6", PU_PATCH));
 | |
| 	line_flat[BPP_SHADOW] = static_cast<patch_t*>(W_CachePatchName("R_RRMLS3", PU_PATCH));
 | |
| 
 | |
| 	// Progress markers
 | |
| 	patch_t *level_dot[BPP_MAIN];
 | |
| 	patch_t *bonus_dot[BPP_MAIN];
 | |
| 	patch_t *capsu_dot[BPP_MAIN];
 | |
| 	patch_t *prize_dot[BPP_MAIN];
 | |
| 
 | |
| 	level_dot[BPP_AHEAD] = static_cast<patch_t*>(W_CachePatchName("R_RRMRK2", PU_PATCH));
 | |
| 	level_dot[BPP_DONE] = static_cast<patch_t*>(W_CachePatchName("R_RRMRK1", PU_PATCH));
 | |
| 
 | |
| 	bonus_dot[BPP_AHEAD] = static_cast<patch_t*>(W_CachePatchName("R_RRMRK7", PU_PATCH));
 | |
| 	bonus_dot[BPP_DONE] = static_cast<patch_t*>(W_CachePatchName("R_RRMRK8", PU_PATCH));
 | |
| 
 | |
| 	capsu_dot[BPP_AHEAD] = static_cast<patch_t*>(W_CachePatchName("R_RRMRK3", PU_PATCH));
 | |
| 	capsu_dot[BPP_DONE] = static_cast<patch_t*>(W_CachePatchName("R_RRMRK5", PU_PATCH));
 | |
| 
 | |
| 	prize_dot[BPP_AHEAD] = static_cast<patch_t*>(W_CachePatchName("R_RRMRK4", PU_PATCH));
 | |
| 	prize_dot[BPP_DONE] = static_cast<patch_t*>(W_CachePatchName("R_RRMRK6", PU_PATCH));
 | |
| 
 | |
| 	UINT8 *colormap = NULL, *oppositemap = NULL;
 | |
| 	fixed_t playerx = 0, playery = 0;
 | |
| 	UINT8 pskin = MAXSKINS;
 | |
| 	UINT16 pcolor = SKINCOLOR_WHITE;
 | |
| 
 | |
| 	if (standings->mainplayer == MAXPLAYERS)
 | |
| 	{
 | |
| 		;
 | |
| 	}
 | |
| 	else if (playeringame[standings->mainplayer] == false)
 | |
| 	{
 | |
| 		standings->mainplayer = MAXPLAYERS;
 | |
| 	}
 | |
| 	else if (players[standings->mainplayer].spectator == false
 | |
| 		&& players[standings->mainplayer].skin < numskins
 | |
| 		&& players[standings->mainplayer].skincolor != SKINCOLOR_NONE
 | |
| 		&& players[standings->mainplayer].skincolor < numskincolors
 | |
| 	)
 | |
| 	{
 | |
| 		pskin = players[standings->mainplayer].skin;
 | |
| 		pcolor = players[standings->mainplayer].skincolor;
 | |
| 	}
 | |
| 
 | |
| 	colormap = R_GetTranslationColormap(TC_DEFAULT, static_cast<skincolornum_t>(pcolor), GTC_CACHE);
 | |
| 	oppositemap = R_GetTranslationColormap(TC_DEFAULT, static_cast<skincolornum_t>(skincolors[pcolor].invcolor), GTC_CACHE);
 | |
| 
 | |
| 	UINT8 workingqueuesize = roundqueue.size;
 | |
| 	boolean upwa = false;
 | |
| 
 | |
| 	if (roundqueue.size > 1
 | |
| 		&& roundqueue.entries[roundqueue.size - 1].rankrestricted == true
 | |
| 	)
 | |
| 	{
 | |
| 		if (roundqueue.size & 1)
 | |
| 		{
 | |
| 			upwa = true;
 | |
| 		}
 | |
| 
 | |
| 		workingqueuesize--;
 | |
| 	}
 | |
| 
 | |
| 	INT32 widthofroundqueue = 24*(workingqueuesize - 1);
 | |
| 
 | |
| 	INT32 x = (BASEVIDWIDTH - widthofroundqueue) / 2;
 | |
| 	INT32 y, basey = 167 + offset;
 | |
| 
 | |
| 	INT32 spacetospecial = 0;
 | |
| 
 | |
| 	// The following block handles horizontal easing of the
 | |
| 	// progression bar on the last non-rankrestricted round.
 | |
| 	if (standings->showrank == true)
 | |
| 	{
 | |
| 		fixed_t percentslide = 0;
 | |
| 		SINT8 deferxoffs = 0;
 | |
| 
 | |
| 		const INT32 desiredx2 = (290 + bufferspace);
 | |
| 		spacetospecial = std::max<INT32>(desiredx2 - widthofroundqueue - (24 - bufferspace), 16);
 | |
| 
 | |
| 		if (roundqueue.position == roundqueue.size)
 | |
| 		{
 | |
| 			percentslide = FRACUNIT;
 | |
| 		}
 | |
| 		else if (doanimations
 | |
| 			&& roundqueue.position == roundqueue.size-1
 | |
| 			&& timer - interpoffs <= 3*TICRATE)
 | |
| 		{
 | |
| 			const INT32 through = (3*TICRATE) - (timer - interpoffs - 1);
 | |
| 			const INT32 slidetime = (TICRATE/2);
 | |
| 
 | |
| 			if (through >= slidetime)
 | |
| 			{
 | |
| 				percentslide = FRACUNIT;
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 					percentslide = R_InterpolateFixed(
 | |
| 						(through - 1) * FRACUNIT,
 | |
| 						(through * FRACUNIT)
 | |
| 					) / slidetime;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if (percentslide != 0)
 | |
| 		{
 | |
| 			const INT32 differencetocover = (x + widthofroundqueue + spacetospecial - desiredx2);
 | |
| 
 | |
| 			if (percentslide == FRACUNIT)
 | |
| 			{
 | |
| 				x -= (differencetocover + deferxoffs);
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 				x -= Easing_OutCubic(
 | |
| 					percentslide,
 | |
| 					0,
 | |
| 					differencetocover * FRACUNIT
 | |
| 				) / FRACUNIT;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Fill in background to left edge of screen
 | |
| 	fixed_t xiter = x;
 | |
| 
 | |
| 	if (upwa == true)
 | |
| 	{
 | |
| 		xiter -= 24;
 | |
| 		V_DrawMappedPatch(xiter, basey, baseflags, queuebg_upwa, greymap);
 | |
| 	}
 | |
| 
 | |
| 	while (xiter > -bufferspace)
 | |
| 	{
 | |
| 		xiter -= 24;
 | |
| 		V_DrawMappedPatch(xiter, basey, baseflags, queuebg_flat, greymap);
 | |
| 	}
 | |
| 
 | |
| 	for (i = 0; i < workingqueuesize; i++)
 | |
| 	{
 | |
| 		// Draw the background, and grab the appropriate line, to the right of the dot
 | |
| 		patch_t **choose_line = NULL;
 | |
| 
 | |
| 		upwa ^= true;
 | |
| 		if (upwa == false)
 | |
| 		{
 | |
| 			y = basey + 4;
 | |
| 
 | |
| 			V_DrawMappedPatch(x, basey, baseflags, queuebg_down, greymap);
 | |
| 
 | |
| 			if (i+1 != workingqueuesize) // no more line?
 | |
| 			{
 | |
| 				choose_line = line_down;
 | |
| 			}
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			y = basey + 12;
 | |
| 
 | |
| 			if (i+1 != workingqueuesize) // no more line?
 | |
| 			{
 | |
| 				V_DrawMappedPatch(x, basey, baseflags, queuebg_upwa, greymap);
 | |
| 
 | |
| 				choose_line = line_upwa;
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 				V_DrawMappedPatch(x, basey, baseflags, queuebg_flat, greymap);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if (roundqueue.position == i+1)
 | |
| 		{
 | |
| 			playerx = (x * FRACUNIT);
 | |
| 			playery = (y * FRACUNIT);
 | |
| 
 | |
| 			// If there's standard progression ahead of us, visibly move along it.
 | |
| 			if (
 | |
| 				doanimations
 | |
| 				&& choose_line != NULL
 | |
| 				&& timer - interpoffs <= 2*TICRATE
 | |
| 			)
 | |
| 			{
 | |
| 				// 8 tics is chosen because it plays nice
 | |
| 				// with both the x and y distance to cover.
 | |
| 				fixed_t through = (2*TICRATE) - (timer - interpoffs - 1);;
 | |
| 
 | |
| 				if (through > 8)
 | |
| 				{
 | |
| 					if (through == 9 + interpoffs)
 | |
| 					{
 | |
| 						// Impactful landing
 | |
| 						playery += FRACUNIT;
 | |
| 					}
 | |
| 
 | |
| 					through = 8 * FRACUNIT;
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					through = R_InterpolateFixed(
 | |
| 						(through - 1) * FRACUNIT,
 | |
| 						(through * FRACUNIT)
 | |
| 					);
 | |
| 				}
 | |
| 
 | |
| 				// 24 pixels when all is said and done
 | |
| 				if (!nextmapoverride)
 | |
| 					playerx += through * 3;
 | |
| 
 | |
| 				if (upwa == false)
 | |
| 				{
 | |
| 					playery += through;
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					playery -= through;
 | |
| 				}
 | |
| 
 | |
| 				if (through > 0 && through < 8 * FRACUNIT)
 | |
| 				{
 | |
| 					// Hoparabola and a skip.
 | |
| 					const fixed_t jumpfactor = through - (4 * FRACUNIT);
 | |
| 					// jumpfactor squared goes through 36 -> 0 -> 36.
 | |
| 					// 12 pixels is an arbitrary jump height, but we match it to invert the parabola.
 | |
| 					playery -= ((12 * FRACUNIT)
 | |
| 							- (FixedMul(jumpfactor, jumpfactor) / 3)
 | |
| 					);
 | |
| 				}
 | |
| 			}
 | |
| 			// End of the moving along
 | |
| 		}
 | |
| 
 | |
| 		if (choose_line != NULL)
 | |
| 		{
 | |
| 			// Draw the line to the right of the dot
 | |
| 
 | |
| 			V_DrawMappedPatch(
 | |
| 				x - 1, basey + 11,
 | |
| 				baseflags,
 | |
| 				choose_line[BPP_SHADOW],
 | |
| 				NULL
 | |
| 			);
 | |
| 
 | |
| 			boolean lineisfull = false, recttoclear = false;
 | |
| 
 | |
| 			if (roundqueue.position > i+1)
 | |
| 			{
 | |
| 				lineisfull = true;
 | |
| 			}
 | |
| 			else if (
 | |
| 				doanimations == true
 | |
| 				&& roundqueue.position == i+1
 | |
| 				&& timer - interpoffs <= 2*TICRATE
 | |
| 			)
 | |
| 			{
 | |
| 				// 8 tics is chosen because it plays nice
 | |
| 				// with both the x and y distance to cover.
 | |
| 				const INT32 through = (2*TICRATE) - (timer - interpoffs - 1);
 | |
| 
 | |
| 				if (through == 0)
 | |
| 				{
 | |
| 					; // no change...
 | |
| 				}
 | |
| 				else if (through > 8)
 | |
| 				{
 | |
| 					lineisfull = true;
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					V_DrawMappedPatch(
 | |
| 						x - 1, basey + 12,
 | |
| 						baseflags,
 | |
| 						choose_line[BPP_DONE],
 | |
| 						colormap
 | |
| 					);
 | |
| 
 | |
| 					V_SetClipRect(
 | |
| 						playerx + FRACUNIT,
 | |
| 						0,
 | |
| 						(BASEVIDWIDTH + bufferspace) << FRACBITS,
 | |
| 						BASEVIDHEIGHT << FRACBITS,
 | |
| 						baseflags
 | |
| 					);
 | |
| 
 | |
| 					recttoclear = true;
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			V_DrawMappedPatch(
 | |
| 				x - 1, basey + 12,
 | |
| 				baseflags,
 | |
| 				choose_line[lineisfull ? BPP_DONE : BPP_AHEAD],
 | |
| 				lineisfull ? colormap : NULL
 | |
| 			);
 | |
| 
 | |
| 			if (recttoclear == true)
 | |
| 			{
 | |
| 				V_ClearClipRect();
 | |
| 			}
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			// No more line! Fill in background to right edge of screen
 | |
| 			xiter = x;
 | |
| 			while (xiter < BASEVIDWIDTH + bufferspace)
 | |
| 			{
 | |
| 				xiter += 24;
 | |
| 				V_DrawMappedPatch(xiter, basey, baseflags, queuebg_flat, greymap);
 | |
| 			}
 | |
| 
 | |
| 			// Handle special entry on the end
 | |
| 			// (has to be drawn before the semifinal dot due to overlap)
 | |
| 			if (standings->showrank == true)
 | |
| 			{
 | |
| 				const fixed_t x2 = x + spacetospecial;
 | |
| 
 | |
| 				if (roundqueue.position == roundqueue.size)
 | |
| 				{
 | |
| 					playerx = (x2 * FRACUNIT);
 | |
| 					playery = (y * FRACUNIT);
 | |
| 				}
 | |
| 				else if (
 | |
| 					doanimations == true
 | |
| 					&& roundqueue.position == roundqueue.size-1
 | |
| 					&& timer - interpoffs <= 2*TICRATE
 | |
| 				)
 | |
| 				{
 | |
| 					const INT32 through = ((2*TICRATE) - (timer - interpoffs - 1));
 | |
| 					fixed_t linefill;
 | |
| 
 | |
| 					if (through > standings->linemeter)
 | |
| 					{
 | |
| 						linefill = standings->linemeter * FRACUNIT;
 | |
| 
 | |
| 						// Small judder if there's enough time for it
 | |
| 						if (timer <= 2)
 | |
| 						{
 | |
| 							;
 | |
| 						}
 | |
| 						else if (through == (standings->linemeter + 1 + interpoffs))
 | |
| 						{
 | |
| 							playerx += FRACUNIT;
 | |
| 						}
 | |
| 						else if (through == (standings->linemeter + 2 + interpoffs))
 | |
| 						{
 | |
| 							playerx -= FRACUNIT;
 | |
| 						}
 | |
| 					}
 | |
| 					else
 | |
| 					{
 | |
| 						linefill = R_InterpolateFixed(
 | |
| 							(through - 1) * FRACUNIT,
 | |
| 							(through * FRACUNIT)
 | |
| 						);
 | |
| 					}
 | |
| 
 | |
| 					const fixed_t percent = FixedDiv(
 | |
| 							linefill,
 | |
| 							(2*TICRATE) * FRACUNIT
 | |
| 						);
 | |
| 
 | |
| 					playerx +=
 | |
| 						FixedMul(
 | |
| 							(x2 - x) * FRACUNIT,
 | |
| 							percent
 | |
| 						);
 | |
| 				}
 | |
| 
 | |
| 				// Special background bump
 | |
| 				V_DrawMappedPatch(x2 - 13, basey, baseflags, queuebg_prize, greymap);
 | |
| 
 | |
| 				// Draw the final line
 | |
| 				const fixed_t barstart = x + 6;
 | |
| 				const fixed_t barend = x2 - 6;
 | |
| 
 | |
| 				if (barend - 2 >= barstart)
 | |
| 				{
 | |
| 					boolean lineisfull = false, recttoclear = false;
 | |
| 
 | |
| 					xiter = barstart;
 | |
| 
 | |
| 					if (playerx >= (barend + 1) * FRACUNIT)
 | |
| 					{
 | |
| 						lineisfull = true;
 | |
| 					}
 | |
| 					else if (playerx <= (barstart - 1) * FRACUNIT)
 | |
| 					{
 | |
| 						;
 | |
| 					}
 | |
| 					else
 | |
| 					{
 | |
| 						const fixed_t fillend = std::min((playerx / FRACUNIT) + 2, barend);
 | |
| 
 | |
| 						while (xiter < fillend)
 | |
| 						{
 | |
| 							V_DrawMappedPatch(
 | |
| 								xiter - 1, basey + 10,
 | |
| 								baseflags,
 | |
| 								line_flat[BPP_SHADOW],
 | |
| 								NULL
 | |
| 							);
 | |
| 
 | |
| 							V_DrawMappedPatch(
 | |
| 								xiter - 1, basey + 12,
 | |
| 								baseflags,
 | |
| 								line_flat[BPP_DONE],
 | |
| 								colormap
 | |
| 							);
 | |
| 
 | |
| 							xiter += 2;
 | |
| 						}
 | |
| 
 | |
| 						// Undo the last step so we can draw the unfilled area of the patch.
 | |
| 						xiter -= 2;
 | |
| 
 | |
| 						V_SetClipRect(
 | |
| 							playerx,
 | |
| 							0,
 | |
| 							(BASEVIDWIDTH + bufferspace) << FRACBITS,
 | |
| 							BASEVIDHEIGHT << FRACBITS,
 | |
| 							baseflags
 | |
| 						);
 | |
| 
 | |
| 						recttoclear = true;
 | |
| 					}
 | |
| 
 | |
| 					while (xiter < barend)
 | |
| 					{
 | |
| 						V_DrawMappedPatch(
 | |
| 							xiter - 1, basey + 10,
 | |
| 							baseflags,
 | |
| 							line_flat[BPP_SHADOW],
 | |
| 							NULL
 | |
| 						);
 | |
| 
 | |
| 						V_DrawMappedPatch(
 | |
| 							xiter - 1, basey + 12,
 | |
| 							baseflags,
 | |
| 							line_flat[lineisfull ? BPP_DONE : BPP_AHEAD],
 | |
| 							lineisfull ? colormap : NULL
 | |
| 						);
 | |
| 
 | |
| 						xiter += 2;
 | |
| 					}
 | |
| 
 | |
| 					if (recttoclear == true)
 | |
| 					{
 | |
| 						V_ClearClipRect();
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				// Draw the final dot
 | |
| 				V_DrawMappedPatch(
 | |
| 					x2 - 8, y,
 | |
| 					baseflags,
 | |
| 					prize_dot[roundqueue.position == roundqueue.size ? BPP_DONE : BPP_AHEAD],
 | |
| 					roundqueue.position == roundqueue.size ? oppositemap : colormap
 | |
| 				);
 | |
| 			}
 | |
| 			// End of the special entry handling
 | |
| 		}
 | |
| 
 | |
| 		// Now draw the dot
 | |
| 		patch_t **chose_dot = NULL;
 | |
| 
 | |
| 		if (roundqueue.entries[i].rankrestricted == true)
 | |
| 		{
 | |
| 			// This shouldn't show up in regular play, but don't hide it entirely.
 | |
| 			chose_dot = prize_dot;
 | |
| 		}
 | |
| 		else if (
 | |
| 			roundqueue.entries[i].overridden == true
 | |
| 			|| (grandprixinfo.gp == true
 | |
| 				&& roundqueue.entries[i].gametype != GT_RACE) // roundqueue.entries[0].gametype
 | |
| 		)
 | |
| 		{
 | |
| 			if ((gametypes[roundqueue.entries[i].gametype]->rules & GTR_PRISONS) == GTR_PRISONS)
 | |
| 			{
 | |
| 				chose_dot = capsu_dot;
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 				chose_dot = bonus_dot;
 | |
| 			}
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			chose_dot = level_dot;
 | |
| 		}
 | |
| 
 | |
| 		if (chose_dot)
 | |
| 		{
 | |
| 			V_DrawMappedPatch(
 | |
| 				x - 8, y,
 | |
| 				baseflags,
 | |
| 				chose_dot[roundqueue.position >= i+1 ? BPP_DONE : BPP_AHEAD],
 | |
| 				roundqueue.position == i+1 ? oppositemap : colormap
 | |
| 			);
 | |
| 		}
 | |
| 
 | |
| 		x += 24;
 | |
| 	}
 | |
| 
 | |
| 	// Draw the player position through the round queue!
 | |
| 	if (playery != 0)
 | |
| 	{
 | |
| 		patch_t *rpmark[2];
 | |
| 		rpmark[0] = static_cast<patch_t*>(W_CachePatchName("R_RPMARK", PU_PATCH));
 | |
| 		rpmark[1] = static_cast<patch_t*>(W_CachePatchName("R_R2MARK", PU_PATCH));
 | |
| 
 | |
| 		// Change alignment
 | |
| 		playerx -= (10 * FRACUNIT);
 | |
| 		playery -= (14 * FRACUNIT);
 | |
| 
 | |
| 		if (pskin < numskins)
 | |
| 		{
 | |
| 			// Draw outline for rank icon
 | |
| 			V_DrawFixedPatch(
 | |
| 				playerx, playery,
 | |
| 				FRACUNIT,
 | |
| 				baseflags,
 | |
| 				rpmark[0],
 | |
| 				NULL
 | |
| 			);
 | |
| 
 | |
| 			// Draw the player's rank icon
 | |
| 			V_DrawFixedPatch(
 | |
| 				playerx + FRACUNIT, playery + FRACUNIT,
 | |
| 				FRACUNIT,
 | |
| 				baseflags,
 | |
| 				faceprefix[pskin][FACE_RANK],
 | |
| 				R_GetTranslationColormap(pskin, static_cast<skincolornum_t>(pcolor), GTC_CACHE)
 | |
| 			);
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			// Draw mini arrow
 | |
| 			V_DrawFixedPatch(
 | |
| 				playerx, playery,
 | |
| 				FRACUNIT,
 | |
| 				baseflags,
 | |
| 				rpmark[1],
 | |
| 				NULL
 | |
| 			);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| #define INTERBUTTONSLIDEIN (TICRATE/2)
 | |
| 
 | |
| //
 | |
| // Y_DrawIntermissionButton
 | |
| //
 | |
| // It's a button that slides at the given time
 | |
| //
 | |
| void Y_DrawIntermissionButton(INT32 startslide, INT32 through, boolean widescreen)
 | |
| {
 | |
| 	INT32 percentslide = 0;
 | |
| 	const INT32 slidetime = (TICRATE/4);
 | |
| 	boolean pressed = false;
 | |
| 
 | |
| 	if (startslide >= 0)
 | |
| 	{
 | |
| 		through = startslide;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		through -= ((TICRATE/2) + 1);
 | |
| 		pressed = (!menuactive && M_MenuConfirmHeld(0));
 | |
| 	}
 | |
| 
 | |
| 	if (through >= 0)
 | |
| 	{
 | |
| 		if (through >= slidetime)
 | |
| 		{
 | |
| 			percentslide = FRACUNIT;
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			percentslide = R_InterpolateFixed(
 | |
| 				(through - 1) * FRACUNIT,
 | |
| 				(through * FRACUNIT)
 | |
| 			) / slidetime;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (percentslide < FRACUNIT)
 | |
| 	{
 | |
| 		INT32 offset = 0;
 | |
| 
 | |
| 		if (percentslide)
 | |
| 		{
 | |
| 			offset = Easing_InCubic(
 | |
| 				percentslide,
 | |
| 				0,
 | |
| 				16 * FRACUNIT
 | |
| 			);
 | |
| 		}
 | |
| 
 | |
| 		using srb2::Draw;
 | |
| 
 | |
| 		Draw::TextElement text = Draw::TextElement().parse(pressed ? "<a_pressed>" : "<a>");
 | |
| 		Draw draw = Draw(FixedToFloat(2*FRACUNIT - offset), FixedToFloat((BASEVIDHEIGHT - 16)*FRACUNIT)).flags(widescreen ? (V_SNAPTOLEFT|V_SNAPTOBOTTOM) : 0);
 | |
| 		draw.text(text.string());
 | |
| 
 | |
| 		/*
 | |
| 		K_drawButton(
 | |
| 			2*FRACUNIT - offset,
 | |
| 			(BASEVIDHEIGHT - 16)*FRACUNIT,
 | |
| 			(widescreen
 | |
| 				? (V_SNAPTOLEFT|V_SNAPTOBOTTOM)
 | |
| 				: 0
 | |
| 			),
 | |
| 			kp_button_a[1],
 | |
| 			pressed
 | |
| 		);
 | |
| 		*/
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void Y_DrawIntermissionHeader(fixed_t x, fixed_t y, boolean gotthrough, const char *headerstring, boolean showroundnum, boolean small)
 | |
| {
 | |
| 	const INT32 v_width = (small ? BASEVIDWIDTH/2 : BASEVIDWIDTH);
 | |
| 	const fixed_t frac = (small ? FRACUNIT/2 : FRACUNIT);
 | |
| 	const INT32 small_flag = (small ? V_SPLITSCREEN : 0);
 | |
| 
 | |
| 	if (small && r_splitscreen > 1)
 | |
| 	{
 | |
| 		V_SetClipRect(
 | |
| 			0,
 | |
| 			0,
 | |
| 			v_width << FRACBITS,
 | |
| 			BASEVIDHEIGHT << FRACBITS,
 | |
| 			V_SPLITSCREEN
 | |
| 		);
 | |
| 	}
 | |
| 
 | |
| 	// Header bar
 | |
| 	patch_t *rtpbr = static_cast<patch_t*>(W_CachePatchName((small ? "R_RTPB4" : "R_RTPBR"), PU_PATCH));
 | |
| 	V_DrawFixedPatch((20 * frac) + x, (24 * frac) + y, FRACUNIT, small_flag, rtpbr, NULL);
 | |
| 
 | |
| 	fixed_t headerx, headery, headerwidth = 0;
 | |
| 
 | |
| 	if (gotthrough)
 | |
| 	{
 | |
| 		headerx = (51 * frac);
 | |
| 		headery = (7 * frac);
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		headerwidth = V_TitleCardStringWidth(headerstring, small);
 | |
| 
 | |
| 		headerx = (v_width - headerwidth) * (FRACUNIT / 2);
 | |
| 		headery = 17 * frac;
 | |
| 	}
 | |
| 
 | |
| 	// Draw round numbers
 | |
| 	if (showroundnum == true)
 | |
| 	{
 | |
| 		patch_t *roundpatch = ST_getRoundPicture(small);
 | |
| 
 | |
| 		if (roundpatch)
 | |
| 		{
 | |
| 			fixed_t roundx = (v_width * 3 * FRACUNIT) / 4;
 | |
| 
 | |
| 			if (headerwidth != 0)
 | |
| 			{
 | |
| 				const fixed_t roundoffset = (8 * frac) + (roundpatch->width * FRACUNIT);
 | |
| 
 | |
| 				roundx = headerx + roundoffset;
 | |
| 				headerx -= roundoffset/2;
 | |
| 			}
 | |
| 
 | |
| 			V_DrawFixedPatch(x + roundx, (39 * frac) + y, FRACUNIT, small_flag, roundpatch, NULL);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	V_DrawTitleCardStringFixed(x + headerx, y + headery, FRACUNIT, headerstring, small_flag, false, 0, 0, small);
 | |
| 
 | |
| 	if (gotthrough)
 | |
| 	{
 | |
| 		// GOT THROUGH ROUND
 | |
| 		patch_t *gthro = static_cast<patch_t*>(W_CachePatchName((small ? "R_GTHR4" : "R_GTHRO"), PU_PATCH));
 | |
| 		V_DrawFixedPatch((50 * frac) + x, (42 * frac) + y, FRACUNIT, small_flag, gthro, NULL);
 | |
| 	}
 | |
| 
 | |
| 	V_ClearClipRect();
 | |
| }
 | |
| 
 | |
| static void Y_DrawMapTitleString(fixed_t x, const char *name)
 | |
| {
 | |
| 	V_DrawStringScaled(
 | |
| 		x - ttlscroll,
 | |
| 		(BASEVIDHEIGHT - 73) * FRACUNIT,
 | |
| 		FRACUNIT,
 | |
| 		FRACUNIT,
 | |
| 		FRACUNIT,
 | |
| 		V_SUBTRACT | V_60TRANS,
 | |
| 		NULL,
 | |
| 		LSHI_FONT,
 | |
| 		name
 | |
| 	);
 | |
| }
 | |
| 
 | |
| static fixed_t Y_DrawMapTitle(void)
 | |
| {
 | |
| 	const char *name = bossinfo.valid && bossinfo.enemyname ?
 | |
| 		bossinfo.enemyname : mapheaderinfo[prevmap]->menuttl;
 | |
| 	char *buf = NULL;
 | |
| 
 | |
| 	if (!name[0])
 | |
| 	{
 | |
| 		buf = G_BuildMapTitle(prevmap + 1);
 | |
| 		name = buf;
 | |
| 	}
 | |
| 
 | |
| 	fixed_t w = V_StringScaledWidth(
 | |
| 		FRACUNIT,
 | |
| 		FRACUNIT,
 | |
| 		FRACUNIT,
 | |
| 		0,
 | |
| 		LSHI_FONT,
 | |
| 		name
 | |
| 	) + (16 * FRACUNIT);
 | |
| 
 | |
| 	fixed_t x = BASEVIDWIDTH * FRACUNIT;
 | |
| 
 | |
| 	while (x > -w)
 | |
| 	{
 | |
| 		Y_DrawMapTitleString(x, name);
 | |
| 		x -= w;
 | |
| 	}
 | |
| 
 | |
| 	Z_Free(buf);
 | |
| 
 | |
| 	return w;
 | |
| }
 | |
| 
 | |
| //
 | |
| // Y_IntermissionDrawer
 | |
| //
 | |
| // Called by D_Display. Nothing is modified here; all it does is draw. (SRB2Kart: er, about that...)
 | |
| // Neat concept, huh?
 | |
| //
 | |
| void Y_IntermissionDrawer(void)
 | |
| {
 | |
| 	// INFO SEGMENT
 | |
| 	// Numbers are V_DrawRightAlignedThinString as flags
 | |
| 	// resbar 1 (48,82)  5 (176, 82)
 | |
| 	// 2 (48, 96)
 | |
| 
 | |
| 	//player icon 1 (55,79) 2 (55,93) 5 (183,79)
 | |
| 
 | |
| 	if (intertype == int_none || rendermode == render_none)
 | |
| 		return;
 | |
| 
 | |
| 	fixed_t x;
 | |
| 
 | |
| 	// Checker scroll
 | |
| 	patch_t *rbgchk = static_cast<patch_t*>(W_CachePatchName("R_RBGCHK", PU_PATCH));
 | |
| 
 | |
| 	// Scrolling marquee
 | |
| 	patch_t *rrmq = static_cast<patch_t*>(W_CachePatchName("R_RRMQ", PU_PATCH));
 | |
| 
 | |
| 	fixed_t mqloop = SHORT(rrmq->width)*FRACUNIT;
 | |
| 	fixed_t chkloop = SHORT(rbgchk->width)*FRACUNIT;
 | |
| 
 | |
| 	UINT8 *bgcolor = R_GetTranslationColormap(TC_INTERMISSION, static_cast<skincolornum_t>(0), GTC_CACHE);
 | |
| 
 | |
| 	// Draw the background
 | |
| 	K_DrawMapThumbnail(0, 0, BASEVIDWIDTH<<FRACBITS, (data.encore ? V_FLIP : 0), prevmap, bgcolor);
 | |
| 
 | |
| 	for (x = -mqscroll; x < (BASEVIDWIDTH * FRACUNIT); x += mqloop)
 | |
| 	{
 | |
| 		V_DrawFixedPatch(x, 154<<FRACBITS, FRACUNIT, V_SUBTRACT, rrmq, NULL);
 | |
| 	}
 | |
| 
 | |
| 	V_DrawFixedPatch(chkscroll, 0, FRACUNIT, V_SUBTRACT, rbgchk, NULL);
 | |
| 	V_DrawFixedPatch(chkscroll - chkloop, 0, FRACUNIT, V_SUBTRACT, rbgchk, NULL);
 | |
| 
 | |
| 	fixed_t ttlloop = Y_DrawMapTitle();
 | |
| 
 | |
| 	// Animate scrolling elements if relevant
 | |
| 	if (!paused && !P_AutoPause())
 | |
| 	{
 | |
| 		mqscroll += renderdeltatics;
 | |
| 		if (mqscroll > mqloop)
 | |
| 			mqscroll %= mqloop;
 | |
| 
 | |
| 		chkscroll += renderdeltatics;
 | |
| 		if (chkscroll > chkloop)
 | |
| 			chkscroll %= chkloop;
 | |
| 
 | |
| 		ttlscroll += renderdeltatics * 2;
 | |
| 		if (ttlscroll > ttlloop)
 | |
| 			ttlscroll %= ttlloop;
 | |
| 	}
 | |
| 
 | |
| 	if (renderisnewtic)
 | |
| 	{
 | |
| 		LUA_HUD_ClearDrawList(luahuddrawlist_intermission);
 | |
| 		LUA_HookHUD(luahuddrawlist_intermission, HUD_HOOK(intermission));
 | |
| 	}
 | |
| 	LUA_HUD_DrawList(luahuddrawlist_intermission);
 | |
| 
 | |
| 	if (!LUA_HudEnabled(hud_intermissiontally))
 | |
| 		goto skiptallydrawer;
 | |
| 
 | |
| 	x = 0;
 | |
| 	if (sorttic != -1 && intertic > sorttic)
 | |
| 	{
 | |
| 		const INT32 count = (intertic - sorttic);
 | |
| 
 | |
| 		if (count < 8)
 | |
| 			x = -((count * BASEVIDWIDTH) / 8);
 | |
| 		else if (count == 8)
 | |
| 			goto skiptallydrawer;
 | |
| 		else if (count < 16)
 | |
| 			x = (((16 - count) * BASEVIDWIDTH) / 8);
 | |
| 	}
 | |
| 
 | |
| 	// Draw the header bar
 | |
| 	Y_DrawIntermissionHeader(x << FRACBITS, 0, data.gotthrough, data.headerstring, data.showroundnum, false);
 | |
| 
 | |
| 	// Returns early if there's no players to draw
 | |
| 	Y_PlayerStandingsDrawer(&data, x);
 | |
| 
 | |
| 	// Draw bottom (and top) pieces
 | |
| skiptallydrawer:
 | |
| 	if (!LUA_HudEnabled(hud_intermissionmessages))
 | |
| 		goto finalcounter;
 | |
| 
 | |
| 	// Returns early if there's no roundqueue entries to draw
 | |
| 	Y_RoundQueueDrawer(&data, 0, true, false);
 | |
| 
 | |
| 	if (netgame)
 | |
| 	{
 | |
| 		if (speedscramble != -1 && speedscramble != gamespeed)
 | |
| 		{
 | |
| 			V_DrawCenteredThinString(BASEVIDWIDTH/2, 154, highlightflags,
 | |
| 				va(M_GetText("Next race will be %s Speed!"), kartspeed_cons_t[1+speedscramble].strvalue));
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| finalcounter:
 | |
| 	if ((modeattacking == ATTACKING_NONE) && demo.recording)
 | |
| 		ST_DrawSaveReplayHint(0);
 | |
| 
 | |
| 	if (Y_CanSkipIntermission())
 | |
| 	{
 | |
| 		const tic_t end = roundqueue.size != 0 ? 3*TICRATE : TICRATE;
 | |
| 		Y_DrawIntermissionButton(INTERBUTTONSLIDEIN - intertic, end - timer, false);
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		const INT32 tickDown = (timer + 1)/TICRATE;
 | |
| 
 | |
| 		// See also k_vote.c
 | |
| 		V__DrawOneScaleString(
 | |
| 			2*FRACUNIT,
 | |
| 			(BASEVIDHEIGHT - (2+8))*FRACUNIT,
 | |
| 			FRACUNIT,
 | |
| 			0, NULL,
 | |
| 			OPPRF_FONT,
 | |
| 			va("%d", tickDown)
 | |
| 		);
 | |
| 	}
 | |
| 
 | |
| 	M_DrawMenuForeground();
 | |
| }
 | |
| 
 | |
| //
 | |
| // 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;
 | |
| 
 | |
| 	if (demo.recording)
 | |
| 		G_CheckDemoTitleEntry();
 | |
| 
 | |
| 	// Check for pause or menu up in single player
 | |
| 	if (paused || P_AutoPause())
 | |
| 		return;
 | |
| 
 | |
| 	LUA_HOOK(IntermissionThinker);
 | |
| 
 | |
| 	if (Y_CanSkipIntermission())
 | |
| 	{
 | |
| 		if (intertic < INTERBUTTONSLIDEIN)
 | |
| 		{
 | |
| 			intertic++;
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		boolean preventintertic = (intertic == INTERBUTTONSLIDEIN);
 | |
| 
 | |
| 		if (!menuactive && M_MenuConfirmPressed(0))
 | |
| 		{
 | |
| 			// If there is a roundqueue, make time for it.
 | |
| 			// Else, end instantly on button press.
 | |
| 			// Actually, give it a slight delay, so the "kaching" sound isn't cut off.
 | |
| 			const tic_t end = roundqueue.size != 0 ? 3*TICRATE : TICRATE;
 | |
| 
 | |
| 			if (intertic == INTERBUTTONSLIDEIN) // card flip hasn't started
 | |
| 			{
 | |
| 				if (sorttic != -1)
 | |
| 				{
 | |
| 					intertic = sorttic;
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					timer = end;
 | |
| 				}
 | |
| 
 | |
| 				preventintertic = false;
 | |
| 			}
 | |
| 			else if (timer >= INFINITE_TIMER && intertic >= sorttic + 16) // card done flipping
 | |
| 			{
 | |
| 				const INT32 kaching = sorttic + 16 + (2*TICRATE);
 | |
| 
 | |
| 				if (intertic < kaching)
 | |
| 				{
 | |
| 					intertic = kaching; // kaching immediately
 | |
| 				}
 | |
| 
 | |
| 				timer = end;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if (preventintertic)
 | |
| 		{
 | |
| 			return;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	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();
 | |
| 	}
 | |
| 	*/
 | |
| 
 | |
| 	if ((timer < INFINITE_TIMER && --timer <= 0)
 | |
| 		|| (intertic == endtic))
 | |
| 	{
 | |
| 		Y_EndIntermission();
 | |
| 		G_AfterIntermission();
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	// Animation sounds for roundqueue, see Y_RoundQueueDrawer
 | |
| 	if (roundqueue.size > 1
 | |
| 		&& roundqueue.position != 0
 | |
| 		&& (timer - 1) <= 2*TICRATE)
 | |
| 	{
 | |
| 		const INT32 through = ((2*TICRATE) - (timer - 1));
 | |
| 
 | |
| 		UINT8 workingqueuesize = roundqueue.size - 1;
 | |
| 
 | |
| 		if (data.showrank == true
 | |
| 			&& roundqueue.position == workingqueuesize)
 | |
| 		{
 | |
| 			// Handle special entry on the end
 | |
| 			if (through == data.linemeter && timer > 2)
 | |
| 			{
 | |
| 				S_StopSoundByID(NULL, sfx_gpmetr);
 | |
| 				S_StartSound(NULL, sfx_kc50);
 | |
| 			}
 | |
| 			else if (through == 0)
 | |
| 			{
 | |
| 				S_StartSound(NULL, sfx_gpmetr);
 | |
| 			}
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			if (data.showrank == false
 | |
| 				&& roundqueue.entries[workingqueuesize].rankrestricted == true)
 | |
| 			{
 | |
| 				workingqueuesize--;
 | |
| 			}
 | |
| 
 | |
| 			if (through == 9
 | |
| 				&& roundqueue.position <= workingqueuesize)
 | |
| 			{
 | |
| 				// Impactful landing
 | |
| 				S_StartSound(NULL, sfx_kc50);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (intertic < TICRATE || endtic != -1)
 | |
| 	{
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (data.rankingsmode && intertic & 1)
 | |
| 	{
 | |
| 		memset(data.jitter, 0, sizeof (data.jitter));
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (intertype == int_time || intertype == int_score)
 | |
| 	{
 | |
| 		{
 | |
| 			if (!data.rankingsmode && sorttic != -1 && (intertic >= sorttic + 8))
 | |
| 			{
 | |
| 				Y_MidIntermission();
 | |
| 				Y_CalculateMatchData(1, Y_CompareRank);
 | |
| 			}
 | |
| 
 | |
| 			if (data.rankingsmode && intertic > sorttic+16+(2*TICRATE))
 | |
| 			{
 | |
| 				INT32 q=0,r=0;
 | |
| 				boolean kaching = true;
 | |
| 
 | |
| 				for (q = 0; q < data.numplayers; q++)
 | |
| 				{
 | |
| 					if (data.num[q] == MAXPLAYERS
 | |
| 						|| !data.increase[data.num[q]]
 | |
| 						|| data.increase[data.num[q]] == INT16_MIN)
 | |
| 					{
 | |
| 						continue;
 | |
| 					}
 | |
| 
 | |
| 					r++;
 | |
| 					data.jitter[data.num[q]] = 1;
 | |
| 
 | |
| 					// Player can skip the tally, kaching!
 | |
| 					if (Y_CanSkipIntermission() && timer < INFINITE_TIMER)
 | |
| 					{
 | |
| 						data.increase[data.num[q]] = 0;
 | |
| 					}
 | |
| 
 | |
| 					if (powertype != PWRLV_DISABLED)
 | |
| 					{
 | |
| 						// Power Levels
 | |
| 						if (abs(data.increase[data.num[q]]) < 10)
 | |
| 						{
 | |
| 							// Not a lot of point increase left, just set to 0 instantly
 | |
| 							data.increase[data.num[q]] = 0;
 | |
| 						}
 | |
| 						else
 | |
| 						{
 | |
| 							SINT8 remove = 0; // default (should not happen)
 | |
| 
 | |
| 							if (data.increase[data.num[q]] < 0)
 | |
| 								remove = -10;
 | |
| 							else if (data.increase[data.num[q]] > 0)
 | |
| 								remove = 10;
 | |
| 
 | |
| 							// Remove 10 points at a time
 | |
| 							data.increase[data.num[q]] -= remove;
 | |
| 
 | |
| 							// Still not zero, no kaching yet
 | |
| 							if (data.increase[data.num[q]] != 0)
 | |
| 								kaching = false;
 | |
| 						}
 | |
| 					}
 | |
| 					else
 | |
| 					{
 | |
| 						// Basic bitch points
 | |
| 						if (data.increase[data.num[q]])
 | |
| 						{
 | |
| 							if (--data.increase[data.num[q]])
 | |
| 								kaching = false;
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				if (r)
 | |
| 				{
 | |
| 					S_StartSound(NULL, (kaching ? sfx_chchng : sfx_ptally));
 | |
| 					Y_CalculateMatchData(2, Y_CompareRank);
 | |
| 				}
 | |
| 				/*else -- This is how to define an endtic, but we currently use timer for both SP and MP.
 | |
| 					endtic = intertic + 3*TICRATE;*/
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| boolean Y_ShouldDoIntermission(void)
 | |
| {
 | |
| 	// no intermission for GP events
 | |
| 	if ((grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE)
 | |
| 	// or for failing in time attack mode
 | |
| 	|| (modeattacking && (players[consoleplayer].pflags & PF_NOCONTEST))
 | |
| 	// or for explicit requested skip (outside of modeattacking)
 | |
| 	|| (modeattacking == ATTACKING_NONE && skipstats != 0)
 | |
| 	// or tutorial skip material
 | |
| 	|| (nextmapoverride == NEXTMAP_TUTORIALCHALLENGE+1 || tutorialchallenge != TUTORIALSKIP_NONE)
 | |
| 	// or title screen attract demos
 | |
| 	|| (demo.playback && demo.attract == DEMO_ATTRACT_TITLE))
 | |
| 	{
 | |
| 		return false;
 | |
| 	}
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| //
 | |
| // Y_DetermineIntermissionType
 | |
| //
 | |
| // Determines the intermission type from the current gametype.
 | |
| //
 | |
| void Y_DetermineIntermissionType(void)
 | |
| {
 | |
| 	// no intermission for GP events
 | |
| 	if (!Y_ShouldDoIntermission())
 | |
| 	{
 | |
| 		intertype = int_none;
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	// set initially
 | |
| 	intertype = static_cast<intertype_t>(gametypes[gametype]->intermission);
 | |
| 
 | |
| 	// special cases
 | |
| 	if (intertype == int_scoreortimeattack)
 | |
| 	{
 | |
| 		UINT8 i = 0, nump = 0;
 | |
| 		for (i = 0; i < MAXPLAYERS; i++)
 | |
| 		{
 | |
| 			if (!playeringame[i] || players[i].spectator)
 | |
| 				continue;
 | |
| 			nump++;
 | |
| 		}
 | |
| 		intertype = (nump < 2 ? int_time : int_score);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static UINT8 Y_PlayersBestPossiblePosition(player_t *const player)
 | |
| {
 | |
| 	UINT8 bestPossiblePosition = MAXPLAYERS + 1;
 | |
| 	UINT8 i = UINT8_MAX;
 | |
| 
 | |
| 	if ((player->pflags & PF_NOCONTEST) == 0)
 | |
| 	{
 | |
| 		if (player->exiting)
 | |
| 		{
 | |
| 			// They are finished, so their position is set in stone.
 | |
| 			bestPossiblePosition = player->position;
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			// If they're NOT finished, then check what their points could be
 | |
| 			// if they finished in the first available position.
 | |
| 			bestPossiblePosition = 1;
 | |
| 
 | |
| 			for (i = 0; i < MAXPLAYERS; i++)
 | |
| 			{
 | |
| 				player_t *const other = &players[i];
 | |
| 
 | |
| 				if (!playeringame[i] || other->spectator)
 | |
| 				{
 | |
| 					continue;
 | |
| 				}
 | |
| 
 | |
| 				if (other == player)
 | |
| 				{
 | |
| 					continue;
 | |
| 				}
 | |
| 
 | |
| 				if (other->exiting)
 | |
| 				{
 | |
| 					bestPossiblePosition = std::max<UINT8>(bestPossiblePosition, other->position + 1);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return bestPossiblePosition;
 | |
| }
 | |
| 
 | |
| static UINT32 Y_EstimatePodiumScore(player_t *const player, UINT8 numPlaying)
 | |
| {
 | |
| 	UINT8 pos = Y_PlayersBestPossiblePosition(player);
 | |
| 	UINT32 ourScore = player->score;
 | |
| 
 | |
| 	if (pos < numPlaying)
 | |
| 	{
 | |
| 		ourScore += K_CalculateGPRankPoints(pos, numPlaying);
 | |
| 	}
 | |
| 
 | |
| 	return ourScore;
 | |
| }
 | |
| 
 | |
| static boolean Y_GuaranteedGPFirstPlace(void)
 | |
| {
 | |
| 	player_t *bestInParty = nullptr;
 | |
| 	UINT32 bestPartyScore = 0;
 | |
| 
 | |
| 	UINT8 numPlaying = spectateGriefed;
 | |
| 
 | |
| 	UINT8 i = UINT8_MAX;
 | |
| 
 | |
| 	// Quick first loop to count players.
 | |
| 	for (i = 0; i < MAXPLAYERS; i++)
 | |
| 	{
 | |
| 		player_t *const comparePlayer = &players[i];
 | |
| 
 | |
| 		if (!playeringame[i] || comparePlayer->spectator)
 | |
| 		{
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		numPlaying++;
 | |
| 	}
 | |
| 
 | |
| 	// Iterate our party, estimate the best possible exiting score out of all of them.
 | |
| 	for (i = 0; i <= r_splitscreen; i++)
 | |
| 	{
 | |
| 		player_t *const comparePlayer = &players[displayplayers[i]];
 | |
| 
 | |
| 		if (comparePlayer->spectator)
 | |
| 		{
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		if (!comparePlayer->exiting)
 | |
| 		{
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		UINT32 newScore = Y_EstimatePodiumScore(comparePlayer, numPlaying);
 | |
| 		if (bestInParty == nullptr || newScore > bestPartyScore)
 | |
| 		{
 | |
| 			bestInParty = comparePlayer;
 | |
| 			bestPartyScore = newScore;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (bestInParty == nullptr)
 | |
| 	{
 | |
| 		// No partied players are actually available,
 | |
| 		// so always use the regular intermission music.
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	// Iterate through all players not belonging to our party.
 | |
| 	// Estimate the possible scores that they could get.
 | |
| 	// Play special music only if none of these scores beat ours!
 | |
| 	for (i = 0; i < MAXPLAYERS; i++)
 | |
| 	{
 | |
| 		player_t *comparePlayer = &players[i];
 | |
| 
 | |
| 		if (!playeringame[i] || comparePlayer->spectator)
 | |
| 		{
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		if (P_IsPartyPlayer(comparePlayer))
 | |
| 		{
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		if (Y_EstimatePodiumScore(comparePlayer, numPlaying) >= bestPartyScore)
 | |
| 		{
 | |
| 			// NO, there is a chance that we will NOT finish first!
 | |
| 			// You may still be able to finish first, but it is NOT guaranteed.
 | |
| 			return false;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// There is an overwhelmingly good chance
 | |
| 	// that we are finishing in first place.
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| void Y_PlayIntermissionMusic(void)
 | |
| {
 | |
| 	if (modeattacking != ATTACKING_NONE)
 | |
| 	{
 | |
| 		Music_Remap("intermission", "timent");
 | |
| 	}
 | |
| 	else if (grandprixinfo.gp == true
 | |
| 		&& grandprixinfo.cup != nullptr
 | |
| 		&& roundqueue.size > 0
 | |
| 		&& roundqueue.roundnum >= grandprixinfo.cup->numlevels)
 | |
| 	{
 | |
| 		if (Y_GuaranteedGPFirstPlace())
 | |
| 		{
 | |
| 			Music_Remap("intermission", "gprnds");
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			Music_Remap("intermission", "gprnd5");
 | |
| 		}
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		Music_Remap("intermission", "racent");
 | |
| 	}
 | |
| 
 | |
| 	if (!Music_Playing("intermission"))
 | |
| 		Music_Play("intermission");
 | |
| }
 | |
| 
 | |
| //
 | |
| // Y_StartIntermission
 | |
| //
 | |
| // Called by G_DoCompleted. Sets up data for intermission drawer/ticker.
 | |
| //
 | |
| void Y_StartIntermission(void)
 | |
| {
 | |
| 	UINT8 i = 0, nump = 0;
 | |
| 	for (i = 0; i < MAXPLAYERS; i++)
 | |
| 	{
 | |
| 		if (!playeringame[i] || players[i].spectator)
 | |
| 			continue;
 | |
| 		nump++;
 | |
| 	}
 | |
| 
 | |
| 	intertic = -1;
 | |
| 
 | |
| #ifdef PARANOIA
 | |
| 	if (endtic != -1)
 | |
| 		I_Error("endtic is dirty");
 | |
| #endif
 | |
| 
 | |
| 	// set player Power Level type
 | |
| 	powertype = K_UsingPowerLevels();
 | |
| 
 | |
| 	// determine the tic the intermission ends
 | |
| 	// Technically cv_inttime is saved to demos... but this permits having extremely long timers for post-netgame chatting without stranding you on the intermission in netreplays.
 | |
| 	if (!K_CanChangeRules(false))
 | |
| 	{
 | |
| 		timer = 10*TICRATE;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		timer = cv_inttime.value*TICRATE;
 | |
| 	}
 | |
| 
 | |
| 	// determine the tic everybody's scores/PWR starts getting sorted
 | |
| 	sorttic = -1;
 | |
| 	if (!timer)
 | |
| 		;
 | |
| 	else if (
 | |
| 		( // Match Race or Time Attack
 | |
| 			netgame == false
 | |
| 			&& grandprixinfo.gp == false
 | |
| 		)
 | |
| 		&& (
 | |
| 			modeattacking != ATTACKING_NONE // Definitely never another map
 | |
| 			|| ( // Any level sequence?
 | |
| 				roundqueue.size == 0 // No maps queued, points aren't relevant
 | |
| 				|| roundqueue.position == 0 // OR points from this round will be discarded
 | |
| 			)
 | |
| 		)
 | |
| 	)
 | |
| 	{
 | |
| 		// No PWR/global score, skip it
 | |
| 		// (the above is influenced by G_GetNextMap)
 | |
| 		timer /= 2;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		// Minimum two seconds for match results, then two second slideover approx halfway through
 | |
| 		sorttic = std::max<INT32>((timer/2) - 2*TICRATE, 2*TICRATE);
 | |
| 	}
 | |
| 
 | |
| 	// TODO: code's a mess, I'm just making it extra clear
 | |
| 	// that this piece of code is supposed to take priority
 | |
| 	// over the above. :)
 | |
| 	if (Y_CanSkipIntermission())
 | |
| 	{
 | |
| 		timer = INFINITE_TIMER; // doesn't count down
 | |
| 
 | |
| 		if (sorttic != -1)
 | |
| 		{
 | |
| 			// Will start immediately, but must be triggered.
 | |
| 			// Needs to be TICRATE to bypass a condition in Y_Ticker.
 | |
| 			sorttic = TICRATE;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// 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 (prevmap >= nummapheaders || !mapheaderinfo[prevmap])
 | |
| 		I_Error("Y_StartIntermission: Internal map ID %d not found (nummapheaders = %d)", prevmap, nummapheaders);
 | |
| 
 | |
| 	switch (intertype)
 | |
| 	{
 | |
| 		case int_score:
 | |
| 		{
 | |
| 			// Calculate who won
 | |
| 			Y_CalculateMatchData(0, Y_CompareScore);
 | |
| 			break;
 | |
| 		}
 | |
| 		case int_time:
 | |
| 		{
 | |
| 			// Calculate who won
 | |
| 			Y_CalculateMatchData(0, Y_CompareTime);
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		case int_none:
 | |
| 		default:
 | |
| 			break;
 | |
| 	}
 | |
| 
 | |
| 	if (powertype != PWRLV_DISABLED)
 | |
| 	{
 | |
| 		for (i = 0; i < MAXPLAYERS; i++)
 | |
| 		{
 | |
| 			// Kind of a hack to do this here,
 | |
| 			// but couldn't think of a better way.
 | |
| 			data.increase[i] = K_FinalPowerIncrement(
 | |
| 				&players[i],
 | |
| 				clientpowerlevels[i][powertype],
 | |
| 				clientPowerAdd[i]
 | |
| 			);
 | |
| 		}
 | |
| 
 | |
| 		K_CashInPowerLevels();
 | |
| 		SV_BumpMatchStats();
 | |
| 	}
 | |
| 
 | |
| 	if (!timer)
 | |
| 	{
 | |
| 		Y_EndIntermission();
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	G_SetGamestate(GS_INTERMISSION);
 | |
| 
 | |
| 	if (demo.playback)
 | |
| 	{
 | |
| 		// Replay menu is inacessible here.
 | |
| 		// Press A to continue!
 | |
| 		M_ClearMenus(true);
 | |
| 	}
 | |
| 
 | |
| 	if (musiccountdown == 0)
 | |
| 	{
 | |
| 		Y_PlayIntermissionMusic();
 | |
| 	}
 | |
| 
 | |
| 	S_ShowMusicCredit(); // Always call
 | |
| 
 | |
| 	LUA_HUD_DestroyDrawList(luahuddrawlist_intermission);
 | |
| 	luahuddrawlist_intermission = LUA_HUD_CreateDrawList();
 | |
| 
 | |
| 	if (roundqueue.size > 0 && roundqueue.position == roundqueue.size)
 | |
| 	{
 | |
| 		Automate_Run(AEV_QUEUEEND);
 | |
| 	}
 | |
| 
 | |
| 	Automate_Run(AEV_INTERMISSIONSTART);
 | |
| 	bgpatch = static_cast<patch_t*>(W_CachePatchName("MENUBG", PU_STATIC));
 | |
| 	widebgpatch = static_cast<patch_t*>(W_CachePatchName("WEIRDRES", PU_STATIC));
 | |
| }
 | |
| 
 | |
| // ======
 | |
| 
 | |
| //
 | |
| // Y_MidIntermission
 | |
| //
 | |
| void Y_MidIntermission(void)
 | |
| {
 | |
| 	// Replacing bots that fail out of play
 | |
| 	K_RetireBots();
 | |
| 
 | |
| 	// If tournament play is not in action...
 | |
| 	if (roundqueue.position == 0)
 | |
| 	{
 | |
| 		// Unset player teams in anticipation of P_ShuffleTeams
 | |
| 
 | |
| 		UINT8 i;
 | |
| 		for (i = 0; i < MAXPLAYERS; i++)
 | |
| 		{
 | |
| 			players[i].team = TEAM_UNASSIGNED;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| //
 | |
| // Y_EndIntermission
 | |
| //
 | |
| void Y_EndIntermission(void)
 | |
| {
 | |
| 	if (!data.rankingsmode)
 | |
| 	{
 | |
| 		Y_MidIntermission();
 | |
| 	}
 | |
| 
 | |
| 	Y_UnloadData();
 | |
| 
 | |
| 	endtic = -1;
 | |
| 	sorttic = -1;
 | |
| 	intertype = int_none;
 | |
| }
 | |
| 
 | |
| #define UNLOAD(x) if (x) {Patch_Free(x);} x = NULL;
 | |
| #define CLEANUP(x) x = NULL;
 | |
| 
 | |
| //
 | |
| // Y_UnloadData
 | |
| //
 | |
| static void Y_UnloadData(void)
 | |
| {
 | |
| 	// In hardware mode, don't Z_ChangeTag a pointer returned by W_CachePatchName().
 | |
| 	// It doesn't work and is unnecessary.
 | |
| 	if (rendermode != render_soft)
 | |
| 		return;
 | |
| 
 | |
| 	// unload the background patches
 | |
| 	UNLOAD(bgpatch);
 | |
| 	UNLOAD(widebgpatch);
 | |
| 	UNLOAD(bgtile);
 | |
| 	UNLOAD(interpic);
 | |
| }
 |