mirror of
				https://github.com/KartKrewDev/RingRacers.git
				synced 2025-10-30 08:01:28 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			3971 lines
		
	
	
	
		
			97 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			3971 lines
		
	
	
	
		
			97 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.
 | 
						|
// Copyright (C) 2016 by Kay "Kaito" Sinclaire.
 | 
						|
//
 | 
						|
// 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  m_cond.c
 | 
						|
/// \brief Challenges internals
 | 
						|
 | 
						|
#include "m_cond.h"
 | 
						|
#include "m_random.h" // M_RandomKey
 | 
						|
#include "doomstat.h"
 | 
						|
#include "z_zone.h"
 | 
						|
 | 
						|
#include "hu_stuff.h" // CEcho
 | 
						|
#include "v_video.h" // video flags
 | 
						|
 | 
						|
#include "g_game.h" // record info
 | 
						|
#include "r_skins.h" // numskins
 | 
						|
#include "k_follower.h"
 | 
						|
#include "r_draw.h" // R_GetColorByName
 | 
						|
#include "s_sound.h" // S_StartSound
 | 
						|
 | 
						|
#include "k_kart.h" // K_IsPLayerLosing
 | 
						|
#include "k_grandprix.h" // grandprixinfo
 | 
						|
#include "k_battle.h" // battleprisons
 | 
						|
#include "k_specialstage.h" // specialstageinfo
 | 
						|
#include "k_podium.h"
 | 
						|
#include "k_pwrlv.h"
 | 
						|
#include "k_profiles.h"
 | 
						|
 | 
						|
gamedata_t *gamedata = NULL;
 | 
						|
boolean netUnlocked[MAXUNLOCKABLES];
 | 
						|
 | 
						|
// The meat of this system lies in condition sets
 | 
						|
conditionset_t conditionSets[MAXCONDITIONSETS];
 | 
						|
 | 
						|
// Emblem locations
 | 
						|
emblem_t emblemlocations[MAXEMBLEMS];
 | 
						|
 | 
						|
// Unlockables
 | 
						|
unlockable_t unlockables[MAXUNLOCKABLES];
 | 
						|
 | 
						|
// Highest used emblem ID
 | 
						|
INT32 numemblems = 0;
 | 
						|
 | 
						|
// The challenge that will truly let the games begin.
 | 
						|
UINT16 gamestartchallenge = 600; // 601
 | 
						|
 | 
						|
// Create a new gamedata_t, for start-up
 | 
						|
void M_NewGameDataStruct(void)
 | 
						|
{
 | 
						|
	gamedata = Z_Calloc(sizeof (gamedata_t), PU_STATIC, NULL);
 | 
						|
	M_ClearSecrets();
 | 
						|
	G_ClearRecords();
 | 
						|
}
 | 
						|
 | 
						|
