From 1527471678331a61beb672da792f908f58fc051d Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 28 May 2023 20:29:40 +0100 Subject: [PATCH] Character win records Track wins per character. - If you have an existing gamedata of minor version 2 or earlier, attempt to import your last-used profile's wins onto the character you last used on that profile, so you're not starting from nothing. It's not quite accurate, but it's something. - Much like map headers and cups, these are also tracked in an unloaded_skins_t linked list. That work was done in this commit because gamedata is actually loaded before even base-game characters, which is annoying but at least confirmed it was working quicker. - TEMPORARY: Your per-character wins are displayed in your latest-log.txt, in lieu of an update to the in-game statistics menu --- src/d_main.c | 15 ++++++ src/g_game.c | 132 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/m_cond.c | 2 + src/m_cond.h | 3 ++ src/p_user.c | 16 ++++-- src/r_skins.c | 36 ++++++++++++++ src/r_skins.h | 19 ++++++++ src/typedef.h | 2 + 8 files changed, 221 insertions(+), 4 deletions(-) diff --git a/src/d_main.c b/src/d_main.c index 1b562b238..89284a677 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -1851,6 +1851,21 @@ void D_SRB2Main(void) { PR_ApplyProfile(cv_ttlprofilen.value, 0); + if (gamedata->importprofilewins == true) + { + profile_t *pr = PR_GetProfile(cv_ttlprofilen.value); + if (pr != NULL) + { + INT32 importskin = R_SkinAvailable(pr->skinname); + if (importskin != -1) + { + CONS_Printf(" Wins for profile \"%s\" imported onto character \"%s\"\n", pr->profilename, skins[importskin].name); + skins[importskin].records.wins = pr->wins; + } + } + gamedata->importprofilewins = false; + } + for (i = 1; i < cv_splitplayers.value; i++) { PR_ApplyProfile(cv_lastprofile[i].value, i); diff --git a/src/g_game.c b/src/g_game.c index 5cb13e080..331dffbf9 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -453,6 +453,11 @@ void G_ClearRecords(void) { UINT16 i; + for (i = 0; i < numskins; i++) + { + memset(&skins[i].records, 0, sizeof(skins[i].records)); + } + for (i = 0; i < nummapheaders; i++) { memset(&mapheaderinfo[i]->records, 0, sizeof(recorddata_t)); @@ -464,6 +469,14 @@ void G_ClearRecords(void) memset(&cup->windata, 0, sizeof(cup->windata)); } + unloaded_skin_t *unloadedskin, *nextunloadedskin = NULL; + for (unloadedskin = unloadedskins; unloadedskin; unloadedskin = nextunloadedskin) + { + nextunloadedskin = unloadedskin->next; + Z_Free(unloadedskin); + } + unloadedskins = NULL; + unloaded_mapheader_t *unloadedmap, *nextunloadedmap = NULL; for (unloadedmap = unloadedmapheaders; unloadedmap; unloadedmap = nextunloadedmap) { @@ -4775,6 +4788,7 @@ void G_LoadGameData(void) savebuffer_t save = {0}; //For records + UINT32 numgamedataskins; UINT32 numgamedatamapheaders; UINT32 numgamedatacups; @@ -4940,6 +4954,58 @@ void G_LoadGameData(void) gamedata->timesBeaten = READUINT32(save.p); // Main records + + if (versionMinor < 3) + gamedata->importprofilewins = true; + else + { + numgamedataskins = READUINT32(save.p); + + for (i = 0; i < numgamedataskins; i++) + { + char skinname[SKINNAMESIZE+1]; + INT32 skin; + + READSTRINGN(save.p, skinname, SKINNAMESIZE); + skin = R_SkinAvailable(skinname); + + skinrecord_t dummyrecord; + + dummyrecord.wins = READUINT32(save.p); + + CONS_Printf(" (TEMPORARY DISPLAY) skinname is \"%s\", has %u wins\n", skinname, dummyrecord.wins); + + if (skin != -1) + { + // We found a skin, so assign the win. + + M_Memcpy(&skins[skin].records, &dummyrecord, sizeof(skinrecord_t)); + } + else if (dummyrecord.wins) + { + // Invalid, but we don't want to lose all the juicy statistics. + // Instead, update a FILO linked list of "unloaded skins". + + unloaded_skin_t *unloadedskin = + Z_Malloc( + sizeof(unloaded_skin_t), + PU_STATIC, NULL + ); + + // Establish properties, for later retrieval on file add. + strlcpy(unloadedskin->name, skinname, sizeof(unloadedskin->name)); + unloadedskin->namehash = quickncasehash(unloadedskin->name, SKINNAMESIZE); + + // Insert at the head, just because it's convenient. + unloadedskin->next = unloadedskins; + unloadedskins = unloadedskin; + + // Finally, copy into. + M_Memcpy(&unloadedskin->records, &dummyrecord, sizeof(skinrecord_t)); + } + } + } + numgamedatamapheaders = READUINT32(save.p); for (i = 0; i < numgamedatamapheaders; i++) @@ -5157,6 +5223,36 @@ void G_SaveGameData(void) length += gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT; } + + UINT32 numgamedataskins = 0; + unloaded_skin_t *unloadedskin; + + for (i = 0; i < numskins; i++) + { + // It's safe to assume a skin with no wins will have no other data worth keeping + if (skins[i].records.wins == 0) + { + continue; + } + + numgamedataskins++; + } + + for (unloadedskin = unloadedskins; unloadedskin; unloadedskin = unloadedskin->next) + { + // Ditto, with the exception that we should warn about it. + if (unloadedskin->records.wins == 0) + { + CONS_Alert(CONS_WARNING, "Unloaded skin \"%s\" has no wins!\n", unloadedskin->name); + continue; + } + + numgamedataskins++; + } + + length += 4 + (numgamedataskins * (SKINNAMESIZE+4)); + + UINT32 numgamedatamapheaders = 0; unloaded_mapheader_t *unloadedmap; @@ -5313,6 +5409,42 @@ void G_SaveGameData(void) WRITEUINT32(save.p, gamedata->timesBeaten); // 4 // Main records + + WRITEUINT32(save.p, numgamedataskins); // 4 + + { + // numgamedataskins * (SKINNAMESIZE+4) + + for (i = 0; i < numskins; i++) + { + if (skins[i].records.wins == 0) + continue; + + WRITESTRINGN(save.p, skins[i].name, SKINNAMESIZE); + + WRITEUINT32(save.p, skins[i].records.wins); + + if (--numgamedataskins == 0) + break; + } + + if (numgamedataskins) + { + for (unloadedskin = unloadedskins; unloadedskin; unloadedskin = unloadedskin->next) + { + if (unloadedskin->records.wins == 0) + continue; + + WRITESTRINGN(save.p, unloadedskin->name, SKINNAMESIZE); + + WRITEUINT32(save.p, unloadedskin->records.wins); + + if (--numgamedataskins == 0) + break; + } + } + } + WRITEUINT32(save.p, numgamedatamapheaders); // 4 if (numgamedatamapheaders) diff --git a/src/m_cond.c b/src/m_cond.c index 62eda53c0..2a016d30f 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -531,6 +531,8 @@ void M_ClearStats(void) gamedata->everseenspecial = false; gamedata->evercrashed = false; gamedata->musicflags = 0; + + gamedata->importprofilewins = false; } void M_ClearSecrets(void) diff --git a/src/m_cond.h b/src/m_cond.h index 9fdb5bd2e..a4fe978bd 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -282,6 +282,9 @@ struct gamedata_t boolean everseenspecial; boolean evercrashed; UINT8 musicflags; + + // BACKWARDS COMPAT ASSIST + boolean importprofilewins; }; extern gamedata_t *gamedata; diff --git a/src/p_user.c b/src/p_user.c index 3afb5b7bf..dd2179fce 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -1355,11 +1355,19 @@ void P_DoPlayerExit(player_t *player, pflags_t flags) G_UpdateRecords(); } - profile_t *pr = PR_GetPlayerProfile(player); - if (pr != NULL && !losing) + if (!losing) { - pr->wins++; - PR_SaveProfiles(); + profile_t *pr = PR_GetPlayerProfile(player); + if (pr != NULL) + { + pr->wins++; + PR_SaveProfiles(); + } + + if (P_IsLocalPlayer(player) && player->skin < numskins) + { + skins[player->skin].records.wins++; + } } player->karthud[khud_cardanimation] = 0; // srb2kart: reset battle animation diff --git a/src/r_skins.c b/src/r_skins.c index 49d1bef7f..cddd8d116 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -41,6 +41,8 @@ INT32 numskins = 0; skin_t skins[MAXSKINS]; +unloaded_skin_t *unloadedskins = NULL; + // FIXTHIS: don't work because it must be inistilised before the config load //#define SKINVALUES #ifdef SKINVALUES @@ -125,6 +127,8 @@ static void Sk_SetDefaultValue(skin_t *skin) skin->highresscale = FRACUNIT; + // no specific memset for skinrecord_t as it's already nuked by the full skin_t wipe + for (i = 0; i < sfx_skinsoundslot0; i++) if (S_sfx[i].skinsound != -1) skin->soundsid[S_sfx[i].skinsound] = i; @@ -1006,6 +1010,38 @@ next_token: // Finally, conclude by setting up final properties. skin->namehash = quickncasehash(skin->name, SKINNAMESIZE); + { + // Check to see if we have any custom skin wins data that we could substitute in. + unloaded_skin_t *unloadedskin, *unloadedprev = NULL; + for (unloadedskin = unloadedskins; unloadedskin; unloadedprev = unloadedskin, unloadedskin = unloadedskin->next) + { + if (unloadedskin->namehash != skin->namehash) + continue; + + if (strcasecmp(skin->name, unloadedskin->name) != 0) + continue; + + // Copy in wins, etc. + M_Memcpy(&skin->records, &unloadedskin->records, sizeof(skin->records)); + + // Remove this entry from the chain. + if (unloadedprev) + { + unloadedprev->next = unloadedskin->next; + } + else + { + unloadedskins = unloadedskin->next; + } + + // Finally, free. + Z_Free(unloadedskin); + + break; + + } + } + numskins++; } return; diff --git a/src/r_skins.h b/src/r_skins.h index 73d8b609e..ec393b744 100644 --- a/src/r_skins.h +++ b/src/r_skins.h @@ -34,6 +34,11 @@ extern "C" { #define DEFAULTSKIN3 "sonic" // third player #define DEFAULTSKIN4 "knuckles" // fourth player +struct skinrecord_t +{ + UINT32 wins; +}; + /// The skin_t struct struct skin_t { @@ -59,6 +64,8 @@ struct skin_t fixed_t highresscale; // scale of highres, default is 0.5 + skinrecord_t records; + char rivals[SKINRIVALS][SKINNAMESIZE+1]; // Your top 3 rivals for GP mode. Uses names so that you can reference skins that aren't added // specific sounds per skin @@ -69,6 +76,18 @@ struct skin_t spriteinfo_t sprinfo[NUMPLAYERSPRITES*2]; }; +struct unloaded_skin_t +{ + char name[SKINNAMESIZE+1]; + UINT32 namehash; + + skinrecord_t records; + + unloaded_skin_t *next; +}; + +extern unloaded_skin_t *unloadedskins; + enum facepatches { FACE_RANK = 0, FACE_WANTED, diff --git a/src/typedef.h b/src/typedef.h index 9253a6807..e8567f3ed 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -385,7 +385,9 @@ TYPEDEF (visffloor_t); TYPEDEF (portal_t); // r_skins.h +TYPEDEF (skinrecord_t); TYPEDEF (skin_t); +TYPEDEF (unloaded_skin_t); // r_splats.h TYPEDEF (floorsplat_t);