Merge branch 'throw-more-dots' into 'master'

Add a bunch more statistics

See merge request KartKrew/Kart!1973
This commit is contained in:
toaster 2024-04-06 22:08:30 +00:00
commit 5b466527e8
18 changed files with 493 additions and 57 deletions

View file

@ -590,6 +590,16 @@ UINT32 quickncasehash (const char *p, size_t n)
return x;
}
// m_cond, doomstat
typedef enum {
GDGT_RACE,
GDGT_BATTLE,
GDGT_PRISONS,
GDGT_SPECIAL,
GDGT_CUSTOM,
GDGT_MAX
} roundsplayed_t;
#ifndef __cplusplus
#ifndef min // Double-Check with WATTCP-32's cdefs.h
#define min(x, y) (((x) < (y)) ? (x) : (y))

View file

@ -122,6 +122,10 @@ extern preciptype_t curWeather;
struct skinrecord_t
{
UINT32 wins;
UINT32 rounds;
UINT32 timeplayed;
UINT32 modetimeplayed[GDGT_MAX];
UINT32 tumbletime;
};
struct unloaded_skin_t
@ -161,6 +165,13 @@ struct recorddata_t
UINT8 mapvisited;
recordtimes_t timeattack; ///< Best times for Time Attack
recordtimes_t spbattack; ///< Best times for SPB Attack
UINT32 timeplayed;
UINT32 netgametimeplayed;
UINT32 modetimeplayed[GDGT_MAX];
UINT32 timeattacktimeplayed;
UINT32 spbattacktimeplayed;
UINT32 rounds;
UINT32 wins;
};
#define KARTSPEED_AUTO -1

View file

