From 65f679c0bcbd03c24ac0b7d1b1c3825dd1ba5f23 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 25 May 2023 17:38:33 +0100 Subject: [PATCH] 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. --- src/dehacked.c | 30 +++++++++ src/doomstat.h | 12 ++++ src/g_game.c | 170 ++++++++++++++++++++++++++++++++++++++----------- src/typedef.h | 1 + 4 files changed, 176 insertions(+), 37 deletions(-) diff --git a/src/dehacked.c b/src/dehacked.c index ea35a07e5..b46805a97 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -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) diff --git a/src/doomstat.h b/src/doomstat.h index 2e06e5ed5..b5c0ea4f7 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -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 diff --git a/src/g_game.c b/src/g_game.c index c1a01ad80..aca6206a3 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -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; diff --git a/src/typedef.h b/src/typedef.h index afb3aba00..9253a6807 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -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);