diff --git a/src/k_menu.h b/src/k_menu.h index bf4d69a57..f992bb3b7 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -1101,6 +1101,8 @@ extern struct challengesmenu_s { INT16 offset; // To make the icons move smoothly when we transition! UINT8 currentunlock; + char *unlockcondition; + tic_t unlockanim; SINT8 row, hilix, focusx; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 9c6e2de64..92b242ab0 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4720,6 +4720,9 @@ challengedesc: V_DrawLSTitleLowString(BASEVIDWIDTH/2 - offset, y+6, 0, str); } - if (!challengesmenu.fade) - V_DrawThinString(20, 120 + 60, V_ALLOWLOWERCASE, "Press (B)"); + // Conditions for unlock + if (challengesmenu.unlockcondition != NULL) + { + V_DrawCenteredString(BASEVIDWIDTH/2, 120 + 40, V_ALLOWLOWERCASE, challengesmenu.unlockcondition); + } } diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 6a7c23007..7c23c3d78 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -1776,15 +1776,14 @@ static inline size_t M_StringHeight(const char *string) void M_StartMessage(const char *string, void *routine, menumessagetype_t itemtype) { const UINT8 pid = 0; - size_t max = 0, start = 0, i, strlines; + size_t max = 0, start = 0, strlines = 0, i; static char *message = NULL; Z_Free(message); message = Z_StrDup(string); DEBFILE(message); // Rudementary word wrapping. - // Simple and effective. Does not handle nonuniform letter sizes, colors, etc. but who cares. - strlines = 0; + // Simple and effective. Does not handle nonuniform letter sizes, etc. but who cares. for (i = 0; message[i]; i++) { if (message[i] == ' ') @@ -1799,6 +1798,8 @@ void M_StartMessage(const char *string, void *routine, menumessagetype_t itemtyp max = 0; continue; } + else if (message[i] & 0x80) + continue; else max += 8; @@ -6859,13 +6860,14 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu) if (challengesmenu.pending || desiredmenu == NULL) { - memset(setup_explosions, 0, sizeof(setup_explosions)); challengesmenu.currentunlock = MAXUNLOCKABLES; - M_PopulateChallengeGrid(); + challengesmenu.unlockcondition = NULL; + M_PopulateChallengeGrid(); if (gamedata->challengegrid) challengesmenu.extradata = M_ChallengeGridExtraData(); + memset(setup_explosions, 0, sizeof(setup_explosions)); memset(&challengesmenu.unlockcount, 0, sizeof(challengesmenu.unlockcount)); for (i = 0; i < MAXUNLOCKABLES; i++) { @@ -6899,6 +6901,7 @@ static void M_ChallengesAutoFocus(UINT8 unlockid, boolean fresh) return; challengesmenu.currentunlock = unlockid; + challengesmenu.unlockcondition = M_BuildConditionSetString(challengesmenu.currentunlock); challengesmenu.unlockanim = 0; if (gamedata->challengegrid == NULL || challengesmenu.extradata == NULL) @@ -7099,6 +7102,9 @@ void M_ChallengesTick(void) gamedata->unlocked[challengesmenu.currentunlock] = true; M_UpdateUnlockablesAndExtraEmblems(true); + // Update shown description just in case..? + challengesmenu.unlockcondition = M_BuildConditionSetString(challengesmenu.currentunlock); + challengesmenu.unlockcount[CC_TALLY]++; challengesmenu.unlockcount[CC_ANIM]++; @@ -7213,6 +7219,8 @@ boolean M_ChallengesInputs(INT32 ch) Z_Free(challengesmenu.extradata); challengesmenu.extradata = NULL; + challengesmenu.unlockcondition = NULL; + return true; } @@ -7336,6 +7344,7 @@ boolean M_ChallengesInputs(INT32 ch) // After movement has been determined, figure out the current selection. i = (challengesmenu.col * CHALLENGEGRIDHEIGHT) + challengesmenu.row; challengesmenu.currentunlock = (gamedata->challengegrid[i]); + challengesmenu.unlockcondition = M_BuildConditionSetString(challengesmenu.currentunlock); challengesmenu.hilix = challengesmenu.col; challengesmenu.hiliy = challengesmenu.row; diff --git a/src/m_cond.c b/src/m_cond.c index 997bae94b..1735b01e6 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -480,6 +480,8 @@ void M_ClearSecrets(void) // ---------------------- // Condition set checking // ---------------------- + +// See also M_GetConditionString UINT8 M_CheckCondition(condition_t *cn) { switch (cn->type) @@ -564,7 +566,226 @@ static UINT8 M_CheckConditionSet(conditionset_t *c) return achievedSoFar; } -void M_CheckUnlockConditions(void) +// 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_MapLocked(i+1) ? Z_StrDup("???") : G_BuildMapTitle(i+1)) + + 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" : "Beat", + 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_TOTALEMBLEMS: // 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 = G_MapNumber(emblemlocations[i].level); + + 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: + 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 screen width. + if (max >= BASEVIDWIDTH && 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; diff --git a/src/m_cond.h b/src/m_cond.h index 04be99379..bab7fc854 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -171,12 +171,15 @@ extern INT32 numemblems; extern UINT32 unlocktriggers; void M_NewGameDataStruct(void); + +// Challenges menu stuff void M_PopulateChallengeGrid(void); UINT8 *M_ChallengeGridExtraData(void); +char *M_BuildConditionSetString(UINT8 unlockid); #define CHE_NONE 0 #define CHE_HINT 1 -#define CHE_CONNECTEDLEFT 2 -#define CHE_CONNECTEDUP 4 +#define CHE_CONNECTEDLEFT (1<<1) +#define CHE_CONNECTEDUP (1<<2) #define CHE_DONTDRAW (CHE_CONNECTEDLEFT|CHE_CONNECTEDUP) // Condition set setup @@ -187,7 +190,6 @@ void M_ClearConditionSet(UINT8 set); void M_ClearSecrets(void); // Updating conditions and unlockables -void M_CheckUnlockConditions(void); UINT8 M_CheckCondition(condition_t *cn); boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud); UINT8 M_GetNextAchievedUnlock(void);