diff --git a/src/deh_soc.c b/src/deh_soc.c index 0194af15c..a748747c1 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -216,6 +216,13 @@ void clear_levels(void) } } + if (gamedata) + { + UINT16 i; + for (i = 1; i < MAXCANCOLORS; i++) + gamedata->spraycans[i].map = 0; + } + // Exit the current gamemap as a safeguard if (Playing()) COM_BufAddText("exitgame"); // Command_ExitGame_f() but delayed diff --git a/src/deh_tables.c b/src/deh_tables.c index 97a78e936..58f70bf80 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -6538,6 +6538,7 @@ struct int_const_s const INT_CONST[] = { // SKINCOLOR_ doesn't include these..! {"MAXSKINCOLORS",MAXSKINCOLORS}, + {"MAXCANCOLORS",MAXCANCOLORS}, {"FIRSTSUPERCOLOR",FIRSTSUPERCOLOR}, {"NUMSUPERCOLORS",NUMSUPERCOLORS}, diff --git a/src/doomdef.h b/src/doomdef.h index 75264bbd0..c9f8b1702 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -346,7 +346,9 @@ typedef enum SKINCOLOR_BLOSSOM, SKINCOLOR_TAFFY, - FIRSTSUPERCOLOR, + MAXCANCOLORS, + + FIRSTSUPERCOLOR = MAXCANCOLORS, // Super special awesome Super flashing colors! SKINCOLOR_SUPERSILVER1 = FIRSTSUPERCOLOR, diff --git a/src/doomstat.h b/src/doomstat.h index c4f8b37e7..eecb6d1c7 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -531,6 +531,9 @@ struct mapheader_t UINT8 precutscenenum; ///< Cutscene number to play BEFORE a level starts. UINT8 cutscenenum; ///< Cutscene number to use, 0 for none. + UINT32 _saveid; ///< Purely assistive in gamedata save processes + UINT16 cachedcan; ///< Cached Spraycan ID + // Lua information UINT8 numCustomOptions; ///< Internal. For Lua custom value support. customoption_t *customopts; ///< Custom options. Allocated dynamically for space reasons. Be careful. diff --git a/src/g_game.c b/src/g_game.c index 19f203995..718b7836d 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4357,7 +4357,7 @@ void G_LoadGameSettings(void) } #define GD_VERSIONCHECK 0xBA5ED123 // Change every major version, as usual -#define GD_VERSIONMINOR 5 // Change every format update +#define GD_VERSIONMINOR 6 // Change every format update typedef enum { @@ -4678,10 +4678,17 @@ void G_LoadGameData(void) } } + UINT16 *tempmapidreferences = NULL; + numgamedatamapheaders = READUINT32(save.p); if (numgamedatamapheaders) { + tempmapidreferences = Z_Malloc( + numgamedatamapheaders * sizeof (UINT16), + PU_STATIC, + NULL + ); for (i = 0; i < numgamedatamapheaders; i++) { @@ -4691,6 +4698,7 @@ void G_LoadGameData(void) READSTRINGL(save.p, mapname, MAXMAPLUMPNAME); mapnum = G_MapNumber(mapname); + tempmapidreferences[i] = (UINT16)mapnum; recorddata_t dummyrecord; @@ -4735,6 +4743,49 @@ void G_LoadGameData(void) } } + if (versionMinor > 5) + { + UINT16 numgamedatacans = READUINT32(save.p); + + if (numgamedatacans != MAXCANCOLORS - 1) + { + save.p += (1 + 4) * numgamedatacans; + } + else + { + for (i = 1; i < MAXCANCOLORS; i++) + { + gamedata->spraycans[i].got = (boolean)READUINT8(save.p); + gamedata->spraycans[i].map = 0; + + UINT32 _saveid = READUINT32(save.p); + + if (_saveid >= numgamedatamapheaders) + { + //CONS_Printf("LOAD - Color %s - id %u, map 0 (invalid id)\n", skincolors[i].name, _saveid); + continue; + } + + UINT16 map = tempmapidreferences[_saveid]; + if (map >= nummapheaders || !mapheaderinfo[map]) + { + //CONS_Printf("LOAD - Color %s - id %u, map 0 (unloaded header)\n", skincolors[i].name, _saveid); + continue; + } + + //CONS_Printf("LOAD - Color %s - id %u, map %d\n", skincolors[i].name, _saveid, map+1); + + gamedata->spraycans[i].map = map+1; + mapheaderinfo[map]->cachedcan = i; + + numgamedatacans--; // this one was successfully placed + } + + gamedata->allspraycansplaced = (numgamedatacans == 0); + //CONS_Printf("CCC - all spray cans placed? %c\n", gamedata->allspraycansplaced ? 'Y' : 'N'); + } + } + if (versionMinor > 1) { numgamedatacups = READUINT32(save.p); @@ -4862,6 +4913,8 @@ void G_LoadGameData(void) if (tempskinreferences) Z_Free(tempskinreferences); + if (tempmapidreferences) + Z_Free(tempmapidreferences); // done P_SaveBufferFree(&save); @@ -4983,12 +5036,16 @@ void G_SaveGameData(void) for (i = 0; i < nummapheaders; i++) { + // No spraycan attached. + if (mapheaderinfo[i]->cachedcan == 0 // 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)) + && !(mapheaderinfo[i]->records.mapvisited & MV_MAX)) { + mapheaderinfo[i]->_saveid = UINT32_MAX; continue; } + mapheaderinfo[i]->_saveid = numgamedatamapheaders; numgamedatamapheaders++; } @@ -5012,6 +5069,9 @@ void G_SaveGameData(void) length += 4 + (numgamedatamapheaders * (MAXMAPLUMPNAME+1+4+4)); + length += 4 + ((MAXCANCOLORS - 1) * (1 + 4)); + + UINT32 numgamedatacups = 0; unloaded_cupheader_t *unloadedcup; @@ -5206,7 +5266,8 @@ void G_SaveGameData(void) for (i = 0; i < nummapheaders; i++) { - if (!(mapheaderinfo[i]->records.mapvisited & MV_MAX)) + if (mapheaderinfo[i]->cachedcan == 0 + && !(mapheaderinfo[i]->records.mapvisited & MV_MAX)) continue; WRITESTRINGL(save.p, mapheaderinfo[i]->lumpname, MAXMAPLUMPNAME); @@ -5247,6 +5308,27 @@ void G_SaveGameData(void) } } + WRITEUINT32(save.p, MAXCANCOLORS - 1); // 4 + + // (MAXCANCOLORS - 1) * (1 + 4) + + for (i = 1; i < MAXCANCOLORS; i++) + { + WRITEUINT8(save.p, gamedata->spraycans[i].got); + + UINT32 _saveid = UINT32_MAX; + + UINT16 map = gamedata->spraycans[i].map; + + if (map > 0 && map <= nummapheaders && mapheaderinfo[map - 1]) + { + _saveid = mapheaderinfo[map - 1]->_saveid; + } + + //CONS_Printf("SAVE - Color %s - id %u, map %d\n", skincolors[i].name, _saveid, map); + + WRITEUINT32(save.p, _saveid); + } WRITEUINT32(save.p, numgamedatacups); // 4 diff --git a/src/k_menudraw.c b/src/k_menudraw.c index b789a97f4..6b7b6fa7f 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -6049,6 +6049,26 @@ challengedesc: static void M_DrawMapMedals(INT32 mapnum, INT32 x, INT32 y) { UINT8 lasttype = UINT8_MAX, curtype; + + boolean start = false; + + if (mapheaderinfo[mapnum]->cachedcan != 0 && mapheaderinfo[mapnum]->cachedcan < MAXCANCOLORS) + { + V_DrawSmallMappedPatch(x, y, 0, W_CachePatchName("GOTITA", PU_CACHE), + R_GetTranslationColormap(TC_RAINBOW, mapheaderinfo[mapnum]->cachedcan, GTC_MENUCACHE)); + //V_DrawRightAlignedThinString(x - 2, y, 0, skincolors[mapheaderinfo[mapnum]->cachedcan].name); + x -= 8; + + start = true; + } + + // Shift over if emblem is of a different discipline + if (start) + x -= 4; + + // M_GetLevelEmblems is ONE-indexed, urgh + mapnum++; + emblem_t *emblem = M_GetLevelEmblems(mapnum); while (emblem) @@ -6091,7 +6111,7 @@ static void M_DrawMapMedals(INT32 mapnum, INT32 x, INT32 y) if (gamedata->collected[emblem-emblemlocations]) V_DrawSmallMappedPatch(x, y, 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_CACHE), - R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_MENUCACHE)); + R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_MENUCACHE)); else V_DrawSmallScaledPatch(x, y, 0, W_CachePatchName("NEEDIT", PU_CACHE)); @@ -6246,7 +6266,7 @@ static void M_DrawStatsMaps(void) } } - M_DrawMapMedals(mnum+1, medalspos - 8, y); + M_DrawMapMedals(mnum, medalspos - 8, y); if (mapheaderinfo[mnum]->menuttl[0]) { diff --git a/src/m_cond.c b/src/m_cond.c index de7a032e0..ae8143b2d 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -621,14 +621,23 @@ void M_ClearStats(void) void M_ClearSecrets(void) { - INT32 i; + memset(gamedata->collected, 0, sizeof(gamedata->collected)); + memset(gamedata->unlocked, 0, sizeof(gamedata->unlocked)); + memset(gamedata->unlockpending, 0, sizeof(gamedata->unlockpending)); + if (!dedicated) + memset(netUnlocked, 0, sizeof(netUnlocked)); + memset(gamedata->achieved, 0, sizeof(gamedata->achieved)); - for (i = 0; i < MAXEMBLEMS; ++i) - gamedata->collected[i] = false; - for (i = 0; i < MAXUNLOCKABLES; ++i) - gamedata->unlocked[i] = gamedata->unlockpending[i] = netUnlocked[i] = false; - for (i = 0; i < MAXCONDITIONSETS; ++i) - gamedata->achieved[i] = false; + gamedata->allspraycansplaced = false; + memset(gamedata->spraycans, 0, sizeof(gamedata->spraycans)); + + INT32 i; + for (i = 0; i < nummapheaders; i++) + { + if (!mapheaderinfo[i]) + continue; + mapheaderinfo[i]->cachedcan = 0; + } Z_Free(gamedata->challengegrid); gamedata->challengegrid = NULL; @@ -640,10 +649,175 @@ void M_ClearSecrets(void) gamedata->chaokeys = 3; // Start with 3 !! } +// For lack of a better idea on where to put this +static void M_Shuffle_UINT16(UINT16 *list, size_t len) +{ + size_t i; + UINT16 temp; + while (--len > 1) // no need to swap on == + { + i = M_RandomKey(len); + temp = list[i]; + list[i] = list[len]; + list[len] = temp; + } +} + +static void M_AssignSpraycans(void) +{ + // Very convenient I'm programming this on + // the release date of "Bomb Rush Cyberfunk". + // ~toast 180823 (committed a day later) + + if (gamedata->allspraycansplaced) + return; + + // Init ordered list of skincolors + UINT16 tempcanlist[MAXCANCOLORS]; + size_t listlen = 0; + + // Todo one of these should be a freebie + UINT16 prependlist[] = + { + SKINCOLOR_RED, + SKINCOLOR_ORANGE, + SKINCOLOR_YELLOW, + SKINCOLOR_GREEN, + SKINCOLOR_BLUE, + SKINCOLOR_PURPLE, + 0 + }; + + UINT16 i; + + for (i = 0; prependlist[i]; i++) + { + if (gamedata->spraycans[prependlist[i]].map > 0 + && gamedata->spraycans[prependlist[i]].map <= nummapheaders) + continue; + + CONS_Printf("DDD - Prepending %d\n", prependlist[i]); + + tempcanlist[listlen] = prependlist[i]; + gamedata->spraycans[prependlist[i]].got = 2; // invalid set to detect in below loop, rather than having to iterate over prependlist again + listlen++; + } + + size_t prepend = listlen; + + for (i = 1; i < MAXCANCOLORS; i++) + { + if (gamedata->spraycans[i].map > 0 + && gamedata->spraycans[i].map <= nummapheaders) + continue; + + if (gamedata->spraycans[i].got == 2) + { + // re-make valid, reject duplicating prepended + gamedata->spraycans[i].got = false; + continue; + } + + CONS_Printf("DDD - Adding %d\n", i); + + tempcanlist[listlen] = i; + listlen++; + } + + if (!listlen) + goto cansdone; + + if (prepend > 0) + { + // Swap the prepend for random order + M_Shuffle_UINT16(tempcanlist, prepend); + } + + if (listlen > prepend) + { + // Swap everything else for random order + M_Shuffle_UINT16(tempcanlist + prepend, listlen - prepend); + } + + i = 0; + + cupheader_t *cup; + + UINT16 level; + + for (cup = kartcupheaders; cup; cup = cup->next) + { + UINT8 j; + for (j = 0; j < cup->numlevels; j++) + { + level = cup->cachedlevels[j]; + + if (level > nummapheaders) + continue; + + if (mapheaderinfo[level]->cachedcan != 0) + continue; + + gamedata->spraycans[tempcanlist[i]].map = level + 1; + mapheaderinfo[level]->cachedcan = tempcanlist[i]; + + if (++i < listlen) + continue; + + goto cansdone; + } + } + + for (level = 0; level < nummapheaders; level++) + { + if (!mapheaderinfo[level] + || !(mapheaderinfo[level]->typeoflevel & TOL_RACE) + || mapheaderinfo[level]->cachedcan != 0) + continue; + + gamedata->spraycans[tempcanlist[i]].map = level + 1; + mapheaderinfo[level]->cachedcan = tempcanlist[i]; + + if (++i < listlen) + continue; + + goto cansdone; + } + +cansdone: + +#ifdef PARANOIA + for (i = 1; i < MAXCANCOLORS; i++) + { + if (gamedata->spraycans[i].map == 0) + I_Error("CANPROBLEM - BAD MAP FOR CAN %d\n", i); + if (gamedata->spraycans[i].map > nummapheaders) + I_Error("CANPROBLEM - TOO BIG MAP FOR CAN %d\n", i); + if (mapheaderinfo[gamedata->spraycans[i].map-1]->cachedcan != i) + I_Error("CANPROBLEM - MAP AND CAN DISAGREE FOR %d (%d)\n", i, mapheaderinfo[gamedata->spraycans[i].map-1]->cachedcan); + } + + for (i = 0; i < nummapheaders; i++) + { + if (!mapheaderinfo[i] || mapheaderinfo[i]->cachedcan == 0) + continue; + if (mapheaderinfo[i]->cachedcan > MAXCANCOLORS) + I_Error("MAPPROBLEM - BAD CAN FOR MAP %d\n", i); + if (gamedata->spraycans[mapheaderinfo[i]->cachedcan].map-1 != i) + I_Error("MAPPROBLEM - CAN AND MAP DISAGREE FOR %d (%d)\n", i, gamedata->spraycans[mapheaderinfo[i]->cachedcan].map-1); + } +#endif + + gamedata->allspraycansplaced = true; +} + void M_FinaliseGameData(void) { //M_PopulateChallengeGrid(); -- This can be done lazily when we actually need it + // Place the spraycans, which CAN'T be done lazily. + M_AssignSpraycans(); + // 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 corruption, which can diff --git a/src/m_cond.h b/src/m_cond.h index b08ff611e..e01c1b2d9 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -251,6 +251,12 @@ typedef enum { GDGT_MAX } roundsplayed_t; +struct candata_t +{ + UINT16 map; + boolean got; +}; + // GAMEDATA STRUCTURE // Everything that would get saved in gamedata.dat struct gamedata_t @@ -272,6 +278,10 @@ struct gamedata_t boolean unlocked[MAXUNLOCKABLES]; boolean unlockpending[MAXUNLOCKABLES]; + // SPRAYCANS COLLECTED + boolean allspraycansplaced; + candata_t spraycans[MAXCANCOLORS]; + // CHALLENGE GRID UINT16 challengegridwidth; UINT16 *challengegrid; diff --git a/src/p_setup.c b/src/p_setup.c index 3f9bfcd2e..b687cd347 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -460,6 +460,8 @@ static void P_ClearSingleMapHeaderInfo(INT16 num) mapheaderinfo[num]->justPlayed = 0; mapheaderinfo[num]->anger = 0; + mapheaderinfo[num]->cachedcan = 0; + mapheaderinfo[num]->customopts = NULL; mapheaderinfo[num]->numCustomOptions = 0; } diff --git a/src/typedef.h b/src/typedef.h index 7bbc232b6..a27ca364f 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -238,6 +238,7 @@ TYPEDEF (condition_t); TYPEDEF (conditionset_t); TYPEDEF (emblem_t); TYPEDEF (unlockable_t); +TYPEDEF (candata_t); TYPEDEF (gamedata_t); TYPEDEF (challengegridextradata_t);