@ -4516,14 +4516,7 @@ static void G_DoCompleted(void)
{
if (gametype != GT_TUTORIAL)
{
UINT8 roundtype = GDGT_CUSTOM;
if (gametype == GT_RACE)
roundtype = GDGT_RACE;
else if (gametype == GT_BATTLE)
roundtype = (battleprisons ? GDGT_PRISONS : GDGT_BATTLE);
else if (gametype == GT_SPECIAL || gametype == GT_VERSUS)
roundtype = GDGT_SPECIAL;
UINT8 roundtype = M_GameDataGameType(gametype, battleprisons);
gamedata->roundsplayed[roundtype]++;
}

View file

@ -46,6 +46,16 @@ void srb2::save_ng_gamedata()
GamedataJson ng {};
ng.playtime.total = gamedata->totalplaytime;
ng.playtime.netgame = gamedata->totalnetgametime;
ng.playtime.timeattack = gamedata->timeattackingtotaltime;
ng.playtime.spbattack = gamedata->spbattackingtotaltime;
ng.playtime.race = gamedata->modeplaytime[GDGT_RACE];
ng.playtime.battle = gamedata->modeplaytime[GDGT_BATTLE];
ng.playtime.prisons = gamedata->modeplaytime[GDGT_PRISONS];
ng.playtime.special = gamedata->modeplaytime[GDGT_SPECIAL];
ng.playtime.custom = gamedata->modeplaytime[GDGT_CUSTOM];
ng.playtime.menus = gamedata->totalmenutime;
ng.playtime.statistics = gamedata->totaltimestaringatstatistics;
ng.rings.total = gamedata->totalrings;
ng.playtime.tumble = gamedata->totaltumbletime;
ng.rounds.race = gamedata->roundsplayed[GDGT_RACE];
@ -103,8 +113,18 @@ void srb2::save_ng_gamedata()
for (int i = 0; i < numskins; i++)
{
srb2::GamedataSkinJson skin {};
std::string name = std::string(skins[i].name);
skin.records.wins = skins[i].records.wins;
skin_t& memskin = skins[i];
std::string name = std::string(memskin.name);
skin.records.wins = memskin.records.wins;
skin.records.rounds = memskin.records.rounds;
skin.records.time.total = memskin.records.timeplayed;
skin.records.time.race = memskin.records.modetimeplayed[GDGT_RACE];
skin.records.time.battle = memskin.records.modetimeplayed[GDGT_BATTLE];
skin.records.time.prisons = memskin.records.modetimeplayed[GDGT_PRISONS];
skin.records.time.special = memskin.records.modetimeplayed[GDGT_SPECIAL];
skin.records.time.custom = memskin.records.modetimeplayed[GDGT_CUSTOM];
skin.records.time.tumble = memskin.records.tumbletime;
ng.skins[name] = std::move(skin);
}
for (auto unloadedskin = unloadedskins; unloadedskin; unloadedskin = unloadedskin->next)
@ -127,6 +147,15 @@ void srb2::save_ng_gamedata()
map.stats.timeattack.bestlap = mapheaderinfo[i]->records.timeattack.lap;
map.stats.spbattack.besttime = mapheaderinfo[i]->records.spbattack.time;
map.stats.spbattack.bestlap = mapheaderinfo[i]->records.spbattack.lap;
map.stats.time.total = mapheaderinfo[i]->records.timeplayed;
map.stats.time.netgame = mapheaderinfo[i]->records.netgametimeplayed;
map.stats.time.race = mapheaderinfo[i]->records.modetimeplayed[GDGT_RACE];
map.stats.time.battle = mapheaderinfo[i]->records.modetimeplayed[GDGT_BATTLE];
map.stats.time.prisons = mapheaderinfo[i]->records.modetimeplayed[GDGT_PRISONS];
map.stats.time.special = mapheaderinfo[i]->records.modetimeplayed[GDGT_SPECIAL];
map.stats.time.custom = mapheaderinfo[i]->records.modetimeplayed[GDGT_CUSTOM];
map.stats.time.timeattack = mapheaderinfo[i]->records.timeattacktimeplayed;
map.stats.time.spbattack = mapheaderinfo[i]->records.spbattacktimeplayed;
ng.maps[lumpname] = std::move(map);
}
for (auto unloadedmap = unloadedmapheaders; unloadedmap; unloadedmap = unloadedmap->next)
@ -142,6 +171,15 @@ void srb2::save_ng_gamedata()
map.stats.timeattack.bestlap = unloadedmap->records.timeattack.lap;
map.stats.spbattack.besttime = unloadedmap->records.spbattack.time;
map.stats.spbattack.bestlap = unloadedmap->records.spbattack.lap;
map.stats.time.total = unloadedmap->records.timeplayed;
map.stats.time.netgame = unloadedmap->records.netgametimeplayed;
map.stats.time.race = unloadedmap->records.modetimeplayed[GDGT_RACE];
map.stats.time.battle = unloadedmap->records.modetimeplayed[GDGT_BATTLE];
map.stats.time.prisons = unloadedmap->records.modetimeplayed[GDGT_PRISONS];
map.stats.time.special = unloadedmap->records.modetimeplayed[GDGT_SPECIAL];
map.stats.time.custom = unloadedmap->records.modetimeplayed[GDGT_CUSTOM];
map.stats.time.timeattack = unloadedmap->records.timeattacktimeplayed;
map.stats.time.spbattack = unloadedmap->records.spbattacktimeplayed;
ng.maps[lumpname] = std::move(map);
}
for (int i = 0; i < gamedata->numspraycans; i++)
@ -412,6 +450,16 @@ void srb2::load_ng_gamedata()
gamedata->evercrashed = dirty;
gamedata->totalplaytime = js.playtime.total;
gamedata->totalnetgametime = js.playtime.netgame;
gamedata->timeattackingtotaltime = js.playtime.timeattack;
gamedata->spbattackingtotaltime = js.playtime.spbattack;
gamedata->modeplaytime[GDGT_RACE] = js.playtime.race;
gamedata->modeplaytime[GDGT_BATTLE] = js.playtime.battle;
gamedata->modeplaytime[GDGT_PRISONS] = js.playtime.prisons;
gamedata->modeplaytime[GDGT_SPECIAL] = js.playtime.special;
gamedata->modeplaytime[GDGT_CUSTOM] = js.playtime.custom;
gamedata->totalmenutime = js.playtime.menus;
gamedata->totaltimestaringatstatistics = js.playtime.statistics;
gamedata->totalrings = js.rings.total;
gamedata->totaltumbletime = js.playtime.tumble;
gamedata->roundsplayed[GDGT_RACE] = js.rounds.race;
@ -504,7 +552,23 @@ void srb2::load_ng_gamedata()
{
INT32 skin = R_SkinAvailableEx(skinpair.first.c_str(), false);
skinrecord_t dummyrecord {};
dummyrecord.wins = skinpair.second.records.wins;
dummyrecord.rounds = skinpair.second.records.rounds;
#ifdef DEVELOP
// Only good for testing, not for active play... cheaters never prosper!
if (dummyrecord.rounds < dummyrecord.wins)
dummyrecord.rounds = dummyrecord.wins;
#endif
dummyrecord.timeplayed = skinpair.second.records.time.total;
dummyrecord.modetimeplayed[GDGT_RACE] = skinpair.second.records.time.race;
dummyrecord.modetimeplayed[GDGT_BATTLE] = skinpair.second.records.time.battle;
dummyrecord.modetimeplayed[GDGT_PRISONS] = skinpair.second.records.time.prisons;
dummyrecord.modetimeplayed[GDGT_SPECIAL] = skinpair.second.records.time.special;
dummyrecord.modetimeplayed[GDGT_CUSTOM] = skinpair.second.records.time.custom;
dummyrecord.tumbletime = skinpair.second.records.time.tumble;
if (skin != -1)
{
@ -547,6 +611,15 @@ void srb2::load_ng_gamedata()
dummyrecord.timeattack.lap = mappair.second.stats.timeattack.bestlap;
dummyrecord.spbattack.time = mappair.second.stats.spbattack.besttime;
dummyrecord.spbattack.lap = mappair.second.stats.spbattack.bestlap;
dummyrecord.timeplayed = mappair.second.stats.time.total;
dummyrecord.netgametimeplayed = mappair.second.stats.time.netgame;
dummyrecord.modetimeplayed[GDGT_RACE] = mappair.second.stats.time.race;
dummyrecord.modetimeplayed[GDGT_BATTLE] = mappair.second.stats.time.battle;
dummyrecord.modetimeplayed[GDGT_PRISONS] = mappair.second.stats.time.prisons;
dummyrecord.modetimeplayed[GDGT_SPECIAL] = mappair.second.stats.time.special;
dummyrecord.modetimeplayed[GDGT_CUSTOM] = mappair.second.stats.time.custom;
dummyrecord.timeattacktimeplayed = mappair.second.stats.time.timeattack;
dummyrecord.spbattacktimeplayed = mappair.second.stats.time.spbattack;
if (mapnum < nummapheaders && mapheaderinfo[mapnum])
{

View file

@ -27,9 +27,33 @@ namespace srb2
struct GamedataPlaytimeJson final
{
uint32_t total;
uint32_t netgame;
uint32_t timeattack;
uint32_t spbattack;
uint32_t race;
uint32_t battle;
uint32_t prisons;
uint32_t special;
uint32_t custom;
uint32_t menus;
uint32_t statistics;
uint32_t tumble;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataPlaytimeJson, total, tumble)
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(
GamedataPlaytimeJson,
total,
netgame,
timeattack,
spbattack,
race,
battle,
prisons,
special,
custom,
menus,
statistics,
tumble
)
};
struct GamedataRingsJson final
@ -104,11 +128,40 @@ struct GamedataChallengeGridJson final
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataChallengeGridJson, width, grid)
};
struct GamedataSkinRecordsPlaytimeJson final
{
uint32_t total;
uint32_t race;
uint32_t battle;
uint32_t prisons;
uint32_t special;
uint32_t custom;
uint32_t tumble;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(
GamedataSkinRecordsPlaytimeJson,
total,
race,
battle,
prisons,
special,
custom,
tumble
)
};
struct GamedataSkinRecordsJson final
{
uint32_t wins;
uint32_t rounds;
GamedataSkinRecordsPlaytimeJson time;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataSkinRecordsJson, wins)
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(
GamedataSkinRecordsJson,
wins,
rounds,
time
)
};
struct GamedataSkinJson final
@ -145,12 +198,44 @@ struct GamedataMapStatsSpbAttackJson final
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataMapStatsSpbAttackJson, besttime, bestlap)
};
struct GamedataMapStatsPlaytimeJson final
{
uint32_t total;
uint32_t netgame;
uint32_t race;
uint32_t battle;
uint32_t prisons;
uint32_t special;
uint32_t custom;
uint32_t timeattack;
uint32_t spbattack;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(
GamedataMapStatsPlaytimeJson,
total,
netgame,
race,
battle,
prisons,
special,
custom,
timeattack,
spbattack
)
};
struct GamedataMapStatsJson final
{
GamedataMapStatsTimeAttackJson timeattack;
GamedataMapStatsSpbAttackJson spbattack;
GamedataMapStatsPlaytimeJson time;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataMapStatsJson, timeattack, spbattack)
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(
GamedataMapStatsJson,
timeattack,
spbattack,
time
)
};
struct GamedataMapJson final

