mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2025-10-30 08:01:28 +00:00
* Correctly identify that two columns with only one major unlock will have 6 empty tile slots, not two. This will result in less underutilised Challenges grid space.
1441 lines
32 KiB
C
1441 lines
32 KiB
C
// SONIC ROBO BLAST 2
|
|
//-----------------------------------------------------------------------------
|
|
// 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_pwrlv.h"
|
|
#include "k_profiles.h"
|
|
|
|
gamedata_t *gamedata = NULL;
|
|
boolean netUnlocked[MAXUNLOCKABLES];
|
|
|
|
// Map triggers for linedef executors
|
|
// 32 triggers, one bit each
|
|
UINT32 unlocktriggers;
|
|
|
|
// 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 (nummajorunlocks > 0)
|
|
{
|
|
I_Error("M_PopulateChallengeGrid: was not able to populate %d large tiles (width %d)", nummajorunlocks, gamedata->challengegridwidth);
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
UINT8 *M_ChallengeGridExtraData(void)
|
|
{
|
|
UINT8 i, j, num, id, tempid, work;
|
|
UINT8 *extradata;
|
|
boolean idchange;
|
|
|
|
if (!gamedata->challengegrid)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
extradata = Z_Malloc(
|
|
(gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT * sizeof(UINT8)),
|
|
PU_STATIC, NULL);
|
|
|
|
if (!extradata)
|
|
{
|
|
I_Error("M_ChallengeGridExtraData: was not able to allocate extradata");
|
|
}
|
|
|
|
//CONS_Printf(" --- \n");
|
|
|
|
for (i = 0; i < gamedata->challengegridwidth; i++)
|
|
{
|
|
for (j = 0; j < CHALLENGEGRIDHEIGHT; j++)
|
|
{
|
|
id = (i * CHALLENGEGRIDHEIGHT) + j;
|
|
num = gamedata->challengegrid[id];
|
|
idchange = false;
|
|
|
|
extradata[id] = CHE_NONE;
|
|
|
|
// 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] = CHE_CONNECTEDUP;
|
|
|
|
// Get the id to write extra hint data to.
|
|
// This check is safe because extradata's order of population
|
|
if (extradata[tempid] & CHE_CONNECTEDLEFT)
|
|
{
|
|
extradata[id] |= 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] == CHE_HINT)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else if (work < MAXUNLOCKABLES && gamedata->unlocked[work])
|
|
{
|
|
extradata[id] = 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] == CHE_HINT)
|
|
{
|
|
extradata[tempid] = CHE_HINT;
|
|
}
|
|
extradata[id] = 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] = CHE_HINT;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Since we're not modifying id past this point, the conditions become much simpler.
|
|
if (extradata[id] == 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] = 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] = CHE_HINT;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return extradata;
|
|
}
|
|
|
|
void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2)
|
|
{
|
|
condition_t *cond;
|
|
UINT32 num, wnum;
|
|
|
|
I_Assert(set && 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;
|
|
}
|
|
|
|
void M_ClearConditionSet(UINT8 set)
|
|
{
|
|
if (conditionSets[set].numconditions)
|
|
{
|
|
Z_Free(conditionSets[set].condition);
|
|
conditionSets[set].condition = NULL;
|
|
conditionSets[set].numconditions = 0;
|
|
}
|
|
gamedata->achieved[set] = false;
|
|
}
|
|
|
|
// Clear ALL secrets.
|
|
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->timesBeaten = 0;
|
|
|
|
// Re-unlock any always unlocked things
|
|
M_UpdateUnlockablesAndExtraEmblems(false);
|
|
}
|
|
|
|
// ----------------------
|
|
// Condition set checking
|
|
// ----------------------
|
|
|
|
// See also M_GetConditionString
|
|
UINT8 M_CheckCondition(condition_t *cn)
|
|
{
|
|
switch (cn->type)
|
|
{
|
|
case UC_PLAYTIME: // Requires total playing time >= x
|
|
return (gamedata->totalplaytime >= (unsigned)cn->requirement);
|
|
case UC_MATCHESPLAYED: // Requires any level completed >= x times
|
|
return (gamedata->matchesplayed >= (unsigned)cn->requirement);
|
|
case UC_POWERLEVEL: // Requires power level >= x on a certain gametype
|
|
{
|
|
UINT8 i;
|
|
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
|
|
{
|
|
UINT8 mvtype = MV_VISITED;
|
|
if (cn->type == UC_MAPBEATEN)
|
|
mvtype = MV_BEATEN;
|
|
else if (cn->type == UC_MAPENCORE)
|
|
mvtype = MV_ENCORE;
|
|
|
|
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_TRIGGER: // requires map trigger set
|
|
return !!(unlocktriggers & (1 << cn->requirement));
|
|
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);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static UINT8 M_CheckConditionSet(conditionset_t *c)
|
|
{
|
|
UINT32 i;
|
|
UINT32 lastID = 0;
|
|
condition_t *cn;
|
|
UINT8 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
|
|
else if (lastID && lastID == cn->id && !achievedSoFar)
|
|
continue;
|
|
|
|
lastID = cn->id;
|
|
achievedSoFar = M_CheckCondition(cn);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// See also M_CheckCondition
|
|
static const char *M_GetConditionString(condition_t *cn)
|
|
{
|
|
INT32 i;
|
|
char *title = NULL;
|
|
const char *work = NULL;
|
|
|
|
#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_MATCHESPLAYED: // Requires any level completed >= x times
|
|
return va("Play %d matches", 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
|
|
{
|
|
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 %s%s",
|
|
(cn->type == UC_MAPVISITED) ? "Visit" : "Finish a round on",
|
|
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_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 = va("Beat %s", 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("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("Find %s%s%s in %s before %i:%02i.%02i",
|
|
astr, colorstr, medalstr, title,
|
|
G_TicsToMinutes(emblemlocations[i].var, true),
|
|
G_TicsToSeconds(emblemlocations[i].var),
|
|
G_TicsToCentiseconds(emblemlocations[i].var));
|
|
}
|
|
else
|
|
{
|
|
work = va("Find %s%s%s in %s",
|
|
astr, colorstr, medalstr, title);
|
|
}
|
|
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
|
|
: "???");
|
|
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
|
|
}
|
|
|
|
//#define ACHIEVEDBRITE
|
|
|
|
char *M_BuildConditionSetString(UINT8 unlockid)
|
|
{
|
|
conditionset_t *c = NULL;
|
|
UINT32 lastID = 0;
|
|
condition_t *cn;
|
|
#ifdef ACHIEVEDBRITE
|
|
boolean achieved = false;
|
|
#endif
|
|
size_t len = 1024, worklen;
|
|
static char message[1024] = "";
|
|
const char *work = NULL;
|
|
size_t max = 0, start = 0, strlines = 0, i;
|
|
|
|
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)
|
|
{
|
|
worklen = 3;
|
|
if (lastID == cn->id)
|
|
{
|
|
strncat(message, "\n& ", len);
|
|
}
|
|
else
|
|
{
|
|
strncat(message, "\nOR ", len);
|
|
worklen++;
|
|
}
|
|
len -= worklen;
|
|
}
|
|
lastID = cn->id;
|
|
|
|
#ifdef ACHIEVEDBRITE
|
|
achieved = M_CheckCondition(cn);
|
|
|
|
if (achieved)
|
|
{
|
|
strncat(message, "\0x82", len);
|
|
len--;
|
|
}
|
|
#endif
|
|
|
|
work = M_GetConditionString(cn);
|
|
worklen = strlen(work);
|
|
|
|
strncat(message, work, len);
|
|
len -= worklen;
|
|
|
|
#ifdef ACHIEVEDBRITE
|
|
if (achieved)
|
|
{
|
|
strncat(message, "\0x80", len);
|
|
len--;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
else if (message[i] == '\n')
|
|
{
|
|
strlines = i;
|
|
start = 0;
|
|
max = 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 -= (start-strlines)*8;
|
|
strlines = start;
|
|
start = 0;
|
|
}
|
|
}
|
|
|
|
return message;
|
|
}
|
|
|
|
static void M_CheckUnlockConditions(void)
|
|
{
|
|
INT32 i;
|
|
conditionset_t *c;
|
|
|
|
for (i = 0; i < MAXCONDITIONSETS; ++i)
|
|
{
|
|
c = &conditionSets[i];
|
|
if (!c->numconditions || gamedata->achieved[i])
|
|
continue;
|
|
|
|
gamedata->achieved[i] = (M_CheckConditionSet(c));
|
|
}
|
|
}
|
|
|
|
boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud)
|
|
{
|
|
INT32 i;
|
|
UINT8 response = 0;
|
|
|
|
if (!loud)
|
|
{
|
|
// Just in case they aren't to sync
|
|
// Done first so that emblems are ready before check
|
|
M_CheckLevelEmblems();
|
|
M_CompletionEmblems();
|
|
}
|
|
|
|
M_CheckUnlockConditions();
|
|
|
|
// 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)
|
|
{
|
|
if (loud)
|
|
{
|
|
S_StartSound(NULL, sfx_ncitem);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
UINT8 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;
|
|
}
|
|
|
|
return MAXUNLOCKABLES;
|
|
}
|
|
|
|
// Emblem unlocking shit
|
|
UINT8 M_CheckLevelEmblems(void)
|
|
{
|
|
INT32 i;
|
|
INT32 valToReach;
|
|
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;
|
|
|
|
switch (emblemlocations[i].type)
|
|
{
|
|
case ET_TIME: // Requires time on map <= x
|
|
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_TIME || 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;
|
|
|
|
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(INT32 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 || 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;
|
|
}
|