Clean up and consolidate all item cooldown code

* The "unique item" flag has moved to kartitem, and split to accomodate
  shields (item slot only, not paper items)
* "BGoneTime" renamed to "CooldownTime"
* indirectitemcooldown has been folded into the existing cooldown system,
  all results with the indirect flag share the highest cooldown time
* Added cooldown debugger to kartdebugdistribution
* Fixed Thunder Shield not resetting cooldown when killing an SPB
This commit is contained in:
GenericHeroGuy 2025-11-09 21:42:47 +01:00
parent ef65f93ada
commit f7c1c474e6
17 changed files with 151 additions and 144 deletions

View file

@ -94,7 +94,7 @@
#define ASSET_HASH_TEXTURES_KART 0xb4211b2f32b6a291
#define ASSET_HASH_CHARS_KART 0x1e68a3e01aa5c68b
#define ASSET_HASH_MAPS_KART 0x38558ed00da41ce9
#define ASSET_HASH_MAIN_PK3 0x5ed08ba7a6c6257f
#define ASSET_HASH_MAIN_PK3 0xfc28316dfbd899a0
#define ASSET_HASH_MAPPATCH_PK3 0xd4d4ce4a090d5473
#define ASSET_HASH_BONUSCHARS_KART 0x60e6f13d822a7461
#ifdef USE_PATCH_FILE

View file

@ -4209,9 +4209,9 @@ void readkartresult(MYFILE *f, kartresult_t *result)
result->unique_odds[oddstable] = get_useoddsfunc(word2);
}
else if (fastcmp(word, "BGONETIME") || fastcmp(word, "BASEBGONE"))
else if (fastcmp(word, "COOLDOWNTIME"))
{
result->basebgone = atoi(word2) * TICRATE;
result->basecooldown = atoi(word2) * TICRATE;
}
else if (fastcmp(word, "DISPLAYNAME"))
{

View file

@ -1560,6 +1560,9 @@ struct int_const_s const INT_CONST[] = {
{"KITEMBLINKMODE_KARMA",KITEMBLINKMODE_KARMA},
// kartitemflags_e
{"KIF_UNIQUESLOT",KIF_UNIQUESLOT},
{"KIF_UNIQUEDROP",KIF_UNIQUEDROP},
{"KIF_UNIQUE",KIF_UNIQUE},
{"KIF_ANIMATED",KIF_ANIMATED},
{"KIF_DARKBG",KIF_DARKBG},
{"KIF_COLPATCH2PLAYER",KIF_COLPATCH2PLAYER},
@ -1571,7 +1574,6 @@ struct int_const_s const INT_CONST[] = {
{"KRF_COOLDOWNONSTART",KRF_COOLDOWNONSTART},
{"KRF_NOTNEAREND",KRF_NOTNEAREND},
{"KRF_NOTFORBOTTOM",KRF_NOTFORBOTTOM},
{"KRF_UNIQUE",KRF_UNIQUE},
{"KRF_RUNNERAUGMENT",KRF_RUNNERAUGMENT},
// kartspinoutflags_t

View file

@ -672,8 +672,6 @@ extern boolean comeback;
extern SINT8 mostwanted;
extern SINT8 battlewanted[4];
extern tic_t wantedcalcdelay;
extern tic_t indirectitemcooldown;
//extern tic_t hyubgone;
extern tic_t mapreset;
extern boolean thwompsactive;
extern UINT8 lastLowestLap;

View file

@ -309,8 +309,6 @@ SINT8 pickedvote; // What vote the host rolls
SINT8 battlewanted[4]; // WANTED players in battle, worth x2 points
SINT8 mostwanted; // The "most wanted" (first in line) player.
tic_t wantedcalcdelay; // Time before it recalculates WANTED
tic_t indirectitemcooldown; // Cooldown before any more Shrink, SPB, or any other item that works indirectly is awarded
// hyubgone was taken out back. See k_items.c
tic_t mapreset; // Map reset delay when enough players have joined an empty game
boolean thwompsactive; // Thwomps activate on lap 2
UINT8 lastLowestLap; // Last lowest lap, for activating race lap executors

View file

@ -515,6 +515,12 @@ static inline BlockItReturn_t PIT_ThunderShieldAttack(mobj_t *thing)
}
#endif
if (thing->type == MT_SPB) // If you destroy a SPB, you don't get the luxury of a cooldown.
{
spbplace = -1;
K_SetIndirectItemCooldown(0);
}
P_DamageMobj(thing, lightningSource, lightningSource, 1, DMG_VOLTAGE|DMG_CANTHURTSELF);
return BMIT_CONTINUE;
}

View file

@ -5686,6 +5686,22 @@ static void K_drawDistributionDebugger(void)
if (K_LegacyOddsMode())
V_DrawSmallString(70, 0, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, "Legacy Distance Mode");
// cooldown timer debugging
x = 240;
y = 160;
for (i = 0; i < numkartresults; i++)
{
kartresult_t *result = &kartresults[i];
if (result->cooldown > 0 && result->isalt == K_IsKartItemAlternate(result->type))
{
INT32 color = result->flags & KRF_INDIRECTITEM ? V_ORANGEMAP : kartitems[result->type].flags & KIF_UNIQUE ? V_YELLOWMAP : 0;
V_DrawScaledPatch(x, y, V_HUDTRANS|V_SNAPTOBOTTOM, K_GetCachedItemPatch(result->type, true, 0));
if (result->amount > 1)
V_DrawThinString(x+30, y+30, V_ALLOWLOWERCASE|V_HUDTRANS|V_SNAPTOBOTTOM|V_6WIDTHSPACE, va("x%d", result->amount));
V_DrawString(x+11, y+31, V_HUDTRANS|V_SNAPTOBOTTOM|color, va("%u", result->cooldown/TICRATE));
x -= 32;
}
}
}
static void K_drawCheckpointDebugger(void)