View file

@ -8207,11 +8207,19 @@ void K_KartPlayerHUDUpdate(player_t *player)
else
player->karthud[khud_finish] = 0;
// Tumble time stat update
if (demo.playback == false && P_IsMachineLocalPlayer(player) == true)
{
if (player->tumbleBounces != 0 && gamedata->totaltumbletime != UINT32_MAX)
{
gamedata->totaltumbletime++;
if (player->skin >= 0 && player->skin < numskins)
{
skin_t *playerskin;
playerskin = &skins[player->skin];
playerskin->records.tumbletime++;
}
}
}
}

View file

@ -1434,6 +1434,7 @@ typedef enum
statisticspage_chars = 0,
statisticspage_gp,
statisticspage_maps,
statisticspage_time,
statisticspage_max
} statisticspage_t;

View file

@ -8310,7 +8310,7 @@ static void M_DrawStatsChars(void)
i = -1;
V_DrawThinString(20, y - 10, highlightflags, "CHARACTER");
V_DrawRightAlignedThinString(BASEVIDWIDTH/2 + 34, y - 10, highlightflags, "WINS");
V_DrawRightAlignedThinString(BASEVIDWIDTH/2 + 34, y - 10, highlightflags, "WINS/ROUNDS");
while ((skin = statisticsmenu.maplist[++i]) < numskins)
{
@ -8328,7 +8328,7 @@ static void M_DrawStatsChars(void)
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));
V_DrawRightAlignedThinString(BASEVIDWIDTH/2 + 30, y+3, 0, va("%d/%d", skins[skin].records.wins, skins[skin].records.rounds));
y += STATSSTEP;
@ -8474,10 +8474,103 @@ bottomarrow:
#undef STATSSTEP
static void M_GetStatsTime(char *beststr, UINT32 totaltime)
{
beststr[0] = 0;
boolean showallsubsequent = false;
UINT32 besttime = G_TicsToHours(totaltime);
if (besttime)
{
showallsubsequent = true;
if (besttime >= 24)
{
strcat(beststr, va("%u day%s, ", besttime/24, (besttime < 48 ? "" : "s")));
besttime %= 24;
}
strcat(beststr, va("%u hour%s, ", besttime, (besttime == 1 ? "" : "s")));
}
besttime = G_TicsToMinutes(totaltime, false);
if (besttime || showallsubsequent)
{
showallsubsequent = true;
strcat(beststr, va("%u minute%s, ", besttime, (besttime == 1 ? "" : "s")));
}
besttime = G_TicsToSeconds(totaltime);
strcat(beststr, va("%i second%s", besttime, (besttime == 1 ? "" : "s")));
}
static void M_DrawStatsTimeTracked(void)
{
INT32 y = 70;
char beststr[256];
#define DISPLAYAMODE(str, besttime) \
{ \
V_DrawThinString(24, y, 0, str); \
M_GetStatsTime(beststr, besttime); \
V_DrawRightAlignedThinString(BASEVIDWIDTH-24, y, 0, beststr); \
y += 10; \
}
DISPLAYAMODE("Race Mode", gamedata->modeplaytime[GDGT_RACE]);
if (gamedata->roundsplayed[GDGT_PRISONS])
{
DISPLAYAMODE("Prison Break", gamedata->modeplaytime[GDGT_PRISONS]);
}
DISPLAYAMODE("Battle Mode", gamedata->modeplaytime[GDGT_BATTLE]);
if (gamedata->roundsplayed[GDGT_SPECIAL])
{
DISPLAYAMODE("Special Mode", gamedata->modeplaytime[GDGT_SPECIAL]);
}
if (gamedata->roundsplayed[GDGT_CUSTOM])
{
DISPLAYAMODE("All Custom Modes", gamedata->modeplaytime[GDGT_CUSTOM]);
}
if (M_SecretUnlocked(SECRET_ONLINE, true))
{
y += 2;
DISPLAYAMODE("Playing Online", gamedata->totalnetgametime);
}
if (M_SecretUnlocked(SECRET_TIMEATTACK, true)
|| M_SecretUnlocked(SECRET_PRISONBREAK, true)
|| M_SecretUnlocked(SECRET_SPECIALATTACK, true))
{
y += 2;
DISPLAYAMODE("Time Attack Modes", gamedata->timeattackingtotaltime);
if (M_SecretUnlocked(SECRET_SPBATTACK, true))
{
DISPLAYAMODE(" (SPB Attack)", gamedata->spbattackingtotaltime);
}
}
if (gamedata->totaltumbletime)
{
y += 2;
DISPLAYAMODE("Tumbling through the air", gamedata->totaltumbletime);
}
y += 2;
DISPLAYAMODE("On Menus", gamedata->totalmenutime);
DISPLAYAMODE(" (staring at this screen)", gamedata->totaltimestaringatstatistics);
}
void M_DrawStatistics(void)
{
char beststr[256];
tic_t besttime = 0;
{
const char *pagename = NULL;
@ -8487,7 +8580,6 @@ void M_DrawStatistics(void)
switch (statisticsmenu.page)
{
case statisticspage_gp:
{
pagename = gamedata->everseenspecial
@ -8511,6 +8603,13 @@ void M_DrawStatistics(void)
break;
}
case statisticspage_time:
{
pagename = "TIME TRACKED";
M_DrawStatsTimeTracked();
break;
}
default:
break;
}
@ -8528,26 +8627,9 @@ void M_DrawStatistics(void)
0, "\x1D"); // right arrow
}
beststr[0] = 0;
V_DrawThinString(20, 30, highlightflags, "Total Play Time:");
besttime = G_TicsToHours(gamedata->totalplaytime);
if (besttime)
{
if (besttime >= 24)
{
strcat(beststr, va("%u day%s, ", besttime/24, (besttime < 48 ? "" : "s")));
besttime %= 24;
}
strcat(beststr, va("%u hour%s, ", besttime, (besttime == 1 ? "" : "s")));
}
besttime = G_TicsToMinutes(gamedata->totalplaytime, false);
if (besttime)
{
strcat(beststr, va("%u minute%s, ", besttime, (besttime == 1 ? "" : "s")));
}
besttime = G_TicsToSeconds(gamedata->totalplaytime);
strcat(beststr, va("%i second%s", besttime, (besttime == 1 ? "" : "s")));
M_GetStatsTime(beststr, gamedata->totalplaytime);
V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 30, 0, beststr);
beststr[0] = 0;

