mirror of
				https://github.com/KartKrewDev/RingRacers.git
				synced 2025-10-30 08:01:28 +00:00 
			
		
		
		
	- Repairs access to TEST RUN cup - We want to make it an unlockable later, so... - M_MapLocked data types corrected
		
			
				
	
	
		
			2183 lines
		
	
	
	
		
			52 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			2183 lines
		
	
	
	
		
			52 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// SONIC ROBO BLAST 2
 | 
						|
//-----------------------------------------------------------------------------
 | 
						|
// Copyright (C) 2022-2023 by Vivian "toastergrl" Grannell.
 | 
						|
// Copyright (C) 2012-2016 by Matthew "Kaito Sinclaire" Walsh.
 | 
						|
// Copyright (C) 2012-2020 by Sonic Team Junior.
 | 
						|
//
 | 
						|
// This program is free software distributed under the
 | 
						|
// terms of the GNU General Public License, version 2.
 | 
						|
// See the 'LICENSE' file for more details.
 | 
						|
//-----------------------------------------------------------------------------
 | 
						|
/// \file  m_cond.c
 | 
						|
/// \brief Unlockable condition system for SRB2 version 2.1
 | 
						|
 | 
						|
#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];
 | 
						|
 | 
						|
// Number of emblems
 | 
						|
INT32 numemblems = 0;
 | 
						|
 | 
						|