void M_PopulateChallengeGrid(void)
 | 
						|
{
 | 
						|
	UINT16 i, j;
 | 
						|
	UINT16 numunlocks = 0, nummajorunlocks = 0, numempty = 0;
 | 
						|
	UINT16 selection[2][MAXUNLOCKABLES + (CHALLENGEGRIDHEIGHT-1)];
 | 
						|
	UINT16 majorcompact = 2;
 | 
						|
 | 
						|
	if (gamedata->challengegrid != NULL)
 | 
						|
	{
 | 
						|
		// todo tweak your grid if unlocks are changed
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	// Go through unlockables
 | 
						|
	for (i = 0; i < MAXUNLOCKABLES; ++i)
 | 
						|
	{
 | 
						|
		if (!unlockables[i].conditionset)
 | 
						|
		{
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		if (unlockables[i].majorunlock)
 | 
						|
		{
 | 
						|
			selection[1][nummajorunlocks++] = i;
 | 
						|
			//CONS_Printf(" found %d (LARGE)\n", selection[1][nummajorunlocks-1]);
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		selection[0][numunlocks++] = i;
 | 
						|
		//CONS_Printf(" found %d\n", selection[0][numunlocks-1]);
 | 
						|
	}
 | 
						|
 | 
						|
	gamedata->challengegridwidth = 0;
 | 
						|
 | 
						|
	if (numunlocks + nummajorunlocks == 0)
 | 
						|
	{
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	if (nummajorunlocks)
 | 
						|
	{
 | 
						|
		// Getting the number of 2-highs you can fit into two adjacent columns.
 | 
						|
		UINT16 majorpad = (CHALLENGEGRIDHEIGHT/2);
 | 
						|
		numempty = nummajorunlocks%majorpad;
 | 
						|
		majorpad = (nummajorunlocks+(majorpad-1))/majorpad;
 | 
						|
 | 
						|
		gamedata->challengegridwidth = majorpad*2;
 | 
						|
		numempty *= 4;
 | 
						|
 | 
						|
#if (CHALLENGEGRIDHEIGHT % 2)
 | 
						|
		// One extra empty per column.
 | 
						|
		numempty += gamedata->challengegridwidth;
 | 
						|
#endif
 | 
						|
 | 
						|
		//CONS_Printf("%d major unlocks means width of %d, numempty of %d\n", nummajorunlocks, gamedata->challengegridwidth, numempty);
 | 
						|
	}
 | 
						|
 | 
						|
	if (numunlocks > numempty)
 | 
						|
	{
 | 
						|
		// Getting the number of extra columns to store normal unlocks
 | 
						|
		UINT16 temp = ((numunlocks - numempty) + (CHALLENGEGRIDHEIGHT-1))/CHALLENGEGRIDHEIGHT;
 | 
						|
		gamedata->challengegridwidth += temp;
 | 
						|
		majorcompact = 1;
 | 
						|
		//CONS_Printf("%d normal unlocks means %d extra entries, additional width of %d\n", numunlocks, (numunlocks - numempty), temp);
 | 
						|
	}
 | 
						|
	else if (challengegridloops)
 | 
						|
	{
 | 
						|
		// Another case where offset large tiles are permitted.
 | 
						|
		majorcompact = 1;
 | 
						|
	}
 | 
						|
 | 
						|
	gamedata->challengegrid = Z_Malloc(
 | 
						|
		(gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT * sizeof(UINT16)),
 | 
						|
		PU_STATIC, NULL);
 | 
						|
 | 
						|
	if (!gamedata->challengegrid)
 | 
						|
	{
 | 
						|
		I_Error("M_PopulateChallengeGrid: was not able to allocate grid");
 | 
						|
	}
 | 
						|
 | 
						|
	for (i = 0; i < (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT); ++i)
 | 
						|
	{
 | 
						|
		gamedata->challengegrid[i] = MAXUNLOCKABLES;
 | 
						|
	}
 | 
						|
 | 
						|
	// Attempt to place all large tiles first.
 | 
						|
	if (nummajorunlocks)
 | 
						|
	{
 | 
						|
		// You lose one from CHALLENGEGRIDHEIGHT because it is impossible to place a 2-high tile on the bottom row.
 | 
						|
		// You lose one from the width if it doesn't loop.
 | 
						|
		// You divide by two if the grid is so compacted that large tiles can't be in offset columns.
 | 
						|
		UINT16 numspots = (gamedata->challengegridwidth - (challengegridloops ? 0 : majorcompact))
 | 
						|
				* ((CHALLENGEGRIDHEIGHT-1) / majorcompact);
 | 
						|
		// 0 is row, 1 is column
 | 
						|
		INT16 *quickcheck = Z_Calloc(sizeof(INT16) * 2 * numspots, PU_STATIC, NULL);
 | 
						|
 | 
						|
		// Prepare the easy-grab spots.
 | 
						|
		for (i = 0; i < numspots; i++)
 | 
						|
		{
 | 
						|
			quickcheck[i * 2 + 0] = i%(CHALLENGEGRIDHEIGHT-1);
 | 
						|
			quickcheck[i * 2 + 1] = majorcompact * i/(CHALLENGEGRIDHEIGHT-1);
 | 
						|
		}
 | 
						|
 | 
						|
		// Place in random valid locations.
 | 
						|
		while (nummajorunlocks > 0 && numspots > 0)
 | 
						|
		{
 | 
						|
			INT16 row, col;
 | 
						|
			j = M_RandomKey(numspots);
 | 
						|
			row = quickcheck[j * 2 + 0];
 | 
						|
			col =  quickcheck[j * 2 + 1];
 | 
						|
 | 
						|
			// We always take from selection[1][] in order, but the PLACEMENT is still random.
 | 
						|
			nummajorunlocks--;
 | 
						|
 | 
						|
			//CONS_Printf("--- %d (LARGE) placed at (%d, %d)\n", selection[1][nummajorunlocks], row, col);
 | 
						|
 | 
						|
			i = row + (col * CHALLENGEGRIDHEIGHT);
 | 
						|
			gamedata->challengegrid[i] = gamedata->challengegrid[i+1] = selection[1][nummajorunlocks];
 | 
						|
			if (col == gamedata->challengegridwidth-1)
 | 
						|
			{
 | 
						|
				i = row;
 | 
						|
			}
 | 
						|
			else
 | 
						|
			{
 | 
						|
				i += CHALLENGEGRIDHEIGHT;
 | 
						|
			}
 | 
						|
			gamedata->challengegrid[i] = gamedata->challengegrid[i+1] = selection[1][nummajorunlocks];
 | 
						|
 | 
						|
			if (nummajorunlocks == 0)
 | 
						|
			{
 | 
						|
				break;
 | 
						|
			}
 | 
						|
 | 
						|
			for (i = 0; i < numspots; i++)
 | 
						|
			{
 | 
						|
quickcheckagain:
 | 
						|
				if (abs((quickcheck[i * 2 + 0]) - (row)) <= 1 // Row distance
 | 
						|
					&& (abs((quickcheck[i * 2 + 1]) - (col)) <= 1 // Column distance
 | 
						|
					|| (quickcheck[i * 2 + 1] == 0 && col == gamedata->challengegridwidth-1) // Wraparounds l->r
 | 
						|
					|| (quickcheck[i * 2 + 1] == gamedata->challengegridwidth-1 && col == 0))) // Wraparounds r->l
 | 
						|
				{
 | 
						|
					numspots--;  // Remove from possible indicies
 | 
						|
					if (i == numspots)
 | 
						|
						break;
 | 
						|
					// Shuffle remaining so we can keep on using M_RandomKey
 | 
						|
					quickcheck[i * 2 + 0] = quickcheck[numspots * 2 + 0];
 | 
						|
					quickcheck[i * 2 + 1] = quickcheck[numspots * 2 + 1];
 | 
						|
					// Woah there - we've gotta check the one that just got put in our place.
 | 
						|
					goto quickcheckagain;
 | 
						|
				}
 | 
						|
				continue;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
#if (CHALLENGEGRIDHEIGHT == 4)
 | 
						|
		while (nummajorunlocks > 0)
 | 
						|
		{
 | 
						|
			UINT16 unlocktomoveup = MAXUNLOCKABLES;
 | 
						|
 | 
						|
			j = gamedata->challengegridwidth-1;
 | 
						|
 | 
						|
			// Attempt to fix our whoopsie.
 | 
						|
			for (i = 0; i < j; i++)
 | 
						|
			{
 | 
						|
				if (gamedata->challengegrid[1 + (i*CHALLENGEGRIDHEIGHT)] != MAXUNLOCKABLES
 | 
						|
					&& gamedata->challengegrid[(i*CHALLENGEGRIDHEIGHT)] == MAXUNLOCKABLES)
 | 
						|
					break;
 | 
						|
			}
 | 
						|
 | 
						|
			if (i == j)
 | 
						|
			{
 | 
						|
				break;
 | 
						|
			}
 | 
						|
 | 
						|
			unlocktomoveup = gamedata->challengegrid[1 + (i*CHALLENGEGRIDHEIGHT)];
 | 
						|
 | 
						|
			if (i == 0
 | 
						|
				&& challengegridloops
 | 
						|
				&& (gamedata->challengegrid [1 + (j*CHALLENGEGRIDHEIGHT)]
 | 
						|
					== gamedata->challengegrid[1]))
 | 
						|
				;
 | 
						|
			else
 | 
						|
			{
 | 
						|
				j = i + 1;
 | 
						|
			}
 | 
						|
 | 
						|
			nummajorunlocks--;
 | 
						|
 | 
						|
			// Push one pair up.
 | 
						|
			gamedata->challengegrid[(i*CHALLENGEGRIDHEIGHT)] = gamedata->challengegrid[(j*CHALLENGEGRIDHEIGHT)] = unlocktomoveup;
 | 
						|
			// Wedge the remaining four underneath.
 | 
						|
			gamedata->challengegrid[2 + (i*CHALLENGEGRIDHEIGHT)] = gamedata->challengegrid[2 + (j*CHALLENGEGRIDHEIGHT)] = selection[1][nummajorunlocks];
 | 
						|
			gamedata->challengegrid[3 + (i*CHALLENGEGRIDHEIGHT)] = gamedata->challengegrid[3 + (j*CHALLENGEGRIDHEIGHT)] = selection[1][nummajorunlocks];
 | 
						|
		}
 | 
						|
#endif
 | 
						|
 | 
						|
		if (nummajorunlocks > 0)
 | 
						|
		{
 | 
						|
			UINT16 widthtoprint = gamedata->challengegridwidth;
 | 
						|
			Z_Free(gamedata->challengegrid);
 | 
						|
			gamedata->challengegrid = NULL;
 | 
						|
 | 
						|
			I_Error("M_PopulateChallengeGrid: was not able to populate %d large tiles (width %d)", nummajorunlocks, widthtoprint);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	numempty = 0;
 | 
						|
	// Space out empty entries to pepper into unlock list
 | 
						|
	for (i = 0; i < gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT; i++)
 | 
						|
	{
 | 
						|
		if (gamedata->challengegrid[i] < MAXUNLOCKABLES)
 | 
						|
		{
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		numempty++;
 | 
						|
	}
 | 
						|
 | 
						|
	if (numunlocks > numempty)
 | 
						|
	{
 | 
						|
		gamedata->challengegridwidth = 0;
 | 
						|
		Z_Free(gamedata->challengegrid);
 | 
						|
		gamedata->challengegrid = NULL;
 | 
						|
 | 
						|
		I_Error("M_PopulateChallengeGrid: %d small unlocks vs %d empty spaces (%d gap)", numunlocks, numempty, (numunlocks-numempty));
 | 
						|
	}
 | 
						|
 | 
						|
	//CONS_Printf(" %d unlocks vs %d empty spaces\n", numunlocks, numempty);
 | 
						|
 | 
						|
	while (numunlocks < numempty)
 | 
						|
	{
 | 
						|
		//CONS_Printf(" adding empty)\n");
 | 
						|
		selection[0][numunlocks++] = MAXUNLOCKABLES;
 | 
						|
	}
 | 
						|
 | 
						|
	// Fill the remaining spots with random ordinary unlocks (and empties).
 | 
						|
	for (i = 0; i < gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT; i++)
 | 
						|
	{
 | 
						|
		if (gamedata->challengegrid[i] < MAXUNLOCKABLES)
 | 
						|
		{
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		j = M_RandomKey(numunlocks); // Get an entry
 | 
						|
		gamedata->challengegrid[i] = selection[0][j]; // Set that entry
 | 
						|
		//CONS_Printf(" %d placed at (%d, %d)\n", selection[0][j], i/CHALLENGEGRIDHEIGHT, i%CHALLENGEGRIDHEIGHT);
 | 
						|
		numunlocks--; // Remove from possible indicies
 | 
						|
		selection[0][j] = selection[0][numunlocks]; // Shuffle remaining so we can keep on using M_RandomKey
 | 
						|
 | 
						|
		if (numunlocks == 0)
 | 
						|
		{
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void M_SanitiseChallengeGrid(void)
 | 
						|
{
 | 
						|
	UINT8 seen[MAXUNLOCKABLES];
 | 
						|
	UINT16 empty[MAXUNLOCKABLES + (CHALLENGEGRIDHEIGHT-1)];
 | 
						|
	UINT16 i, j, numempty = 0;
 | 
						|
 | 
						|
	if (gamedata->challengegrid == NULL)
 | 
						|
		return;
 | 
						|
 | 
						|
	memset(seen, 0, sizeof(seen));
 | 
						|
 | 
						|
	// Go through all spots to identify duplicates and absences.
 | 
						|
	for (j = 0; j < gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT; j++)
 | 
						|
	{
 | 
						|
		i = gamedata->challengegrid[j];
 | 
						|
 | 
						|
		if (i >= MAXUNLOCKABLES || !unlockables[i].conditionset)
 | 
						|
		{
 | 
						|
			empty[numempty++] = j;
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		if (seen[i] != 5) // Arbitrary cap greater than 4
 | 
						|
		{
 | 
						|
			seen[i]++;
 | 
						|
 | 
						|
			if (seen[i] == 1 || unlockables[i].majorunlock)
 | 
						|
			{
 | 
						|
				continue;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		empty[numempty++] = j;
 | 
						|
	}
 | 
						|
 | 
						|
	// Go through unlockables to identify if any haven't been seen.
 | 
						|
	for (i = 0; i < MAXUNLOCKABLES; ++i)
 | 
						|
	{
 | 
						|
		if (!unlockables[i].conditionset)
 | 
						|
		{
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		if (unlockables[i].majorunlock && seen[i] != 4)
 | 
						|
		{
 | 
						|
			// Probably not enough spots to retrofit.
 | 
						|
			goto badgrid;
 | 
						|
		}
 | 
						|
 | 
						|
		if (seen[i] != 0)
 | 
						|
		{
 | 
						|
			// Present on the challenge grid.
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		if (numempty != 0)
 | 
						|
		{
 | 
						|
			// Small ones can be slotted in easy.
 | 
						|
			j = empty[--numempty];
 | 
						|
			gamedata->challengegrid[j] = i;
 | 
						|
		}
 | 
						|
 | 
						|
		// Nothing we can do to recover.
 | 
						|
		goto badgrid;
 | 
						|
	}
 | 
						|
 | 
						|
	// Fill the remaining spots with empties.
 | 
						|
	while (numempty != 0)
 | 
						|
	{
 | 
						|
		j = empty[--numempty];
 | 
						|
		gamedata->challengegrid[j] = MAXUNLOCKABLES;
 | 
						|
	}
 | 
						|
 | 
						|
	return;
 | 
						|
 | 
						|
badgrid:
 | 
						|
	// Just remove everything and let it get regenerated.
 | 
						|
	Z_Free(gamedata->challengegrid);
 | 
						|
	gamedata->challengegrid = NULL;
 | 
						|
	gamedata->challengegridwidth = 0;
 | 
						|
}
 | 
						|
 | 
						|
void M_UpdateChallengeGridExtraData(challengegridextradata_t *extradata)
 | 
						|
{
 | 
						|
	UINT16 i, j, num, id, tempid, work;
 | 
						|
	boolean idchange;
 | 
						|
 | 
						|
	if (gamedata->challengegrid == NULL)
 | 
						|
	{
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	if (extradata == NULL)
 | 
						|
	{
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	//CONS_Printf(" --- \n");
 | 
						|
 | 
						|
	// Pre-wipe flags.
 | 
						|
	for (i = 0; i < gamedata->challengegridwidth; i++)
 | 
						|
	{
 | 
						|
		for (j = 0; j < CHALLENGEGRIDHEIGHT; j++)
 | 
						|
		{
 | 
						|
			id = (i * CHALLENGEGRIDHEIGHT) + j;
 | 
						|
			num = gamedata->challengegrid[id];
 | 
						|
			if (num >= MAXUNLOCKABLES || unlockables[num].majorunlock == false || gamedata->unlocked[num] == true)
 | 
						|
			{
 | 
						|
				extradata[id].flags = CHE_NONE;
 | 
						|
				continue;
 | 
						|
			}
 | 
						|
 | 
						|
			// We only do this for locked large tiles, to reduce the
 | 
						|
			// complexity of most standard tile challenge comparisons
 | 
						|
			extradata[id].flags = CHE_ALLCLEAR;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Populate extra data.
 | 
						|
	for (i = 0; i < gamedata->challengegridwidth; i++)
 | 
						|
	{
 | 
						|
		for (j = 0; j < CHALLENGEGRIDHEIGHT; j++)
 | 
						|
		{
 | 
						|
			id = (i * CHALLENGEGRIDHEIGHT) + j;
 | 
						|
			num = gamedata->challengegrid[id];
 | 
						|
			idchange = false;
 | 
						|
 | 
						|
			// Empty spots in the grid are always unconnected.
 | 
						|
			if (num >= MAXUNLOCKABLES)
 | 
						|
			{
 | 
						|
				continue;
 | 
						|
			}
 | 
						|
 | 
						|
			// Check the spot above.
 | 
						|
			if (j > 0)
 | 
						|
			{
 | 
						|
				tempid = (i * CHALLENGEGRIDHEIGHT) + (j - 1);
 | 
						|
				work = gamedata->challengegrid[tempid];
 | 
						|
				if (work == num)
 | 
						|
				{
 | 
						|
					extradata[id].flags = CHE_CONNECTEDUP;
 | 
						|
 | 
						|
					// Get the id to write extra hint data to.
 | 
						|
					// This check is safe because extradata's order of population
 | 
						|
					if (extradata[tempid].flags & CHE_CONNECTEDLEFT)
 | 
						|
					{
 | 
						|
						extradata[id].flags |= CHE_CONNECTEDLEFT;
 | 
						|
						//CONS_Printf(" %d - %d above %d is invalid, check to left\n", num, tempid, id);
 | 
						|
						if (i > 0)
 | 
						|
						{
 | 
						|
							tempid -= CHALLENGEGRIDHEIGHT;
 | 
						|
						}
 | 
						|
						else
 | 
						|
						{
 | 
						|
							tempid = ((gamedata->challengegridwidth - 1) * CHALLENGEGRIDHEIGHT) + j - 1;
 | 
						|
						}
 | 
						|
					}
 | 
						|
					/*else
 | 
						|
						CONS_Printf(" %d - %d above %d is valid\n", num, tempid, id);*/
 | 
						|
 | 
						|
					id = tempid;
 | 
						|
					idchange = true;
 | 
						|
 | 
						|
					if (extradata[id].flags == CHE_HINT)
 | 
						|
					{
 | 
						|
						// CHE_ALLCLEAR has already been removed,
 | 
						|
						// and CHE_HINT has already been applied,
 | 
						|
						// so nothing more needs to be done here.
 | 
						|
						continue;
 | 
						|
					}
 | 
						|
				}
 | 
						|
				else if (work < MAXUNLOCKABLES)
 | 
						|
				{
 | 
						|
					if (gamedata->unlocked[work] == true)
 | 
						|
					{
 | 
						|
						extradata[id].flags |= CHE_HINT;
 | 
						|
					}
 | 
						|
					else
 | 
						|
					{
 | 
						|
						extradata[id].flags &= ~CHE_ALLCLEAR;
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			// Check the spot to the left.
 | 
						|
			{
 | 
						|
				if (i > 0)
 | 
						|
				{
 | 
						|
					tempid = ((i - 1) * CHALLENGEGRIDHEIGHT) + j;
 | 
						|
				}
 | 
						|
				else
 | 
						|
				{
 | 
						|
					tempid = ((gamedata->challengegridwidth - 1) * CHALLENGEGRIDHEIGHT) + j;
 | 
						|
				}
 | 
						|
				work = gamedata->challengegrid[tempid];
 | 
						|
 | 
						|
				if (work == num)
 | 
						|
				{
 | 
						|
					if (!idchange && (i > 0 || challengegridloops))
 | 
						|
					{
 | 
						|
						//CONS_Printf(" %d - %d to left of %d is valid\n", work, tempid, id);
 | 
						|
						// If we haven't already updated our id, it's the one to our left.
 | 
						|
						if (extradata[id].flags & CHE_HINT)
 | 
						|
						{
 | 
						|
							extradata[tempid].flags |= CHE_HINT;
 | 
						|
						}
 | 
						|
						if (!(extradata[id].flags & CHE_ALLCLEAR))
 | 
						|
						{
 | 
						|
							extradata[tempid].flags &= ~CHE_ALLCLEAR;
 | 
						|
						}
 | 
						|
						extradata[id].flags = CHE_CONNECTEDLEFT;
 | 
						|
						id = tempid;
 | 
						|
					}
 | 
						|
					/*else
 | 
						|
						CONS_Printf(" %d - %d to left of %d is invalid\n", work, tempid, id);*/
 | 
						|
				}
 | 
						|
				else if (work < MAXUNLOCKABLES)
 | 
						|
				{
 | 
						|
					if (gamedata->unlocked[work] == true)
 | 
						|
					{
 | 
						|
						extradata[id].flags |= CHE_HINT;
 | 
						|
					}
 | 
						|
					else
 | 
						|
					{
 | 
						|
						extradata[id].flags &= ~CHE_ALLCLEAR;
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			// Since we're not modifying id past this point, the conditions become much simpler.
 | 
						|
			if (extradata[id].flags == CHE_HINT)
 | 
						|
			{
 | 
						|
				// CHE_ALLCLEAR has already been removed,
 | 
						|
				// and CHE_HINT has already been applied,
 | 
						|
				// so nothing more needs to be done here.
 | 
						|
				continue;
 | 
						|
			}
 | 
						|
 | 
						|
			// Check the spot below.
 | 
						|
			if (j < CHALLENGEGRIDHEIGHT-1)
 | 
						|
			{
 | 
						|
				tempid = (i * CHALLENGEGRIDHEIGHT) + (j + 1);
 | 
						|
				work = gamedata->challengegrid[tempid];
 | 
						|
 | 
						|
				if (work == num)
 | 
						|
				{
 | 
						|
					;
 | 
						|
				}
 | 
						|
				else if (work < MAXUNLOCKABLES)
 | 
						|
				{
 | 
						|
					if (gamedata->unlocked[work] == true)
 | 
						|
					{
 | 
						|
						extradata[id].flags |= CHE_HINT;
 | 
						|
					}
 | 
						|
					else
 | 
						|
					{
 | 
						|
						extradata[id].flags &= ~CHE_ALLCLEAR;
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			// Check the spot to the right.
 | 
						|
			{
 | 
						|
				if (i < (gamedata->challengegridwidth - 1))
 | 
						|
				{
 | 
						|
					tempid = ((i + 1) * CHALLENGEGRIDHEIGHT) + j;
 | 
						|
				}
 | 
						|
				else
 | 
						|
				{
 | 
						|
					tempid = j;
 | 
						|
				}
 | 
						|
				work = gamedata->challengegrid[tempid];
 | 
						|
 | 
						|
				if (work == num)
 | 
						|
				{
 | 
						|
					;
 | 
						|
				}
 | 
						|
				else if (work < MAXUNLOCKABLES)
 | 
						|
				{
 | 
						|
					if (gamedata->unlocked[work] == true)
 | 
						|
					{
 | 
						|
						extradata[id].flags |= CHE_HINT;
 | 
						|
					}
 | 
						|
					else
 | 
						|
					{
 | 
						|
						extradata[id].flags &= ~CHE_ALLCLEAR;
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void M_AddRawCondition(UINT16 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2, char *stringvar)
 | 
						|
{
 | 
						|
	condition_t *cond;
 | 
						|
	UINT32 num, wnum;
 | 
						|
 | 
						|
	I_Assert(set < MAXCONDITIONSETS);
 | 
						|
 | 
						|
	wnum = conditionSets[set].numconditions;
 | 
						|
	num = ++conditionSets[set].numconditions;
 | 
						|
 | 
						|
	conditionSets[set].condition = Z_Realloc(conditionSets[set].condition, sizeof(condition_t)*num, PU_STATIC, 0);
 | 
						|
 | 
						|
	cond = conditionSets[set].condition;
 | 
						|
 | 
						|
	cond[wnum].id = id;
 | 
						|
	cond[wnum].type = c;
 | 
						|
	cond[wnum].requirement = r;
 | 
						|
	cond[wnum].extrainfo1 = x1;
 | 
						|
	cond[wnum].extrainfo2 = x2;
 | 
						|
	cond[wnum].stringvar = stringvar;
 | 
						|
}
 | 
						|
 | 
						|
void M_ClearConditionSet(UINT16 set)
 | 
						|
{
 | 
						|
	if (conditionSets[set].numconditions)
 | 
						|
	{
 | 
						|
		while (conditionSets[set].numconditions > 0)
 | 
						|
		{
 | 
						|
			--conditionSets[set].numconditions;
 | 
						|
			Z_Free(conditionSets[set].condition[conditionSets[set].numconditions].stringvar);
 | 
						|
		}
 | 
						|
 | 
						|
		Z_Free(conditionSets[set].condition);
 | 
						|
		conditionSets[set].condition = NULL;
 | 
						|
	}
 | 
						|
	gamedata->achieved[set] = false;
 | 
						|
}
 | 
						|
 | 
						|
// Clear ALL secrets.
 | 
						|
void M_ClearStats(void)
 | 
						|
{
 | 
						|
	UINT16 i;
 | 
						|
 | 
						|
	gamedata->totalplaytime = 0;
 | 
						|
	gamedata->totalnetgametime = 0;
 | 
						|
	gamedata->timeattackingtotaltime = 0;
 | 
						|
	gamedata->spbattackingtotaltime = 0;
 | 
						|
	for (i = 0; i < GDGT_MAX; ++i)
 | 
						|
		gamedata->modeplaytime[i] = 0;
 | 
						|
	gamedata->totalmenutime = 0;
 | 
						|
	gamedata->totaltimestaringatstatistics = 0;
 | 
						|
	gamedata->totalrings = 0;
 | 
						|
	gamedata->totaltumbletime = 0;
 | 
						|
	for (i = 0; i < GDGT_MAX; ++i)
 | 
						|
		gamedata->roundsplayed[i] = 0;
 | 
						|
	gamedata->timesBeaten = 0;
 | 
						|
 | 
						|
	gamedata->everloadedaddon = false;
 | 
						|
	gamedata->everfinishedcredits = false;
 | 
						|
	gamedata->eversavedreplay = false;
 | 
						|
	gamedata->everseenspecial = false;
 | 
						|
	gamedata->evercrashed = false;
 | 
						|
	gamedata->chaokeytutorial = false;
 | 
						|
	gamedata->majorkeyskipattempted = false;
 | 
						|
	gamedata->enteredtutorialchallenge = false;
 | 
						|
	gamedata->finishedtutorialchallenge = false;
 | 
						|
	gamedata->sealedswapalerted = false;
 | 
						|
	gamedata->musicstate = GDMUSIC_NONE;
 | 
						|
 | 
						|
	gamedata->importprofilewins = false;
 | 
						|
 | 
						|
	// Skins only store stats, not progression metrics. Good to clear entirely here.
 | 
						|
 | 
						|
	for (i = 0; i < numskins; i++)
 | 
						|
	{
 | 
						|
		memset(&skins[i].records, 0, sizeof(skins[i].records));
 | 
						|
	}
 | 
						|
 | 
						|
	unloaded_skin_t *unloadedskin, *nextunloadedskin = NULL;
 | 
						|
	for (unloadedskin = unloadedskins; unloadedskin; unloadedskin = nextunloadedskin)
 | 
						|
	{
 | 
						|
		nextunloadedskin = unloadedskin->next;
 | 
						|
		Z_Free(unloadedskin);
 | 
						|
	}
 | 
						|
	unloadedskins = NULL;
 | 
						|
 | 
						|
	// We retain exclusively the most important stuff from maps.
 | 
						|
 | 
						|
	UINT8 restoremapvisited;
 | 
						|
	recordtimes_t restoretimeattack;
 | 
						|
	recordtimes_t restorespbattack;
 | 
						|
 | 
						|
	for (i = 0; i < nummapheaders; i++)
 | 
						|
	{
 | 
						|
		restoremapvisited = mapheaderinfo[i]->records.mapvisited;
 | 
						|
		restoretimeattack = mapheaderinfo[i]->records.timeattack;
 | 
						|
		restorespbattack = mapheaderinfo[i]->records.spbattack;
 | 
						|
 | 
						|
		memset(&mapheaderinfo[i]->records, 0, sizeof(recorddata_t));
 | 
						|
 | 
						|
		mapheaderinfo[i]->records.mapvisited = restoremapvisited;
 | 
						|
		mapheaderinfo[i]->records.timeattack = restoretimeattack;
 | 
						|
		mapheaderinfo[i]->records.spbattack = restorespbattack;
 | 
						|
	}
 | 
						|
 | 
						|
	unloaded_mapheader_t *unloadedmap;
 | 
						|
	for (unloadedmap = unloadedmapheaders; unloadedmap; unloadedmap = unloadedmap->next)
 | 
						|
	{
 | 
						|
		restoremapvisited = unloadedmap->records.mapvisited;
 | 
						|
		restoretimeattack = unloadedmap->records.timeattack;
 | 
						|
		restorespbattack = unloadedmap->records.spbattack;
 | 
						|
 | 
						|
		memset(&unloadedmap->records, 0, sizeof(recorddata_t));
 | 
						|
 | 
						|
		unloadedmap->records.mapvisited = restoremapvisited;
 | 
						|
		unloadedmap->records.timeattack = restoretimeattack;
 | 
						|
		unloadedmap->records.spbattack = restorespbattack;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void M_ClearSecrets(void)
 | 
						|
{
 | 
						|
	memset(gamedata->collected, 0, sizeof(gamedata->collected));
 | 
						|
	memset(gamedata->unlocked, 0, sizeof(gamedata->unlocked));
 | 
						|
	memset(gamedata->unlockpending, 0, sizeof(gamedata->unlockpending));
 | 
						|
	memset(netUnlocked, 0, sizeof(netUnlocked));
 | 
						|
	memset(gamedata->achieved, 0, sizeof(gamedata->achieved));
 | 
						|
 | 
						|
	Z_Free(gamedata->spraycans);
 | 
						|
	gamedata->spraycans = NULL;
 | 
						|
	gamedata->numspraycans = 0;
 | 
						|
	gamedata->gotspraycans = 0;
 | 
						|
 | 
						|
	Z_Free(gamedata->prisoneggpickups);
 | 
						|
	gamedata->prisoneggpickups = NULL;
 | 
						|
	gamedata->numprisoneggpickups = 0;
 | 
						|
	gamedata->thisprisoneggpickup = MAXCONDITIONSETS;
 | 
						|
	gamedata->thisprisoneggpickup_cached = NULL;
 | 
						|
	gamedata->thisprisoneggpickupgrabbed = false;
 | 
						|
 | 
						|
	UINT16 i, j;
 | 
						|
	for (i = 0; i < nummapheaders; i++)
 | 
						|
	{
 | 
						|
		if (!mapheaderinfo[i])
 | 
						|
			continue;
 | 
						|
 | 
						|
		mapheaderinfo[i]->records.mapvisited = 0;
 | 
						|
 | 
						|
		mapheaderinfo[i]->cache_spraycan = UINT16_MAX;
 | 
						|
 | 
						|
		mapheaderinfo[i]->cache_maplock = MAXUNLOCKABLES;
 | 
						|
 | 
						|
		for (j = 1; j < mapheaderinfo[i]->musname_size; j++)
 | 
						|
		{
 | 
						|
			mapheaderinfo[i]->cache_muslock[j-1] = MAXUNLOCKABLES;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	cupheader_t *cup;
 | 
						|
	for (cup = kartcupheaders; cup; cup = cup->next)
 | 
						|
	{
 | 
						|
		cup->cache_cuplock = MAXUNLOCKABLES;
 | 
						|
	}
 | 
						|
 | 
						|
	for (i = 0; i < numskincolors; i++)
 | 
						|
	{
 | 
						|
		skincolors[i].cache_spraycan = UINT16_MAX;
 | 
						|
	}
 | 
						|
 | 
						|
	memset(gamedata->sealedswaps, 0, sizeof(gamedata->sealedswaps));
 | 
						|
 | 
						|
	Z_Free(gamedata->challengegrid);
 | 
						|
	gamedata->challengegrid = NULL;
 | 
						|
	gamedata->challengegridwidth = 0;
 | 
						|
 | 
						|
	gamedata->pendingkeyrounds = 0;
 | 
						|
	gamedata->pendingkeyroundoffset = 0;
 | 
						|
	gamedata->keyspending = 0;
 | 
						|
 | 
						|
	gamedata->chaokeys = GDINIT_CHAOKEYS;
 | 
						|
	gamedata->prisoneggstothispickup = GDINIT_PRISONSTOPRIZE;
 | 
						|
 | 
						|
	gamedata->gonerlevel = GDGONER_INIT;
 | 
						|
}
 | 
						|
 | 
						|
// For lack of a better idea on where to put this
 | 
						|
static void M_Shuffle_UINT16(UINT16 *list, size_t len)
 | 
						|
{
 | 
						|
	size_t i;
 | 
						|
	UINT16 temp;
 | 
						|
 | 
						|
	while (len > 1)
 | 
						|
	{
 | 
						|
		i = M_RandomKey(len);
 | 
						|
 | 
						|
		if (i == --len)
 | 
						|
			continue;
 | 
						|
 | 
						|
		temp = list[i];
 | 
						|
		list[i] = list[len];
 | 
						|
		list[len] = temp;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void M_AssignSpraycans(void)
 | 
						|
{
 | 
						|
	// Very convenient I'm programming this on
 | 
						|
	// the release date of "Bomb Rush Cyberfunk".
 | 
						|
	// ~toast 180823 (committed a day later)
 | 
						|
 | 
						|
	// Init ordered list of skincolors
 | 
						|
	UINT16 tempcanlist[MAXSKINCOLORS];
 | 
						|
	UINT16 listlen = 0, prependlen = 0;
 | 
						|
 | 
						|
	UINT32 i, j;
 | 
						|
	conditionset_t *c;
 | 
						|
	condition_t *cn;
 | 
						|
 | 
						|
	const UINT16 prependoffset = MAXSKINCOLORS-1;
 | 
						|
 | 
						|
	// None of the following accounts for cans being removed, only added...
 | 
						|
	for (i = 0; i < MAXCONDITIONSETS; ++i)
 | 
						|
	{
 | 
						|
		c = &conditionSets[i];
 | 
						|
		if (!c->numconditions)
 | 
						|
			continue;
 | 
						|
 | 
						|
		for (j = 0; j < c->numconditions; ++j)
 | 
						|
		{
 | 
						|
			cn = &c->condition[j];
 | 
						|
			if (cn->type != UC_SPRAYCAN)
 | 
						|
				continue;
 | 
						|
 | 
						|
			// G_LoadGamedata, G_SaveGameData doesn't support custom skincolors right now.
 | 
						|
			if (cn->requirement >= SKINCOLOR_FIRSTFREESLOT) //numskincolors)
 | 
						|
				continue;
 | 
						|
 | 
						|
			if (skincolors[cn->requirement].cache_spraycan != UINT16_MAX)
 | 
						|
				continue;
 | 
						|
 | 
						|
			// Still invalid, just in case it isn't assigned one later
 | 
						|
			skincolors[cn->requirement].cache_spraycan = UINT16_MAX-1;
 | 
						|
 | 
						|
			if (!cn->extrainfo1)
 | 
						|
			{
 | 
						|
				//CONS_Printf("DDD - Adding standard can color %d\n", cn->requirement);
 | 
						|
 | 
						|
				tempcanlist[listlen] = cn->requirement;
 | 
						|
				listlen++;
 | 
						|
				continue;
 | 
						|
			}
 | 
						|
 | 
						|
			//CONS_Printf("DDD - Prepending early can color %d\n", cn->requirement);
 | 
						|
 | 
						|
			tempcanlist[prependoffset - prependlen] = cn->requirement;
 | 
						|
			prependlen++;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (listlen)
 | 
						|
	{
 | 
						|
		// Swap the standard colours for random order
 | 
						|
		M_Shuffle_UINT16(tempcanlist, listlen);
 | 
						|
	}
 | 
						|
	else if (!prependlen)
 | 
						|
	{
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	if (prependlen)
 | 
						|
	{
 | 
						|
		// Swap the early colours for random order
 | 
						|
		M_Shuffle_UINT16(tempcanlist + prependoffset - (prependlen - 1), prependlen);
 | 
						|
 | 
						|
		// Put at the front of the main list
 | 
						|
		// (technically messes with the main order, but it
 | 
						|
		// was LITERALLY just shuffled so it doesn't matter)
 | 
						|
		i = 0;
 | 
						|
		while (i < prependlen)
 | 
						|
		{
 | 
						|
			tempcanlist[listlen] = tempcanlist[i];
 | 
						|
			tempcanlist[i] = tempcanlist[prependoffset - i];
 | 
						|
			listlen++;
 | 
						|
			i++;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	gamedata->spraycans = Z_Realloc(
 | 
						|
		gamedata->spraycans,
 | 
						|
		sizeof(candata_t) * (gamedata->numspraycans + listlen),
 | 
						|
		PU_STATIC,
 | 
						|
		NULL);
 | 
						|
 | 
						|
	for (i = 0; i < listlen; i++)
 | 
						|
	{
 | 
						|
		gamedata->spraycans[gamedata->numspraycans].map = NEXTMAP_INVALID;
 | 
						|
		gamedata->spraycans[gamedata->numspraycans].col = tempcanlist[i];
 | 
						|
 | 
						|
		skincolors[tempcanlist[i]].cache_spraycan = gamedata->numspraycans;
 | 
						|
 | 
						|
		gamedata->numspraycans++;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void M_InitPrisonEggPickups(void)
 | 
						|
{
 | 
						|
	// Init ordered list of skincolors
 | 
						|
	UINT16 temppickups[MAXCONDITIONSETS];
 | 
						|
	UINT16 listlen = 0;
 | 
						|
 | 
						|
	UINT32 i, j;
 | 
						|
	conditionset_t *c;
 | 
						|
	condition_t *cn;
 | 
						|
 | 
						|
	for (i = 0; i < MAXCONDITIONSETS; ++i)
 | 
						|
	{
 | 
						|
		// Optimisation - unlike Spray Cans, these are rebuilt every game launch/savedata wipe.
 | 
						|
		// Therefore, we don't need to re-store the ones that have been achieved.
 | 
						|
		if (gamedata->achieved[i])
 | 
						|
			continue;
 | 
						|
 | 
						|
		c = &conditionSets[i];
 | 
						|
		if (!c->numconditions)
 | 
						|
			continue;
 | 
						|
 | 
						|
		for (j = 0; j < c->numconditions; ++j)
 | 
						|
		{
 | 
						|
			cn = &c->condition[j];
 | 
						|
			if (cn->type != UC_PRISONEGGCD)
 | 
						|
				continue;
 | 
						|
 | 
						|
			temppickups[listlen] = i;
 | 
						|
			listlen++;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (!listlen)
 | 
						|
	{
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	// This list doesn't need to be shuffled because it's always being randomly grabbed.
 | 
						|
	// (Unlike Spray Cans, you don't know which CD you miss out on.)
 | 
						|
 | 
						|
	gamedata->prisoneggpickups = Z_Realloc(
 | 
						|
		gamedata->prisoneggpickups,
 | 
						|
		sizeof(UINT16) * listlen,
 | 
						|
		PU_STATIC,
 | 
						|
		NULL);
 | 
						|
 | 
						|
	while (gamedata->numprisoneggpickups < listlen)
 | 
						|
	{
 | 
						|
		gamedata->prisoneggpickups[gamedata->numprisoneggpickups]
 | 
						|
			= temppickups[gamedata->numprisoneggpickups];
 | 
						|
		gamedata->numprisoneggpickups++;
 | 
						|
	}
 | 
						|
 | 
						|
	M_UpdateNextPrisonEggPickup();
 | 
						|
}
 | 
						|
 | 
						|
void M_UpdateNextPrisonEggPickup(void)
 | 
						|
{
 | 
						|
	UINT16 i, j, swap;
 | 
						|
 | 
						|
	conditionset_t *c;
 | 
						|
	condition_t *cn;
 | 
						|
 | 
						|
#ifdef DEVELOP
 | 
						|
	extern consvar_t cv_debugprisoncd;
 | 
						|
#endif
 | 
						|
 | 
						|
cacheprisoneggpickup:
 | 
						|
 | 
						|
	// Check if the current roll is fine
 | 
						|
	gamedata->thisprisoneggpickup_cached = NULL;
 | 
						|
	if (gamedata->thisprisoneggpickup < MAXCONDITIONSETS)
 | 
						|
	{
 | 
						|
#ifdef DEVELOP
 | 
						|
		if (cv_debugprisoncd.value)
 | 
						|
			CONS_Printf("CACHE TEST: thisprisoneggpickup is set to %u\n", gamedata->thisprisoneggpickup);
 | 
						|
#endif
 | 
						|
 | 
						|
		if (gamedata->achieved[gamedata->thisprisoneggpickup] == false)
 | 
						|
		{
 | 
						|
			c = &conditionSets[gamedata->thisprisoneggpickup];
 | 
						|
			if (c->numconditions)
 | 
						|
			{
 | 
						|
				for (j = 0; j < c->numconditions; ++j)
 | 
						|
				{
 | 
						|
					cn = &c->condition[j];
 | 
						|
					if (cn->type != UC_PRISONEGGCD)
 | 
						|
						continue;
 | 
						|
 | 
						|
					if (cn->requirement < nummapheaders && M_MapLocked(cn->requirement+1))
 | 
						|
						continue;
 | 
						|
 | 
						|
					// Good! Attach the cache.
 | 
						|
					gamedata->thisprisoneggpickup_cached = cn;
 | 
						|
 | 
						|
#ifdef DEVELOP
 | 
						|
					if (cv_debugprisoncd.value)
 | 
						|
						CONS_Printf(" successfully set to cn!\n");
 | 
						|
#endif
 | 
						|
 | 
						|
					break;
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if (gamedata->thisprisoneggpickup_cached == NULL)
 | 
						|
		{
 | 
						|
			gamedata->thisprisoneggpickup = MAXCONDITIONSETS;
 | 
						|
			gamedata->thisprisoneggpickupgrabbed = false;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (gamedata->numprisoneggpickups && gamedata->thisprisoneggpickup >= MAXCONDITIONSETS)
 | 
						|
	{
 | 
						|
#ifdef DEVELOP
 | 
						|
		if (cv_debugprisoncd.value)
 | 
						|
			CONS_Printf(" Invalid thisprisoneggpickup, rolling a random one...\n");
 | 
						|
#endif
 | 
						|
 | 
						|
		UINT16 gettableprisoneggpickups = 0;
 | 
						|
 | 
						|
		for (i = 0; i < gamedata->numprisoneggpickups; i++)
 | 
						|
		{
 | 
						|
			if (gamedata->achieved[gamedata->prisoneggpickups[i]] == false)
 | 
						|
			{
 | 
						|
				c = &conditionSets[gamedata->prisoneggpickups[i]];
 | 
						|
				if (c->numconditions)
 | 
						|
				{
 | 
						|
					for (j = 0; j < c->numconditions; ++j)
 | 
						|
					{
 | 
						|
						cn = &c->condition[j];
 | 
						|
						if (cn->type != UC_PRISONEGGCD)
 | 
						|
							continue;
 | 
						|
 | 
						|
						// Locked associated map? Keep in the rear end dimension!
 | 
						|
						if (cn->requirement < nummapheaders && M_MapLocked(cn->requirement+1))
 | 
						|
							break; // not continue intentionally
 | 
						|
 | 
						|
						// Okay, this should be available.
 | 
						|
						// Bring to the front!
 | 
						|
						if (i != gettableprisoneggpickups)
 | 
						|
						{
 | 
						|
							swap = gamedata->prisoneggpickups[gettableprisoneggpickups];
 | 
						|
							gamedata->prisoneggpickups[gettableprisoneggpickups] =
 | 
						|
								gamedata->prisoneggpickups[i];
 | 
						|
							gamedata->prisoneggpickups[i] = swap;
 | 
						|
						}
 | 
						|
 | 
						|
						gettableprisoneggpickups++;
 | 
						|
 | 
						|
						break;
 | 
						|
					}
 | 
						|
 | 
						|
					if (j < c->numconditions)
 | 
						|
						continue;
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if (gettableprisoneggpickups != 0)
 | 
						|
		{
 | 
						|
			gamedata->thisprisoneggpickup =
 | 
						|
				gamedata->prisoneggpickups[
 | 
						|
					M_RandomKey(gettableprisoneggpickups)
 | 
						|
				];
 | 
						|
 | 
						|
#ifdef DEVELOP
 | 
						|
			if (cv_debugprisoncd.value)
 | 
						|
				CONS_Printf(" Selected %u, trying again...\n", gamedata->thisprisoneggpickup);
 | 
						|
#endif
 | 
						|
 | 
						|
			goto cacheprisoneggpickup;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
#ifdef DEVELOP
 | 
						|
	if (cv_debugprisoncd.value)
 | 
						|
		CONS_Printf("thisprisoneggpickup = %u (MAXCONDITIONSETS is %u)\n", gamedata->thisprisoneggpickup, MAXCONDITIONSETS);
 | 
						|
 | 
						|
#if 0
 | 
						|
	// If all drops are collected, just force the first valid one.
 | 
						|
	// THIS DOESN'T ACTUALLY WORK IF ALL PRISON PRIZES HAVE BEEN REDEEMED AND THE GAME IS RELAUNCHED, so it is not reliable enough to expose as a debugging tool
 | 
						|
	if (cv_debugprisoncd.value && gamedata->thisprisoneggpickup_cached == NULL)
 | 
						|
	{
 | 
						|
		for (i = 0; gamedata->thisprisoneggpickup_cached == NULL &&
 | 
						|
			i < gamedata->numprisoneggpickups; i++)
 | 
						|
		{
 | 
						|
			c = &conditionSets[gamedata->prisoneggpickups[i]];
 | 
						|
			if (c->numconditions)
 | 
						|
			{
 | 
						|
				for (j = 0; j < c->numconditions; ++j)
 | 
						|
				{
 | 
						|
					cn = &c->condition[j];
 | 
						|
					if (cn->type != UC_PRISONEGGCD)
 | 
						|
						continue;
 | 
						|
 | 
						|
					gamedata->thisprisoneggpickup = gamedata->prisoneggpickups[i];
 | 
						|
					gamedata->thisprisoneggpickup_cached = cn;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
#endif
 | 
						|
 | 
						|
#endif // DEVELOP
 | 
						|
}
 | 
						|
 | 
						|
static void M_PrecacheLevelLocks(void)
 | 
						|
{
 | 
						|
	UINT16 i, j;
 | 
						|
 | 
						|
	for (i = 0; i < MAXUNLOCKABLES; ++i)
 | 
						|
	{
 | 
						|
		switch (unlockables[i].type)
 | 
						|
		{
 | 
						|
			// SECRET_SKIN, SECRET_COLOR, SECRET_FOLLOWER are instantiated too late to use
 | 
						|
			case SECRET_MAP:
 | 
						|
			{
 | 
						|
				UINT16 map = M_UnlockableMapNum(&unlockables[i]);
 | 
						|
				if (map < nummapheaders
 | 
						|
					&& mapheaderinfo[map])
 | 
						|
				{
 | 
						|
					if (mapheaderinfo[map]->cache_maplock != MAXUNLOCKABLES)
 | 
						|
						CONS_Alert(CONS_ERROR, "Unlockable %u: Too many SECRET_MAPs associated with Level %s\n", i+1, mapheaderinfo[map]->lumpname);
 | 
						|
					mapheaderinfo[map]->cache_maplock = i;
 | 
						|
				}
 | 
						|
				break;
 | 
						|
			}
 | 
						|
 | 
						|
			case SECRET_ALTMUSIC:
 | 
						|
			{
 | 
						|
				UINT16 map = M_UnlockableMapNum(&unlockables[i]);
 | 
						|
				const char *tempstr = NULL;
 | 
						|
 | 
						|
				if (map < nummapheaders
 | 
						|
					&& mapheaderinfo[map])
 | 
						|
				{
 | 
						|
					UINT8 greatersize = max(mapheaderinfo[map]->musname_size, mapheaderinfo[map]->encoremusname_size);
 | 
						|
					for (j = 1; j < greatersize; j++)
 | 
						|
					{
 | 
						|
						if (mapheaderinfo[map]->cache_muslock[j - 1] != MAXUNLOCKABLES)
 | 
						|
						{
 | 
						|
							continue;
 | 
						|
						}
 | 
						|
 | 
						|
						mapheaderinfo[map]->cache_muslock[j - 1] = i;
 | 
						|
 | 
						|
						UINT8 positionid = 0;
 | 
						|
 | 
						|
						if (mapheaderinfo[map]->cup)
 | 
						|
						{
 | 
						|
							for (positionid = 0; positionid < CUPCACHE_PODIUM; positionid++)
 | 
						|
							{
 | 
						|
								if (mapheaderinfo[map]->cup->cachedlevels[positionid] != map)
 | 
						|
									continue;
 | 
						|
								break;
 | 
						|
							}
 | 
						|
 | 
						|
							if (positionid < CUPCACHE_PODIUM)
 | 
						|
							{
 | 
						|
								char prefix = 'R';
 | 
						|
								if (positionid >= CUPCACHE_BONUS)
 | 
						|
								{
 | 
						|
									positionid -= (CUPCACHE_BONUS);
 | 
						|
									prefix = 'B';
 | 
						|
								}
 | 
						|
 | 
						|
								tempstr = va(
 | 
						|
									"Music: %s CUP %c%u %c",
 | 
						|
									mapheaderinfo[map]->cup->realname,
 | 
						|
									prefix,
 | 
						|
									positionid + 1,
 | 
						|
									'A' + j // :D ?
 | 
						|
								);
 | 
						|
							}
 | 
						|
						}
 | 
						|
 | 
						|
						if (tempstr == NULL)
 | 
						|
						{
 | 
						|
							UINT16 mapcheck;
 | 
						|
							for (mapcheck = 0; mapcheck < map; mapcheck++)
 | 
						|
							{
 | 
						|
								if (!mapheaderinfo[mapcheck] || mapheaderinfo[mapcheck]->cup != NULL)
 | 
						|
									continue;
 | 
						|
								if (mapheaderinfo[mapcheck]->menuflags & LF2_HIDEINMENU)
 | 
						|
									continue;
 | 
						|
								if (((mapheaderinfo[mapcheck]->typeoflevel & TOL_TUTORIAL) == TOL_TUTORIAL)
 | 
						|
									!= ((mapheaderinfo[map]->typeoflevel & TOL_TUTORIAL) == TOL_TUTORIAL))
 | 
						|
									continue;
 | 
						|
 | 
						|
								// We don't check for locked, because the levels exist
 | 
						|
								positionid++;
 | 
						|
							}
 | 
						|
 | 
						|
							tempstr = va(
 | 
						|
								"Music: %s #%u %c",
 | 
						|
								(mapheaderinfo[map]->typeoflevel & TOL_TUTORIAL) ? "Tutorial" : "Lost & Found",
 | 
						|
								positionid + 1,
 | 
						|
								'A' + j // :D ?
 | 
						|
							);
 | 
						|
						}
 | 
						|
 | 
						|
						break;
 | 
						|
					}
 | 
						|
					if (j == greatersize)
 | 
						|
						CONS_Alert(CONS_ERROR, "Unlockable %u: Too many SECRET_ALTMUSICs associated with Level %s\n", i+1, mapheaderinfo[map]->lumpname);
 | 
						|
				}
 | 
						|
				else
 | 
						|
				{
 | 
						|
					CONS_Alert(CONS_ERROR, "Unlockable %u: Invalid levelname %s for SECRET_ALTMUSIC\n", i+1, unlockables[i].stringVar);
 | 
						|
				}
 | 
						|
 | 
						|
				if (tempstr == NULL)
 | 
						|
				{
 | 
						|
					tempstr = va("INVALID MUSIC UNLOCK %u", i+1);
 | 
						|
				}
 | 
						|
 | 
						|
				strlcpy(unlockables[i].name, tempstr, sizeof (unlockables[i].name));
 | 
						|
 | 
						|
				break;
 | 
						|
			}
 | 
						|
 | 
						|
			case SECRET_CUP:
 | 
						|
			{
 | 
						|
				cupheader_t *cup = M_UnlockableCup(&unlockables[i]);
 | 
						|
				if (cup)
 | 
						|
				{
 | 
						|
					if (cup->cache_cuplock != MAXUNLOCKABLES)
 | 
						|
						CONS_Alert(CONS_ERROR, "Unlockable %u: Too many SECRET_CUPs associated with Cup %s\n", i+1, cup->name);
 | 
						|
					cup->cache_cuplock = i;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
				break;
 | 
						|
			}
 | 
						|
 | 
						|
			default:
 | 
						|
				break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void M_FinaliseGameData(void)
 | 
						|
{
 | 
						|
	//M_PopulateChallengeGrid(); -- This can be done lazily when we actually need it
 | 
						|
 | 
						|
	// Precache as many unlockables as is meaningfully feasible
 | 
						|
	M_PrecacheLevelLocks();
 | 
						|
 | 
						|
	// Place the spraycans, which CAN'T be done lazily.
 | 
						|
	M_AssignSpraycans();
 | 
						|
 | 
						|
	// You could probably do the Prison Egg Pickups lazily, but it'd be a lagspike mid-combat.
 | 
						|
	M_InitPrisonEggPickups();
 | 
						|
 | 
						|
	// Don't consider loaded until it's a success!
 | 
						|
	// It used to do this much earlier, but this would cause the gamedata
 | 
						|
	// to save over itself when it I_Errors from corruption,  which can
 | 
						|
	// accidentally delete players' legitimate data if the code ever has
 | 
						|
	// any tiny mistakes!
 | 
						|
	gamedata->loaded = true;
 | 
						|
 | 
						|
	// Silent update unlockables in case they're out of sync with conditions
 | 
						|
	M_UpdateUnlockablesAndExtraEmblems(false, true);
 | 
						|
}
 | 
						|
 | 
						|
void M_SetNetUnlocked(void)
 | 
						|
{
 | 
						|
	UINT16 i;
 | 
						|
 | 
						|
	// Use your gamedata as baseline
 | 
						|
	for (i = 0; i < MAXUNLOCKABLES; i++)
 | 
						|
	{
 | 
						|
		netUnlocked[i] = gamedata->unlocked[i];
 | 
						|
	}
 | 
						|
 | 
						|
	if (!dedicated)
 | 
						|
	{
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	// Dedicated spoiler password - tournament mode equivalent.
 | 
						|
	if (usedTourney)
 | 
						|
	{
 | 
						|
		for (i = 0; i < MAXUNLOCKABLES; i++)
 | 
						|
		{
 | 
						|
			if (unlockables[i].conditionset == CH_FURYBIKE)
 | 
						|
				continue;
 | 
						|
 | 
						|
			netUnlocked[i] = true;
 | 
						|
		}
 | 
						|
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	// Okay, now it's dedicated first-week spoilerless behaviour.
 | 
						|
	for (i = 0; i < MAXUNLOCKABLES; i++)
 | 
						|
	{
 | 
						|
		if (netUnlocked[i])
 | 
						|
			continue;
 | 
						|
 | 
						|
		switch (unlockables[i].type)
 | 
						|
		{
 | 
						|
			case SECRET_CUP:
 | 
						|
			{
 | 
						|
				// Give the first seven Cups for free.
 | 
						|
				cupheader_t *cup = M_UnlockableCup(&unlockables[i]);
 | 
						|
				if (cup && cup->id < 7)
 | 
						|
					netUnlocked[i] = true;
 | 
						|
 | 
						|
				break;
 | 
						|
			}
 | 
						|
			case SECRET_ADDONS:
 | 
						|
			{
 | 
						|
				netUnlocked[i] = true;
 | 
						|
				break;
 | 
						|
			}
 | 
						|
			default:
 | 
						|
			{
 | 
						|
				// Most stuff isn't given to dedis for free
 | 
						|
				break;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// ----------------------
 | 
						|
// Condition set checking
 | 
						|
// ----------------------
 | 
						|
 | 
						|
void M_UpdateConditionSetsPending(void)
 | 
						|
{
 | 
						|
	UINT32 i, j, k;
 | 
						|
	conditionset_t *c;
 | 
						|
	condition_t *cn;
 | 
						|
 | 
						|
	for (i = 0; i < MAXCONDITIONSETS; ++i)
 | 
						|
	{
 | 
						|
		c = &conditionSets[i];
 | 
						|
		if (!c->numconditions)
 | 
						|
			continue;
 | 
						|
 | 
						|
		for (j = 0; j < c->numconditions; ++j)
 | 
						|
		{
 | 
						|
			cn = &c->condition[j];
 | 
						|
			if (cn->stringvar == NULL)
 | 
						|
				continue;
 | 
						|
 | 
						|
			switch (cn->type)
 | 
						|
			{
 | 
						|
				case UC_CHARACTERWINS:
 | 
						|
				case UCRP_ISCHARACTER:
 | 
						|
				case UCRP_MAKERETIRE:
 | 
						|
				{
 | 
						|
					cn->requirement = R_SkinAvailableEx(cn->stringvar, false);
 | 
						|
 | 
						|
					if (cn->requirement < 0)
 | 
						|
					{
 | 
						|
						CONS_Alert(CONS_WARNING, "UC TYPE %u: Invalid character %s for condition ID %d", cn->type, cn->stringvar, cn->id+1);
 | 
						|
						continue;
 | 
						|
					}
 | 
						|
 | 
						|
					Z_Free(cn->stringvar);
 | 
						|
					cn->stringvar = NULL;
 | 
						|
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				case UCRP_HASFOLLOWER:
 | 
						|
				{
 | 
						|
					// match deh_soc readfollower()
 | 
						|
					for (k = 0; cn->stringvar[k]; k++)
 | 
						|
					{
 | 
						|
						if (cn->stringvar[k] == '_')
 | 
						|
							cn->stringvar[k] = ' ';
 | 
						|
					}
 | 
						|
 | 
						|
					cn->requirement = K_FollowerAvailable(cn->stringvar);
 | 
						|
 | 
						|
					if (cn->requirement < 0)
 | 
						|
					{
 | 
						|
						CONS_Alert(CONS_WARNING, "UC TYPE %u: Invalid character %s for condition ID %d", cn->type, cn->stringvar, cn->id+1);
 | 
						|
						continue;
 | 
						|
					}
 | 
						|
 | 
						|
					Z_Free(cn->stringvar);
 | 
						|
					cn->stringvar = NULL;
 | 
						|
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				case UCRP_WETPLAYER:
 | 
						|
				{
 | 
						|
					if (cn->extrainfo1)
 | 
						|
					{
 | 
						|
						char *l;
 | 
						|
 | 
						|
						for (l = cn->stringvar; *l != '\0'; l++)
 | 
						|
						{
 | 
						|
							*l = tolower(*l);
 | 
						|
						}
 | 
						|
 | 
						|
						cn->extrainfo1 = 0;
 | 
						|
					}
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				default:
 | 
						|
					break;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
boolean M_NotFreePlay(void)
 | 
						|
{
 | 
						|
	UINT8 i;
 | 
						|
	UINT8 nump = 0;
 | 
						|
 | 
						|
	if (K_CanChangeRules(true) == false)
 | 
						|
	{
 | 
						|
		// Rounds with direction are never FREE PLAY.
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	if (battleprisons)
 | 
						|
	{
 | 
						|
		// Prison Break is battle's FREE PLAY.
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	for (i = 0; i < MAXPLAYERS; i++)
 | 
						|
	{
 | 
						|
		if (playeringame[i] == false || players[i].spectator == true)
 | 
						|
		{
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		nump++;
 | 
						|
 | 
						|
		if (nump > 1)
 | 
						|
		{
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
UINT16 M_CheckCupEmeralds(UINT8 difficulty)
 | 
						|
{
 | 
						|
	if (difficulty == 0)
 | 
						|
		return 0;
 | 
						|
 | 
						|
	if (difficulty >= KARTGP_MAX)
 | 
						|
		difficulty = KARTGP_MASTER;
 | 
						|
 | 
						|
	cupheader_t *cup;
 | 
						|
	UINT16 ret = 0, seen = 0;
 | 
						|
 | 
						|
	for (cup = kartcupheaders; cup; cup = cup->next)
 | 
						|
	{
 | 
						|
		// Don't use custom material
 | 
						|
		if (cup->id >= basenumkartcupheaders)
 | 
						|
			break;
 | 
						|
 | 
						|
		// Does it not *have* an emerald?
 | 
						|
		if (cup->emeraldnum == 0 || cup->emeraldnum > 14)
 | 
						|
			continue;
 | 
						|
 | 
						|
		UINT16 emerald = 1<<(cup->emeraldnum-1);
 | 
						|
 | 
						|
		// Only count the first reference.
 | 
						|
		if (seen & emerald)
 | 
						|
			continue;
 | 
						|
 | 
						|
		// We've seen it, prevent future repetitions.
 | 
						|
		seen |= emerald;
 | 
						|
 | 
						|
		// Did you actually get it?
 | 
						|
		if (cup->windata[difficulty].got_emerald == false)
 | 
						|
			continue;
 | 
						|
 | 
						|
		// Wa hoo !
 | 
						|
		ret |= emerald;
 | 
						|
	}
 | 
						|
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
// See also M_GetConditionString
 | 
						|
boolean M_CheckCondition(condition_t *cn, player_t *player)
 | 
						|
{
 | 
						|
	switch (cn->type)
 | 
						|
	{
 | 
						|
		case UC_NONE:
 | 
						|
			return false;
 | 
						|
		case UC_PLAYTIME: // Requires total playing time >= x
 | 
						|
			return (gamedata->totalplaytime >= (unsigned)cn->requirement);
 | 
						|
		case UC_ROUNDSPLAYED: // Requires any level completed >= x times
 | 
						|
		{
 | 
						|
			if (cn->extrainfo1 == GDGT_MAX)
 | 
						|
			{
 | 
						|
				UINT8 i;
 | 
						|
				UINT32 sum = 0;
 | 
						|
 | 
						|
				for (i = 0; i < GDGT_MAX; i++)
 | 
						|
				{
 | 
						|
					sum += gamedata->roundsplayed[i];
 | 
						|
				}
 | 
						|
 | 
						|
				return (sum >= (unsigned)cn->requirement);
 | 
						|
			}
 | 
						|
			return (gamedata->roundsplayed[cn->extrainfo1] >= (unsigned)cn->requirement);
 | 
						|
		}
 | 
						|
		case UC_TOTALRINGS: // Requires grabbing >= x rings
 | 
						|
			return (gamedata->totalrings >= (unsigned)cn->requirement);
 | 
						|
		case UC_TOTALTUMBLETIME: // Requires total tumbling time >= x
 | 
						|
			return (gamedata->totaltumbletime >= (unsigned)cn->requirement);
 | 
						|
		case UC_GAMECLEAR: // Requires game beaten >= x times
 | 
						|
			return (gamedata->timesBeaten >= (unsigned)cn->requirement);
 | 
						|
		case UC_OVERALLTIME: // Requires overall time <= x
 | 
						|
			return (M_GotLowEnoughTime(cn->requirement));
 | 
						|
		case UC_MAPVISITED: // Requires map x to be visited
 | 
						|
		case UC_MAPBEATEN: // Requires map x to be beaten
 | 
						|
		case UC_MAPENCORE: // Requires map x to be beaten in encore
 | 
						|
		case UC_MAPSPBATTACK: // Requires map x to be beaten in SPB Attack
 | 
						|
		case UC_MAPMYSTICMELODY: // Mystic Melody on map x's Ancient Shrine
 | 
						|
		{
 | 
						|
			UINT8 mvtype = MV_VISITED;
 | 
						|
			if (cn->type == UC_MAPBEATEN)
 | 
						|
				mvtype = MV_BEATEN;
 | 
						|
			else if (cn->type == UC_MAPENCORE)
 | 
						|
				mvtype = MV_ENCORE;
 | 
						|
			else if (cn->type == UC_MAPSPBATTACK)
 | 
						|
				mvtype = MV_SPBATTACK;
 | 
						|
			else if (cn->type == UC_MAPMYSTICMELODY)
 | 
						|
				mvtype = MV_MYSTICMELODY;
 | 
						|
 | 
						|
			return ((cn->requirement < nummapheaders)
 | 
						|
				&& (mapheaderinfo[cn->requirement])
 | 
						|
				&& ((mapheaderinfo[cn->requirement]->records.mapvisited & mvtype) == mvtype));
 | 
						|
		}
 | 
						|
		case UC_MAPTIME: // Requires time on map <= x
 | 
						|
			return (G_GetBestTime(cn->extrainfo1) <= (unsigned)cn->requirement);
 | 
						|
 | 
						|
		case UC_CHARACTERWINS:
 | 
						|
			if (cn->requirement < 0)
 | 
						|
				return false;
 | 
						|
 | 
						|
			return (skins[cn->requirement].records.wins >= (UINT32)cn->extrainfo1);
 | 
						|
 | 
						|
		case UC_ALLCUPRECORDS:
 | 
						|
		{
 | 
						|
			if (gamestate == GS_LEVEL)
 | 
						|
				return false; // this one could be laggy with many cups available
 | 
						|
 | 
						|
			INT32 requiredid = cn->requirement;
 | 
						|
			if (requiredid == -1) // stop at all basegame cup
 | 
						|
				requiredid = basenumkartcupheaders;
 | 
						|
 | 
						|
			UINT8 difficulty = cn->extrainfo2;
 | 
						|
			if (difficulty > KARTGP_MASTER)
 | 
						|
				difficulty = KARTGP_MASTER;
 | 
						|
 | 
						|
			cupheader_t *cup;
 | 
						|
			for (cup = kartcupheaders; cup; cup = cup->next)
 | 
						|
			{
 | 
						|
				// Ok, achieved up to the desired cup.
 | 
						|
				if (cup->id == requiredid)
 | 
						|
					return true;
 | 
						|
 | 
						|
				cupwindata_t *windata = &cup->windata[difficulty];
 | 
						|
 | 
						|
				// Did you actually get it?
 | 
						|
				if (windata->best_placement == 0)
 | 
						|
					return false;
 | 
						|
 | 
						|
				// Sufficient placement?
 | 
						|
				if (cn->extrainfo1 && windata->best_placement > cn->extrainfo1)
 | 
						|
					return false;
 | 
						|
			}
 | 
						|
 | 
						|
			// If we ended up here, check we were looking for all cups achieved.
 | 
						|
			return (requiredid == basenumkartcupheaders);
 | 
						|
		}
 | 
						|
 | 
						|
 | 
						|
		case UC_ALLCHAOS:
 | 
						|
		case UC_ALLSUPER:
 | 
						|
		case UC_ALLEMERALDS:
 | 
						|
		{
 | 
						|
			UINT16 ret = 0;
 | 
						|
 | 
						|
			if (gamestate == GS_LEVEL)
 | 
						|
				return false; // this one could be laggy with many cups available
 | 
						|
 | 
						|
			ret = M_CheckCupEmeralds(cn->requirement);
 | 
						|
 | 
						|
			if (cn->type == UC_ALLCHAOS)
 | 
						|
				return ALLCHAOSEMERALDS(ret);
 | 
						|
			if (cn->type == UC_ALLSUPER)
 | 
						|
				return ALLSUPEREMERALDS(ret);
 | 
						|
			return ALLEMERALDS(ret);
 | 
						|
		}
 | 
						|
 | 
						|
		case UC_TOTALMEDALS: // Requires number of emblems >= x
 | 
						|
			return (M_GotEnoughMedals(cn->requirement));
 | 
						|
		case UC_EMBLEM: // Requires emblem x to be obtained
 | 
						|
			return gamedata->collected[cn->requirement-1];
 | 
						|
		case UC_UNLOCKABLE: // Requires unlockable x to be obtained
 | 
						|
			return gamedata->unlocked[cn->requirement-1];
 | 
						|
		case UC_CONDITIONSET: // requires condition set x to already be achieved
 | 
						|
			return M_Achieved(cn->requirement-1);
 | 
						|
 | 
						|
		case UC_UNLOCKPERCENT:
 | 
						|
		{
 | 
						|
			// Don't let netgame sessions intefere
 | 
						|
			// or have this give a performance hit
 | 
						|
			// (This is formulated this way to
 | 
						|
			// perfectly eclipse M_CheckNetUnlockByID)
 | 
						|
			if (netgame || demo.playback || Playing())
 | 
						|
				return false;
 | 
						|
 | 
						|
			UINT16 i, unlocked = cn->extrainfo2, total = 0;
 | 
						|
 | 
						|
			// Special case for maps
 | 
						|
			if (cn->extrainfo1 == SECRET_MAP)
 | 
						|
			{
 | 
						|
				for (i = 0; i < basenummapheaders; i++)
 | 
						|
				{
 | 
						|
					if (!mapheaderinfo[i] || mapheaderinfo[i]->menuflags & (LF2_HIDEINMENU))
 | 
						|
						continue;
 | 
						|
 | 
						|
					total++;
 | 
						|
 | 
						|
					// Check for completion
 | 
						|
					if ((mapheaderinfo[i]->menuflags & LF2_FINISHNEEDED)
 | 
						|
					&& !(mapheaderinfo[i]->records.mapvisited & MV_BEATEN))
 | 
						|
						continue;
 | 
						|
 | 
						|
					// Check for unlock
 | 
						|
					if (M_MapLocked(i+1))
 | 
						|
						continue;
 | 
						|
 | 
						|
					unlocked++;
 | 
						|
				}
 | 
						|
			}
 | 
						|
			// Special case for SECRET_COLOR
 | 
						|
			else if (cn->extrainfo1 == SECRET_COLOR)
 | 
						|
			{
 | 
						|
				total = gamedata->numspraycans;
 | 
						|
				unlocked = gamedata->gotspraycans;
 | 
						|
			}
 | 
						|
			// Special case for raw Challenge count
 | 
						|
			else if (cn->extrainfo1 == SECRET_NONE)
 | 
						|
			{
 | 
						|
				for (i = 0; i < MAXUNLOCKABLES; i++)
 | 
						|
				{
 | 
						|
					if (unlockables[i].type == SECRET_NONE)
 | 
						|
						continue;
 | 
						|
 | 
						|
					total++;
 | 
						|
 | 
						|
					if (M_Achieved(unlockables[i].conditionset - 1) == false)
 | 
						|
						continue;
 | 
						|
 | 
						|
					unlocked++;
 | 
						|
				}
 | 
						|
			}
 | 
						|
			else
 | 
						|
			{
 | 
						|
				for (i = 0; i < MAXUNLOCKABLES; i++)
 | 
						|
				{
 | 
						|
					if (unlockables[i].type != cn->extrainfo1)
 | 
						|
						continue;
 | 
						|
 | 
						|
					total++;
 | 
						|
 | 
						|
					if (gamedata->unlocked[i] == false)
 | 
						|
						continue;
 | 
						|
 | 
						|
					unlocked++;
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			if (!total)
 | 
						|
				return false;
 | 
						|
 | 
						|
			// No need to do a pesky divide
 | 
						|
			return ((100 * unlocked) >= (total * cn->requirement));
 | 
						|
		}
 | 
						|
 | 
						|
		case UC_ADDON:
 | 
						|
			return ((gamedata->everloadedaddon == true)
 | 
						|
				&& M_SecretUnlocked(SECRET_ADDONS, true));
 | 
						|
		case UC_CREDITS:
 | 
						|
			return (gamedata->everfinishedcredits == true);
 | 
						|
		case UC_REPLAY:
 | 
						|
			return (gamedata->eversavedreplay == true);
 | 
						|
		case UC_CRASH:
 | 
						|
			if (gamedata->evercrashed)
 | 
						|
			{
 | 
						|
				if (gamedata->musicstate < GDMUSIC_LOSERCLUB)
 | 
						|
					gamedata->musicstate = GDMUSIC_LOSERCLUB;
 | 
						|
				return true;
 | 
						|
			}
 | 
						|
			return false;
 | 
						|
		case UC_TUTORIALSKIP:
 | 
						|
			return (gamedata->finishedtutorialchallenge == true);
 | 
						|
		case UC_PASSWORD:
 | 
						|
			return (cn->stringvar == NULL);
 | 
						|
 | 
						|
		case UC_SPRAYCAN:
 | 
						|
		{
 | 
						|
			if (cn->requirement <= 0
 | 
						|
			|| cn->requirement >= numskincolors)
 | 
						|
				return false;
 | 
						|
 | 
						|
			UINT16 can_id = skincolors[cn->requirement].cache_spraycan;
 | 
						|
 | 
						|
			if (can_id >= gamedata->numspraycans)
 | 
						|
				return false;
 | 
						|
 | 
						|
			return (gamedata->spraycans[can_id].map < nummapheaders);
 | 
						|
		}
 | 
						|
 | 
						|
		case UC_PRISONEGGCD:
 | 
						|
			return ((gamedata->thisprisoneggpickupgrabbed == true) && (cn == gamedata->thisprisoneggpickup_cached));
 | 
						|
 | 
						|
		// Just for string building
 | 
						|
		case UC_AND:
 | 
						|
		case UC_THEN:
 | 
						|
		case UC_COMMA:
 | 
						|
		case UC_DESCRIPTIONOVERRIDE:
 | 
						|
			return true;
 | 
						|
 | 
						|
		case UCRP_PREFIX_GRANDPRIX:
 | 
						|
			return (grandprixinfo.gp == true);
 | 
						|
		case UCRP_PREFIX_BONUSROUND:
 | 
						|
			return ((grandprixinfo.gp == true) && (grandprixinfo.eventmode == GPEVENT_BONUS));
 | 
						|
		case UCRP_PREFIX_TIMEATTACK:
 | 
						|
			return (modeattacking != ATTACKING_NONE);
 | 
						|
		case UCRP_PREFIX_PRISONBREAK:
 | 
						|
			return ((gametyperules & GTR_PRISONS) && battleprisons);
 | 
						|
		case UCRP_PREFIX_SEALEDSTAR:
 | 
						|
			return (specialstageinfo.valid == true);
 | 
						|
 | 
						|
		case UCRP_PREFIX_ISMAP:
 | 
						|
		case UCRP_ISMAP:
 | 
						|
			return (gamemap == cn->requirement+1);
 | 
						|
		case UCRP_ISCHARACTER:
 | 
						|
			return (
 | 
						|
				player->roundconditions.switched_skin == false
 | 
						|
				&& player->skin == cn->requirement
 | 
						|
			);
 | 
						|
		case UCRP_ISENGINECLASS:
 | 
						|
			return (player->roundconditions.switched_skin == false
 | 
						|
				&& player->skin < numskins
 | 
						|
				&& R_GetEngineClass(
 | 
						|
					skins[player->skin].kartspeed,
 | 
						|
					skins[player->skin].kartweight,
 | 
						|
					skins[player->skin].flags
 | 
						|
				) == (unsigned)cn->requirement);
 | 
						|
		case UCRP_HASFOLLOWER:
 | 
						|
			return (cn->requirement != -1 && player->followerskin == cn->requirement);
 | 
						|
		case UCRP_ISDIFFICULTY:
 | 
						|
			if (grandprixinfo.gp == false)
 | 
						|
				return false;
 | 
						|
			if (cn->requirement == KARTGP_MASTER)
 | 
						|
				return (grandprixinfo.masterbots == true);
 | 
						|
			return (grandprixinfo.gamespeed >= cn->requirement);
 | 
						|
		case UCRP_ISGEAR:
 | 
						|
			return (gamespeed >= cn->requirement);
 | 
						|
 | 
						|
		case UCRP_PODIUMCUP:
 | 
						|
			if (grandprixinfo.gp == false || K_PodiumSequence() == false)
 | 
						|
				return false;
 | 
						|
			if (grandprixinfo.cup == NULL
 | 
						|
				|| (
 | 
						|
					cn->requirement != -1 // Any
 | 
						|
					&& grandprixinfo.cup->id != cn->requirement
 | 
						|
				)
 | 
						|
			)
 | 
						|
				return false;
 | 
						|
			if (cn->extrainfo2 != 0)
 | 
						|
				return (K_PodiumGrade() >= cn->extrainfo1);
 | 
						|
			if (cn->extrainfo1 != 0)
 | 
						|
				return (player->position != 0
 | 
						|
					&& player->position <= cn->extrainfo1);
 | 
						|
			return true;
 | 
						|
		case UCRP_PODIUMEMERALD:
 | 
						|
		case UCRP_PODIUMPRIZE:
 | 
						|
			return (grandprixinfo.gp == true
 | 
						|
				&& K_PodiumSequence() == true
 | 
						|
				&& grandprixinfo.rank.specialWon == true);
 | 
						|
		case UCRP_PODIUMNOCONTINUES:
 | 
						|
			return (grandprixinfo.gp == true
 | 
						|
				&& K_PodiumSequence() == true
 | 
						|
				&& grandprixinfo.rank.continuesUsed == 0);
 | 
						|
 | 
						|
		case UCRP_FINISHCOOL:
 | 
						|
			return (player->exiting
 | 
						|
				&& !(player->pflags & PF_NOCONTEST)
 | 
						|
				&& M_NotFreePlay()
 | 
						|
				&& !K_IsPlayerLosing(player));
 | 
						|
		case UCRP_FINISHPERFECT:
 | 
						|
			return (player->exiting
 | 
						|
				&& !(player->pflags & PF_NOCONTEST)
 | 
						|
				&& M_NotFreePlay()
 | 
						|
				&& (gamespeed != KARTSPEED_EASY)
 | 
						|
				&& (player->tally.active == true)
 | 
						|
				&& (player->tally.totalLaps > 0) // Only true if not Time Attack
 | 
						|
				&& (player->tally.laps >= player->tally.totalLaps));
 | 
						|
		case UCRP_FINISHALLPRISONS:
 | 
						|
			return (battleprisons
 | 
						|
				&& !(player->pflags & PF_NOCONTEST)
 | 
						|
				//&& M_NotFreePlay()
 | 
						|
				&& numtargets >= maptargets);
 | 
						|
		case UCRP_SURVIVE:
 | 
						|
			return (player->exiting
 | 
						|
				&& !(player->pflags & PF_NOCONTEST));
 | 
						|
		case UCRP_NOCONTEST:
 | 
						|
			return (player->pflags & PF_NOCONTEST);
 | 
						|
 | 
						|
		case UCRP_SMASHUFO:
 | 
						|
			return (
 | 
						|
				specialstageinfo.valid == true
 | 
						|
				&& (
 | 
						|
					P_MobjWasRemoved(specialstageinfo.ufo)
 | 
						|
					|| specialstageinfo.ufo->health <= 1
 | 
						|
				)
 | 
						|
			);
 | 
						|
		case UCRP_CHASEDBYSPB:
 | 
						|
			// The PERFECT implementation would check spbplace, iterate over trackercap, etc.
 | 
						|
			// But the game already has this handy-dandy SPB signal for us...
 | 
						|
			// It's only MAYBE invalid in modded context. And mods can already cheat...
 | 
						|
			return ((player->pflags & PF_RINGLOCK) == PF_RINGLOCK);
 | 
						|
		case UCRP_MAPDESTROYOBJECTS:
 | 
						|
			return (
 | 
						|
				gamemap == cn->requirement+1
 | 
						|
				&& numchallengedestructibles == UINT16_MAX
 | 
						|
			);
 | 
						|
 | 
						|
		case UCRP_MAKERETIRE:
 | 
						|
		{
 | 
						|
			// You can't "make" someone retire in coop.
 | 
						|
			if (K_Cooperative() == true)
 | 
						|
			{
 | 
						|
				return false;
 | 
						|
			}
 | 
						|
 | 
						|
			// The following is basically UCRP_FINISHCOOL,
 | 
						|
			// but without the M_NotFreePlay check since this
 | 
						|
			// condition is already dependent on other players.
 | 
						|
			if ((player->exiting
 | 
						|
				&& !(player->pflags & PF_NOCONTEST)
 | 
						|
				&& !K_IsPlayerLosing(player)) == false)
 | 
						|
			{
 | 
						|
				return false;
 | 
						|
			}
 | 
						|
 | 
						|
			UINT8 i;
 | 
						|
			for (i = 0; i < MAXPLAYERS; i++)
 | 
						|
			{
 | 
						|
				if (playeringame[i] == false)
 | 
						|
				{
 | 
						|
					continue;
 | 
						|
				}
 | 
						|
 | 
						|
				// This player is ME!
 | 
						|
				if (player == players+i)
 | 
						|
				{
 | 
						|
					continue;
 | 
						|
				}
 | 
						|
 | 
						|
				// This player didn't NO CONTEST.
 | 
						|
				if (!(players[i].pflags & PF_NOCONTEST))
 | 
						|
				{
 | 
						|
					continue;
 | 
						|
				}
 | 
						|
 | 
						|
				// This player doesn't have the right skin.
 | 
						|
				if (players[i].skin != cn->requirement)
 | 
						|
				{
 | 
						|
					continue;
 | 
						|
				}
 | 
						|
 | 
						|
				// Okay, the right player is dead!
 | 
						|
				break;
 | 
						|
			}
 | 
						|
 | 
						|
			return (i != MAXPLAYERS);
 | 
						|
		}
 | 
						|
 | 
						|
		case UCRP_FINISHPLACE:
 | 
						|
			return (player->exiting
 | 
						|
				&& !(player->pflags & PF_NOCONTEST)
 | 
						|
				&& M_NotFreePlay()
 | 
						|
				&& player->position != 0
 | 
						|
				&& player->position <= cn->requirement);
 | 
						|
		case UCRP_FINISHPLACEEXACT:
 | 
						|
			return (player->exiting
 | 
						|
				&& !(player->pflags & PF_NOCONTEST)
 | 
						|
				&& M_NotFreePlay()
 | 
						|
				&& player->position == cn->requirement);
 | 
						|
		case UCRP_FINISHGRADE:
 | 
						|
			return (player->exiting
 | 
						|
				&& !(player->pflags & PF_NOCONTEST)
 | 
						|
				&& M_NotFreePlay()
 | 
						|
				&& (player->tally.active == true)
 | 
						|
				&& (player->tally.state >= TALLY_ST_GRADE_APPEAR)
 | 
						|
				&& (player->tally.state <= TALLY_ST_DONE)
 | 
						|
				&& (player->tally.rank >= cn->requirement));
 | 
						|
		case UCRP_FINISHTIME:
 | 
						|
			return (player->exiting
 | 
						|
				&& !(player->pflags & PF_NOCONTEST)
 | 
						|
				&& (!battleprisons || numtargets >= maptargets)
 | 
						|
				//&& M_NotFreePlay()
 | 
						|
				&& player->realtime <= (unsigned)cn->requirement);
 | 
						|
		case UCRP_FINISHTIMEEXACT:
 | 
						|
			return (player->exiting
 | 
						|
				&& !(player->pflags & PF_NOCONTEST)
 | 
						|
				&& (!battleprisons || numtargets >= maptargets)
 | 
						|
				//&& M_NotFreePlay()
 | 
						|
				&& player->realtime/TICRATE == (unsigned)cn->requirement/TICRATE);
 | 
						|
		case UCRP_FINISHTIMELEFT:
 | 
						|
			return (timelimitintics
 | 
						|
				&& player->exiting
 | 
						|
				&& !(player->pflags & PF_NOCONTEST)
 | 
						|
				&& (!battleprisons || numtargets >= maptargets)
 | 
						|
				&& !K_CanChangeRules(false) // too easy to change cv_timelimit
 | 
						|
				&& player->realtime < timelimitintics
 | 
						|
				&& (timelimitintics + extratimeintics + secretextratime - player->realtime) >= (unsigned)cn->requirement);
 | 
						|
 | 
						|
		case UCRP_RINGS:
 | 
						|
			return (player->hudrings >= cn->requirement);
 | 
						|
		case UCRP_RINGSEXACT:
 | 
						|
			return (player->hudrings == cn->requirement);
 | 
						|
 | 
						|
		case UCRP_SPEEDOMETER:
 | 
						|
			return (player->roundconditions.maxspeed >= cn->requirement);
 | 
						|
		case UCRP_DRAFTDURATION:
 | 
						|
			return (player->roundconditions.continuousdraft_best >= ((tic_t)cn->requirement)*TICRATE);
 | 
						|
		case UCRP_GROWCONSECUTIVEBEAMS:
 | 
						|
			return (player->roundconditions.best_consecutive_grow_lasers >= cn->requirement);
 | 
						|
 | 
						|
		case UCRP_TRIGGER: // requires map trigger set
 | 
						|
			return !!(player->roundconditions.unlocktriggers & (1 << cn->requirement));
 | 
						|
 | 
						|
		case UCRP_FALLOFF:
 | 
						|
			return ((cn->requirement == 1 || player->exiting || (player->pflags & PF_NOCONTEST))
 | 
						|
				&& player->roundconditions.fell_off == (cn->requirement == 1));
 | 
						|
		case UCRP_TOUCHOFFROAD:
 | 
						|
			return ((cn->requirement == 1 || player->exiting || (player->pflags & PF_NOCONTEST))
 | 
						|
				&& player->roundconditions.touched_offroad == (cn->requirement == 1));
 | 
						|
		case UCRP_TOUCHSNEAKERPANEL:
 | 
						|
			return ((cn->requirement == 1 || player->exiting || (player->pflags & PF_NOCONTEST))
 | 
						|
				&& player->roundconditions.touched_sneakerpanel == (cn->requirement == 1));
 | 
						|
		case UCRP_RINGDEBT:
 | 
						|
			return (!(gametyperules & GTR_SPHERES)
 | 
						|
				&& (cn->requirement == 1 || player->exiting || (player->pflags & PF_NOCONTEST))
 | 
						|
				&& (player->roundconditions.debt_rings == (cn->requirement == 1)));
 | 
						|
		case UCRP_FAULTED:
 | 
						|
			return ((cn->requirement == 1 || player->latestlap >= 1)
 | 
						|
				&& (player->roundconditions.faulted == (cn->requirement == 1)));
 | 
						|
 | 
						|
		case UCRP_TRIPWIREHYUU:
 | 
						|
			return (player->roundconditions.tripwire_hyuu);
 | 
						|
		case UCRP_WHIPHYUU:
 | 
						|
			return (player->roundconditions.whip_hyuu);
 | 
						|
		case UCRP_SPBNEUTER:
 | 
						|
			return (player->roundconditions.spb_neuter);
 | 
						|
		case UCRP_LANDMINEDUNK:
 | 
						|
			return (player->roundconditions.landmine_dunk);
 | 
						|
		case UCRP_HITMIDAIR:
 | 
						|
			return (player->roundconditions.hit_midair);
 | 
						|
		case UCRP_HITDRAFTERLOOKBACK:
 | 
						|
			return (player->roundconditions.hit_drafter_lookback);
 | 
						|
		case UCRP_GIANTRACERSHRUNKENORBI:
 | 
						|
			return (player->roundconditions.giant_foe_shrunken_orbi);
 | 
						|
		case UCRP_RETURNMARKTOSENDER:
 | 
						|
			return (player->roundconditions.returntosender_mark);
 | 
						|
 | 
						|
		case UCRP_TRACKHAZARD:
 | 
						|
		{
 | 
						|
			if (!(gametyperules & GTR_CIRCUIT))
 | 
						|
			{
 | 
						|
				// Prison Break/Versus
 | 
						|
 | 
						|
				if (!player->exiting && cn->requirement == 0)
 | 
						|
					return false;
 | 
						|
 | 
						|
				return (((player->roundconditions.hittrackhazard[0] & 1) == 1) == (cn->requirement == 1));
 | 
						|
			}
 | 
						|
 | 
						|
			INT16 requiredlap = cn->extrainfo1;
 | 
						|
 | 
						|
			if (requiredlap < 0)
 | 
						|
			{
 | 
						|
				// Prevents lowered numlaps from activating it
 | 
						|
				// (this also handles exiting, for all-laps situations)
 | 
						|
				requiredlap = max(mapheaderinfo[gamemap-1]->numlaps, numlaps);
 | 
						|
			}
 | 
						|
 | 
						|
			// cn->requirement is used as an offset here
 | 
						|
			// so if you need to get hit on lap x, the
 | 
						|
			// condition can fire while that lap is active
 | 
						|
			// but if you need to NOT get hit on lap X,
 | 
						|
			// it only fires once the lap is complete
 | 
						|
			if (player->latestlap <= (requiredlap - cn->requirement))
 | 
						|
				return false;
 | 
						|
 | 
						|
			UINT8 requiredbit = 1<<(requiredlap & 7);
 | 
						|
			requiredlap /= 8;
 | 
						|
 | 
						|
			if (cn->extrainfo1 == -1)
 | 
						|
			{
 | 
						|
				if (cn->requirement == 0)
 | 
						|
				{
 | 
						|
					// The "don't get hit on any lap" check is trivial.
 | 
						|
					for (; requiredlap > 0; requiredlap--)
 | 
						|
					{
 | 
						|
						if (player->roundconditions.hittrackhazard[requiredlap] != 0)
 | 
						|
							return false;
 | 
						|
					}
 | 
						|
 | 
						|
					return (player->roundconditions.hittrackhazard[0] == 0);
 | 
						|
				}
 | 
						|
 | 
						|
				// The following is my attempt at a major optimisation.
 | 
						|
				// The naive version was MAX_LAP bools, which is ridiculous.
 | 
						|
 | 
						|
				// Check the highest relevant byte for all necessary bits.
 | 
						|
				// We only do this if an == 0xFF/0xFE check wouldn't satisfy.
 | 
						|
				if (requiredbit != (1<<7))
 | 
						|
				{
 | 
						|
					// Last bit MAYBE not needed, POSITION doesn't count.
 | 
						|
					const UINT8 finalbit = (requiredlap == 0) ? 1 : 0;
 | 
						|
					while (requiredbit != finalbit)
 | 
						|
					{
 | 
						|
						if (!(player->roundconditions.hittrackhazard[requiredlap] & requiredbit))
 | 
						|
							return false;
 | 
						|
						requiredbit /= 2;
 | 
						|
					}
 | 
						|
 | 
						|
					if (requiredlap == 0)
 | 
						|
						return true;
 | 
						|
 | 
						|
					requiredlap--;
 | 
						|
				}
 | 
						|
 | 
						|
				// All bytes between the top and the bottom need to be checked for saturation.
 | 
						|
				for (; requiredlap > 0; requiredlap--)
 | 
						|
				{
 | 
						|
					if (player->roundconditions.hittrackhazard[requiredlap] != 0xFF)
 | 
						|
						return false;
 | 
						|
				}
 | 
						|
 | 
						|
				// Last bit not needed, POSITION doesn't count.
 | 
						|
				return (player->roundconditions.hittrackhazard[0] == 0xFE);
 | 
						|
			}
 | 
						|
 | 
						|
			return (((player->roundconditions.hittrackhazard[requiredlap] & requiredbit) == requiredbit) == (cn->requirement == 1));
 | 
						|
		}
 | 
						|
 | 
						|
		case UCRP_TARGETATTACKMETHOD:
 | 
						|
			return (player->roundconditions.targetdamaging == (targetdamaging_t)cn->requirement);
 | 
						|
 | 
						|
		case UCRP_GACHABOMMISER:
 | 
						|
			return (
 | 
						|
				player->roundconditions.targetdamaging == UFOD_GACHABOM
 | 
						|
				&& player->roundconditions.gachabom_miser != 0xFF
 | 
						|
			);
 | 
						|
 | 
						|
		case UCRP_WETPLAYER:
 | 
						|
			return (((player->roundconditions.wet_player & cn->requirement) == 0)
 | 
						|
				&& !player->roundconditions.fell_off); // Levels with water tend to texture their pits as water too
 | 
						|
	}
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
static boolean M_CheckConditionSet(conditionset_t *c, player_t *player)
 | 
						|
{
 | 
						|
	UINT32 i;
 | 
						|
	UINT32 lastID = 0;
 | 
						|
	condition_t *cn;
 | 
						|
	boolean achievedSoFar = true;
 | 
						|
 | 
						|
	for (i = 0; i < c->numconditions; ++i)
 | 
						|
	{
 | 
						|
		cn = &c->condition[i];
 | 
						|
 | 
						|
		// If the ID is changed and all previous statements of the same ID were true
 | 
						|
		// then this condition has been successfully achieved
 | 
						|
		if (lastID && lastID != cn->id && achievedSoFar)
 | 
						|
			return true;
 | 
						|
 | 
						|
		// Skip future conditions with the same ID if one fails, for obvious reasons
 | 
						|
		if (lastID && lastID == cn->id && !achievedSoFar)
 | 
						|
			continue;
 | 
						|
 | 
						|
		// Skip entries that are JUST for string building
 | 
						|
		if (cn->type == UC_AND || cn->type == UC_THEN || cn->type == UC_COMMA || cn->type == UC_DESCRIPTIONOVERRIDE)
 | 
						|
			continue;
 | 
						|
 | 
						|
		lastID = cn->id;
 | 
						|
 | 
						|
		if ((player != NULL) != (cn->type >= UCRP_REQUIRESPLAYING))
 | 
						|
		{
 | 
						|
			//CONS_Printf("skipping %s:%u:%u (%s)\n", sizeu1(c-conditionSets), cn->id, i, player ? "player exists" : "player does not exist");
 | 
						|
			achievedSoFar = false;
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		achievedSoFar = M_CheckCondition(cn, player);
 | 
						|
		//CONS_Printf("%s:%u:%u - %u is %s\n", sizeu1(c-conditionSets), cn->id, i, cn->type, achievedSoFar ? "true" : "false");
 | 
						|
	}
 | 
						|
 | 
						|
	return achievedSoFar;
 | 
						|
}
 | 
						|
 | 
						|
static char *M_BuildConditionTitle(UINT16 map)
 | 
						|
{
 | 
						|
	char *title, *ref;
 | 
						|
 | 
						|
	if ((!(mapheaderinfo[map]->menuflags & LF2_NOVISITNEEDED)
 | 
						|
	// the following is intentionally not MV_BEATEN, just in case the title is for "Finish a round on X"
 | 
						|
	&& !(mapheaderinfo[map]->records.mapvisited & MV_VISITED))
 | 
						|
	|| M_MapLocked(map+1))
 | 
						|
		return Z_StrDup("???");
 | 
						|
 | 
						|
	if (mapheaderinfo[map]->menuttl[0])
 | 
						|
	{
 | 
						|
		if (mapheaderinfo[map]->typeoflevel & TOL_TUTORIAL)
 | 
						|
		{
 | 
						|
			// Intentionally not forced uppercase
 | 
						|
			return Z_StrDup(va("the %s Tutorial", mapheaderinfo[map]->menuttl));
 | 
						|
		}
 | 
						|
		title = ref = Z_StrDup(mapheaderinfo[map]->menuttl);
 | 
						|
	}
 | 
						|
	else
 | 
						|
	{
 | 
						|
		title = ref = G_BuildMapTitle(map+1);
 | 
						|
	}
 | 
						|
 | 
						|
	if (!title)
 | 
						|
		I_Error("M_BuildConditionTitle: out of memory");
 | 
						|
 | 
						|
	while (*ref != '\0')
 | 
						|
	{
 | 
						|
		*ref = toupper(*ref);
 | 
						|
		ref++;
 | 
						|
	}
 | 
						|
 | 
						|
	return title;
 | 
						|
}
 | 
						|
 | 
						|
static const char *M_GetConditionCharacter(INT32 skin, boolean directlyrequires)
 | 
						|
{
 | 
						|
	// First we check for direct unlock.
 | 
						|
	boolean permitname = R_SkinUsable(-1, skin, false);
 | 
						|
 | 
						|
	if (permitname == false && directlyrequires == false)
 | 
						|
	{
 | 
						|
		// If there's no direct unlock, we CAN check for if the
 | 
						|
		// character is the Rival of somebody we DO have unlocked...
 | 
						|
 | 
						|
		UINT8 i, j;
 | 
						|
		for (i = 0; i < numskins; i++)
 | 
						|
		{
 | 
						|
			if (i == skin)
 | 
						|
				continue;
 | 
						|
 | 
						|
			if (R_SkinUsable(-1, i, false) == false)
 | 
						|
				continue;
 | 
						|
 | 
						|
			for (j = 0; j < SKINRIVALS; j++)
 | 
						|
			{
 | 
						|
				const char *rivalname = skins[i].rivals[j];
 | 
						|
				INT32 rivalnum = R_SkinAvailableEx(rivalname, false);
 | 
						|
 | 
						|
				if (rivalnum != skin)
 | 
						|
					continue;
 | 
						|
 | 
						|
				// We can see this character as a Rival!
 | 
						|
				break;
 | 
						|
			}
 | 
						|
 | 
						|
			if (j == SKINRIVALS)
 | 
						|
				continue;
 | 
						|
 | 
						|
			// "break" our way up the nesting...
 | 
						|
			break;
 | 
						|
		}
 | 
						|
 | 
						|
		// We stopped before the end, we can see it!
 | 
						|
		if (i != numskins)
 | 
						|
			permitname = true;
 | 
						|
	}
 | 
						|
 | 
						|
	return (permitname)
 | 
						|
		? skins[skin].realname
 | 
						|
		: "???";
 | 
						|
}
 | 
						|
 | 
						|
static const char *M_GetNthType(UINT8 position)
 | 
						|
{
 | 
						|
	if (position == 1)
 | 
						|
		return "st";
 | 
						|
	if (position == 2)
 | 
						|
		return "nd";
 | 
						|
	if (position == 3)
 | 
						|
		return "rd";
 | 
						|
	return "th";
 | 
						|
}
 | 
						|
 | 
						|
// See also M_CheckCondition
 | 
						|
static const char *M_GetConditionString(condition_t *cn)
 | 
						|
{
 | 
						|
	INT32 i;
 | 
						|
	char *title = NULL;
 | 
						|
	const char *work = NULL;
 | 
						|
 | 
						|
	// If this function returns NULL, it stops building the condition and just does ???'s.
 | 
						|
 | 
						|
	switch (cn->type)
 | 
						|
	{
 | 
						|
		case UC_PLAYTIME: // Requires total playing time >= x
 | 
						|
			return va("play the game for %i:%02i:%02i",
 | 
						|
				G_TicsToHours(cn->requirement),
 | 
						|
				G_TicsToMinutes(cn->requirement, false),
 | 
						|
				G_TicsToSeconds(cn->requirement));
 | 
						|
 | 
						|
		case UC_ROUNDSPLAYED: // Requires any level completed >= x times
 | 
						|
			if (cn->extrainfo1 == GDGT_MAX)
 | 
						|
				work = "";
 | 
						|
			else if (cn->extrainfo1 != GDGT_RACE && cn->extrainfo1 != GDGT_BATTLE // Base gametypes
 | 
						|
				&& (cn->extrainfo1 != GDGT_CUSTOM || M_SecretUnlocked(SECRET_ADDONS, true) == false) // Custom is visible at 0 if addons are unlocked
 | 
						|
				&& gamedata->roundsplayed[cn->extrainfo1] == 0)
 | 
						|
					work = " ???";
 | 
						|
			else switch (cn->extrainfo1)
 | 
						|
			{
 | 
						|
				case GDGT_RACE:
 | 
						|
					work = " Race";
 | 
						|
					break;
 | 
						|
				case GDGT_PRISONS:
 | 
						|
					work = " Prison";
 | 
						|
					break;
 | 
						|
				case GDGT_BATTLE:
 | 
						|
					work = " Battle";
 | 
						|
					break;
 | 
						|
				case GDGT_SPECIAL:
 | 
						|
					work = " Special";
 | 
						|
					break;
 | 
						|
				case GDGT_CUSTOM:
 | 
						|
					work = " custom gametype";
 | 
						|
					break;
 | 
						|
				default:
 | 
						|
					return va("INVALID GAMETYPE CONDITION \"%d:%d:%d\"", cn->type, cn->extrainfo1, cn->requirement);
 | 
						|
			}
 | 
						|
 | 
						|
			return va("clear %d%s Round%s", cn->requirement, work,
 | 
						|
				(cn->requirement == 1 ? "" : "s"));
 | 
						|
 | 
						|
		case UC_TOTALRINGS: // Requires collecting >= x rings
 | 
						|
			if (cn->requirement >= 1000000)
 | 
						|
				return va("collect %u,%03u,%03u Rings", (cn->requirement/1000000), (cn->requirement/1000)%1000, (cn->requirement%1000));
 | 
						|
			if (cn->requirement >= 1000)
 | 
						|
				return va("collect %u,%03u Rings", (cn->requirement/1000), (cn->requirement%1000));
 | 
						|
			return va("collect %u Rings", cn->requirement);
 | 
						|
 | 
						|
		case UC_TOTALTUMBLETIME:
 | 
						|
			return va("tumble through the air for %i:%02i.%02i",
 | 
						|
				G_TicsToMinutes(cn->requirement, true),
 | 
						|
				G_TicsToSeconds(cn->requirement),
 | 
						|
				G_TicsToCentiseconds(cn->requirement));
 | 
						|
 | 
						|
		case UC_GAMECLEAR: // Requires game beaten >= x times
 | 
						|
			if (cn->requirement > 1)
 | 
						|
				return va("beat the game %d times", cn->requirement);
 | 
						|
			else
 | 
						|
				return va("beat the game");
 | 
						|
 | 
						|
		case UC_OVERALLTIME: // Requires overall time <= x
 | 
						|
			return va("get overall time of %i:%02i:%02i",
 | 
						|
				G_TicsToHours(cn->requirement),
 | 
						|
				G_TicsToMinutes(cn->requirement, false),
 | 
						|
				G_TicsToSeconds(cn->requirement));
 | 
						|
 | 
						|
		case UC_MAPVISITED: // Requires map x to be visited
 | 
						|
		case UC_MAPBEATEN: // Requires map x to be beaten
 | 
						|
		case UC_MAPENCORE: // Requires map x to be beaten in encore
 | 
						|
		case UC_MAPSPBATTACK: // Requires map x to be beaten in SPB Attack
 | 
						|
		case UC_MAPMYSTICMELODY: // Mystic Melody on map x's Ancient Shrine
 | 
						|
		{
 | 
						|
			const char *prefix = "";
 | 
						|
 | 
						|
			if (cn->requirement >= nummapheaders || !mapheaderinfo[cn->requirement])
 | 
						|
				return va("INVALID MAP CONDITION \"%d:%d\"", cn->type, cn->requirement);
 | 
						|
 | 
						|
			title = M_BuildConditionTitle(cn->requirement);
 | 
						|
 | 
						|
			if (cn->type == UC_MAPSPBATTACK)
 | 
						|
				prefix = (M_SecretUnlocked(SECRET_SPBATTACK, true) ? "SPB ATTACK: " : "???: ");
 | 
						|
			else if (cn->type == UC_MAPENCORE)
 | 
						|
				prefix = (M_SecretUnlocked(SECRET_ENCORE, true) ? "ENCORE MODE: " : "???: ");
 | 
						|
 | 
						|
			work = "finish a round on";
 | 
						|
			if (cn->type == UC_MAPVISITED)
 | 
						|
				work = "visit";
 | 
						|
			else if (cn->type == UC_MAPSPBATTACK)
 | 
						|
				work = "conquer";
 | 
						|
			else if (cn->type == UC_MAPMYSTICMELODY)
 | 
						|
				work = "play a melody for the ancient shrine in";
 | 
						|
 | 
						|
			work = va("%s%s %s",
 | 
						|
				prefix,
 | 
						|
				work,
 | 
						|
				title);
 | 
						|
			Z_Free(title);
 | 
						|
			return work;
 | 
						|
		}
 | 
						|
 | 
						|
		case UC_MAPTIME: // Requires time on map <= x
 | 
						|
		{
 | 
						|
			if (cn->extrainfo1 >= nummapheaders || !mapheaderinfo[cn->extrainfo1])
 | 
						|
				return va("INVALID MAP CONDITION \"%d:%d:%d\"", cn->type, cn->extrainfo1, cn->requirement);
 | 
						|
 | 
						|
			title = M_BuildConditionTitle(cn->extrainfo1);
 | 
						|
			work = va("beat %s in %i:%02i.%02i", title,
 | 
						|
				G_TicsToMinutes(cn->requirement, true),
 | 
						|
				G_TicsToSeconds(cn->requirement),
 | 
						|
				G_TicsToCentiseconds(cn->requirement));
 | 
						|
 | 
						|
			Z_Free(title);
 | 
						|
			return work;
 | 
						|
		}
 | 
						|
 | 
						|
		case UC_CHARACTERWINS:
 | 
						|
		{
 | 
						|
			if (cn->requirement < 0 || !skins[cn->requirement].realname[0])
 | 
						|
				return va("INVALID CHAR CONDITION \"%d:%d:%d\"", cn->type, cn->requirement, cn->extrainfo1);
 | 
						|
			work = M_GetConditionCharacter(cn->requirement, true);
 | 
						|
			return va("win %d Round%s as %s",
 | 
						|
				cn->extrainfo1,
 | 
						|
				cn->extrainfo1 == 1 ? "" : "s",
 | 
						|
				work);
 | 
						|
		}
 | 
						|
 | 
						|
		case UC_ALLCUPRECORDS:
 | 
						|
		{
 | 
						|
			const char *completetype = "Complete", *orbetter = "", *specialtext = NULL, *speedtext = "";
 | 
						|
 | 
						|
			if (cn->extrainfo1 == 0)
 | 
						|
				;
 | 
						|
			else if (cn->extrainfo1 == 1)
 | 
						|
				completetype = "get Gold over";
 | 
						|
			else
 | 
						|
			{
 | 
						|
				if (cn->extrainfo1 == 2)
 | 
						|
					completetype = "get Silver";
 | 
						|
				else if (cn->extrainfo1 == 3)
 | 
						|
					completetype = "get Bronze";
 | 
						|
				orbetter = " or better over";
 | 
						|
			}
 | 
						|
 | 
						|
			if (cn->extrainfo2 == KARTSPEED_NORMAL)
 | 
						|
			{
 | 
						|
				speedtext = " on Normal";
 | 
						|
			}
 | 
						|
			else if (cn->extrainfo2 == KARTSPEED_HARD)
 | 
						|
			{
 | 
						|
				speedtext = " on Hard";
 | 
						|
			}
 | 
						|
			else if (cn->extrainfo2 == KARTGP_MASTER)
 | 
						|
			{
 | 
						|
				if (M_SecretUnlocked(SECRET_MASTERMODE, true))
 | 
						|
					speedtext = " on Master";
 | 
						|
				else
 | 
						|
					speedtext = " on ???";
 | 
						|
			}
 | 
						|
 | 
						|
			if (cn->requirement == -1)
 | 
						|
				specialtext = "every Cup";
 | 
						|
			else if (M_CupSecondRowLocked() == true && cn->requirement+1 >= CUPMENU_COLUMNS)
 | 
						|
				specialtext = "the first ??? Cups";
 | 
						|
 | 
						|
			if (specialtext != NULL)
 | 
						|
				return va("GRAND PRIX: %s%s %s%s", completetype, orbetter, specialtext, speedtext);
 | 
						|
 | 
						|
			return va("GRAND PRIX: %s%s the first %d Cups%s", completetype, orbetter, cn->requirement, speedtext);
 | 
						|
		}
 | 
						|
 | 
						|
		case UC_ALLCHAOS:
 | 
						|
		case UC_ALLSUPER:
 | 
						|
		case UC_ALLEMERALDS:
 | 
						|
		{
 | 
						|
			const char *chaostext, *speedtext = "";
 | 
						|
 | 
						|
			if (!gamedata->everseenspecial)
 | 
						|
				return NULL;
 | 
						|
 | 
						|
			if (cn->type == UC_ALLCHAOS)
 | 
						|
				chaostext = "7 Chaos";
 | 
						|
			else if (M_CupSecondRowLocked() == true)
 | 
						|
				return NULL;
 | 
						|
			else if (cn->type == UC_ALLSUPER)
 | 
						|
				chaostext = "7 Super";
 | 
						|
			else
 | 
						|
				chaostext = "14";
 | 
						|
 | 
						|
			/*if (cn->requirement == KARTSPEED_NORMAL) -- Emeralds can not be collected on Easy
 | 
						|
			{
 | 
						|
				speedtext = " on Normal";
 | 
						|
			}
 | 
						|
			else*/
 | 
						|
			if (cn->requirement == KARTSPEED_HARD)
 | 
						|
			{
 | 
						|
				speedtext = " on Hard";
 | 
						|
			}
 | 
						|
			else if (cn->requirement == KARTGP_MASTER)
 | 
						|
			{
 | 
						|
				if (M_SecretUnlocked(SECRET_MASTERMODE, true))
 | 
						|
					speedtext = " on Master";
 | 
						|
				else
 | 
						|
					speedtext = " on ???";
 | 
						|
			}
 | 
						|
 | 
						|
			return va("GRAND PRIX: collect all %s Emeralds%s", chaostext, speedtext);
 | 
						|
		}
 | 
						|
 | 
						|
		case UC_TOTALMEDALS: // Requires number of emblems >= x
 | 
						|
			return va("get %d Medals", cn->requirement);
 | 
						|
 | 
						|
		case UC_EMBLEM: // Requires emblem x to be obtained
 | 
						|
		{
 | 
						|
			INT32 checkLevel;
 | 
						|
 | 
						|
			i = cn->requirement-1;
 | 
						|
			checkLevel = M_EmblemMapNum(&emblemlocations[i]);
 | 
						|
 | 
						|
			if (checkLevel >= nummapheaders || !mapheaderinfo[checkLevel] || emblemlocations[i].type == ET_NONE)
 | 
						|
				return va("INVALID MEDAL MAP \"%d:%d\"", cn->requirement, checkLevel);
 | 
						|
 | 
						|
			title = M_BuildConditionTitle(checkLevel);
 | 
						|
			switch (emblemlocations[i].type)
 | 
						|
			{
 | 
						|
				case ET_MAP:
 | 
						|
					work = "";
 | 
						|
					if (emblemlocations[i].flags & ME_SPBATTACK)
 | 
						|
						work = (M_SecretUnlocked(SECRET_SPBATTACK, true) ? "SPB ATTACK: " : "???: ");
 | 
						|
					else if (emblemlocations[i].flags & ME_ENCORE)
 | 
						|
						work = (M_SecretUnlocked(SECRET_ENCORE, true) ? "ENCORE MODE: " : "???: ");
 | 
						|
 | 
						|
					work = va("%s%s %s",
 | 
						|
						work,
 | 
						|
						(emblemlocations[i].flags & ME_SPBATTACK) ? "conquer" : "finish a round on",
 | 
						|
						title);
 | 
						|
					break;
 | 
						|
				case ET_TIME:
 | 
						|
					if (emblemlocations[i].color <= 0 || emblemlocations[i].color >= numskincolors)
 | 
						|
					{
 | 
						|
						Z_Free(title);
 | 
						|
						return va("INVALID MEDAL COLOR \"%d:%d\"", cn->requirement, checkLevel);
 | 
						|
					}
 | 
						|
					work = va("TIME ATTACK: get the %s Medal for %s", skincolors[emblemlocations[i].color].name, title);
 | 
						|
					break;
 | 
						|
				case ET_GLOBAL:
 | 
						|
				{
 | 
						|
					const char *astr, *colorstr, *medalstr;
 | 
						|
 | 
						|
					if (emblemlocations[i].flags & GE_NOTMEDAL)
 | 
						|
					{
 | 
						|
						astr = "a ";
 | 
						|
						colorstr = "";
 | 
						|
						medalstr = "secret";
 | 
						|
					}
 | 
						|
					else if (emblemlocations[i].color <= 0 || emblemlocations[i].color >= numskincolors)
 | 
						|
					{
 | 
						|
						Z_Free(title);
 | 
						|
						return va("INVALID MEDAL COLOR \"%d:%d:%d\"", cn->requirement, emblemlocations[i].tag, checkLevel);
 | 
						|
					}
 | 
						|
					else
 | 
						|
					{
 | 
						|
						astr = "the ";
 | 
						|
						colorstr = skincolors[emblemlocations[i].color].name;
 | 
						|
						medalstr = " Medal";
 | 
						|
					}
 | 
						|
 | 
						|
					if (emblemlocations[i].flags & GE_TIMED)
 | 
						|
					{
 | 
						|
						work = va("%s: find %s%s%s before %i:%02i.%02i",
 | 
						|
							title, astr, colorstr, medalstr,
 | 
						|
							G_TicsToMinutes(emblemlocations[i].var, true),
 | 
						|
							G_TicsToSeconds(emblemlocations[i].var),
 | 
						|
							G_TicsToCentiseconds(emblemlocations[i].var));
 | 
						|
					}
 | 
						|
					else
 | 
						|
					{
 | 
						|
						work = va("%s: find %s%s%s",
 | 
						|
							title, astr, colorstr, medalstr);
 | 
						|
					}
 | 
						|
					break;
 | 
						|
				}
 | 
						|
				default:
 | 
						|
					work = va("find a secret in %s", title);
 | 
						|
					break;
 | 
						|
			}
 | 
						|
 | 
						|
			Z_Free(title);
 | 
						|
			return work;
 | 
						|
		}
 | 
						|
		case UC_UNLOCKABLE: // Requires unlockable x to be obtained
 | 
						|
			return va("get %s",
 | 
						|
				gamedata->unlocked[cn->requirement-1]
 | 
						|
				? unlockables[cn->requirement-1].name
 | 
						|
				: "???");
 | 
						|
 | 
						|
		case UC_UNLOCKPERCENT:
 | 
						|
		{
 | 
						|
			boolean checkavailable = false;
 | 
						|
 | 
						|
			switch (cn->extrainfo1)
 | 
						|
			{
 | 
						|
				case SECRET_NONE:
 | 
						|
					work = "completion";
 | 
						|
					break;
 | 
						|
				case SECRET_EXTRAMEDAL:
 | 
						|
					work = "of Challenge Medals";
 | 
						|
					break;
 | 
						|
				case SECRET_CUP:
 | 
						|
					work = "of Cups";
 | 
						|
					break;
 | 
						|
				case SECRET_MAP:
 | 
						|
					work = "of Courses";
 | 
						|
					break;
 | 
						|
				case SECRET_ALTMUSIC:
 | 
						|
					work = "of alternate music";
 | 
						|
					checkavailable = true;
 | 
						|
					break;
 | 
						|
				case SECRET_SKIN:
 | 
						|
					work = "of Characters";
 | 
						|
					checkavailable = true;
 | 
						|
					break;
 | 
						|
				case SECRET_FOLLOWER:
 | 
						|
					work = "of Followers";
 | 
						|
					checkavailable = true;
 | 
						|
					break;
 | 
						|
				case SECRET_COLOR:
 | 
						|
					work = (gamedata->gotspraycans == 0) ? "of ???" : "of Spray Cans";
 | 
						|
					//checkavailable = true;
 | 
						|
					break;
 | 
						|
				default:
 | 
						|
					return va("INVALID CHALLENGE FOR PERCENT \"%d\"", cn->requirement);
 | 
						|
			}
 | 
						|
 | 
						|
			if (checkavailable == true)
 | 
						|
			{
 | 
						|
				for (i = 0; i < MAXUNLOCKABLES; ++i)
 | 
						|
				{
 | 
						|
					if (unlockables[i].type != cn->extrainfo1)
 | 
						|
						continue;
 | 
						|
					if (gamedata->unlocked[i] == false)
 | 
						|
						continue;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				if (i == MAXUNLOCKABLES)
 | 
						|
					work = "of ???";
 | 
						|
			}
 | 
						|
 | 
						|
			return va("CHALLENGES: get %u%% %s", cn->requirement, work);
 | 
						|
		}
 | 
						|
 | 
						|
		case UC_ADDON:
 | 
						|
			if (!M_SecretUnlocked(SECRET_ADDONS, true))
 | 
						|
				return NULL;
 | 
						|
			return "load a custom addon";
 | 
						|
		case UC_CREDITS:
 | 
						|
			return "watch the developer credits all the way from start to finish";
 | 
						|
		case UC_REPLAY:
 | 
						|
			return "save a replay after finishing a round";
 | 
						|
		case UC_CRASH:
 | 
						|
			if (gamedata->evercrashed)
 | 
						|
				return "re-launch the game after a crash";
 | 
						|
			return NULL;
 | 
						|
		case UC_TUTORIALSKIP:
 | 
						|
			return "successfully skip the Tutorial";
 | 
						|
		case UC_PASSWORD:
 | 
						|
			return "enter a secret password";
 | 
						|
 | 
						|
		case UC_SPRAYCAN:
 | 
						|
		{
 | 
						|
			if (cn->requirement <= 0
 | 
						|
			|| cn->requirement >= numskincolors)
 | 
						|
				return va("INVALID SPRAYCAN COLOR \"%d\"", cn->requirement);
 | 
						|
 | 
						|
			UINT16 can_id = skincolors[cn->requirement].cache_spraycan;
 | 
						|
 | 
						|
			if (can_id >= gamedata->numspraycans)
 | 
						|
				return va("INVALID SPRAYCAN ID \"%d:%u\"",
 | 
						|
					cn->requirement,
 | 
						|
					skincolors[cn->requirement].cache_spraycan
 | 
						|
				);
 | 
						|
 | 
						|
			if (can_id == 0)
 | 
						|
				return "grab a Spray Can"; // Special case for the head of the list
 | 
						|
 | 
						|
			if (gamedata->spraycans[0].map >= nummapheaders)
 | 
						|
				return NULL; // Don't tease that there are many until you have one
 | 
						|
 | 
						|
			return va("grab %d Spray Cans", can_id + 1);
 | 
						|
		}
 | 
						|
 | 
						|
		case UC_PRISONEGGCD:
 | 
						|
			// :butterfly: "alternatively you could say 'grab a hot toooon' or 'smooth beeat'"
 | 
						|
			return "GRAND PRIX: grab a certain prize from a random Prison Egg";
 | 
						|
 | 
						|
		case UC_AND:
 | 
						|
			return "&";
 | 
						|
		case UC_THEN:
 | 
						|
			return "then";
 | 
						|
		case UC_COMMA:
 | 
						|
			return ",";
 | 
						|
		case UC_DESCRIPTIONOVERRIDE:
 | 
						|
			return cn->stringvar;
 | 
						|
 | 
						|
		case UCRP_PREFIX_BONUSROUND:
 | 
						|
			//return "BONUS ROUND:"; -- our final testers bounced off this, just fallthru to GRAND PRIX instead
 | 
						|
		case UCRP_PREFIX_GRANDPRIX:
 | 
						|
			return "GRAND PRIX:";
 | 
						|
		case UCRP_PREFIX_TIMEATTACK:
 | 
						|
			if (!M_SecretUnlocked(SECRET_TIMEATTACK, true))
 | 
						|
				return NULL;
 | 
						|
			return "TIME ATTACK:";
 | 
						|
		case UCRP_PREFIX_PRISONBREAK:
 | 
						|
			return "PRISON BREAK:";
 | 
						|
		case UCRP_PREFIX_SEALEDSTAR:
 | 
						|
			if (!gamedata->everseenspecial)
 | 
						|
				return NULL;
 | 
						|
			return "SEALED STARS:";
 | 
						|
 | 
						|
		case UCRP_PREFIX_ISMAP:
 | 
						|
			if (cn->requirement >= nummapheaders || !mapheaderinfo[cn->requirement])
 | 
						|
				return va("INVALID MAP CONDITION \"%d:%d\":", cn->type, cn->requirement);
 | 
						|
 | 
						|
			title = M_BuildConditionTitle(cn->requirement);
 | 
						|
			work = va("%s:", title);
 | 
						|
			Z_Free(title);
 | 
						|
			return work;
 | 
						|
		case UCRP_ISMAP:
 | 
						|
			if (cn->requirement >= nummapheaders || !mapheaderinfo[cn->requirement])
 | 
						|
				return va("INVALID MAP CONDITION \"%d:%d\"", cn->type, cn->requirement);
 | 
						|
 | 
						|
			title = M_BuildConditionTitle(cn->requirement);
 | 
						|
			work = va("on %s", title);
 | 
						|
			Z_Free(title);
 | 
						|
			return work;
 | 
						|
		case UCRP_ISCHARACTER:
 | 
						|
			if (cn->requirement < 0 || !skins[cn->requirement].realname[0])
 | 
						|
				return va("INVALID CHAR CONDITION \"%d:%d\"", cn->type, cn->requirement);
 | 
						|
			work = M_GetConditionCharacter(cn->requirement, true);
 | 
						|
			return va("as %s", work);
 | 
						|
		case UCRP_ISENGINECLASS:
 | 
						|
			return va("with engine class %c", 'A' + cn->requirement);
 | 
						|
		case UCRP_HASFOLLOWER:
 | 
						|
			if (cn->requirement < 0 || !followers[cn->requirement].name[0])
 | 
						|
				return va("INVALID FOLLOWER CONDITION \"%d:%d\"", cn->type, cn->requirement);
 | 
						|
			work = (K_FollowerUsable(cn->requirement))
 | 
						|
				? followers[cn->requirement].name
 | 
						|
				: "???";
 | 
						|
			return va("with %s in tow", work);
 | 
						|
		case UCRP_ISDIFFICULTY:
 | 
						|
		{
 | 
						|
			const char *speedtext = "";
 | 
						|
 | 
						|
			if (cn->requirement == KARTSPEED_NORMAL)
 | 
						|
			{
 | 
						|
				speedtext = "on Normal";
 | 
						|
			}
 | 
						|
			else if (cn->requirement == KARTSPEED_HARD)
 | 
						|
			{
 | 
						|
				speedtext = "on Hard";
 | 
						|
			}
 | 
						|
			else if (cn->requirement == KARTGP_MASTER)
 | 
						|
			{
 | 
						|
				if (M_SecretUnlocked(SECRET_MASTERMODE, true))
 | 
						|
					speedtext = "on Master";
 | 
						|
				else
 | 
						|
					speedtext = "on ???";
 | 
						|
			}
 | 
						|
 | 
						|
			return speedtext;
 | 
						|
		}
 | 
						|
		case UCRP_ISGEAR:
 | 
						|
			return va("in Gear %d", cn->requirement + 1);
 | 
						|
 | 
						|
		case UCRP_PODIUMCUP:
 | 
						|
		{
 | 
						|
			cupheader_t *cup;
 | 
						|
			const char *completetype = "complete", *orbetter = "";
 | 
						|
 | 
						|
			if (cn->extrainfo2)
 | 
						|
			{
 | 
						|
				switch (cn->extrainfo1)
 | 
						|
				{
 | 
						|
					case GRADE_E: { completetype = "get grade E"; break; }
 | 
						|
					case GRADE_D: { completetype = "get grade D"; break; }
 | 
						|
					case GRADE_C: { completetype = "get grade C"; break; }
 | 
						|
					case GRADE_B: { completetype = "get grade B"; break; }
 | 
						|
					case GRADE_A: { completetype = "get grade A"; break; }
 | 
						|
					case GRADE_S: { completetype = "get grade S"; break; }
 | 
						|
					default: { break; }
 | 
						|
				}
 | 
						|
 | 
						|
				if (cn->requirement < GRADE_S)
 | 
						|
					orbetter = " or better in";
 | 
						|
				else
 | 
						|
					orbetter = " in";
 | 
						|
			}
 | 
						|
			else if (cn->extrainfo1 == 0)
 | 
						|
				;
 | 
						|
			else if (cn->extrainfo1 == 1)
 | 
						|
				completetype = "get Gold in";
 | 
						|
			else
 | 
						|
			{
 | 
						|
				if (cn->extrainfo1 == 2)
 | 
						|
					completetype = "get Silver";
 | 
						|
				else if (cn->extrainfo1 == 3)
 | 
						|
					completetype = "get Bronze";
 | 
						|
				orbetter = " or better in";
 | 
						|
			}
 | 
						|
 | 
						|
			if (cn->requirement == -1)
 | 
						|
			{
 | 
						|
				return va("%s%s any Cup",
 | 
						|
					completetype, orbetter
 | 
						|
				);
 | 
						|
			}
 | 
						|
 | 
						|
			for (cup = kartcupheaders; cup; cup = cup->next)
 | 
						|
			{
 | 
						|
				if (cup->id != cn->requirement)
 | 
						|
					continue;
 | 
						|
				return va("%s%s %s CUP",
 | 
						|
					completetype, orbetter,
 | 
						|
					(M_CupLocked(cup) ? "???" : cup->realname)
 | 
						|
				);
 | 
						|
			}
 | 
						|
			return va("INVALID CUP CONDITION \"%d:%d\"", cn->type, cn->requirement);
 | 
						|
		}
 | 
						|
		case UCRP_PODIUMEMERALD:
 | 
						|
			if (!gamedata->everseenspecial)
 | 
						|
				return "???";
 | 
						|
			return "collect the Emerald";
 | 
						|
		case UCRP_PODIUMPRIZE:
 | 
						|
			if (!gamedata->everseenspecial)
 | 
						|
				return "???";
 | 
						|
			return "collect the prize";
 | 
						|
		case UCRP_PODIUMNOCONTINUES:
 | 
						|
			return "without using any continues";
 | 
						|
 | 
						|
		case UCRP_FINISHCOOL:
 | 
						|
			return "finish in good standing";
 | 
						|
		case UCRP_FINISHPERFECT:
 | 
						|
			return "finish a perfect round";
 | 
						|
		case UCRP_FINISHALLPRISONS:
 | 
						|
			return "break every Prison Egg";
 | 
						|
		case UCRP_SURVIVE:
 | 
						|
			return "survive";
 | 
						|
		case UCRP_NOCONTEST:
 | 
						|
			return "NO CONTEST";
 | 
						|
 | 
						|
		case UCRP_SMASHUFO:
 | 
						|
			if (!gamedata->everseenspecial)
 | 
						|
				return NULL;
 | 
						|
			return "smash the UFO Catcher";
 | 
						|
		case UCRP_CHASEDBYSPB:
 | 
						|
			return "while chased by a Self-Propelled Bomb";
 | 
						|
		case UCRP_MAPDESTROYOBJECTS:
 | 
						|
		{
 | 
						|
			if (cn->stringvar == NULL)
 | 
						|
				return va("INVALID DESTROY CONDITION \"%d\"", cn->type);
 | 
						|
 | 
						|
			title = M_BuildConditionTitle(cn->requirement);
 | 
						|
			work = va("%s: destroy all the %s", title, cn->stringvar);
 | 
						|
			Z_Free(title);
 | 
						|
			return work;
 | 
						|
		}
 | 
						|
 | 
						|
		case UCRP_MAKERETIRE:
 | 
						|
		{
 | 
						|
			if (cn->requirement < 0 || !skins[cn->requirement].realname[0])
 | 
						|
				return va("INVALID CHAR CONDITION \"%d:%d\"", cn->type, cn->requirement);
 | 
						|
 | 
						|
			work = M_GetConditionCharacter(cn->requirement, false);
 | 
						|
			return va("make %s retire", work);
 | 
						|
		}
 | 
						|
 | 
						|
		case UCRP_FINISHPLACE:
 | 
						|
		case UCRP_FINISHPLACEEXACT:
 | 
						|
			return va("finish in %d%s%s", cn->requirement, M_GetNthType(cn->requirement),
 | 
						|
				((cn->type == UCRP_FINISHPLACE && cn->requirement > 1)
 | 
						|
					? " or better" : ""));
 | 
						|
		case UCRP_FINISHGRADE:
 | 
						|
		{
 | 
						|
			char gradeletter = '?';
 | 
						|
			const char *orbetter = "";
 | 
						|
 | 
						|
			switch (cn->requirement)
 | 
						|
			{
 | 
						|
				case GRADE_E: { gradeletter = 'E'; break; }
 | 
						|
				case GRADE_D: { gradeletter = 'D'; break; }
 | 
						|
				case GRADE_C: { gradeletter = 'C'; break; }
 | 
						|
				case GRADE_B: { gradeletter = 'B'; break; }
 | 
						|
				case GRADE_A: { gradeletter = 'A'; break; }
 | 
						|
				default: { break; }
 | 
						|
			}
 | 
						|
 | 
						|
			if (cn->requirement < GRADE_A)
 | 
						|
				orbetter = " or better";
 | 
						|
 | 
						|
			return va("get grade %c%s",
 | 
						|
				gradeletter, orbetter
 | 
						|
			);
 | 
						|
		}
 | 
						|
		case UCRP_FINISHTIME:
 | 
						|
			return va("finish in %i:%02i.%02i",
 | 
						|
				G_TicsToMinutes(cn->requirement, true),
 | 
						|
				G_TicsToSeconds(cn->requirement),
 | 
						|
				G_TicsToCentiseconds(cn->requirement));
 | 
						|
		case UCRP_FINISHTIMEEXACT:
 | 
						|
			return va("finish in exactly %i:%02i.XX",
 | 
						|
				G_TicsToMinutes(cn->requirement, true),
 | 
						|
				G_TicsToSeconds(cn->requirement));
 | 
						|
		case UCRP_FINISHTIMELEFT:
 | 
						|
			return va("finish with %i:%02i.%02i remaining",
 | 
						|
				G_TicsToMinutes(cn->requirement, true),
 | 
						|
				G_TicsToSeconds(cn->requirement),
 | 
						|
				G_TicsToCentiseconds(cn->requirement));
 | 
						|
 | 
						|
		case UCRP_RINGS:
 | 
						|
			if (cn->requirement != 20)
 | 
						|
				return va("with at least %d Rings", cn->requirement);
 | 
						|
			// FALLTHRU
 | 
						|
		case UCRP_RINGSEXACT:
 | 
						|
			return va("with exactly %d Rings", cn->requirement);
 | 
						|
 | 
						|
		case UCRP_SPEEDOMETER:
 | 
						|
			return va("reach %s%u%% on the speedometer",
 | 
						|
				(cn->requirement == 999)
 | 
						|
					? "" : "at least ",
 | 
						|
				cn->requirement
 | 
						|
			);
 | 
						|
		case UCRP_DRAFTDURATION:
 | 
						|
			return va("consistently tether off other racers for %u seconds", cn->requirement);
 | 
						|
		case UCRP_GROWCONSECUTIVEBEAMS:
 | 
						|
			return va("touch the blue beams from your own Shrink at least %u times before returning to normal size", cn->requirement);
 | 
						|
 | 
						|
		case UCRP_TRIGGER:
 | 
						|
			return "do something special";
 | 
						|
 | 
						|
		case UCRP_FALLOFF:
 | 
						|
			return (cn->requirement == 1) ? "fall off the course" : "don't fall off the course";
 | 
						|
		case UCRP_TOUCHOFFROAD:
 | 
						|
			return (cn->requirement == 1) ? "touch offroad" : "don't touch any offroad";
 | 
						|
		case UCRP_TOUCHSNEAKERPANEL:
 | 
						|
			return (cn->requirement == 1) ? "touch a Sneaker Panel" : "don't touch any Sneaker Panels";
 | 
						|
		case UCRP_RINGDEBT:
 | 
						|
			return (cn->requirement == 1) ? "go into Ring debt" : "don't go into Ring debt";
 | 
						|
		case UCRP_FAULTED:
 | 
						|
			return (cn->requirement == 1) ? "FAULT during POSITION" : "don't FAULT during POSITION";
 | 
						|
 | 
						|
		case UCRP_TRIPWIREHYUU:
 | 
						|
			return "go through Tripwire while afflicted by Hyudoro";
 | 
						|
		case UCRP_WHIPHYUU:
 | 
						|
			return "Insta-Whip a racer while afflicted by Hyudoro";
 | 
						|
		case UCRP_SPBNEUTER:
 | 
						|
			return "shock a Self-Propelled Bomb into submission";
 | 
						|
		case UCRP_LANDMINEDUNK:
 | 
						|
			return "dunk a Land Mine on another racer's head";
 | 
						|
		case UCRP_HITMIDAIR:
 | 
						|
			return "hit another racer with a projectile while you're both in the air";
 | 
						|
		case UCRP_HITDRAFTERLOOKBACK:
 | 
						|
			return "hit a racer tethering off you while looking back at them";
 | 
						|
		case UCRP_GIANTRACERSHRUNKENORBI:
 | 
						|
			return "hit a giant racer with a shrunken Orbinaut";
 | 
						|
		case UCRP_RETURNMARKTOSENDER:
 | 
						|
			return "when cursed with Eggmark, blow up the racer responsible";
 | 
						|
 | 
						|
		case UCRP_TRACKHAZARD:
 | 
						|
		{
 | 
						|
			work = (cn->requirement == 1) ? "touch a course hazard" : "don't touch any course hazards";
 | 
						|
			if (cn->extrainfo1 == -1)
 | 
						|
				return va("%s%s", work, (cn->requirement == 1) ? " on every lap" : "");
 | 
						|
			if (cn->extrainfo1 == -2)
 | 
						|
				return va("%s on the final lap", work);
 | 
						|
			if (cn->extrainfo1 == 0)
 | 
						|
				return va("%s during POSITION", work);
 | 
						|
			return va("%s on lap %u", work, cn->extrainfo1);
 | 
						|
		}
 | 
						|
 | 
						|
		case UCRP_TARGETATTACKMETHOD:
 | 
						|
		{
 | 
						|
			work = NULL;
 | 
						|
 | 
						|
			switch (cn->requirement)
 | 
						|
			{
 | 
						|
				// See targetdamaging_t
 | 
						|
				case UFOD_BOOST:
 | 
						|
					work = "boost power";
 | 
						|
					break;
 | 
						|
				case UFOD_WHIP:
 | 
						|
					work = "Insta-Whip";
 | 
						|
					break;
 | 
						|
				case UFOD_BANANA:
 | 
						|
					work = "Bananas";
 | 
						|
					break;
 | 
						|
				case UFOD_ORBINAUT:
 | 
						|
					work = "Orbinauts";
 | 
						|
					break;
 | 
						|
				case UFOD_JAWZ:
 | 
						|
					work = "Jawz";
 | 
						|
					break;
 | 
						|
				case UFOD_SPB:
 | 
						|
					work = "Self-Propelled Bombs";
 | 
						|
					break;
 | 
						|
				case UFOD_GACHABOM:
 | 
						|
					work = "Gachabom";
 | 
						|
					break;
 | 
						|
				default:
 | 
						|
					break;
 | 
						|
			}
 | 
						|
 | 
						|
			if (work == NULL)
 | 
						|
				return va("INVALID ATTACK CONDITION \"%d:%d\"", cn->type, cn->requirement);
 | 
						|
 | 
						|
			return va("using only %s", work);
 | 
						|
		}
 | 
						|
 | 
						|
		case UCRP_GACHABOMMISER:
 | 
						|
			return "using exactly one Gachabom repeatedly";
 | 
						|
 | 
						|
		case UCRP_WETPLAYER:
 | 
						|
			return va("without %s %s",
 | 
						|
				(cn->requirement & MFE_TOUCHWATER) ? "touching any" : "going into",
 | 
						|
				(cn->stringvar) ? cn->stringvar : "water");
 | 
						|
 | 
						|
		default:
 | 
						|
			break;
 | 
						|
	}
 | 
						|
	// UC_MAPTRIGGER and UC_CONDITIONSET are explicitly very hard to support proper descriptions for
 | 
						|
	return va("UNSUPPORTED CONDITION \"%d\"", cn->type);
 | 
						|
}
 | 
						|
 | 
						|
char *M_BuildConditionSetString(UINT16 unlockid)
 | 
						|
{
 | 
						|
	conditionset_t *c = NULL;
 | 
						|
	UINT32 lastID = 0;
 | 
						|
	condition_t *cn;
 | 
						|
	size_t len = 1024, worklen;
 | 
						|
	static char message[1024] = "";
 | 
						|
	const char *work = NULL;
 | 
						|
	size_t i;
 | 
						|
	UINT8 stopasap = 0;
 | 
						|
 | 
						|
	message[0] = '\0';
 | 
						|
 | 
						|
	if (unlockid >= MAXUNLOCKABLES)
 | 
						|
	{
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	if (!unlockables[unlockid].conditionset)
 | 
						|
	{
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	if (gamedata->unlocked[unlockid] == true && M_Achieved(unlockables[unlockid].conditionset - 1) == false)
 | 
						|
	{
 | 
						|
		message[0] = '\x86'; // the following text will be grey
 | 
						|
		message[1] = '\0';
 | 
						|
		len--;
 | 
						|
	}
 | 
						|
 | 
						|
	c = &conditionSets[unlockables[unlockid].conditionset-1];
 | 
						|
 | 
						|
	for (i = 0; i < c->numconditions; ++i)
 | 
						|
	{
 | 
						|
		cn = &c->condition[i];
 | 
						|
 | 
						|
		if (i > 0)
 | 
						|
		{
 | 
						|
			worklen = 0;
 | 
						|
			if (lastID != cn->id)
 | 
						|
			{
 | 
						|
				stopasap = 0;
 | 
						|
				worklen = 6;
 | 
						|
				strncat(message, " - OR ", len);
 | 
						|
			}
 | 
						|
			else if (stopasap == 0 && cn->type != UC_COMMA)
 | 
						|
			{
 | 
						|
				worklen = 1;
 | 
						|
				strncat(message, " ", len);
 | 
						|
			}
 | 
						|
			len -= worklen;
 | 
						|
		}
 | 
						|
 | 
						|
		lastID = cn->id;
 | 
						|
 | 
						|
		if (stopasap == 1)
 | 
						|
		{
 | 
						|
			// Secret challenge -- show unrelated condition IDs
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		work = M_GetConditionString(cn);
 | 
						|
		if (work == NULL)
 | 
						|
		{
 | 
						|
			stopasap = 1;
 | 
						|
			if (message[0] && message[1])
 | 
						|
				work = "???";
 | 
						|
			else
 | 
						|
				work = "(Find other secrets to learn about this...)";
 | 
						|
		}
 | 
						|
		else if (cn->type == UC_DESCRIPTIONOVERRIDE)
 | 
						|
		{
 | 
						|
			stopasap = 2;
 | 
						|
		}
 | 
						|
		worklen = strlen(work);
 | 
						|
 | 
						|
		strncat(message, work, len);
 | 
						|
		len -= worklen;
 | 
						|
 | 
						|
		if (stopasap == 2)
 | 
						|
		{
 | 
						|
			// Description override - hide all further ones
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (message[0] == '\0')
 | 
						|
	{
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	// Valid sentence capitalisation handling.
 | 
						|
	{
 | 
						|
		// Finds the first : character, indicating the end of the prefix.
 | 
						|
		for (i = 0; message[i]; i++)
 | 
						|
		{
 | 
						|
			if (message[i] != ':')
 | 
						|
				continue;
 | 
						|
			i++;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
 | 
						|
		// Okay, now make the first non-whitespace character after this a capital.
 | 
						|
		// Doesn't matter if !isalpha() - toupper is a no-op.
 | 
						|
		// (If the first loop hit the string's end, the message[i] check keeps us safe)
 | 
						|
		for (; message[i]; i++)
 | 
						|
		{
 | 
						|
			if ((message[i] & 0x80) || isspace(message[i]))
 | 
						|
				continue;
 | 
						|
			message[i] = toupper(message[i]);
 | 
						|
			break;
 | 
						|
		}
 | 
						|
 | 
						|
		// Also do this for the prefix.
 | 
						|
		// This might seem redundant, but "the Controls Tutorial:" is a possible prefix!
 | 
						|
		for (i = 0; message[i]; i++)
 | 
						|
		{
 | 
						|
			if ((message[i] & 0x80) || isspace(message[i]))
 | 
						|
				continue;
 | 
						|
			message[i] = toupper(message[i]);
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (usedTourney && unlockables[unlockid].conditionset == CH_FURYBIKE && gamedata->unlocked[unlockid] == false)
 | 
						|
	{
 | 
						|
		strcpy(message, "Power shrouds this challenge in darkness... (Return here without Tournament Mode!)\0");
 | 
						|
	}
 | 
						|
 | 
						|
	// Finally, do a clean wordwrap!
 | 
						|
	return V_ScaledWordWrap(
 | 
						|
		DESCRIPTIONWIDTH << FRACBITS,
 | 
						|
		FRACUNIT, FRACUNIT, FRACUNIT,
 | 
						|
		0,
 | 
						|
		TINY_FONT,
 | 
						|
		message
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
static boolean M_CheckUnlockConditions(player_t *player)
 | 
						|
{
 | 
						|
	UINT32 i;
 | 
						|
	conditionset_t *c;
 | 
						|
	boolean ret;
 | 
						|
 | 
						|
	for (i = 0; i < MAXCONDITIONSETS; ++i)
 | 
						|
	{
 | 
						|
		c = &conditionSets[i];
 | 
						|
		if (!c->numconditions || gamedata->achieved[i])
 | 
						|
			continue;
 | 
						|
 | 
						|
		if ((gamedata->achieved[i] = (M_CheckConditionSet(c, player))) != true)
 | 
						|
			continue;
 | 
						|
 | 
						|
		ret = true;
 | 
						|
	}
 | 
						|
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud, boolean doall)
 | 
						|
{
 | 
						|
	UINT16 i = 0, response = 0, newkeys = 0;
 | 
						|
 | 
						|
	if (!gamedata)
 | 
						|
	{
 | 
						|
		// Don't attempt to write/check anything.
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	if (!loud)
 | 
						|
	{
 | 
						|
		// Just in case they aren't to sync
 | 
						|
		// Done first so that emblems are ready before check
 | 
						|
		M_CheckLevelEmblems();
 | 
						|
		M_CompletionEmblems();
 | 
						|
		doall = true;
 | 
						|
	}
 | 
						|
 | 
						|
	if (gamedata->deferredconditioncheck == true)
 | 
						|
	{
 | 
						|
		// Handle deferred all-condition checks
 | 
						|
		gamedata->deferredconditioncheck = false;
 | 
						|
		doall = true;
 | 
						|
	}
 | 
						|
 | 
						|
	if (doall)
 | 
						|
	{
 | 
						|
		response = M_CheckUnlockConditions(NULL);
 | 
						|
 | 
						|
		M_UpdateNextPrisonEggPickup();
 | 
						|
 | 
						|
		if (gamedata->pendingkeyrounds == 0
 | 
						|
			|| (gamedata->chaokeys >= GDMAX_CHAOKEYS))
 | 
						|
		{
 | 
						|
			gamedata->keyspending = 0;
 | 
						|
		}
 | 
						|
		else while ((gamedata->keyspending + gamedata->chaokeys) < GDMAX_CHAOKEYS
 | 
						|
			&& ((gamedata->pendingkeyrounds + gamedata->pendingkeyroundoffset)/GDCONVERT_ROUNDSTOKEY) > gamedata->keyspending)
 | 
						|
		{
 | 
						|
			gamedata->keyspending++;
 | 
						|
			newkeys++;
 | 
						|
			response |= true;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (!demo.playback && Playing() && (gamestate == GS_LEVEL || K_PodiumSequence() == true))
 | 
						|
	{
 | 
						|
		for (i = 0; i <= splitscreen; i++)
 | 
						|
		{
 | 
						|
			if (!playeringame[g_localplayers[i]])
 | 
						|
				continue;
 | 
						|
			if (players[g_localplayers[i]].spectator)
 | 
						|
				continue;
 | 
						|
			if (!doall && players[g_localplayers[i]].roundconditions.checkthisframe == false)
 | 
						|
				continue;
 | 
						|
			response |= M_CheckUnlockConditions(&players[g_localplayers[i]]);
 | 
						|
			players[g_localplayers[i]].roundconditions.checkthisframe = false;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (loud && response == 0)
 | 
						|
	{
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	response = 0;
 | 
						|
 | 
						|
	// Go through unlockables
 | 
						|
	for (i = 0; i < MAXUNLOCKABLES; ++i)
 | 
						|
	{
 | 
						|
		if (gamedata->unlocked[i] || !unlockables[i].conditionset)
 | 
						|
		{
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		if (gamedata->unlocked[i] == true
 | 
						|
			|| gamedata->unlockpending[i] == true)
 | 
						|
		{
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		if (M_Achieved(unlockables[i].conditionset - 1) == false)
 | 
						|
		{
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		gamedata->unlockpending[i] = true;
 | 
						|
		response++;
 | 
						|
	}
 | 
						|
 | 
						|
	// Announce
 | 
						|
	if (response != 0)
 | 
						|
	{
 | 
						|
		if (loud)
 | 
						|
		{
 | 
						|
			S_StartSound(NULL, sfx_achiev);
 | 
						|
		}
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	if (newkeys != 0)
 | 
						|
	{
 | 
						|
		if (loud)
 | 
						|
		{
 | 
						|
			S_StartSound(NULL, sfx_keygen);
 | 
						|
		}
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
UINT16 M_GetNextAchievedUnlock(boolean canskipchaokeys)
 | 
						|
{
 | 
						|
	UINT16 i;
 | 
						|
 | 
						|
	// Go through unlockables
 | 
						|
	for (i = 0; i < MAXUNLOCKABLES; ++i)
 | 
						|
	{
 | 
						|
		if (!unlockables[i].conditionset)
 | 
						|
		{
 | 
						|
			// Not worthy of consideration
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		if (gamedata->unlocked[i] == true)
 | 
						|
		{
 | 
						|
			// Already unlocked, no need to engage
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		if (gamedata->unlockpending[i] == false)
 | 
						|
		{
 | 
						|
			// Not unlocked AND not pending, which means chao keys can be used on something
 | 
						|
			canskipchaokeys = false;
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		return i;
 | 
						|
	}
 | 
						|
 | 
						|
	if (canskipchaokeys == true)
 | 
						|
	{
 | 
						|
		// Okay, we're skipping chao keys - let's just insta-digest them.
 | 
						|
 | 
						|
		if (gamedata->chaokeys + gamedata->keyspending < GDMAX_CHAOKEYS)
 | 
						|
		{
 | 
						|
			gamedata->chaokeys += gamedata->keyspending;
 | 
						|
			gamedata->pendingkeyroundoffset =
 | 
						|
				(gamedata->pendingkeyroundoffset + gamedata->pendingkeyrounds)
 | 
						|
				% GDCONVERT_ROUNDSTOKEY;
 | 
						|
 | 
						|
		}
 | 
						|
		else
 | 
						|
		{
 | 
						|
			gamedata->chaokeys = GDMAX_CHAOKEYS;
 | 
						|
			gamedata->pendingkeyroundoffset = 0;
 | 
						|
		}
 | 
						|
 | 
						|
		gamedata->keyspending = 0;
 | 
						|
		gamedata->pendingkeyrounds = 0;
 | 
						|
	}
 | 
						|
	else if (gamedata->keyspending != 0)
 | 
						|
	{
 | 
						|
		return PENDING_CHAOKEYS;
 | 
						|
	}
 | 
						|
 | 
						|
	return MAXUNLOCKABLES;
 | 
						|
}
 | 
						|
 | 
						|
// Emblem unlocking shit
 | 
						|
UINT16 M_CheckLevelEmblems(void)
 | 
						|
{
 | 
						|
	INT32 i;
 | 
						|
	INT32 valToReach;
 | 
						|
	INT16 tag;
 | 
						|
	INT16 levelnum;
 | 
						|
	boolean res;
 | 
						|
	UINT16 somethingUnlocked = 0;
 | 
						|
 | 
						|
	// Update Score, Time, Rings emblems
 | 
						|
	for (i = 0; i < numemblems; ++i)
 | 
						|
	{
 | 
						|
		INT32 checkLevel;
 | 
						|
 | 
						|
		if (emblemlocations[i].type < ET_TIME || gamedata->collected[i])
 | 
						|
			continue;
 | 
						|
 | 
						|
		checkLevel = M_EmblemMapNum(&emblemlocations[i]);
 | 
						|
 | 
						|
		if (checkLevel >= nummapheaders || !mapheaderinfo[checkLevel])
 | 
						|
			continue;
 | 
						|
 | 
						|
		levelnum = checkLevel;
 | 
						|
		valToReach = emblemlocations[i].var;
 | 
						|
		tag = emblemlocations[i].tag;
 | 
						|
 | 
						|
		switch (emblemlocations[i].type)
 | 
						|
		{
 | 
						|
			case ET_TIME: // Requires time on map <= x
 | 
						|
				if (tag > 0)
 | 
						|
				{
 | 
						|
					if (tag > mapheaderinfo[checkLevel]->ghostCount
 | 
						|
					|| mapheaderinfo[checkLevel]->ghostBrief[tag-1] == NULL)
 | 
						|
						continue;
 | 
						|
 | 
						|
					res = (G_GetBestTime(levelnum) <= mapheaderinfo[checkLevel]->ghostBrief[tag-1]->time);
 | 
						|
				}
 | 
						|
				else if (tag < 0 && tag > AUTOMEDAL_MAX)
 | 
						|
				{
 | 
						|
					// Use auto medal times for emblem tags, see AUTOMEDAL_ in m_cond.h
 | 
						|
					int index = -tag - 1; // 0 is Platinum, 3 is Bronze
 | 
						|
					tic_t time = mapheaderinfo[checkLevel]->automedaltime[index];
 | 
						|
 | 
						|
					res = (G_GetBestTime(levelnum) <= time);
 | 
						|
				}
 | 
						|
				else
 | 
						|
				{
 | 
						|
					res = (G_GetBestTime(levelnum) <= (unsigned)valToReach);
 | 
						|
				}
 | 
						|
				break;
 | 
						|
			default: // unreachable but shuts the compiler up.
 | 
						|
				continue;
 | 
						|
		}
 | 
						|
 | 
						|
		gamedata->collected[i] = res;
 | 
						|
		if (res)
 | 
						|
			++somethingUnlocked;
 | 
						|
	}
 | 
						|
	return somethingUnlocked;
 | 
						|
}
 | 
						|
 | 
						|
UINT16 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separate print when awarding emblems and it's sorta different enough.
 | 
						|
{
 | 
						|
	INT32 i;
 | 
						|
	INT32 embtype;
 | 
						|
	INT16 levelnum;
 | 
						|
	boolean res;
 | 
						|
	UINT16 somethingUnlocked = 0;
 | 
						|
	UINT8 flags;
 | 
						|
 | 
						|
	for (i = 0; i < numemblems; ++i)
 | 
						|
	{
 | 
						|
		INT32 checkLevel;
 | 
						|
 | 
						|
		if (emblemlocations[i].type != ET_MAP || gamedata->collected[i])
 | 
						|
			continue;
 | 
						|
 | 
						|
		checkLevel = M_EmblemMapNum(&emblemlocations[i]);
 | 
						|
 | 
						|
		if (checkLevel >= nummapheaders || !mapheaderinfo[checkLevel])
 | 
						|
			continue;
 | 
						|
 | 
						|
		levelnum = checkLevel;
 | 
						|
		embtype = emblemlocations[i].flags;
 | 
						|
		flags = MV_BEATEN;
 | 
						|
 | 
						|
		if (embtype & ME_ENCORE)
 | 
						|
			flags |= MV_ENCORE;
 | 
						|
 | 
						|
		if (embtype & ME_SPBATTACK)
 | 
						|
			flags |= MV_SPBATTACK;
 | 
						|
 | 
						|
		res = ((mapheaderinfo[levelnum]->records.mapvisited & flags) == flags);
 | 
						|
 | 
						|
		gamedata->collected[i] = res;
 | 
						|
		if (res)
 | 
						|
			++somethingUnlocked;
 | 
						|
	}
 | 
						|
 | 
						|
	return somethingUnlocked;
 | 
						|
}
 | 
						|
 | 
						|
// -------------------
 | 
						|
// Quick unlock checks
 | 
						|
// -------------------
 | 
						|
 | 
						|
boolean M_GameTrulyStarted(void)
 | 
						|
{
 | 
						|
	// Fail safe
 | 
						|
	if (gamedata == NULL)
 | 
						|
		return false;
 | 
						|
 | 
						|
	// Not set
 | 
						|
	if (gamestartchallenge >= MAXUNLOCKABLES)
 | 
						|
		return true;
 | 
						|
 | 
						|
	// An unfortunate sidestep, but sync is important.
 | 
						|
	if (netgame)
 | 
						|
		return true;
 | 
						|
 | 
						|
	// Okay, we can check to see if this challenge has been achieved.
 | 
						|
	/*return (
 | 
						|
		gamedata->unlockpending[gamestartchallenge]
 | 
						|
		|| gamedata->unlocked[gamestartchallenge]
 | 
						|
	);*/
 | 
						|
	// Actually, on second thought, let's let the Goner Setup play one last time
 | 
						|
	// The above is used in M_StartControlPanel instead
 | 
						|
	return (gamedata->gonerlevel == GDGONER_DONE);
 | 
						|
}
 | 
						|
 | 
						|
boolean M_CheckNetUnlockByID(UINT16 unlockid)
 | 
						|
{
 | 
						|
	if (unlockid >= MAXUNLOCKABLES
 | 
						|
		|| !unlockables[unlockid].conditionset)
 | 
						|
	{
 | 
						|
		return true; // default permit
 | 
						|
	}
 | 
						|
 | 
						|
	if (netgame || demo.playback)
 | 
						|
	{
 | 
						|
		return netUnlocked[unlockid];
 | 
						|
	}
 | 
						|
 | 
						|
	return gamedata->unlocked[unlockid];
 | 
						|
}
 | 
						|
 | 
						|
boolean M_SecretUnlocked(INT32 type, boolean local)
 | 
						|
{
 | 
						|
	INT32 i;
 | 
						|
 | 
						|
#if 0
 | 
						|
	(void)type;
 | 
						|
	(void)i;
 | 
						|
	return false; // for quick testing
 | 
						|
#else
 | 
						|
 | 
						|
	for (i = 0; i < MAXUNLOCKABLES; ++i)
 | 
						|
	{
 | 
						|
		if (unlockables[i].type != type)
 | 
						|
			continue;
 | 
						|
		if ((local && gamedata->unlocked[i])
 | 
						|
			|| (!local && M_CheckNetUnlockByID(i)))
 | 
						|
			continue;
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	return true;
 | 
						|
 | 
						|
#endif //if 0
 | 
						|
}
 | 
						|
 | 
						|
boolean M_CupLocked(cupheader_t *cup)
 | 
						|
{
 | 
						|
	// No skipping over any part of your marathon.
 | 
						|
	if (marathonmode)
 | 
						|
		return false;
 | 
						|
 | 
						|
	if (!cup)
 | 
						|
		return false;
 | 
						|
 | 
						|
#if 0 // perfect uncached behaviour
 | 
						|
	UINT16 i;
 | 
						|
 | 
						|
	for (i = 0; i < MAXUNLOCKABLES; ++i)
 | 
						|
	{
 | 
						|
		if (unlockables[i].type != SECRET_CUP)
 | 
						|
			continue;
 | 
						|
		if (M_UnlockableCup(&unlockables[i]) != cup)
 | 
						|
			continue;
 | 
						|
		return !M_CheckNetUnlockByID(i);
 | 
						|
	}
 | 
						|
#else
 | 
						|
	if (cup->cache_cuplock < MAXUNLOCKABLES)
 | 
						|
		return !M_CheckNetUnlockByID(cup->cache_cuplock);
 | 
						|
#endif
 | 
						|
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
boolean M_CupSecondRowLocked(void)
 | 
						|
{
 | 
						|
	// The following was pre-optimised for cached behaviour.
 | 
						|
	// It would need a refactor if the cache system were to
 | 
						|
	// change, maybe to iterate over unlockable_t instead.
 | 
						|
	cupheader_t *cup;
 | 
						|
	for (cup = kartcupheaders; cup; cup = cup->next)
 | 
						|
	{
 | 
						|
		// Only important for the second row.
 | 
						|
		if ((cup->id % (CUPMENU_COLUMNS * CUPMENU_ROWS)) < CUPMENU_COLUMNS)
 | 
						|
			continue;
 | 
						|
 | 
						|
		// Only important for ones that can be locked.
 | 
						|
		if (cup->cache_cuplock == MAXUNLOCKABLES)
 | 
						|
			continue;
 | 
						|
 | 
						|
		// If it's NOT unlocked, can't be used as proof of unlock.
 | 
						|
		if (!M_CheckNetUnlockByID(cup->cache_cuplock))
 | 
						|
			continue;
 | 
						|
 | 
						|
		// Okay, at least one cup on the second row is unlocked!
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
boolean M_MapLocked(UINT16 mapnum)
 | 
						|
{
 | 
						|
	// No skipping over any part of your marathon.
 | 
						|
	if (marathonmode)
 | 
						|
		return false;
 | 
						|
 | 
						|
	if (mapnum == 0 || mapnum > nummapheaders)
 | 
						|
		return false;
 | 
						|
 | 
						|
	if (!mapheaderinfo[mapnum-1])
 | 
						|
		return false;
 | 
						|
 | 
						|
	if (mapheaderinfo[mapnum-1]->cup)
 | 
						|
	{
 | 
						|
		return M_CupLocked(mapheaderinfo[mapnum-1]->cup);
 | 
						|
	}
 | 
						|
 | 
						|
#if 0 // perfect uncached behaviour
 | 
						|
	UINT16 i;
 | 
						|
 | 
						|
	for (i = 0; i < MAXUNLOCKABLES; ++i)
 | 
						|
	{
 | 
						|
		if (unlockables[i].type != SECRET_MAP)
 | 
						|
			continue;
 | 
						|
		if (M_UnlockableMapNum(&unlockables[i]) != mapnum-1)
 | 
						|
			continue;
 | 
						|
		return !M_CheckNetUnlockByID(i);
 | 
						|
	}
 | 
						|
#else
 | 
						|
	if (mapheaderinfo[mapnum-1]->cache_maplock < MAXUNLOCKABLES)
 | 
						|
		return !M_CheckNetUnlockByID(mapheaderinfo[mapnum-1]->cache_maplock);
 | 
						|
#endif
 | 
						|
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
INT32 M_CountMedals(boolean all, boolean extraonly)
 | 
						|
{
 | 
						|
	INT32 found = 0, i;
 | 
						|
	if (!extraonly)
 | 
						|
	{
 | 
						|
		for (i = 0; i < numemblems; ++i)
 | 
						|
		{
 | 
						|
			// Not init in SOC
 | 
						|
			if (emblemlocations[i].type == ET_NONE)
 | 
						|
				continue;
 | 
						|
 | 
						|
			// Not explicitly a medal
 | 
						|
			if ((emblemlocations[i].type == ET_GLOBAL)
 | 
						|
				&& (emblemlocations[i].flags & GE_NOTMEDAL))
 | 
						|
				continue;
 | 
						|
 | 
						|
			// Not getting the counter, and not collected
 | 
						|
			if (!all && !gamedata->collected[i])
 | 
						|
				continue;
 | 
						|
 | 
						|
			// Don't count Platinums in the overall count, so you can get 101% going for them
 | 
						|
			if (all
 | 
						|
				&& (emblemlocations[i].type == ET_TIME)
 | 
						|
				&& (emblemlocations[i].tag == AUTOMEDAL_PLATINUM))
 | 
						|
				continue;
 | 
						|
 | 
						|
			// Relevant, add to da counter
 | 
						|
			found++;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Above but for extramedals
 | 
						|
	for (i = 0; i < MAXUNLOCKABLES; ++i)
 | 
						|
	{
 | 
						|
		if (unlockables[i].type != SECRET_EXTRAMEDAL)
 | 
						|
			continue;
 | 
						|
		if (!all && !gamedata->unlocked[i])
 | 
						|
			continue;
 | 
						|
		found++;
 | 
						|
	}
 | 
						|
 | 
						|
	return found;
 | 
						|
}
 | 
						|
 | 
						|
// --------------------------------------
 | 
						|
// Quick functions for calculating things
 | 
						|
// --------------------------------------
 | 
						|
 | 
						|
// Theoretically faster than using M_CountMedals()
 | 
						|
// Stops when it reaches the target number of medals.
 | 
						|
boolean M_GotEnoughMedals(INT32 number)
 | 
						|
{
 | 
						|
	INT32 i, gottenmedals = 0;
 | 
						|
	for (i = 0; i < numemblems; ++i)
 | 
						|
	{
 | 
						|
		// Not init in SOC
 | 
						|
		if (emblemlocations[i].type == ET_NONE)
 | 
						|
			continue;
 | 
						|
 | 
						|
		// Not explicitly a medal
 | 
						|
		if ((emblemlocations[i].type == ET_GLOBAL)
 | 
						|
			&& (emblemlocations[i].flags & GE_NOTMEDAL))
 | 
						|
			continue;
 | 
						|
 | 
						|
		// Not collected
 | 
						|
		if (!gamedata->collected[i])
 | 
						|
			continue;
 | 
						|
 | 
						|
		// Add to counter. Hit our threshold?
 | 
						|
		if (++gottenmedals < number)
 | 
						|
			continue;
 | 
						|
 | 
						|
		// We did!
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	// Above but for extramedals
 | 
						|
	for (i = 0; i < MAXUNLOCKABLES; ++i)
 | 
						|
	{
 | 
						|
		if (unlockables[i].type != SECRET_EXTRAMEDAL)
 | 
						|
			continue;
 | 
						|
		if (!gamedata->unlocked[i])
 | 
						|
			continue;
 | 
						|
		if (++gottenmedals < number)
 | 
						|
			continue;
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	// Didn't hit our counter!
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
boolean M_GotLowEnoughTime(INT32 tictime)
 | 
						|
{
 | 
						|
	INT32 curtics = 0;
 | 
						|
	INT32 i;
 | 
						|
 | 
						|
	for (i = 0; i < nummapheaders; ++i)
 | 
						|
	{
 | 
						|
		if (!mapheaderinfo[i] || (mapheaderinfo[i]->menuflags & LF2_NOTIMEATTACK))
 | 
						|
			continue;
 | 
						|
 | 
						|
		if (!mapheaderinfo[i]->records.timeattack.time)
 | 
						|
			return false;
 | 
						|
		if ((curtics += mapheaderinfo[i]->records.timeattack.time) > tictime)
 | 
						|
			return false;
 | 
						|
	}
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
// Gets the skin number for a SECRET_SKIN unlockable.
 | 
						|
INT32 M_UnlockableSkinNum(unlockable_t *unlock)
 | 
						|
{
 | 
						|
	if (unlock->type != SECRET_SKIN)
 | 
						|
	{
 | 
						|
		// This isn't a skin unlockable...
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	if (unlock->stringVar && unlock->stringVar[0])
 | 
						|
	{
 | 
						|
		INT32 skinnum;
 | 
						|
 | 
						|
		if (unlock->stringVarCache != -1)
 | 
						|
		{
 | 
						|
			return unlock->stringVarCache;
 | 
						|
		}
 | 
						|
 | 
						|
		// Get the skin from the string.
 | 
						|
		skinnum = R_SkinAvailableEx(unlock->stringVar, false);
 | 
						|
		if (skinnum != -1)
 | 
						|
		{
 | 
						|
			unlock->stringVarCache = skinnum;
 | 
						|
			return skinnum;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (unlock->variable >= 0 && unlock->variable < numskins)
 | 
						|
	{
 | 
						|
		// Use the number directly.
 | 
						|
		return unlock->variable;
 | 
						|
	}
 | 
						|
 | 
						|
	// Invalid skin unlockable.
 | 
						|
	return -1;
 | 
						|
}
 | 
						|
 | 
						|
// Gets the skin number for a SECRET_FOLLOWER unlockable.
 | 
						|
INT32 M_UnlockableFollowerNum(unlockable_t *unlock)
 | 
						|
{
 | 
						|
	if (unlock->type != SECRET_FOLLOWER)
 | 
						|
	{
 | 
						|
		// This isn't a follower unlockable...
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	if (unlock->stringVar && unlock->stringVar[0])
 | 
						|
	{
 | 
						|
		INT32 skinnum;
 | 
						|
		size_t i;
 | 
						|
		char testname[SKINNAMESIZE+1];
 | 
						|
 | 
						|
		if (unlock->stringVarCache != -1)
 | 
						|
		{
 | 
						|
			return unlock->stringVarCache;
 | 
						|
		}
 | 
						|
 | 
						|
		// match deh_soc readfollower()
 | 
						|
		for (i = 0; unlock->stringVar[i]; i++)
 | 
						|
		{
 | 
						|
			testname[i] = unlock->stringVar[i];
 | 
						|
			if (unlock->stringVar[i] == '_')
 | 
						|
				testname[i] = ' ';
 | 
						|
		}
 | 
						|
		testname[i] = '\0';
 | 
						|
 | 
						|
		// Get the skin from the string.
 | 
						|
		skinnum = K_FollowerAvailable(testname);
 | 
						|
		if (skinnum != -1)
 | 
						|
		{
 | 
						|
			unlock->stringVarCache = skinnum;
 | 
						|
			return skinnum;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (unlock->variable >= 0 && unlock->variable < numfollowers)
 | 
						|
	{
 | 
						|
		// Use the number directly.
 | 
						|
		return unlock->variable;
 | 
						|
	}
 | 
						|
 | 
						|
	// Invalid follower unlockable.
 | 
						|
	return -1;
 | 
						|
}
 | 
						|
 | 
						|
INT32 M_UnlockableColorNum(unlockable_t *unlock)
 | 
						|
{
 | 
						|
	if (unlock->type != SECRET_COLOR)
 | 
						|
	{
 | 
						|
		// This isn't a color unlockable...
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	if (unlock->stringVar && unlock->stringVar[0])
 | 
						|
	{
 | 
						|
		skincolornum_t colornum = SKINCOLOR_NONE;
 | 
						|
 | 
						|
		if (unlock->stringVarCache != -1)
 | 
						|
		{
 | 
						|
			return unlock->stringVarCache;
 | 
						|
		}
 | 
						|
 | 
						|
		// Get the skin from the string.
 | 
						|
		colornum = R_GetColorByName(unlock->stringVar);
 | 
						|
		if (colornum != SKINCOLOR_NONE)
 | 
						|
		{
 | 
						|
			unlock->stringVarCache = colornum;
 | 
						|
			return colornum;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (unlock->variable > SKINCOLOR_NONE && unlock->variable < numskincolors)
 | 
						|
	{
 | 
						|
		// Use the number directly.
 | 
						|
		return unlock->variable;
 | 
						|
	}
 | 
						|
 | 
						|
	// Invalid color unlockable.
 | 
						|
	return -1;
 | 
						|
}
 | 
						|
 | 
						|
cupheader_t *M_UnlockableCup(unlockable_t *unlock)
 | 
						|
{
 | 
						|
	cupheader_t *cup = kartcupheaders;
 | 
						|
	INT16 val = unlock->variable-1;
 | 
						|
 | 
						|
	if (unlock->type != SECRET_CUP)
 | 
						|
	{
 | 
						|
		// This isn't a cup unlockable...
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	if (unlock->stringVar && unlock->stringVar[0])
 | 
						|
	{
 | 
						|
		if (unlock->stringVarCache == -1)
 | 
						|
		{
 | 
						|
			// Get the cup from the string.
 | 
						|
			UINT32 hash = quickncasehash(unlock->stringVar, MAXCUPNAME);
 | 
						|
			while (cup)
 | 
						|
			{
 | 
						|
				if (hash == cup->namehash && !strcmp(cup->name, unlock->stringVar))
 | 
						|
					break;
 | 
						|
				cup = cup->next;
 | 
						|
			}
 | 
						|
 | 
						|
			if (cup)
 | 
						|
			{
 | 
						|
				unlock->stringVarCache = cup->id;
 | 
						|
			}
 | 
						|
			return cup;
 | 
						|
		}
 | 
						|
 | 
						|
		val = unlock->stringVarCache;
 | 
						|
	}
 | 
						|
	else if (val == -1)
 | 
						|
	{
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	// Use the number directly.
 | 
						|
	while (cup)
 | 
						|
	{
 | 
						|
		if (cup->id == val)
 | 
						|
			break;
 | 
						|
		cup = cup->next;
 | 
						|
	}
 | 
						|
 | 
						|
	return cup;
 | 
						|
}
 | 
						|
 | 
						|
UINT16 M_UnlockableMapNum(unlockable_t *unlock)
 | 
						|
{
 | 
						|
	if (unlock->type != SECRET_MAP && unlock->type != SECRET_ALTMUSIC)
 | 
						|
	{
 | 
						|
		// This isn't a map unlockable...
 | 
						|
		return NEXTMAP_INVALID;
 | 
						|
	}
 | 
						|
 | 
						|
	if (unlock->stringVar && unlock->stringVar[0])
 | 
						|
	{
 | 
						|
		if (unlock->stringVarCache == -1)
 | 
						|
		{
 | 
						|
			INT32 result = G_MapNumber(unlock->stringVar);
 | 
						|
 | 
						|
			if (result >= nummapheaders)
 | 
						|
				return result;
 | 
						|
 | 
						|
			unlock->stringVarCache = result;
 | 
						|
		}
 | 
						|
 | 
						|
		return unlock->stringVarCache;
 | 
						|
	}
 | 
						|
 | 
						|
	return NEXTMAP_INVALID;
 | 
						|
}
 | 
						|
 | 
						|
// ----------------
 | 
						|
// Misc Emblem shit
 | 
						|
// ----------------
 | 
						|
 | 
						|
UINT16 M_EmblemMapNum(emblem_t *emblem)
 | 
						|
{
 | 
						|
	if (emblem->levelCache == NEXTMAP_INVALID)
 | 
						|
	{
 | 
						|
		UINT16 result = G_MapNumber(emblem->level);
 | 
						|
 | 
						|
		if (result >= nummapheaders)
 | 
						|
			return result;
 | 
						|
 | 
						|
		emblem->levelCache = result;
 | 
						|
	}
 | 
						|
 | 
						|
	return emblem->levelCache;
 | 
						|
}
 | 
						|
 | 
						|
// Returns pointer to an emblem if an emblem exists for that level.
 | 
						|
// Pass -1 mapnum to continue from last emblem.
 | 
						|
// NULL if not found.
 | 
						|
// note that this goes in reverse!!
 | 
						|
emblem_t *M_GetLevelEmblems(INT32 mapnum)
 | 
						|
{
 | 
						|
	static INT32 map = -1;
 | 
						|
	static INT32 i = -1;
 | 
						|
 | 
						|
	if (mapnum > 0)
 | 
						|
	{
 | 
						|
		map = mapnum-1;
 | 
						|
		i = numemblems;
 | 
						|
	}
 | 
						|
 | 
						|
	while (--i >= 0)
 | 
						|
	{
 | 
						|
		if (emblemlocations[i].type == ET_NONE)
 | 
						|
			continue;
 | 
						|
 | 
						|
		INT32 checkLevel = M_EmblemMapNum(&emblemlocations[i]);
 | 
						|
 | 
						|
		if (checkLevel >= nummapheaders || !mapheaderinfo[checkLevel])
 | 
						|
			continue;
 | 
						|
 | 
						|
		if (checkLevel != map)
 | 
						|
			continue;
 | 
						|
 | 
						|
		return &emblemlocations[i];
 | 
						|
	}
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
skincolornum_t M_GetEmblemColor(emblem_t *em)
 | 
						|
{
 | 
						|
	if (!em || !em->color || em->color >= numskincolors)
 | 
						|
		return SKINCOLOR_GOLD;
 | 
						|
	return em->color;
 | 
						|
}
 | 
						|
 | 
						|
const char *M_GetEmblemPatch(emblem_t *em, boolean big)
 | 
						|
{
 | 
						|
	static char pnamebuf[7];
 | 
						|
 | 
						|
	if (!big)
 | 
						|
		strcpy(pnamebuf, "GOTITn");
 | 
						|
	else
 | 
						|
		strcpy(pnamebuf, "EMBMn0");
 | 
						|
 | 
						|
	I_Assert(em->sprite >= 'A' && em->sprite <= 'Z');
 | 
						|
 | 
						|
	if (!big)
 | 
						|
		pnamebuf[5] = em->sprite;
 | 
						|
	else
 | 
						|
		pnamebuf[4] = em->sprite;
 | 
						|
 | 
						|
	return pnamebuf;
 | 
						|
}
 | 
						|
 | 
						|
boolean M_UseAlternateTitleScreen(void)
 | 
						|
{
 | 
						|
	extern consvar_t cv_alttitle;
 | 
						|
	return cv_alttitle.value && M_SecretUnlocked(SECRET_ALTTITLE, true);
 | 
						|
}
 | 
						|
 | 
						|
INT32 M_GameDataGameType(INT32 lgametype, boolean lbattleprisons)
 | 
						|
{
 | 
						|
	INT32 playtimemode = GDGT_CUSTOM;
 | 
						|
	if (lgametype == GT_RACE)
 | 
						|
		playtimemode = GDGT_RACE;
 | 
						|
	else if (lgametype == GT_BATTLE)
 | 
						|
		playtimemode = lbattleprisons ? GDGT_PRISONS : GDGT_BATTLE;
 | 
						|
	else if (lgametype == GT_SPECIAL || lgametype == GT_VERSUS)
 | 
						|
		playtimemode = GDGT_SPECIAL;
 | 
						|
 | 
						|
	return playtimemode;
 | 
						|
}
 | 
						|
 |