Merge branch 'saturnstats' into blankart-dev

This commit is contained in:
NepDisk 2025-02-25 15:27:10 -05:00
commit a082850e56
14 changed files with 496 additions and 104 deletions

View file

@ -129,3 +129,4 @@ k_brightmap.c
k_director.c
k_follower.c
k_mapuser.c
k_stats.c

View file

@ -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");
}

View file

@ -2482,7 +2482,7 @@ void readsound(MYFILE *f, INT32 num)
* \sa readmaincfg()
* \author Graue <graue@oceanbase.org>
*/
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)
{
@ -3242,7 +3242,8 @@ void readmaincfg(MYFILE *f)
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,7 +3272,7 @@ void readmaincfg(MYFILE *f)
refreshdirmenu |= REFRESHDIR_GAMEDATA;
gamedataadded = true;
titlechanged = true;
titlechanged = true;*/
}
else if (fastcmp(word, "RESETDATA"))
{

View file

@ -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.

View file

@ -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
@ -673,6 +672,36 @@ static void G_UpdateRecordReplays(void)
G_SaveGameData();
}
// 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;
if (savemoddata)
return;
// save vanilla data just to be sure
G_SaveGameData();
savemoddata = true;
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)
{
@ -687,8 +716,11 @@ 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)
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)
@ -2198,7 +2230,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();
@ -2952,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);
@ -3999,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])
@ -4300,9 +4339,7 @@ void G_LoadGameData(void)
// to new gamedata
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
K_EraseStats(); // stats
for (i = 0; i < PWRLV_NUMTYPES; i++) // SRB2Kart: online rank system
vspowerlevel[i] = PWRLVRECORD_START;
@ -4334,9 +4371,9 @@ 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);
// well no clue but dont think it would like reading garbage from vanilla files
K_ReadStats(&save, !savemoddata);
for (i = 0; i < PWRLV_NUMTYPES; i++)
{
@ -4473,21 +4510,10 @@ 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
WRITEUINT32(save.p, totalplaytime); // 4
WRITEUINT32(save.p, matchesplayed); // 4
K_WriteStats(&save, !savemoddata);
for (i = 0; i < PWRLV_NUMTYPES; i++)
WRITEUINT16(save.p, vspowerlevel[i]);

View file

@ -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);

View file

@ -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);
}

214
src/k_stats.c Normal file
View file

@ -0,0 +1,214 @@
#include "k_stats.h"
#include "doomstat.h"
#include "g_game.h"
#include "byteptr.h"
#include "z_zone.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 && !players[consoleplayer].spectator)
{
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;
if (kartstats.copy)
Z_Free(kartstats.copy);
memset(&kartstats, 0, sizeof(kartstats_t));
kartstats.vanilla = vanilla;
}
static void K_ReadStatsCustom(savebuffer_t *save)
{
kartstats.size = READUINT32(save->p);
kartstats.version = READUINT32(save->p);
// 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);
// 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_ReadStats(savebuffer_t *save, boolean vanilla)
{
// 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);
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);
// 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);
}
}

66
src/k_stats.h Normal file
View file

@ -0,0 +1,66 @@
#ifndef __K_STATS__
#define __K_STATS__
#include "doomtype.h"
#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;
// Vanilla
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;
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;
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;
// 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

View file

@ -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

View file

@ -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"
@ -2951,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)
{
@ -5015,7 +5005,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;
}
@ -7534,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[] = {
{ "Play Time Statistics", M_DrawStatsPlaytime, },
{ "Level Statistics", M_DrawStatsMaps, },
{ "Extra Statistics", M_DrawStatsExtra, },
};
#define LENSTATSPAGES (sizeof(statsPages)/sizeof(statsPages[0]))
#define NUMSTATSPAGES (kartstats.vanilla ? 2 : LENSTATSPAGES)
static void M_Statistics(INT32 choice)
{
@ -7567,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;
@ -7574,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
@ -7669,58 +7711,78 @@ bottomarrow:
'\x1B' | highlightflags, false); // down arrow
}
static void M_DrawLevelStats(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",
G_TicsToHours(totalplaytime),
G_TicsToMinutes(totalplaytime, false),
G_TicsToSeconds(totalplaytime)));
V_DrawString(20, 42, highlightflags, "Total Matches:");
V_DrawRightAlignedString(BASEVIDWIDTH-16, 42, 0, va("%i played", 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]));
for (i = 0; i < nummapheaders; i++)
{
if (!mapheaderinfo[i] || (mapheaderinfo[i]->menuflags & LF2_NOTIMEATTACK))
continue;
if (!mapheaderinfo[i]->mainrecord || mapheaderinfo[i]->mainrecord->time <= 0)
{
mapsunfinished++;
continue;
}
besttime += mapheaderinfo[i]->mainrecord->time;
#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); \
}
V_DrawString(20, 70, highlightflags, "Combined time records:");
#define DRAWAMOUNTSTAT(y, title, field) { \
V_DrawString(20, (y), highlightflags, title); \
unsigned amountval = kartstats.field; \
V_DrawRightAlignedString(BASEVIDWIDTH-16, (y), 0, va("%u", amountval)); \
}
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);
static void M_DrawStatsPlaytime(void)
{
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, 62, highlightflags, "Total Matches:");
V_DrawRightAlignedString(BASEVIDWIDTH-16, 62, 0, va("%i played", kartstats.matchesplayed));
if (mapsunfinished)
V_DrawRightAlignedString(BASEVIDWIDTH-16, 78, warningflags, va("(%d unfinished)", mapsunfinished));
else
V_DrawRightAlignedString(BASEVIDWIDTH-16, 78, recommendedflags, "(complete)");
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]));
V_DrawString(32, 78, V_ALLOWLOWERCASE, va("x %d/%d", M_CountEmblems(), numemblems+numextraemblems));
V_DrawSmallScaledPatch(20, 78, 0, W_CachePatchName("GOTITA", PU_STATIC));
// Nothing else to draw
if (kartstats.vanilla)
return;
M_DrawStatsMaps(statsLocation);
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
static void M_DrawStatsExtra(void)
{
DRAWTIMESTAT(42, "Time being SPB target:", spbtargettime);
DRAWTIMESTAT(52, "Time spent in spinout:", spinouttime);
DRAWAMOUNTSTAT(72, "Total wins:", totalwins);
DRAWAMOUNTSTAT(82, "Total podium (2nd/3rd place):", totalpodium);
DRAWAMOUNTSTAT(102, "Hits landed:", hits);
DRAWAMOUNTSTAT(112, "Self-hits landed:", selfhits);
DRAWAMOUNTSTAT(132, "Sinks landed:", sinks);
DRAWAMOUNTSTAT(142, "Times hit by sink:", sinked);
DRAWAMOUNTSTAT(162, "Total respawns:", respawns);
}
#undef DRAWAMOUNTSTAT
#undef DRAWTIMESTAT
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.
@ -7731,23 +7793,46 @@ static void M_HandleLevelStats(INT32 choice)
switch (choice)
{
case KEY_DOWNARROW:
if (statsCurrentPage != 1) // Must be on level stats page
break;
S_StartSound(NULL, sfx_menu1);
if (statsLocation < statsMax)
++statsLocation;
break;
case KEY_UPARROW:
if (statsCurrentPage != 1) // 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 != 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 != 1) // Must be on level stats page
break;
S_StartSound(NULL, sfx_menu1);
statsLocation -= (statsLocation < 13) ? statsLocation : 13;
break;
@ -10249,8 +10334,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();

View file

@ -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();
}

View file

@ -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

View file

@ -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();