RingRacers/src/menus/extras-statistics.c
toaster 83366b6507 More universal Course restriction based on progression
All courses are restricted in Match Race/Time Attack/Online if not visited in GP UNLESS:
- It is the first Race Course of a Cup
- Course has "NoVisitNeeded = True`
    - The Controls Tutorial and Test Run are the only two stages that will need this

The above replaces:
- A lot of restricted courses having to be marked with `FinishNeeded = True`
    - Hidden Palace
    - Sealed Stars 1-14
        - Once Special Mode is unlocked, it will now be possible to practice Sealed Stars before rematching them in GP
- Almost all Tutorial-specific behaviour, since it was heinously hacky

HOWEVER, `FinishNeeded = True` was left in specifically for future releases, and I reserve the right to use it on Adventure Example again before launch.
2024-01-07 13:57:05 +00:00

345 lines
6.8 KiB
C

/// \file menus/extras-challenges.c
/// \brief Statistics menu
#include "../k_menu.h"
#include "../z_zone.h"
#include "../m_cond.h" // Condition Sets
#include "../s_sound.h"
#include "../r_skins.h"
struct statisticsmenu_s statisticsmenu;
static boolean M_StatisticsAddMap(UINT16 map, cupheader_t *cup, boolean *headerexists, boolean tutorial)
{
if (!mapheaderinfo[map])
return false;
if (mapheaderinfo[map]->cup != cup)
return false;
if (((mapheaderinfo[map]->typeoflevel & TOL_TUTORIAL) == TOL_TUTORIAL) != tutorial)
return false;
// Check for no visibility
if (mapheaderinfo[map]->menuflags & LF2_HIDEINMENU)
return false;
// Check for visitation
// (M_CanShowLevelInList also checks being the first map in a cup,
// but we don't need to do this here because it'd just muddy stats!)
if (!(mapheaderinfo[map]->menuflags & LF2_NOVISITNEEDED)
&& !(mapheaderinfo[map]->records.mapvisited & MV_VISITED))
return false;
#if 0
// Check for completion
if ((mapheaderinfo[map]->menuflags & LF2_FINISHNEEDED)
&& !(mapheaderinfo[map]->records.mapvisited & MV_BEATEN))
return false;
#endif
// Check for unlock
if (M_MapLocked(map+1))
return false;
if (*headerexists == false)
{
statisticsmenu.maplist[statisticsmenu.nummaps++] = NEXTMAP_TITLE; // cheeky hack
*headerexists = true;
}
statisticsmenu.maplist[statisticsmenu.nummaps++] = map;
return true;
}
static void M_StatisticsMaps(void)
{
cupheader_t *cup;
UINT16 i;
boolean headerexists;
statisticsmenu.maplist = Z_Malloc(sizeof(UINT16) * (nummapheaders+1 + numkartcupheaders), PU_STATIC, NULL);
statisticsmenu.nummaps = 0;
// Cups
for (cup = kartcupheaders; cup; cup = cup->next)
{
headerexists = false;
if (M_CupLocked(cup))
continue;
for (i = 0; i < CUPCACHE_PODIUM; i++)
{
if (cup->cachedlevels[i] >= nummapheaders)
continue;
M_StatisticsAddMap(cup->cachedlevels[i], cup, &headerexists, false);
}
}
// Lost and Found
headerexists = false;
for (i = 0; i < nummapheaders; i++)
{
M_StatisticsAddMap(i, NULL, &headerexists, false);
}
// Tutorial
headerexists = false;
for (i = 0; i < nummapheaders; i++)
{
M_StatisticsAddMap(i, NULL, &headerexists, true);
}
if ((i = statisticsmenu.numextramedals) != 0)
i += 2;
statisticsmenu.maplist[statisticsmenu.nummaps] = NEXTMAP_INVALID;
statisticsmenu.maxscroll = (statisticsmenu.nummaps + i) - 12;
statisticsmenu.location = 0;
if (statisticsmenu.maxscroll < 0)
{
statisticsmenu.maxscroll = 0;
}
}
static void M_StatisticsChars(void)
{
UINT16 i;
statisticsmenu.maplist = Z_Malloc(sizeof(UINT16) * (1 + numskins), PU_STATIC, NULL);
statisticsmenu.nummaps = 0;
UINT32 beststat = 0;
for (i = 0; i < numskins; i++)
{
if (!R_SkinUsable(-1, i, false))
continue;
statisticsmenu.maplist[statisticsmenu.nummaps++] = i;
if (skins[i].records.wins == 0)
continue;
// The following is a partial duplication of R_GetEngineClass
{
if (skins[i].flags & SF_IRONMAN)
continue; // does not add to any engine class
INT32 s = (skins[i].kartspeed - 1);
INT32 w = (skins[i].kartweight - 1);
#define LOCKSTAT(stat) \
if (stat < 0) { continue; } \
if (stat > 8) { continue; }
LOCKSTAT(s);
LOCKSTAT(w);
#undef LOCKSTAT
if (
statisticsmenu.statgridplayed[s][w] > skins[i].records.wins
&& (UINT32_MAX - statisticsmenu.statgridplayed[s][w]) < skins[i].records.wins
)
continue; // overflow protection
statisticsmenu.statgridplayed[s][w] += skins[i].records.wins;
if (beststat >= statisticsmenu.statgridplayed[s][w])
continue;
beststat = statisticsmenu.statgridplayed[s][w];
}
}
statisticsmenu.maplist[statisticsmenu.nummaps] = MAXSKINS;
statisticsmenu.location = 0;
statisticsmenu.maxscroll = statisticsmenu.nummaps - 6;
if (statisticsmenu.maxscroll < 0)
{
statisticsmenu.maxscroll = 0;
}
if (beststat != 0)
{
UINT16 j;
UINT8 shif = 0;
// Done this way to ensure ample precision but also prevent overflow
while (beststat < FRACUNIT)
{
beststat <<= 1;
shif++;
}
for (i = 0; i < 9; i++)
{
for (j = 0; j < 9; j++)
{
if (statisticsmenu.statgridplayed[i][j] == 0)
continue;
statisticsmenu.statgridplayed[i][j] =
FixedDiv(
statisticsmenu.statgridplayed[i][j] << shif,
beststat
);
if (statisticsmenu.statgridplayed[i][j] == 0)
statisticsmenu.statgridplayed[i][j] = 1;
}
}
}
}
static void M_StatisticsGP(void)
{
statisticsmenu.maplist = Z_Malloc(sizeof(UINT16) * (1 + numkartcupheaders), PU_STATIC, NULL);
statisticsmenu.nummaps = 0;
cupheader_t *cup;
for (cup = kartcupheaders; cup; cup = cup->next)
{
if (M_CupLocked(cup))
continue;
statisticsmenu.maplist[statisticsmenu.nummaps++] = cup->id;
}
statisticsmenu.maplist[statisticsmenu.nummaps] = UINT16_MAX;
statisticsmenu.location = 0;
statisticsmenu.maxscroll = statisticsmenu.nummaps - 5;
if (statisticsmenu.maxscroll < 0)
{
statisticsmenu.maxscroll = 0;
}
}
static void M_StatisticsPageInit(void)
{
switch (statisticsmenu.page)
{
case statisticspage_maps:
{
M_StatisticsMaps();
break;
}
case statisticspage_chars:
{
M_StatisticsChars();
break;
}
case statisticspage_gp:
{
M_StatisticsGP();
break;
}
default:
break;
}
}
static void M_StatisticsPageClear(void)
{
if (statisticsmenu.maplist != NULL)
{
Z_Free(statisticsmenu.maplist);
statisticsmenu.maplist = NULL;
}
}
void M_Statistics(INT32 choice)
{
(void)choice;
statisticsmenu.gotmedals = M_CountMedals(false, false);
statisticsmenu.nummedals = M_CountMedals(true, false);
statisticsmenu.numextramedals = M_CountMedals(true, true);
M_StatisticsPageInit();
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);
M_StatisticsPageClear();
return true;
}
if (menucmd[pid].dpad_lr != 0)
{
M_StatisticsPageClear();
statisticsmenu.page +=
statisticspage_max
+ (
(menucmd[pid].dpad_lr > 0)
? 1
: -1
);
statisticsmenu.page %= statisticspage_max;
M_StatisticsPageInit();
S_StartSound(NULL, sfx_s3k5b);
M_SetMenuDelay(pid);
return true;
}
if (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;
}