View file

@ -1429,6 +1429,12 @@ void M_Ticker(void)
skullAnimCounter = 8;
}
if (!Playing())
{
// Anything in M_Ticker that isn't actively playing is considered "in menus" for time tracking
gamedata->totalmenutime++;
}
if (!Playing() && !M_GameTrulyStarted())
{
M_GonerBGTick();

View file

@ -88,6 +88,7 @@ profile_t* PR_MakeProfile(
memcpy(newprofile->controls, controlarray, sizeof(newprofile->controls));
newprofile->wins = 0;
newprofile->rounds = 0;
return newprofile;
}
@ -293,6 +294,7 @@ void PR_SaveProfiles(void)
jsonprof.followercolorname = std::string(skincolors[cprof->followercolor].name);
}
jsonprof.records.wins = cprof->wins;
jsonprof.records.rounds = cprof->rounds;
jsonprof.preferences.kickstartaccel = cprof->kickstartaccel;
jsonprof.preferences.autoroulette = cprof->autoroulette;
jsonprof.preferences.litesteer = cprof->litesteer;
@ -458,6 +460,7 @@ void PR_LoadProfiles(void)
}
newprof->wins = jsprof.records.wins;
newprof->rounds = jsprof.records.rounds;
newprof->kickstartaccel = jsprof.preferences.kickstartaccel;
newprof->autoroulette = jsprof.preferences.autoroulette;
newprof->litesteer = jsprof.preferences.litesteer;

