From cc9a65c8f8e28bace77b1c320b2aba21684c1615 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 12 Dec 2022 16:55:56 +0000 Subject: [PATCH] Add unlock condition text to Challenges menu Has ifdef'd out code for conditions to change between white and yellow when achieved to match V1 behaviour... wasn't able to figure out why it wasn't working, so dummied out for now. --- src/k_menu.h | 2 + src/k_menudraw.c | 7 +- src/k_menufunc.c | 19 ++-- src/m_cond.c | 223 ++++++++++++++++++++++++++++++++++++++++++++++- src/m_cond.h | 8 +- 5 files changed, 248 insertions(+), 11 deletions(-) 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);