// 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;
 | 
						|
	UINT8 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.
 | 
						|
		UINT8 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(UINT8)),
 | 
						|
		PU_STATIC, NULL);
 | 
						|
 | 
						|
	if (!gamedata->challengegrid)
 | 
						|
	{
 | 
						|
		I_Error("M_PopulateChallengeGrid: was not able to allocate grid");
 | 
						|
	}
 | 
						|
 | 
						|
	memset(gamedata->challengegrid,
 | 
						|
		MAXUNLOCKABLES,
 | 
						|
		(gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT * sizeof(UINT8)));
 | 
						|
 | 
						|
	// 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[numspots][2];
 | 
						|
 | 
						|
		// Prepare the easy-grab spots.
 | 
						|
		for (i = 0; i < numspots; i++)
 | 
						|
		{
 | 
						|
			quickcheck[i][0] = i%(CHALLENGEGRIDHEIGHT-1);
 | 
						|
			quickcheck[i][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][0];
 | 
						|
			col =  quickcheck[j][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][0]) - (row)) <= 1 // Row distance
 | 
						|
					&& (abs((quickcheck[i][1]) - (col)) <= 1 // Column distance
 | 
						|
					|| (quickcheck[i][1] == 0 && col == gamedata->challengegridwidth-1) // Wraparounds l->r
 | 
						|
					|| (quickcheck[i][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][0] = quickcheck[numspots][0];
 | 
						|
					quickcheck[i][1] = quickcheck[numspots][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)
 | 
						|
		{
 | 
						|
			UINT8 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_UpdateChallengeGridExtraData(challengegridextradata_t *extradata)
 | 
						|
{
 | 
						|
	UINT8 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;
 | 
						|
			extradata[id].flags = CHE_NONE;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// 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)
 | 
						|
					{
 | 
						|
						continue;
 | 
						|
					}
 | 
						|
				}
 | 
						|
				else if (work < MAXUNLOCKABLES && gamedata->unlocked[work])
 | 
						|
				{
 | 
						|
					extradata[id].flags = CHE_HINT;
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			// 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;
 | 
						|
						}
 | 
						|
						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 && gamedata->unlocked[work])
 | 
						|
				{
 | 
						|
					extradata[id].flags = CHE_HINT;
 | 
						|
					continue;
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			// Since we're not modifying id past this point, the conditions become much simpler.
 | 
						|
			if ((extradata[id].flags & (CHE_HINT|CHE_DONTDRAW)) == CHE_HINT)
 | 
						|
			{
 | 
						|
				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 && gamedata->unlocked[work])
 | 
						|
				{
 | 
						|
					extradata[id].flags = CHE_HINT;
 | 
						|
					continue;
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			// 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 && gamedata->unlocked[work])
 | 
						|
				{
 | 
						|
					extradata[id].flags = CHE_HINT;
 | 
						|
					continue;
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void M_AddRawCondition(UINT8 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(UINT8 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)
 | 
						|
{
 | 
						|
	UINT8 i;
 | 
						|
	gamedata->totalplaytime = 0;
 | 
						|
	gamedata->totalrings = 0;
 | 
						|
	for (i = 0; i < GDGT_MAX; ++i)
 | 
						|
		gamedata->roundsplayed[i] = 0;
 | 
						|
	gamedata->timesBeaten = 0;
 | 
						|
 | 
						|
	gamedata->everloadedaddon = false;
 | 
						|
	gamedata->eversavedreplay = false;
 | 
						|
	gamedata->everseenspecial = false;
 | 
						|
	gamedata->crashflags = 0;
 | 
						|
}
 | 
						|
 | 
						|
void M_ClearSecrets(void)
 | 
						|
{
 | 
						|
	INT32 i;
 | 
						|
 | 
						|
	for (i = 0; i < nummapheaders; ++i)
 | 
						|
	{
 | 
						|
		mapheaderinfo[i]->mapvisited = 0;
 | 
						|
	}
 | 
						|
 | 
						|
	for (i = 0; i < MAXEMBLEMS; ++i)
 | 
						|
		gamedata->collected[i] = false;
 | 
						|
	for (i = 0; i < MAXUNLOCKABLES; ++i)
 | 
						|
		gamedata->unlocked[i] = gamedata->unlockpending[i] = netUnlocked[i] = false;
 | 
						|
	for (i = 0; i < MAXCONDITIONSETS; ++i)
 | 
						|
		gamedata->achieved[i] = false;
 | 
						|
 | 
						|
	Z_Free(gamedata->challengegrid);
 | 
						|
	gamedata->challengegrid = NULL;
 | 
						|
	gamedata->challengegridwidth = 0;
 | 
						|
 | 
						|
	gamedata->pendingkeyrounds = 0;
 | 
						|
	gamedata->pendingkeyroundoffset = 0;
 | 
						|
	gamedata->keyspending = 0;
 | 
						|
	gamedata->chaokeys = 3; // Start with 3 !!
 | 
						|
	gamedata->usedkeys = 0;
 | 
						|
}
 | 
						|
 | 
						|
// ----------------------
 | 
						|
// Condition set checking
 | 
						|
// ----------------------
 | 
						|
 | 
						|
void M_UpdateConditionSetsPending(void)
 | 
						|
{
 | 
						|
	UINT32 i, j;
 | 
						|
	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 UCRP_ISCHARACTER:
 | 
						|
				{
 | 
						|
					cn->requirement = R_SkinAvailable(cn->stringvar);
 | 
						|
 | 
						|
					if (cn->requirement < 0)
 | 
						|
					{
 | 
						|
						CONS_Alert(CONS_WARNING, "UCRP_ISCHARACTER: Invalid character %s for condition ID %d", cn->stringvar, cn->id+1);
 | 
						|
						return;
 | 
						|
					}
 | 
						|
 | 
						|
					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;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static boolean M_NotFreePlay(player_t *player)
 | 
						|
{
 | 
						|
	UINT8 i;
 | 
						|
 | 
						|
	for (i = 0; i < MAXPLAYERS; i++)
 | 
						|
	{
 | 
						|
		if (playeringame[i] == false || players[i].spectator == true)
 | 
						|
		{
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		if (player == &players[i])
 | 
						|
		{
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
// See also M_GetConditionString
 | 
						|
boolean M_CheckCondition(condition_t *cn, player_t *player)
 | 
						|
{
 | 
						|
	switch (cn->type)
 | 
						|
	{
 | 
						|
		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_POWERLEVEL: // Requires power level >= x on a certain gametype
 | 
						|
		{
 | 
						|
			UINT8 i;
 | 
						|
 | 
						|
			if (gamestate == GS_LEVEL)
 | 
						|
				return false; // this one could be laggy with many profiles available
 | 
						|
 | 
						|
			for (i = PROFILE_GUEST; i < PR_GetNumProfiles(); i++)
 | 
						|
			{
 | 
						|
				profile_t *p = PR_GetProfile(i);
 | 
						|
 | 
						|
				if (p->powerlevels[cn->extrainfo1] >= (unsigned)cn->requirement)
 | 
						|
				{
 | 
						|
					return true;
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
		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
 | 
						|
		{
 | 
						|
			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;
 | 
						|
 | 
						|
			return ((cn->requirement < nummapheaders)
 | 
						|
				&& (mapheaderinfo[cn->requirement])
 | 
						|
				&& ((mapheaderinfo[cn->requirement]->mapvisited & mvtype) == mvtype));
 | 
						|
		}
 | 
						|
		case UC_MAPTIME: // Requires time on map <= x
 | 
						|
			return (G_GetBestTime(cn->extrainfo1) <= (unsigned)cn->requirement);
 | 
						|
 | 
						|
		case UC_ALLCHAOS:
 | 
						|
		case UC_ALLSUPER:
 | 
						|
		case UC_ALLEMERALDS:
 | 
						|
		{
 | 
						|
			cupheader_t *cup;
 | 
						|
			UINT16 ret = 0;
 | 
						|
			UINT8 i;
 | 
						|
 | 
						|
			if (gamestate == GS_LEVEL)
 | 
						|
				return false; // this one could be laggy with many cups available
 | 
						|
 | 
						|
			for (cup = kartcupheaders; cup; cup = cup->next)
 | 
						|
			{
 | 
						|
				if (cup->emeraldnum == 0)
 | 
						|
					continue;
 | 
						|
 | 
						|
				i = cn->requirement;
 | 
						|
				for (i = cn->requirement; i < KARTGP_MAX; i++)
 | 
						|
				{
 | 
						|
					if (cup->windata[i].got_emerald == true)
 | 
						|
						break;
 | 
						|
				}
 | 
						|
 | 
						|
				if (i == KARTGP_MAX)
 | 
						|
					continue;
 | 
						|
 | 
						|
				ret |= 1<<(cup->emeraldnum-1);
 | 
						|
			}
 | 
						|
 | 
						|
			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_ADDON:
 | 
						|
			return (
 | 
						|
#ifndef DEVELOP
 | 
						|
				M_SecretUnlocked(SECRET_ADDONS, true) &&
 | 
						|
#endif
 | 
						|
				(gamedata->everloadedaddon == true));
 | 
						|
		case UC_REPLAY:
 | 
						|
			return (gamedata->eversavedreplay == true);
 | 
						|
		case UC_CRASH:
 | 
						|
			if (gamedata->crashflags & (GDCRASH_LAST|GDCRASH_ANY))
 | 
						|
			{
 | 
						|
				gamedata->crashflags |= GDCRASH_LOSERCLUB;
 | 
						|
				return true;
 | 
						|
			}
 | 
						|
			return false;
 | 
						|
 | 
						|
		// Just for string building
 | 
						|
		case UC_AND:
 | 
						|
		case UC_COMMA:
 | 
						|
			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->skin == cn->requirement);
 | 
						|
		case UCRP_ISENGINECLASS:
 | 
						|
			return (player->skin < numskins
 | 
						|
				&& R_GetEngineClass(
 | 
						|
					skins[player->skin].kartspeed,
 | 
						|
					skins[player->skin].kartweight,
 | 
						|
					skins[player->skin].flags
 | 
						|
				) == (unsigned)cn->requirement);
 | 
						|
		case UCRP_ISDIFFICULTY:
 | 
						|
			if (grandprixinfo.gp == false)
 | 
						|
				return (gamespeed >= cn->requirement);
 | 
						|
			if (cn->requirement == KARTGP_MASTER)
 | 
						|
				return (grandprixinfo.masterbots == true);
 | 
						|
			return (grandprixinfo.gamespeed >= cn->requirement);
 | 
						|
 | 
						|
		case UCRP_PODIUMCUP:
 | 
						|
			if (K_PodiumRanking() == false)
 | 
						|
				return false;
 | 
						|
			if (grandprixinfo.cup == NULL
 | 
						|
				|| grandprixinfo.cup->id != cn->requirement)
 | 
						|
				return false;
 | 
						|
			if (cn->extrainfo2)
 | 
						|
				return (K_PodiumGrade() >= (unsigned)cn->requirement);
 | 
						|
			if (cn->extrainfo1 != 0)
 | 
						|
				return (player->position != 0
 | 
						|
					&& player->position <= cn->extrainfo1);
 | 
						|
			return true;
 | 
						|
		case UCRP_PODIUMEMERALD:
 | 
						|
		case UCRP_PODIUMPRIZE:
 | 
						|
			return (K_PodiumRanking() == true
 | 
						|
				&& grandprixinfo.rank.specialWon == true);
 | 
						|
 | 
						|
		case UCRP_FINISHCOOL:
 | 
						|
			return (player->exiting
 | 
						|
				&& !(player->pflags & PF_NOCONTEST)
 | 
						|
				&& M_NotFreePlay(player)
 | 
						|
				&& !K_IsPlayerLosing(player));
 | 
						|
		case UCRP_FINISHALLCAPSULES:
 | 
						|
			return (battleprisons
 | 
						|
				&& !(player->pflags & PF_NOCONTEST)
 | 
						|
				//&& M_NotFreePlay(player)
 | 
						|
				&& numtargets >= maptargets);
 | 
						|
		case UCRP_NOCONTEST:
 | 
						|
			return (player->pflags & PF_NOCONTEST);
 | 
						|
		case UCRP_FINISHPLACE:
 | 
						|
			return (player->exiting
 | 
						|
				&& !(player->pflags & PF_NOCONTEST)
 | 
						|
				&& M_NotFreePlay(player)
 | 
						|
				&& player->position != 0
 | 
						|
				&& player->position <= cn->requirement);
 | 
						|
		case UCRP_FINISHPLACEEXACT:
 | 
						|
			return (player->exiting 
 | 
						|
				&& !(player->pflags & PF_NOCONTEST)
 | 
						|
				&& M_NotFreePlay(player)
 | 
						|
				&& player->position == cn->requirement);
 | 
						|
		case UCRP_FINISHTIME:
 | 
						|
			return (player->exiting
 | 
						|
				&& !(player->pflags & PF_NOCONTEST)
 | 
						|
				//&& M_NotFreePlay(player)
 | 
						|
				&& player->realtime <= (unsigned)cn->requirement);
 | 
						|
		case UCRP_FINISHTIMEEXACT:
 | 
						|
			return (player->exiting
 | 
						|
				&& !(player->pflags & PF_NOCONTEST)
 | 
						|
				//&& M_NotFreePlay(player)
 | 
						|
				&& player->realtime == (unsigned)cn->requirement);
 | 
						|
		case UCRP_FINISHTIMELEFT:
 | 
						|
			return (timelimitintics
 | 
						|
				&& player->exiting
 | 
						|
				&& !(player->pflags & PF_NOCONTEST)
 | 
						|
				&& !K_CanChangeRules(false) // too easy to change cv_timelimit
 | 
						|
				&& player->realtime < timelimitintics
 | 
						|
				&& (timelimitintics + extratimeintics + secretextratime - player->realtime) >= (unsigned)cn->requirement);
 | 
						|
 | 
						|
		case UCRP_TRIGGER: // requires map trigger set
 | 
						|
			return !!(player->roundconditions.unlocktriggers & (1 << cn->requirement));
 | 
						|
 | 
						|
		case UCRP_FALLOFF:
 | 
						|
			return (player->roundconditions.fell_off == (cn->requirement == 1));
 | 
						|
		case UCRP_TOUCHOFFROAD:
 | 
						|
			return (player->roundconditions.touched_offroad == (cn->requirement == 1));
 | 
						|
		case UCRP_TOUCHSNEAKERPANEL:
 | 
						|
			return (player->roundconditions.touched_sneakerpanel == (cn->requirement == 1));
 | 
						|
		case UCRP_RINGDEBT:
 | 
						|
			return (!(gametyperules & GTR_SPHERES) && (player->roundconditions.debt_rings == (cn->requirement == 1)));
 | 
						|
 | 
						|
		case UCRP_TRIPWIREHYUU:
 | 
						|
			return (player->roundconditions.tripwire_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_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_COMMA)
 | 
						|
			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_FINISHNEEDED)
 | 
						|
	// the following is intentionally not MV_BEATEN, just in case the title is for "Finish a round on X"
 | 
						|
	&& !(mapheaderinfo[map]->mapvisited & MV_VISITED))
 | 
						|
	|| M_MapLocked(map+1))
 | 
						|
		return Z_StrDup("???");
 | 
						|
 | 
						|
	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_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.
 | 
						|
 | 
						|
#define BUILDCONDITIONTITLE(i) (M_BuildConditionTitle(i))
 | 
						|
 | 
						|
	switch (cn->type)
 | 
						|
	{
 | 
						|
		case UC_PLAYTIME: // Requires total playing time >= x
 | 
						|
 | 
						|
			return va("play 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 = " Capsule";
 | 
						|
					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("play %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,%u,%u Rings", (cn->requirement/1000000), (cn->requirement/1000)%1000, (cn->requirement%1000));
 | 
						|
			if (cn->requirement >= 1000)
 | 
						|
				return va("collect %u,%u Rings", (cn->requirement/1000), (cn->requirement%1000));
 | 
						|
			return va("collect %u Rings", cn->requirement);
 | 
						|
 | 
						|
		case UC_POWERLEVEL: // Requires power level >= x on a certain gametype
 | 
						|
			return va("get a PWR of %d in %s", cn->requirement,
 | 
						|
				(cn->extrainfo1 == PWRLV_RACE)
 | 
						|
				? "Race"
 | 
						|
				: "Battle");
 | 
						|
 | 
						|
		case UC_GAMECLEAR: // Requires game beaten >= x times
 | 
						|
			if (cn->requirement > 1)
 | 
						|
				return va("beat 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
 | 
						|
		{
 | 
						|
			const char *prefix = "";
 | 
						|
 | 
						|
			if (cn->requirement >= nummapheaders || !mapheaderinfo[cn->requirement])
 | 
						|
				return va("INVALID MAP CONDITION \"%d:%d\"", cn->type, cn->requirement);
 | 
						|
 | 
						|
			title = 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";
 | 
						|
 | 
						|
			work = va("%s%s %s%s",
 | 
						|
				prefix,
 | 
						|
				work,
 | 
						|
				title,
 | 
						|
				(cn->type == UC_MAPENCORE) ? " in Encore Mode" : "");
 | 
						|
			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 = 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_ALLCHAOS:
 | 
						|
		case UC_ALLSUPER:
 | 
						|
		case UC_ALLEMERALDS:
 | 
						|
		{
 | 
						|
			const char *chaostext, *speedtext = "", *orbetter = "";
 | 
						|
 | 
						|
			if (!gamedata->everseenspecial)
 | 
						|
				return NULL;
 | 
						|
 | 
						|
			if (cn->type == UC_ALLCHAOS)
 | 
						|
				chaostext = "7 Chaos";
 | 
						|
			else if (cn->type == UC_ALLSUPER)
 | 
						|
				chaostext = "7 Super";
 | 
						|
			else
 | 
						|
				chaostext = "14";
 | 
						|
 | 
						|
			if (cn->requirement == KARTSPEED_NORMAL)
 | 
						|
			{
 | 
						|
				speedtext = " on Normal difficulty";
 | 
						|
				//if (M_SecretUnlocked(SECRET_HARDSPEED, true))
 | 
						|
					orbetter = " or better";
 | 
						|
			}
 | 
						|
			else if (cn->requirement == KARTSPEED_HARD)
 | 
						|
			{
 | 
						|
				speedtext = " on Hard difficulty";
 | 
						|
				if (M_SecretUnlocked(SECRET_MASTERMODE, true))
 | 
						|
					orbetter = " or better";
 | 
						|
			}
 | 
						|
			else if (cn->requirement == KARTGP_MASTER)
 | 
						|
			{
 | 
						|
				if (M_SecretUnlocked(SECRET_MASTERMODE, true))
 | 
						|
					speedtext = " on Master difficulty";
 | 
						|
				else
 | 
						|
					speedtext = " on ???";
 | 
						|
			}
 | 
						|
 | 
						|
			return va("collect all %s Emeralds%s%s", chaostext, speedtext, orbetter);
 | 
						|
		}
 | 
						|
 | 
						|
		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])
 | 
						|
				return va("INVALID MEDAL MAP \"%d:%d\"", cn->requirement, checkLevel);
 | 
						|
 | 
						|
			title = 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_ADDON:
 | 
						|
			if (!M_SecretUnlocked(SECRET_ADDONS, true) && !gamedata->everloadedaddon)
 | 
						|
				return NULL;
 | 
						|
			return "load a custom addon into \"Dr. Robotnik's Ring Racers\"";
 | 
						|
		case UC_REPLAY:
 | 
						|
			return "save a replay after finishing a round";
 | 
						|
		case UC_CRASH:
 | 
						|
			if (gamedata->crashflags & (GDCRASH_LAST|GDCRASH_ANY))
 | 
						|
				return "launch \"Dr. Robotnik's Ring Racers\" again after a game crash";
 | 
						|
			return NULL;
 | 
						|
 | 
						|
		case UC_AND:
 | 
						|
			return "&";
 | 
						|
		case UC_COMMA:
 | 
						|
			return ",";
 | 
						|
 | 
						|
		case UCRP_PREFIX_GRANDPRIX:
 | 
						|
			return "GRAND PRIX:";
 | 
						|
		case UCRP_PREFIX_BONUSROUND:
 | 
						|
			return "BONUS ROUND:";
 | 
						|
		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 = 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 = 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);
 | 
						|
			return va("as %s", skins[cn->requirement].realname);
 | 
						|
		case UCRP_ISENGINECLASS:
 | 
						|
			return va("with engine class %c", 'A' + cn->requirement);
 | 
						|
		case UCRP_ISDIFFICULTY:
 | 
						|
		{
 | 
						|
			const char *speedtext = "", *orbetter = "";
 | 
						|
 | 
						|
			if (cn->requirement == KARTSPEED_NORMAL)
 | 
						|
			{
 | 
						|
				speedtext = "on Normal difficulty";
 | 
						|
				//if (M_SecretUnlocked(SECRET_HARDSPEED, true))
 | 
						|
					orbetter = " or better";
 | 
						|
			}
 | 
						|
			else if (cn->requirement == KARTSPEED_HARD)
 | 
						|
			{
 | 
						|
				speedtext = "on Hard difficulty";
 | 
						|
				if (M_SecretUnlocked(SECRET_MASTERMODE, true))
 | 
						|
					orbetter = " or better";
 | 
						|
			}
 | 
						|
			else if (cn->requirement == KARTGP_MASTER)
 | 
						|
			{
 | 
						|
				if (M_SecretUnlocked(SECRET_MASTERMODE, true))
 | 
						|
					speedtext = "on Master difficulty";
 | 
						|
				else
 | 
						|
					speedtext = "on ???";
 | 
						|
			}
 | 
						|
 | 
						|
			return va("%s%s", speedtext, orbetter);
 | 
						|
		}
 | 
						|
 | 
						|
		case UCRP_PODIUMCUP:
 | 
						|
		{
 | 
						|
			cupheader_t *cup;
 | 
						|
			const char *completetype = "complete", *orbetter = "";
 | 
						|
 | 
						|
			if (cn->extrainfo2)
 | 
						|
			{
 | 
						|
				switch (cn->requirement)
 | 
						|
				{
 | 
						|
					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";
 | 
						|
			}
 | 
						|
 | 
						|
			for (cup = kartcupheaders; cup; cup = cup->next)
 | 
						|
			{
 | 
						|
				if (cup->id != cn->requirement)
 | 
						|
					continue;
 | 
						|
				return va("%s%s %s CUP", completetype, orbetter, cup->name);
 | 
						|
			}
 | 
						|
			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_FINISHCOOL:
 | 
						|
			return "finish in good standing";
 | 
						|
		case UCRP_FINISHALLCAPSULES:
 | 
						|
			return "break every capsule";
 | 
						|
		case UCRP_NOCONTEST:
 | 
						|
			return "NO CONTEST";
 | 
						|
		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_FINISHTIME:
 | 
						|
		case UCRP_FINISHTIMEEXACT:
 | 
						|
			return va("finish in %s%i:%02i.%02i",
 | 
						|
				(cn->type == UCRP_FINISHTIMEEXACT ? "exactly " : ""),
 | 
						|
				G_TicsToMinutes(cn->requirement, true),
 | 
						|
				G_TicsToSeconds(cn->requirement),
 | 
						|
				G_TicsToCentiseconds(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_TRIGGER:
 | 
						|
			return cn->stringvar;
 | 
						|
 | 
						|
		case UCRP_FALLOFF:
 | 
						|
			return (cn->requirement == 1) ? "fall off the course" : "without falling off";
 | 
						|
		case UCRP_TOUCHOFFROAD:
 | 
						|
			return (cn->requirement == 1) ? "touch offroad" : "without touching any offroad";
 | 
						|
		case UCRP_TOUCHSNEAKERPANEL:
 | 
						|
			return (cn->requirement == 1) ? "touch a Sneaker Panel" : "without touching any Sneaker Panels";
 | 
						|
		case UCRP_RINGDEBT:
 | 
						|
			return (cn->requirement == 1) ? "go into Ring debt" : "without going into Ring debt";
 | 
						|
 | 
						|
		case UCRP_TRIPWIREHYUU:
 | 
						|
			return "go through Tripwire after getting snared by Hyudoro";
 | 
						|
		case UCRP_SPBNEUTER:
 | 
						|
			return "shock a Self Propelled Bomb into submission";
 | 
						|
		case UCRP_LANDMINEDUNK:
 | 
						|
			return "dunk a Landmine on another racer's head";
 | 
						|
		case UCRP_HITMIDAIR:
 | 
						|
			return "hit another racer with a projectile while you're both in the air";
 | 
						|
 | 
						|
		case UCRP_WETPLAYER:
 | 
						|
			return va("without %s %s",
 | 
						|
				(cn->requirement & MFE_TOUCHWATER) ? "touching any" : "going into",
 | 
						|
				cn->stringvar);
 | 
						|
 | 
						|
		default:
 | 
						|
			break;
 | 
						|
	}
 | 
						|
	// UC_MAPTRIGGER and UC_CONDITIONSET are explicitly very hard to support proper descriptions for
 | 
						|
	return va("UNSUPPORTED CONDITION \"%d\"", cn->type);
 | 
						|
 | 
						|
#undef BUILDCONDITIONTITLE
 | 
						|
}
 | 
						|
 | 
						|
char *M_BuildConditionSetString(UINT8 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 max = 0, maxatstart = 0, start = 0, i;
 | 
						|
	boolean stopasap = false;
 | 
						|
 | 
						|
	message[0] = '\0';
 | 
						|
 | 
						|
	if (unlockid >= MAXUNLOCKABLES)
 | 
						|
	{
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	if (!unlockables[unlockid].conditionset)
 | 
						|
	{
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	c = &conditionSets[unlockables[unlockid].conditionset-1];
 | 
						|
 | 
						|
	for (i = 0; i < c->numconditions; ++i)
 | 
						|
	{
 | 
						|
		cn = &c->condition[i];
 | 
						|
 | 
						|
		if (i > 0 && (cn->type != UC_COMMA))
 | 
						|
		{
 | 
						|
			if (lastID != cn->id)
 | 
						|
			{
 | 
						|
				worklen = 4;
 | 
						|
				strncat(message, "\nOR ", len);
 | 
						|
			}
 | 
						|
			else
 | 
						|
			{
 | 
						|
				worklen = 1;
 | 
						|
				strncat(message, " ", len);
 | 
						|
			}
 | 
						|
			len -= worklen;
 | 
						|
		}
 | 
						|
 | 
						|
		lastID = cn->id;
 | 
						|
 | 
						|
		work = M_GetConditionString(cn);
 | 
						|
		if (work == NULL)
 | 
						|
		{
 | 
						|
			stopasap = true;
 | 
						|
			work = "???";
 | 
						|
		}
 | 
						|
		worklen = strlen(work);
 | 
						|
 | 
						|
		strncat(message, work, len);
 | 
						|
		len -= worklen;
 | 
						|
 | 
						|
		if (stopasap)
 | 
						|
		{
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Rudementary word wrapping.
 | 
						|
	// Simple and effective. Does not handle nonuniform letter sizes, etc. but who cares.
 | 
						|
	for (i = 0; message[i]; i++)
 | 
						|
	{
 | 
						|
		if (message[i] == ' ')
 | 
						|
		{
 | 
						|
			start = i;
 | 
						|
			max += 4;
 | 
						|
			maxatstart = max;
 | 
						|
		}
 | 
						|
		else if (message[i] == '\n')
 | 
						|
		{
 | 
						|
			start = 0;
 | 
						|
			max = 0;
 | 
						|
			maxatstart = 0;
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
		else if (message[i] & 0x80)
 | 
						|
			continue;
 | 
						|
		else
 | 
						|
			max += 8;
 | 
						|
 | 
						|
		// Start trying to wrap if presumed length exceeds the space we have on-screen.
 | 
						|
		if (max >= DESCRIPTIONWIDTH && start > 0)
 | 
						|
		{
 | 
						|
			message[start] = '\n';
 | 
						|
			max -= maxatstart;
 | 
						|
			start = 0;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	for (i = 0; message[i]; i++)
 | 
						|
	{
 | 
						|
		if (message[i] == toupper(message[i]))
 | 
						|
			continue;
 | 
						|
		message[i] = toupper(message[i]);
 | 
						|
		break;
 | 
						|
	}
 | 
						|
 | 
						|
	return message;
 | 
						|
}
 | 
						|
 | 
						|
static boolean M_CheckUnlockConditions(player_t *player)
 | 
						|
{
 | 
						|
	INT32 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);
 | 
						|
 | 
						|
		while ((gamedata->keyspending + gamedata->chaokeys + gamedata->usedkeys) < GDMAX_CHAOKEYS
 | 
						|
			&& ((gamedata->pendingkeyrounds + gamedata->pendingkeyroundoffset)/GDCONVERT_ROUNDSTOKEY) > gamedata->keyspending)
 | 
						|
		{
 | 
						|
			gamedata->keyspending++;
 | 
						|
			newkeys++;
 | 
						|
			response |= true;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (!demo.playback && Playing() && (gamestate == GS_LEVEL || K_PodiumRanking() == 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++;
 | 
						|
	}
 | 
						|
 | 
						|
	response += newkeys;
 | 
						|
 | 
						|
	// Announce
 | 
						|
	if (response != 0)
 | 
						|
	{
 | 
						|
		if (loud)
 | 
						|
		{
 | 
						|
			S_StartSound(NULL, sfx_achiev);
 | 
						|
		}
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
UINT16 M_GetNextAchievedUnlock(void)
 | 
						|
{
 | 
						|
	UINT8 i;
 | 
						|
 | 
						|
	// Go through unlockables
 | 
						|
	for (i = 0; i < MAXUNLOCKABLES; ++i)
 | 
						|
	{
 | 
						|
		if (gamedata->unlocked[i] || !unlockables[i].conditionset)
 | 
						|
		{
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		if (gamedata->unlocked[i] == true)
 | 
						|
		{
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		if (gamedata->unlockpending[i] == false)
 | 
						|
		{
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		return i;
 | 
						|
	}
 | 
						|
 | 
						|
	if (gamedata->keyspending > 0)
 | 
						|
	{
 | 
						|
		return PENDING_CHAOKEYS;
 | 
						|
	}
 | 
						|
 | 
						|
	return MAXUNLOCKABLES;
 | 
						|
}
 | 
						|
 | 
						|
// Emblem unlocking shit
 | 
						|
UINT8 M_CheckLevelEmblems(void)
 | 
						|
{
 | 
						|
	INT32 i;
 | 
						|
	INT32 valToReach;
 | 
						|
	INT16 tag;
 | 
						|
	INT16 levelnum;
 | 
						|
	UINT8 res;
 | 
						|
	UINT8 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
 | 
						|
				{
 | 
						|
					res = (G_GetBestTime(levelnum) <= (unsigned)valToReach);
 | 
						|
				}
 | 
						|
				break;
 | 
						|
			default: // unreachable but shuts the compiler up.
 | 
						|
				continue;
 | 
						|
		}
 | 
						|
 | 
						|
		gamedata->collected[i] = res;
 | 
						|
		if (res)
 | 
						|
			++somethingUnlocked;
 | 
						|
	}
 | 
						|
	return somethingUnlocked;
 | 
						|
}
 | 
						|
 | 
						|
UINT8 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;
 | 
						|
	UINT8 res;
 | 
						|
	UINT8 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]->mapvisited & flags) == flags);
 | 
						|
 | 
						|
		gamedata->collected[i] = res;
 | 
						|
		if (res)
 | 
						|
			++somethingUnlocked;
 | 
						|
	}
 | 
						|
 | 
						|
	return somethingUnlocked;
 | 
						|
}
 | 
						|
 | 
						|
// -------------------
 | 
						|
// Quick unlock checks
 | 
						|
// -------------------
 | 
						|
 | 
						|
boolean M_CheckNetUnlockByID(UINT8 unlockid)
 | 
						|
{
 | 
						|
	if (unlockid >= MAXUNLOCKABLES
 | 
						|
		|| !unlockables[unlockid].conditionset)
 | 
						|
	{
 | 
						|
		return true; // default permit
 | 
						|
	}
 | 
						|
 | 
						|
	if (netgame)
 | 
						|
	{
 | 
						|
		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)
 | 
						|
{
 | 
						|
	UINT8 i;
 | 
						|
 | 
						|
	// Don't lock maps in dedicated servers.
 | 
						|
	// That just makes hosts' lives hell.
 | 
						|
	if (dedicated)
 | 
						|
		return false;
 | 
						|
 | 
						|
	// No skipping over any part of your marathon.
 | 
						|
	if (marathonmode)
 | 
						|
		return false;
 | 
						|
 | 
						|
	if (!cup)
 | 
						|
		return false;
 | 
						|
 | 
						|
	for (i = 0; i < MAXUNLOCKABLES; ++i)
 | 
						|
	{
 | 
						|
		if (unlockables[i].type != SECRET_CUP)
 | 
						|
			continue;
 | 
						|
		if (M_UnlockableCup(&unlockables[i]) != cup)
 | 
						|
			continue;
 | 
						|
		return !M_CheckNetUnlockByID(i);
 | 
						|
	}
 | 
						|
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
boolean M_MapLocked(UINT16 mapnum)
 | 
						|
{
 | 
						|
	UINT8 i;
 | 
						|
 | 
						|
	// Don't lock maps in dedicated servers.
 | 
						|
	// That just makes hosts' lives hell.
 | 
						|
	if (dedicated)
 | 
						|
		return false;
 | 
						|
 | 
						|
	// No skipping over any part of your marathon.
 | 
						|
	if (marathonmode)
 | 
						|
		return false;
 | 
						|
 | 
						|
	if (mapnum <= 1 || mapnum > nummapheaders)
 | 
						|
		return false;
 | 
						|
	
 | 
						|
	if (!mapheaderinfo[mapnum-1])
 | 
						|
		return false;
 | 
						|
 | 
						|
	if (mapheaderinfo[mapnum-1]->cup)
 | 
						|
	{
 | 
						|
		return M_CupLocked(mapheaderinfo[mapnum-1]->cup);
 | 
						|
	}
 | 
						|
 | 
						|
	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);
 | 
						|
	}
 | 
						|
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
INT32 M_CountMedals(boolean all, boolean extraonly)
 | 
						|
{
 | 
						|
	INT32 found = 0, i;
 | 
						|
	if (!extraonly)
 | 
						|
	{
 | 
						|
		for (i = 0; i < numemblems; ++i)
 | 
						|
		{
 | 
						|
			if ((emblemlocations[i].type == ET_GLOBAL)
 | 
						|
				&& (emblemlocations[i].flags & GE_NOTMEDAL))
 | 
						|
				continue;
 | 
						|
			if (!all && !gamedata->collected[i])
 | 
						|
				continue;
 | 
						|
			found++;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	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.
 | 
						|
UINT8 M_GotEnoughMedals(INT32 number)
 | 
						|
{
 | 
						|
	INT32 i, gottenmedals = 0;
 | 
						|
	for (i = 0; i < numemblems; ++i)
 | 
						|
	{
 | 
						|
		if (!gamedata->collected[i])
 | 
						|
			continue;
 | 
						|
		if (++gottenmedals < number)
 | 
						|
			continue;
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
	for (i = 0; i < MAXUNLOCKABLES; ++i)
 | 
						|
	{
 | 
						|
		if (unlockables[i].type != SECRET_EXTRAMEDAL)
 | 
						|
			continue;
 | 
						|
		if (!gamedata->unlocked[i])
 | 
						|
			continue;
 | 
						|
		if (++gottenmedals < number)
 | 
						|
			continue;
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
UINT8 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]->mainrecord || !mapheaderinfo[i]->mainrecord->time)
 | 
						|
			return false;
 | 
						|
		else if ((curtics += mapheaderinfo[i]->mainrecord->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_SkinAvailable(unlock->stringVar);
 | 
						|
		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;
 | 
						|
}
 | 
						|
 | 
						|
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.
 | 
						|
			while (cup)
 | 
						|
			{
 | 
						|
				if (!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)
 | 
						|
	{
 | 
						|
		// 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)
 | 
						|
	{
 | 
						|
		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;
 | 
						|
}
 |