Merge branch 'the-hunt-continues' into 'master'

Add bonus pickups in conditions you couldn't grab Spray Cans before

See merge request kart-krew-dev/ring-racers-internal!2502
This commit is contained in:
Oni VelocitOni 2025-05-20 22:45:12 +00:00
commit a35bbaacf7
13 changed files with 300 additions and 158 deletions

View file

@ -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;

View file

@ -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

View file

@ -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<uint16_t*>(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<candata_t> 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<candata_t*>(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<candata_t*>(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;

View file

@ -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<String, GamedataSkinJson> skins;
Vector<String> spraycans_v2;
HashMap<String, GamedataMapJson> maps;
Vector<GamedataSprayCanJson> spraycans;
Vector<GamedataSprayCanJson> spraycans; // Deprecated
HashMap<String, GamedataCupJson> cups;
Vector<GamedataSealedSwapJson> sealedswaps;
@ -317,6 +320,7 @@ struct GamedataJson final
challengegrid,
timesBeaten,
skins,
spraycans_v2,
maps,
spraycans,
cups,

View file

@ -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

View file

@ -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

View file

@ -1486,6 +1486,7 @@ extern struct statisticsmenu_s {
INT32 gotmedals;
INT32 nummedals;
INT32 numextramedals;
INT32 numcanbonus;
UINT32 statgridplayed[9][9];
INT32 maxscroll;
UINT16 *maplist;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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

View file

@ -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;