M_PopulateChallengeGrid

Basic challenge grid data is now generated the first time you head to the challenges menu.
- Large tiles are placed first.
    - `UINT8 majorunlock` in `unlockable_t`.
    - Builds a list of all possible positions the first large tile could be plonked at.
    - Randomly selects from that list, then removes every position that overlaps the given spot before the next large tile is handled.
- Smaller tiles are filled into all the remaining gaps.
    - Currently bubbles gaps through the random list if empty spots after large tile placement > number of small tiles to place, but all the gaps could be forced to the end.
- Has a REALLY prelim drawer, literally just enough to confirm the tilegrid data is correct visually.
- DEVELOP: Can be regenerated by pressing (C) while the challenges are up.
Also, general maintenance.
- Remove `showconditionset`, `nocecho`, and `nochecklist` from `unlockable_t` for not fitting with our new intent for challenges
- Remove M_AnySecretUnlocked - Not currently used, but its eventual use - stopping a player from seeing a completely blank challenges grid - isn't in the spirit.
- M_MapLocked no longer permits all map transitions in DEVELOP, so unlocks can actually be tested.
This commit is contained in:
toaster 2022-12-01 16:04:12 +00:00
parent f0e5e1b71a
commit 1391cbe01e
6 changed files with 255 additions and 36 deletions

View file

@ -2336,12 +2336,8 @@ void readunlockable(MYFILE *f, INT32 num)
if (fastcmp(word, "CONDITIONSET"))
unlockables[num].conditionset = (UINT8)i;
else if (fastcmp(word, "SHOWCONDITIONSET"))
unlockables[num].showconditionset = (UINT8)i;
else if (fastcmp(word, "NOCECHO"))
unlockables[num].nocecho = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y');
else if (fastcmp(word, "NOCHECKLIST"))
unlockables[num].nochecklist = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y');
else if (fastcmp(word, "MAJORUNLOCK"))
unlockables[num].majorunlock = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y');
else if (fastcmp(word, "TYPE"))
{
if (fastcmp(word2, "NONE"))

View file

@ -4411,6 +4411,23 @@ void G_LoadGameData(void)
i += j;
}
gamedata->challengegridwidth = READUINT16(save_p);
Z_Free(gamedata->challengegrid);
if (gamedata->challengegridwidth)
{
gamedata->challengegrid = Z_Malloc(
(gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT * sizeof(UINT8)),
PU_STATIC, NULL);
for (i = 0; i < (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT); i++)
{
gamedata->challengegrid[i] = READUINT8(save_p);
}
}
else
{
gamedata->challengegrid = NULL;
}
gamedata->timesBeaten = READUINT32(save_p);
// Main records
@ -4554,6 +4571,19 @@ void G_SaveGameData(void)
i += j;
}
if (gamedata->challengegrid)
{
WRITEUINT16(save_p, gamedata->challengegridwidth);
for (i = 0; i < (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT); i++)
{
WRITEUINT8(save_p, gamedata->challengegrid[i]);
}
}
else
{
WRITEUINT16(save_p, 0);
}
WRITEUINT32(save_p, gamedata->timesBeaten); // 4
// Main records

View file

