From 42f4f28e06479a23b8f221f7f24a45662e7becad Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 14 Dec 2022 17:53:35 +0000 Subject: [PATCH] Statistics menu Relatively quick port from v1., but with under-the-hood changes to match the Emblem/Medal and Extra Medal reworks. We can make it prettier (and contain more data) later. --- src/k_menu.h | 14 ++- src/k_menudef.c | 23 ++++- src/k_menudraw.c | 227 +++++++++++++++++++++++++++++++++++++++++++++++ src/k_menufunc.c | 89 +++++++++++++++++++ src/m_cond.c | 23 ++--- src/m_cond.h | 2 +- 6 files changed, 362 insertions(+), 16 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index f992bb3b7..ac58a5e5c 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -405,8 +405,9 @@ extern menu_t MISC_ManualDef; extern menuitem_t MISC_Addons[]; extern menu_t MISC_AddonsDef; -extern menuitem_t MISC_Challenges[]; +extern menuitem_t MISC_ChallengesStatsDummyMenu[]; extern menu_t MISC_ChallengesDef; +extern menu_t MISC_StatisticsDef; // We'll need this since we're gonna have to dynamically enable and disable options depending on which state we're in. typedef enum @@ -1124,6 +1125,17 @@ void M_DrawChallenges(void); void M_ChallengesTick(void); boolean M_ChallengesInputs(INT32 ch); +extern struct statisticsmenu_s { + INT32 location; + INT32 nummaps; + INT32 maxscroll; + UINT16 *maplist; +} statisticsmenu; + +void M_Statistics(INT32 choice); +void M_DrawStatistics(void); +boolean M_StatisticsInputs(INT32 ch); + // These defines make it a little easier to make menus #define DEFAULTMENUSTYLE(source, prev, x, y)\ {\ diff --git a/src/k_menudef.c b/src/k_menudef.c index 9cbbdf932..99903d788 100644 --- a/src/k_menudef.c +++ b/src/k_menudef.c @@ -1503,7 +1503,7 @@ menuitem_t EXTRAS_Main[] = NULL, {.routine = M_ReplayHut}, 0, 0}, {IT_STRING | IT_CALL, "Statistics", "Look back on some of your greatest achievements such as your playtime and wins!", - NULL, {NULL}, 0, 0}, + NULL, {.routine = M_Statistics}, 0, 0}, }; // the extras menu essentially reuses the options menu stuff @@ -1746,16 +1746,16 @@ menu_t MISC_AddonsDef = { }; // Challenges. -menuitem_t MISC_ChallengesMenu[] = +menuitem_t MISC_ChallengesStatsDummyMenu[] = { {IT_STRING | IT_CALL, "Back", NULL, NULL, {.routine = M_GoBack}, 0, 0}, }; menu_t MISC_ChallengesDef = { - sizeof (MISC_ChallengesMenu)/sizeof (menuitem_t), + sizeof (MISC_ChallengesStatsDummyMenu)/sizeof (menuitem_t), &MainDef, 0, - MISC_ChallengesMenu, + MISC_ChallengesStatsDummyMenu, BASEVIDWIDTH/2, 32, 0, 0, 98, 0, @@ -1765,3 +1765,18 @@ menu_t MISC_ChallengesDef = { NULL, M_ChallengesInputs, }; + +menu_t MISC_StatisticsDef = { + sizeof (MISC_ChallengesStatsDummyMenu)/sizeof (menuitem_t), + &MainDef, + 0, + MISC_ChallengesStatsDummyMenu, + 280, 185, + 0, 0, + 98, 0, + M_DrawStatistics, + NULL, + NULL, + NULL, + M_StatisticsInputs, +}; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 300b9543a..55c358648 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4461,6 +4461,8 @@ void M_DrawAddons(void) #undef addonsseperation +// Challenges Menu + #define challengesbordercolor 27 static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili) @@ -4894,3 +4896,228 @@ challengedesc: V_DrawCenteredString(BASEVIDWIDTH/2, 120 + 32, V_ALLOWLOWERCASE, challengesmenu.unlockcondition); } } + +// Statistics menu + +#define STATSSTEP 10 + +static void M_DrawMapMedals(INT32 mapnum, INT32 x, INT32 y) +{ + UINT8 lasttype = UINT8_MAX, curtype; + emblem_t *emblem = M_GetLevelEmblems(mapnum); + + while (emblem) + { + switch (emblem->type) + { + case ET_TIME: + curtype = 1; + break; + case ET_GLOBAL: + { + if (emblem->flags & GE_NOTMEDAL) + { + emblem = M_GetLevelEmblems(-1); + continue; + } + curtype = 2; + break; + } + default: + curtype = 0; + break; + } + + // Shift over if emblem is of a different discipline + if (lasttype != UINT8_MAX && lasttype != curtype) + x -= 4; + lasttype = curtype; + + if (gamedata->collected[emblem-emblemlocations]) + V_DrawSmallMappedPatch(x, y, 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_CACHE), + R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_MENUCACHE)); + else + V_DrawSmallScaledPatch(x, y, 0, W_CachePatchName("NEEDIT", PU_CACHE)); + + emblem = M_GetLevelEmblems(-1); + x -= 8; + } +} + +static void M_DrawStatsMaps(void) +{ + INT32 y = 80, i = -1; + INT16 mnum; + boolean dotopname = true, dobottomarrow = (statisticsmenu.location < statisticsmenu.maxscroll); + INT32 location = statisticsmenu.location; + + if (location) + V_DrawCharacter(10, y-(skullAnimCounter/5), + '\x1A' | highlightflags, false); // up arrow + + while (statisticsmenu.maplist[++i] != NEXTMAP_INVALID) + { + if (location) + { + --location; + continue; + } + else if (dotopname) + { + V_DrawThinString(20, y, highlightflags, "LEVEL NAME"); + V_DrawRightAlignedThinString(BASEVIDWIDTH-20, y, highlightflags, "MEDALS"); + y += STATSSTEP; + dotopname = false; + } + + mnum = statisticsmenu.maplist[i]+1; + M_DrawMapMedals(mnum, 291, y); + + { + char *title = G_BuildMapTitle(mnum); + V_DrawThinString(20, y, 0, title); + Z_Free(title); + } + + y += STATSSTEP; + + if (y >= BASEVIDHEIGHT-STATSSTEP) + goto bottomarrow; + } + if (dotopname && !location) + { + V_DrawString(20, y, highlightflags, "LEVEL NAME"); + V_DrawString(256, y, highlightflags, "MEDALS"); + y += STATSSTEP; + } + else if (location) + --location; + + // Extra Emblem headers + for (i = 0; i < 2; ++i) + { + if (i == 1) + { + V_DrawThinString(20, y, highlightflags, "EXTRA MEDALS"); + if (location) + { + y += STATSSTEP; + location++; + } + } + if (location) + { + --location; + continue; + } + + y += STATSSTEP; + + if (y >= BASEVIDHEIGHT-STATSSTEP) + goto bottomarrow; + } + + // Extra Emblems + for (i = 0; i < MAXUNLOCKABLES; i++) + { + if (unlockables[i].type != SECRET_EXTRAMEDAL) + { + continue; + } + + if (location) + { + --location; + continue; + } + + if (i >= 0) + { + if (gamedata->unlocked[i]) + { + UINT16 color = min(unlockables[i].color, numskincolors-1); + if (!color) + color = SKINCOLOR_GOLD; + V_DrawSmallMappedPatch(291, y+1, 0, W_CachePatchName("GOTITA", PU_CACHE), + R_GetTranslationColormap(TC_DEFAULT, color, GTC_MENUCACHE)); + } + else + { + V_DrawSmallScaledPatch(291, y+1, 0, W_CachePatchName("NEEDIT", PU_CACHE)); + } + + V_DrawThinString(20, y, 0, va("%s", unlockables[i].name)); + } + + y += STATSSTEP; + + if (y >= BASEVIDHEIGHT-STATSSTEP) + goto bottomarrow; + } +bottomarrow: + if (dobottomarrow) + V_DrawCharacter(10, y-STATSSTEP + (skullAnimCounter/5), + '\x1B' | highlightflags, false); // down arrow +} + +void M_DrawStatistics(void) +{ + char beststr[40]; + + tic_t besttime = 0; + + INT32 i; + INT32 mapsunfinished = 0; + + { + patch_t *bg = W_CachePatchName("M_XTRABG", PU_CACHE); + V_DrawFixedPatch(0, 0, FRACUNIT, 0, bg, NULL); + } + + V_DrawThinString(20, 22, V_ALLOWLOWERCASE|highlightflags, "Total Play Time:"); + V_DrawCenteredThinString(BASEVIDWIDTH/2, 32, 0, + va("%i hours, %i minutes, %i seconds", + G_TicsToHours(gamedata->totalplaytime), + G_TicsToMinutes(gamedata->totalplaytime, false), + G_TicsToSeconds(gamedata->totalplaytime))); + V_DrawThinString(20, 42, V_ALLOWLOWERCASE|highlightflags, "Total Matches:"); + V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 42, 0, va("%i played", gamedata->matchesplayed)); + + if (!statisticsmenu.maplist) + { + V_DrawCenteredThinString(BASEVIDWIDTH/2, 62, V_ALLOWLOWERCASE, "No maps!?"); + return; + } + + for (i = 0; i < nummapheaders; i++) + { + if (!mapheaderinfo[i] || (mapheaderinfo[i]->menuflags & (LF2_NOTIMEATTACK|LF2_HIDEINSTATS|LF2_HIDEINMENU))) + continue; + + if (!mapheaderinfo[i]->mainrecord || mapheaderinfo[i]->mainrecord->time <= 0) + { + mapsunfinished++; + continue; + } + + besttime += mapheaderinfo[i]->mainrecord->time; + } + + V_DrawThinString(20, 60, V_ALLOWLOWERCASE, "Combined time records:"); + + sprintf(beststr, "%i:%02i:%02i.%02i", G_TicsToHours(besttime), G_TicsToMinutes(besttime, false), G_TicsToSeconds(besttime), G_TicsToCentiseconds(besttime)); + V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 60, V_ALLOWLOWERCASE|(mapsunfinished ? V_REDMAP : 0), beststr); + + if (mapsunfinished) + V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 70, V_ALLOWLOWERCASE|V_REDMAP, va("(%d unfinished)", mapsunfinished)); + else + V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 70, V_ALLOWLOWERCASE, "(complete)"); + + V_DrawThinString(32, 70, 0, va("x %d/%d", M_CountMedals(false, false), M_CountMedals(true, false))); + V_DrawSmallMappedPatch(20, 70, 0, W_CachePatchName("GOTITA", PU_CACHE), + R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_GOLD, GTC_MENUCACHE)); + + M_DrawStatsMaps(); +} + +#undef STATSSTEP diff --git a/src/k_menufunc.c b/src/k_menufunc.c index bcfc87bef..76fb8b738 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -6853,6 +6853,8 @@ void M_Manual(INT32 choice) M_SetupNextMenu(&MISC_ManualDef, true); } +// Challenges menu + struct challengesmenu_s challengesmenu; menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu) @@ -7385,3 +7387,90 @@ boolean M_ChallengesInputs(INT32 ch) return true; } + +// Statistics menu + +struct statisticsmenu_s statisticsmenu; + +void M_Statistics(INT32 choice) +{ + UINT16 i = 0; + + (void)choice; + + statisticsmenu.maplist = Z_Malloc(sizeof(UINT16) * nummapheaders, PU_STATIC, NULL); + statisticsmenu.nummaps = 0; + + for (i = 0; i < nummapheaders; i++) + { + if (!mapheaderinfo[i]) + continue; + + if (mapheaderinfo[i]->menuflags & (LF2_NOTIMEATTACK|LF2_HIDEINSTATS|LF2_HIDEINMENU)) + continue; + + if (M_MapLocked(i+1)) + continue; + + statisticsmenu.maplist[statisticsmenu.nummaps++] = i; + } + statisticsmenu.maplist[statisticsmenu.nummaps] = NEXTMAP_INVALID; + statisticsmenu.maxscroll = (statisticsmenu.nummaps + M_CountMedals(true, true) + 2) - 10; + statisticsmenu.location = 0; + + if (statisticsmenu.maxscroll < 0) + { + statisticsmenu.maxscroll = 0; + } + + MISC_StatisticsDef.prevMenu = currentMenu; + M_SetupNextMenu(&MISC_StatisticsDef, false); +} + +boolean M_StatisticsInputs(INT32 ch) +{ + const UINT8 pid = 0; + + (void)ch; + + if (M_MenuBackPressed(pid)) + { + M_GoBack(0); + M_SetMenuDelay(pid); + + Z_Free(statisticsmenu.maplist); + statisticsmenu.maplist = NULL; + + return true; + } + + if (M_MenuExtraPressed(pid)) + { + if (statisticsmenu.location > 0) + { + statisticsmenu.location = 0; + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + } + else if (menucmd[pid].dpad_ud > 0) + { + if (statisticsmenu.location < statisticsmenu.maxscroll) + { + statisticsmenu.location++; + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + } + else if (menucmd[pid].dpad_ud < 0) + { + if (statisticsmenu.location > 0) + { + statisticsmenu.location--; + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + } + + return true; +} diff --git a/src/m_cond.c b/src/m_cond.c index 9c0448ba6..052f23dc0 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -1123,17 +1123,20 @@ boolean M_MapLocked(INT32 mapnum) return false; } -INT32 M_CountMedals(boolean all) +INT32 M_CountMedals(boolean all, boolean extraonly) { INT32 found = 0, i; - for (i = 0; i < numemblems; ++i) + if (!extraonly) { - if ((emblemlocations[i].type == ET_GLOBAL) - && (emblemlocations[i].flags & GE_NOTMEDAL)) - continue; - if (!all && !gamedata->collected[i]) - continue; - found++; + 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) { @@ -1404,8 +1407,8 @@ emblem_t *M_GetLevelEmblems(INT32 mapnum) skincolornum_t M_GetEmblemColor(emblem_t *em) { - if (!em || em->color >= numskincolors) - return SKINCOLOR_NONE; + if (!em || !em->color || em->color >= numskincolors) + return SKINCOLOR_GOLD; return em->color; } diff --git a/src/m_cond.h b/src/m_cond.h index af4bd4fc8..1150b57ef 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -211,7 +211,7 @@ boolean M_CheckNetUnlockByID(UINT8 unlockid); boolean M_SecretUnlocked(INT32 type, boolean local); boolean M_CupLocked(cupheader_t *cup); boolean M_MapLocked(INT32 mapnum); -INT32 M_CountMedals(boolean all); +INT32 M_CountMedals(boolean all, boolean extraonly); // Emblem shit emblem_t *M_GetLevelEmblems(INT32 mapnum);