View file

@ -36,8 +36,9 @@ namespace srb2
struct ProfileRecordsJson
{
uint32_t wins;
uint32_t rounds;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ProfileRecordsJson, wins)
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ProfileRecordsJson, wins, rounds)
};
struct ProfilePreferencesJson
@ -147,6 +148,7 @@ struct profile_t
UINT16 followercolor; // Follower color
UINT32 wins; // I win I win I win
UINT32 rounds; // I played I played I played
// Player-specific consvars.
// @TODO: List all of those

View file

@ -652,6 +652,13 @@ void M_ClearStats(void)
{
UINT8 i;
gamedata->totalplaytime = 0;
gamedata->totalnetgametime = 0;
gamedata->timeattackingtotaltime = 0;
gamedata->spbattackingtotaltime = 0;
for (i = 0; i < GDGT_MAX; ++i)
gamedata->modeplaytime[i] = 0;
gamedata->totalmenutime = 0;
gamedata->totaltimestaringatstatistics = 0;
gamedata->totalrings = 0;
gamedata->totaltumbletime = 0;
for (i = 0; i < GDGT_MAX; ++i)
@ -3823,3 +3830,17 @@ boolean M_UseAlternateTitleScreen(void)
extern consvar_t cv_alttitle;
return cv_alttitle.value && M_SecretUnlocked(SECRET_ALTTITLE, true);
}
INT32 M_GameDataGameType(INT32 lgametype, boolean lbattleprisons)
{
INT32 playtimemode = GDGT_CUSTOM;
if (lgametype == GT_RACE)
playtimemode = GDGT_RACE;
else if (lgametype == GT_BATTLE)
playtimemode = lbattleprisons ? GDGT_PRISONS : GDGT_BATTLE;
else if (lgametype == GT_SPECIAL || lgametype == GT_VERSUS)
playtimemode = GDGT_SPECIAL;
return playtimemode;
}

