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
This commit is contained in:
toaster 2023-05-28 20:29:40 +01:00
parent aa35e249d5
commit 1527471678
8 changed files with 221 additions and 4 deletions

View file

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

View file

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

View file

@ -531,6 +531,8 @@ void M_ClearStats(void)
gamedata->everseenspecial = false;
gamedata->evercrashed = false;
gamedata->musicflags = 0;
gamedata->importprofilewins = false;
}
void M_ClearSecrets(void)

View file

@ -282,6 +282,9 @@ struct gamedata_t
boolean everseenspecial;
boolean evercrashed;
UINT8 musicflags;
// BACKWARDS COMPAT ASSIST
boolean importprofilewins;
};
extern gamedata_t *gamedata;

View file

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

View file

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

View file

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

View file

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