From 533508d7b060e42000e339a8be52b27deff76c38 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 23 May 2023 17:41:32 +0100 Subject: [PATCH 01/31] Level and cup header information: Hashing for referencing by name Foundational assistive work. Also guarantees a consistent cup name length in memory, as some places read/wrote 15 bytes, and some places read/wrote 16 bytes. We settle on the latter (including null terminator) for the broadest backwards compatibility. --- src/deh_soc.c | 4 +++- src/dehacked.c | 64 ++++++++++++++++++++++++++++++-------------------- src/doomstat.h | 8 ++++++- src/g_game.c | 11 ++++++++- src/m_cond.c | 3 ++- 5 files changed, 60 insertions(+), 30 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index 751c3c07a..2bd662828 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -953,6 +953,7 @@ void readlevelheader(MYFILE *f, char * name) if (mapheaderinfo[num]->lumpname == NULL) { mapheaderinfo[num]->lumpname = Z_StrDup(name); + mapheaderinfo[num]->lumpnamehash = quickncasehash(mapheaderinfo[num]->lumpname, MAXMAPLUMPNAME); } do @@ -2638,9 +2639,10 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) ty = UCRP_PODIUMCUP; { cupheader_t *cup = kartcupheaders; + UINT32 hash = quickncasehash(params[1], MAXCUPNAME); while (cup) { - if (!strcmp(cup->name, params[1])) + if (hash == cup->namehash && !strcmp(cup->name, params[1])) break; cup = cup->next; } diff --git a/src/dehacked.c b/src/dehacked.c index 9e50a9f42..ea35a07e5 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -465,39 +465,51 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile) // else if (fastcmp(word, "CUP")) { - cupheader_t *cup = kartcupheaders; - cupheader_t *prev = NULL; - - while (cup) + size_t len = strlen(word2); + if (len <= MAXCUPNAME-1) { - if (fastcmp(cup->name, word2)) + cupheader_t *cup = kartcupheaders; + cupheader_t *prev = NULL; + UINT32 hash = quickncasehash(word2, MAXCUPNAME); + + while (cup) { - // Only a major mod if editing stuff that isn't your own! - G_SetGameModified(multiplayer, true); - break; + if (hash == cup->namehash && fastcmp(cup->name, word2)) + { + // Only a major mod if editing stuff that isn't your own! + G_SetGameModified(multiplayer, true); + break; + } + + prev = cup; + cup = cup->next; } - prev = cup; - cup = cup->next; - } + // Nothing found, add to the end. + if (!cup) + { + cup = Z_Calloc(sizeof (cupheader_t), PU_STATIC, NULL); + cup->id = numkartcupheaders; + cup->monitor = 1; + deh_strlcpy(cup->name, word2, + sizeof(cup->name), va("Cup header %s: name", word2)); + cup->namehash = hash; + if (prev != NULL) + prev->next = cup; + if (kartcupheaders == NULL) + kartcupheaders = cup; + numkartcupheaders++; + CONS_Printf("Added cup %d ('%s')\n", cup->id, cup->name); + } - // Nothing found, add to the end. - if (!cup) + readcupheader(f, cup); + + } + else { - cup = Z_Calloc(sizeof (cupheader_t), PU_STATIC, NULL); - cup->id = numkartcupheaders; - cup->monitor = 1; - deh_strlcpy(cup->name, word2, - sizeof(cup->name), va("Cup header %s: name", word2)); - if (prev != NULL) - prev->next = cup; - if (kartcupheaders == NULL) - kartcupheaders = cup; - numkartcupheaders++; - CONS_Printf("Added cup %d ('%s')\n", cup->id, cup->name); + deh_warning("Cup header's name %s is too long (%s characters VS %d max)", word2, sizeu1(len), (MAXCUPNAME-1)); + ignorelines(f); } - - readcupheader(f, cup); } else if (fastcmp(word, "WEATHER") || fastcmp(word, "PRECIP") || fastcmp(word, "PRECIPITATION")) { diff --git a/src/doomstat.h b/src/doomstat.h index 77317fd0a..ab73c70dc 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -363,11 +363,16 @@ struct customoption_t #define CUPCACHE_SPECIAL (CUPCACHE_BONUS+MAXBONUSLIST) #define CUPCACHE_MAX (CUPCACHE_SPECIAL+1) +#define MAXCUPNAME 16 // includes \0, for cleaner savedata + struct cupheader_t { UINT16 id; ///< Cup ID UINT8 monitor; ///< Monitor graphic 1-9 or A-Z - char name[15]; ///< Cup title (14 chars) + + char name[MAXCUPNAME]; ///< Cup title + UINT32 namehash; ///< Cup title hash + char icon[9]; ///< Name of the icon patch char *levellist[CUPCACHE_MAX]; ///< List of levels that belong to this cup INT16 cachedlevels[CUPCACHE_MAX]; ///< IDs in levellist, bonusgame, and specialstage @@ -400,6 +405,7 @@ struct mapheader_t { // Core game information, not user-modifiable directly char *lumpname; ///< Lump name can be really long + UINT32 lumpnamehash; ///< quickncasehash(->lumpname, MAXMAPLUMPNAME) lumpnum_t lumpnum; ///< Lump number for the map, used by vres_GetMap void *thumbnailPic; ///< Lump data for the level select thumbnail. diff --git a/src/g_game.c b/src/g_game.c index 9f75d137a..cf2e4b184 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -807,9 +807,13 @@ INT32 G_MapNumber(const char * name) #endif { INT32 map; + UINT32 hash = quickncasehash(name, MAXMAPLUMPNAME); for (map = 0; map < nummapheaders; ++map) { + if (hash != mapheaderinfo[map]->lumpnamehash) + continue; + if (strcasecmp(mapheaderinfo[map]->lumpname, name) != 0) continue; @@ -4980,15 +4984,20 @@ void G_LoadGameData(void) for (i = 0; i < numgamedatacups; i++) { - char cupname[16]; + char cupname[MAXCUPNAME]; cupheader_t *cup; // Find the relevant cup. READSTRINGN(save.p, cupname, sizeof(cupname)); + UINT32 hash = quickncasehash(cupname, MAXCUPNAME); for (cup = kartcupheaders; cup; cup = cup->next) { + if (cup->namehash != hash) + continue; + if (strcmp(cup->name, cupname)) continue; + break; } diff --git a/src/m_cond.c b/src/m_cond.c index 5b551f646..41510210c 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -2064,9 +2064,10 @@ cupheader_t *M_UnlockableCup(unlockable_t *unlock) if (unlock->stringVarCache == -1) { // Get the cup from the string. + UINT32 hash = quickncasehash(unlock->stringVar, MAXCUPNAME); while (cup) { - if (!strcmp(cup->name, unlock->stringVar)) + if (hash == cup->namehash && !strcmp(cup->name, unlock->stringVar)) break; cup = cup->next; } From 4c34d04f8ae681c1d690d7102db70c63dd01d7f3 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 24 May 2023 00:18:12 +0100 Subject: [PATCH 02/31] recorddata_t handling adjustments Preperatory work for the next feature on my agenda. - No longer independently allocated. - This was a byproduct of the previous NUMMAPS-based implementation. It's just cleaner to have it live directly on the mapheader_t, no caveats about it. - Now contains mapvisited bitflag array. - Now all to-gamedata properties on a mapheader's struct are grouped together. - I was of two minds about it, but decided that this would have cleaner guarantees for compartmentalisation, saving, and loading. - They can still be wiped independently (G_ClearRecords for time/lap and M_ClearSecrets for mapvisited). --- src/deh_soc.c | 2 - src/doomstat.h | 22 +++++----- src/g_game.c | 68 +++++++++--------------------- src/g_game.h | 1 - src/k_menudraw.c | 11 ++--- src/m_cond.c | 12 +++--- src/menus/extras-statistics.c | 2 +- src/menus/transient/level-select.c | 2 +- src/p_setup.c | 7 +-- src/s_sound.c | 2 +- 10 files changed, 46 insertions(+), 83 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index 2bd662828..d5fb03cd3 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -192,8 +192,6 @@ void clear_levels(void) P_DeleteHeaderFollowers(nummapheaders); - Z_Free(mapheaderinfo[nummapheaders]->mainrecord); - Patch_Free(mapheaderinfo[nummapheaders]->thumbnailPic); Patch_Free(mapheaderinfo[nummapheaders]->minimapPic); diff --git a/src/doomstat.h b/src/doomstat.h index ab73c70dc..4983442c2 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -112,12 +112,19 @@ extern preciptype_t curWeather; /** Time attack information, currently a very small structure. */ + +// mapvisited is now a set of flags that says what we've done in the map. +#define MV_VISITED (1) +#define MV_BEATEN (1<<1) +#define MV_ENCORE (1<<2) +#define MV_SPBATTACK (1<<3) +#define MV_MAX (MV_VISITED|MV_BEATEN|MV_ENCORE|MV_SPBATTACK) + struct recorddata_t { + UINT8 mapvisited; tic_t time; ///< Time in which the level was finished. tic_t lap; ///< Best lap time for this level. - //UINT32 score; ///< Score when the level was finished. - //UINT16 rings; ///< Rings when the level was finished. }; #define KARTSPEED_AUTO -1 @@ -144,14 +151,6 @@ struct cupwindata_t boolean got_emerald; }; -// mapvisited is now a set of flags that says what we've done in the map. -#define MV_VISITED (1) -#define MV_BEATEN (1<<1) -#define MV_ENCORE (1<<2) -#define MV_SPBATTACK (1<<3) -#define MV_MAX (MV_VISITED|MV_BEATEN|MV_ENCORE|MV_SPBATTACK) -#define MV_MP ((MV_MAX+1)<<1) - // Set if homebrew PWAD stuff has been added. extern boolean modifiedgame; extern boolean majormods; @@ -417,8 +416,7 @@ struct mapheader_t UINT8 ghostCount; ///< Count of valid staff ghosts staffbrief_t *ghostBrief[MAXSTAFF]; ///< Mallocated array of names for each staff ghost - UINT8 mapvisited; ///< A set of flags that says what we've done in the map. - recorddata_t *mainrecord; ///< Stores best time attack data + recorddata_t records; ///< Stores completion/record attack data cupheader_t *cup; ///< Cached cup diff --git a/src/g_game.c b/src/g_game.c index cf2e4b184..bf4537fa7 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -444,16 +444,6 @@ consvar_t cv_rumble[MAXSPLITSCREENPLAYERS] = { char player_names[MAXPLAYERS][MAXPLAYERNAME+1]; INT32 player_name_changes[MAXPLAYERS]; -// Allocation for time and nights data -void G_AllocMainRecordData(INT16 i) -{ - if (i > nummapheaders || !mapheaderinfo[i]) - I_Error("G_AllocMainRecordData: Internal map ID %d not found (nummapheaders = %d)\n", i, nummapheaders); - if (!mapheaderinfo[i]->mainrecord) - mapheaderinfo[i]->mainrecord = Z_Malloc(sizeof(recorddata_t), PU_STATIC, NULL); - memset(mapheaderinfo[i]->mainrecord, 0, sizeof(recorddata_t)); -} - // MAKE SURE YOU SAVE DATA BEFORE CALLING THIS void G_ClearRecords(void) { @@ -462,11 +452,8 @@ void G_ClearRecords(void) for (i = 0; i < nummapheaders; ++i) { - if (mapheaderinfo[i]->mainrecord) - { - Z_Free(mapheaderinfo[i]->mainrecord); - mapheaderinfo[i]->mainrecord = NULL; - } + mapheaderinfo[i]->records.time = 0; + mapheaderinfo[i]->records.lap = 0; } for (cup = kartcupheaders; cup; cup = cup->next) @@ -478,10 +465,10 @@ void G_ClearRecords(void) // For easy retrieval of records tic_t G_GetBestTime(INT16 map) { - if (!mapheaderinfo[map] || !mapheaderinfo[map]->mainrecord || mapheaderinfo[map]->mainrecord->time <= 0) + if (!mapheaderinfo[map] || mapheaderinfo[map]->records.time <= 0) return (tic_t)UINT32_MAX; - return mapheaderinfo[map]->mainrecord->time; + return mapheaderinfo[map]->records.time; } // BE RIGHT BACK @@ -490,10 +477,10 @@ tic_t G_GetBestTime(INT16 map) /* tic_t G_GetBestLap(INT16 map) { - if (!mapheaderinfo[map] || !mapheaderinfo[map]->mainrecord || mapheaderinfo[map]->mainrecord->lap <= 0) + if (!mapheaderinfo[map] || mapheaderinfo[map]->records.lap <= 0) return (tic_t)UINT32_MAX; - return mapheaderinfo[map]->mainrecord->lap; + return mapheaderinfo[map]->records.lap; } */ @@ -621,32 +608,28 @@ void G_UpdateRecords(void) { UINT8 earnedEmblems; - // Record new best time - if (!mapheaderinfo[gamemap-1]->mainrecord) - G_AllocMainRecordData(gamemap-1); - if (modeattacking & ATTACKING_TIME) { tic_t time = players[consoleplayer].realtime; if (players[consoleplayer].pflags & PF_NOCONTEST) time = UINT32_MAX; - if (((mapheaderinfo[gamemap-1]->mainrecord->time == 0) || (time < mapheaderinfo[gamemap-1]->mainrecord->time)) + if (((mapheaderinfo[gamemap-1]->records.time == 0) || (time < mapheaderinfo[gamemap-1]->records.time)) && (time < UINT32_MAX)) // DNF - mapheaderinfo[gamemap-1]->mainrecord->time = time; + mapheaderinfo[gamemap-1]->records.time = time; } else { - mapheaderinfo[gamemap-1]->mainrecord->time = 0; + mapheaderinfo[gamemap-1]->records.time = 0; } if (modeattacking & ATTACKING_LAP) { - if ((mapheaderinfo[gamemap-1]->mainrecord->lap == 0) || (bestlap < mapheaderinfo[gamemap-1]->mainrecord->lap)) - mapheaderinfo[gamemap-1]->mainrecord->lap = bestlap; + if ((mapheaderinfo[gamemap-1]->records.lap == 0) || (bestlap < mapheaderinfo[gamemap-1]->records.lap)) + mapheaderinfo[gamemap-1]->records.lap = bestlap; } else { - mapheaderinfo[gamemap-1]->mainrecord->lap = 0; + mapheaderinfo[gamemap-1]->records.lap = 0; } // Check emblems when level data is updated @@ -3986,16 +3969,16 @@ static void G_UpdateVisited(void) return; // Update visitation flags - mapheaderinfo[prevmap]->mapvisited |= MV_BEATEN; + mapheaderinfo[prevmap]->records.mapvisited |= MV_BEATEN; if (encoremode == true) { - mapheaderinfo[prevmap]->mapvisited |= MV_ENCORE; + mapheaderinfo[prevmap]->records.mapvisited |= MV_ENCORE; } if (modeattacking & ATTACKING_SPB) { - mapheaderinfo[prevmap]->mapvisited |= MV_SPBATTACK; + mapheaderinfo[prevmap]->records.mapvisited |= MV_SPBATTACK; } if (modeattacking) @@ -4959,14 +4942,13 @@ void G_LoadGameData(void) { // Valid mapheader, time to populate with record data. - if ((mapheaderinfo[mapnum]->mapvisited = rtemp) & ~MV_MAX) + if ((mapheaderinfo[mapnum]->records.mapvisited = rtemp) & ~MV_MAX) goto datacorrupt; if (rectime || reclap) { - G_AllocMainRecordData((INT16)i); - mapheaderinfo[i]->mainrecord->time = rectime; - mapheaderinfo[i]->mainrecord->lap = reclap; + mapheaderinfo[i]->records.time = rectime; + mapheaderinfo[i]->records.lap = reclap; //CONS_Printf("ID %d, Time = %d, Lap = %d\n", i, rectime/35, reclap/35); } } @@ -5219,18 +5201,10 @@ void G_SaveGameData(void) // For figuring out which header to assign it to on load WRITESTRINGN(save.p, mapheaderinfo[i]->lumpname, MAXMAPLUMPNAME); - WRITEUINT8(save.p, (mapheaderinfo[i]->mapvisited & MV_MAX)); + WRITEUINT8(save.p, (mapheaderinfo[i]->records.mapvisited & MV_MAX)); - if (mapheaderinfo[i]->mainrecord) - { - WRITEUINT32(save.p, mapheaderinfo[i]->mainrecord->time); - WRITEUINT32(save.p, mapheaderinfo[i]->mainrecord->lap); - } - else - { - WRITEUINT32(save.p, 0); - WRITEUINT32(save.p, 0); - } + WRITEUINT32(save.p, mapheaderinfo[i]->records.time); + WRITEUINT32(save.p, mapheaderinfo[i]->records.lap); } WRITEUINT32(save.p, numcups); // 4 diff --git a/src/g_game.h b/src/g_game.h index e7e028250..2f498134e 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -264,7 +264,6 @@ void G_SetGameModified(boolean silent, boolean major); void G_SetUsedCheats(void); // Gamedata record shit -void G_AllocMainRecordData(INT16 i); void G_ClearRecords(void); tic_t G_GetBestTime(INT16 map); diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 8c99f5e31..49b9c4f67 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -2640,11 +2640,8 @@ void M_DrawTimeAttack(void) if ((minimap = mapheaderinfo[map]->minimapPic)) V_DrawScaledPatch(24-t, 82, 0, minimap); - if (mapheaderinfo[map]->mainrecord) - { - timerec = mapheaderinfo[map]->mainrecord->time; - laprec = mapheaderinfo[map]->mainrecord->lap; - } + timerec = mapheaderinfo[map]->records.time; + laprec = mapheaderinfo[map]->records.lap; if ((gametypes[levellist.newgametype]->rules & GTR_CIRCUIT) && (mapheaderinfo[map]->numlaps != 1)) @@ -5779,13 +5776,13 @@ void M_DrawStatistics(void) if (!mapheaderinfo[i] || (mapheaderinfo[i]->menuflags & (LF2_NOTIMEATTACK|LF2_HIDEINSTATS|LF2_HIDEINMENU))) continue; - if (!mapheaderinfo[i]->mainrecord || mapheaderinfo[i]->mainrecord->time <= 0) + if (mapheaderinfo[i]->records.time <= 0) { mapsunfinished++; continue; } - besttime += mapheaderinfo[i]->mainrecord->time; + besttime += mapheaderinfo[i]->records.time; } V_DrawThinString(20, 60, V_6WIDTHSPACE|V_ALLOWLOWERCASE, "Combined time records:"); diff --git a/src/m_cond.c b/src/m_cond.c index 41510210c..4e64e6c43 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -539,7 +539,7 @@ void M_ClearSecrets(void) for (i = 0; i < nummapheaders; ++i) { - mapheaderinfo[i]->mapvisited = 0; + mapheaderinfo[i]->records.mapvisited = 0; } for (i = 0; i < MAXEMBLEMS; ++i) @@ -702,7 +702,7 @@ boolean M_CheckCondition(condition_t *cn, player_t *player) return ((cn->requirement < nummapheaders) && (mapheaderinfo[cn->requirement]) - && ((mapheaderinfo[cn->requirement]->mapvisited & mvtype) == mvtype)); + && ((mapheaderinfo[cn->requirement]->records.mapvisited & mvtype) == mvtype)); } case UC_MAPTIME: // Requires time on map <= x return (G_GetBestTime(cn->extrainfo1) <= (unsigned)cn->requirement); @@ -932,7 +932,7 @@ static char *M_BuildConditionTitle(UINT16 map) if (((mapheaderinfo[map]->menuflags & LF2_FINISHNEEDED) // the following is intentionally not MV_BEATEN, just in case the title is for "Finish a round on X" - && !(mapheaderinfo[map]->mapvisited & MV_VISITED)) + && !(mapheaderinfo[map]->records.mapvisited & MV_VISITED)) || M_MapLocked(map+1)) return Z_StrDup("???"); @@ -1770,7 +1770,7 @@ UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separa if (embtype & ME_SPBATTACK) flags |= MV_SPBATTACK; - res = ((mapheaderinfo[levelnum]->mapvisited & flags) == flags); + res = ((mapheaderinfo[levelnum]->records.mapvisited & flags) == flags); gamedata->collected[i] = res; if (res) @@ -1955,9 +1955,9 @@ UINT8 M_GotLowEnoughTime(INT32 tictime) if (!mapheaderinfo[i] || (mapheaderinfo[i]->menuflags & LF2_NOTIMEATTACK)) continue; - if (!mapheaderinfo[i]->mainrecord || !mapheaderinfo[i]->mainrecord->time) + if (!mapheaderinfo[i]->records.time) return false; - else if ((curtics += mapheaderinfo[i]->mainrecord->time) > tictime) + if ((curtics += mapheaderinfo[i]->records.time) > tictime) return false; } return true; diff --git a/src/menus/extras-statistics.c b/src/menus/extras-statistics.c index 356bf46b2..bc4843b27 100644 --- a/src/menus/extras-statistics.c +++ b/src/menus/extras-statistics.c @@ -26,7 +26,7 @@ static boolean M_StatisticsAddMap(UINT16 map, cupheader_t *cup, boolean *headere // Check for completion if ((mapheaderinfo[map]->menuflags & LF2_FINISHNEEDED) - && !(mapheaderinfo[map]->mapvisited & MV_BEATEN)) + && !(mapheaderinfo[map]->records.mapvisited & MV_BEATEN)) return false; // Check for unlock diff --git a/src/menus/transient/level-select.c b/src/menus/transient/level-select.c index db6e257bb..2aeb50f55 100644 --- a/src/menus/transient/level-select.c +++ b/src/menus/transient/level-select.c @@ -88,7 +88,7 @@ boolean M_CanShowLevelInList(INT16 mapnum, levelsearch_t *levelsearch) { // Check for completion if ((mapheaderinfo[mapnum]->menuflags & LF2_FINISHNEEDED) - && !(mapheaderinfo[mapnum]->mapvisited & MV_BEATEN)) + && !(mapheaderinfo[mapnum]->records.mapvisited & MV_BEATEN)) return false; // Check for unlock diff --git a/src/p_setup.c b/src/p_setup.c index 4153f889a..1542eed02 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -446,9 +446,7 @@ static void P_ClearSingleMapHeaderInfo(INT16 num) P_DeleteHeaderFollowers(num); #endif - mapheaderinfo[num]->mapvisited = 0; - Z_Free(mapheaderinfo[num]->mainrecord); - mapheaderinfo[num]->mainrecord = NULL; + memset(&mapheaderinfo[num]->records, 0, sizeof(recorddata_t)); mapheaderinfo[num]->justPlayed = 0; mapheaderinfo[num]->anger = 0; @@ -506,7 +504,6 @@ void P_AllocMapHeader(INT16 i) mapheaderinfo[i]->minimapPic = NULL; mapheaderinfo[i]->ghostCount = 0; mapheaderinfo[i]->cup = NULL; - mapheaderinfo[i]->mainrecord = NULL; mapheaderinfo[i]->followers = NULL; nummapheaders++; } @@ -8312,7 +8309,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) if (!demo.playback) { - mapheaderinfo[gamemap-1]->mapvisited |= MV_VISITED; + mapheaderinfo[gamemap-1]->records.mapvisited |= MV_VISITED; M_UpdateUnlockablesAndExtraEmblems(true, true); G_SaveGameData(); diff --git a/src/s_sound.c b/src/s_sound.c index 9f04c82e9..9a2fbe466 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -1534,7 +1534,7 @@ static boolean S_SoundTestDefLocked(musicdef_t *def) // Is the level tied to SP progression? if ((mapheaderinfo[def->sequence.map]->menuflags & LF2_FINISHNEEDED) - && !(mapheaderinfo[def->sequence.map]->mapvisited & MV_BEATEN)) + && !(mapheaderinfo[def->sequence.map]->records.mapvisited & MV_BEATEN)) return true; // Finally, do a full-fat map check. From 4682f03f917ebc8190e1db88ba0018184e6d7787 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 24 May 2023 16:58:45 +0100 Subject: [PATCH 03/31] On reflection, whether a map is visited should be cleared at the same time as its time attack record data, not seperately. --- src/g_game.c | 3 +-- src/m_cond.c | 5 ----- src/menus/options-data-erase-1.c | 4 ++-- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index bf4537fa7..b41da25d5 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -452,8 +452,7 @@ void G_ClearRecords(void) for (i = 0; i < nummapheaders; ++i) { - mapheaderinfo[i]->records.time = 0; - mapheaderinfo[i]->records.lap = 0; + memset(&mapheaderinfo[i]->records, 0, sizeof(recorddata_t)); } for (cup = kartcupheaders; cup; cup = cup->next) diff --git a/src/m_cond.c b/src/m_cond.c index 4e64e6c43..62eda53c0 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -537,11 +537,6 @@ void M_ClearSecrets(void) { INT32 i; - for (i = 0; i < nummapheaders; ++i) - { - mapheaderinfo[i]->records.mapvisited = 0; - } - for (i = 0; i < MAXEMBLEMS; ++i) gamedata->collected[i] = false; for (i = 0; i < MAXUNLOCKABLES; ++i) diff --git a/src/menus/options-data-erase-1.c b/src/menus/options-data-erase-1.c index 2af6f0ffb..88d5b474b 100644 --- a/src/menus/options-data-erase-1.c +++ b/src/menus/options-data-erase-1.c @@ -19,7 +19,7 @@ menuitem_t OPTIONS_DataErase[] = {IT_STRING | IT_CALL, "Erase Statistics Data", "Be careful! What's deleted is gone forever!", NULL, {.routine = M_EraseData}, EC_STATISTICS, 0}, - {IT_STRING | IT_CALL, "Erase GP & Time Attack Data", "Be careful! What's deleted is gone forever!", + {IT_STRING | IT_CALL, "Erase GP & Record Data", "Be careful! What's deleted is gone forever!", NULL, {.routine = M_EraseData}, EC_TIMEATTACK, 0}, {IT_STRING | IT_CALL, "\x85\x45rase all Game Data", "Be careful! What's deleted is gone forever!", @@ -89,7 +89,7 @@ void M_EraseData(INT32 choice) else if (optionsmenu.erasecontext == EC_STATISTICS) eschoice = M_GetText("Statistics data"); else if (optionsmenu.erasecontext == EC_TIMEATTACK) - eschoice = M_GetText("GP & Time Attack data"); + eschoice = M_GetText("GP & Record data"); else if (optionsmenu.erasecontext == EC_ALLGAME) eschoice = M_GetText("ALL game data"); else From 28677da5b917e26f21be43a08209397dc2ec314c Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 24 May 2023 18:17:48 +0100 Subject: [PATCH 04/31] Unloaded mapheader record tracking system In the previous entry in the series, due to level header records existing in a NUMMAPS-sized table always saved and loaded in full, Time Attack times persisted even across game loads without the relevant custom levels added. However, this was changed with the long map name system. Map records were assigned to level headers, which were only created on level load. This new system brings Ring Racers up to parity (or better, due to the reduced incidence of header conflicts!) - All levels currently loaded with records attached are written on gamedata save. - This reduces gamedata size for a player who has not unlocked every level! - On gamedata load, if a level is not loaded, store those extra records on a linked list. - On level header creation, check the linked list to see if an associated unloaded mapheader record exists. - If it does, write the record onto the map structure directly, and delete the "unloaded mapheader" storage struct! - Then on the NEXT gamedata save, in addition to all loaded mapheaders, it writes the extra records kept in long term storage in exactly the same format. --- src/deh_soc.c | 41 ++++++++++++++- src/doomstat.h | 12 +++++ src/g_game.c | 135 +++++++++++++++++++++++++++++++++++++++---------- src/typedef.h | 1 + 4 files changed, 159 insertions(+), 30 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index d5fb03cd3..8cdb8ba81 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -950,8 +950,45 @@ void readlevelheader(MYFILE *f, char * name) if (mapheaderinfo[num]->lumpname == NULL) { - mapheaderinfo[num]->lumpname = Z_StrDup(name); - mapheaderinfo[num]->lumpnamehash = quickncasehash(mapheaderinfo[num]->lumpname, MAXMAPLUMPNAME); + mapheaderinfo[num]->lumpnamehash = quickncasehash(name, MAXMAPLUMPNAME); + + // Check to see if we have any custom map record data that we could substitute in. + unloaded_mapheader_t *unloadedmap, *prev = NULL; + for (unloadedmap = unloadedmapheaders; unloadedmap; prev = unloadedmap, unloadedmap = unloadedmap->next) + { + if (unloadedmap->lumpnamehash != mapheaderinfo[num]->lumpnamehash) + continue; + + if (strcasecmp(name, unloadedmap->lumpname) != 0) + continue; + + // Copy in mapvisited, time, lap, etc. + M_Memcpy(&mapheaderinfo[num]->records, &unloadedmap->records, sizeof(recorddata_t)); + + // Reuse the zone-allocated lumpname string. + mapheaderinfo[num]->lumpname = unloadedmap->lumpname; + + // Remove this entry from the chain. + if (prev) + { + prev->next = unloadedmap->next; + } + else + { + unloadedmapheaders = unloadedmap->next; + } + + // Finally, free. + Z_Free(unloadedmap); + + break; + } + + if (mapheaderinfo[num]->lumpname == NULL) + { + // If there was no string to reuse, dup our own. + mapheaderinfo[num]->lumpname = Z_StrDup(name); + } } do diff --git a/src/doomstat.h b/src/doomstat.h index 4983442c2..b7c074a14 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -500,6 +500,18 @@ struct mapheader_t extern mapheader_t** mapheaderinfo; extern INT32 nummapheaders, mapallocsize; +struct unloaded_mapheader_t +{ + char *lumpname; + UINT32 lumpnamehash; + + recorddata_t records; + + unloaded_mapheader_t *next; +}; + +extern unloaded_mapheader_t *unloadedmapheaders; + // Gametypes #define NUMGAMETYPEFREESLOTS (128) #define MAXGAMETYPELENGTH (32) diff --git a/src/g_game.c b/src/g_game.c index b41da25d5..0a91da2ed 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -200,6 +200,8 @@ mapheader_t** mapheaderinfo = {NULL}; INT32 nummapheaders = 0; INT32 mapallocsize = 0; +unloaded_mapheader_t *unloadedmapheaders = NULL; + // Kart cup definitions cupheader_t *kartcupheaders = NULL; UINT16 numkartcupheaders = 0; @@ -448,13 +450,22 @@ INT32 player_name_changes[MAXPLAYERS]; void G_ClearRecords(void) { INT16 i; - cupheader_t *cup; for (i = 0; i < nummapheaders; ++i) { 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)); @@ -4920,43 +4931,54 @@ void G_LoadGameData(void) // Main records numgamedatamapheaders = READUINT32(save.p); - if (numgamedatamapheaders >= NEXTMAP_SPECIAL) - goto datacorrupt; for (i = 0; i < numgamedatamapheaders; i++) { char mapname[MAXMAPLUMPNAME]; INT16 mapnum; - tic_t rectime; - tic_t reclap; READSTRINGN(save.p, mapname, sizeof(mapname)); mapnum = G_MapNumber(mapname); + recorddata_t dummyrecord, *record = &dummyrecord; + rtemp = READUINT8(save.p); - rectime = (tic_t)READUINT32(save.p); - reclap = (tic_t)READUINT32(save.p); + + if (rtemp & ~MV_MAX) + goto datacorrupt; if (mapnum < nummapheaders && mapheaderinfo[mapnum]) { // Valid mapheader, time to populate with record data. - if ((mapheaderinfo[mapnum]->records.mapvisited = rtemp) & ~MV_MAX) - goto datacorrupt; - - if (rectime || reclap) - { - mapheaderinfo[i]->records.time = rectime; - mapheaderinfo[i]->records.lap = reclap; - //CONS_Printf("ID %d, Time = %d, Lap = %d\n", i, rectime/35, reclap/35); - } + record = &mapheaderinfo[mapnum]->records; } - else + else if (rtemp) // Mapvisited will never be 0 for a map with a record. { - // Since it's not worth declaring the entire gamedata - // corrupt over extra maps, we report and move on. - CONS_Alert(CONS_WARNING, "Map with lumpname %s does not exist, time record data will be discarded\n", mapname); + // Invalid, but we don't want to lose all the juicy statistics. + // Instead, update a FILO linked list of "unloaded mapheaders". + + unloaded_mapheader_t *unloadedmap = + Z_Malloc( + sizeof(unloaded_mapheader_t), + PU_STATIC, NULL + ); + + // Establish properties, for later retrieval on file add. + unloadedmap->lumpname = Z_StrDup(mapname); + unloadedmap->lumpnamehash = quickncasehash(mapname, MAXMAPLUMPNAME); + + // Insert at the head, just because it's convenient. + unloadedmap->next = unloadedmapheaders; + unloadedmapheaders = unloadedmap; + + // Finally, set the pointer to write into. + record = &unloadedmap->records; } + + record->mapvisited = rtemp; + record->time = (tic_t)READUINT32(save.p); + record->lap = (tic_t)READUINT32(save.p); } if (versionMinor > 1) @@ -5095,7 +5117,34 @@ void G_SaveGameData(void) { length += gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT; } - length += 4 + (nummapheaders * (MAXMAPLUMPNAME+1+4+4)); + + UINT32 numgamedatamapheaders = 0; + unloaded_mapheader_t *unloadedmap; + + for (i = 0; i < nummapheaders; i++) + { + // It's safe to assume a level with no mapvisited will have no other data worth keeping, since you get MV_VISITED just for opening it. + if (!(mapheaderinfo[i]->records.mapvisited & MV_MAX)) + { + continue; + } + + numgamedatamapheaders++; + } + + for (unloadedmap = unloadedmapheaders; unloadedmap; unloadedmap = unloadedmap->next) + { + // Ditto, with the exception that we should warn about it. + if (!(unloadedmap->records.mapvisited & MV_MAX)) + { + CONS_Alert(CONS_WARNING, "Unloaded map \"%s\" has no mapvisited!\n", unloadedmap->lumpname); + continue; + } + + numgamedatamapheaders++; + } + + length += 4 + (numgamedatamapheaders * (MAXMAPLUMPNAME+1+4+4)); numcups = 0; for (cup = kartcupheaders; cup; cup = cup->next) @@ -5193,17 +5242,47 @@ void G_SaveGameData(void) WRITEUINT32(save.p, gamedata->timesBeaten); // 4 // Main records - WRITEUINT32(save.p, nummapheaders); // 4 + WRITEUINT32(save.p, numgamedatamapheaders); // 4 - for (i = 0; i < nummapheaders; i++) // nummapheaders * (255+1+4+4) { - // For figuring out which header to assign it to on load - WRITESTRINGN(save.p, mapheaderinfo[i]->lumpname, MAXMAPLUMPNAME); + // numgamedatamapheaders * (MAXMAPLUMPNAME+1+4+4) - WRITEUINT8(save.p, (mapheaderinfo[i]->records.mapvisited & MV_MAX)); + UINT32 writtengamedatamapheaders = 0; - WRITEUINT32(save.p, mapheaderinfo[i]->records.time); - WRITEUINT32(save.p, mapheaderinfo[i]->records.lap); + for (i = 0; i < nummapheaders; i++) + { + if (!(mapheaderinfo[i]->records.mapvisited & MV_MAX)) + continue; + + WRITESTRINGN(save.p, mapheaderinfo[i]->lumpname, MAXMAPLUMPNAME); + + WRITEUINT8(save.p, (mapheaderinfo[i]->records.mapvisited & MV_MAX)); + + WRITEUINT32(save.p, mapheaderinfo[i]->records.time); + WRITEUINT32(save.p, mapheaderinfo[i]->records.lap); + + if (++writtengamedatamapheaders >= numgamedatamapheaders) + break; + } + + if (writtengamedatamapheaders < numgamedatamapheaders) + { + for (unloadedmap = unloadedmapheaders; unloadedmap; unloadedmap = unloadedmap->next) + { + if (!(unloadedmap->records.mapvisited & MV_MAX)) + continue; + + WRITESTRINGN(save.p, unloadedmap->lumpname, MAXMAPLUMPNAME); + + WRITEUINT8(save.p, (unloadedmap->records.mapvisited & MV_MAX)); + + WRITEUINT32(save.p, unloadedmap->records.time); + WRITEUINT32(save.p, unloadedmap->records.lap); + + if (++writtengamedatamapheaders >= numgamedatamapheaders) + break; + } + } } WRITEUINT32(save.p, numcups); // 4 diff --git a/src/typedef.h b/src/typedef.h index 00eb492a0..afb3aba00 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -125,6 +125,7 @@ TYPEDEF (customoption_t); TYPEDEF (gametype_t); TYPEDEF (staffbrief_t); TYPEDEF (mapheader_t); +TYPEDEF (unloaded_mapheader_t); TYPEDEF (tolinfo_t); TYPEDEF (cupheader_t); From 3027d254fe9c6501c69a0d37852c8d85d3787e54 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 24 May 2023 18:24:58 +0100 Subject: [PATCH 05/31] Rollover protection for unloaded mapheader record tracking system You will run into memory limits before this happens, but... if you have major quantities of unloaded mapheader record data, avoid a rollover in the counter of records to write. --- src/g_game.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/g_game.c b/src/g_game.c index 0a91da2ed..a458d78e4 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -5141,7 +5141,12 @@ void G_SaveGameData(void) continue; } - numgamedatamapheaders++; + // It's far off on the horizon, beyond many memory limits, but prevent a potential misery moment of losing ALL your data. + if (++numgamedatamapheaders == UINT32_MAX) + { + CONS_Alert(CONS_WARNING, "Some unloaded map record data has been dropped due to datatype limitations.\n"); + break; + } } length += 4 + (numgamedatamapheaders * (MAXMAPLUMPNAME+1+4+4)); From eb26f9fda47e6b8f85c77c58f5900c2fff00b09d Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 25 May 2023 14:40:33 +0100 Subject: [PATCH 06/31] Unloaded mapheader record tracking: Manage memory usage by dropping unimportant stats Only persist the full data while unloaded if player has explicitly achieved something on that level, rather than merely visited (such as in a netgame). Achieving something counts as: - Getting a best time - Getting a best lap time (Race only) - Getting MV_SPBATTACK (completing an SPB run) - Completing the level IF the level has LF2_FINISHNEEDED (finish level in GP/MP before you see it in Time Attack) --- src/deh_soc.c | 1 + src/doomstat.h | 12 +++++++----- src/g_game.c | 38 +++++++++++++++++++++++--------------- 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index 8cdb8ba81..b7d54be2a 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -963,6 +963,7 @@ void readlevelheader(MYFILE *f, char * name) continue; // Copy in mapvisited, time, lap, etc. + unloadedmap->records.mapvisited &= MV_MAX; M_Memcpy(&mapheaderinfo[num]->records, &unloadedmap->records, sizeof(recorddata_t)); // Reuse the zone-allocated lumpname string. diff --git a/src/doomstat.h b/src/doomstat.h index b7c074a14..2e06e5ed5 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -114,11 +114,13 @@ extern preciptype_t curWeather; */ // mapvisited is now a set of flags that says what we've done in the map. -#define MV_VISITED (1) -#define MV_BEATEN (1<<1) -#define MV_ENCORE (1<<2) -#define MV_SPBATTACK (1<<3) -#define MV_MAX (MV_VISITED|MV_BEATEN|MV_ENCORE|MV_SPBATTACK) +#define MV_VISITED (1) +#define MV_BEATEN (1<<1) +#define MV_ENCORE (1<<2) +#define MV_SPBATTACK (1<<3) +#define MV_MAX (MV_VISITED|MV_BEATEN|MV_ENCORE|MV_SPBATTACK) +#define MV_FINISHNEEDED (1<<7) +#define MV_PERSISTUNLOADED (MV_SPBATTACK|MV_FINISHNEEDED) struct recorddata_t { diff --git a/src/g_game.c b/src/g_game.c index a458d78e4..c1a01ad80 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4940,20 +4940,25 @@ void G_LoadGameData(void) READSTRINGN(save.p, mapname, sizeof(mapname)); mapnum = G_MapNumber(mapname); - recorddata_t dummyrecord, *record = &dummyrecord; + recorddata_t dummyrecord; - rtemp = READUINT8(save.p); - - if (rtemp & ~MV_MAX) - goto datacorrupt; + dummyrecord.mapvisited = READUINT8(save.p); + dummyrecord.time = (tic_t)READUINT32(save.p); + dummyrecord.lap = (tic_t)READUINT32(save.p); if (mapnum < nummapheaders && mapheaderinfo[mapnum]) { // Valid mapheader, time to populate with record data. - record = &mapheaderinfo[mapnum]->records; + dummyrecord.mapvisited &= MV_MAX; + M_Memcpy(&mapheaderinfo[mapnum]->records, &dummyrecord, sizeof(recorddata_t)); } - else if (rtemp) // Mapvisited will never be 0 for a map with a record. + else if ( + ((dummyrecord.mapvisited & MV_PERSISTUNLOADED) != 0 + && (dummyrecord.mapvisited & MV_BEATEN) != 0) + || dummyrecord.time != 0 + || dummyrecord.lap != 0 + ) { // Invalid, but we don't want to lose all the juicy statistics. // Instead, update a FILO linked list of "unloaded mapheaders". @@ -4972,13 +4977,9 @@ void G_LoadGameData(void) unloadedmap->next = unloadedmapheaders; unloadedmapheaders = unloadedmap; - // Finally, set the pointer to write into. - record = &unloadedmap->records; + // Finally, copy into. + M_Memcpy(&unloadedmap->records, &dummyrecord, sizeof(recorddata_t)); } - - record->mapvisited = rtemp; - record->time = (tic_t)READUINT32(save.p); - record->lap = (tic_t)READUINT32(save.p); } if (versionMinor > 1) @@ -5261,7 +5262,14 @@ void G_SaveGameData(void) WRITESTRINGN(save.p, mapheaderinfo[i]->lumpname, MAXMAPLUMPNAME); - WRITEUINT8(save.p, (mapheaderinfo[i]->records.mapvisited & MV_MAX)); + UINT8 mapvisitedtemp = (mapheaderinfo[i]->records.mapvisited & MV_MAX); + + if ((mapheaderinfo[i]->menuflags & LF2_FINISHNEEDED)) + { + mapvisitedtemp |= MV_FINISHNEEDED; + } + + WRITEUINT8(save.p, mapvisitedtemp); WRITEUINT32(save.p, mapheaderinfo[i]->records.time); WRITEUINT32(save.p, mapheaderinfo[i]->records.lap); @@ -5279,7 +5287,7 @@ void G_SaveGameData(void) WRITESTRINGN(save.p, unloadedmap->lumpname, MAXMAPLUMPNAME); - WRITEUINT8(save.p, (unloadedmap->records.mapvisited & MV_MAX)); + WRITEUINT8(save.p, unloadedmap->records.mapvisited); WRITEUINT32(save.p, unloadedmap->records.time); WRITEUINT32(save.p, unloadedmap->records.lap); From b186e89353840aaa6a3812577c4f9702c12b6aa4 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 25 May 2023 17:33:58 +0100 Subject: [PATCH 07/31] K_ResetCeremony: Populate results downward in difficulty Per request, someone who beats a cup on Normal doesn't have to worry about beating that cup on Easy as well unless they want to of their own volition. --- src/k_podium.c | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/k_podium.c b/src/k_podium.c index 46de1eb79..a9e6f90f1 100644 --- a/src/k_podium.c +++ b/src/k_podium.c @@ -305,7 +305,7 @@ void K_FinishCeremony(void) --------------------------------------------------*/ void K_ResetCeremony(void) { - UINT8 i; + SINT8 i; memset(&podiumData, 0, sizeof(struct podiumData_s)); @@ -326,22 +326,27 @@ void K_ResetCeremony(void) // Write grade, position, and emerald-having-ness for later sessions! i = (grandprixinfo.masterbots) ? KARTGP_MASTER : grandprixinfo.gamespeed; - if ((grandprixinfo.cup->windata[i].best_placement == 0) // First run - || (podiumData.rank.position < grandprixinfo.cup->windata[i].best_placement)) // Later, better run + // All results populate downwards in difficulty. This prevents someone + // who's just won on Normal from feeling obligated to complete Easy too. + for (; i >= 0; i--) { - grandprixinfo.cup->windata[i].best_placement = podiumData.rank.position; + if ((grandprixinfo.cup->windata[i].best_placement == 0) // First run + || (podiumData.rank.position < grandprixinfo.cup->windata[i].best_placement)) // Later, better run + { + grandprixinfo.cup->windata[i].best_placement = podiumData.rank.position; - // The following will not occour in unmodified builds, but pre-emptively sanitise gamedata if someone just changes MAXPLAYERS and calls it a day - if (grandprixinfo.cup->windata[i].best_placement > 0x0F) - grandprixinfo.cup->windata[i].best_placement = 0x0F; + // The following will not occur in unmodified builds, but pre-emptively sanitise gamedata if someone just changes MAXPLAYERS and calls it a day + if (grandprixinfo.cup->windata[i].best_placement > 0x0F) + grandprixinfo.cup->windata[i].best_placement = 0x0F; + } + + if (podiumData.grade > grandprixinfo.cup->windata[i].best_grade) + grandprixinfo.cup->windata[i].best_grade = podiumData.grade; + + if (podiumData.rank.specialWon == true) + grandprixinfo.cup->windata[i].got_emerald = true; } - if (podiumData.grade > grandprixinfo.cup->windata[i].best_grade) - grandprixinfo.cup->windata[i].best_grade = podiumData.grade; - - if (i != KARTSPEED_EASY && podiumData.rank.specialWon == true) - grandprixinfo.cup->windata[i].got_emerald = true; - // Save before playing the noise G_SaveGameData(); } From 65f679c0bcbd03c24ac0b7d1b1c3825dd1ba5f23 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 25 May 2023 17:38:33 +0100 Subject: [PATCH 08/31] 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); From acc5fc85b902b81f55cb029564420ef073341299 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 27 May 2023 16:35:08 +0100 Subject: [PATCH 09/31] Skin rivals system: Adjust for fixes/consistency - Previously duplicated across R_AddSkins and R_PatchSkins, now handled in R_ProcessPatchableFields. - Increase the size of the buffer to SKINNAMESIZE+1, to prevent silent failure for long character names. --- src/r_skins.c | 130 ++++++++++++++++++++------------------------------ 1 file changed, 51 insertions(+), 79 deletions(-) diff --git a/src/r_skins.c b/src/r_skins.c index 9a9df4f88..1bdba8132 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -727,9 +727,59 @@ static void R_LoadSkinSprites(UINT16 wadnum, UINT16 *lump, UINT16 *lastlump, ski // returns whether found appropriate property static boolean R_ProcessPatchableFields(skin_t *skin, char *stoken, char *value) { + if (!stricmp(stoken, "rivals")) + { + size_t len = strlen(value); + size_t i; + char rivalname[SKINNAMESIZE+1] = ""; + UINT8 pos = 0; + UINT8 numrivals = 0; + + // Can't use strtok, because the above function's already using it. + // Using it causes it to upset the saved pointer, + // corrupting the reading for the rest of the file. + + // So instead we get to crawl through the value, character by character, + // and write it down as we go, until we hit a comma or the end of the string. + // Yaaay. + + for (i = 0; i <= len; i++) + { + if (numrivals >= SKINRIVALS) + { + break; + } + + if (value[i] == ',' || i == len) + { + if (pos == 0) + continue; + + STRBUFCPY(skin->rivals[numrivals], rivalname); + strlwr(skin->rivals[numrivals]); + numrivals++; + + if (i == len) + break; + + for (; pos > 0; pos--) + { + rivalname[pos] = '\0'; + } + + continue; + } + + rivalname[pos] = value[i]; + pos++; + } + } + // custom translation table - if (!stricmp(stoken, "startcolor")) + else if (!stricmp(stoken, "startcolor")) + { skin->starttranscolor = atoi(value); + } #define FULLPROCESS(field) else if (!stricmp(stoken, #field)) skin->field = get_number(value); // character type identification @@ -917,45 +967,6 @@ void R_AddSkins(UINT16 wadnum, boolean mainfile) STRBUFCPY(skin->realname, value); SYMBOLCONVERT(skin->realname) } - else if (!stricmp(stoken, "rivals")) - { - size_t len = strlen(value); - size_t i; - char rivalname[SKINNAMESIZE] = ""; - UINT8 pos = 0; - UINT8 numrivals = 0; - - // Can't use strtok, because this function's already using it. - // Using it causes it to upset the saved pointer, - // corrupting the reading for the rest of the file. - - // So instead we get to crawl through the value, character by character, - // and write it down as we go, until we hit a comma or the end of the string. - // Yaaay. - - for (i = 0; i <= len; i++) - { - if (numrivals >= SKINRIVALS) - { - break; - } - - if (value[i] == ',' || i == len) - { - STRBUFCPY(skin->rivals[numrivals], rivalname); - strlwr(skin->rivals[numrivals]); - numrivals++; - - memset(rivalname, 0, sizeof (rivalname)); - pos = 0; - - continue; - } - - rivalname[pos] = value[i]; - pos++; - } - } else if (!R_ProcessPatchableFields(skin, stoken, value)) CONS_Debug(DBG_SETUP, "R_AddSkins: Unknown keyword '%s' in S_SKIN lump #%d (WAD %s)\n", stoken, lump, wadfiles[wadnum]->filename); @@ -1073,45 +1084,6 @@ void R_PatchSkins(UINT16 wadnum, boolean mainfile) STRBUFCPY(skin->realname, value); SYMBOLCONVERT(skin->realname) } - else if (!stricmp(stoken, "rivals")) - { - size_t len = strlen(value); - size_t i; - char rivalname[SKINNAMESIZE] = ""; - UINT8 pos = 0; - UINT8 numrivals = 0; - - // Can't use strtok, because this function's already using it. - // Using it causes it to upset the saved pointer, - // corrupting the reading for the rest of the file. - - // So instead we get to crawl through the value, character by character, - // and write it down as we go, until we hit a comma or the end of the string. - // Yaaay. - - for (i = 0; i <= len; i++) - { - if (numrivals >= SKINRIVALS) - { - break; - } - - if (value[i] == ',' || i == len) - { - STRBUFCPY(skin->rivals[numrivals], rivalname); - strlwr(skin->rivals[numrivals]); - numrivals++; - - memset(rivalname, 0, sizeof (rivalname)); - pos = 0; - - continue; - } - - rivalname[pos] = value[i]; - pos++; - } - } else if (!R_ProcessPatchableFields(skin, stoken, value)) CONS_Debug(DBG_SETUP, "R_PatchSkins: Unknown keyword '%s' in P_SKIN lump #%d (WAD %s)\n", stoken, lump, wadfiles[wadnum]->filename); } From fde06aace2e63b105bb6e0de30ef41d49c965e5b Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 27 May 2023 16:36:48 +0100 Subject: [PATCH 10/31] CV_SetValueMaybeStealth: Correctly null terminate tmpskin for forcecharacter --- src/command.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/command.c b/src/command.c index 6cacd3bcd..157cb87bc 100644 --- a/src/command.c +++ b/src/command.c @@ -2051,7 +2051,7 @@ static void CV_SetValueMaybeStealth(consvar_t *var, INT32 value, boolean stealth tmpskin = "None"; else tmpskin = skins[value].name; - strncpy(val, tmpskin, SKINNAMESIZE); + strlcpy(val, tmpskin, SKINNAMESIZE+1); } else sprintf(val, "%d", value); From 0f266198ca6040ad64aee3e57255f12bce0dc828 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 27 May 2023 18:48:21 +0100 Subject: [PATCH 11/31] ACS_GetColorFromString; Fix copypaste error indexing into skin array rather than color array Discovered while writing the following commit. --- src/acs/call-funcs.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/acs/call-funcs.cpp b/src/acs/call-funcs.cpp index 1f9904ccd..4bfca2960 100644 --- a/src/acs/call-funcs.cpp +++ b/src/acs/call-funcs.cpp @@ -293,7 +293,7 @@ static bool ACS_GetColorFromString(const char *word, skincolornum_t *type) { for (int i = 0; i < numskincolors; i++) { - if (fastcmp(word, skins[i].name)) + if (fastcmp(word, skincolors[i].name)) { *type = static_cast(i); return true; From 8d9b42e49b6fda035379599b5304fa6c0c50a869 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 27 May 2023 19:08:14 +0100 Subject: [PATCH 12/31] Skin name search-assistive hashing More opportunities for early rejection in table-wide searches, in anticipation of future work. - Many circumstances independently implemented string name comparisons. Most of these have been converted to use R_SkinAvailable, with one exception. - M_CharacterSelectInit already needs to iterate over the characters, so produce the skin hash independently. --- src/acs/call-funcs.cpp | 14 +++++--------- src/hardware/hw_md2.c | 18 ++++++++---------- src/lua_hudlib.c | 6 ++---- src/lua_mobjlib.c | 21 ++++++++++----------- src/lua_skinlib.c | 12 ++++++------ src/menus/play-char-select.c | 14 +++++++++++++- src/r_skins.c | 13 ++++++++++--- src/r_skins.h | 3 ++- 8 files changed, 56 insertions(+), 45 deletions(-) diff --git a/src/acs/call-funcs.cpp b/src/acs/call-funcs.cpp index 4bfca2960..c5e07f025 100644 --- a/src/acs/call-funcs.cpp +++ b/src/acs/call-funcs.cpp @@ -264,16 +264,12 @@ static bool ACS_GetStateFromString(const char *word, statenum_t *type) --------------------------------------------------*/ static bool ACS_GetSkinFromString(const char *word, INT32 *type) { - for (int i = 0; i < numskins; i++) - { - if (fastcmp(word, skins[i].name)) - { - *type = i; - return true; - } - } + INT32 skin = R_SkinAvailable(word); + if (skin == -1) + return false; - return false; + *type = skin; + return true; } /*-------------------------------------------------- diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c index 26d342b1b..8772e152a 100644 --- a/src/hardware/hw_md2.c +++ b/src/hardware/hw_md2.c @@ -570,17 +570,15 @@ void HWR_InitModels(void) addskinmodel: // add player model - for (s = 0; s < MAXSKINS; s++) + s = R_SkinAvailable(skinname); + if (s != -1) { - if (stricmp(skinname, skins[s].name) == 0) - { - md2_playermodels[s].skin = s; - md2_playermodels[s].scale = scale; - md2_playermodels[s].offset = offset; - md2_playermodels[s].notfound = false; - strcpy(md2_playermodels[s].filename, filename); - goto modelfound; - } + md2_playermodels[s].skin = s; + md2_playermodels[s].scale = scale; + md2_playermodels[s].offset = offset; + md2_playermodels[s].notfound = false; + strcpy(md2_playermodels[s].filename, filename); + goto modelfound; } // no sprite/player skin name found?!?D diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c index aab85e26c..f99f33f6b 100644 --- a/src/lua_hudlib.c +++ b/src/lua_hudlib.c @@ -386,10 +386,8 @@ static int libd_getSprite2Patch(lua_State *L) else // find skin by name { const char *name = luaL_checkstring(L, 1); - for (i = 0; i < numskins; i++) - if (fastcmp(skins[i].name, name)) - break; - if (i >= numskins) + i = R_SkinAvailable(name); + if (i == -1) return 0; } diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c index 835e2bb4d..1ae7a6a78 100644 --- a/src/lua_mobjlib.c +++ b/src/lua_mobjlib.c @@ -662,17 +662,16 @@ static int mobj_set(lua_State *L) break; case mobj_skin: // set skin by name { - INT32 i; - char skin[SKINNAMESIZE+1]; // all skin names are limited to this length - strlcpy(skin, luaL_checkstring(L, 3), sizeof skin); - strlwr(skin); // all skin names are lowercase - for (i = 0; i < numskins; i++) - if (fastcmp(skins[i].name, skin)) - { - if (!mo->player || R_SkinUsable(mo->player-players, i, false)) - mo->skin = &skins[i]; - return 0; - } + const char *name = luaL_checkstring(L, 3); + INT32 skin = R_SkinAvailable(name); + + if (skin != -1) + { + if (!mo->player || R_SkinUsable(mo->player-players, skin, false)) + mo->skin = &skins[skin]; + + return 0; + } return luaL_error(L, "mobj.skin '%s' not found!", skin); } case mobj_color: diff --git a/src/lua_skinlib.c b/src/lua_skinlib.c index 6c4089de2..2950af461 100644 --- a/src/lua_skinlib.c +++ b/src/lua_skinlib.c @@ -196,12 +196,12 @@ static int lib_getSkin(lua_State *L) } // find skin by name - for (i = 0; i < numskins; i++) - if (fastcmp(skins[i].name, field)) - { - LUA_PushUserdata(L, &skins[i], META_SKIN); - return 1; - } + i = R_SkinAvailable(field); + if (i != -1) + { + LUA_PushUserdata(L, &skins[i], META_SKIN); + return 1; + } return 0; } diff --git a/src/menus/play-char-select.c b/src/menus/play-char-select.c index c21990ba4..0110870d9 100644 --- a/src/menus/play-char-select.c +++ b/src/menus/play-char-select.c @@ -404,6 +404,8 @@ void M_CharacterSelectInit(void) memset(setup_explosions, 0, sizeof(setup_explosions)); setup_animcounter = 0; + UINT32 localskinhash[MAXSPLITSCREENPLAYERS]; + for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) { // Default to no follower / match colour. @@ -414,6 +416,9 @@ void M_CharacterSelectInit(void) // Set default selected profile to the last used profile for each player: // (Make sure we don't overshoot it somehow if we deleted profiles or whatnot) setup_player[i].profilen = min(cv_lastprofile[i].value, PR_GetNumProfiles()); + + // Assistant for comparisons. + localskinhash[i] = quickncasehash(cv_skin[i].string, SKINNAMESIZE); } for (i = 0; i < numskins; i++) @@ -436,7 +441,14 @@ void M_CharacterSelectInit(void) for (j = 0; j < MAXSPLITSCREENPLAYERS; j++) { - if (!strcmp(cv_skin[j].string, skins[i].name)) + // See also R_SkinAvailable + + if (localskinhash[j] != skins[i].namehash) + continue; + + if (!stricmp(cv_skin[j].string, skins[i].name)) + continue; + { setup_player[j].gridx = x; setup_player[j].gridy = y; diff --git a/src/r_skins.c b/src/r_skins.c index 1bdba8132..432052db1 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -314,12 +314,17 @@ UINT32 R_GetLocalRandomSkin(void) INT32 R_SkinAvailable(const char *name) { INT32 i; + UINT32 hash = quickncasehash(name, SKINNAMESIZE); for (i = 0; i < numskins; i++) { - // search in the skin list - if (stricmp(skins[i].name,name)==0) - return i; + if (skins[i].namehash != hash) + continue; + + if (stricmp(skins[i].name,name)!=0) + continue; + + return i; } return -1; } @@ -954,6 +959,8 @@ void R_AddSkins(UINT16 wadnum, boolean mainfile) Z_Free(value2); } + skin->namehash = quickncasehash(skin->name, SKINNAMESIZE); + // copy to hudname and fullname as a default. if (!realname) { diff --git a/src/r_skins.h b/src/r_skins.h index 2770846f1..73d8b609e 100644 --- a/src/r_skins.h +++ b/src/r_skins.h @@ -37,7 +37,8 @@ extern "C" { /// The skin_t struct struct skin_t { - char name[SKINNAMESIZE+1]; // INT16 descriptive name of the skin + char name[SKINNAMESIZE+1]; // descriptive name of the skin + UINT32 namehash; // quickncasehash(->name, SKINNAMESIZE) UINT16 wadnum; skinflags_t flags; From 0b5124b8c4d120237b42c194ea81015bfac1bf58 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 28 May 2023 19:00:06 +0100 Subject: [PATCH 13/31] G_SaveGameData: Remove the "writtengamedata" secondary counter Instead, subtract from the associated "numgamedata" variable and check if nonzero to continue. --- src/g_game.c | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index aca6206a3..d70a319d6 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -5220,7 +5220,7 @@ void G_SaveGameData(void) } } - length += 4 + (numgamedatacups * (4+16)); + length += 4 + (numgamedatacups * (MAXCUPNAME+4)); if (P_SaveBufferAlloc(&save, length) == false) { @@ -5313,11 +5313,10 @@ void G_SaveGameData(void) // Main records WRITEUINT32(save.p, numgamedatamapheaders); // 4 + if (numgamedatamapheaders) { // numgamedatamapheaders * (MAXMAPLUMPNAME+1+4+4) - UINT32 writtengamedatamapheaders = 0; - for (i = 0; i < nummapheaders; i++) { if (!(mapheaderinfo[i]->records.mapvisited & MV_MAX)) @@ -5337,11 +5336,11 @@ void G_SaveGameData(void) WRITEUINT32(save.p, mapheaderinfo[i]->records.time); WRITEUINT32(save.p, mapheaderinfo[i]->records.lap); - if (++writtengamedatamapheaders >= numgamedatamapheaders) + if (--numgamedatamapheaders == 0) break; } - if (writtengamedatamapheaders < numgamedatamapheaders) + if (numgamedatamapheaders) { for (unloadedmap = unloadedmapheaders; unloadedmap; unloadedmap = unloadedmap->next) { @@ -5355,7 +5354,7 @@ void G_SaveGameData(void) WRITEUINT32(save.p, unloadedmap->records.time); WRITEUINT32(save.p, unloadedmap->records.lap); - if (++writtengamedatamapheaders >= numgamedatamapheaders) + if (--numgamedatamapheaders == 0) break; } } @@ -5364,10 +5363,9 @@ void G_SaveGameData(void) WRITEUINT32(save.p, numgamedatacups); // 4 + if (numgamedatacups) { - // (numgamedatacups * (4+16)) - - UINT32 writtengamedatacups = 0; + // numgamedatacups * (MAXCUPNAME+4) #define WRITECUPWINDATA(maybeunloadedcup) \ for (i = 0; i < KARTGP_MAX; i++) \ @@ -5389,11 +5387,11 @@ void G_SaveGameData(void) WRITECUPWINDATA(cup); - if (++writtengamedatacups >= numgamedatacups) + if (--numgamedatacups == 0) break; } - if (writtengamedatacups < numgamedatacups) + if (numgamedatacups) { for (unloadedcup = unloadedcupheaders; unloadedcup; unloadedcup = unloadedcup->next) { @@ -5404,7 +5402,7 @@ void G_SaveGameData(void) WRITECUPWINDATA(unloadedcup); - if (++writtengamedatacups >= numgamedatacups) + if (--numgamedatacups == 0) break; } } From 3e663867b96fb403e7b71a8d611bd33025ef5834 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 28 May 2023 19:27:25 +0100 Subject: [PATCH 14/31] Move skin->namehash set to conclusion of R_AddSkins loop just before numskins is incremented, as it's possible to have a default name for a skin that doesn't touch the hash codepath --- src/r_skins.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/r_skins.c b/src/r_skins.c index 432052db1..49d1bef7f 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -959,8 +959,6 @@ void R_AddSkins(UINT16 wadnum, boolean mainfile) Z_Free(value2); } - skin->namehash = quickncasehash(skin->name, SKINNAMESIZE); - // copy to hudname and fullname as a default. if (!realname) { @@ -1005,6 +1003,9 @@ next_token: HWR_AddPlayerModel(numskins); #endif + // Finally, conclude by setting up final properties. + skin->namehash = quickncasehash(skin->name, SKINNAMESIZE); + numskins++; } return; From aa35e249d58a00f5c3907e11d8d63a2c0fafa972 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 28 May 2023 20:19:30 +0100 Subject: [PATCH 15/31] g_game.c: Tidy in anticipation of next commit - Use WRITESTRINGL and READSTRINGL instead of their N equivalents. These are mostly the same, but the macro length is inclusive of the null terminator, so this prevents one-byte overruns. - Make sure datatype for iteration over nummapheaders is of sufficient unsigned region - Make sure hashes are calculated based on final field for guaranteed consistency, not initial buffer - Additional newlines for spacing --- src/g_game.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index d70a319d6..5cb13e080 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -451,9 +451,9 @@ INT32 player_name_changes[MAXPLAYERS]; // MAKE SURE YOU SAVE DATA BEFORE CALLING THIS void G_ClearRecords(void) { - INT16 i; + UINT16 i; - for (i = 0; i < nummapheaders; ++i) + for (i = 0; i < nummapheaders; i++) { memset(&mapheaderinfo[i]->records, 0, sizeof(recorddata_t)); } @@ -4947,7 +4947,7 @@ void G_LoadGameData(void) char mapname[MAXMAPLUMPNAME]; INT16 mapnum; - READSTRINGN(save.p, mapname, sizeof(mapname)); + READSTRINGL(save.p, mapname, MAXMAPLUMPNAME); mapnum = G_MapNumber(mapname); recorddata_t dummyrecord; @@ -4981,7 +4981,7 @@ void G_LoadGameData(void) // Establish properties, for later retrieval on file add. unloadedmap->lumpname = Z_StrDup(mapname); - unloadedmap->lumpnamehash = quickncasehash(mapname, MAXMAPLUMPNAME); + unloadedmap->lumpnamehash = quickncasehash(unloadedmap->lumpname, MAXMAPLUMPNAME); // Insert at the head, just because it's convenient. unloadedmap->next = unloadedmapheaders; @@ -5004,7 +5004,7 @@ void G_LoadGameData(void) cupwindata_t dummywindata[4]; // Find the relevant cup. - READSTRINGN(save.p, cupname, sizeof(cupname)); + READSTRINGL(save.p, cupname, sizeof(cupname)); UINT32 hash = quickncasehash(cupname, MAXCUPNAME); for (cup = kartcupheaders; cup; cup = cup->next) { @@ -5053,7 +5053,7 @@ void G_LoadGameData(void) // Establish properties, for later retrieval on file add. strlcpy(unloadedcup->name, cupname, sizeof(unloadedcup->name)); - unloadedcup->namehash = quickncasehash(cupname, MAXCUPNAME); + unloadedcup->namehash = quickncasehash(unloadedcup->name, MAXCUPNAME); // Insert at the head, just because it's convenient. unloadedcup->next = unloadedcupheaders; @@ -5190,6 +5190,7 @@ void G_SaveGameData(void) length += 4 + (numgamedatamapheaders * (MAXMAPLUMPNAME+1+4+4)); + UINT32 numgamedatacups = 0; unloaded_cupheader_t *unloadedcup; @@ -5222,6 +5223,7 @@ void G_SaveGameData(void) length += 4 + (numgamedatacups * (MAXCUPNAME+4)); + if (P_SaveBufferAlloc(&save, length) == false) { CONS_Alert(CONS_ERROR, M_GetText("No more free memory for saving game data\n")); @@ -5322,7 +5324,7 @@ void G_SaveGameData(void) if (!(mapheaderinfo[i]->records.mapvisited & MV_MAX)) continue; - WRITESTRINGN(save.p, mapheaderinfo[i]->lumpname, MAXMAPLUMPNAME); + WRITESTRINGL(save.p, mapheaderinfo[i]->lumpname, MAXMAPLUMPNAME); UINT8 mapvisitedtemp = (mapheaderinfo[i]->records.mapvisited & MV_MAX); @@ -5347,7 +5349,7 @@ void G_SaveGameData(void) if (!(unloadedmap->records.mapvisited & MV_MAX)) continue; - WRITESTRINGN(save.p, unloadedmap->lumpname, MAXMAPLUMPNAME); + WRITESTRINGL(save.p, unloadedmap->lumpname, MAXMAPLUMPNAME); WRITEUINT8(save.p, unloadedmap->records.mapvisited); @@ -5383,7 +5385,7 @@ void G_SaveGameData(void) if (cup->windata[0].best_placement == 0) continue; - WRITESTRINGN(save.p, cup->name, MAXCUPNAME); + WRITESTRINGL(save.p, cup->name, MAXCUPNAME); WRITECUPWINDATA(cup); @@ -5398,7 +5400,7 @@ void G_SaveGameData(void) if (unloadedcup->windata[0].best_placement == 0) continue; - WRITESTRINGN(save.p, unloadedcup->name, MAXCUPNAME); + WRITESTRINGL(save.p, unloadedcup->name, MAXCUPNAME); WRITECUPWINDATA(unloadedcup); From 1527471678331a61beb672da792f908f58fc051d Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 28 May 2023 20:29:40 +0100 Subject: [PATCH 16/31] 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); From cd2a4b3807d6baa0b4177c3dbf35ef0d73bd4aa4 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 29 May 2023 12:57:42 +0100 Subject: [PATCH 17/31] Move skinrecord_t and unloaded_skin_t to doomstat.h Unfortunately, they are necessary to have in this already bloated file, to avoid circular dependency in future work. --- src/doomstat.h | 17 +++++++++++++++++ src/r_skins.h | 17 ----------------- src/typedef.h | 4 ++-- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/doomstat.h b/src/doomstat.h index b5c0ea4f7..ef5ed8a82 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -113,6 +113,23 @@ extern preciptype_t curWeather; /** Time attack information, currently a very small structure. */ +struct skinrecord_t +{ + UINT32 wins; +}; + +struct unloaded_skin_t +{ + char name[SKINNAMESIZE+1]; + UINT32 namehash; + + skinrecord_t records; + + unloaded_skin_t *next; +}; + +extern unloaded_skin_t *unloadedskins; + // mapvisited is now a set of flags that says what we've done in the map. #define MV_VISITED (1) #define MV_BEATEN (1<<1) diff --git a/src/r_skins.h b/src/r_skins.h index ec393b744..9a1c45ce6 100644 --- a/src/r_skins.h +++ b/src/r_skins.h @@ -34,11 +34,6 @@ extern "C" { #define DEFAULTSKIN3 "sonic" // third player #define DEFAULTSKIN4 "knuckles" // fourth player -struct skinrecord_t -{ - UINT32 wins; -}; - /// The skin_t struct struct skin_t { @@ -76,18 +71,6 @@ 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 e8567f3ed..771313e1c 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -113,6 +113,8 @@ TYPEDEF (skincolor_t); // doomstat.h TYPEDEF (precipprops_t); +TYPEDEF (skinrecord_t); +TYPEDEF (unloaded_skin_t); TYPEDEF (recorddata_t); TYPEDEF (cupwindata_t); TYPEDEF (scene_t); @@ -385,9 +387,7 @@ 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); From a36213c435a9439b18064acc2bae5c4ba4050d53 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 29 May 2023 16:56:17 +0100 Subject: [PATCH 18/31] G_LoadGameData: Improve indentation for gamedata skins block Done seperately to reduce complexity of diff for the next commit. --- src/g_game.c | 74 +++++++++++++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 331dffbf9..7e5193886 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4956,52 +4956,60 @@ void G_LoadGameData(void) // Main records if (versionMinor < 3) + { gamedata->importprofilewins = true; + numgamedataskins = 0; + } else { numgamedataskins = READUINT32(save.p); - for (i = 0; i < numgamedataskins; i++) + if (numgamedataskins) { - 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) + for (i = 0; i < numgamedataskins; i++) { - // We found a skin, so assign the win. + char skinname[SKINNAMESIZE+1]; + INT32 skin; - 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". + READSTRINGN(save.p, skinname, SKINNAMESIZE); + skin = R_SkinAvailable(skinname); - unloaded_skin_t *unloadedskin = - Z_Malloc( - sizeof(unloaded_skin_t), - PU_STATIC, NULL - ); + skinrecord_t dummyrecord; - // Establish properties, for later retrieval on file add. - strlcpy(unloadedskin->name, skinname, sizeof(unloadedskin->name)); - unloadedskin->namehash = quickncasehash(unloadedskin->name, SKINNAMESIZE); + dummyrecord.wins = READUINT32(save.p); - // Insert at the head, just because it's convenient. - unloadedskin->next = unloadedskins; - unloadedskins = unloadedskin; + CONS_Printf(" (TEMPORARY DISPLAY) skinname is \"%s\", has %u wins\n", skinname, dummyrecord.wins); - // Finally, copy into. - M_Memcpy(&unloadedskin->records, &dummyrecord, sizeof(skinrecord_t)); + 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)); + } } } } From 989070c3f10d9ca5e11e93dcaa43c609701cc7b7 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 29 May 2023 17:16:53 +0100 Subject: [PATCH 19/31] skinreference_t Please note this is incompatible with gamedata from previous commits on this branch. As always, keep a backup of your last stable gamedata. A system for record data to point to either loaded or unloaded skins. - When writing to gamedata, stores a funny internal reference id on skinrecord_t. - This ID is then used when writing out subsequent references ala mobjnum. - As an example, has been attached to cup windata. - Also assigned based on profile skin when gamedata importprofilewins event is occouring. - Set to your current skin if you complete a Cup, OR if you get equal or better on any existing Cup. - Successfully reassigned alongside unloadedskin records when a skin is added. - TEMPORARY: Character ownership of Cup wins are displayed in your latest-log.txt, in lieu of an update to the cupgrid --- src/d_main.c | 28 +++++++++++++++++++- src/doomstat.h | 10 +++++++ src/g_game.c | 72 +++++++++++++++++++++++++++++++++++++++++++++----- src/k_podium.c | 20 ++++++++++++-- src/k_rank.h | 1 + src/r_skins.c | 27 +++++++++++++++++++ src/typedef.h | 1 + 7 files changed, 150 insertions(+), 9 deletions(-) diff --git a/src/d_main.c b/src/d_main.c index 89284a677..5f4cf88ba 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -1859,10 +1859,36 @@ void D_SRB2Main(void) 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; + + cupheader_t *cup; + for (cup = kartcupheaders; cup; cup = cup->next) + { + for (i = 0; i < KARTGP_MAX; i++) + { + if (cup->windata[i].best_placement == 0) + continue; + cup->windata[i].best_skin.id = importskin; + cup->windata[i].best_skin.unloaded = NULL; + } + } + + unloaded_cupheader_t *unloadedcup; + for (unloadedcup = unloadedcupheaders; unloadedcup; unloadedcup = unloadedcup->next) + { + for (i = 0; i < KARTGP_MAX; i++) + { + if (unloadedcup->windata[i].best_placement == 0) + continue; + unloadedcup->windata[i].best_skin.id = importskin; + unloadedcup->windata[i].best_skin.unloaded = NULL; + } + } + + CONS_Printf(" Wins for profile \"%s\" imported onto character \"%s\"\n", pr->profilename, skins[importskin].name); } } + gamedata->importprofilewins = false; } diff --git a/src/doomstat.h b/src/doomstat.h index ef5ed8a82..619f01db3 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -116,6 +116,9 @@ extern preciptype_t curWeather; struct skinrecord_t { UINT32 wins; + + // Purely assistive in gamedata save processes + UINT32 _saveid; }; struct unloaded_skin_t @@ -130,6 +133,12 @@ struct unloaded_skin_t extern unloaded_skin_t *unloadedskins; +struct skinreference_t +{ + unloaded_skin_t *unloaded; + UINT8 id; +}; + // mapvisited is now a set of flags that says what we've done in the map. #define MV_VISITED (1) #define MV_BEATEN (1<<1) @@ -168,6 +177,7 @@ struct cupwindata_t UINT8 best_placement; gp_rank_e best_grade; boolean got_emerald; + skinreference_t best_skin; }; // Set if homebrew PWAD stuff has been added. diff --git a/src/g_game.c b/src/g_game.c index 7e5193886..89bc1c0fd 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4466,6 +4466,7 @@ static void G_DoCompleted(void) grandprixinfo.rank.prisons += numtargets; grandprixinfo.rank.position = MAXPLAYERS; + grandprixinfo.rank.skin = MAXSKINS; for (i = 0; i < MAXPLAYERS; i++) { @@ -4495,7 +4496,12 @@ static void G_DoCompleted(void) if (players[i].bot == false) { - grandprixinfo.rank.position = min(grandprixinfo.rank.position, K_GetPodiumPosition(&players[i])); + UINT8 podiumposition = K_GetPodiumPosition(&players[i]); + if (podiumposition <= grandprixinfo.rank.position) + { + grandprixinfo.rank.position = podiumposition; + grandprixinfo.rank.skin = players[i].skin; + } } } } @@ -4955,6 +4961,8 @@ void G_LoadGameData(void) // Main records + skinreference_t *tempskinreferences = NULL; + if (versionMinor < 3) { gamedata->importprofilewins = true; @@ -4966,6 +4974,11 @@ void G_LoadGameData(void) if (numgamedataskins) { + tempskinreferences = Z_Malloc( + numgamedataskins * sizeof (skinreference_t), + PU_STATIC, + NULL + ); for (i = 0; i < numgamedataskins; i++) { @@ -4978,15 +4991,20 @@ void G_LoadGameData(void) skinrecord_t dummyrecord; dummyrecord.wins = READUINT32(save.p); + dummyrecord._saveid = i; CONS_Printf(" (TEMPORARY DISPLAY) skinname is \"%s\", has %u wins\n", skinname, dummyrecord.wins); + tempskinreferences[i].id = MAXSKINS; + if (skin != -1) { // We found a skin, so assign the win. M_Memcpy(&skins[skin].records, &dummyrecord, sizeof(skinrecord_t)); + tempskinreferences[i].id = skin; + tempskinreferences[i].unloaded = NULL; } else if (dummyrecord.wins) { @@ -5009,6 +5027,8 @@ void G_LoadGameData(void) // Finally, copy into. M_Memcpy(&unloadedskin->records, &dummyrecord, sizeof(skinrecord_t)); + + tempskinreferences[i].unloaded = unloadedskin; } } } @@ -5100,6 +5120,23 @@ void G_LoadGameData(void) dummywindata[j].best_grade = (rtemp & 0x70)>>4; if (rtemp & 0x80) dummywindata[j].got_emerald = true; + + dummywindata[j].best_skin.id = MAXSKINS; + dummywindata[j].best_skin.unloaded = NULL; + if (versionMinor >= 3) + { + UINT32 _saveid = READUINT32(save.p); + if (_saveid < numgamedataskins) + { + const char *charstr = NULL; + if (tempskinreferences[_saveid].unloaded) + charstr = tempskinreferences[_saveid].unloaded->name; + else + charstr = skins[tempskinreferences[_saveid].id].name; + CONS_Printf(" (TEMPORARY DISPLAY) Cup \"%s\" difficulty %u was completed by skin \"%s\"\n", cupname, j, charstr); + M_Memcpy(&dummywindata[j].best_skin, &tempskinreferences[_saveid], sizeof(dummywindata[j].best_skin)); + } + } } if (versionMinor < 3 && dummywindata[0].best_placement == 0) @@ -5139,6 +5176,9 @@ void G_LoadGameData(void) } } + if (tempskinreferences) + Z_Free(tempskinreferences); + // done P_SaveBufferFree(&save); @@ -5325,7 +5365,7 @@ void G_SaveGameData(void) } } - length += 4 + (numgamedatacups * (MAXCUPNAME+4)); + length += 4 + (numgamedatacups * (MAXCUPNAME + 4*(1+4))); if (P_SaveBufferAlloc(&save, length) == false) @@ -5423,6 +5463,8 @@ void G_SaveGameData(void) { // numgamedataskins * (SKINNAMESIZE+4) + UINT32 maxid = 0; + for (i = 0; i < numskins; i++) { if (skins[i].records.wins == 0) @@ -5432,11 +5474,12 @@ void G_SaveGameData(void) WRITEUINT32(save.p, skins[i].records.wins); - if (--numgamedataskins == 0) + skins[i].records._saveid = maxid; + if (++maxid == numgamedataskins) break; } - if (numgamedataskins) + if (maxid < numgamedataskins) { for (unloadedskin = unloadedskins; unloadedskin; unloadedskin = unloadedskin->next) { @@ -5447,12 +5490,23 @@ void G_SaveGameData(void) WRITEUINT32(save.p, unloadedskin->records.wins); - if (--numgamedataskins == 0) + unloadedskin->records._saveid = maxid; + if (++maxid == numgamedataskins) break; } } } +#define GETSKINREFSAVEID(ref, var) \ + { \ + if (ref.unloaded != NULL) \ + var = ref.unloaded->records._saveid;\ + else if (ref.id < numskins)\ + var = skins[ref.id].records._saveid; \ + else \ + var = UINT32_MAX; \ + } + WRITEUINT32(save.p, numgamedatamapheaders); // 4 if (numgamedatamapheaders) @@ -5507,7 +5561,7 @@ void G_SaveGameData(void) if (numgamedatacups) { - // numgamedatacups * (MAXCUPNAME+4) + // numgamedatacups * (MAXCUPNAME + 4*(1+4)) #define WRITECUPWINDATA(maybeunloadedcup) \ for (i = 0; i < KARTGP_MAX; i++) \ @@ -5518,6 +5572,10 @@ void G_SaveGameData(void) btemp |= 0x80; \ \ WRITEUINT8(save.p, btemp); \ + \ + GETSKINREFSAVEID(maybeunloadedcup->windata[i].best_skin, j); \ + \ + WRITEUINT32(save.p, j); \ } for (cup = kartcupheaders; cup; cup = cup->next) @@ -5552,6 +5610,8 @@ void G_SaveGameData(void) #undef WRITECUPWINDATA } +#undef GETSKINREFSAVEID + length = save.p - save.buffer; FIL_WriteFile(va(pandf, srb2home, gamedatafilename), save.buffer, length); diff --git a/src/k_podium.c b/src/k_podium.c index a9e6f90f1..4c64261ba 100644 --- a/src/k_podium.c +++ b/src/k_podium.c @@ -330,21 +330,37 @@ void K_ResetCeremony(void) // who's just won on Normal from feeling obligated to complete Easy too. for (; i >= 0; i--) { + boolean anymerit = false; + if ((grandprixinfo.cup->windata[i].best_placement == 0) // First run - || (podiumData.rank.position < grandprixinfo.cup->windata[i].best_placement)) // Later, better run + || (podiumData.rank.position <= grandprixinfo.cup->windata[i].best_placement)) // Later, better run { grandprixinfo.cup->windata[i].best_placement = podiumData.rank.position; // The following will not occur in unmodified builds, but pre-emptively sanitise gamedata if someone just changes MAXPLAYERS and calls it a day if (grandprixinfo.cup->windata[i].best_placement > 0x0F) grandprixinfo.cup->windata[i].best_placement = 0x0F; + + anymerit = true; } - if (podiumData.grade > grandprixinfo.cup->windata[i].best_grade) + if (podiumData.grade >= grandprixinfo.cup->windata[i].best_grade) + { grandprixinfo.cup->windata[i].best_grade = podiumData.grade; + anymerit = true; + } if (podiumData.rank.specialWon == true) + { grandprixinfo.cup->windata[i].got_emerald = true; + anymerit = true; + } + + if (anymerit == true) + { + grandprixinfo.cup->windata[i].best_skin.id = podiumData.rank.skin; + grandprixinfo.cup->windata[i].best_skin.unloaded = NULL; + } } // Save before playing the noise diff --git a/src/k_rank.h b/src/k_rank.h index eb2c420cd..0a4375309 100644 --- a/src/k_rank.h +++ b/src/k_rank.h @@ -26,6 +26,7 @@ struct gpRank_t UINT8 totalPlayers; UINT8 position; + UINT8 skin; UINT32 winPoints; UINT32 totalPoints; diff --git a/src/r_skins.c b/src/r_skins.c index cddd8d116..94ac8bb03 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -1034,6 +1034,33 @@ next_token: unloadedskins = unloadedskin->next; } + // Now... we assign everything which used this pointer the new skin id. + UINT8 i; + + cupheader_t *cup; + for (cup = kartcupheaders; cup; cup = cup->next) + { + for (i = 0; i < KARTGP_MAX; i++) + { + if (cup->windata[i].best_skin.unloaded != unloadedskin) + continue; + cup->windata[i].best_skin.id = numskins; + cup->windata[i].best_skin.unloaded = NULL; + } + } + + unloaded_cupheader_t *unloadedcup; + for (unloadedcup = unloadedcupheaders; unloadedcup; unloadedcup = unloadedcup->next) + { + for (i = 0; i < KARTGP_MAX; i++) + { + if (unloadedcup->windata[i].best_skin.unloaded != unloadedskin) + continue; + unloadedcup->windata[i].best_skin.id = numskins; + unloadedcup->windata[i].best_skin.unloaded = NULL; + } + } + // Finally, free. Z_Free(unloadedskin); diff --git a/src/typedef.h b/src/typedef.h index 771313e1c..a54ae82e5 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -115,6 +115,7 @@ TYPEDEF (skincolor_t); TYPEDEF (precipprops_t); TYPEDEF (skinrecord_t); TYPEDEF (unloaded_skin_t); +TYPEDEF (skinreference_t); TYPEDEF (recorddata_t); TYPEDEF (cupwindata_t); TYPEDEF (scene_t); From 956b3f985f7c023ae058f79fff9e63af02e0eb2d Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 29 May 2023 23:54:17 +0100 Subject: [PATCH 20/31] Do not leave uninitialised memory for dummywindata best_placement emerald collection --- src/g_game.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 89bc1c0fd..8a65caaac 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -5118,8 +5118,7 @@ void G_LoadGameData(void) dummywindata[j].best_placement = (rtemp & 0x0F); dummywindata[j].best_grade = (rtemp & 0x70)>>4; - if (rtemp & 0x80) - dummywindata[j].got_emerald = true; + dummywindata[j].got_emerald = !!(rtemp & 0x80); dummywindata[j].best_skin.id = MAXSKINS; dummywindata[j].best_skin.unloaded = NULL; From 4387ea2f71bb102317e48ac17455bab2e7228b2a Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 30 May 2023 00:04:42 +0100 Subject: [PATCH 21/31] M_DrawCupSelect: Updated visuals for Cup windata per difficulty - New Rank icons - Use Minimap-sized Emerald instead of Battle HUD - Shows associated character you last updated (or matched) the position with - Also removes the temporary printout display on G_LoadGameData. --- src/g_game.c | 6 ---- src/k_menudraw.c | 72 +++++++++++++++++++++++++++++++++++++----------- 2 files changed, 56 insertions(+), 22 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 8a65caaac..9d14e0eb0 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -5127,12 +5127,6 @@ void G_LoadGameData(void) UINT32 _saveid = READUINT32(save.p); if (_saveid < numgamedataskins) { - const char *charstr = NULL; - if (tempskinreferences[_saveid].unloaded) - charstr = tempskinreferences[_saveid].unloaded->name; - else - charstr = skins[tempskinreferences[_saveid].id].name; - CONS_Printf(" (TEMPORARY DISPLAY) Cup \"%s\" difficulty %u was completed by skin \"%s\"\n", cupname, j, charstr); M_Memcpy(&dummywindata[j].best_skin, &tempskinreferences[_saveid], sizeof(dummywindata[j].best_skin)); } } diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 49b9c4f67..70422ae13 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -2298,7 +2298,6 @@ void M_DrawCupSelect(void) INT16 icony = 7; char status = 'A'; char monitor; - INT32 rankx = 0; if (!cupgrid.builtgrid[id]) break; @@ -2353,7 +2352,6 @@ void M_DrawCupSelect(void) if (monitor == '2') { icony = 5; - rankx = 2; } } else @@ -2385,25 +2383,68 @@ void M_DrawCupSelect(void) ; else if (windata->best_placement != 0) { - char gradeChar = '?'; + const INT32 rankw = 14 + 12 + 12 + 2; + INT32 rankx = (x + 19) - (rankw / 2); + const INT32 ranky = 8 + (j*100) - (30*menutransition.tics); + + patch_t *gradePat = NULL; switch (windata->best_grade) { - case GRADE_E: { gradeChar = 'E'; break; } - case GRADE_D: { gradeChar = 'D'; break; } - case GRADE_C: { gradeChar = 'C'; break; } - case GRADE_B: { gradeChar = 'B'; break; } - case GRADE_A: { gradeChar = 'A'; break; } - case GRADE_S: { gradeChar = 'S'; break; } - default: { break; } + case GRADE_E: + gradePat = W_CachePatchName("R_CUPRNE", PU_CACHE); + break; + case GRADE_D: + gradePat = W_CachePatchName("R_CUPRND", PU_CACHE); + break; + case GRADE_C: + gradePat = W_CachePatchName("R_CUPRNC", PU_CACHE); + break; + case GRADE_B: + gradePat = W_CachePatchName("R_CUPRNB", PU_CACHE); + break; + case GRADE_A: + gradePat = W_CachePatchName("R_CUPRNA", PU_CACHE); + break; + case GRADE_S: + gradePat = W_CachePatchName("R_CUPRNS", PU_CACHE); + break; + default: + break; } - V_DrawCharacter(x + 5 + rankx, y + icony + 14, gradeChar, false); // rank + if (gradePat) + V_DrawFixedPatch((rankx)*FRACUNIT, (ranky)*FRACUNIT, FRACUNIT, 0, gradePat, NULL); + + rankx += 14 + 1; + + patch_t *charPat = NULL; + + if ((windata->best_skin.unloaded != NULL) + || (windata->best_skin.id > numskins)) + { + colormap = NULL; + + charPat = W_CachePatchName("HUHMAP", PU_CACHE); + } + else + { + UINT8 skin = windata->best_skin.id; + + colormap = R_GetTranslationColormap(skin, skins[skin].prefcolor, GTC_MENUCACHE); + + charPat = faceprefix[skin][FACE_MINIMAP]; + } + + if (charPat) + V_DrawFixedPatch((rankx)*FRACUNIT, (ranky)*FRACUNIT, FRACUNIT, 0, charPat, colormap); if (windata->got_emerald == true) { + rankx += 12 + 1; + if (templevelsearch.cup->emeraldnum == 0) - V_DrawCharacter(x + 26 - rankx, y + icony + 14, '*', false); // rank + V_DrawCharacter(rankx+2, ranky+2, '+', false); else { UINT16 col = SKINCOLOR_CHAOSEMERALD1 + (templevelsearch.cup->emeraldnum-1) % 7; @@ -2413,15 +2454,14 @@ void M_DrawCupSelect(void) if (templevelsearch.cup->emeraldnum > 7) { - em = W_CachePatchName("K_SUPER1", PU_CACHE); - rankx += 2; + em = W_CachePatchName("SUPMAP", PU_CACHE); } else { - em = W_CachePatchName("K_EMERC", PU_CACHE); + em = W_CachePatchName("EMEMAP", PU_CACHE); } - V_DrawFixedPatch((x + 26 - rankx)*FRACUNIT, (y + icony + 13)*FRACUNIT, FRACUNIT, 0, em, colormap); + V_DrawFixedPatch((rankx)*FRACUNIT, (ranky)*FRACUNIT, FRACUNIT, 0, em, colormap); } } } From 782da8c325150510ac07ac974f1f017d3e12a085 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 30 May 2023 13:12:10 +0100 Subject: [PATCH 22/31] K_PlayerFinishGrandPrix: Cap RINGTOTAL to 20 when writing to totalring and grandprixinfo rank Permitted 109/100 rings at the end of GP !? --- src/k_grandprix.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/k_grandprix.c b/src/k_grandprix.c index 151ab7724..dff88ceab 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -815,10 +815,13 @@ void K_PlayerFinishGrandPrix(player_t *player) grandprixinfo.wonround = true; // Increase your total rings - if (RINGTOTAL(player) > 0) + INT32 ringtotal = RINGTOTAL(player); + if (ringtotal > 0) { - player->totalring += RINGTOTAL(player); - grandprixinfo.rank.rings += RINGTOTAL(player); + if (ringtotal > 20) + ringtotal = 20; + player->totalring += ringtotal; + grandprixinfo.rank.rings += ringtotal; } if (grandprixinfo.eventmode == GPEVENT_NONE) From 9e50fea2dc13dd7620aff7bea84f4f3392e7bb49 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 30 May 2023 13:30:22 +0100 Subject: [PATCH 23/31] Now that Super Emerald minimap graphics are in the game, use them on the Sealed Star progression bar. --- src/k_hud.c | 11 ++++++++--- src/objects/ufo.c | 3 ++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/k_hud.c b/src/k_hud.c index 0ce3ccbda..995877f8d 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -81,7 +81,7 @@ static patch_t *kp_nocontestminimap; static patch_t *kp_spbminimap; static patch_t *kp_wouldyoustillcatchmeifiwereaworm; static patch_t *kp_catcherminimap; -static patch_t *kp_emeraldminimap; +static patch_t *kp_emeraldminimap[2]; static patch_t *kp_capsuleminimap[3]; static patch_t *kp_ringsticker[2]; @@ -358,7 +358,8 @@ void K_LoadKartHUDGraphics(void) HU_UpdatePatch(&kp_wouldyoustillcatchmeifiwereaworm, "MINIPROG"); HU_UpdatePatch(&kp_catcherminimap, "UFOMAP"); - HU_UpdatePatch(&kp_emeraldminimap, "EMEMAP"); + HU_UpdatePatch(&kp_emeraldminimap[0], "EMEMAP"); + HU_UpdatePatch(&kp_emeraldminimap[1], "SUPMAP"); HU_UpdatePatch(&kp_capsuleminimap[0], "MINICAP1"); HU_UpdatePatch(&kp_capsuleminimap[1], "MINICAP2"); @@ -3880,7 +3881,11 @@ static void K_drawKartMinimap(void) } else { - workingPic = kp_emeraldminimap; + UINT8 emid = 0; + if (specialstageinfo.ufo->cvmem > 7) + emid = 1; + workingPic = kp_emeraldminimap[emid]; + if (specialstageinfo.ufo->color) { colormap = R_GetTranslationColormap(TC_DEFAULT, specialstageinfo.ufo->color, GTC_CACHE); diff --git a/src/objects/ufo.c b/src/objects/ufo.c index 7a4cf56c0..dacce52e6 100644 --- a/src/objects/ufo.c +++ b/src/objects/ufo.c @@ -38,6 +38,7 @@ #define UFO_NUMARMS (3) #define UFO_ARMDELTA (ANGLE_MAX / UFO_NUMARMS) +#define ufo_emeraldnum(o) ((o)->cvmem) #define ufo_waypoint(o) ((o)->extravalue1) #define ufo_distancetofinish(o) ((o)->extravalue2) #define ufo_speed(o) ((o)->watertop) @@ -1005,7 +1006,7 @@ static mobj_t *InitSpecialUFO(waypoint_t *start) overlay = P_SpawnMobjFromMobj(ufo, 0, 0, 0, MT_OVERLAY); ufo->color = SKINCOLOR_CHAOSEMERALD1; - i = P_GetNextEmerald(); + i = ufo_emeraldnum(ufo) = P_GetNextEmerald(); if (i > 0) { ufo->color += (i - 1) % 7; From 0c7d66791bd993314300f8554d30f0c7d415c121 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 30 May 2023 21:08:29 +0100 Subject: [PATCH 24/31] Expand datatype for Unlockables, emblems (Medals + nonmedals), and Conditions. Made sure there is more than enough headroom for our current purposes. It should be easy to double again if necessary now that the datatypes have been increased... but that would be obscene at this point - 1024 Unlockables and 1024 Conditions (these were always tied together in slots) - 2048 emblems (Medals + nonmedals). If we ship with ~250 maps this permits 8 Medals per map - which is higher than we intend right now but could easily fill out in patches --- src/deh_soc.c | 6 ++-- src/g_game.c | 54 +++++++++++++++++++++++++---------- src/k_follower.c | 2 +- src/k_menu.h | 8 +++--- src/k_menudraw.c | 2 +- src/m_cheat.c | 4 +-- src/m_cond.c | 47 +++++++++++++++--------------- src/m_cond.h | 24 ++++++++-------- src/menus/extras-challenges.c | 10 +++---- src/r_skins.c | 5 ++-- 10 files changed, 94 insertions(+), 68 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index b7d54be2a..b466f1158 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -170,7 +170,7 @@ void clear_unlockables(void) void clear_conditionsets(void) { - UINT8 i; + UINT16 i; for (i = 0; i < MAXCONDITIONSETS; ++i) M_ClearConditionSet(i); } @@ -2317,9 +2317,9 @@ void readunlockable(MYFILE *f, INT32 num) strupr(word2); if (fastcmp(word, "CONDITIONSET")) - unlockables[num].conditionset = (UINT8)i; + unlockables[num].conditionset = (UINT16)i; else if (fastcmp(word, "MAJORUNLOCK")) - unlockables[num].majorunlock = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y'); + unlockables[num].majorunlock = (UINT8)(i != 0 || word2[0] == 'T' || word2[0] == 'Y'); else if (fastcmp(word, "TYPE")) { if (fastcmp(word2, "NONE")) diff --git a/src/g_game.c b/src/g_game.c index 9d14e0eb0..2f86dc500 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4793,6 +4793,11 @@ void G_LoadGameData(void) boolean gridunusable = false; savebuffer_t save = {0}; + UINT16 emblemreadcount = MAXEMBLEMS; + UINT16 unlockreadcount = MAXUNLOCKABLES; + UINT16 conditionreadcount = MAXCONDITIONSETS; + size_t unlockreadsize = sizeof(UINT16); + //For records UINT32 numgamedataskins; UINT32 numgamedatamapheaders; @@ -4897,32 +4902,39 @@ void G_LoadGameData(void) } } + if (versionMinor < 3) + { + emblemreadcount = 512; + unlockreadcount = conditionreadcount = UINT8_MAX; + unlockreadsize = sizeof(UINT8); + } + // To save space, use one bit per collected/achieved/unlocked flag - for (i = 0; i < MAXEMBLEMS;) + for (i = 0; i < emblemreadcount;) { rtemp = READUINT8(save.p); - for (j = 0; j < 8 && j+i < MAXEMBLEMS; ++j) + for (j = 0; j < 8 && j+i < emblemreadcount; ++j) gamedata->collected[j+i] = ((rtemp >> j) & 1); i += j; } - for (i = 0; i < MAXUNLOCKABLES;) + for (i = 0; i < unlockreadcount;) { rtemp = READUINT8(save.p); - for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j) + for (j = 0; j < 8 && j+i < unlockreadcount; ++j) gamedata->unlocked[j+i] = ((rtemp >> j) & 1); i += j; } - for (i = 0; i < MAXUNLOCKABLES;) + for (i = 0; i < unlockreadcount;) { rtemp = READUINT8(save.p); - for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j) + for (j = 0; j < 8 && j+i < unlockreadcount; ++j) gamedata->unlockpending[j+i] = ((rtemp >> j) & 1); i += j; } - for (i = 0; i < MAXCONDITIONSETS;) + for (i = 0; i < conditionreadcount;) { rtemp = READUINT8(save.p); - for (j = 0; j < 8 && j+i < MAXCONDITIONSETS; ++j) + for (j = 0; j < 8 && j+i < conditionreadcount; ++j) gamedata->achieved[j+i] = ((rtemp >> j) & 1); i += j; } @@ -4931,7 +4943,7 @@ void G_LoadGameData(void) { UINT16 burn = READUINT16(save.p); // Previous challengegridwidth UINT8 height = (versionMinor > 0) ? CHALLENGEGRIDHEIGHT : 5; - save.p += (burn * height * sizeof(UINT8)); // Step over previous grid data + save.p += (burn * height * unlockreadsize); // Step over previous grid data gamedata->challengegridwidth = 0; Z_Free(gamedata->challengegrid); @@ -4944,11 +4956,23 @@ void G_LoadGameData(void) if (gamedata->challengegridwidth) { gamedata->challengegrid = Z_Malloc( - (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT * sizeof(UINT8)), + (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT * sizeof(UINT16)), PU_STATIC, NULL); - for (i = 0; i < (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT); i++) + if (unlockreadsize == sizeof(UINT8)) { - gamedata->challengegrid[i] = READUINT8(save.p); + for (i = 0; i < (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT); i++) + { + gamedata->challengegrid[i] = READUINT8(save.p); + if (gamedata->challengegrid[i] == unlockreadcount) + gamedata->challengegrid[i] = MAXUNLOCKABLES; + } + } + else + { + for (i = 0; i < (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT); i++) + { + gamedata->challengegrid[i] = READUINT16(save.p); + } } } else @@ -5261,7 +5285,7 @@ void G_SaveGameData(void) if (gamedata->challengegrid) { - length += gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT; + length += (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT) * 2; } @@ -5434,12 +5458,12 @@ void G_SaveGameData(void) i += j; } - if (gamedata->challengegrid) // 2 + gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT + if (gamedata->challengegrid) // 2 + (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT) * 2 { WRITEUINT16(save.p, gamedata->challengegridwidth); for (i = 0; i < (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT); i++) { - WRITEUINT8(save.p, gamedata->challengegrid[i]); + WRITEUINT16(save.p, gamedata->challengegrid[i]); } } else // 2 diff --git a/src/k_follower.c b/src/k_follower.c index daa7b9d0d..48b9646e2 100644 --- a/src/k_follower.c +++ b/src/k_follower.c @@ -48,7 +48,7 @@ boolean K_FollowerUsable(INT32 skinnum) { // Unlike R_SkinUsable, not netsynced. // Solely used to prevent an invalid value being sent over the wire. - UINT8 i; + UINT16 i; INT32 fid; if (skinnum == -1 || demo.playback) diff --git a/src/k_menu.h b/src/k_menu.h index d461973b4..09cfbf772 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -1189,13 +1189,13 @@ extern struct challengesmenu_s { tic_t ticker; // How long the menu's been open for INT16 offset; // To make the icons move smoothly when we transition! - UINT8 currentunlock; + UINT16 currentunlock; char *unlockcondition; tic_t unlockanim; - SINT8 row, hilix, focusx; - UINT8 col, hiliy; + INT16 row, hilix, focusx; + UINT16 col, hiliy; challengegridextradata_t *extradata; @@ -1205,7 +1205,7 @@ extern struct challengesmenu_s { boolean requestflip; - UINT8 unlockcount[CC_MAX]; + UINT16 unlockcount[CC_MAX]; UINT8 fade; } challengesmenu; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 70422ae13..19126e02e 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4650,7 +4650,7 @@ static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili patch_t *pat = missingpat; UINT8 *colormap = NULL, *bgmap = NULL; fixed_t siz, accordion; - UINT8 id, num; + UINT16 id, num; boolean unlockedyet; boolean categoryside; diff --git a/src/m_cheat.c b/src/m_cheat.c index e4aec3b41..72ffd6523 100644 --- a/src/m_cheat.c +++ b/src/m_cheat.c @@ -62,7 +62,7 @@ typedef struct // Cheat responders static UINT8 cheatf_warp(void) { - UINT8 i; + UINT16 i; boolean success = false; /*if (modifiedgame) @@ -98,7 +98,7 @@ static UINT8 cheatf_warp(void) #ifdef DEVELOP static UINT8 cheatf_devmode(void) { - UINT8 i; + UINT16 i; if (modifiedgame) return 0; diff --git a/src/m_cond.c b/src/m_cond.c index 2a016d30f..87bed8df0 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -60,7 +60,7 @@ void M_PopulateChallengeGrid(void) { UINT16 i, j; UINT16 numunlocks = 0, nummajorunlocks = 0, numempty = 0; - UINT8 selection[2][MAXUNLOCKABLES + (CHALLENGEGRIDHEIGHT-1)]; + UINT16 selection[2][MAXUNLOCKABLES + (CHALLENGEGRIDHEIGHT-1)]; UINT16 majorcompact = 2; if (gamedata->challengegrid != NULL) @@ -98,7 +98,7 @@ void M_PopulateChallengeGrid(void) if (nummajorunlocks) { // Getting the number of 2-highs you can fit into two adjacent columns. - UINT8 majorpad = (CHALLENGEGRIDHEIGHT/2); + UINT16 majorpad = (CHALLENGEGRIDHEIGHT/2); numempty = nummajorunlocks%majorpad; majorpad = (nummajorunlocks+(majorpad-1))/majorpad; @@ -128,7 +128,7 @@ void M_PopulateChallengeGrid(void) } gamedata->challengegrid = Z_Malloc( - (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT * sizeof(UINT8)), + (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT * sizeof(UINT16)), PU_STATIC, NULL); if (!gamedata->challengegrid) @@ -136,9 +136,10 @@ void M_PopulateChallengeGrid(void) I_Error("M_PopulateChallengeGrid: was not able to allocate grid"); } - memset(gamedata->challengegrid, - MAXUNLOCKABLES, - (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT * sizeof(UINT8))); + for (i = 0; i < MAXUNLOCKABLES; ++i) + { + gamedata->challengegrid[i] = MAXUNLOCKABLES; + } // Attempt to place all large tiles first. if (nummajorunlocks) @@ -212,7 +213,7 @@ quickcheckagain: #if (CHALLENGEGRIDHEIGHT == 4) while (nummajorunlocks > 0) { - UINT8 unlocktomoveup = MAXUNLOCKABLES; + UINT16 unlocktomoveup = MAXUNLOCKABLES; j = gamedata->challengegridwidth-1; @@ -313,7 +314,7 @@ quickcheckagain: void M_UpdateChallengeGridExtraData(challengegridextradata_t *extradata) { - UINT8 i, j, num, id, tempid, work; + UINT16 i, j, num, id, tempid, work; boolean idchange; if (gamedata->challengegrid == NULL) @@ -478,7 +479,7 @@ void M_UpdateChallengeGridExtraData(challengegridextradata_t *extradata) } } -void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2, char *stringvar) +void M_AddRawCondition(UINT16 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2, char *stringvar) { condition_t *cond; UINT32 num, wnum; @@ -500,7 +501,7 @@ void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1 cond[wnum].stringvar = stringvar; } -void M_ClearConditionSet(UINT8 set) +void M_ClearConditionSet(UINT16 set) { if (conditionSets[set].numconditions) { @@ -1395,7 +1396,7 @@ static const char *M_GetConditionString(condition_t *cn) #undef BUILDCONDITIONTITLE } -char *M_BuildConditionSetString(UINT8 unlockid) +char *M_BuildConditionSetString(UINT16 unlockid) { conditionset_t *c = NULL; UINT32 lastID = 0; @@ -1651,7 +1652,7 @@ boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud, boolean doall) UINT16 M_GetNextAchievedUnlock(void) { - UINT8 i; + UINT16 i; // Go through unlockables for (i = 0; i < MAXUNLOCKABLES; ++i) @@ -1683,14 +1684,14 @@ UINT16 M_GetNextAchievedUnlock(void) } // Emblem unlocking shit -UINT8 M_CheckLevelEmblems(void) +UINT16 M_CheckLevelEmblems(void) { INT32 i; INT32 valToReach; INT16 tag; INT16 levelnum; - UINT8 res; - UINT8 somethingUnlocked = 0; + boolean res; + UINT16 somethingUnlocked = 0; // Update Score, Time, Rings emblems for (i = 0; i < numemblems; ++i) @@ -1736,13 +1737,13 @@ UINT8 M_CheckLevelEmblems(void) return somethingUnlocked; } -UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separate print when awarding emblems and it's sorta different enough. +UINT16 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separate print when awarding emblems and it's sorta different enough. { INT32 i; INT32 embtype; INT16 levelnum; - UINT8 res; - UINT8 somethingUnlocked = 0; + boolean res; + UINT16 somethingUnlocked = 0; UINT8 flags; for (i = 0; i < numemblems; ++i) @@ -1781,7 +1782,7 @@ UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separa // Quick unlock checks // ------------------- -boolean M_CheckNetUnlockByID(UINT8 unlockid) +boolean M_CheckNetUnlockByID(UINT16 unlockid) { if (unlockid >= MAXUNLOCKABLES || !unlockables[unlockid].conditionset) @@ -1824,7 +1825,7 @@ boolean M_SecretUnlocked(INT32 type, boolean local) boolean M_CupLocked(cupheader_t *cup) { - UINT8 i; + UINT16 i; // Don't lock maps in dedicated servers. // That just makes hosts' lives hell. @@ -1852,7 +1853,7 @@ boolean M_CupLocked(cupheader_t *cup) boolean M_MapLocked(UINT16 mapnum) { - UINT8 i; + UINT16 i; // Don't lock maps in dedicated servers. // That just makes hosts' lives hell. @@ -1918,7 +1919,7 @@ INT32 M_CountMedals(boolean all, boolean extraonly) // Theoretically faster than using M_CountMedals() // Stops when it reaches the target number of medals. -UINT8 M_GotEnoughMedals(INT32 number) +boolean M_GotEnoughMedals(INT32 number) { INT32 i, gottenmedals = 0; for (i = 0; i < numemblems; ++i) @@ -1942,7 +1943,7 @@ UINT8 M_GotEnoughMedals(INT32 number) return false; } -UINT8 M_GotLowEnoughTime(INT32 tictime) +boolean M_GotLowEnoughTime(INT32 tictime) { INT32 curtics = 0; INT32 i; diff --git a/src/m_cond.h b/src/m_cond.h index a4fe978bd..08b3e28e0 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -159,7 +159,7 @@ struct unlockable_t char name[64]; char *icon; UINT16 color; - UINT8 conditionset; + UINT16 conditionset; INT16 type; INT16 variable; char *stringVar; @@ -206,8 +206,8 @@ typedef enum // If you have more secrets than these variables allow in your game, // you seriously need to get a life. -#define MAXCONDITIONSETS UINT8_MAX -#define MAXEMBLEMS 512 +#define MAXCONDITIONSETS 1024 +#define MAXEMBLEMS (MAXCONDITIONSETS*2) #define MAXUNLOCKABLES MAXCONDITIONSETS #define CHALLENGEGRIDHEIGHT 4 @@ -260,7 +260,7 @@ struct gamedata_t // CHALLENGE GRID UINT16 challengegridwidth; - UINT8 *challengegrid; + UINT16 *challengegrid; // # OF TIMES THE GAME HAS BEEN BEATEN UINT32 timesBeaten; @@ -317,15 +317,15 @@ void M_UpdateChallengeGridExtraData(challengegridextradata_t *extradata); #define CHE_CONNECTEDUP (1<<2) #define CHE_DONTDRAW (CHE_CONNECTEDLEFT|CHE_CONNECTEDUP) -char *M_BuildConditionSetString(UINT8 unlockid); +char *M_BuildConditionSetString(UINT16 unlockid); #define DESCRIPTIONWIDTH 170 // Condition set setup -void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2, char *stringvar); +void M_AddRawCondition(UINT16 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2, char *stringvar); void M_UpdateConditionSetsPending(void); // Clearing secrets -void M_ClearConditionSet(UINT8 set); +void M_ClearConditionSet(UINT16 set); void M_ClearSecrets(void); void M_ClearStats(void); @@ -338,11 +338,11 @@ boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud, boolean doall); #define PENDING_CHAOKEYS (UINT16_MAX-1) UINT16 M_GetNextAchievedUnlock(void); -UINT8 M_CheckLevelEmblems(void); -UINT8 M_CompletionEmblems(void); +UINT16 M_CheckLevelEmblems(void); +UINT16 M_CompletionEmblems(void); // Checking unlockable status -boolean M_CheckNetUnlockByID(UINT8 unlockid); +boolean M_CheckNetUnlockByID(UINT16 unlockid); boolean M_SecretUnlocked(INT32 type, boolean local); boolean M_CupLocked(cupheader_t *cup); boolean M_MapLocked(UINT16 mapnum); @@ -356,8 +356,8 @@ const char *M_GetEmblemPatch(emblem_t *em, boolean big); // If you're looking to compare stats for unlocks or what not, use these // They stop checking upon reaching the target number so they // should be (theoretically?) slightly faster. -UINT8 M_GotEnoughMedals(INT32 number); -UINT8 M_GotLowEnoughTime(INT32 tictime); +boolean M_GotEnoughMedals(INT32 number); +boolean M_GotLowEnoughTime(INT32 tictime); INT32 M_UnlockableSkinNum(unlockable_t *unlock); INT32 M_UnlockableFollowerNum(unlockable_t *unlock); diff --git a/src/menus/extras-challenges.c b/src/menus/extras-challenges.c index c7d6674e1..eb49b762a 100644 --- a/src/menus/extras-challenges.c +++ b/src/menus/extras-challenges.c @@ -53,8 +53,8 @@ struct challengesmenu_s challengesmenu; static void M_ChallengesAutoFocus(UINT16 unlockid, boolean fresh) { - UINT8 i; - SINT8 work; + UINT16 i; + INT16 work; if (unlockid >= MAXUNLOCKABLES && gamedata->pendingkeyrounds > 0 && (gamedata->chaokeys < GDMAX_CHAOKEYS)) @@ -62,8 +62,8 @@ static void M_ChallengesAutoFocus(UINT16 unlockid, boolean fresh) if (fresh && unlockid >= MAXUNLOCKABLES) { - UINT8 selection[MAXUNLOCKABLES]; - UINT8 numunlocks = 0; + UINT16 selection[MAXUNLOCKABLES]; + UINT16 numunlocks = 0; // Get a random available unlockable. for (i = 0; i < MAXUNLOCKABLES; i++) @@ -512,7 +512,7 @@ void M_ChallengesTick(void) boolean M_ChallengesInputs(INT32 ch) { const UINT8 pid = 0; - UINT8 i; + UINT16 i; const boolean start = M_MenuButtonPressed(pid, MBT_START); const boolean move = (menucmd[pid].dpad_ud != 0 || menucmd[pid].dpad_lr != 0); (void) ch; diff --git a/src/r_skins.c b/src/r_skins.c index 94ac8bb03..30c4fa487 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -187,7 +187,8 @@ void R_InitSkins(void) UINT8 *R_GetSkinAvailabilities(boolean demolock, boolean forbots) { - UINT8 i, shif, byte; + UINT16 i; + UINT8 shif, byte; INT32 skinid; static UINT8 responsebuffer[MAXAVAILABILITY]; UINT8 defaultbotskin = R_BotDefaultSkin(); @@ -225,7 +226,7 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum, boolean demoskins) { boolean needsunlocked = false; boolean useplayerstruct = (Playing() && playernum != -1); - UINT8 i; + UINT16 i; INT32 skinid; if (skinnum == -1) From cc29c23ac6971abc938f42e7f51c4f02b18940fb Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 30 May 2023 21:09:15 +0100 Subject: [PATCH 25/31] Fix the width of the pending keys for gamedata to prevent unintentional rollover --- src/g_game.c | 15 ++++++++++++--- src/m_cond.h | 2 +- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 2f86dc500..14748a158 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4878,7 +4878,16 @@ void G_LoadGameData(void) gamedata->pendingkeyrounds = READUINT32(save.p); gamedata->pendingkeyroundoffset = READUINT8(save.p); - gamedata->keyspending = READUINT8(save.p); + + if (versionMinor < 3) + { + gamedata->keyspending = READUINT8(save.p); + } + else + { + gamedata->keyspending = READUINT16(save.p); + } + gamedata->chaokeys = READUINT16(save.p); gamedata->everloadedaddon = (boolean)READUINT8(save.p); @@ -5277,7 +5286,7 @@ void G_SaveGameData(void) length = (4+1+1+ 4+4+ (4*GDGT_MAX)+ - 4+1+1+2+ + 4+1+2+2+ 1+1+1+ 4+ (MAXEMBLEMS+(MAXUNLOCKABLES*2)+MAXCONDITIONSETS)+ @@ -5412,7 +5421,7 @@ void G_SaveGameData(void) WRITEUINT32(save.p, gamedata->pendingkeyrounds); // 4 WRITEUINT8(save.p, gamedata->pendingkeyroundoffset); // 1 - WRITEUINT8(save.p, gamedata->keyspending); // 1 + WRITEUINT16(save.p, gamedata->keyspending); // 2 WRITEUINT16(save.p, gamedata->chaokeys); // 2 WRITEUINT8(save.p, gamedata->everloadedaddon); // 1 diff --git a/src/m_cond.h b/src/m_cond.h index 08b3e28e0..fb96a14b7 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -273,7 +273,7 @@ struct gamedata_t // Chao Key condition bypass UINT32 pendingkeyrounds; UINT8 pendingkeyroundoffset; - UINT8 keyspending; + UINT16 keyspending; UINT16 chaokeys; // SPECIFIC SPECIAL EVENTS From d9940fdef21bd93555d5c48e9673d428cd9f1f6d Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 30 May 2023 21:23:07 +0100 Subject: [PATCH 26/31] M_DrawCupSelect: Flickering Chaos/Super Emerald icon ala S3 Data Select --- src/k_menudraw.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 19126e02e..e5379275d 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -2447,21 +2447,22 @@ void M_DrawCupSelect(void) V_DrawCharacter(rankx+2, ranky+2, '+', false); else { - UINT16 col = SKINCOLOR_CHAOSEMERALD1 + (templevelsearch.cup->emeraldnum-1) % 7; - patch_t *em; + colormap = NULL; - colormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE); + if (!(cupgrid.previewanim & 1)) + { + UINT16 col = SKINCOLOR_CHAOSEMERALD1 + (templevelsearch.cup->emeraldnum-1) % 7; - if (templevelsearch.cup->emeraldnum > 7) - { - em = W_CachePatchName("SUPMAP", PU_CACHE); - } - else - { - em = W_CachePatchName("EMEMAP", PU_CACHE); + colormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE); } - V_DrawFixedPatch((rankx)*FRACUNIT, (ranky)*FRACUNIT, FRACUNIT, 0, em, colormap); + const char *emname = va( + "%sMAP%c", + (templevelsearch.cup->emeraldnum > 7) ? "SUP" : "EME", + colormap ? '\0' : 'B' + ); + + V_DrawFixedPatch((rankx)*FRACUNIT, (ranky)*FRACUNIT, FRACUNIT, 0, W_CachePatchName(emname, PU_CACHE), colormap); } } } From e74d01660fe714f3daaf26ab92f13bd0e47e7e26 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 30 May 2023 22:15:08 +0100 Subject: [PATCH 27/31] K_GetGradeColor Colors for grades. Done as a seperate function so that future other circumstances can utilise it. But for now, just apply to M_DrawCupSelect windata. --- src/k_menudraw.c | 12 ++++++++++-- src/k_rank.c | 28 ++++++++++++++++++++++++++++ src/k_rank.h | 14 ++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index e5379275d..067037915 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -51,6 +51,7 @@ #include "d_player.h" // KITEM_ constants #include "doomstat.h" // MAXSPLITSCREENPLAYERS #include "k_grandprix.h" // K_CanChangeRules +#include "k_rank.h" // K_GetGradeColor #include "y_inter.h" // Y_RoundQueueDrawer @@ -2388,8 +2389,15 @@ void M_DrawCupSelect(void) const INT32 ranky = 8 + (j*100) - (30*menutransition.tics); patch_t *gradePat = NULL; + colormap = NULL; - switch (windata->best_grade) + const gp_rank_e grade = windata->best_grade; // (cupgrid.previewanim/TICRATE) % (GRADE_S + 1); -- testing + UINT16 gradecolor = K_GetGradeColor(grade); + + if (gradecolor != SKINCOLOR_NONE) + colormap = R_GetTranslationColormap(TC_DEFAULT, gradecolor, GTC_MENUCACHE); + + switch (grade) { case GRADE_E: gradePat = W_CachePatchName("R_CUPRNE", PU_CACHE); @@ -2414,7 +2422,7 @@ void M_DrawCupSelect(void) } if (gradePat) - V_DrawFixedPatch((rankx)*FRACUNIT, (ranky)*FRACUNIT, FRACUNIT, 0, gradePat, NULL); + V_DrawFixedPatch((rankx)*FRACUNIT, (ranky)*FRACUNIT, FRACUNIT, 0, gradePat, colormap); rankx += 14 + 1; diff --git a/src/k_rank.c b/src/k_rank.c index 61c5a49c6..1420cc7b3 100644 --- a/src/k_rank.c +++ b/src/k_rank.c @@ -417,3 +417,31 @@ gp_rank_e K_CalculateGPGrade(gpRank_t *rankData) return retGrade; } + +/*-------------------------------------------------- + UINT16 K_GetGradeColor(gp_rank_e grade) + + See header file for description. +--------------------------------------------------*/ +UINT16 K_GetGradeColor(gp_rank_e grade) +{ + switch (grade) + { + case GRADE_E: + return SKINCOLOR_BLUE; + case GRADE_D: + return SKINCOLOR_TURTLE; + case GRADE_C: + return SKINCOLOR_ORANGE; + case GRADE_B: + return SKINCOLOR_RED; + case GRADE_A: + return SKINCOLOR_MAGENTA; + case GRADE_S: + return SKINCOLOR_PIGEON; + default: + break; + } + + return SKINCOLOR_NONE; +} diff --git a/src/k_rank.h b/src/k_rank.h index 0a4375309..fd3e86f97 100644 --- a/src/k_rank.h +++ b/src/k_rank.h @@ -82,6 +82,20 @@ void K_InitGrandPrixRank(gpRank_t *rankData); gp_rank_e K_CalculateGPGrade(gpRank_t *rankData); +/*-------------------------------------------------- + UINT16 K_GetGradeColor(gp_rank_e grade) + + Maps grades to skincolors for HUD purposes. + + Input Arguments:- + grade - gp_rank_e representing an achieved ranking. + + Return:- + skincolor ID representing the achieved grade. +--------------------------------------------------*/ +UINT16 K_GetGradeColor(gp_rank_e grade); + + #ifdef __cplusplus } // extern "C" #endif From c0e4e4075b12b417177d0a4f895aa0a7c716edfa Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 31 May 2023 00:53:57 +0100 Subject: [PATCH 28/31] M_PopulateChallengeGrid: Fix memory corruption on fresh gamedata creation --- src/m_cond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/m_cond.c b/src/m_cond.c index 87bed8df0..728185978 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -136,7 +136,7 @@ void M_PopulateChallengeGrid(void) I_Error("M_PopulateChallengeGrid: was not able to allocate grid"); } - for (i = 0; i < MAXUNLOCKABLES; ++i) + for (i = 0; i < (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT); ++i) { gamedata->challengegrid[i] = MAXUNLOCKABLES; } From ceb165518557814d87468c9e96e0eece56764877 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 31 May 2023 11:55:20 +0100 Subject: [PATCH 29/31] M_DrawCupSelect: Only draw Emerald on difficulties greater than Easy. --- src/k_menudraw.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 067037915..174868fbf 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -2447,7 +2447,8 @@ void M_DrawCupSelect(void) if (charPat) V_DrawFixedPatch((rankx)*FRACUNIT, (ranky)*FRACUNIT, FRACUNIT, 0, charPat, colormap); - if (windata->got_emerald == true) + if (cv_dummygpdifficulty.value > 0 + && windata->got_emerald == true) { rankx += 12 + 1; From 5f1f3a6fe3c1ac85568c1b6fedf813a9bbcd00f7 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 31 May 2023 13:18:56 +0100 Subject: [PATCH 30/31] When converting Gamedata, Profiles, and Serverstats files forwards to future versions, create backups. - Same name, but with a ".bak" appended. - Also prevents future versions of serverstats from being loaded into older executables. --- src/g_game.c | 6 ++++++ src/k_profiles.c | 5 +++++ src/k_serverstats.c | 11 ++++++++++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/g_game.c b/src/g_game.c index 14748a158..080ac382d 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4851,6 +4851,12 @@ void G_LoadGameData(void) P_SaveBufferFree(&save); I_Error("Game data is from the future! (expected %d, got %d)\nRename or delete %s (maybe in %s) and try again.", GD_VERSIONMINOR, versionMinor, gamedatafilename, gdfolder); } + else if (versionMinor < GD_VERSIONMINOR) + { + // We're converting - let'd create a backup. + FIL_WriteFile(va("%s" PATHSEP "%s.bak", srb2home, gamedatafilename), save.buffer, save.size); + } + if ((versionMinor == 0 || versionMinor == 1) #ifdef DEVELOP || M_CheckParm("-resetchallengegrid") diff --git a/src/k_profiles.c b/src/k_profiles.c index e536f95e3..48cf6472d 100644 --- a/src/k_profiles.c +++ b/src/k_profiles.c @@ -329,6 +329,11 @@ void PR_LoadProfiles(void) P_SaveBufferFree(&save); I_Error("Existing %s is from the future! (expected %d, got %d)", PROFILESFILE, PROFILEVER, version); } + else if (version < PROFILEVER) + { + // We're converting - let'd create a backup. + FIL_WriteFile(va("%s" PATHSEP "%s.bak", srb2home, PROFILESFILE), save.buffer, save.size); + } numprofiles = READUINT8(save.p); if (numprofiles > MAXPROFILES) diff --git a/src/k_serverstats.c b/src/k_serverstats.c index 77ba7bb4a..3bdd1d7d6 100644 --- a/src/k_serverstats.c +++ b/src/k_serverstats.c @@ -98,7 +98,16 @@ void SV_LoadStats(void) save.p += headerlen; UINT8 version = READUINT8(save.p); - (void)version; // for now + if (version > SERVERSTATSVER) + { + P_SaveBufferFree(&save); + I_Error("Existing %s is from the future! (expected %d, got %d)", SERVERSTATSFILE, SERVERSTATSVER, version); + } + else if (version < SERVERSTATSVER) + { + // We're converting - let'd create a backup. + FIL_WriteFile(va("%s" PATHSEP "%s.bak", srb2home, SERVERSTATSFILE), save.buffer, save.size); + } numtracked = READUINT32(save.p); From bca7449d1552b6cf7aa7aeac074fff873b3b3bbb Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 31 May 2023 13:34:18 +0100 Subject: [PATCH 31/31] G_LoadGameData; Improve backfilling cup windata to do the job properly --- src/g_game.c | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 080ac382d..f65f0ba6d 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -5171,10 +5171,40 @@ void G_LoadGameData(void) } } - if (versionMinor < 3 && dummywindata[0].best_placement == 0) + if (versionMinor < 3) { - // We now require backfilling of placement information. - M_Memcpy(&dummywindata[0], &dummywindata[1], sizeof(dummywindata[0])); + // We now require backfilling of placement information. + + cupwindata_t bestwindata; + bestwindata.best_placement = 0; + + j = KARTGP_MAX; + while (j > 0) + { + j--; + + if (bestwindata.best_placement == 0) + { + if (dummywindata[j].best_placement != 0) + { + M_Memcpy(&bestwindata, &dummywindata[j], sizeof(bestwindata)); + } + continue; + } + + if (dummywindata[j].best_placement != 0) + { + if (dummywindata[j].best_placement < bestwindata.best_placement) + bestwindata.best_placement = dummywindata[j].best_placement; + + if (dummywindata[j].best_grade > bestwindata.best_grade) + bestwindata.best_grade = dummywindata[j].best_grade; + + bestwindata.got_emerald |= dummywindata[j].got_emerald; + } + + M_Memcpy(&dummywindata[j], &bestwindata, sizeof(dummywindata[j])); + } } if (cup)