View file

@ -306,14 +306,8 @@ static void K_AwardPlayerItem(player_t *player, kartresult_t *result)
return;
}
if (result->flags & KRF_INDIRECTITEM) // Indirect items
{
indirectitemcooldown = 20*TICRATE;
}
else if (result->basebgone > 0) // Item cooldowns
{
result->bgone = result->basebgone;
}
if (result->basecooldown > 0)
result->cooldown = result->basecooldown;
player->itemtype = result->type;
UINT8 itemamount = result->amount;
@ -441,39 +435,95 @@ static INT32 K_KartGetInvincibilityOdds(UINT32 dist)
#undef FRAC_3h
// Assigns general cooldowns to item results ith the KRF_UNIQUE flag.
void K_KartHandleUniqueCooldown(void)
// updates all result cooldown timers, and sets cooldowns for "unique" items
void K_UpdateItemCooldown(void)
{
UINT8 i, j;
INT32 i;
UINT8 pingame = 0, pexiting = 0;
// tick down the cooldown timers
for (i = 0; i < numkartresults; i++)
if (kartresults[i].cooldown > 0)
kartresults[i].cooldown--;
// figure out which unique items need their cooldown set
boolean setcooldown[MAXKARTITEMS] = {0};
// start by checking each player's item slot (KIF_UNIQUESLOT)
for (i = 0; i < MAXPLAYERS; i++)
{
player_t *player = &players[i];
if (!playeringame[i] || player->spectator || player->exiting > 0)
continue;
if (gametyperules & GTR_BUMPERS && player->bumper == 0)
continue;
// special case for legacy shrink
if (player->growshrinktimer < 0 && !K_IsKartItemAlternate(KITEM_SHRINK))
setcooldown[KITEM_SHRINK] = true;
if (player->itemtype <= 0 || player->itemtype >= numkartitems || player->itemamount <= 0)
continue;
if (kartitems[player->itemtype].flags & KIF_UNIQUESLOT)
setcooldown[player->itemtype] = true;
}
// next, check for dropped items (KIF_UNIQUEDROP)
for (thinker_t *th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
{
mobj_t *mobj = (mobj_t *)th;
if (P_MobjWasRemoved(mobj))
continue;
switch (mobj->type)
{
case MT_FLOATINGITEM:
if (mobj->threshold > 0 && mobj->threshold < numkartitems && kartitems[mobj->threshold].flags & KIF_UNIQUEDROP)
setcooldown[mobj->threshold] = true;
break;
case MT_SPB: // SPBs in the field also count
setcooldown[KITEM_SPB] = true;
break;
default:
break;
}
}
// now it's FINALLY time to set cooldowns!
// unique item cooldowns only apply to race odds, unless it's an indirect item
for (i = 0; i < numkartresults; i++)
{
if (kartresults[i].flags & KRF_UNIQUE)
{
// TODO: bring this out
for (j = 0; j < MAXPLAYERS; j++)
{
if (!playeringame[j] || players[j].spectator)
continue;
kartresult_t *result = &kartresults[i];
if ((gametyperules & GTR_RACEODDS || result->flags & KRF_INDIRECTITEM) && setcooldown[result->type])
result->cooldown = result->basecooldown;
}
if (!(gametyperules & GTR_BUMPERS) || players[j].bumper)
pingame++;
// all indirect items share a single timer, whichever is higher
K_SetIndirectItemCooldown(K_GetIndirectItemCooldown());
}
if (players[j].exiting)
pexiting++;
tic_t K_GetIndirectItemCooldown(void)
{
tic_t maxcooldown = 0;
for (UINT8 i = 0; i < numkartresults; i++)
{
if (kartresults[i].flags & KRF_INDIRECTITEM)
maxcooldown = max(maxcooldown, kartresults[i].cooldown);
}
return maxcooldown;
}
if (players[j].itemtype == kartresults[i].type &&
players[j].itemamount > 0 && kartresults[i].basebgone > 0)
{
// If this item has a cooldown, force-apply the cooldown
// preeemptively for the entire time the item is being held
// by a player.
kartresults[i].bgone = kartresults[i].basebgone;
}
}
}
void K_SetIndirectItemCooldown(tic_t cooldown)
{
for (UINT8 i = 0; i < numkartresults; i++)
{
if (kartresults[i].flags & KRF_INDIRECTITEM)
kartresults[i].cooldown = cooldown;
}
}
@ -624,8 +674,6 @@ static INT32 GetItemOdds(kartroulette_t *roulette, kartresult_t *result, UINT8 *
case KITEM_SHRINK:
if (!K_IsKartItemAlternate(KITEM_SHRINK))
{
flags |= KRF_INDIRECTITEM;
if (roulette->pingame-1 <= roulette->pexiting)
newodds = 0;
}
@ -676,14 +724,9 @@ static INT32 GetItemOdds(kartroulette_t *roulette, kartresult_t *result, UINT8 *
newodds *= multiplier;
}
if (result->bgone > 0)
if (result->cooldown > 0)
{
// (Replaces hyubgone) This item is on cooldown; don't let it get rolled.
newodds = 0;
}
else if (flags & KRF_INDIRECTITEM && indirectitemcooldown > 0)
{
// Too many items that act indirectly in a match can feel kind of bad.
// This item is on cooldown; don't let it get rolled.
newodds = 0;
}
else if (flags & KRF_COOLDOWNONSTART && K_InStartCooldown())
@ -701,28 +744,6 @@ static INT32 GetItemOdds(kartroulette_t *roulette, kartresult_t *result, UINT8 *
// This item should not appear for losing players. (Usually items that feel less effective at these positions)
newodds = 0;
}
else if (flags & KRF_UNIQUE)
{
// TODO: bring this out
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator)
continue;
if (players[i].exiting)
continue;
if ((gametyperules & GTR_BUMPERS) && !players[i].bumper)
continue;
if (players[i].itemtype == result->type && players[i].itemamount > 0)
{
// Can only appear in one player's item slot, and can't be rolled if already held by a player
newodds = 0;
break;
}
}
}
else if (flags & KRF_POWERITEM)
{
// This item is a "power item". This activates "frantic item" toggle related functionality.
@ -770,11 +791,9 @@ void K_KartGetItemOdds(kartroulette_t *roulette, INT32 outodds[static MAXKARTRES
UINT8 K_FindUseodds(const player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbumper, boolean spbrush)
{
fixed_t oddsfac = max(FRACUNIT, (MAXODDS * FRACUNIT) / 8);
INT32 oddsdiv = ((MAXODDS - 1) * 2);
UINT8 i, j;
UINT8 useodds = 0;
UINT8 disttable[oddsdiv];
UINT8 disttable[(MAXODDS - 1) * 2];
UINT8 distlen = 0;
boolean oddsvalid[MAXODDS];
boolean rivalodds = false;
@ -832,7 +851,7 @@ UINT8 K_FindUseodds(const player_t *player, fixed_t mashed, UINT32 pdis, UINT8 b
for (i = num; i; --i) \
{ \
disttable[distlen++] = odds; \
distlen = min(oddsdiv - 1, distlen); \
distlen = min(sizeof(disttable) - 1, distlen); \
}
if (gametyperules & GTR_BATTLEODDS) // Battle Mode
@ -896,13 +915,13 @@ UINT8 K_FindUseodds(const player_t *player, fixed_t mashed, UINT32 pdis, UINT8 b
if (pdis == 0)
useodds = disttable[0];
else if (pdis > (UINT32)ACTIVEDISTVAR * ((12 * distlen) / oddsdiv))
useodds = disttable[distlen-1];
else if (pdis > (UINT32)ACTIVEDISTVAR * ((12 * distlen) / sizeof(disttable)))
useodds = disttable[max(0, distlen-1)];
else
{
for (i = 1; i < (oddsdiv - 1); i++)
for (i = 1; i < (sizeof(disttable) - 1); i++)
{
INT32 distcalc = min(distlen-1, (i * distlen) / oddsdiv);
INT32 distcalc = min(distlen-1, (i * distlen) / sizeof(disttable));
if (pdis <= (UINT32)usedistvar * distcalc)
{
@ -1366,7 +1385,7 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd)
// SPECIAL CASE No. 6:
// In battle, an SPB is forced onto players to target the "most wanted" player
if (K_BattleForceSPB(player)
&& spbplace == -1 && !indirectitemcooldown && !dontforcespb
&& spbplace == -1 && K_GetKartResult("spb")->cooldown == 0 && !dontforcespb
&& K_ItemResultEnabled(K_GetKartResult("spb")))
{
K_AwardPlayerItem(player, K_GetKartResult("spb"));
@ -1500,7 +1519,7 @@ INT32 KO_SPBRaceOdds(INT32 odds, const kartroulette_t *roulette, const kartresul
*forceme = 0;
}
else if (K_RaceForceSPB(roulette->playerpos, roulette->pdis)
&& spbplace == -1 && !indirectitemcooldown)
&& spbplace == -1 && K_GetKartResult("spb")->cooldown == 0)
{
// Force SPB onto 2nd if they get too far behind.
*forceme = 2;

View file

@ -52,26 +52,28 @@ typedef enum
MAXODDSTABLES
} ATTRPACK kartoddstable_e;
// item flags for HUD and usage
// item flags for usage and HUD
typedef enum
{
KIF_ANIMATED = 1<<0, // animate patches instead of using diffrent patches for different amounts (inversion of xItemLib's XIF_ICONFORAMT)
KIF_DARKBG = 1<<1, // use dark item roulette BG
KIF_COLPATCH2PLAYER = 1<<2, // colourize patch to player
KIF_HIDEFROMROULETTE = 1<<3, // don't show this item in the roulette (inversion of xItem's showInRoulette item flag)
KIF_UNIQUESLOT = 1<<0, // Prevents the item from being rolled if in a player's item slot
KIF_UNIQUEDROP = 1<<1, // Prevents the item from being rolled if it exists as a dropped item
KIF_UNIQUE = KIF_UNIQUESLOT|KIF_UNIQUEDROP,
KIF_ANIMATED = 1<<2, // animate patches instead of using diffrent patches for different amounts (inversion of xItemLib's XIF_ICONFORAMT)
KIF_DARKBG = 1<<3, // use dark item roulette BG
KIF_COLPATCH2PLAYER = 1<<4, // colourize patch to player
KIF_HIDEFROMROULETTE = 1<<5, // don't show this item in the roulette (inversion of xItem's showInRoulette item flag)
} ATTRPACK kartitemflags_e;
// flags relevant to item rolls and usage
// result flags relevant to the roulette
typedef enum
{
KRF_INDIRECTITEM = 1<<0, // This item affects others indirectly. A 20 second cooldown is applied to all other items with this flag when an indirect item is rolled.
KRF_POWERITEM = 1<<1, // This item is a "power item". This activates "frantic item" toggle related functionality.
KRF_COOLDOWNONSTART = 1<<2, // This item should not appear at the beginning of a race. (Usually really powerful crowd-breaking items)
KRF_NOTNEAREND = 1<<3, // This item should not appear at the end of a race. (Usually trap items that lose their effectiveness)
KRF_NOTFORBOTTOM = 1<<4, // After the first 30 seconds, this item stops appearing for losing players. (Usually items that feel less effective at these positions)
KRF_UNIQUE = 1<<5, // If already in a player's item slot, prevents all other players from obtaining this item from item boxes
KRF_RUNNERAUGMENT = 1<<6, // This item's odds are affected by the severity of 1st place's frontrun. (Very likely only for SPBs, but who knows?)
// free: 1<<7
KRF_INDIRECTITEM = 1<<0, // This result's item affects others indirectly. All results with this flag share a single cooldown timer.
KRF_POWERITEM = 1<<1, // This result is a "power item". This activates "frantic item" toggle related functionality.
KRF_COOLDOWNONSTART = 1<<2, // This result should not appear at the beginning of a race. (Usually really powerful crowd-breaking items)
KRF_NOTNEAREND = 1<<3, // This result should not appear at the end of a race. (Usually trap items that lose their effectiveness)
KRF_NOTFORBOTTOM = 1<<4, // After the first 30 seconds, this result stops appearing for losing players. (Usually items that feel less effective at these positions)
KRF_RUNNERAUGMENT = 1<<5, // This result's odds are affected by the severity of 1st place's frontrun. (Very likely only for SPBs, but who knows?)
} ATTRPACK kartresultflags_e;
// Unique useodds function
@ -117,7 +119,7 @@ struct kartresult_t
// If NULL, the functions never execute.
useoddsfunc_f *unique_odds[MAXODDSTABLES];
tic_t basebgone, bgone;
tic_t basecooldown, cooldown;
};
// contains all data for a call to K_KartGetItemOdds
@ -159,7 +161,9 @@ boolean K_IsKartItemAlternate(kartitemtype_e itemtype);
UINT8 K_GetItemNumberDisplayMin(kartitemtype_e type, boolean tiny);
void K_UpdateMobjItemOverlay(mobj_t *part, kartitemtype_e itemType, UINT8 itemCount);
void K_KartHandleUniqueCooldown(void);
void K_UpdateItemCooldown(void);
tic_t K_GetIndirectItemCooldown(void);
void K_SetIndirectItemCooldown(tic_t cooldown);
boolean K_LegacyOddsMode(void);
UINT32 K_GetCongaLineDistance(const player_t *player, UINT8 startPos);

View file

@ -89,7 +89,6 @@ consvar_t cv_karthitemdialog = CVAR_INIT ("karthitemdialog", "On", CV_SAVE, CV_O
// encoremode is Encore Mode (duh), bool
// comeback is Battle Mode's karma comeback, also bool
// battlewanted is an array of the WANTED player nums, -1 for no player in that slot
// indirectitemcooldown is timer before anyone's allowed another Shrink/SPB
// mapreset is set when enough players fill an empty server
// Cluster point.
@ -10223,11 +10222,6 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
if (player->growshrinktimer <= 0)
player->growcancel = -1;
if (player->itemtype == KITEM_SPB
|| player->itemtype == KITEM_SHRINK
|| (player->growshrinktimer < 0 && !K_IsKartItemAlternate(KITEM_SHRINK)))
indirectitemcooldown = 20*TICRATE;
if (player->hyudorotimer > 0)
{
INT32 hyu = hyudorotime;

View file

@ -4136,7 +4136,7 @@ static int lib_kSetIndirectItemCountdown(lua_State *L)
{
tic_t c = (tic_t)luaL_checkinteger(L, 1);
NOHUD
indirectitemcooldown = c;
K_SetIndirectItemCooldown(c);
return 0;
}
@ -4145,7 +4145,7 @@ static int lib_kSetHyuCountdown(lua_State *L)
{
tic_t c = (tic_t)luaL_checkinteger(L, 1);
NOHUD
K_GetKartResult("hyudoro")->bgone = c;
K_GetKartResult("hyudoro")->cooldown = c;
return 0;
}

View file

@ -418,7 +418,7 @@ int LUA_PushGlobals(lua_State *L, const char *word)
lua_pushboolean(L, itempushing); // hmm... i think this should be a boolean
return 1;
} else if (fastcmp(word,"hyubgone")) {
lua_pushinteger(L, K_GetKartResult("hyudoro")->bgone);
lua_pushinteger(L, K_GetKartResult("hyudoro")->cooldown);
return 1;
} else if (fastcmp(word,"encoremode")) {
lua_pushboolean(L, encoremode);
@ -433,7 +433,7 @@ int LUA_PushGlobals(lua_State *L, const char *word)
lua_pushinteger(L, wantedcalcdelay);
return 1;
} else if (fastcmp(word,"indirectitemcooldown")) {
lua_pushinteger(L, indirectitemcooldown);
lua_pushinteger(L, K_GetIndirectItemCooldown());
return 1;
} else if (fastcmp(word,"thwompsactive")) {
lua_pushboolean(L, thwompsactive);
@ -559,9 +559,9 @@ int LUA_WriteGlobals(lua_State *L, const char *word)
else if (fastcmp(word,"exitcountdown"))
exitcountdown = (tic_t)luaL_checkinteger(L, 2);
else if (fastcmp(word,"indirectitemcooldown"))
indirectitemcooldown = (tic_t)luaL_checkinteger(L, 2);
K_SetIndirectItemCooldown(luaL_checkinteger(L, 2));
else if (fastcmp(word,"hyubgone"))
K_GetKartResult("hyudoro")->bgone = (tic_t)luaL_checkinteger(L, 2);
K_GetKartResult("hyudoro")->cooldown = (tic_t)luaL_checkinteger(L, 2);
else if (fastcmp(word,"starttime"))
starttime = (tic_t)luaL_checkinteger(L, 2);
else if (fastcmp(word,"introtime"))

View file

@ -8410,11 +8410,6 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
}
}
if (mobj->threshold == KITEM_SPB || mobj->threshold == KITEM_SHRINK)
{
indirectitemcooldown = 20*TICRATE;
}
K_UpdateMobjItemOverlay(mobj, mobj->threshold, mobj->movecount);
break;
}
@ -8683,8 +8678,6 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
mobj->threshold--;
break;
case MT_SPB:
indirectitemcooldown = 20*TICRATE;
/* FALLTHRU */
case MT_BALLHOG:
{
mobj_t *ghost = P_SpawnGhostMobj(mobj);

View file

@ -4266,14 +4266,9 @@ static boolean P_NetSyncMisc(savebuffer_t *save, boolean resending)
SYNC(battlewanted[i]);
SYNC(wantedcalcdelay);
SYNC(indirectitemcooldown);
for (i = 0; i < numkartresults; i++)
{
// hyubgone
//SYNC(kartresults[i].basebgone);
SYNC(kartresults[i].bgone);
}
SYNC(kartresults[i].cooldown);
SYNC(mapreset);

View file

@ -8400,13 +8400,9 @@ static void P_InitGametype(void)
numlaps = K_RaceLapCount(gamemap - 1);
wantedcalcdelay = wantedfrequency*2;
indirectitemcooldown = 0;
for (i = 0; i < numkartresults; i++)
{
// hyubgone
kartresults[i].bgone = 0;
}
kartresults[i].cooldown = 0;
mapreset = 0;

View file

@ -897,21 +897,7 @@ void P_Ticker(boolean run)
if (exitcountdown > 1)
exitcountdown--;
if (indirectitemcooldown > 0)
indirectitemcooldown--;
for (i = 0; i < numkartresults; i++)
{
// hyubgone
if (kartresults[i].bgone > 0)
kartresults[i].bgone--;
}
if (gametyperules & GTR_RACEODDS)
{
// Unique item cooldowns
K_KartHandleUniqueCooldown();
}
K_UpdateItemCooldown();
K_BossInfoTicker();

View file

@ -2495,7 +2495,7 @@ void P_NukeEnemies(mobj_t *inflictor, mobj_t *source, fixed_t radius)
if (mo->type == MT_SPB) // If you destroy a SPB, you don't get the luxury of a cooldown.
{
spbplace = -1;
indirectitemcooldown = 0;
K_SetIndirectItemCooldown(0);
}
if (mo->flags & MF_BOSS) //don't OHKO bosses nor players!