View file

@ -311,15 +311,6 @@ typedef enum {
GDGONER_DONE,
} gdgoner_t;
typedef enum {
GDGT_RACE,
GDGT_BATTLE,
GDGT_PRISONS,
GDGT_SPECIAL,
GDGT_CUSTOM,
GDGT_MAX
} roundsplayed_t;
struct candata_t
{
UINT16 col;
@ -369,6 +360,12 @@ struct gamedata_t
// PLAY TIME
UINT32 totalplaytime;
UINT32 totalnetgametime;
UINT32 timeattackingtotaltime;
UINT32 spbattackingtotaltime;
UINT32 modeplaytime[GDGT_MAX];
UINT32 totalmenutime;
UINT32 totaltimestaringatstatistics;
UINT32 roundsplayed[GDGT_MAX];
UINT32 totalrings;
UINT32 totaltumbletime;
@ -495,6 +492,7 @@ UINT16 M_EmblemMapNum(emblem_t *emblem);
#define M_Achieved(a) ((a) >= MAXCONDITIONSETS || gamedata->achieved[a])
boolean M_UseAlternateTitleScreen(void);
INT32 M_GameDataGameType(INT32 gametype, boolean battleprisons);
#ifdef __cplusplus
} // extern "C"

View file

@ -3,7 +3,7 @@ target_sources(SRB2SDL2 PRIVATE
extras-addons.c
extras-challenges.c
extras-egg-tv.cpp
extras-statistics.c
extras-statistics.cpp
extras-wrong.c
main-1.c
main-goner.cpp

View file

@ -23,6 +23,12 @@
extern consvar_t cv_debugchallenges;
#endif
static void M_StatisticsTicker(void)
{
// the funny
gamedata->totaltimestaringatstatistics++;
}
menuitem_t MISC_ChallengesStatsDummyMenu[] =
{
{IT_STRING | IT_CALL, "Back", NULL, NULL, {.routine = M_GoBack}, 0, 0},
@ -60,7 +66,7 @@ menu_t MISC_StatisticsDef = {
98, 0,
M_DrawStatistics,
M_DrawExtrasBack,
NULL,
M_StatisticsTicker,
NULL,
NULL,
M_StatisticsInputs,

View file

@ -68,7 +68,7 @@ static void M_StatisticsMaps(void)
UINT16 i;
boolean headerexists;
statisticsmenu.maplist = Z_Malloc(sizeof(UINT16) * (nummapheaders+1 + numkartcupheaders), PU_STATIC, NULL);
statisticsmenu.maplist = static_cast<UINT16*>(Z_Malloc(sizeof(UINT16) * (nummapheaders+1 + numkartcupheaders), PU_STATIC, NULL));
statisticsmenu.nummaps = 0;
// Cups
@ -119,7 +119,7 @@ static void M_StatisticsChars(void)
{
UINT16 i;
statisticsmenu.maplist = Z_Malloc(sizeof(UINT16) * (1 + numskins), PU_STATIC, NULL);
statisticsmenu.maplist = static_cast<UINT16*>(Z_Malloc(sizeof(UINT16) * (1 + numskins), PU_STATIC, NULL));
statisticsmenu.nummaps = 0;
UINT32 beststat = 0;
@ -166,6 +166,19 @@ static void M_StatisticsChars(void)
statisticsmenu.maplist[statisticsmenu.nummaps] = MAXSKINS;
std::sort(
statisticsmenu.maplist,
statisticsmenu.maplist + statisticsmenu.nummaps,
[](UINT16 a, UINT16 b) {
if (skins[a].records.rounds > skins[b].records.rounds)
return true;
if (skins[a].records.rounds != skins[b].records.rounds)
return false;
// Stable for skin ID
return (a < b);
}
);
statisticsmenu.location = 0;
statisticsmenu.maxscroll = statisticsmenu.nummaps - 6;
@ -208,7 +221,7 @@ static void M_StatisticsChars(void)
static void M_StatisticsGP(void)
{
statisticsmenu.maplist = Z_Malloc(sizeof(UINT16) * (1 + numkartcupheaders), PU_STATIC, NULL);
statisticsmenu.maplist = static_cast<UINT16*>(Z_Malloc(sizeof(UINT16) * (1 + numkartcupheaders), PU_STATIC, NULL));
statisticsmenu.nummaps = 0;
cupheader_t *cup;
@ -255,7 +268,10 @@ static void M_StatisticsPageInit(void)
}
default:
{
statisticsmenu.maplist = NULL;
break;
}
}
}
@ -302,15 +318,17 @@ boolean M_StatisticsInputs(INT32 ch)
{
M_StatisticsPageClear();
statisticsmenu.page +=
statisticspage_max
int newpage = static_cast<int>(statisticsmenu.page)
+ static_cast<int>(statisticspage_max)
+ (
(menucmd[pid].dpad_lr > 0)
? 1
: -1
);
statisticsmenu.page %= statisticspage_max;
newpage %= static_cast<int>(statisticspage_max);
statisticsmenu.page = static_cast<statisticspage_t>(newpage);
M_StatisticsPageInit();

View file

@ -941,9 +941,115 @@ void P_Ticker(boolean run)
if (gamedata && gamestate == GS_LEVEL && !demo.playback)
{
mapheader_t *mapheader;
mapheader = mapheaderinfo[gamemap - 1];
// Keep track of how long they've been playing!
gamedata->totalplaytime++;
// Map playtime
if (mapheader)
{
mapheader->records.timeplayed++;
}
// Netgame total time
if (netgame)
{
gamedata->totalnetgametime++;
if (mapheader)
{
mapheader->records.netgametimeplayed++;
}
}
// Per-skin total playtime for all machine-local players
for (i = 0; i < MAXPLAYERS; i++)
{
skin_t *playerskin;
if (!P_IsMachineLocalPlayer(&players[i]))
{
continue;
}
if (!(playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo)))
{
continue;
}
if (players[i].skin >= numskins)
{
continue;
}
playerskin = &skins[players[i].skin];
playerskin->records.timeplayed++;
}
if (gametype != GT_TUTORIAL)
{
INT32 mode = M_GameDataGameType(gametype, battleprisons);
// Gamedata mode playtime
if (mode >= 0 && mode < GDGT_MAX)
{
gamedata->modeplaytime[mode]++;
if (mapheader)
{
mapheader->records.modetimeplayed[mode]++;
}
}
// Attacking mode playtime
if (modeattacking != ATTACKING_NONE)
{
if (encoremode) // ((modeattacking & ATTACKING_SPB) != 0)
{
gamedata->spbattackingtotaltime++;
if (mapheader)
{
mapheader->records.spbattacktimeplayed++;
}
}
//else
{
gamedata->timeattackingtotaltime++;
if (mapheader)
{
mapheader->records.timeattacktimeplayed++;
}
}
}
// Per-skin mode playtime
for (i = 0; i < MAXPLAYERS; i++)
{
skin_t *playerskin;
if (!P_IsMachineLocalPlayer(&players[i]))
{
continue;
}
if (!(playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo)))
{
continue;
}
if (players[i].skin >= numskins)
{
continue;
}
playerskin = &skins[players[i].skin];
playerskin->records.modetimeplayed[mode]++;
}
}
// TODO would this be laggy with more conditions in play...
if (
(leveltime > introtime

View file

@ -1315,18 +1315,31 @@ void P_DoPlayerExit(player_t *player, pflags_t flags)
G_UpdateRecords();
}
if (!losing)
// Profile and skin record updates
if (P_IsMachineLocalPlayer(player))
{
profile_t *pr = PR_GetPlayerProfile(player);
// Profile records
if (pr != NULL)
{
pr->wins++;
if (!losing)
{
pr->wins++;
}
pr->rounds++;
PR_SaveProfiles();
}
if (P_IsMachineLocalPlayer(player) && player->skin < numskins)
// Skin records (saved to gamedata)
if (player->skin < numskins)
{
skins[player->skin].records.wins++;
skin_t *playerskin = &skins[player->skin];
if (!losing)
{
playerskin->records.wins++;
}
playerskin->records.rounds++;
}
}