diff --git a/src/command.h b/src/command.h index d1ddfa337..8ad27bfbc 100644 --- a/src/command.h +++ b/src/command.h @@ -174,11 +174,7 @@ extern CV_PossibleValue_t CV_Unsigned[]; extern CV_PossibleValue_t CV_Natural[]; // SRB2kart -#define KARTSPEED_AUTO -1 -#define KARTSPEED_EASY 0 -#define KARTSPEED_NORMAL 1 -#define KARTSPEED_HARD 2 -#define KARTGP_MASTER 3 // Not a speed setting, gives the hardest speed with maxed out bots +// the KARTSPEED and KARTGP were previously defined here, but moved to doomstat to avoid circular dependencies extern CV_PossibleValue_t kartspeed_cons_t[], dummykartspeed_cons_t[], gpdifficulty_cons_t[]; extern consvar_t cv_execversion; diff --git a/src/doomstat.h b/src/doomstat.h index 33c200dbf..ba12196c9 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -120,6 +120,30 @@ struct recorddata_t //UINT16 rings; ///< Rings when the level was finished. }; +#define KARTSPEED_AUTO -1 +#define KARTSPEED_EASY 0 +#define KARTSPEED_NORMAL 1 +#define KARTSPEED_HARD 2 +#define KARTGP_MASTER 3 // Not a speed setting, gives the hardest speed with maxed out bots +#define KARTGP_MAX 4 + +typedef enum +{ + GRADE_E, + GRADE_D, + GRADE_C, + GRADE_B, + GRADE_A, + GRADE_S +} gp_rank_e; + +struct cupwindata_t +{ + UINT8 best_placement; + gp_rank_e best_grade; + 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) @@ -358,6 +382,7 @@ struct cupheader_t UINT8 numlevels; ///< Number of levels defined in levellist UINT8 numbonus; ///< Number of bonus stages defined UINT8 emeraldnum; ///< ID of Emerald to use for special stage (1-7 for Chaos Emeralds, 8-14 for Super Emeralds, 0 for no emerald) + cupwindata_t windata[4]; ///< Data for cup visitation cupheader_t *next; ///< Next cup in linked list }; diff --git a/src/g_game.c b/src/g_game.c index e22b5d659..b48ee700b 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -463,6 +463,8 @@ void G_AllocMainRecordData(INT16 i) void G_ClearRecords(void) { INT16 i; + cupheader_t *cup; + for (i = 0; i < nummapheaders; ++i) { if (mapheaderinfo[i]->mainrecord) @@ -471,6 +473,11 @@ void G_ClearRecords(void) mapheaderinfo[i]->mainrecord = NULL; } } + + for (cup = kartcupheaders; cup; cup = cup->next) + { + memset(&cup->windata, 0, sizeof(cup->windata)); + } } // For easy retrieval of records @@ -4498,6 +4505,7 @@ void G_LoadGameData(void) //For records UINT32 numgamedatamapheaders; + UINT32 numgamedatacups; // Stop saving, until we successfully load it again. gamedata->loaded = false; @@ -4696,21 +4704,57 @@ void G_LoadGameData(void) } } + if (versionMinor > 1) + { + numgamedatacups = READUINT32(save.p); + + for (i = 0; i < numgamedatacups; i++) + { + char cupname[16]; + cupheader_t *cup; + + READSTRINGN(save.p, cupname, sizeof(cupname)); + for (cup = kartcupheaders; cup; cup = cup->next) + { + if (strcmp(cup->name, cupname)) + continue; + + for (j = 0; j < KARTGP_MAX; j++) + { + rtemp = READUINT8(save.p); + + cup->windata[j].best_placement = (rtemp & 0x0F); + cup->windata[j].best_grade = (rtemp & 0x70)>>4; + if (rtemp & 0x80) + { + if (j == 0) + goto datacorrupt; + + cup->windata[j].got_emerald = true; + } + } + + break; + } + } + } + // done P_SaveBufferFree(&save); -finalisegamedata: + finalisegamedata: + { + // Don't consider loaded until it's a success! + // It used to do this much earlier, but this would cause the gamedata to + // save over itself when it I_Errors from the corruption landing point below, + // which can accidentally delete players' legitimate data if the code ever has any tiny mistakes! + gamedata->loaded = true; - // Don't consider loaded until it's a success! - // It used to do this much earlier, but this would cause the gamedata to - // save over itself when it I_Errors from the corruption landing point below, - // which can accidentally delete players' legitimate data if the code ever has any tiny mistakes! - gamedata->loaded = true; + // Silent update unlockables in case they're out of sync with conditions + M_UpdateUnlockablesAndExtraEmblems(false); - // Silent update unlockables in case they're out of sync with conditions - M_UpdateUnlockablesAndExtraEmblems(false); - - return; + return; + } // Landing point for corrupt gamedata datacorrupt: @@ -4730,7 +4774,8 @@ finalisegamedata: void G_SaveGameData(boolean dirty) { size_t length; - INT32 i, j; + INT32 i, j, numcups; + cupheader_t *cup; UINT8 btemp; savebuffer_t save = {0}; @@ -4752,12 +4797,20 @@ void G_SaveGameData(boolean dirty) 4+1+1+1+1+ 1+1+4+ (MAXEMBLEMS+(MAXUNLOCKABLES*2)+MAXCONDITIONSETS)+ - 4+4+2); + 4+2); + if (gamedata->challengegrid) { length += gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT; } - length += nummapheaders * (MAXMAPLUMPNAME+1+4+4); + length += 4 + (nummapheaders * (MAXMAPLUMPNAME+1+4+4)); + + numcups = 0; + for (cup = kartcupheaders; cup; cup = cup->next) + { + numcups++; + } + length += 4 + (numcups * 4); if (P_SaveBufferAlloc(&save, length) == false) { @@ -4851,7 +4904,7 @@ void G_SaveGameData(boolean dirty) for (i = 0; i < nummapheaders; i++) // nummapheaders * (255+1+4+4) { - // For figuring out which header to assing it to on load + // 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)); @@ -4868,6 +4921,24 @@ void G_SaveGameData(boolean dirty) } } + WRITEUINT32(save.p, numcups); // 4 + + for (cup = kartcupheaders; cup; cup = cup->next) + { + // For figuring out which header to assign it to on load + WRITESTRINGN(save.p, cup->name, 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; + + WRITEUINT8(save.p, btemp); // 4 * numcups + } + } + length = save.p - save.buffer; FIL_WriteFile(va(pandf, srb2home, gamedatafilename), save.buffer, length); diff --git a/src/k_menudraw.c b/src/k_menudraw.c index ba48c2a30..eb0cf5e00 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -2113,6 +2113,8 @@ static void M_DrawCupTitle(INT16 y, levelsearch_t *levelsearch) void M_DrawCupSelect(void) { UINT8 i, j, temp = 0; + UINT8 *colormap = NULL; + cupwindata_t *windata = NULL; levelsearch_t templevelsearch = levellist.levelsearch; // full copy for (i = 0; i < CUPMENU_COLUMNS; i++) @@ -2123,26 +2125,62 @@ void M_DrawCupSelect(void) patch_t *patch = NULL; INT16 x, y; INT16 icony = 7; + char status = 'A'; + UINT8 monitor = 1; + INT32 rankx = 0; if (!cupgrid.builtgrid[id]) break; templevelsearch.cup = cupgrid.builtgrid[id]; - /*if (templevelsearch.cup->emeraldnum == 0) - patch = W_CachePatchName("CUPMON3A", PU_CACHE); - else*/ if (templevelsearch.cup->emeraldnum > 7) + if (cupgrid.grandprix + && (cv_dummygpdifficulty.value >= 0 && cv_dummygpdifficulty.value < KARTGP_MAX)) { - patch = W_CachePatchName("CUPMON2A", PU_CACHE); - icony = 5; + UINT16 col = SKINCOLOR_NONE; + + windata = &templevelsearch.cup->windata[cv_dummygpdifficulty.value]; + + switch (windata->best_placement) + { + case 0: + break; + case 1: + col = SKINCOLOR_GOLD; + status = 'B'; + break; + case 2: + col = SKINCOLOR_SILVER; + status = 'B'; + break; + case 3: + col = SKINCOLOR_BRONZE; + status = 'B'; + break; + default: + col = SKINCOLOR_BEIGE; + break; + } + + if (col != SKINCOLOR_NONE) + colormap = R_GetTranslationColormap(TC_RAINBOW, col, GTC_MENUCACHE); + else + colormap = NULL; } - else - patch = W_CachePatchName("CUPMON1A", PU_CACHE); + + if (templevelsearch.cup->emeraldnum > 7) + { + monitor = 2; + icony = 5; + rankx = 2; + } + + patch = W_CachePatchName(va("CUPMON%d%c", monitor, status), PU_CACHE); x = 14 + (i*42); y = 20 + (j*44) - (30*menutransition.tics); - V_DrawScaledPatch(x, y, 0, patch); + V_DrawFixedPatch((x)*FRACUNIT, (y)<icon, PU_CACHE)); V_DrawScaledPatch(x + 8, y + icony, 0, W_CachePatchName("CUPBOX", PU_CACHE)); + + if (!windata) + ; + else if (windata->best_placement != 0) + { + char gradeChar = '?'; + + 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; } + } + + V_DrawCharacter(x + 5 + rankx, y + icony + 14, gradeChar, false); // rank + + if (windata->got_emerald == true) + { + if (templevelsearch.cup->emeraldnum == 0) + V_DrawCharacter(x + 26 - rankx, y + icony + 14, '*', false); // rank + else + { + UINT16 col = SKINCOLOR_CHAOSEMERALD1 + (templevelsearch.cup->emeraldnum-1) % 7; + colormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE); + + V_DrawFixedPatch((x + 26 - rankx)*FRACUNIT, (y + icony + 13)*FRACUNIT, FRACUNIT, 0, W_CachePatchName("K_EMERC", PU_CACHE), colormap); + } + } + } } } } diff --git a/src/k_podium.c b/src/k_podium.c index 7f00834d9..0d6b23e5b 100644 --- a/src/k_podium.c +++ b/src/k_podium.c @@ -264,6 +264,10 @@ void K_FinishCeremony(void) } podiumData.ranking = true; + + // Play the noise now + M_UpdateUnlockablesAndExtraEmblems(true); + G_SaveGameData(true); } /*-------------------------------------------------- @@ -273,6 +277,8 @@ void K_FinishCeremony(void) --------------------------------------------------*/ void K_ResetCeremony(void) { + UINT8 i; + memset(&podiumData, 0, sizeof(struct podiumData_s)); if (K_PodiumSequence() == false) @@ -280,8 +286,36 @@ void K_ResetCeremony(void) return; } + // Establish rank and grade for this play session. podiumData.rank = grandprixinfo.rank; podiumData.grade = K_CalculateGPGrade(&podiumData.rank); + + if (!grandprixinfo.cup) + { + return; + } + + // 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 + { + 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; + } + + 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(true); } /*-------------------------------------------------- diff --git a/src/k_rank.h b/src/k_rank.h index cc675db17..7903de708 100644 --- a/src/k_rank.h +++ b/src/k_rank.h @@ -44,15 +44,7 @@ struct gpRank_t boolean specialWon; }; -typedef enum -{ - GRADE_E, - GRADE_D, - GRADE_C, - GRADE_B, - GRADE_A, - GRADE_S -} gp_rank_e; +// gp_rank_e was once defined here, but moved to doomstat.h to prevent circular dependency // 3rd place is neutral, anything below is a penalty #define RANK_NEUTRAL_POSITION (3) diff --git a/src/typedef.h b/src/typedef.h index 5071ff01f..0615e7034 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -108,6 +108,7 @@ TYPEDEF (skincolor_t); // doomstat.h TYPEDEF (precipprops_t); TYPEDEF (recorddata_t); +TYPEDEF (cupwindata_t); TYPEDEF (scene_t); TYPEDEF (cutscene_t); TYPEDEF (textpage_t);