Add "Characters & Emgine Classes" page to statistics

- Shows wins for every character
- Shows heatmap of which stats are your favourite (of the loaded skins)
- Adds a header to the top of all statistics pages showing you can scroll left and right
    - General spacing is adjusted to accomodate
This commit is contained in:
toaster 2023-08-07 00:16:08 +01:00
parent 52b4104f86
commit 9ed1471c95
3 changed files with 270 additions and 35 deletions

View file

@ -1263,6 +1263,7 @@ typedef enum
{ {
statisticspage_basic = 0, statisticspage_basic = 0,
statisticspage_maps, statisticspage_maps,
statisticspage_chars,
statisticspage_max statisticspage_max
} statisticspage_t; } statisticspage_t;
@ -1273,6 +1274,7 @@ extern struct statisticsmenu_s {
INT32 gotmedals; INT32 gotmedals;
INT32 nummedals; INT32 nummedals;
INT32 numextramedals; INT32 numextramedals;
UINT32 statgridplayed[9][9];
INT32 maxscroll; INT32 maxscroll;
UINT16 *maplist; UINT16 *maplist;
} statisticsmenu; } statisticsmenu;

View file

@ -6180,6 +6180,120 @@ bottomarrow:
'\x1B' | highlightflags, false); // down arrow '\x1B' | highlightflags, false); // down arrow
} }
#undef STATSSTEP
#define STATSSTEP 18
static void M_DrawStatsChars(void)
{
INT32 y = 80, i, j;
INT16 skin;
boolean dobottomarrow = (statisticsmenu.location < statisticsmenu.maxscroll);
INT32 location = statisticsmenu.location;
if (!statisticsmenu.maplist)
{
V_DrawCenteredThinString(BASEVIDWIDTH/2, 62, 0, "No chars!?");
return;
}
if (location)
V_DrawCharacter(10, y-(skullAnimCounter/5),
'\x1A' | highlightflags, false); // up arrow
i = -1;
V_DrawThinString(20, y - 10, highlightflags, "CHARACTER");
V_DrawRightAlignedThinString(BASEVIDWIDTH/2 + 34, y - 10, highlightflags, "WINS");
while ((skin = statisticsmenu.maplist[++i]) != NEXTMAP_INVALID)
{
if (location)
{
--location;
continue;
}
{
UINT8 *colormap = R_GetTranslationColormap(skin, skins[skin].prefcolor, GTC_MENUCACHE);
V_DrawFixedPatch(24*FRACUNIT, y*FRACUNIT,
FRACUNIT,
0, faceprefix[skin][FACE_RANK],
colormap);
V_DrawFadeFill(24+16, y, 16, 16, 0, 31, 8); // challengetransparentstrength
V_DrawFill(24+16+5, y+1, 1, 14, 0);
V_DrawFill(24+16+5+5, y+1, 1, 14, 0);
V_DrawFill(24+16+1, y+5, 14, 1, 0);
V_DrawFill(24+16+1, y+5+5, 14, 1, 0);
// The following is a partial duplication of R_GetEngineClass
{
INT32 s = (skins[skin].kartspeed - 1)/3;
INT32 w = (skins[skin].kartweight - 1)/3;
#define LOCKSTAT(stat) \
if (stat < 0) { stat = 0; } \
if (stat > 2) { stat = 2; }
LOCKSTAT(s);
LOCKSTAT(w);
#undef LOCKSTAT
V_DrawFill(24+16 + (s*5), y + (w*5), 6, 6, 0);
}
}
V_DrawThinString(24+32+2, y+3, 0, skins[skin].realname);
V_DrawRightAlignedThinString(BASEVIDWIDTH/2 + 30, y+3, 0, va("%d", skins[skin].records.wins));
y += STATSSTEP;
if (y >= BASEVIDHEIGHT-STATSSTEP)
goto bottomarrow;
}
bottomarrow:
if (dobottomarrow)
V_DrawCharacter(10, y-10 + (skullAnimCounter/5),
'\x1B' | highlightflags, false); // down arrow
UINT32 x = BASEVIDWIDTH - 20 - 90;
y = 88;
V_DrawCenteredThinString(x + 45, y - 10, highlightflags, "HEATMAP");
V_DrawFadeFill(x, y, 91, 91, 0, 31, 8); // challengetransparentstrength
V_DrawFill(x+30, y+1, 1, 89, 0);
V_DrawFill(x+60, y+1, 1, 89, 0);
V_DrawFill(x+1, y+30, 89, 1, 0);
V_DrawFill(x+1, y+60, 89, 1, 0);
x++;
y++;
for (i = 0; i < 9; i++)
{
for (j = 0; j < 9; j++)
{
if (statisticsmenu.statgridplayed[i][j] == 0)
continue;
V_DrawFill(
x + (i * 10),
y + (j * 10),
9,
9,
31 - ((statisticsmenu.statgridplayed[i][j] - 1) * 32) / FRACUNIT
);
}
}
}
#undef STATSSTEP
void M_DrawStatistics(void) void M_DrawStatistics(void)
{ {
char beststr[256]; char beststr[256];
@ -6190,8 +6304,47 @@ void M_DrawStatistics(void)
V_DrawFixedPatch(0, 0, FRACUNIT, 0, bg, NULL); V_DrawFixedPatch(0, 0, FRACUNIT, 0, bg, NULL);
} }
{
const char *pagename = NULL;
INT32 pagenamewidth = 0;
V_DrawFixedPatch(0, 0, FRACUNIT, 0, W_CachePatchName("MENUHINT", PU_CACHE), NULL);
switch (statisticsmenu.page)
{
case statisticspage_maps:
{
pagename = "LEVELS & MEDALS";
M_DrawStatsMaps();
break;
}
case statisticspage_chars:
{
pagename = "CHARACTERS & ENGINE CLASSES";
M_DrawStatsChars();
break;
}
default:
break;
}
if (pagename)
{
pagenamewidth = V_ThinStringWidth(pagename, 0);
V_DrawThinString((BASEVIDWIDTH - pagenamewidth)/2, 12, 0, pagename);
}
V_DrawCharacter((BASEVIDWIDTH - pagenamewidth)/2 - 10 - (skullAnimCounter/5), 12,
'\x1C', false); // left arrow
V_DrawCharacter((BASEVIDWIDTH + pagenamewidth)/2 + 2 + (skullAnimCounter/5), 12,
'\x1D', false); // right arrow
}
beststr[0] = 0; beststr[0] = 0;
V_DrawThinString(20, 22, highlightflags, "Total Play Time:"); V_DrawThinString(20, 30, highlightflags, "Total Play Time:");
besttime = G_TicsToHours(gamedata->totalplaytime); besttime = G_TicsToHours(gamedata->totalplaytime);
if (besttime) if (besttime)
{ {
@ -6210,10 +6363,10 @@ void M_DrawStatistics(void)
} }
besttime = G_TicsToSeconds(gamedata->totalplaytime); besttime = G_TicsToSeconds(gamedata->totalplaytime);
strcat(beststr, va("%i second%s", besttime, (besttime == 1 ? "" : "s"))); strcat(beststr, va("%i second%s", besttime, (besttime == 1 ? "" : "s")));
V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 22, 0, beststr); V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 30, 0, beststr);
beststr[0] = 0; beststr[0] = 0;
V_DrawThinString(20, 32, highlightflags, "Total Rings:"); V_DrawThinString(20, 40, highlightflags, "Total Rings:");
if (gamedata->totalrings > GDMAX_RINGS) if (gamedata->totalrings > GDMAX_RINGS)
{ {
sprintf(beststr, "%c999,999,999+", '\x82'); sprintf(beststr, "%c999,999,999+", '\x82');
@ -6230,10 +6383,10 @@ void M_DrawStatistics(void)
{ {
sprintf(beststr, "%u", gamedata->totalrings); sprintf(beststr, "%u", gamedata->totalrings);
} }
V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 32, 0, va("%s collected", beststr)); V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 40, 0, va("%s collected", beststr));
beststr[0] = 0; beststr[0] = 0;
V_DrawThinString(20, 42, highlightflags, "Total Rounds:"); V_DrawThinString(20, 50, highlightflags, "Total Rounds:");
strcat(beststr, va("%u Race", gamedata->roundsplayed[GDGT_RACE])); strcat(beststr, va("%u Race", gamedata->roundsplayed[GDGT_RACE]));
@ -6254,23 +6407,9 @@ void M_DrawStatistics(void)
strcat(beststr, va(", %u Custom", gamedata->roundsplayed[GDGT_CUSTOM])); strcat(beststr, va(", %u Custom", gamedata->roundsplayed[GDGT_CUSTOM]));
} }
V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 42, 0, beststr); V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 50, 0, beststr);
switch (statisticsmenu.page)
{
case statisticspage_maps:
{
M_DrawStatsMaps();
break;
}
default:
break;
}
} }
#undef STATSSTEP
static INT32 M_WrongWarpFallingHelper(INT32 y, INT32 falltime) static INT32 M_WrongWarpFallingHelper(INT32 y, INT32 falltime)
{ {
if (wrongwarp.ticker < falltime) if (wrongwarp.ticker < falltime)

View file

@ -5,6 +5,7 @@
#include "../z_zone.h" #include "../z_zone.h"
#include "../m_cond.h" // Condition Sets #include "../m_cond.h" // Condition Sets
#include "../s_sound.h" #include "../s_sound.h"
#include "../r_skins.h"
struct statisticsmenu_s statisticsmenu; struct statisticsmenu_s statisticsmenu;
@ -88,6 +89,97 @@ static void M_StatisticsMaps(void)
} }
} }
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] = NEXTMAP_INVALID;
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_StatisticsPageInit(void) static void M_StatisticsPageInit(void)
{ {
switch (statisticsmenu.page) switch (statisticsmenu.page)
@ -98,20 +190,9 @@ static void M_StatisticsPageInit(void)
break; break;
} }
default: case statisticspage_chars:
break;
}
}
static void M_StatisticsPageClear(void)
{
switch (statisticsmenu.page)
{
case statisticspage_maps:
{ {
Z_Free(statisticsmenu.maplist); M_StatisticsChars();
statisticsmenu.maplist = NULL;
break; break;
} }
@ -120,10 +201,23 @@ static void M_StatisticsPageClear(void)
} }
} }
static void M_StatisticsPageClear(void)
{
if (statisticsmenu.maplist != NULL)
{
Z_Free(statisticsmenu.maplist);
statisticsmenu.maplist = NULL;
}
}
void M_Statistics(INT32 choice) void M_Statistics(INT32 choice)
{ {
(void)choice; (void)choice;
statisticsmenu.gotmedals = M_CountMedals(false, false);
statisticsmenu.nummedals = M_CountMedals(true, false);
statisticsmenu.numextramedals = M_CountMedals(true, true);
statisticsmenu.page = statisticspage_basic; statisticsmenu.page = statisticspage_basic;
M_StatisticsPageInit(); M_StatisticsPageInit();
@ -169,8 +263,8 @@ boolean M_StatisticsInputs(INT32 ch)
return true; return true;
} }
if (statisticsmenu.page != statisticspage_maps) if (statisticsmenu.maplist == NULL)
return true; // temporary return true;
if (M_MenuExtraPressed(pid)) if (M_MenuExtraPressed(pid))
{ {