diff --git a/src/doomdef.h b/src/doomdef.h index f9f7b65f8..69faba1a0 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -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)) diff --git a/src/doomstat.h b/src/doomstat.h index 42420d911..20f0531ba 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -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 diff --git a/src/g_game.c b/src/g_game.c index 1c4d23a80..96d57b7f5 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -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]++; } diff --git a/src/g_gamedata.cpp b/src/g_gamedata.cpp index fc1bdd277..e080d5c5e 100644 --- a/src/g_gamedata.cpp +++ b/src/g_gamedata.cpp @@ -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]) { diff --git a/src/g_gamedata.h b/src/g_gamedata.h index c5ba7bdcd..3c21ea5c2 100644 --- a/src/g_gamedata.h +++ b/src/g_gamedata.h @@ -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 diff --git a/src/k_kart.c b/src/k_kart.c index 7fc2d5aea..f96710a29 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -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++; + } } } } diff --git a/src/k_menu.h b/src/k_menu.h index 47a92f58b..d4e214878 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -1434,6 +1434,7 @@ typedef enum statisticspage_chars = 0, statisticspage_gp, statisticspage_maps, + statisticspage_time, statisticspage_max } statisticspage_t; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index f3c9ead4e..68c532f60 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -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; diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 618840024..b8155c46e 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -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(); diff --git a/src/k_profiles.cpp b/src/k_profiles.cpp index 1206c90c8..2d29e8c82 100644 --- a/src/k_profiles.cpp +++ b/src/k_profiles.cpp @@ -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; diff --git a/src/k_profiles.h b/src/k_profiles.h index 1bbb48d6e..0d1c922bd 100644 --- a/src/k_profiles.h +++ b/src/k_profiles.h @@ -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 diff --git a/src/m_cond.c b/src/m_cond.c index 860bad128..51b28614b 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -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; +} + diff --git a/src/m_cond.h b/src/m_cond.h index 42e7184a9..90c219c39 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -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" diff --git a/src/menus/CMakeLists.txt b/src/menus/CMakeLists.txt index be72a4996..80d3c261e 100644 --- a/src/menus/CMakeLists.txt +++ b/src/menus/CMakeLists.txt @@ -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 diff --git a/src/menus/extras-challenges.c b/src/menus/extras-challenges.c index dfc0bd69e..dc488f953 100644 --- a/src/menus/extras-challenges.c +++ b/src/menus/extras-challenges.c @@ -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, diff --git a/src/menus/extras-statistics.c b/src/menus/extras-statistics.cpp similarity index 88% rename from src/menus/extras-statistics.c rename to src/menus/extras-statistics.cpp index 005c6e43f..75c6821b1 100644 --- a/src/menus/extras-statistics.c +++ b/src/menus/extras-statistics.cpp @@ -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(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(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(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(statisticsmenu.page) + + static_cast(statisticspage_max) + ( (menucmd[pid].dpad_lr > 0) ? 1 : -1 ); - statisticsmenu.page %= statisticspage_max; + newpage %= static_cast(statisticspage_max); + + statisticsmenu.page = static_cast(newpage); M_StatisticsPageInit(); diff --git a/src/p_tick.c b/src/p_tick.c index ae753dea8..d00b08549 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -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 diff --git a/src/p_user.c b/src/p_user.c index e4bdd8a3b..ecfe54dee 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -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++; } }