@ -4460,7 +4460,8 @@ void M_DrawAddons(void)
void M_DrawChallenges(void)
{
INT32 x, y;
INT32 x = 20, y = 20;
UINT8 i, j, unlock;
{
patch_t *bg = W_CachePatchName("M_XTRABG", PU_CACHE);
@ -4469,16 +4470,34 @@ void M_DrawChallenges(void)
if (challengesmenu.currentunlock < MAXUNLOCKABLES)
{
V_DrawThinString(currentMenu->x, currentMenu->y, V_ALLOWLOWERCASE, unlockables[challengesmenu.currentunlock].name);
V_DrawThinString(x, y, V_ALLOWLOWERCASE, unlockables[challengesmenu.currentunlock].name);
if (challengesmenu.unlockanim >= UNLOCKTIME)
V_DrawThinString(currentMenu->x, currentMenu->y + 10, V_ALLOWLOWERCASE, "Press (A)");
V_DrawThinString(x, y + 10, V_ALLOWLOWERCASE, "Press (A)");
}
else
{
V_DrawThinString(currentMenu->x, currentMenu->y, V_ALLOWLOWERCASE, va("pending = %c", challengesmenu.pending ? 'T' : 'F'));
V_DrawThinString(x, y, V_ALLOWLOWERCASE, va("pending = %c", challengesmenu.pending ? 'T' : 'F'));
if (challengesmenu.unlockanim >= UNLOCKTIME)
V_DrawThinString(currentMenu->x, currentMenu->y + 10, V_ALLOWLOWERCASE, "Press (B)");
V_DrawThinString(x, y + 10, V_ALLOWLOWERCASE, "Press (B)");
}
x = currentMenu->x;
y = currentMenu->y;
for (i = 0; i < gamedata->challengegridwidth; i++)
{
for (j = 0; j < CHALLENGEGRIDHEIGHT; j++)
{
unlock = gamedata->challengegrid[(i * CHALLENGEGRIDHEIGHT) + j];
// very WIP render of tilegrid
if (unlock >= MAXUNLOCKABLES)
V_DrawString(x + 10*i, y + 10*j, V_ALLOWLOWERCASE, ".");
else if (gamedata->unlocked[unlock] == false)
V_DrawString(x + 10*i, y + 10*j, V_ALLOWLOWERCASE, "?");
else
V_DrawString(x + 10*i, y + 10*j, V_ALLOWLOWERCASE, va("%c", unlockables[unlock].name[0]));
}
}
}

View file

@ -781,8 +781,10 @@ static boolean M_PrevOpt(void)
if (M_GetNextAchievedUnlock(false) < MAXUNLOCKABLES)
{
MISC_ChallengesDef.prevMenu = desiredmenu;
challengesmenu.pending = true;
challengesmenu.currentunlock = MAXUNLOCKABLES;
M_PopulateChallengeGrid();
return &MISC_ChallengesDef;
}
@ -6856,6 +6858,17 @@ boolean M_ChallengesInputs(INT32 ch)
{
;
}
#ifdef DEVELOP
else if (M_MenuExtraPressed(pid)) // debugging
{
Z_Free(gamedata->challengegrid);
gamedata->challengegrid = NULL;
gamedata->challengegridwidth = 0;
M_PopulateChallengeGrid();
challengesmenu.unlockanim = 0;
return true;
}
#endif
else if (challengesmenu.pending)
{
if ((M_MenuConfirmPressed(pid) || start))

View file

@ -11,6 +11,7 @@
/// \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"
@ -54,6 +55,179 @@ void M_NewGameDataStruct(void)
G_ClearRecords();
}
void M_PopulateChallengeGrid(void)
{
UINT16 i, j;
UINT16 numunlocks = 0, nummajorunlocks = 0, numempty = 0;
UINT8 selection[2][MAXUNLOCKABLES + (CHALLENGEGRIDHEIGHT-1)];
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]);
}
if (numunlocks + nummajorunlocks == 0)
{
gamedata->challengegridwidth = 0;
return;
}
gamedata->challengegridwidth = (numunlocks + (nummajorunlocks * 4) + (CHALLENGEGRIDHEIGHT-1))/CHALLENGEGRIDHEIGHT;
if (nummajorunlocks)
{
// Getting the number of 2-highs you can fit into two adjacent columns.
UINT8 majorpad = (CHALLENGEGRIDHEIGHT/2);
majorpad = (nummajorunlocks/majorpad)+1;
if (gamedata->challengegridwidth < majorpad*2)
gamedata->challengegridwidth = majorpad*2;
}
gamedata->challengegrid = Z_Malloc(
(gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT * sizeof(UINT8)),
PU_STATIC, NULL);
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.
UINT16 numspots = gamedata->challengegridwidth * (CHALLENGEGRIDHEIGHT-1);
// 0 is row, 1 is column
UINT8 quickcheck[numspots][2];
// Prepare the easy-grab spots.
for (i = 0; i < numspots; i++)
{
quickcheck[i][0] = i%(CHALLENGEGRIDHEIGHT-1);
quickcheck[i][1] = i/(CHALLENGEGRIDHEIGHT-1);
}
// Place in random valid locations.
while (nummajorunlocks > 0 && numspots > 0)
{
UINT8 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(((signed)quickcheck[i][0]) - ((signed)row)) <= 1 // Row distance
|| abs(((signed)quickcheck[i][1]) - ((signed)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", nummajorunlocks);
}
}
// 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;
}
}
}
void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2)
{
condition_t *cond;
@ -105,6 +279,10 @@ void M_ClearSecrets(void)
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
@ -406,22 +584,6 @@ UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separa
// -------------------
// Quick unlock checks
// -------------------
UINT8 M_AnySecretUnlocked(void)
{
INT32 i;
#ifdef DEVELOP
if (1)
return true;
#endif
for (i = 0; i < MAXUNLOCKABLES; ++i)
{
if (!unlockables[i].nocecho && gamedata->unlocked[i])
return true;
}
return false;
}
UINT8 M_SecretUnlocked(INT32 type)
{
@ -455,10 +617,6 @@ UINT8 M_SecretUnlocked(INT32 type)
UINT8 M_MapLocked(INT32 mapnum)
{
#ifdef DEVELOP
(void)mapnum;
return false;
#else
// Don't lock maps in dedicated servers.
// That just makes hosts' lives hell.
if (dedicated)
@ -471,7 +629,6 @@ UINT8 M_MapLocked(INT32 mapnum)
return true;
return false;
#endif
}
INT32 M_CountEmblems(void)

View file

@ -94,12 +94,10 @@ typedef struct
char name[64];
char objective[64];
UINT8 conditionset;
UINT8 showconditionset;
INT16 type;
INT16 variable;
char *stringVar;
UINT8 nocecho;
UINT8 nochecklist;
UINT8 majorunlock;
} unlockable_t;
#define SECRET_NONE 0 // Does nil. Use with levels locked by UnlockRequired
@ -130,6 +128,8 @@ typedef struct
#define MAXEXTRAEMBLEMS 16
#define MAXUNLOCKABLES 32
#define CHALLENGEGRIDHEIGHT 5
// GAMEDATA STRUCTURE
// Everything that would get saved in gamedata.dat
typedef struct
@ -149,6 +149,10 @@ typedef struct
// UNLOCKABLES UNLOCKED
boolean unlocked[MAXUNLOCKABLES];
// CHALLENGE GRID
UINT16 challengegridwidth;
UINT8 *challengegrid;
// # OF TIMES THE GAME HAS BEEN BEATEN
UINT32 timesBeaten;
@ -170,6 +174,7 @@ extern INT32 numextraemblems;
extern UINT32 unlocktriggers;
void M_NewGameDataStruct(void);
void M_PopulateChallengeGrid(void);
// Condition set setup
void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2);
@ -187,7 +192,6 @@ UINT8 M_CheckLevelEmblems(void);
UINT8 M_CompletionEmblems(void);
// Checking unlockable status
UINT8 M_AnySecretUnlocked(void);
UINT8 M_SecretUnlocked(INT32 type);
UINT8 M_MapLocked(INT32 mapnum);
INT32 M_CountEmblems(void);