From 8b142ceb62530d659ecfb5b6f9f8ef6b81d191a9 Mon Sep 17 00:00:00 2001 From: NepDisk Date: Tue, 25 Feb 2025 14:00:10 -0500 Subject: [PATCH 01/19] Add kartstats https://github.com/Indev450/SRB2Kart-Saturn/commit/4942db2806bac1f097e5ce913c18a67ce73da907 --- src/Sourcefile | 1 + src/doomstat.h | 3 - src/g_game.c | 17 ++---- src/k_collide.c | 3 + src/k_kart.c | 3 + src/k_stats.c | 146 ++++++++++++++++++++++++++++++++++++++++++++++++ src/k_stats.h | 56 +++++++++++++++++++ src/m_cond.c | 5 +- src/m_menu.c | 13 ++--- src/m_random.c | 4 +- src/p_map.c | 2 +- src/p_tick.c | 4 +- 12 files changed, 229 insertions(+), 28 deletions(-) create mode 100644 src/k_stats.c create mode 100644 src/k_stats.h diff --git a/src/Sourcefile b/src/Sourcefile index 17a687d5d..dbea241ca 100644 --- a/src/Sourcefile +++ b/src/Sourcefile @@ -129,3 +129,4 @@ k_brightmap.c k_director.c k_follower.c k_mapuser.c +k_stats.c diff --git a/src/doomstat.h b/src/doomstat.h index e310eb549..1006d7ff6 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -557,9 +557,6 @@ struct tolinfo_t extern tolinfo_t TYPEOFLEVEL[NUMTOLNAMES]; extern UINT32 lastcustomtol; -extern tic_t totalplaytime; -extern UINT32 matchesplayed; - extern UINT8 stagefailed; // Emeralds stored as bits to throw savegame hackers off. diff --git a/src/g_game.c b/src/g_game.c index 740938394..e349d7844 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -53,6 +53,7 @@ // SRB2kart #include "k_kart.h" +#include "k_stats.h" // SRB2kart #include "k_battle.h" #include "k_pwrlv.h" #include "k_color.h" @@ -196,8 +197,6 @@ UINT32 tokenlist; // List of tokens collected boolean gottoken; // Did you get a token? Used for end of act INT32 tokenbits; // Used for setting token bits -tic_t totalplaytime; -UINT32 matchesplayed; // SRB2Kart boolean gamedataloaded = false; // Temporary holding place for nights data for the current map @@ -2198,7 +2197,7 @@ static inline void G_PlayerFinishLevel(INT32 player) { if (legitimateexit && !demo.playback && !mapreset) // (yes you're allowed to unlock stuff this way when the game is modified) { - matchesplayed++; + kartstats.matchesplayed++; if (M_UpdateUnlockablesAndExtraEmblems()) S_StartSound(NULL, sfx_ncitem); G_SaveGameData(); @@ -4044,6 +4043,7 @@ static void G_DoCompleted(void) if (!demo.playback) { nextmap = G_GetNextMap(true); + K_StatRound(); // Remember last map for when you come out of the special stage. if (!spec) @@ -4301,9 +4301,6 @@ void G_LoadGameData(void) G_ClearRecords(); // main and nights records M_ClearSecrets(); // emblems, unlocks, maps visited, etc - totalplaytime = 0; // total play time (separate from all) - matchesplayed = 0; // SRB2Kart: matches played & finished - for (i = 0; i < PWRLV_NUMTYPES; i++) // SRB2Kart: online rank system vspowerlevel[i] = PWRLVRECORD_START; @@ -4334,9 +4331,8 @@ void G_LoadGameData(void) P_SaveBufferFree(&save); I_Error("Game data is not for SRB2Kart v2.\nDelete %s(maybe in %s) and try again.", gamedatafilename, gdfolder); } - - totalplaytime = READUINT32(save.p); - matchesplayed = READUINT32(save.p); + + K_ReadStats(&save, true); for (i = 0; i < PWRLV_NUMTYPES; i++) { @@ -4486,8 +4482,7 @@ void G_SaveGameData(void) // Version test WRITEUINT32(save.p, GD_VERSIONCHECK); // 4 - WRITEUINT32(save.p, totalplaytime); // 4 - WRITEUINT32(save.p, matchesplayed); // 4 + K_WriteStats(&save, true); for (i = 0; i < PWRLV_NUMTYPES; i++) WRITEUINT16(save.p, vspowerlevel[i]); diff --git a/src/k_collide.c b/src/k_collide.c index fbdf2ed69..dff678846 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -2,6 +2,7 @@ /// \brief SRB2Kart item collision hooks #include "k_collide.h" +#include "k_stats.h" #include "doomstat.h" #include "doomtype.h" #include "p_mobj.h" @@ -800,6 +801,8 @@ boolean K_KitchenSinkCollide(mobj_t *t1, mobj_t *t2) if (t2->player->flashing > 0) return true; + K_StatPlayerSink(t1->player, P_MobjWasRemoved(t2->target) ? NULL : t2->target->player); + S_StartSound(NULL, sfx_cgot); //let all players hear it. HU_SetCEchoFlags(0); diff --git a/src/k_kart.c b/src/k_kart.c index e5cd00e1f..99b379b35 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -38,6 +38,8 @@ #include "m_cheat.h" // objectplacing #include "p_spec.h" + +#include "k_stats.h" #include "k_waypoint.h" #include "k_bot.h" #include "k_hud.h" @@ -3620,6 +3622,7 @@ void K_SpinPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, INT32 typ S_StartSound(player->mo, sfx_slip); } + K_StatPlayerHit(player, source ? source->player : NULL); player->spinouttimer = (3*TICRATE/2)+2; P_SetPlayerMobjState(player->mo, S_KART_SPINOUT); } diff --git a/src/k_stats.c b/src/k_stats.c new file mode 100644 index 000000000..4c126f700 --- /dev/null +++ b/src/k_stats.c @@ -0,0 +1,146 @@ +#include "k_stats.h" +#include "doomstat.h" +#include "g_game.h" +#include "byteptr.h" + +kartstats_t kartstats = {0}; + +void K_StatTicker(void) +{ + if (demo.playback) + return; + + kartstats.totalplaytime++; + + if (netgame) + kartstats.onlineplaytime++; + else if (modeattacking) + kartstats.raplaytime++; + + if (gametype == GT_RACE) + kartstats.raceplaytime++; + else if (gametype == GT_BATTLE) + kartstats.battleplaytime++; + + // Should this also track splitscreen players? + if (!players[consoleplayer].spectator) + { + player_t *p = &players[consoleplayer]; + + if (p->kartstuff[k_position] == spbplace) + kartstats.spbtargettime++; + + if (max(p->kartstuff[k_spinouttimer], p->kartstuff[k_wipeoutslow]) > 0) + kartstats.spinouttime++; + } +} + +void K_StatPlayerHit(player_t *victim, player_t *source) +{ + if (demo.playback) + return; + + if (victim == &players[consoleplayer]) + { + if (victim == source) + kartstats.selfhits++; + } + else if (source == &players[consoleplayer]) + kartstats.hits++; +} + +void K_StatPlayerSink(player_t *victim, player_t *source) +{ + if (demo.playback) + return; + + if (victim == &players[consoleplayer]) + kartstats.sinked++; + else if (source == &players[consoleplayer]) + kartstats.sinks++; +} + +void K_StatRound(void) +{ + if (demo.playback) + return; + + int numplayers = 0; + + for (int i = 0; i < MAXPLAYERS; ++i) + { + if (playeringame[i] && !players[i].spectator) + ++numplayers; + } + + if (numplayers > 1) + { + if (players[consoleplayer].kartstuff[k_position] == 1) + kartstats.totalwins++; + else if (players[consoleplayer].kartstuff[k_position] <= 3) // Should this check if there are more than 3 players in game? + kartstats.totalpodium++; + } +} + +void K_EraseStats(void) +{ + // The only field we want to remember + boolean vanilla = kartstats.vanilla; + + memset(&kartstats, 0, sizeof(kartstats_t)); + + kartstats.vanilla = vanilla; +} + +void K_ReadStats(savebuffer_t *save, boolean vanilla) +{ + memset(&kartstats, 0, sizeof(kartstats_t)); + + kartstats.vanilla = vanilla; + + kartstats.totalplaytime = READUINT32(save->p); + kartstats.matchesplayed = READUINT32(save->p); + + // Vanilla only has those 2 + if (vanilla) + return; + + // So many similar-looking read's... scary... + kartstats.raplaytime = READUINT32(save->p); + kartstats.onlineplaytime = READUINT32(save->p); + kartstats.raceplaytime = READUINT32(save->p); + kartstats.battleplaytime = READUINT32(save->p); + kartstats.spbtargettime = READUINT32(save->p); + kartstats.spinouttime = READUINT32(save->p); + kartstats.totalwins = READUINT32(save->p); + kartstats.totalpodium = READUINT32(save->p); + kartstats.hits = READUINT32(save->p); + kartstats.selfhits = READUINT32(save->p); + kartstats.sinks = READUINT32(save->p); + kartstats.sinked = READUINT32(save->p); + kartstats.respawns = READUINT32(save->p); +} + +void K_WriteStats(savebuffer_t *save, boolean vanilla) +{ + WRITEUINT32(save->p, kartstats.totalplaytime); + WRITEUINT32(save->p, kartstats.matchesplayed); + + // Vanilla only has those 2 + if (vanilla) + return; + + WRITEUINT32(save->p, kartstats.raplaytime); + WRITEUINT32(save->p, kartstats.onlineplaytime); + WRITEUINT32(save->p, kartstats.raceplaytime); + WRITEUINT32(save->p, kartstats.battleplaytime); + WRITEUINT32(save->p, kartstats.spbtargettime); + WRITEUINT32(save->p, kartstats.spinouttime); + WRITEUINT32(save->p, kartstats.totalwins); + WRITEUINT32(save->p, kartstats.totalpodium); + WRITEUINT32(save->p, kartstats.hits); + WRITEUINT32(save->p, kartstats.selfhits); + WRITEUINT32(save->p, kartstats.sinks); + WRITEUINT32(save->p, kartstats.sinked); + WRITEUINT32(save->p, kartstats.respawns); +} diff --git a/src/k_stats.h b/src/k_stats.h new file mode 100644 index 000000000..87a173653 --- /dev/null +++ b/src/k_stats.h @@ -0,0 +1,56 @@ +#ifndef __K_STATS__ +#define __K_STATS__ + +#include "doomtype.h" +#include "d_player.h" +#include "p_saveg.h" + +typedef struct kartstats_s { + // Remember if stats we loaded are vanilla ones or not + boolean vanilla; + + // Vanilla + tic_t totalplaytime; + UINT32 matchesplayed; + + tic_t raplaytime; + tic_t onlineplaytime; + tic_t raceplaytime; + tic_t battleplaytime; + tic_t spbtargettime; + tic_t spinouttime; + UINT32 totalwins; // 1st place + UINT32 totalpodium; // 2nd and 3rd place, *but not 1st* + UINT32 hits; + UINT32 selfhits; + UINT32 sinks; // Times hitting *others* by kitchen sink + UINT32 sinked; // Times *being hit* by kitchen sink + UINT32 respawns; +} kartstats_t; + +extern kartstats_t kartstats; + +// Note: All stat-tracking functions check for demo.playback and early return if its true + +// Update global stats such as total play time, etc +void K_StatTicker(void); + +// Update hit-related stats (PlayerSpin, PlayerSquish, PlayerExplode) +void K_StatPlayerHit(player_t *victim, player_t *source); + +// Separate from stuff above +void K_StatPlayerSink(player_t *victim, player_t *source); + +// Update round-related stats (such as matchesplayed, totalwins, etc) +void K_StatRound(void); + +void K_EraseStats(void); + +// If vanilla is true, only read vanilla-supported fields. Everything else is initialized to 0 +void K_ReadStats(savebuffer_t *save, boolean vanilla); + +// Same as above, except it just doesn't write non-vanilla fields if vanilla is true +void K_WriteStats(savebuffer_t *save, boolean vanilla); + + +#endif diff --git a/src/m_cond.c b/src/m_cond.c index 64b580191..205087653 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -21,6 +21,7 @@ #include "r_skins.h" // numskins #include "r_draw.h" // R_GetColorByName #include "k_pwrlv.h" +#include "k_stats.h" // Map triggers for linedef executors // 32 triggers, one bit each @@ -174,9 +175,9 @@ UINT8 M_CheckCondition(condition_t *cn) switch (cn->type) { case UC_PLAYTIME: // Requires total playing time >= x - return (totalplaytime >= (unsigned)cn->requirement); + return (kartstats.totalplaytime >= (unsigned)cn->requirement); case UC_MATCHESPLAYED: // Requires any level completed >= x times - return (matchesplayed >= (unsigned)cn->requirement); + return (kartstats.matchesplayed >= (unsigned)cn->requirement); case UC_POWERLEVEL: // Requires power level >= x on a certain gametype return (vspowerlevel[cn->extrainfo1] >= (unsigned)cn->requirement); case UC_GAMECLEAR: // Requires game beaten >= x times diff --git a/src/m_menu.c b/src/m_menu.c index 7597dee78..70933cb56 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -64,6 +64,7 @@ #include "k_hud.h" // SRB2kart #include "k_kart.h" // KartItemCVars #include "k_pwrlv.h" +#include "k_stats.h" // SRB2kart #include "d_player.h" // KITEM_ constants #include "k_color.h" #include "k_grandprix.h" @@ -7682,12 +7683,11 @@ static void M_DrawLevelStats(void) V_DrawString(20, 24, highlightflags, "Total Play Time:"); V_DrawCenteredString(BASEVIDWIDTH/2, 32, 0, va("%i hours, %i minutes, %i seconds", - G_TicsToHours(totalplaytime), - G_TicsToMinutes(totalplaytime, false), - G_TicsToSeconds(totalplaytime))); - + G_TicsToHours(kartstats.totalplaytime), + G_TicsToMinutes(kartstats.totalplaytime, false), + G_TicsToSeconds(kartstats.totalplaytime))); V_DrawString(20, 42, highlightflags, "Total Matches:"); - V_DrawRightAlignedString(BASEVIDWIDTH-16, 42, 0, va("%i played", matchesplayed)); + V_DrawRightAlignedString(BASEVIDWIDTH-16, 42, 0, va("%i played", kartstats.matchesplayed)); V_DrawString(20, 52, highlightflags, "Online Power Level:"); V_DrawRightAlignedString(BASEVIDWIDTH-16, 52, 0, va("Race: %i", vspowerlevel[PWRLV_RACE])); @@ -10249,8 +10249,7 @@ static void M_EraseDataResponse(INT32 ch) if (erasecontext == 2) { // SRB2Kart: This actually needs to be done FIRST, so that you don't immediately regain playtime/matches secrets - totalplaytime = 0; - matchesplayed = 0; + K_EraseStats(); for (i = 0; i < PWRLV_NUMTYPES; i++) vspowerlevel[i] = PWRLVRECORD_START; F_StartIntro(); diff --git a/src/m_random.c b/src/m_random.c index 481fdb72b..fa4a00f63 100644 --- a/src/m_random.c +++ b/src/m_random.c @@ -14,7 +14,7 @@ #include "doomdef.h" #include "doomtype.h" -#include "doomstat.h" // totalplaytime +#include "k_stats.h" // kartstats.totalplaytime #include "m_random.h" #include "m_fixed.h" @@ -252,5 +252,5 @@ void P_SetRandSeedD(const char *rfile, INT32 rline, UINT32 seed) */ UINT32 M_RandomizedSeed(void) { - return ((totalplaytime & 0xFFFF) << 16)|M_RandomFixed(); + return ((kartstats.totalplaytime & 0xFFFF) << 16)|M_RandomFixed(); } diff --git a/src/p_map.c b/src/p_map.c index f65e23140..4ea3e6b12 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -25,7 +25,7 @@ #include "r_sky.h" #include "s_sound.h" #include "w_wad.h" - +#include "k_stats.h" #include "k_kart.h" // SRB2kart 011617 #include "k_collide.h" #include "hu_stuff.h" // SRB2kart diff --git a/src/p_tick.c b/src/p_tick.c index 691a717de..61d754dd7 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -22,6 +22,7 @@ #include "m_random.h" #include "lua_script.h" #include "lua_hook.h" +#include "k_stats.h" #include "m_perfstats.h" #include "i_system.h" // I_GetPreciseTime #include "i_video.h" @@ -746,8 +747,7 @@ void P_Ticker(boolean run) } // Keep track of how long they've been playing! - if (!demo.playback) // Don't increment if a demo is playing. - totalplaytime++; + K_StatTicker(); // formality so kitemcap gets updated properly each frame. P_RunKartItems(); From f9a2623773da82329c0e94e7ed8f2d4d119b3fb5 Mon Sep 17 00:00:00 2001 From: Alug Date: Mon, 13 Jan 2025 10:47:38 +0100 Subject: [PATCH 02/19] make game use seperate save when addons are loaded so you can record attack and it will also keep saving to statistics also added a few things it now keeps track of TODO: unfuck the statistics page and add a 2nd one perhaps? due to space reasons menucode is hell --- src/deh_soc.c | 8 ++++---- src/g_game.c | 34 +++++++++++++++++++++++++++++++--- src/m_menu.c | 2 +- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index 85b1afda1..1d7ceca3d 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2482,7 +2482,7 @@ void readsound(MYFILE *f, INT32 num) * \sa readmaincfg() * \author Graue */ -static boolean GoodDataFileName(const char *s) +/*static boolean GoodDataFileName(const char *s) { const char *p; const char *tail = ".dat"; @@ -2497,7 +2497,7 @@ static boolean GoodDataFileName(const char *s) if (fasticmp(s, "gamedata.dat")) return false; return true; -} +}*/ void reademblemdata(MYFILE *f, INT32 num) { @@ -3240,7 +3240,7 @@ void readmaincfg(MYFILE *f) maxXtraLife = (UINT8)get_number(word2); } - else if (fastcmp(word, "GAMEDATA")) + /*else if (fastcmp(word, "GAMEDATA")) { size_t filenamelen; @@ -3272,7 +3272,7 @@ void readmaincfg(MYFILE *f) refreshdirmenu |= REFRESHDIR_GAMEDATA; gamedataadded = true; titlechanged = true; - } + }*/ else if (fastcmp(word, "RESETDATA")) { P_ResetData(value); diff --git a/src/g_game.c b/src/g_game.c index e349d7844..80b58a494 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -672,6 +672,31 @@ static void G_UpdateRecordReplays(void) G_SaveGameData(); } +// hack t +static void G_SetSaveGameModified(void) +{ + size_t filenamelen; + + savemoddata = true; + majormods = false; // FIXME: this breaks the menu warning screen from popping up, id still want to use it to mention the diff savefile + + strlcpy(gamedatafilename, "modkartdata.dat", sizeof (gamedatafilename)); + strlwr(gamedatafilename); + + // Also save a time attack folder + filenamelen = strlen(gamedatafilename)-4; // Strip off the extension + filenamelen = min(filenamelen, sizeof (timeattackfolder)); + memcpy(timeattackfolder, gamedatafilename, filenamelen); + timeattackfolder[min(filenamelen, sizeof (timeattackfolder) - 1)] = '\0'; + + strcpy(savegamename, timeattackfolder); + strlcat(savegamename, "%u.ssg", sizeof(savegamename)); + // can't use sprintf since there is %u in savegamename + strcatbf(savegamename, srb2home, PATHSEP); + + G_LoadGameData(); +} + // for consistency among messages: this modifies the game and removes savemoddata. void G_SetGameModified(boolean silent, boolean major) { @@ -686,8 +711,10 @@ void G_SetGameModified(boolean silent, boolean major) //savemoddata = false; -- there is literally no reason to do this anymore. majormods = true; + G_SetSaveGameModified(); + if (!silent) - CONS_Alert(CONS_NOTICE, M_GetText("Game must be restarted to play Record Attack.\n")); + CONS_Alert(CONS_NOTICE, M_GetText("Record Attack data will be saved to seperate save.\n")); // If in record attack recording, cancel it. if (modeattacking) @@ -4332,7 +4359,8 @@ void G_LoadGameData(void) I_Error("Game data is not for SRB2Kart v2.\nDelete %s(maybe in %s) and try again.", gamedatafilename, gdfolder); } - K_ReadStats(&save, true); + // well no clue but dont think it would like reading garbage from vanilla files + K_ReadStats(&save, strcmp(gamedatafilename, "modkartdata.dat") == 0); for (i = 0; i < PWRLV_NUMTYPES; i++) { @@ -4482,7 +4510,7 @@ void G_SaveGameData(void) // Version test WRITEUINT32(save.p, GD_VERSIONCHECK); // 4 - K_WriteStats(&save, true); + K_WriteStats(&save, strcmp(gamedatafilename, "modkartdata.dat") == 0); for (i = 0; i < PWRLV_NUMTYPES; i++) WRITEUINT16(save.p, vspowerlevel[i]); diff --git a/src/m_menu.c b/src/m_menu.c index 70933cb56..e0a1fe65e 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -5016,7 +5016,7 @@ static boolean M_AddonsRefresh(void) else if (majormods && !prevmajormods) { S_StartSound(NULL, sfx_s221); - message = va("%c%s\x80\nYou've loaded a gameplay-modifying addon.\n\nRecord Attack has been disabled, but you\ncan still play alone in local Multiplayer.\n\nIf you wish to play Record Attack mode, restart the game to disable loaded addons.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname); + message = va("%c%s\x80\nYou've loaded a gameplay-modifying addon.\n\nRecord Attack data will be saved to seperate save.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname); prevmajormods = majormods; } From e29aca5ff0661b756122c04b9969d6421f42f20b Mon Sep 17 00:00:00 2001 From: Indev Date: Mon, 13 Jan 2025 17:37:50 +0300 Subject: [PATCH 03/19] Oops --- src/g_game.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 80b58a494..3edff30f7 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4360,7 +4360,7 @@ void G_LoadGameData(void) } // well no clue but dont think it would like reading garbage from vanilla files - K_ReadStats(&save, strcmp(gamedatafilename, "modkartdata.dat") == 0); + K_ReadStats(&save, strcmp(gamedatafilename, "modkartdata.dat") != 0); for (i = 0; i < PWRLV_NUMTYPES; i++) { @@ -4510,7 +4510,7 @@ void G_SaveGameData(void) // Version test WRITEUINT32(save.p, GD_VERSIONCHECK); // 4 - K_WriteStats(&save, strcmp(gamedatafilename, "modkartdata.dat") == 0); + K_WriteStats(&save, strcmp(gamedatafilename, "modkartdata.dat") != 0); for (i = 0; i < PWRLV_NUMTYPES; i++) WRITEUINT16(save.p, vspowerlevel[i]); From 939ffd61db1d1c2ccbd7611122eefbcc45c79451 Mon Sep 17 00:00:00 2001 From: Indev Date: Mon, 13 Jan 2025 17:52:41 +0300 Subject: [PATCH 04/19] Forgor respawns --- src/g_game.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/g_game.c b/src/g_game.c index 3edff30f7..b6b774b70 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2590,6 +2590,9 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) if (leveltime > (starttime + (TICRATE/2)) && !p->spectator) p->respawn = 48; // Respawn effect + + if (!demo.playback && p == &players[consoleplayer]) + kartstats.respawns++; } // From 19b7019e404b7a788c627d2b361aa08298019322 Mon Sep 17 00:00:00 2001 From: Alug Date: Mon, 13 Jan 2025 16:37:56 +0100 Subject: [PATCH 05/19] remove "majormods" checks from stuff that saves to savegame so its only there to throw a notice when you load an addon --- src/d_netcmd.c | 8 ++------ src/g_game.c | 14 ++------------ src/m_menu.c | 11 ----------- 3 files changed, 4 insertions(+), 29 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index d7ddd9fe6..fa14bbf1e 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -5826,12 +5826,8 @@ void Command_Retry_f(void) */ static void Command_Isgamemodified_f(void) { - if (majormods) - CONS_Printf("The game has been modified with major addons, so you cannot play Record Attack.\n"); - else if (savemoddata) - CONS_Printf("The game has been modified with an addon with its own save data, so you can play Record Attack and earn medals.\n"); - else if (modifiedgame) - CONS_Printf("The game has been modified with only minor addons. You can play Record Attack, earn medals and unlock extras.\n"); + if (majormods || modifiedgame) + CONS_Printf("The game has been modified, Record Attack data will be saved to a seperate savegame.\n"); else CONS_Printf("The game has not been modified. You can play Record Attack, earn medals and unlock extras.\n"); } diff --git a/src/g_game.c b/src/g_game.c index b6b774b70..1454db7d2 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -672,13 +672,12 @@ static void G_UpdateRecordReplays(void) G_SaveGameData(); } -// hack t +// kinda hacky way to do this, but this sets the game to use a seperate savefile if you have addons loaded static void G_SetSaveGameModified(void) { size_t filenamelen; savemoddata = true; - majormods = false; // FIXME: this breaks the menu warning screen from popping up, id still want to use it to mention the diff savefile strlcpy(gamedatafilename, "modkartdata.dat", sizeof (gamedatafilename)); strlwr(gamedatafilename); @@ -711,6 +710,7 @@ void G_SetGameModified(boolean silent, boolean major) //savemoddata = false; -- there is literally no reason to do this anymore. majormods = true; + // should this only be done when you load a "major" gameplay modifieng addon? G_SetSaveGameModified(); if (!silent) @@ -4500,16 +4500,6 @@ void G_SaveGameData(void) return; } -#if 0 - // SRB2Kart: Let players unlock stuff with addons. - if (modifiedgame && !savemoddata) - { - free(save.buffer); - save.p = save.buffer = NULL; - return; - } -#endif - // Version test WRITEUINT32(save.p, GD_VERSIONCHECK); // 4 diff --git a/src/m_menu.c b/src/m_menu.c index e0a1fe65e..66f13a842 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -2952,17 +2952,6 @@ boolean M_Responder(event_t *ev) if (routine) { - if (((currentMenu->menuitems[itemOn].status & IT_TYPE)==IT_CALL - || (currentMenu->menuitems[itemOn].status & IT_TYPE)==IT_SUBMENU) - && (currentMenu->menuitems[itemOn].status & IT_CALLTYPE)) - { - if (((currentMenu->menuitems[itemOn].status & IT_CALLTYPE) & IT_CALL_NOTMODIFIED) && majormods) - { - S_StartSound(NULL, sfx_menu1); - M_StartMessage(M_GetText("This cannot be done with complex addons\nor in a cheated game.\n\n(Press a key)\n"), NULL, MM_NOTHING); - return true; - } - } S_StartSound(NULL, sfx_menu1); switch (currentMenu->menuitems[itemOn].status & IT_TYPE) { From 3560e603485c31b673a1181143e8cfb8d63c2204 Mon Sep 17 00:00:00 2001 From: Alug Date: Mon, 13 Jan 2025 16:41:45 +0100 Subject: [PATCH 06/19] save vanilla save when switching to modified savegame also add check so we dont rerun this everytime setgamemodified is called --- src/g_game.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/g_game.c b/src/g_game.c index 1454db7d2..8ed2f8ec7 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -677,6 +677,12 @@ static void G_SetSaveGameModified(void) { size_t filenamelen; + if (savemoddata) + return; + + // save vanilla data just to be sure + G_SaveGameData(true); + savemoddata = true; strlcpy(gamedatafilename, "modkartdata.dat", sizeof (gamedatafilename)); From ddd9a5a71e55975145638624f4c3553e01e84d57 Mon Sep 17 00:00:00 2001 From: Alug Date: Mon, 13 Jan 2025 17:42:22 +0100 Subject: [PATCH 07/19] no need to strcmp just check for "savemoddata" that bool only ever gets set to true if the savegame has been switched to the modded one and unlike in srb2 this WILL never be reset to false again --- src/g_game.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 8ed2f8ec7..a381d82c5 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4369,7 +4369,7 @@ void G_LoadGameData(void) } // well no clue but dont think it would like reading garbage from vanilla files - K_ReadStats(&save, strcmp(gamedatafilename, "modkartdata.dat") != 0); + K_ReadStats(&save, !savemoddata); for (i = 0; i < PWRLV_NUMTYPES; i++) { @@ -4509,7 +4509,7 @@ void G_SaveGameData(void) // Version test WRITEUINT32(save.p, GD_VERSIONCHECK); // 4 - K_WriteStats(&save, strcmp(gamedatafilename, "modkartdata.dat") != 0); + K_WriteStats(&save, !savemoddata); for (i = 0; i < PWRLV_NUMTYPES; i++) WRITEUINT16(save.p, vspowerlevel[i]); From ec59f0a233872f7ba015356a0a67fd0f273221d1 Mon Sep 17 00:00:00 2001 From: Indev Date: Mon, 13 Jan 2025 23:10:56 +0300 Subject: [PATCH 08/19] Player kartstuff's get reset in G_PlayerFinishLevel, so need to call K_StatRound earlier --- src/g_game.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/g_game.c b/src/g_game.c index a381d82c5..23b177c18 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4034,9 +4034,13 @@ static void G_DoCompleted(void) if (metalrecording) G_StopMetalRecording(false); + K_StatRound(); + G_SetGamestate(GS_NULL); wipegamestate = GS_NULL; + K_StatRound(); + for (i = 0; i < MAXPLAYERS; i++) { if (playeringame[i]) @@ -4079,7 +4083,6 @@ static void G_DoCompleted(void) if (!demo.playback) { nextmap = G_GetNextMap(true); - K_StatRound(); // Remember last map for when you come out of the special stage. if (!spec) From 778e30ade20df026f88f1f787d0c479b8e35b29d Mon Sep 17 00:00:00 2001 From: Indev Date: Mon, 13 Jan 2025 23:17:51 +0300 Subject: [PATCH 09/19] G_PlayerReborn didn't work for 'respawns' stats, maybe G_DoReborn will? --- src/g_game.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 23b177c18..9078e5c58 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2596,9 +2596,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) if (leveltime > (starttime + (TICRATE/2)) && !p->spectator) p->respawn = 48; // Respawn effect - - if (!demo.playback && p == &players[consoleplayer]) - kartstats.respawns++; } // @@ -2987,6 +2984,9 @@ void G_DoReborn(INT32 playernum) G_SpawnPlayer(playernum, starpost); if (oldmo) G_ChangePlayerReferences(oldmo, players[playernum].mo); + + if (!demo.playback && playernum == consoleplayer) + kartstats.respawns++; } ACS_RunPlayerEnterScript(player); From c8e78711a69169de4f6c9e48a93c8c85f981b75c Mon Sep 17 00:00:00 2001 From: NepDisk Date: Tue, 25 Feb 2025 14:15:19 -0500 Subject: [PATCH 10/19] dont throw a warning about addons with custom gamedata just ignore it --- src/deh_soc.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index 1d7ceca3d..599367a9d 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -3240,9 +3240,10 @@ void readmaincfg(MYFILE *f) maxXtraLife = (UINT8)get_number(word2); } - /*else if (fastcmp(word, "GAMEDATA")) + else if (fastcmp(word, "GAMEDATA")) { - size_t filenamelen; + // just ignore it but dont throw a warning + /*size_t filenamelen; // Check the data filename so that mods // can't write arbitrary files. @@ -3271,8 +3272,8 @@ void readmaincfg(MYFILE *f) refreshdirmenu |= REFRESHDIR_GAMEDATA; gamedataadded = true; - titlechanged = true; - }*/ + titlechanged = true;*/ + } else if (fastcmp(word, "RESETDATA")) { P_ResetData(value); From 1922870773879cf8b1634613cf58bd8294cb661c Mon Sep 17 00:00:00 2001 From: Indev Date: Sun, 19 Jan 2025 02:28:16 +0300 Subject: [PATCH 11/19] Make custom stats fully compatible between different versions, so new fields can be added in future without save corruptions --- src/k_stats.c | 98 +++++++++++++++++++++++++++++++++++++++++++-------- src/k_stats.h | 10 ++++++ 2 files changed, 94 insertions(+), 14 deletions(-) diff --git a/src/k_stats.c b/src/k_stats.c index 4c126f700..e2da4cbee 100644 --- a/src/k_stats.c +++ b/src/k_stats.c @@ -2,6 +2,7 @@ #include "doomstat.h" #include "g_game.h" #include "byteptr.h" +#include "z_zone.h" kartstats_t kartstats = {0}; @@ -65,6 +66,8 @@ void K_StatRound(void) if (demo.playback) return; + kartstats.somenewfield = 42; + int numplayers = 0; for (int i = 0; i < MAXPLAYERS; ++i) @@ -87,23 +90,18 @@ void K_EraseStats(void) // The only field we want to remember boolean vanilla = kartstats.vanilla; + if (kartstats.copy) + Z_Free(kartstats.copy); + memset(&kartstats, 0, sizeof(kartstats_t)); kartstats.vanilla = vanilla; } -void K_ReadStats(savebuffer_t *save, boolean vanilla) +static void K_ReadStatsCustom(savebuffer_t *save) { - memset(&kartstats, 0, sizeof(kartstats_t)); - - kartstats.vanilla = vanilla; - - kartstats.totalplaytime = READUINT32(save->p); - kartstats.matchesplayed = READUINT32(save->p); - - // Vanilla only has those 2 - if (vanilla) - return; + kartstats.size = READUINT32(save->p); + kartstats.version = READUINT32(save->p); // So many similar-looking read's... scary... kartstats.raplaytime = READUINT32(save->p); @@ -119,17 +117,51 @@ void K_ReadStats(savebuffer_t *save, boolean vanilla) kartstats.sinks = READUINT32(save->p); kartstats.sinked = READUINT32(save->p); kartstats.respawns = READUINT32(save->p); + + // If kartstats gets updated, uncomment this and read next fields after this early return. Do same on next updates, + // this way even data is read on different versions, it doesn't get corrupted (as long as fields aren't removed, which shouldn't happen) + //if (kartstats.version < 2) + // return; + // + //kartstats.somenewfield = READUINT32(save->p); } -void K_WriteStats(savebuffer_t *save, boolean vanilla) +void K_ReadStats(savebuffer_t *save, boolean vanilla) { - WRITEUINT32(save->p, kartstats.totalplaytime); - WRITEUINT32(save->p, kartstats.matchesplayed); + // Basically, free old kartstats.copy if needed and memset kartstats with zeros + K_EraseStats(); + + kartstats.vanilla = vanilla; + + kartstats.totalplaytime = READUINT32(save->p); + kartstats.matchesplayed = READUINT32(save->p); // Vanilla only has those 2 if (vanilla) return; + // Save where block of custom stats starts + UINT8 *customblockstart = save->p; + + K_ReadStatsCustom(save); + + // Make a copy of entire custom stats block, so unread values will still be written back + kartstats.copy = Z_Malloc(kartstats.size, PU_STATIC, NULL); + memcpy(kartstats.copy, customblockstart, kartstats.size); + + // If there were extra fields we couldn't read, skip them + save->p = customblockstart + kartstats.size; +} + +static void K_WriteStatsCustom(savebuffer_t *save) +{ + // Update, if needed + kartstats.version = max(kartstats.version, KARTSTATSVERSION); + + WRITEUINT32(save->p, kartstats.size); // Note: if we will end up writing more that stored in there, this field in save file would be updated + WRITEUINT32(save->p, kartstats.version); + + // Version 1 WRITEUINT32(save->p, kartstats.raplaytime); WRITEUINT32(save->p, kartstats.onlineplaytime); WRITEUINT32(save->p, kartstats.raceplaytime); @@ -143,4 +175,42 @@ void K_WriteStats(savebuffer_t *save, boolean vanilla) WRITEUINT32(save->p, kartstats.sinks); WRITEUINT32(save->p, kartstats.sinked); WRITEUINT32(save->p, kartstats.respawns); + + // No need for early returns, but please mark each new block of write's with version it corresponds to :3 + + // Version 2 + //WRITEUINT32(save->p, kartstats.somenewfield); +} + +void K_WriteStats(savebuffer_t *save, boolean vanilla) +{ + WRITEUINT32(save->p, kartstats.totalplaytime); + WRITEUINT32(save->p, kartstats.matchesplayed); + + // Vanilla only has those 2 + if (vanilla) + return; + + // Now need to be careful... We save start of custom stats block + UINT8 *customblockstart = save->p; + + // Write everything we know how to write + K_WriteStatsCustom(save); + + // Calculate size of data that we wrote + UINT32 size = (UINT32)(save->p - customblockstart); + + // Now, if we wrote less data than save originally had, that means our copy that we kept in K_ReadStats + // has stats from newer version that we need to append + if (size < kartstats.size) + { + I_Assert(kartstats.copy != NULL); // If kartstats.copy is null, kartstats.size **always** should be 0, this can only happen if we create new save + WRITEMEM(save->p, kartstats.copy + size, kartstats.size - size); + } + else if (size > kartstats.size) // No need to do anything if we wrote exactly same amount... + { + // ...but, if we wrote more, we need to update size field in header. **It's always located at start of block** + // So we can just: + WRITEUINT32(customblockstart, size); + } } diff --git a/src/k_stats.h b/src/k_stats.h index 87a173653..bd145de56 100644 --- a/src/k_stats.h +++ b/src/k_stats.h @@ -5,6 +5,8 @@ #include "d_player.h" #include "p_saveg.h" +#define KARTSTATSVERSION 1 + typedef struct kartstats_s { // Remember if stats we loaded are vanilla ones or not boolean vanilla; @@ -13,6 +15,12 @@ typedef struct kartstats_s { tic_t totalplaytime; UINT32 matchesplayed; + // Custom data, fields go in same order as they are written into game save + + // IMPORTANT: size **must be first**, so K_WriteStats can overwrite it (not that we're gonna change order, but just in case) + UINT32 size; // Size required for our stats. If older version reads save from newer one, it will just skip unread fields, avoiding reading corrupted values + UINT32 version; // Version, so we know which fields we actually can read + tic_t raplaytime; tic_t onlineplaytime; tic_t raceplaytime; @@ -26,6 +34,8 @@ typedef struct kartstats_s { UINT32 sinks; // Times hitting *others* by kitchen sink UINT32 sinked; // Times *being hit* by kitchen sink UINT32 respawns; + + UINT8 *copy; // Copy of stats block that has been read from file. In case we're older version that reads newer save, we will keep values that we couldn't read } kartstats_t; extern kartstats_t kartstats; From 437b8425c7a3c549af09ffa101d312a888bc6277 Mon Sep 17 00:00:00 2001 From: Indev Date: Sun, 19 Jan 2025 02:29:41 +0300 Subject: [PATCH 12/19] That was from testing a --- src/k_stats.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/k_stats.c b/src/k_stats.c index e2da4cbee..fc467ce29 100644 --- a/src/k_stats.c +++ b/src/k_stats.c @@ -66,8 +66,6 @@ void K_StatRound(void) if (demo.playback) return; - kartstats.somenewfield = 42; - int numplayers = 0; for (int i = 0; i < MAXPLAYERS; ++i) From 151b0ba9d9e9b49e8fbbd3f75142298705e17a8f Mon Sep 17 00:00:00 2001 From: Indev Date: Sun, 19 Jan 2025 02:43:18 +0300 Subject: [PATCH 13/19] Wipe kart stats on game data reload --- src/g_game.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/g_game.c b/src/g_game.c index 9078e5c58..8c4b84360 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4339,6 +4339,7 @@ void G_LoadGameData(void) // to new gamedata G_ClearRecords(); // main and nights records M_ClearSecrets(); // emblems, unlocks, maps visited, etc + K_EraseStats(); // stats for (i = 0; i < PWRLV_NUMTYPES; i++) // SRB2Kart: online rank system vspowerlevel[i] = PWRLVRECORD_START; From 87693db34e43afe49ad8fcbe982005b9f65c18d0 Mon Sep 17 00:00:00 2001 From: Indev Date: Tue, 21 Jan 2025 17:38:09 +0300 Subject: [PATCH 14/19] Check for spectator in K_StatRound --- src/k_stats.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_stats.c b/src/k_stats.c index fc467ce29..dfdd142f1 100644 --- a/src/k_stats.c +++ b/src/k_stats.c @@ -74,7 +74,7 @@ void K_StatRound(void) ++numplayers; } - if (numplayers > 1) + if (numplayers > 1 && !players[consoleplayer].spectator) { if (players[consoleplayer].kartstuff[k_position] == 1) kartstats.totalwins++; From a8f29fbdf24fd2e4f98e6e0e2d0797dceccd2041 Mon Sep 17 00:00:00 2001 From: Indev Date: Sun, 19 Jan 2025 02:55:39 +0300 Subject: [PATCH 15/19] New statistics menu (WIP) --- src/g_game.c | 2 +- src/m_menu.c | 172 +++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 133 insertions(+), 41 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 8c4b84360..318cb486c 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -681,7 +681,7 @@ static void G_SetSaveGameModified(void) return; // save vanilla data just to be sure - G_SaveGameData(true); + G_SaveGameData(); savemoddata = true; diff --git a/src/m_menu.c b/src/m_menu.c index 66f13a842..0dc731f56 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -7524,10 +7524,29 @@ static void M_ChoosePlayer(INT32 choice) // STATISTICS MENU // =============== +static void M_DrawStatsMaps(void); +static void M_DrawStatsPlaytime(void); +static void M_DrawStatsExtra(void); // dunno how to name this one + static INT32 statsLocation; static INT32 statsMax; static INT16 *statsMapList = NULL; static INT16 statsMapListLen; +static UINT8 statsCurrentPage = 0; + +typedef struct statpage_s { + const char *title; + void (*drawer)(void); +} statpage_t; + +static statpage_t statsPages[] = { + { "Level Statistics", M_DrawStatsMaps, }, + { "Play Time Statistics", M_DrawStatsPlaytime, }, + { "Extra Statistics", M_DrawStatsExtra, }, +}; + +#define LENSTATSPAGES (sizeof(statsPages)/sizeof(statsPages[0])) +#define NUMSTATSPAGES (kartstats.vanilla ? 2 : LENSTATSPAGES) static void M_Statistics(INT32 choice) { @@ -7557,6 +7576,7 @@ static void M_Statistics(INT32 choice) statsMapList[j] = -1; statsMax = j - 11 + numextraemblems; statsLocation = 0; + statsCurrentPage = 0; if (statsMax < 0) statsMax = 0; @@ -7564,13 +7584,45 @@ static void M_Statistics(INT32 choice) M_SetupNextMenu(&SP_LevelStatsDef); } -static void M_DrawStatsMaps(int location) +static void M_DrawStatsMaps(void) { - INT32 y = 88, i = -1; + char beststr[40]; + tic_t besttime = 0; + INT32 mapsunfinished = 0; + + int location = statsLocation; + INT32 y = 62, i = -1, j; INT16 mnum; extraemblem_t *exemblem; boolean dotopname = true, dobottomarrow = (location < statsMax); + for (j = 0; j < nummapheaders; j++) + { + if (!mapheaderinfo[j] || (mapheaderinfo[j]->menuflags & LF2_NOTIMEATTACK)) + continue; + + if (!mapheaderinfo[j]->mainrecord || mapheaderinfo[j]->mainrecord->time <= 0) + { + mapsunfinished++; + continue; + } + + besttime += mapheaderinfo[j]->mainrecord->time; + } + + V_DrawString(20, 42, highlightflags, "Combined time records:"); + + sprintf(beststr, "%i:%02i:%02i.%02i", G_TicsToHours(besttime), G_TicsToMinutes(besttime, false), G_TicsToSeconds(besttime), G_TicsToCentiseconds(besttime)); + V_DrawRightAlignedString(BASEVIDWIDTH-16, 42, (mapsunfinished ? warningflags : 0), beststr); + + if (mapsunfinished) + V_DrawRightAlignedString(BASEVIDWIDTH-16, 50, warningflags, va("(%d unfinished)", mapsunfinished)); + else + V_DrawRightAlignedString(BASEVIDWIDTH-16, 50, recommendedflags, "(complete)"); + + V_DrawString(32, 50, 0, va("x %d/%d", M_CountEmblems(), numemblems+numextraemblems)); + V_DrawSmallScaledPatch(20, 50, 0, W_CachePatchName("GOTITA", PU_STATIC)); + if (location) V_DrawCharacter(10, y-(skullAnimCounter/5), '\x1A' | highlightflags, false); // up arrow @@ -7659,57 +7711,74 @@ bottomarrow: '\x1B' | highlightflags, false); // down arrow } -static void M_DrawLevelStats(void) +#define DRAWTIMESTAT(y, title, field) { \ + char timebuf[80]; \ + V_DrawString(20, (y), highlightflags, title); \ + tic_t timeval = kartstats.field; \ + snprintf(timebuf, 80, "%02i:%02i:%02i", G_TicsToHours(timeval), G_TicsToMinutes(timeval, false), G_TicsToSeconds(timeval)); \ + V_DrawRightAlignedString(BASEVIDWIDTH-16, (y), 0, timebuf); \ + } + +#define DRAWAMOUNTSTAT(y, title, field) { \ + V_DrawString(20, (y), highlightflags, title); \ + unsigned amountval = kartstats.field; \ + V_DrawRightAlignedString(BASEVIDWIDTH-16, (y), 0, va("%u", amountval)); \ + } + +static void M_DrawStatsPlaytime(void) { - char beststr[40]; - - tic_t besttime = 0; - - INT32 i; - INT32 mapsunfinished = 0; - - M_DrawMenuTitle(); - - V_DrawString(20, 24, highlightflags, "Total Play Time:"); - V_DrawCenteredString(BASEVIDWIDTH/2, 32, 0, va("%i hours, %i minutes, %i seconds", + V_DrawString(20, 42, highlightflags, "Total Play Time:"); + V_DrawCenteredString(BASEVIDWIDTH/2, 52, 0, va("%i hours, %i minutes, %i seconds", G_TicsToHours(kartstats.totalplaytime), G_TicsToMinutes(kartstats.totalplaytime, false), G_TicsToSeconds(kartstats.totalplaytime))); - V_DrawString(20, 42, highlightflags, "Total Matches:"); - V_DrawRightAlignedString(BASEVIDWIDTH-16, 42, 0, va("%i played", kartstats.matchesplayed)); + V_DrawString(20, 62, highlightflags, "Total Matches:"); + V_DrawRightAlignedString(BASEVIDWIDTH-16, 62, 0, va("%i played", kartstats.matchesplayed)); - V_DrawString(20, 52, highlightflags, "Online Power Level:"); - V_DrawRightAlignedString(BASEVIDWIDTH-16, 52, 0, va("Race: %i", vspowerlevel[PWRLV_RACE])); - V_DrawRightAlignedString(BASEVIDWIDTH-16, 60, 0, va("Battle: %i", vspowerlevel[PWRLV_BATTLE])); + // Nothing else to draw + if (kartstats.vanilla) + return; - for (i = 0; i < nummapheaders; i++) - { - if (!mapheaderinfo[i] || (mapheaderinfo[i]->menuflags & LF2_NOTIMEATTACK)) - continue; + DRAWTIMESTAT(82, "RA Play Time:", raplaytime); + DRAWTIMESTAT(92, "Online Play Time:", onlineplaytime); + DRAWTIMESTAT(102, "Race Play Time:", raceplaytime); + DRAWTIMESTAT(112, "Battle Play Time:", battleplaytime); +} - if (!mapheaderinfo[i]->mainrecord || mapheaderinfo[i]->mainrecord->time <= 0) - { - mapsunfinished++; - continue; - } +// Note: only available with non-vanilla stats loaded, so it doesn't check for that +static void M_DrawStatsExtra(void) +{ + DRAWTIMESTAT(42, "Time being SPB target:", spbtargettime); + DRAWTIMESTAT(52, "Time spent in spinout:", spinouttime); - besttime += mapheaderinfo[i]->mainrecord->time; - } + DRAWAMOUNTSTAT(72, "Total wins:", totalwins); + DRAWAMOUNTSTAT(82, "Total podium (2nd/3rd place):", totalwins); - V_DrawString(20, 70, highlightflags, "Combined time records:"); + DRAWAMOUNTSTAT(102, "Hits landed:", hits); + DRAWAMOUNTSTAT(112, "Self-hits landed:", selfhits); - sprintf(beststr, "%i:%02i:%02i.%02i", G_TicsToHours(besttime), G_TicsToMinutes(besttime, false), G_TicsToSeconds(besttime), G_TicsToCentiseconds(besttime)); - V_DrawRightAlignedString(BASEVIDWIDTH-16, 70, (mapsunfinished ? warningflags : 0), beststr); + DRAWAMOUNTSTAT(132, "Sinks landed:", sinks); + DRAWAMOUNTSTAT(142, "Times hit by sink:", sinked); - if (mapsunfinished) - V_DrawRightAlignedString(BASEVIDWIDTH-16, 78, warningflags, va("(%d unfinished)", mapsunfinished)); - else - V_DrawRightAlignedString(BASEVIDWIDTH-16, 78, recommendedflags, "(complete)"); + DRAWAMOUNTSTAT(162, "Total respawns:", respawns); +} - V_DrawString(32, 78, V_ALLOWLOWERCASE, va("x %d/%d", M_CountEmblems(), numemblems+numextraemblems)); - V_DrawSmallScaledPatch(20, 78, 0, W_CachePatchName("GOTITA", PU_STATIC)); +#undef DRAWAMOUNTSTAT +#undef DRAWTIMESTAT - M_DrawStatsMaps(statsLocation); +static void M_DrawLevelStats(void) +{ + M_DrawMenuTitle(); + + V_DrawCenteredString(BASEVIDWIDTH/2, 28, highlightflags, statsPages[statsCurrentPage].title); + + INT32 w = V_StringWidth(statsPages[statsCurrentPage].title, highlightflags); + V_DrawCharacter(BASEVIDWIDTH/2 - w/2 - 10 - (skullAnimCounter/5), 28, + '\x1C' | highlightflags, false); // left arrow + V_DrawCharacter(BASEVIDWIDTH/2 + w/2 + 2 + (skullAnimCounter/5), 28, + '\x1D' | highlightflags, false); // right arrow + + statsPages[statsCurrentPage].drawer(); } // Handle statistics. @@ -7720,23 +7789,46 @@ static void M_HandleLevelStats(INT32 choice) switch (choice) { case KEY_DOWNARROW: + if (statsCurrentPage != 0) // Must be on level stats page + break; S_StartSound(NULL, sfx_menu1); if (statsLocation < statsMax) ++statsLocation; break; case KEY_UPARROW: + if (statsCurrentPage != 0) // Must be on level stats page + break; S_StartSound(NULL, sfx_menu1); if (statsLocation) --statsLocation; break; + case KEY_RIGHTARROW: + S_StartSound(NULL, sfx_menu1); + statsCurrentPage++; + if (statsCurrentPage >= NUMSTATSPAGES) + statsCurrentPage = 0; + break; + + case KEY_LEFTARROW: + S_StartSound(NULL, sfx_menu1); + if (statsCurrentPage == 0) + statsCurrentPage = NUMSTATSPAGES-1; + else + --statsCurrentPage; + break; + case KEY_PGDN: + if (statsCurrentPage != 0) // Must be on level stats page + break; S_StartSound(NULL, sfx_menu1); statsLocation += (statsLocation+13 >= statsMax) ? statsMax-statsLocation : 13; break; case KEY_PGUP: + if (statsCurrentPage != 0) // Must be on level stats page + break; S_StartSound(NULL, sfx_menu1); statsLocation -= (statsLocation < 13) ? statsLocation : 13; break; From cbfc0715126e9e1e3b1bbd344eb7e5098033ed16 Mon Sep 17 00:00:00 2001 From: Indev Date: Sun, 19 Jan 2025 03:04:24 +0300 Subject: [PATCH 16/19] Put playtime statistics at first page instead of RA medals --- src/m_menu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/m_menu.c b/src/m_menu.c index 0dc731f56..444d57e29 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -7540,8 +7540,8 @@ typedef struct statpage_s { } statpage_t; static statpage_t statsPages[] = { - { "Level Statistics", M_DrawStatsMaps, }, { "Play Time Statistics", M_DrawStatsPlaytime, }, + { "Level Statistics", M_DrawStatsMaps, }, { "Extra Statistics", M_DrawStatsExtra, }, }; From 287f9ce9911a883601124b714d6a41f094144d9a Mon Sep 17 00:00:00 2001 From: Alug Date: Tue, 21 Jan 2025 16:54:28 +0100 Subject: [PATCH 17/19] fix level statistics page controls --- src/m_menu.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/m_menu.c b/src/m_menu.c index 444d57e29..54b9717f7 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -7789,7 +7789,7 @@ static void M_HandleLevelStats(INT32 choice) switch (choice) { case KEY_DOWNARROW: - if (statsCurrentPage != 0) // Must be on level stats page + if (statsCurrentPage != 1) // Must be on level stats page break; S_StartSound(NULL, sfx_menu1); if (statsLocation < statsMax) @@ -7797,7 +7797,7 @@ static void M_HandleLevelStats(INT32 choice) break; case KEY_UPARROW: - if (statsCurrentPage != 0) // Must be on level stats page + if (statsCurrentPage != 1) // Must be on level stats page break; S_StartSound(NULL, sfx_menu1); if (statsLocation) @@ -7820,14 +7820,14 @@ static void M_HandleLevelStats(INT32 choice) break; case KEY_PGDN: - if (statsCurrentPage != 0) // Must be on level stats page + if (statsCurrentPage != 1) // Must be on level stats page break; S_StartSound(NULL, sfx_menu1); statsLocation += (statsLocation+13 >= statsMax) ? statsMax-statsLocation : 13; break; case KEY_PGUP: - if (statsCurrentPage != 0) // Must be on level stats page + if (statsCurrentPage != 1) // Must be on level stats page break; S_StartSound(NULL, sfx_menu1); statsLocation -= (statsLocation < 13) ? statsLocation : 13; From 9ee3a2c088638e2d4ff6fe8f4e261158967d39b4 Mon Sep 17 00:00:00 2001 From: Indev Date: Sat, 25 Jan 2025 01:09:43 +0300 Subject: [PATCH 18/19] Fix 'total podium' stat display using totalwins --- src/m_menu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/m_menu.c b/src/m_menu.c index 54b9717f7..391da052c 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -7752,7 +7752,7 @@ static void M_DrawStatsExtra(void) DRAWTIMESTAT(52, "Time spent in spinout:", spinouttime); DRAWAMOUNTSTAT(72, "Total wins:", totalwins); - DRAWAMOUNTSTAT(82, "Total podium (2nd/3rd place):", totalwins); + DRAWAMOUNTSTAT(82, "Total podium (2nd/3rd place):", totalpodium); DRAWAMOUNTSTAT(102, "Hits landed:", hits); DRAWAMOUNTSTAT(112, "Self-hits landed:", selfhits); From 6b4c331f5d967b5c72ba63e2d284be41c1428d8f Mon Sep 17 00:00:00 2001 From: NepDisk Date: Tue, 25 Feb 2025 14:47:41 -0500 Subject: [PATCH 19/19] add pwrlvl to stats menu --- src/m_menu.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/m_menu.c b/src/m_menu.c index 391da052c..3e12b90ed 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -7735,14 +7735,18 @@ static void M_DrawStatsPlaytime(void) V_DrawString(20, 62, highlightflags, "Total Matches:"); V_DrawRightAlignedString(BASEVIDWIDTH-16, 62, 0, va("%i played", kartstats.matchesplayed)); + V_DrawString(20, 82, highlightflags, "Online Power Level:"); + V_DrawRightAlignedString(BASEVIDWIDTH-16, 92, 0, va("Race: %i", vspowerlevel[PWRLV_RACE])); + V_DrawRightAlignedString(BASEVIDWIDTH-16, 102, 0, va("Battle: %i", vspowerlevel[PWRLV_BATTLE])); + // Nothing else to draw if (kartstats.vanilla) return; - DRAWTIMESTAT(82, "RA Play Time:", raplaytime); - DRAWTIMESTAT(92, "Online Play Time:", onlineplaytime); - DRAWTIMESTAT(102, "Race Play Time:", raceplaytime); - DRAWTIMESTAT(112, "Battle Play Time:", battleplaytime); + DRAWTIMESTAT(122, "RA Play Time:", raplaytime); + DRAWTIMESTAT(132, "Online Play Time:", onlineplaytime); + DRAWTIMESTAT(142, "Race Play Time:", raceplaytime); + DRAWTIMESTAT(152, "Battle Play Time:", battleplaytime); } // Note: only available with non-vanilla stats loaded, so it doesn't check for that