diff --git a/src/deh_soc.c b/src/deh_soc.c index 74a0920e4..5c01f5d43 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -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")) diff --git a/src/g_game.c b/src/g_game.c index fcdc4b628..e2f0524ee 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -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 diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 48652c6f9..d2093c970 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -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])); + } } } diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 84070b3d4..89bd748f2 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -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)) diff --git a/src/m_cond.c b/src/m_cond.c index 68ec3e206..4d33a0a05 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -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) diff --git a/src/m_cond.h b/src/m_cond.h index e60ae6f31..54f266859 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -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);