Unloaded cupheader windata tracking

IMPORTANT NOTE - increments gamedata minor version to 3, as it is necessary to backfill windata to prevent losing existing cup records.

This is essentially commit 1482fd5 but for cups.
- All cups currently loaded with nonzero windata are written on gamedata save.
    - This reduces gamedata size for a player who has not unlocked every cup!
- On gamedata load, if a cup is not loaded, store the extra windata on a linked list.
- On cup header creation, check the linked list to see if an associated unloaded cupheader windata exists.
    - If it does, write the record onto the cup structure directly, and delete the "unloaded cupheader" storage struct!
- Then on the NEXT gamedata save, in addition to all loaded cupheaders, it writes the extra windata kept in long term storage in exactly the same format.
This commit is contained in:
toaster 2023-05-25 17:38:33 +01:00
parent b186e89353
commit 65f679c0bc
4 changed files with 176 additions and 37 deletions

View file

@ -494,6 +494,36 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
deh_strlcpy(cup->name, word2,
sizeof(cup->name), va("Cup header %s: name", word2));
cup->namehash = hash;
// Check to see if we have any custom cup record data that we could substitute in.
unloaded_cupheader_t *unloadedcup, *unloadedprev = NULL;
for (unloadedcup = unloadedcupheaders; unloadedcup; unloadedprev = unloadedcup, unloadedcup = unloadedcup->next)
{
if (unloadedcup->namehash != hash)
continue;
if (strcasecmp(word2, unloadedcup->name) != 0)
continue;
// Copy in standings, etc.
M_Memcpy(&cup->windata, &unloadedcup->windata, sizeof(cup->windata));
// Remove this entry from the chain.
if (unloadedprev)
{
unloadedprev->next = unloadedcup->next;
}
else
{
unloadedcupheaders = unloadedcup->next;
}
// Finally, free.
Z_Free(unloadedcup);
break;
}
if (prev != NULL)
prev->next = cup;
if (kartcupheaders == NULL)

View file

@ -387,6 +387,18 @@ struct cupheader_t
extern cupheader_t *kartcupheaders; // Start of cup linked list
extern UINT16 numkartcupheaders;
struct unloaded_cupheader_t
{
char name[MAXCUPNAME];
UINT32 namehash;
cupwindata_t windata[4];
unloaded_cupheader_t *next;
};
extern unloaded_cupheader_t *unloadedcupheaders;
#define MAXMAPLUMPNAME 64 // includes \0, for cleaner savedata
#define MAXSTAFF 3

View file

