diff --git a/src/acs/call-funcs.cpp b/src/acs/call-funcs.cpp index 812bfdbb4..47502cb0f 100644 --- a/src/acs/call-funcs.cpp +++ b/src/acs/call-funcs.cpp @@ -1913,8 +1913,9 @@ bool CallFunc_GetGrabbedSprayCan(ACSVM::Thread *thread, const ACSVM::Word *argV, && gamemap-1 < basenummapheaders) { // See also P_SprayCanInit - UINT16 can_id = mapheaderinfo[gamemap-1]->cache_spraycan; + UINT16 can_id = mapheaderinfo[gamemap-1]->records.spraycan; + // Intentionally not affected by MCAN_BONUS if (can_id < gamedata->numspraycans) { UINT16 col = gamedata->spraycans[can_id].col; diff --git a/src/doomstat.h b/src/doomstat.h index a9e46a0e9..022cd054e 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -156,6 +156,9 @@ struct skinreference_t #define MV_MYSTICMELODY (1<<4) #define MV_MAX (MV_VISITED|MV_BEATEN|MV_ENCORE|MV_SPBATTACK|MV_MYSTICMELODY) +#define MCAN_INVALID (UINT16_MAX) +#define MCAN_BONUS (UINT16_MAX-1) + struct recordtimes_t { tic_t time; ///< Time in which the level was finished. @@ -164,9 +167,10 @@ struct recordtimes_t struct recorddata_t { - UINT8 mapvisited; + UINT8 mapvisited; ///< Generalised flags recordtimes_t timeattack; ///< Best times for Time Attack recordtimes_t spbattack; ///< Best times for SPB Attack + UINT16 spraycan; ///< Associated spraycan id UINT32 timeplayed; UINT32 netgametimeplayed; UINT32 modetimeplayed[GDGT_MAX]; @@ -575,7 +579,6 @@ struct mapheader_t mobjtype_t destroyforchallenge[MAXDESTRUCTIBLES]; ///< Assistive for UCRP_MAPDESTROYOBJECTS UINT8 destroyforchallenge_size; ///< Number for above - UINT16 cache_spraycan; ///< Cached Spraycan ID UINT16 cache_maplock; ///< Cached Unlockable ID // Lua information diff --git a/src/g_gamedata.cpp b/src/g_gamedata.cpp index 58a84685c..556def12a 100644 --- a/src/g_gamedata.cpp +++ b/src/g_gamedata.cpp @@ -29,7 +29,9 @@ namespace fs = std::filesystem; #define GD_VERSION_MAJOR (0xBA5ED321) -#define GD_VERSION_MINOR (1) +#define GD_VERSION_MINOR (2) + +#define GD_MINIMUM_SPRAYCANSV2 (2) void srb2::save_ng_gamedata() { @@ -146,6 +148,18 @@ void srb2::save_ng_gamedata() ng.skins[name] = std::move(skin); } + for (int i = 0; i < gamedata->numspraycans; i++) + { + uint16_t col = gamedata->spraycans[i].col; + + if (col >= SKINCOLOR_FIRSTFREESLOT) + { + col = SKINCOLOR_NONE; + } + + ng.spraycans_v2.emplace_back(String(skincolors[col].name)); + } + auto maptojson = [](recorddata_t *records) { srb2::GamedataMapJson map {}; @@ -167,6 +181,7 @@ void srb2::save_ng_gamedata() map.stats.time.custom = records->modetimeplayed[GDGT_CUSTOM]; map.stats.time.timeattack = records->timeattacktimeplayed; map.stats.time.spbattack = records->spbattacktimeplayed; + map.spraycan = records->spraycan; return map; }; @@ -183,38 +198,6 @@ void srb2::save_ng_gamedata() srb2::String lumpname { unloadedmap->lumpname }; ng.maps[lumpname] = std::move(map); } - for (int i = 0; i < gamedata->numspraycans; i++) - { - srb2::GamedataSprayCanJson spraycan {}; - - candata_t* can = &gamedata->spraycans[i]; - - if (can->col >= numskincolors) - { - continue; - } - spraycan.color = String(skincolors[can->col].name); - - if (can->map == NEXTMAP_INVALID) - { - spraycan.map = ""; - ng.spraycans.emplace_back(std::move(spraycan)); - continue; - } - - if (can->map >= nummapheaders) - { - continue; - } - - mapheader_t* mapheader = mapheaderinfo[can->map]; - if (!mapheader) - { - continue; - } - spraycan.map = String(mapheader->lumpname); - ng.spraycans.emplace_back(std::move(spraycan)); - } auto cuptojson = [](cupwindata_t *windata) { @@ -401,6 +384,7 @@ void srb2::load_ng_gamedata() uint32_t majorversion; uint8_t minorversion; uint8_t dirty; + bool converted = false; try { majorversion = srb2::io::read_uint32(bis); @@ -519,23 +503,11 @@ void srb2::load_ng_gamedata() gamedata->achieved[i] = js.conditionsets[i]; } - if (M_CheckParm("-resetchallengegrid")) - { - gamedata->challengegridwidth = 0; - if (gamedata->challengegrid) - { - Z_Free(gamedata->challengegrid); - gamedata->challengegrid = nullptr; - } - } - else +#ifdef DEVELOP + if (!M_CheckParm("-resetchallengegrid")) +#endif { gamedata->challengegridwidth = std::max(js.challengegrid.width, (uint32_t)0); - if (gamedata->challengegrid) - { - Z_Free(gamedata->challengegrid); - gamedata->challengegrid = nullptr; - } if (gamedata->challengegridwidth) { gamedata->challengegrid = static_cast(Z_Malloc( @@ -549,10 +521,6 @@ void srb2::load_ng_gamedata() M_SanitiseChallengeGrid(); } - else - { - gamedata->challengegrid = nullptr; - } } gamedata->timesBeaten = js.timesBeaten; @@ -609,9 +577,37 @@ void srb2::load_ng_gamedata() } } + std::vector tempcans; + +#ifdef DEVELOP + if (M_CheckParm("-resetspraycans")) + ; + else +#endif + for (auto& cancolor : js.spraycans_v2) + { + // Version 2 behaviour - spraycans_v2, not spraycans! + + candata_t tempcan; + tempcan.col = SKINCOLOR_NONE; + tempcan.map = NEXTMAP_INVALID; + + // Find the skin color index for the name + for (size_t i = 0; i < SKINCOLOR_FIRSTFREESLOT; i++) + { + if (cancolor != skincolors[i].name) + continue; + + tempcan.col = i; + break; + } + + tempcans.emplace_back(std::move(tempcan)); + } + for (auto& mappair : js.maps) { - UINT16 mapnum = G_MapNumber(mappair.first.c_str()); + uint16_t mapnum = G_MapNumber(mappair.first.c_str()); recorddata_t dummyrecord {}; dummyrecord.mapvisited |= mappair.second.visited.visited ? MV_VISITED : 0; dummyrecord.mapvisited |= mappair.second.visited.beaten ? MV_BEATEN : 0; @@ -632,15 +628,37 @@ void srb2::load_ng_gamedata() dummyrecord.timeattacktimeplayed = mappair.second.stats.time.timeattack; dummyrecord.spbattacktimeplayed = mappair.second.stats.time.spbattack; + dummyrecord.spraycan = (minorversion >= GD_MINIMUM_SPRAYCANSV2) + ? mappair.second.spraycan + : MCAN_INVALID; + if (mapnum < nummapheaders && mapheaderinfo[mapnum]) { // Valid mapheader, time to populate with record data. + // Infill Spray Can info + if ( + dummyrecord.spraycan < tempcans.size() + && (mapnum < basenummapheaders) + && (tempcans[dummyrecord.spraycan].map >= basenummapheaders) + ) + { + // Assign map ID. + tempcans[dummyrecord.spraycan].map = mapnum; + } + + if (dummyrecord.spraycan != MCAN_INVALID) + { + // Yes, even if it's valid. We reassign later. + dummyrecord.spraycan = MCAN_BONUS; + } + mapheaderinfo[mapnum]->records = dummyrecord; } else if (dummyrecord.mapvisited & MV_BEATEN || dummyrecord.timeattack.time != 0 || dummyrecord.timeattack.lap != 0 - || dummyrecord.spbattack.time != 0 || dummyrecord.spbattack.lap != 0) + || dummyrecord.spbattack.time != 0 || dummyrecord.spbattack.lap != 0 + || dummyrecord.spraycan != MCAN_INVALID) { // Invalid, but we don't want to lose all the juicy statistics. // Instead, update a FILO linked list of "unloaded mapheaders". @@ -659,81 +677,98 @@ void srb2::load_ng_gamedata() unloadedmap->next = unloadedmapheaders; unloadedmapheaders = unloadedmap; + if (dummyrecord.spraycan != MCAN_INVALID) + { + // Invalidate non-bonus spraycans. + dummyrecord.spraycan = MCAN_BONUS; + } + // Finally, copy into. unloadedmap->records = dummyrecord; } } - gamedata->gotspraycans = 0; - gamedata->numspraycans = js.spraycans.size(); - if (gamedata->spraycans) + if ((minorversion < GD_MINIMUM_SPRAYCANSV2) && (js.spraycans.size() > 1)) { - Z_Free(gamedata->spraycans); - } - if (gamedata->numspraycans) - { - gamedata->spraycans = static_cast(Z_Malloc( - (gamedata->numspraycans * sizeof(candata_t)), - PU_STATIC, NULL)); + // Deprecated behaviour! Look above for spraycans_v2 handling - for (size_t i = 0; i < gamedata->numspraycans; i++) + converted = true; + + for (auto& deprecatedcan : js.spraycans) { - auto& can = js.spraycans[i]; + candata_t tempcan; + tempcan.col = SKINCOLOR_NONE; // Find the skin color index for the name - bool foundcolor = false; - for (size_t j = 0; j < numskincolors; j++) + for (size_t i = 0; i < SKINCOLOR_FIRSTFREESLOT; i++) { - if (can.color == skincolors[j].name) - { - gamedata->spraycans[i].col = j; - skincolors[j].cache_spraycan = i; - foundcolor = true; - break; - } - } - if (!foundcolor) - { - // Invalid color name? Ignore the spraycan - gamedata->numspraycans -= 1; - i -= 1; - continue; - } + if (deprecatedcan.color != skincolors[i].name) + continue; - gamedata->spraycans[i].map = NEXTMAP_INVALID; + tempcan.col = i; + break; + } UINT16 mapnum = NEXTMAP_INVALID; - if (!can.map.empty()) + if (!deprecatedcan.map.empty()) { - mapnum = G_MapNumber(can.map.c_str()); - } - gamedata->spraycans[i].map = mapnum; - if (mapnum >= nummapheaders) - { - // Can has not been grabbed on any map, this is intentional. - continue; + mapnum = G_MapNumber(deprecatedcan.map.c_str()); } + tempcan.map = mapnum; - if (gamedata->gotspraycans != i) - { - //CONS_Printf("LOAD - Swapping gotten can %u, color %s with prior ungotten can %u\n", i, skincolors[col].name, gamedata->gotspraycans); - - // All grabbed cans should be at the head of the list. - // Let's swap with the can the disjoint occoured at. - // This will prevent a gap from occouring on reload. - candata_t copycan = gamedata->spraycans[gamedata->gotspraycans]; - gamedata->spraycans[gamedata->gotspraycans] = gamedata->spraycans[i]; - gamedata->spraycans[i] = copycan; - - mapheaderinfo[copycan.map]->cache_spraycan = i; - } - mapheaderinfo[mapnum]->cache_spraycan = gamedata->gotspraycans; - gamedata->gotspraycans++; + tempcans.emplace_back(std::move(tempcan)); } } - else + { - gamedata->spraycans = nullptr; + // Post-process of Spray Cans - a component of both v1 and v2 spraycans behaviour + + // Determine sizes. + for (auto& tempcan : tempcans) + { + if (tempcan.col == SKINCOLOR_NONE) + continue; + + gamedata->numspraycans++; + + if (tempcan.map >= nummapheaders) + continue; + + gamedata->gotspraycans++; + } + + if (gamedata->numspraycans) + { + // Arrange with collected first + std::stable_sort(tempcans.begin(), tempcans.end(), [ ]( auto& lhs, auto& rhs ) + { + return (rhs.map >= basenummapheaders && lhs.map < basenummapheaders); + }); + + gamedata->spraycans = static_cast(Z_Malloc( + (gamedata->numspraycans * sizeof(candata_t)), + PU_STATIC, NULL)); + + // Finally, fill can data. + size_t i = 0; + for (auto& tempcan : tempcans) + { + if (tempcan.col == SKINCOLOR_NONE) + continue; + + skincolors[tempcan.col].cache_spraycan = i; + + if (tempcan.map < basenummapheaders) + mapheaderinfo[tempcan.map]->records.spraycan = i; + + gamedata->spraycans[i] = tempcan; + + if (++i < gamedata->numspraycans) + continue; + + break; + } + } } for (auto& cuppair : js.cups) @@ -838,7 +873,6 @@ void srb2::load_ng_gamedata() } } - bool converted = false; UINT32 chao_key_rounds = GDCONVERT_ROUNDSTOKEY; UINT32 start_keys = GDINIT_CHAOKEYS; diff --git a/src/g_gamedata.h b/src/g_gamedata.h index bde624c4b..0027ef582 100644 --- a/src/g_gamedata.h +++ b/src/g_gamedata.h @@ -243,10 +243,12 @@ struct GamedataMapJson final { GamedataMapVisitedJson visited; GamedataMapStatsJson stats; + uint16_t spraycan; - SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataMapJson, visited, stats) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataMapJson, visited, stats, spraycan) }; +// Deprecated struct GamedataSprayCanJson final { String map; @@ -296,8 +298,9 @@ struct GamedataJson final GamedataChallengeGridJson challengegrid; uint32_t timesBeaten; HashMap skins; + Vector spraycans_v2; HashMap maps; - Vector spraycans; + Vector spraycans; // Deprecated HashMap cups; Vector sealedswaps; @@ -317,6 +320,7 @@ struct GamedataJson final challengegrid, timesBeaten, skins, + spraycans_v2, maps, spraycans, cups, diff --git a/src/info.c b/src/info.c index 01efdaa1e..15ea02dd9 100644 --- a/src/info.c +++ b/src/info.c @@ -50,6 +50,7 @@ char sprnames[NUMSPRITES + 1][5] = "BSPH", // Sphere "EMBM", "SPCN", // Spray Can + "SBON", // Spray Can replacement bonus "MMSH", // Ancient Shrine "MORB", // One Morbillion "EMRC", // Chaos Emeralds diff --git a/src/info.h b/src/info.h index 19061c438..2519259e8 100644 --- a/src/info.h +++ b/src/info.h @@ -589,6 +589,7 @@ typedef enum sprite SPR_BSPH, // Sphere SPR_EMBM, SPR_SPCN, // Spray Can + SPR_SBON, // Spray Can replacement bonus SPR_MMSH, // Ancient Shrine SPR_MORB, // One Morbillion SPR_EMRC, // Chaos Emeralds diff --git a/src/k_menu.h b/src/k_menu.h index 21cef5fa5..c9564c484 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -1486,6 +1486,7 @@ extern struct statisticsmenu_s { INT32 gotmedals; INT32 nummedals; INT32 numextramedals; + INT32 numcanbonus; UINT32 statgridplayed[9][9]; INT32 maxscroll; UINT16 *maplist; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 7000bd6a6..61853ece5 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -3315,7 +3315,7 @@ void M_DrawCupSelect(void) incj = false; work_array[j].medal = NULL; - work_array[j].col = work_array[j].dotcol = UINT16_MAX; + work_array[j].col = work_array[j].dotcol = MCAN_INVALID; if (templevelsearch.timeattack) { @@ -3354,13 +3354,17 @@ void M_DrawCupSelect(void) emblem = M_GetLevelEmblems(-1); } } - else if ((gamedata->gotspraycans > 0) && (mapheaderinfo[map]->typeoflevel & TOL_RACE)) + else if (mapheaderinfo[map]->typeoflevel & TOL_RACE) { incj = true; - if (mapheaderinfo[map]->cache_spraycan < gamedata->numspraycans) + if (mapheaderinfo[map]->records.spraycan == MCAN_BONUS) { - work_array[j].col = gamedata->spraycans[mapheaderinfo[map]->cache_spraycan].col; + work_array[j].col = MCAN_BONUS; + } + else if (mapheaderinfo[map]->records.spraycan < gamedata->numspraycans) + { + work_array[j].col = gamedata->spraycans[mapheaderinfo[map]->records.spraycan].col; } if (mapheaderinfo[map]->records.mapvisited & MV_MYSTICMELODY) @@ -3409,7 +3413,13 @@ void M_DrawCupSelect(void) } else { - if (work_array[i].col < numskincolors) + if (work_array[i].col == MCAN_BONUS) + { + // Bonus in place of Spray Can + + V_DrawScaledPatch(x, y, 0, W_CachePatchName("GOTBON", PU_CACHE)); + } + else if (work_array[i].col < numskincolors) { // Spray Can @@ -8324,9 +8334,16 @@ static INT32 M_DrawMapMedals(INT32 mapnum, INT32 x, INT32 y, boolean allowtime, if (hasmedals) x -= 4; - if (mapheaderinfo[mapnum]->cache_spraycan < gamedata->numspraycans) + if (mapheaderinfo[mapnum]->records.spraycan == MCAN_BONUS) { - UINT16 col = gamedata->spraycans[mapheaderinfo[mapnum]->cache_spraycan].col; + if (draw) + V_DrawScaledPatch(x, y, 0, W_CachePatchName("GOTBON", PU_CACHE)); + + x -= 8; + } + else if (mapheaderinfo[mapnum]->records.spraycan < gamedata->numspraycans) + { + UINT16 col = gamedata->spraycans[mapheaderinfo[mapnum]->records.spraycan].col; if (draw && col < numskincolors) { @@ -8374,11 +8391,18 @@ static void M_DrawStatsMaps(void) if (gamedata->numspraycans) { medalspos = 30 + V_ThinStringWidth(medalcountstr, 0); - medalcountstr = va("x %d/%d", gamedata->gotspraycans, gamedata->numspraycans); + medalcountstr = va("x %d/%d", gamedata->gotspraycans + statisticsmenu.numcanbonus, gamedata->numspraycans); V_DrawThinString(20 + medalspos, 60, 0, medalcountstr); V_DrawMappedPatch(10 + medalspos, 60, 0, W_CachePatchName("GOTCAN", PU_CACHE), R_GetTranslationColormap(TC_DEFAULT, gamedata->spraycans[0].col, GTC_MENUCACHE)); } + else if (statisticsmenu.numcanbonus) + { + medalspos = 30 + V_ThinStringWidth(medalcountstr, 0); + medalcountstr = va("x %d", statisticsmenu.numcanbonus); + V_DrawThinString(20 + medalspos, 60, 0, medalcountstr); + V_DrawScaledPatch(10 + medalspos, 60, 0, W_CachePatchName("GOTBON", PU_CACHE)); + } medalspos = BASEVIDWIDTH - 20; diff --git a/src/m_cond.c b/src/m_cond.c index 5b3d37daf..5659205ee 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -743,8 +743,7 @@ void M_ClearSecrets(void) continue; mapheaderinfo[i]->records.mapvisited = 0; - - mapheaderinfo[i]->cache_spraycan = UINT16_MAX; + mapheaderinfo[i]->records.spraycan = MCAN_INVALID; mapheaderinfo[i]->cache_maplock = MAXUNLOCKABLES; @@ -814,6 +813,30 @@ static void M_AssignSpraycans(void) conditionset_t *c; condition_t *cn; + UINT16 bonustocanmap = 0; + + // First, turn outstanding bonuses into existing uncollected Spray Cans. + while (gamedata->gotspraycans < gamedata->numspraycans) + { + while (bonustocanmap < basenummapheaders) + { + if (mapheaderinfo[bonustocanmap]->records.spraycan != MCAN_BONUS) + { + bonustocanmap++; + continue; + } + + break; + } + + if (bonustocanmap == basenummapheaders) + break; + + mapheaderinfo[bonustocanmap]->records.spraycan = gamedata->gotspraycans; + gamedata->spraycans[gamedata->gotspraycans].map = bonustocanmap; + gamedata->gotspraycans++; + } + const UINT16 prependoffset = MAXSKINCOLORS-1; // None of the following accounts for cans being removed, only added... @@ -829,7 +852,7 @@ static void M_AssignSpraycans(void) if (cn->type != UC_SPRAYCAN) continue; - // G_LoadGamedata, G_SaveGameData doesn't support custom skincolors right now. + // This will likely never support custom skincolors. if (cn->requirement >= SKINCOLOR_FIRSTFREESLOT) //numskincolors) continue; @@ -891,7 +914,24 @@ static void M_AssignSpraycans(void) for (i = 0; i < listlen; i++) { - gamedata->spraycans[gamedata->numspraycans].map = NEXTMAP_INVALID; + // Convert bonus pickups into Spray Cans if new ones have been added. + while (bonustocanmap < basenummapheaders) + { + if (mapheaderinfo[bonustocanmap]->records.spraycan != MCAN_BONUS) + { + bonustocanmap++; + continue; + } + + gamedata->gotspraycans++; + mapheaderinfo[bonustocanmap]->records.spraycan = gamedata->numspraycans; + break; + } + gamedata->spraycans[gamedata->numspraycans].map = ( + (bonustocanmap == basenummapheaders) + ? NEXTMAP_INVALID + : bonustocanmap + ); gamedata->spraycans[gamedata->numspraycans].col = tempcanlist[i]; skincolors[tempcanlist[i]].cache_spraycan = gamedata->numspraycans; diff --git a/src/menus/extras-statistics.cpp b/src/menus/extras-statistics.cpp index efea7af67..e270b2db6 100644 --- a/src/menus/extras-statistics.cpp +++ b/src/menus/extras-statistics.cpp @@ -290,10 +290,22 @@ void M_Statistics(INT32 choice) { (void)choice; + UINT16 i; + statisticsmenu.gotmedals = M_CountMedals(false, false); statisticsmenu.nummedals = M_CountMedals(true, false); statisticsmenu.numextramedals = M_CountMedals(true, true); + statisticsmenu.numcanbonus = 0; + for (i = 0; i < basenummapheaders; i++) + { + if (!mapheaderinfo[i]) + continue; + if (mapheaderinfo[i]->records.spraycan != MCAN_BONUS) + continue; + statisticsmenu.numcanbonus++; + } + M_StatisticsPageInit(); MISC_StatisticsDef.prevMenu = currentMenu; diff --git a/src/p_inter.c b/src/p_inter.c index 320aa28e7..6d83c4a8f 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -779,15 +779,27 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) } // See also P_SprayCanInit - UINT16 can_id = mapheaderinfo[gamemap-1]->cache_spraycan; + UINT16 can_id = mapheaderinfo[gamemap-1]->records.spraycan; - if (can_id < gamedata->numspraycans) + if (can_id < gamedata->numspraycans || can_id == MCAN_BONUS) { // Assigned to this level, has been grabbed return; } - // Prevent footguns - these won't persist when custom levels are unloaded - else if (gamemap-1 < basenummapheaders) + + if ( + (gamemap-1 >= basenummapheaders) + || (gamedata->gotspraycans >= gamedata->numspraycans) + ) + { + // Custom course OR we ran out of assignables. + + if (special->threshold != 0) + return; + + can_id = MCAN_BONUS; + } + else { // Unassigned, get the next grabbable colour can_id = gamedata->gotspraycans; @@ -810,18 +822,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) skincolors[swapcol].cache_spraycan = can_id; } - } - - if (can_id >= gamedata->numspraycans) - { - // We've exhausted all the spraycans to grab. - return; - } - - if (gamedata->spraycans[can_id].map >= nummapheaders) - { gamedata->spraycans[can_id].map = gamemap-1; - mapheaderinfo[gamemap-1]->cache_spraycan = can_id; if (gamedata->gotspraycans == 0 && gametype == GT_TUTORIAL @@ -837,12 +838,14 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) } gamedata->gotspraycans++; - - if (!M_UpdateUnlockablesAndExtraEmblems(true, true)) - S_StartSound(NULL, sfx_ncitem); - gamedata->deferredsave = true; } + mapheaderinfo[gamemap-1]->records.spraycan = can_id; + + if (!M_UpdateUnlockablesAndExtraEmblems(true, true)) + S_StartSound(NULL, sfx_ncitem); + gamedata->deferredsave = true; + { mobj_t *canmo = NULL; mobj_t *next = NULL; diff --git a/src/p_mobj.c b/src/p_mobj.c index dc3138bca..baceb1aed 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -12915,9 +12915,9 @@ static boolean P_SetupEmblem(mapthing_t *mthing, mobj_t *mobj) void P_SprayCanInit(mobj_t* mobj) { // See also P_TouchSpecialThing - UINT16 can_id = mapheaderinfo[gamemap-1]->cache_spraycan; + UINT16 can_id = mapheaderinfo[gamemap-1]->records.spraycan; - if (can_id < gamedata->numspraycans) + if (can_id < gamedata->numspraycans || can_id == MCAN_BONUS) { // Assigned to this level, has been grabbed mobj->renderflags = (tr_trans50 << RF_TRANSSHIFT); @@ -12925,19 +12925,38 @@ void P_SprayCanInit(mobj_t* mobj) // Prevent footguns - these won't persist when custom levels are unloaded else if (gamemap-1 < basenummapheaders) { - // Unassigned, get the next grabbable colour (offset by threshold) - can_id = gamedata->gotspraycans; + if (gamedata->gotspraycans >= gamedata->numspraycans) + { + can_id = MCAN_BONUS; + } + else + { + // Unassigned, get the next grabbable colour (offset by threshold) + can_id = gamedata->gotspraycans; - // It's ok if this goes over gamedata->numspraycans, as they're - // capped below in this func... but NEVER let this go backwards!! - if (mobj->threshold != 0) - can_id += (mobj->threshold & UINT8_MAX); + // It's ok if this goes over gamedata->numspraycans, as they're + // capped below in this func... but NEVER let this go backwards!! + if (mobj->threshold != 0) + can_id += (mobj->threshold & UINT8_MAX); + } mobj->renderflags = 0; } - - if (can_id < gamedata->numspraycans) + else { + // Custom course, bonus only + can_id = MCAN_BONUS; + } + + if (can_id == MCAN_BONUS && mobj->threshold == 0) + { + // Only one bonus possible + // We modify sprite instead of state for netsync reasons + mobj->sprite = SPR_SBON; + } + else if (can_id < gamedata->numspraycans) + { + mobj->sprite = mobj->state->sprite; mobj->color = gamedata->spraycans[can_id].col; } else diff --git a/src/p_setup.cpp b/src/p_setup.cpp index fa1240f7e..3ad9d6a16 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -498,14 +498,13 @@ static void P_ClearSingleMapHeaderInfo(INT16 num) #endif memset(&mapheaderinfo[num]->records, 0, sizeof(recorddata_t)); + mapheaderinfo[num]->records.spraycan = MCAN_INVALID; mapheaderinfo[num]->justPlayed = 0; mapheaderinfo[num]->anger = 0; mapheaderinfo[num]->destroyforchallenge_size = 0; - mapheaderinfo[num]->cache_spraycan = UINT16_MAX; - mapheaderinfo[num]->cache_maplock = MAXUNLOCKABLES; mapheaderinfo[num]->customopts = NULL;