@ -206,6 +206,8 @@ unloaded_mapheader_t *unloadedmapheaders = NULL;
cupheader_t *kartcupheaders = NULL;
UINT16 numkartcupheaders = 0;
unloaded_cupheader_t *unloadedcupheaders = NULL;
static boolean exitgame = false;
static boolean retrying = false;
static boolean retryingmodeattack = false;
@ -456,20 +458,28 @@ void G_ClearRecords(void)
memset(&mapheaderinfo[i]->records, 0, sizeof(recorddata_t));
}
unloaded_mapheader_t *unloadedmap, *next = NULL;
for (unloadedmap = unloadedmapheaders; unloadedmap; unloadedmap = next)
{
next = unloadedmap->next;
Z_Free(unloadedmap->lumpname);
Z_Free(unloadedmap);
}
unloadedmapheaders = NULL;
cupheader_t *cup;
for (cup = kartcupheaders; cup; cup = cup->next)
{
memset(&cup->windata, 0, sizeof(cup->windata));
}
unloaded_mapheader_t *unloadedmap, *nextunloadedmap = NULL;
for (unloadedmap = unloadedmapheaders; unloadedmap; unloadedmap = nextunloadedmap)
{
nextunloadedmap = unloadedmap->next;
Z_Free(unloadedmap->lumpname);
Z_Free(unloadedmap);
}
unloadedmapheaders = NULL;
unloaded_cupheader_t *unloadedcup, *nextunloadedcup = NULL;
for (unloadedcup = unloadedcupheaders; unloadedcup; unloadedcup = nextunloadedcup)
{
nextunloadedcup = unloadedcup->next;
Z_Free(unloadedcup);
}
unloadedcupheaders = NULL;
}
// For easy retrieval of records
@ -4743,7 +4753,7 @@ void G_LoadGameSettings(void)
}
#define GD_VERSIONCHECK 0xBA5ED123 // Change every major version, as usual
#define GD_VERSIONMINOR 2 // Change every format update
#define GD_VERSIONMINOR 3 // Change every format update
static const char *G_GameDataFolder(void)
{
@ -4991,6 +5001,8 @@ void G_LoadGameData(void)
char cupname[MAXCUPNAME];
cupheader_t *cup;
cupwindata_t dummywindata[4];
// Find the relevant cup.
READSTRINGN(save.p, cupname, sizeof(cupname));
UINT32 hash = quickncasehash(cupname, MAXCUPNAME);
@ -5010,19 +5022,45 @@ void G_LoadGameData(void)
{
rtemp = READUINT8(save.p);
// ...but only record it if we actually found the associated cup.
if (cup)
{
cup->windata[j].best_placement = (rtemp & 0x0F);
cup->windata[j].best_grade = (rtemp & 0x70)>>4;
if (rtemp & 0x80)
{
if (j == 0)
goto datacorrupt;
dummywindata[j].best_placement = (rtemp & 0x0F);
dummywindata[j].best_grade = (rtemp & 0x70)>>4;
if (rtemp & 0x80)
dummywindata[j].got_emerald = true;
}
cup->windata[j].got_emerald = true;
}
}
if (versionMinor < 3 && dummywindata[0].best_placement == 0)
{
// We now require backfilling of placement information.
M_Memcpy(&dummywindata[0], &dummywindata[1], sizeof(dummywindata[0]));
}
if (cup)
{
// We found a cup, so assign the windata.
M_Memcpy(&cup->windata, &dummywindata, sizeof(cup->windata));
}
else if (dummywindata[0].best_placement != 0)
{
// Invalid, but we don't want to lose all the juicy statistics.
// Instead, update a FILO linked list of "unloaded cupheaders".
unloaded_cupheader_t *unloadedcup =
Z_Malloc(
sizeof(unloaded_cupheader_t),
PU_STATIC, NULL
);
// Establish properties, for later retrieval on file add.
strlcpy(unloadedcup->name, cupname, sizeof(unloadedcup->name));
unloadedcup->namehash = quickncasehash(cupname, MAXCUPNAME);
// Insert at the head, just because it's convenient.
unloadedcup->next = unloadedcupheaders;
unloadedcupheaders = unloadedcup;
// Finally, copy into.
M_Memcpy(&unloadedcup->windata, &dummywindata, sizeof(unloadedcup->windata));
}
}
}
@ -5087,7 +5125,7 @@ void G_DirtyGameData(void)
void G_SaveGameData(void)
{
size_t length;
INT32 i, j, numcups;
INT32 i, j;
cupheader_t *cup;
UINT8 btemp;
savebuffer_t save = {0};
@ -5152,12 +5190,37 @@ void G_SaveGameData(void)
length += 4 + (numgamedatamapheaders * (MAXMAPLUMPNAME+1+4+4));
numcups = 0;
UINT32 numgamedatacups = 0;
unloaded_cupheader_t *unloadedcup;
for (cup = kartcupheaders; cup; cup = cup->next)
{
numcups++;
// Results are populated downwards, so no Easy win
// means there's no important player data to save.
if (cup->windata[0].best_placement == 0)
continue;
numgamedatacups++;
}
length += 4 + (numcups * (4+16));
for (unloadedcup = unloadedcupheaders; unloadedcup; unloadedcup = unloadedcup->next)
{
// Ditto, with the exception that we should warn about it.
if (unloadedcup->windata[0].best_placement == 0)
{
CONS_Alert(CONS_WARNING, "Unloaded cup \"%s\" has no windata!\n", unloadedcup->name);
continue;
}
// It's far off on the horizon, beyond many memory limits, but prevent a potential misery moment of losing ALL your data.
if (++numgamedatacups == UINT32_MAX)
{
CONS_Alert(CONS_WARNING, "Some unloaded cup standings data has been dropped due to datatype limitations.\n");
break;
}
}
length += 4 + (numgamedatacups * (4+16));
if (P_SaveBufferAlloc(&save, length) == false)
{
@ -5298,22 +5361,55 @@ void G_SaveGameData(void)
}
}
WRITEUINT32(save.p, numcups); // 4
for (cup = kartcupheaders; cup; cup = cup->next)
WRITEUINT32(save.p, numgamedatacups); // 4
{
// For figuring out which header to assign it to on load
WRITESTRINGN(save.p, cup->name, 16);
// (numgamedatacups * (4+16))
for (i = 0; i < KARTGP_MAX; i++)
{
btemp = min(cup->windata[i].best_placement, 0x0F);
btemp |= (cup->windata[i].best_grade<<4);
if (i != 0 && cup->windata[i].got_emerald == true)
btemp |= 0x80;
UINT32 writtengamedatacups = 0;
WRITEUINT8(save.p, btemp); // 4 * numcups
#define WRITECUPWINDATA(maybeunloadedcup) \
for (i = 0; i < KARTGP_MAX; i++) \
{ \
btemp = min(maybeunloadedcup->windata[i].best_placement, 0x0F); \
btemp |= (maybeunloadedcup->windata[i].best_grade<<4); \
if (maybeunloadedcup->windata[i].got_emerald == true) \
btemp |= 0x80; \
\
WRITEUINT8(save.p, btemp); \
}
for (cup = kartcupheaders; cup; cup = cup->next)
{
if (cup->windata[0].best_placement == 0)
continue;
WRITESTRINGN(save.p, cup->name, MAXCUPNAME);
WRITECUPWINDATA(cup);
if (++writtengamedatacups >= numgamedatacups)
break;
}
if (writtengamedatacups < numgamedatacups)
{
for (unloadedcup = unloadedcupheaders; unloadedcup; unloadedcup = unloadedcup->next)
{
if (unloadedcup->windata[0].best_placement == 0)
continue;
WRITESTRINGN(save.p, unloadedcup->name, MAXCUPNAME);
WRITECUPWINDATA(unloadedcup);
if (++writtengamedatacups >= numgamedatacups)
break;
}
}
#undef WRITECUPWINDATA
}
length = save.p - save.buffer;

View file

@ -128,6 +128,7 @@ TYPEDEF (mapheader_t);
TYPEDEF (unloaded_mapheader_t);
TYPEDEF (tolinfo_t);
TYPEDEF (cupheader_t);
TYPEDEF (unloaded_cupheader_t);
// font.h
TYPEDEF (font_t);