Merge branch 'master' into jartha/fix-stone-relink

# Conflicts:
#	src/p_mobj.c
This commit is contained in:
VelocitOni 2025-07-14 22:05:30 -04:00
commit f7705e6475
19 changed files with 955 additions and 153 deletions

View file

@ -687,6 +687,7 @@ consvar_t cv_items[] = {
UnsavedNetVar("gardentop", "On").on_off(), UnsavedNetVar("gardentop", "On").on_off(),
UnsavedNetVar("gachabom", "On").on_off(), UnsavedNetVar("gachabom", "On").on_off(),
UnsavedNetVar("stoneshoe", "On").on_off(), UnsavedNetVar("stoneshoe", "On").on_off(),
UnsavedNetVar("toxomister", "On").on_off(),
UnsavedNetVar("dualsneaker", "On").on_off(), UnsavedNetVar("dualsneaker", "On").on_off(),
UnsavedNetVar("triplesneaker", "On").on_off(), UnsavedNetVar("triplesneaker", "On").on_off(),
UnsavedNetVar("triplebanana", "On").on_off(), UnsavedNetVar("triplebanana", "On").on_off(),

View file

@ -199,7 +199,8 @@ Run this macro, then #undef FOREACH afterward
FOREACH (DROPTARGET, 21),\ FOREACH (DROPTARGET, 21),\
FOREACH (GARDENTOP, 22),\ FOREACH (GARDENTOP, 22),\
FOREACH (GACHABOM, 23),\ FOREACH (GACHABOM, 23),\
FOREACH (STONESHOE, 24) FOREACH (STONESHOE, 24),\
FOREACH (TOXOMISTER, 25)
typedef enum typedef enum
{ {
@ -1066,6 +1067,7 @@ struct player_t
mobj_t *hand; mobj_t *hand;
mobj_t *flickyAttacker; mobj_t *flickyAttacker;
mobj_t *stoneShoe; mobj_t *stoneShoe;
mobj_t *toxomisterCloud;
SINT8 pitblame; // Index of last player that hit you, resets after being in control for a bit. If you deathpit, credit the old attacker! SINT8 pitblame; // Index of last player that hit you, resets after being in control for a bit. If you deathpit, credit the old attacker!

View file

@ -3120,6 +3120,11 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi
"S_FLYBOT767", "S_FLYBOT767",
"S_STON", "S_STON",
"S_TOXAA",
"S_TOXAA_DEAD",
"S_TOXAB",
"S_TOXBA",
}; };
// RegEx to generate this from info.h: ^\tMT_([^,]+), --> \t"MT_\1", // RegEx to generate this from info.h: ^\tMT_([^,]+), --> \t"MT_\1",
@ -4028,6 +4033,10 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t
"MT_STONESHOE", "MT_STONESHOE",
"MT_STONESHOE_CHAIN", "MT_STONESHOE_CHAIN",
"MT_TOXOMISTER_POLE",
"MT_TOXOMISTER_EYE",
"MT_TOXOMISTER_CLOUD",
}; };
const char *const MOBJFLAG_LIST[] = { const char *const MOBJFLAG_LIST[] = {

View file

@ -801,6 +801,8 @@ char sprnames[NUMSPRITES + 1][5] =
"STUN", "STUN",
"STON", "STON",
"TOXA",
"TOXB",
// Pulley // Pulley
"HCCH", "HCCH",
@ -3704,6 +3706,11 @@ state_t states[NUMSTATES] =
{SPR_STUN, FF_FULLBRIGHT|FF_ANIMATE, -1, {NULL}, 4, 4, S_NULL}, // S_FLYBOT767 {SPR_STUN, FF_FULLBRIGHT|FF_ANIMATE, -1, {NULL}, 4, 4, S_NULL}, // S_FLYBOT767
{SPR_STON, 0, -1, {NULL}, 0, 0, S_STON}, // S_STON {SPR_STON, 0, -1, {NULL}, 0, 0, S_STON}, // S_STON
//
{SPR_TOXA, 0, -1, {NULL}, 0, 0, S_TOXAA}, // S_TOXAA
{SPR_TOXA, 0, 175, {NULL}, 0, 0, S_NULL}, // S_TOXAA_DEAD
{SPR_TOXA, 1, -1, {NULL}, 0, 0, S_TOXAB}, // S_TOXAB
{SPR_TOXB, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 6, 5, S_TOXBA}, // S_TOXBA
}; };
mobjinfo_t mobjinfo[NUMMOBJTYPES] = mobjinfo_t mobjinfo[NUMMOBJTYPES] =
@ -22712,6 +22719,84 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
MF_SPECIAL|MF_SCENERY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_PICKUPFROMBELOW|MF_DONTENCOREMAP, // flags MF_SPECIAL|MF_SCENERY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_PICKUPFROMBELOW|MF_DONTENCOREMAP, // flags
S_NULL // raisestate S_NULL // raisestate
}, },
{ // MT_TOXOMISTER_POLE
-1, // doomednum
S_TOXAA, // spawnstate
1, // spawnhealth
S_NULL, // seestate
sfx_tossed, // seesound
8, // reactiontime
sfx_None, // attacksound
S_NULL, // painstate
0, // painchance
sfx_None, // painsound
S_NULL, // meleestate
S_NULL, // missilestate
S_TOXAA_DEAD, // deathstate
S_NULL, // xdeathstate
sfx_None, // deathsound
0, // speed
32*FRACUNIT, // radius
64*FRACUNIT, // height
0, // display offset
0, // mass
0, // damage
sfx_None, // activesound
MF_SHOOTABLE|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
{ // MT_TOXOMISTER_EYE
-1, // doomednum
S_TOXAB, // spawnstate
1000, // spawnhealth
S_NULL, // seestate
sfx_None, // seesound
8, // reactiontime
sfx_None, // attacksound
S_NULL, // painstate
0, // painchance
sfx_None, // painsound
S_NULL, // meleestate
S_NULL, // missilestate
S_NULL, // deathstate
S_NULL, // xdeathstate
sfx_None, // deathsound
0, // speed
32*FRACUNIT, // radius
64*FRACUNIT, // height
0, // display offset
0, // mass
0, // damage
sfx_None, // activesound
MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPTHING|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
{ // MT_TOXOMISTER_CLOUD
-1, // doomednum
S_TOXBA, // spawnstate
1000, // spawnhealth
S_NULL, // seestate
sfx_None, // seesound
8, // reactiontime
sfx_None, // attacksound
S_NULL, // painstate
0, // painchance
sfx_None, // painsound
S_NULL, // meleestate
S_NULL, // missilestate
S_NULL, // deathstate
S_NULL, // xdeathstate
sfx_None, // deathsound
0, // speed
70*FRACUNIT, // radius
70*FRACUNIT, // height
0, // display offset
0, // mass
0, // damage
sfx_None, // activesound
MF_SPECIAL|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
}; };

View file

@ -1338,6 +1338,8 @@ typedef enum sprite
SPR_STUN, SPR_STUN,
SPR_STON, SPR_STON,
SPR_TOXA,
SPR_TOXB,
// Pulley // Pulley
SPR_HCCH, SPR_HCCH,
@ -4188,6 +4190,11 @@ typedef enum state
S_STON, S_STON,
S_TOXAA,
S_TOXAA_DEAD,
S_TOXAB,
S_TOXBA,
S_FIRSTFREESLOT, S_FIRSTFREESLOT,
S_LASTFREESLOT = S_FIRSTFREESLOT + NUMSTATEFREESLOTS - 1, S_LASTFREESLOT = S_FIRSTFREESLOT + NUMSTATEFREESLOTS - 1,
NUMSTATES NUMSTATES
@ -5119,6 +5126,10 @@ typedef enum mobj_type
MT_STONESHOE, MT_STONESHOE,
MT_STONESHOE_CHAIN, MT_STONESHOE_CHAIN,
MT_TOXOMISTER_POLE,
MT_TOXOMISTER_EYE,
MT_TOXOMISTER_CLOUD,
MT_FIRSTFREESLOT, MT_FIRSTFREESLOT,
MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1, MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1,
NUMMOBJTYPES NUMMOBJTYPES

View file

@ -2160,3 +2160,10 @@ void K_UpdateBotGameplayVars(player_t *player)
K_UpdateBotGameplayVarsItemUsage(player); K_UpdateBotGameplayVarsItemUsage(player);
} }
boolean K_BotUnderstandsItem(kartitems_t item)
{
if (item == KITEM_BALLHOG)
return false; // Sorry. MRs welcome!
return true;
}

View file

@ -401,6 +401,7 @@ void K_BotItemUsage(const player_t *player, ticcmd_t *cmd, INT16 turnamt);
void K_BotPickItemPriority(player_t *player); void K_BotPickItemPriority(player_t *player);
boolean K_BotUnderstandsItem(kartitems_t item);
#ifdef __cplusplus #ifdef __cplusplus
} // extern "C" } // extern "C"

View file

@ -172,6 +172,7 @@ static patch_t *kp_droptarget[3];
static patch_t *kp_gardentop[3]; static patch_t *kp_gardentop[3];
static patch_t *kp_gachabom[3]; static patch_t *kp_gachabom[3];
static patch_t *kp_stoneshoe[3]; static patch_t *kp_stoneshoe[3];
static patch_t *kp_toxomister[3];
static patch_t *kp_bar[2]; static patch_t *kp_bar[2];
static patch_t *kp_doublebar[2]; static patch_t *kp_doublebar[2];
static patch_t *kp_triplebar[2]; static patch_t *kp_triplebar[2];
@ -239,8 +240,11 @@ static patch_t *kp_team_you;
static patch_t *kp_duel_foe; static patch_t *kp_duel_foe;
static patch_t *kp_duel_you; static patch_t *kp_duel_you;
static patch_t *kp_duel_sticker; static patch_t *kp_duel_sticker;
static patch_t *kp_duel_4sticker;
static patch_t *kp_duel_under; static patch_t *kp_duel_under;
static patch_t *kp_duel_4under;
static patch_t *kp_duel_over; static patch_t *kp_duel_over;
static patch_t *kp_duel_4over;
static patch_t *kp_duel_margin[24]; static patch_t *kp_duel_margin[24];
patch_t *kp_autoroulette; patch_t *kp_autoroulette;
@ -649,6 +653,7 @@ void K_LoadKartHUDGraphics(void)
HU_UpdatePatch(&kp_gardentop[0], "K_ITGTOP"); HU_UpdatePatch(&kp_gardentop[0], "K_ITGTOP");
HU_UpdatePatch(&kp_gachabom[0], "K_ITGBOM"); HU_UpdatePatch(&kp_gachabom[0], "K_ITGBOM");
HU_UpdatePatch(&kp_stoneshoe[0], "K_ITSTON"); HU_UpdatePatch(&kp_stoneshoe[0], "K_ITSTON");
HU_UpdatePatch(&kp_toxomister[0], "K_ITTOX");
HU_UpdatePatch(&kp_bar[0], "K_RBBAR"); HU_UpdatePatch(&kp_bar[0], "K_RBBAR");
HU_UpdatePatch(&kp_doublebar[0], "K_RBBAR2"); HU_UpdatePatch(&kp_doublebar[0], "K_RBBAR2");
HU_UpdatePatch(&kp_triplebar[0], "K_RBBAR3"); HU_UpdatePatch(&kp_triplebar[0], "K_RBBAR3");
@ -710,6 +715,7 @@ void K_LoadKartHUDGraphics(void)
HU_UpdatePatch(&kp_gardentop[1], "K_ISGTOP"); HU_UpdatePatch(&kp_gardentop[1], "K_ISGTOP");
HU_UpdatePatch(&kp_gachabom[1], "K_ISGBOM"); HU_UpdatePatch(&kp_gachabom[1], "K_ISGBOM");
HU_UpdatePatch(&kp_stoneshoe[1], "K_ISSTON"); HU_UpdatePatch(&kp_stoneshoe[1], "K_ISSTON");
HU_UpdatePatch(&kp_toxomister[1], "K_ISTOX");
HU_UpdatePatch(&kp_bar[1], "K_SBBAR"); HU_UpdatePatch(&kp_bar[1], "K_SBBAR");
HU_UpdatePatch(&kp_doublebar[1], "K_SBBAR2"); HU_UpdatePatch(&kp_doublebar[1], "K_SBBAR2");
HU_UpdatePatch(&kp_triplebar[1], "K_SBBAR3"); HU_UpdatePatch(&kp_triplebar[1], "K_SBBAR3");
@ -769,6 +775,7 @@ void K_LoadKartHUDGraphics(void)
HU_UpdatePatch(&kp_gardentop[2], "ISPYGTOP"); HU_UpdatePatch(&kp_gardentop[2], "ISPYGTOP");
HU_UpdatePatch(&kp_gachabom[2], "ISPYGBOM"); HU_UpdatePatch(&kp_gachabom[2], "ISPYGBOM");
HU_UpdatePatch(&kp_stoneshoe[2], "ISPYSTON"); HU_UpdatePatch(&kp_stoneshoe[2], "ISPYSTON");
HU_UpdatePatch(&kp_toxomister[2], "ISPYTOX");
// CHECK indicators // CHECK indicators
sprintf(buffer, "K_CHECKx"); sprintf(buffer, "K_CHECKx");
@ -1083,8 +1090,11 @@ void K_LoadKartHUDGraphics(void)
HU_UpdatePatch(&kp_duel_foe, "DUEL_FOE"); HU_UpdatePatch(&kp_duel_foe, "DUEL_FOE");
HU_UpdatePatch(&kp_duel_sticker, "DUEL_S"); HU_UpdatePatch(&kp_duel_sticker, "DUEL_S");
HU_UpdatePatch(&kp_duel_4sticker, "DUEL4_S");
HU_UpdatePatch(&kp_duel_under, "DUEL_B"); HU_UpdatePatch(&kp_duel_under, "DUEL_B");
HU_UpdatePatch(&kp_duel_4under, "DUEL4_B");
HU_UpdatePatch(&kp_duel_over, "DUEL_B2"); HU_UpdatePatch(&kp_duel_over, "DUEL_B2");
HU_UpdatePatch(&kp_duel_4over, "DUEL4_B2");
HU_UpdatePatch(&kp_duel_you, "DUEL_YOU"); HU_UpdatePatch(&kp_duel_you, "DUEL_YOU");
sprintf(buffer, "DUELMBxx"); sprintf(buffer, "DUELMBxx");
@ -1190,6 +1200,7 @@ static patch_t *K_GetCachedItemPatch(INT32 item, UINT8 offset)
kp_gardentop, kp_gardentop,
kp_gachabom, kp_gachabom,
kp_stoneshoe, kp_stoneshoe,
kp_toxomister,
}; };
if (item == KITEM_SAD || (item > KITEM_NONE && item < NUMKARTITEMS)) if (item == KITEM_SAD || (item > KITEM_NONE && item < NUMKARTITEMS))
@ -3306,20 +3317,50 @@ INT32 K_GetTransFlagFromFixed(fixed_t value)
} }
} }
static tic_t duel_lastleveltime = 0; // We want to draw teams and duel HUD in a player context,
static INT32 duel_marginanim = 0; // but also precisely control how often it's drawn, even if
static INT32 duel_lastmargin = 0; // some players have no view.
static INT32 youheight = 0; static UINT8 K_FirstActiveDisplayPlayer(player_t *player)
{
UINT8 i;
for (i = 0; i <= r_splitscreen; i++)
{
player_t *pl = &players[displayplayers[i]];
if (!pl->spectator && !camera[i].freecam)
break;
}
if (player == &players[displayplayers[i]])
return true;
return false;
}
// MAXSPLITSCREENPLAYERS not allowed here, warning for change later
static tic_t duel_lastleveltime[4];
static INT32 duel_marginanim[4];
static INT32 duel_lastmargin[4];
static INT32 youheight[4];
static void K_drawKartDuelScores(void) static void K_drawKartDuelScores(void)
{ {
if (!K_InRaceDuel()) if (!K_InRaceDuel())
return; return;
if (r_splitscreen > 1 && !K_FirstActiveDisplayPlayer(stplyr))
return;
using srb2::Draw; using srb2::Draw;
player_t *foe = K_DuelOpponent(stplyr); player_t *foe = K_DuelOpponent(stplyr);
if (stplyr == foe)
return;
boolean use4p = (r_splitscreen) ? 1 : 0;
UINT8 vn = R_GetViewNumber();
INT32 basex = 0; INT32 basex = 0;
INT32 basey = 48; INT32 basey = 48;
INT32 flags = V_SNAPTOLEFT|V_HUDTRANS|V_SLIDEIN; INT32 flags = V_SNAPTOLEFT|V_HUDTRANS|V_SLIDEIN;
@ -3342,13 +3383,66 @@ static void K_drawKartDuelScores(void)
INT32 youscorex = 16; INT32 youscorex = 16;
INT32 youscorey = 69; INT32 youscorey = 69;
Draw::Font scorefont = Draw::Font::kThinTimer; INT32 margx = 0;
INT32 margy = 0;
boolean redraw = false; // Draw a duplicate?
boolean redrawn = false;
if (use4p)
{
basex = BASEVIDWIDTH/2 - 40;
basey = 0;
flags = V_SNAPTOTOP|V_HUDTRANS|V_SLIDEIN;
redraw = true;
if (r_splitscreen == 1)
{
redraw = false;
flags |= V_SNAPTORIGHT;
if (R_GetViewNumber() == 1)
{
flags |= V_SNAPTOBOTTOM;
flags &= ~V_SNAPTOTOP;
basey = BASEVIDHEIGHT - 40;
}
basex = BASEVIDWIDTH - 80;
}
barx = 40;
bary = 7;
barheight = 35; // MOTHERFUCK FLIPPED IN 4P
barwidth = 4; // DITTO
foex = 6;
foey = 12;
youx = 63;
youy = 12;
foescorex = foex + 6;
foescorey = foey + 12;
youscorex = youx + 6;
youscorey = youy + 12;
margx = 15;
margy = -40;
}
redraw:
Draw::Font scorefont = use4p ? Draw::Font::kZVote : Draw::Font::kThinTimer;
Draw::Align scorealign = use4p ? Draw::Align::kCenter : Draw::Align::kLeft;
UINT8 ri = 6; UINT8 ri = 6;
INT32 youfill = skincolors[stplyr->skincolor].ramp[ri]; INT32 youfill = skincolors[stplyr->skincolor].ramp[ri];
INT32 foefill = skincolors[foe->skincolor].ramp[ri]; INT32 foefill = skincolors[foe->skincolor].ramp[ri];
V_DrawScaledPatch(basex, basey, flags, kp_duel_sticker); if (use4p)
V_DrawScaledPatch(basex, basey, flags, kp_duel_4sticker);
else
V_DrawScaledPatch(basex, basey, flags, kp_duel_sticker);
INT32 scoredelta = stplyr->duelscore - foe->duelscore; INT32 scoredelta = stplyr->duelscore - foe->duelscore;
INT32 clutchscore = DUELWINNINGSCORE - 1; // we want the bar to be full when NEXT checkpoint wins... INT32 clutchscore = DUELWINNINGSCORE - 1; // we want the bar to be full when NEXT checkpoint wins...
@ -3371,27 +3465,43 @@ static void K_drawKartDuelScores(void)
targetyouheight = 2*barheight - savemargin; targetyouheight = 2*barheight - savemargin;
} }
if (leveltime != duel_lastleveltime) if (leveltime != duel_lastleveltime[vn])
{ {
INT32 slide = std::max(1, abs(targetyouheight - youheight)/3); INT32 slide = std::max(1, abs(targetyouheight - youheight[vn])/3);
if (targetyouheight > youheight) if (targetyouheight > youheight[vn])
youheight += slide; youheight[vn] += slide;
else if (targetyouheight < youheight) else if (targetyouheight < youheight[vn])
youheight -= slide; youheight[vn] -= slide;
} }
INT32 foeheight = 2*barheight-youheight; // barheight is a single tied bar, so total height of the full gauge is 2x barheight INT32 foeheight = 2*barheight-youheight[vn]; // barheight is a single tied bar, so total height of the full gauge is 2x barheight
V_DrawFill(basex+barx, basey+bary-barheight, barwidth, foeheight, foefill|flags); if (use4p)
V_DrawFill(basex+barx, basey+bary-barheight+foeheight, barwidth, youheight, youfill|flags); {
V_DrawFill(basex+barx-barheight, basey+bary, foeheight, barwidth, foefill|flags);
V_DrawFill(basex+barx-barheight+foeheight, basey+bary, youheight[vn], barwidth, youfill|flags);
}
else
{
V_DrawFill(basex+barx, basey+bary-barheight, barwidth, foeheight, foefill|flags);
V_DrawFill(basex+barx, basey+bary-barheight+foeheight, barwidth, youheight[vn], youfill|flags);
}
V_DrawScaledPatch(basex, basey, flags, kp_duel_under); V_DrawScaledPatch(basex, basey, flags, use4p ? kp_duel_4under : kp_duel_under);
V_DrawScaledPatch(basex, basey-barheight+foeheight, flags, kp_duel_over);
V_DrawScaledPatch(basex, basey, flags, kp_duel_foe);
V_DrawScaledPatch(basex, basey, flags, kp_duel_you);
Draw foenum = Draw(basex+foescorex, basey+foescorey).flags(flags).font(scorefont).align(Draw::Align::kLeft); if (use4p)
Draw younum = Draw(basex+youscorex, basey+youscorey).flags(flags).font(scorefont).align(Draw::Align::kLeft); V_DrawScaledPatch(basex-barheight+foeheight, basey, flags, kp_duel_4over);
else
V_DrawScaledPatch(basex, basey-barheight+foeheight, flags, kp_duel_over);
if (!use4p)
{
V_DrawScaledPatch(basex, basey, flags, kp_duel_foe);
V_DrawScaledPatch(basex, basey, flags, kp_duel_you);
}
Draw foenum = Draw(basex+foescorex, basey+foescorey).flags(flags).font(scorefont).align(scorealign);
Draw younum = Draw(basex+youscorex, basey+youscorey).flags(flags).font(scorefont).align(scorealign);
if (abs(scoredelta) == clutchscore && ((leveltime % 2) || cv_reducevfx.value)) if (abs(scoredelta) == clutchscore && ((leveltime % 2) || cv_reducevfx.value))
{ {
@ -3414,8 +3524,8 @@ static void K_drawKartDuelScores(void)
for (UINT8 draw = 0; draw < 2; draw++) for (UINT8 draw = 0; draw < 2; draw++)
{ {
UINT8 drawme = draw ? (stplyr - players) : (foe - players); UINT8 drawme = draw ? (stplyr - players) : (foe - players);
UINT8 drawx = basex + (draw ? youx : foex); UINT16 drawx = basex + (draw ? youx : foex);
UINT8 drawy = basey + (draw ? youy : foey); UINT16 drawy = basey + (draw ? youy : foey);
if (!playeringame[drawme] || players[drawme].spectator) if (!playeringame[drawme] || players[drawme].spectator)
continue; continue;
@ -3450,7 +3560,7 @@ static void K_drawKartDuelScores(void)
else else
colormap = R_GetTranslationColormap(workingskin, static_cast<skincolornum_t>(players[drawme].mo->color), GTC_CACHE); colormap = R_GetTranslationColormap(workingskin, static_cast<skincolornum_t>(players[drawme].mo->color), GTC_CACHE);
V_DrawMappedPatch(drawx+xoff, drawy+yoff, flags|flipflag, faceprefix[workingskin][FACE_RANK], colormap); V_DrawMappedPatch(drawx+xoff, drawy+yoff, flags|flipflag, faceprefix[workingskin][use4p ? FACE_MINIMAP : FACE_RANK], colormap);
} }
// Dogshit. Should have just figured out how to do log base 5 in C++. // Dogshit. Should have just figured out how to do log base 5 in C++.
@ -3466,139 +3576,150 @@ static void K_drawKartDuelScores(void)
INT32 boostspersymbol = 3; // How many boosts should it take to see a new symbol? INT32 boostspersymbol = 3; // How many boosts should it take to see a new symbol?
// rawmargin = (leveltime/10)%(3*boostspersymbol); // rawmargin = (leveltime/10)%(3*boostspersymbol);
if (duel_lastleveltime != leveltime) // Trigger the "slide" animation when rawmargin changes. if (duel_lastleveltime[vn] != leveltime) // Trigger the "slide" animation when rawmargin changes.
{ {
duel_marginanim = std::min(duel_marginanim + 1, 100); // not magic just arbitrary duel_marginanim[vn] = std::min(duel_marginanim[vn] + 1, 100); // not magic just arbitrary
if (duel_lastmargin != rawmargin) if (duel_lastmargin[vn] != rawmargin)
{ {
duel_marginanim = 0; duel_marginanim[vn] = 0;
duel_lastmargin = rawmargin; duel_lastmargin[vn] = rawmargin;
} }
} }
duel_lastleveltime = leveltime; duel_lastleveltime[vn] = leveltime;
// CONS_Printf("=== RAWMARGIN %d\n", rawmargin); // CONS_Printf("=== RAWMARGIN %d\n", rawmargin);
if (rawmargin == 0) if (rawmargin != 0)
return;
rawmargin--; // Start at 0, idiot
// We're invoking the RNG to get a slightly chaotic symbol distribution,
// but we're a HUD hook, so we need to keep the results of the call consistent.
P_SetRandSeed(PR_NUISANCE, 69 + rawmargin);
INT32 highsymbol = rawmargin/boostspersymbol + 1; // Highest symbol that should appear.
INT32 symbolsperupgrade = 5; // What is each symbol worth relative to each other? Like, 5 Stars = 1 Moon, etc.
// Okay, so we would LOVE to do this in a way that isn't a big clusterfuck, like just
// doing rawmargin^3 and then subtracting powers of 5 out of that. Unfortunately, UINT64
// is too small for the values that feel intuitively right here, so we have to do some of
// the math on a limited set of symbols, then shift up. This is the concept of "symbol
// headroom" that's in use here.
//
// (Note that Puyo~n uses a super inconsistent symbol table, probably to avoid this problem,
// but we're assholes and want things to feel logically consistent I guess?
// I dunno. I sort of feel like I should have just directly used the Puyo~n garbage table and
// avoided most of this, LOL)
INT32 symbolheadroom = 5; // Maximum # symbols we can "step down".
INT32 frac = rawmargin % boostspersymbol; // Used in intermediate calculations.
INT32 minsymbol = std::max(1, highsymbol - symbolheadroom); // The lowest symbol that should appear.
INT32 symbolheadroominuse = highsymbol - minsymbol; // The # of symbols we are stepping down.
INT32 minscore = std::pow(symbolsperupgrade, symbolheadroominuse+1);
INT32 maxscore = std::pow(symbolsperupgrade, symbolheadroominuse+2) - 1;
// CONS_Printf("min %d max %d\n", minscore, maxscore);
// We show the player successive combos with the same leading symbol, but we
// waht them to feel intuitively like they're increasing each time.
// Maxscore and minscore have been mapped to the correct power-of-N, so any
// point we pick between them will lead with the correct symbol once we adjust
// for symbol headroom. Pick a point that's appropriate for how "far" into the
// current symbol we are.
fixed_t lobound = FRACUNIT * frac / boostspersymbol;
fixed_t hibound = FRACUNIT * (frac+1) / boostspersymbol;
fixed_t roll = P_RandomRange(PR_NUISANCE, lobound, hibound);
INT32 margin = Easing_Linear(roll, minscore, maxscore); // The score we're trying to draw a garbage stack for.
INT32 margindigits[5];
memset(margindigits, -1, sizeof(margindigits));
INT32 nummargindigits = 0;
// CONS_Printf("margin %d min %d max %d roll %d shiu %d ms %d\n", margin, minscore, maxscore, roll, symbolheadroominuse, minsymbol);
if (rawmargin/boostspersymbol >= (MARGINLEVELS-1))
{ {
// Capped out. Show 5 Chaos. rawmargin--; // Start at 0, idiot
nummargindigits = 5;
for(UINT8 i = 0; i < nummargindigits; i++) // We're invoking the RNG to get a slightly chaotic symbol distribution,
// but we're a HUD hook, so we need to keep the results of the call consistent.
P_SetRandSeed(PR_NUISANCE, 69 + rawmargin);
INT32 highsymbol = rawmargin/boostspersymbol + 1; // Highest symbol that should appear.
INT32 symbolsperupgrade = 5; // What is each symbol worth relative to each other? Like, 5 Stars = 1 Moon, etc.
// Okay, so we would LOVE to do this in a way that isn't a big clusterfuck, like just
// doing rawmargin^3 and then subtracting powers of 5 out of that. Unfortunately, UINT64
// is too small for the values that feel intuitively right here, so we have to do some of
// the math on a limited set of symbols, then shift up. This is the concept of "symbol
// headroom" that's in use here.
//
// (Note that Puyo~n uses a super inconsistent symbol table, probably to avoid this problem,
// but we're assholes and want things to feel logically consistent I guess?
// I dunno. I sort of feel like I should have just directly used the Puyo~n garbage table and
// avoided most of this, LOL)
INT32 symbolheadroom = 5; // Maximum # symbols we can "step down".
INT32 frac = rawmargin % boostspersymbol; // Used in intermediate calculations.
INT32 minsymbol = std::max(1, highsymbol - symbolheadroom); // The lowest symbol that should appear.
INT32 symbolheadroominuse = highsymbol - minsymbol; // The # of symbols we are stepping down.
INT32 minscore = std::pow(symbolsperupgrade, symbolheadroominuse+1);
INT32 maxscore = std::pow(symbolsperupgrade, symbolheadroominuse+2) - 1;
// CONS_Printf("min %d max %d\n", minscore, maxscore);
// We show the player successive combos with the same leading symbol, but we
// waht them to feel intuitively like they're increasing each time.
// Maxscore and minscore have been mapped to the correct power-of-N, so any
// point we pick between them will lead with the correct symbol once we adjust
// for symbol headroom. Pick a point that's appropriate for how "far" into the
// current symbol we are.
fixed_t lobound = FRACUNIT * frac / boostspersymbol;
fixed_t hibound = FRACUNIT * (frac+1) / boostspersymbol;
fixed_t roll = P_RandomRange(PR_NUISANCE, lobound, hibound);
INT32 margin = Easing_Linear(roll, minscore, maxscore); // The score we're trying to draw a garbage stack for.
INT32 margindigits[5];
memset(margindigits, -1, sizeof(margindigits));
INT32 nummargindigits = 0;
// CONS_Printf("margin %d min %d max %d roll %d shiu %d ms %d\n", margin, minscore, maxscore, roll, symbolheadroominuse, minsymbol);
if (rawmargin/boostspersymbol >= (MARGINLEVELS-1))
{ {
margindigits[i] = MARGINLEVELS-1; // Capped out. Show 5 Chaos.
} nummargindigits = 5;
} for(UINT8 i = 0; i < nummargindigits; i++)
else
{
// Subtract powers of N from our chosen score to create a decent-enough-looking
// garbage stack, then queue up the right patches to be drawn, shifting all the math
// up by "minsymbol"—remember, once maxsymbol goes above symbolheadroom, we are doing
// a low-precision version of the math that ignores low enough symbols.
while (margin > 0)
{
INT32 significant_margin = 0;
for (UINT8 i = symbolheadroominuse+1; i >= 0; i--)
{ {
INT32 test = std::pow(symbolsperupgrade, i); margindigits[i] = MARGINLEVELS-1;
// CONS_Printf("testing %d (%d)\n", i, test);
if (margin >= test)
{
significant_margin = i;
break;
}
} }
}
else
{
// Subtract powers of N from our chosen score to create a decent-enough-looking
// garbage stack, then queue up the right patches to be drawn, shifting all the math
// up by "minsymbol"—remember, once maxsymbol goes above symbolheadroom, we are doing
// a low-precision version of the math that ignores low enough symbols.
while (margin > 0)
{
INT32 significant_margin = 0;
for (UINT8 i = symbolheadroominuse+1; i >= 0; i--)
{
INT32 test = std::pow(symbolsperupgrade, i);
// CONS_Printf("testing %d (%d)\n", i, test);
if (margin >= test)
{
significant_margin = i;
break;
}
}
INT32 index = significant_margin; INT32 index = significant_margin;
margindigits[nummargindigits] = index + minsymbol - 1; margindigits[nummargindigits] = index + minsymbol - 1;
// CONS_Printf("digit %d %d\n", nummargindigits, margindigits[nummargindigits]); // CONS_Printf("digit %d %d\n", nummargindigits, margindigits[nummargindigits]);
nummargindigits++; nummargindigits++;
// CONS_Printf("margin was %d ", margin); // CONS_Printf("margin was %d ", margin);
margin -= std::pow(symbolsperupgrade, index); margin -= std::pow(symbolsperupgrade, index);
// CONS_Printf("is %d\n", margin); // CONS_Printf("is %d\n", margin);
if (nummargindigits >= 3 + frac) if (nummargindigits >= 3 + frac)
break; break;
}
}
INT32 marginspacing = std::min(6, duel_marginanim[vn]);
INT32 marginx = ((nummargindigits-1) * marginspacing)/2;
for (INT32 i = nummargindigits - 1; i >= 0; i--)
{
// CONS_Printf("draw %d - %d\n", i, margindigits[i]);
V_DrawScaledPatch(basex + margx + marginx, basey + margy, flags, kp_duel_margin[margindigits[i]]);
marginx -= marginspacing;
} }
} }
INT32 marginspacing = std::min(6, duel_marginanim); if (redraw && !redrawn)
INT32 marginx = ((nummargindigits-1) * marginspacing)/2;
for (INT32 i = nummargindigits - 1; i >= 0; i--)
{ {
// CONS_Printf("draw %d - %d\n", i, margindigits[i]); basey = BASEVIDHEIGHT - 40;
V_DrawScaledPatch(basex + marginx, basey, flags, kp_duel_margin[margindigits[i]]); flags &= ~V_SNAPTOTOP;
marginx -= marginspacing; flags |= V_SNAPTOBOTTOM;
redrawn = true;
goto redraw;
} }
} }
static INT32 easedallyscore = 0; // MAXSPLITSCREENPLAYERS not allowed here, warning for changes later
static tic_t scorechangecooldown = 0; static INT32 easedallyscore[4];
static tic_t scorechangecooldown[4];
// Mildly ugly. Don't want to export this to khud when it's so nicely handled here, // Mildly ugly. Don't want to export this to khud when it's so nicely handled here,
// but HUD hooks run at variable timing based on your actual framerate. // but HUD hooks run at variable timing based on your actual framerate.
static tic_t teams_lastleveltime = 0; static tic_t teams_lastleveltime[4];
void K_drawKartTeamScores(boolean fromintermission, INT32 interoffset) void K_drawKartTeamScores(boolean fromintermission, INT32 interoffset)
{ {
if (G_GametypeHasTeams() == false) if (G_GametypeHasTeams() == false)
{
return; return;
}
if (r_splitscreen > 1 && !K_FirstActiveDisplayPlayer(stplyr))
return;
if (TEAM__MAX != 3) if (TEAM__MAX != 3)
return; // "maybe someday" - the magic conch return; // "maybe someday" - the magic conch
@ -3606,7 +3727,8 @@ void K_drawKartTeamScores(boolean fromintermission, INT32 interoffset)
// I get to write HUD code from scratch, so it's going to be horribly // I get to write HUD code from scratch, so it's going to be horribly
// verbose and obnoxious. // verbose and obnoxious.
UINT8 use4p = (r_splitscreen > 1) ? 1 : 0; UINT8 use4p = !!(r_splitscreen);
UINT8 vn = R_GetViewNumber();
INT32 basex = BASEVIDWIDTH/2 + 20; INT32 basex = BASEVIDWIDTH/2 + 20;
INT32 basey = 0; INT32 basey = 0;
@ -3660,6 +3782,18 @@ void K_drawKartTeamScores(boolean fromintermission, INT32 interoffset)
facex = -2; facex = -2;
facey = -5; facey = -5;
faceoff = 4; faceoff = 4;
if (r_splitscreen == 1 && !fromintermission)
{
basex += 110;
flags |= V_SNAPTORIGHT;
if (R_GetViewNumber() == 1)
{
flags |= V_SNAPTOBOTTOM;
flags &= ~V_SNAPTOTOP;
basey = 170;
}
}
} }
if (fromintermission) if (fromintermission)
@ -3723,47 +3857,47 @@ void K_drawKartTeamScores(boolean fromintermission, INT32 interoffset)
R_GetTranslationColormap(TC_RAINBOW, g_teaminfo[allies].color, GTC_CACHE) : R_GetTranslationColormap(TC_RAINBOW, g_teaminfo[allies].color, GTC_CACHE) :
R_GetTranslationColormap(TC_RAINBOW, g_teaminfo[enemies].color, GTC_CACHE); R_GetTranslationColormap(TC_RAINBOW, g_teaminfo[enemies].color, GTC_CACHE);
if (scorechangecooldown) if (scorechangecooldown[vn])
scorechangecooldown--; scorechangecooldown[vn]--;
// prevent giga flicker on team scoring // prevent giga flicker on team scoring
if (easedallyscore == allyscore) if (easedallyscore[vn] == allyscore)
{ {
// :O // :O
} }
else else
{ {
if (teams_lastleveltime != leveltime) // Timing consistency if (teams_lastleveltime[vn] != leveltime) // Timing consistency
{ {
INT32 delta = abs(easedallyscore - allyscore); // how wrong is display score? INT32 delta = abs(easedallyscore[vn] - allyscore); // how wrong is display score?
if (scorechangecooldown == 0 && delta) if (scorechangecooldown[vn] == 0 && delta)
{ {
if (allyscore > easedallyscore) if (allyscore > easedallyscore[vn])
{ {
easedallyscore++; easedallyscore[vn]++;
if (!cv_reducevfx.value) if (!cv_reducevfx.value)
allycolor = R_GetTranslationColormap(TC_BLINK, SKINCOLOR_WHITE, GTC_CACHE); allycolor = R_GetTranslationColormap(TC_BLINK, SKINCOLOR_WHITE, GTC_CACHE);
} }
else else
{ {
easedallyscore--; easedallyscore[vn]--;
if (!cv_reducevfx.value) if (!cv_reducevfx.value)
enemycolor = R_GetTranslationColormap(TC_BLINK, SKINCOLOR_WHITE, GTC_CACHE); enemycolor = R_GetTranslationColormap(TC_BLINK, SKINCOLOR_WHITE, GTC_CACHE);
} }
scorechangecooldown = TICRATE/delta; scorechangecooldown[vn] = TICRATE/delta;
} }
} }
if (!fromintermission) if (!fromintermission)
{ {
// replace scores with eased scores // replace scores with eased scores
allyscore = easedallyscore; allyscore = easedallyscore[vn];
enemyscore = totalscore - allyscore; enemyscore = totalscore - allyscore;
} }
} }
teams_lastleveltime = leveltime; teams_lastleveltime[vn] = leveltime;
fixed_t enemypercent = FixedDiv(enemyscore*FRACUNIT, totalscore*FRACUNIT); fixed_t enemypercent = FixedDiv(enemyscore*FRACUNIT, totalscore*FRACUNIT);
// fixed_t allypercent = FixedDiv(allyscore*FRACUNIT, totalscore*FRACUNIT); // fixed_t allypercent = FixedDiv(allyscore*FRACUNIT, totalscore*FRACUNIT);
@ -3806,7 +3940,7 @@ void K_drawKartTeamScores(boolean fromintermission, INT32 interoffset)
// Draw at the top and bottom of the screen in 4P. // Draw at the top and bottom of the screen in 4P.
// Draw only at the bottom in intermission. // Draw only at the bottom in intermission.
boolean shouldsecondpass = use4p; boolean shouldsecondpass = (r_splitscreen > 1);
boolean onsecondpass = fromintermission; boolean onsecondpass = fromintermission;
draw: draw:
@ -7746,11 +7880,8 @@ void K_drawKartHUD(void)
K_DrawKartPositionNum(stplyr->position); K_DrawKartPositionNum(stplyr->position);
} }
if (R_GetViewNumber() == 0) K_drawKartTeamScores(false, 0);
{ K_drawKartDuelScores();
K_drawKartTeamScores(false, 0);
K_drawKartDuelScores();
}
} }
// This sucks, but we need to draw rings before EXP because 4P amps // This sucks, but we need to draw rings before EXP because 4P amps

View file

@ -4143,6 +4143,11 @@ fixed_t K_GetNewSpeed(const player_t *player)
p_speed = 15 * p_speed / 10; p_speed = 15 * p_speed / 10;
} }
if (!P_MobjWasRemoved(player->toxomisterCloud))
{
p_speed = FixedMul(p_speed, Obj_GetToxomisterCloudDrag(player->toxomisterCloud));
}
if (K_PlayerUsesBotMovement(player) == true && player->botvars.rubberband > 0) if (K_PlayerUsesBotMovement(player) == true && player->botvars.rubberband > 0)
{ {
// Acceleration is tied to top speed... // Acceleration is tied to top speed...
@ -7076,7 +7081,7 @@ mobj_t *K_ThrowKartItemEx(player_t *player, boolean missile, mobjtype_t mapthing
{ {
mobj_t *lasttrail = K_FindLastTrailMobj(player); mobj_t *lasttrail = K_FindLastTrailMobj(player);
if (mapthing == MT_BUBBLESHIELDTRAP) // Drop directly on top of you. if (mapthing == MT_BUBBLESHIELDTRAP || mapthing == MT_TOXOMISTER_POLE) // Drop directly on top of you.
{ {
newangle = player->mo->angle; newangle = player->mo->angle;
newx = player->mo->x + player->mo->momx; newx = player->mo->x + player->mo->momx;
@ -9579,6 +9584,11 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
player->pflags2 &= ~PF2_SUPERTRANSFERVFX; player->pflags2 &= ~PF2_SUPERTRANSFERVFX;
} }
if (K_PlayerUsesBotMovement(player) && !K_BotUnderstandsItem(player->itemtype) && player->itemamount)
{
K_DropItems(player);
}
if (player->transfer) if (player->transfer)
{ {
if (player->fastfall) if (player->fastfall)
@ -14049,7 +14059,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
UINT32 debtrings = 20; UINT32 debtrings = 20;
if (player->rings < 0) if (player->rings < 0)
{ {
debtrings -= player->rings; debtrings += player->rings;
player->rings = 0; player->rings = 0;
} }
@ -15069,6 +15079,21 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
player->botvars.itemconfirm = 0; player->botvars.itemconfirm = 0;
} }
break; break;
case KITEM_TOXOMISTER:
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
{
K_SetItemOut(player); // need this to set itemscale
mobj_t *pole = K_ThrowKartItem(player, false, MT_TOXOMISTER_POLE, -1, 0, 0);
Obj_InitToxomisterPole(pole);
K_UnsetItemOut(player);
player->itemamount--;
K_PlayAttackTaunt(player->mo);
player->botvars.itemconfirm = 0;
}
break;
case KITEM_SAD: case KITEM_SAD:
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO
&& !player->sadtimer) && !player->sadtimer)
@ -16363,6 +16388,7 @@ boolean K_IsPickMeUpItem(mobjtype_t type)
case MT_SSMINE: case MT_SSMINE:
case MT_SSMINE_SHIELD: case MT_SSMINE_SHIELD:
case MT_FLOATINGITEM: // Stone Shoe case MT_FLOATINGITEM: // Stone Shoe
case MT_TOXOMISTER_POLE:
return true; return true;
default: default:
return false; return false;
@ -16425,6 +16451,9 @@ static boolean K_PickUp(player_t *player, mobj_t *picked)
else else
type = KITEM_SAD; type = KITEM_SAD;
break; break;
case MT_TOXOMISTER_POLE:
type = KITEM_TOXOMISTER;
break;
default: default:
type = KITEM_SAD; type = KITEM_SAD;
break; break;

View file

@ -46,10 +46,10 @@ Make sure this matches the actual number of states
#define BAIL_MAXCHARGE (84) // tics to bail when in painstate nad in air, on ground is half, if you touch this, also update Obj_BailChargeThink synced animation logic #define BAIL_MAXCHARGE (84) // tics to bail when in painstate nad in air, on ground is half, if you touch this, also update Obj_BailChargeThink synced animation logic
#define BAIL_DROP (FRACUNIT) #define BAIL_DROP (FRACUNIT)
#define BAIL_BOOST (FRACUNIT) #define BAIL_BOOST (6*FRACUNIT/5)
#define BAIL_CREDIT_DEBTRINGS (true) #define BAIL_CREDIT_DEBTRINGS (true)
#define BAIL_DROPFREQUENCY (2) #define BAIL_DROPFREQUENCY (2)
#define BAILSTUN (TICRATE*10) #define BAILSTUN (TICRATE*7)
#define MAXCOMBOTHRUST (mapobjectscale*20) #define MAXCOMBOTHRUST (mapobjectscale*20)
#define MAXCOMBOFLOAT (mapobjectscale*10) #define MAXCOMBOFLOAT (mapobjectscale*10)

View file

@ -475,6 +475,16 @@ boolean Obj_TickStoneShoeChain(mobj_t *chain);
player_t *Obj_StoneShoeOwnerPlayer(mobj_t *shoe); player_t *Obj_StoneShoeOwnerPlayer(mobj_t *shoe);
void Obj_CollideStoneShoe(mobj_t *mover, mobj_t *mobj); void Obj_CollideStoneShoe(mobj_t *mover, mobj_t *mobj);
/* Toxomister */
void Obj_InitToxomisterPole(mobj_t *pole);
boolean Obj_TickToxomisterPole(mobj_t *pole);
boolean Obj_TickToxomisterEye(mobj_t *eye);
boolean Obj_TickToxomisterCloud(mobj_t *cloud);
boolean Obj_ToxomisterPoleCollide(mobj_t *pole, mobj_t *toucher);
boolean Obj_ToxomisterCloudCollide(mobj_t *cloud, mobj_t *toucher);
fixed_t Obj_GetToxomisterCloudDrag(mobj_t *cloud);
#ifdef __cplusplus #ifdef __cplusplus
} // extern "C" } // extern "C"
#endif #endif

View file

@ -96,14 +96,15 @@ static UINT32 K_DynamicItemOddsRace[NUMKARTRESULTS-1][2] =
{1, 1}, // lightningshield {1, 1}, // lightningshield
{25, 4}, // bubbleshield {25, 4}, // bubbleshield
{66, 9}, // flameshield {66, 9}, // flameshield
{1, 3}, // hyudoro {1, 2}, // hyudoro
{0, 0}, // pogospring {0, 0}, // pogospring
{30, 8}, // superring (SPECIAL! distance value specifies when this can NO LONGER appear) {30, 8}, // superring (SPECIAL! distance value specifies when this can NO LONGER appear)
{0, 0}, // kitchensink {0, 0}, // kitchensink
{1, 3}, // droptarget {1, 2}, // droptarget
{43, 5}, // gardentop {43, 5}, // gardentop
{0, 0}, // gachabom {0, 0}, // gachabom
{1, 3}, // stoneshoe {1, 2}, // stoneshoe
{1, 2}, // toxomister
{45, 6}, // dualsneaker {45, 6}, // dualsneaker
{55, 8}, // triplesneaker {55, 8}, // triplesneaker
{25, 2}, // triplebanana {25, 2}, // triplebanana
@ -140,6 +141,7 @@ static UINT32 K_DynamicItemOddsBattle[NUMKARTRESULTS-1][2] =
{0, 0}, // gardentop {0, 0}, // gardentop
{10, 5}, // gachabom {10, 5}, // gachabom
{0, 0}, // stoneshoe {0, 0}, // stoneshoe
{0, 0}, // toxomister
{0, 0}, // dualsneaker {0, 0}, // dualsneaker
{20, 1}, // triplesneaker {20, 1}, // triplesneaker
{0, 0}, // triplebanana {0, 0}, // triplebanana
@ -176,6 +178,7 @@ static UINT32 K_DynamicItemOddsSpecial[NUMKARTRESULTS-1][2] =
{0, 0}, // gardentop {0, 0}, // gardentop
{0, 0}, // gachabom {0, 0}, // gachabom
{0, 0}, // stoneshoe {0, 0}, // stoneshoe
{0, 0}, // toxomister
{35, 2}, // dualsneaker {35, 2}, // dualsneaker
{0, 0}, // triplesneaker {0, 0}, // triplesneaker
{0, 0}, // triplebanana {0, 0}, // triplebanana
@ -212,6 +215,7 @@ static UINT8 K_KartLegacyBattleOdds[NUMKARTRESULTS-1][2] =
{ 0, 0 }, // Garden Top { 0, 0 }, // Garden Top
{ 5, 0 }, // Gachabom { 5, 0 }, // Gachabom
{ 0, 1 }, // Stone Shoe { 0, 1 }, // Stone Shoe
{ 0, 1 }, // Toxomister
{ 0, 0 }, // Sneaker x2 { 0, 0 }, // Sneaker x2
{ 0, 1 }, // Sneaker x3 { 0, 1 }, // Sneaker x3
{ 0, 0 }, // Banana x3 { 0, 0 }, // Banana x3
@ -373,6 +377,7 @@ botItemPriority_e K_GetBotItemPriority(kartitems_t result)
case KITEM_EGGMAN: case KITEM_EGGMAN:
case KITEM_GACHABOM: case KITEM_GACHABOM:
case KITEM_STONESHOE: case KITEM_STONESHOE:
case KITEM_TOXOMISTER:
case KITEM_KITCHENSINK: case KITEM_KITCHENSINK:
{ {
// Used when in 1st place and relatively far from players. // Used when in 1st place and relatively far from players.
@ -924,6 +929,9 @@ static void K_PushToRouletteItemList(itemroulette_t *const roulette, INT32 item)
--------------------------------------------------*/ --------------------------------------------------*/
static void K_AddItemToReel(const player_t *player, itemroulette_t *const roulette, kartitems_t item) static void K_AddItemToReel(const player_t *player, itemroulette_t *const roulette, kartitems_t item)
{ {
if (player && K_PlayerUsesBotMovement(player) && !K_BotUnderstandsItem(item))
return;
K_PushToRouletteItemList(roulette, item); K_PushToRouletteItemList(roulette, item);
if (player == NULL) if (player == NULL)
@ -1052,6 +1060,7 @@ static boolean K_IsItemFirstOnly(kartitems_t item)
case KITEM_HYUDORO: case KITEM_HYUDORO:
case KITEM_DROPTARGET: case KITEM_DROPTARGET:
case KITEM_STONESHOE: case KITEM_STONESHOE:
case KITEM_TOXOMISTER:
return true; return true;
default: default:
return false; return false;
@ -1229,6 +1238,17 @@ static boolean K_TimingPermitsItem(kartitems_t item, const itemroulette_t *roule
return true; return true;
} }
static void K_FixEmptyRoulette(const player_t *player, itemroulette_t *const roulette)
{
if (roulette->itemListLen > 0)
return;
if (K_PlayerUsesBotMovement(player)) // Bots can't use certain items. Give them _something_.
K_PushToRouletteItemList(roulette, KITEM_SUPERRING);
else // Players can use all items, so this should never happen.
K_PushToRouletteItemList(roulette, KITEM_SAD);
}
/*-------------------------------------------------- /*--------------------------------------------------
void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette, boolean ringbox, boolean dryrun) void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette, boolean ringbox, boolean dryrun)
@ -1373,6 +1393,7 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet
if (K_ForcedSPB(player, roulette) == true) if (K_ForcedSPB(player, roulette) == true)
{ {
K_AddItemToReel(player, roulette, KITEM_SPB); K_AddItemToReel(player, roulette, KITEM_SPB);
K_FixEmptyRoulette(player, roulette);
return; return;
} }
@ -1397,6 +1418,7 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet
// singleItem = KITEM_SAD by default, // singleItem = KITEM_SAD by default,
// so it will be used when all items are turned off. // so it will be used when all items are turned off.
K_AddItemToReel(player, roulette, singleItem); K_AddItemToReel(player, roulette, singleItem);
K_FixEmptyRoulette(player, roulette);
return; return;
} }
@ -1752,6 +1774,8 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet
totalSpawnChance--; totalSpawnChance--;
} }
K_FixEmptyRoulette(player, roulette);
} }
/*-------------------------------------------------- /*--------------------------------------------------

View file

@ -67,6 +67,7 @@ target_sources(SRB2SDL2 PRIVATE
stone-shoe.cpp stone-shoe.cpp
exp.c exp.c
bail.c bail.c
toxomister.cpp
) )
add_subdirectory(versus) add_subdirectory(versus)

439
src/objects/toxomister.cpp Normal file
View file

@ -0,0 +1,439 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2025 by James Robert Roman
// Copyright (C) 2025 by Kart Krew
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include <algorithm>
#include <array>
#include "objects.hpp"
#include "../core/static_vec.hpp"
#include "../d_player.h"
#include "../doomdef.h"
#include "../doomtype.h"
#include "../g_game.h"
#include "../k_hud.h" // transflag
#include "../m_easing.h"
#include "../m_fixed.h"
#include "../m_random.h"
#include "../r_main.h"
#include "../tables.h"
using namespace srb2::objects;
namespace
{
Fixed distance3d(const Mobj* a, const Mobj* b)
{
return FixedHypot(FixedHypot(a->x - b->x, a->y - b->y), a->z - b->z);
}
Vec2<Fixed> angle_vector(angle_t x)
{
return Vec2<Fixed> {FCOS(x), FSIN(x)};
}
// copied from objects/hyudoro.c
static void
sine_bob
( mobj_t * hyu,
INT32 height,
angle_t a,
fixed_t sineofs)
{
hyu->sprzoff = FixedMul(height * hyu->scale,
sineofs + FINESINE(a >> ANGLETOFINESHIFT)) * P_MobjFlip(hyu);
}
static void
bob_in_place
( mobj_t * hyu,
INT32 height,
INT32 bob_speed)
{
sine_bob(hyu,
height,
(leveltime & (bob_speed - 1)) *
(ANGLE_MAX / bob_speed), -(3*FRACUNIT/4));
}
struct Eye;
struct Pole;
struct Cloud;
struct Eye : Mobj
{
static constexpr INT32 kOrbitRadius = 24;
bool valid() const { return Mobj::valid(owner()) && owner()->health > 0; }
bool tick()
{
if (!valid())
{
remove();
return false;
}
return true;
}
};
struct Pole : Mobj
{
static constexpr sfxenum_t kSound = sfx_s3kdal;
void extravalue1() = delete;
tic_t last_touch0() const { return mobj_t::extravalue1; }
void last_touch0(tic_t n) { mobj_t::extravalue1 = n; }
void extravalue2() = delete;
bool clouds_spawned() const { return mobj_t::extravalue2; }
void clouds_spawned(bool n) { mobj_t::extravalue2 = n; }
void reactiontime() = delete;
tic_t sound_started() const { return mobj_t::reactiontime; }
void sound_started(tic_t n) { mobj_t::reactiontime = n; }
void tracer() = delete;
Eye* eye() const { return Mobj::tracer<Eye>(); }
void eye(Eye* n) { Mobj::tracer(n); }
bool valid() const
{
if (!Mobj::valid(eye()))
return false;
return true;
}
void init()
{
Eye* p_eye = spawn_from<Eye>(MT_TOXOMISTER_EYE);
p_eye->owner(this);
p_eye->spriteyoffset(96*FRACUNIT);
last_touch0(leveltime);
clouds_spawned(false);
eye(p_eye);
flags |= MF_SPECIAL;
}
void spawn_clouds_in_orbit();
bool tick()
{
if (!valid())
{
remove();
return false;
}
if (P_IsObjectOnGround(this))
{
if (!clouds_spawned())
{
spawn_clouds_in_orbit();
clouds_spawned(true);
voice(sfx_s3k9e);
}
if (!voice_playing(kSound))
{
voice(kSound);
sound_started(leveltime);
}
if ((leveltime - sound_started()) % 256 == 0)
voice(kSound);
}
else
{
P_SpawnGhostMobj(this);
}
tick_eye();
return true;
}
void tick_eye()
{
Mobj::PosArg p = {pos2d(), z};
p.x += momx;
p.y += momy;
p.z += momz;
Mobj* targ = find_nearest_eyeball_target();
if (targ)
{
INT32 angle_to_targ = angle_to2d(targ);
Vec2<Fixed> v = angle_vector(angle_to_targ) * Fixed {Eye::kOrbitRadius * mapobjectscale};
p.x += v.x;
p.y += v.y;
eye()->angle = angle_to_targ;
}
eye()->move_origin(p);
}
angle_t angle_to2d(Mobj* mobj) const
{
return R_PointToAngle2(x, y, mobj->x, mobj->y);
}
Mobj* find_nearest_eyeball_target() const
{
srb2::StaticVec<Mobj*, MAXPLAYERS> targets;
for (INT32 i = 0; i < MAXPLAYERS; ++i)
{
if (!playeringame[i])
continue;
if (!players[i].mo)
continue;
targets.push_back(static_cast<Mobj*>(players[i].mo));
}
if (targets.empty())
return nullptr;
return *std::min_element(
targets.begin(),
targets.end(),
[this](Mobj* a, Mobj* b) { return distance3d(this, a) < distance3d(this, b); }
);
}
bool touch(Mobj* toucher)
{
if (touch_cooldown(toucher, 0))
return false;
if (K_TryPickMeUp(this, toucher, false))
return false;
// Adapted from P_XYMovement, MT_JAWZ
voice(info->deathsound);
P_KillMobj(this, NULL, NULL, DMG_NORMAL);
P_SetObjectMomZ(this, 24*FRACUNIT, false);
instathrust(R_PointToAngle2(toucher->x, toucher->y, x, y), 32 * mapobjectscale);
flags &= ~MF_NOGRAVITY;
hitlag(toucher, toucher, 8, true);
return false;
}
bool touch_cooldown
( Mobj* toucher,
UINT8 k)
{
tic_t cooldown = leveltime - last_touch0();
if (toucher == target() && cooldown < 10)
{
last_touch0(leveltime);
return true;
}
return false;
}
};
struct Cloud : Mobj
{
static constexpr INT32 kMaxFuse = 5*TICRATE;
void hnext() = delete;
Mobj* follow() const { return Mobj::hnext<Mobj>(); }
void follow(Mobj* n) { Mobj::hnext(n); }
void tracer() = delete;
Pole* pole() const { return Mobj::tracer<Pole>(); }
void pole(Pole* n) { Mobj::tracer(n); }
Fixed fuse_frac() const { return FRACUNIT - fuse * FRACUNIT / kMaxFuse; }
Fixed drag_var() const { return Easing_Linear(fuse_frac(), FRACUNIT/3, FRACUNIT); }
bool tick()
{
if (Mobj::valid(follow()))
return tick_follow();
return tick_patrol();
}
bool tick_follow()
{
if (!Mobj::valid(follow()))
{
remove();
return false;
}
move_origin(follow()->pos());
momx = 0;
momy = 0;
momz = 0;
bob_in_place(this, 8, 64);
voice_loop(sfx_s3kcfl);
if (leveltime % (TICRATE/3) == 0 && follow()->player->rings > -20) // toxomister ring drain
{
follow()->player->rings--;
S_StartSound(follow()->player->mo, sfx_antiri);
}
if (fuse < 3*TICRATE && leveltime % (1 + fuse / TICRATE) == 0)
{
renderflags ^= RF_DONTDRAW;
}
if (fuse < kMaxFuse && (kMaxFuse - fuse) % 20 == 0 && Mobj::valid(target()) && target()->player && follow()->player)
{
K_SpawnAmps(target()->player, K_PvPAmpReward(3, target()->player, follow()->player), this);
}
follow()->player->stunned = fuse; // stunned as long as cloud is here
return true;
}
bool tick_patrol()
{
if (Mobj::valid(pole()) && pole()->health > 0)
{
move_origin(pole()->pos());
instathrust(angle, 64 * mapobjectscale);
}
else
{
if (!fuse)
{
fuse = 3*TICRATE;
instathrust(angle, 2 * mapobjectscale);
}
if (leveltime & 1)
{
renderflags ^= RF_DONTDRAW;
}
}
return true;
}
bool touch(Mobj* toucher)
{
if (toucher == target())
return false;
if (toucher->player)
{
if (this == toucher->player->toxomisterCloud) // already attached
return true;
if (!P_MobjWasRemoved(toucher->player->toxomisterCloud))
{
toucher->player->pflags |= PF_CASTSHADOW;
return true;
}
P_SetTarget(&toucher->player->toxomisterCloud, this);
}
toucher->hitlag(8);
scale_to(destscale);
follow(toucher);
fuse = kMaxFuse;
renderflags &= ~RF_DONTDRAW;
voice(sfx_s3k8a);
return true;
}
};
void Pole::spawn_clouds_in_orbit()
{
constexpr INT32 kNumClouds = 6;
std::array<UINT32, kNumClouds> weights;
std::array<INT32, kNumClouds> order;
angle_t a = 0;
angle_t a_incr = ANGLE_MAX / kNumClouds;
for (INT32 i = 0; i < kNumClouds; ++i)
{
weights[i] = P_Random(PR_TRACKHAZARD);
order[i] = i;
}
std::stable_sort(order.begin(), order.end(), [&](INT32 a, INT32 b) { return weights[a] < weights[b]; });
for (INT32 i : order)
{
Cloud* cloud = spawn_from<Cloud>({}, MT_TOXOMISTER_CLOUD);
cloud->pole(this);
cloud->angle = a;
cloud->target(target());
cloud->spriteyoffset(24*FRACUNIT);
cloud->hitlag(2 + i * 4);
cloud->scale_between(1, cloud->scale(), cloud->scale() / 5);
a += a_incr;
}
}
}; // namespace
void Obj_InitToxomisterPole(mobj_t *pole)
{
static_cast<Pole*>(pole)->init();
}
boolean Obj_TickToxomisterPole(mobj_t *pole)
{
return static_cast<Pole*>(pole)->tick();
}
boolean Obj_TickToxomisterEye(mobj_t *eye)
{
return static_cast<Eye*>(eye)->tick();
}
boolean Obj_TickToxomisterCloud(mobj_t *cloud)
{
return static_cast<Cloud*>(cloud)->tick();
}
boolean Obj_ToxomisterPoleCollide(mobj_t *pole, mobj_t *toucher)
{
return static_cast<Pole*>(pole)->touch(static_cast<Mobj*>(toucher));
}
boolean Obj_ToxomisterCloudCollide(mobj_t *cloud, mobj_t *toucher)
{
return static_cast<Cloud*>(cloud)->touch(static_cast<Mobj*>(toucher));
}
fixed_t Obj_GetToxomisterCloudDrag(mobj_t *cloud)
{
return static_cast<Cloud*>(cloud)->drag_var();
}

View file

@ -1129,6 +1129,14 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
Obj_CollideStoneShoe(toucher, special); Obj_CollideStoneShoe(toucher, special);
return; return;
case MT_TOXOMISTER_POLE:
Obj_ToxomisterPoleCollide(special, toucher);
return;
case MT_TOXOMISTER_CLOUD:
Obj_ToxomisterCloudCollide(special, toucher);
return;
default: // SOC or script pickup default: // SOC or script pickup
P_SetTarget(&special->target, toucher); P_SetTarget(&special->target, toucher);
break; break;

View file

@ -1025,6 +1025,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
|| g_tm.thing->type == MT_MONITOR || g_tm.thing->type == MT_MONITOR
|| g_tm.thing->type == MT_BATTLECAPSULE || g_tm.thing->type == MT_BATTLECAPSULE
|| g_tm.thing->type == MT_KART_LEFTOVER || g_tm.thing->type == MT_KART_LEFTOVER
|| g_tm.thing->type == MT_TOXOMISTER_POLE
|| (g_tm.thing->type == MT_PLAYER))) || (g_tm.thing->type == MT_PLAYER)))
{ {
// see if it went over / under // see if it went over / under
@ -1043,6 +1044,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
|| thing->type == MT_MONITOR || thing->type == MT_MONITOR
|| thing->type == MT_BATTLECAPSULE || thing->type == MT_BATTLECAPSULE
|| thing->type == MT_KART_LEFTOVER || thing->type == MT_KART_LEFTOVER
|| thing->type == MT_TOXOMISTER_POLE
|| (thing->type == MT_PLAYER))) || (thing->type == MT_PLAYER)))
{ {
// see if it went over / under // see if it went over / under

View file

@ -1248,6 +1248,10 @@ fixed_t P_GetMobjGravity(mobj_t *mo)
case MT_GACHABOM: case MT_GACHABOM:
gravityadd = (5*gravityadd)/2; gravityadd = (5*gravityadd)/2;
break; break;
case MT_TOXOMISTER_POLE:
if (mo->health > 0)
gravityadd = (5*gravityadd)/2;
break;
case MT_BANANA: case MT_BANANA:
case MT_BALLHOG: case MT_BALLHOG:
case MT_BALLHOG_RETICULE_TEST: case MT_BALLHOG_RETICULE_TEST:
@ -2337,6 +2341,7 @@ boolean P_ZMovement(mobj_t *mo)
case MT_BIGTUMBLEWEED: case MT_BIGTUMBLEWEED:
case MT_LITTLETUMBLEWEED: case MT_LITTLETUMBLEWEED:
case MT_EMERALD: case MT_EMERALD:
case MT_TOXOMISTER_POLE:
if (!(mo->flags & MF_NOCLIPHEIGHT) && P_CheckDeathPitCollide(mo)) if (!(mo->flags & MF_NOCLIPHEIGHT) && P_CheckDeathPitCollide(mo))
{ {
P_RemoveMobj(mo); P_RemoveMobj(mo);
@ -5320,6 +5325,7 @@ boolean P_IsKartItem(INT32 type)
case MT_HYUDORO: case MT_HYUDORO:
case MT_SINK: case MT_SINK:
case MT_GACHABOM: case MT_GACHABOM:
case MT_TOXOMISTER_POLE:
return true; return true;
default: default:
@ -5346,6 +5352,7 @@ boolean P_IsKartFieldItem(INT32 type)
case MT_DROPTARGET: case MT_DROPTARGET:
case MT_DUELBOMB: case MT_DUELBOMB:
case MT_GACHABOM: case MT_GACHABOM:
case MT_TOXOMISTER_POLE:
return true; return true;
default: default:
@ -5379,6 +5386,7 @@ boolean P_IsRelinkItem(INT32 type)
case MT_HYUDORO_CENTER: case MT_HYUDORO_CENTER:
case MT_SINK: case MT_SINK:
case MT_GACHABOM: case MT_GACHABOM:
case MT_TOXOMISTER_POLE:
case MT_FLOATINGITEM: // Stone Shoe Trap case MT_FLOATINGITEM: // Stone Shoe Trap
return true; return true;
@ -6864,6 +6872,12 @@ static boolean P_MobjDeadThink(mobj_t *mobj)
P_SetMobjState(mobj, mobj->info->xdeathstate); P_SetMobjState(mobj, mobj->info->xdeathstate);
/* FALLTHRU */ /* FALLTHRU */
case MT_JAWZ_SHIELD: case MT_JAWZ_SHIELD:
mobj->renderflags ^= RF_DONTDRAW;
break;
case MT_TOXOMISTER_POLE:
if (mobj->momz == 0 && P_IsObjectOnGround(mobj))
P_SetMobjState(mobj, mobj->info->xdeathstate);
mobj->renderflags ^= RF_DONTDRAW; mobj->renderflags ^= RF_DONTDRAW;
break; break;
case MT_SSMINE: case MT_SSMINE:
@ -10296,6 +10310,15 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
case MT_STONESHOE: case MT_STONESHOE:
return Obj_TickStoneShoe(mobj); return Obj_TickStoneShoe(mobj);
case MT_TOXOMISTER_POLE:
return Obj_TickToxomisterPole(mobj);
case MT_TOXOMISTER_EYE:
return Obj_TickToxomisterEye(mobj);
case MT_TOXOMISTER_CLOUD:
return Obj_TickToxomisterCloud(mobj);
default: default:
// check mobj against possible water content, before movement code // check mobj against possible water content, before movement code
P_MobjCheckWater(mobj); P_MobjCheckWater(mobj);
@ -11160,6 +11183,9 @@ static void P_DefaultMobjShadowScale(mobj_t *thing)
case MT_STONESHOE_CHAIN: case MT_STONESHOE_CHAIN:
thing->shadowscale = FRACUNIT/5; thing->shadowscale = FRACUNIT/5;
break; break;
case MT_TOXOMISTER_POLE:
thing->shadowscale = FRACUNIT;
break;
default: default:
if (thing->flags & (MF_ENEMY|MF_BOSS)) if (thing->flags & (MF_ENEMY|MF_BOSS))
thing->shadowscale = FRACUNIT; thing->shadowscale = FRACUNIT;

View file

@ -93,6 +93,7 @@ typedef enum
BALLHOGRETICULE = 0x8000, BALLHOGRETICULE = 0x8000,
STONESHOE = 0x10000, STONESHOE = 0x10000,
FLYBOT = 0x20000, FLYBOT = 0x20000,
TOXOMISTERCLOUD = 0x40000,
} player_saveflags; } player_saveflags;
static inline void P_ArchivePlayer(savebuffer_t *save) static inline void P_ArchivePlayer(savebuffer_t *save)
@ -368,6 +369,9 @@ static void P_NetArchivePlayers(savebuffer_t *save)
if (players[i].stoneShoe) if (players[i].stoneShoe)
flags |= STONESHOE; flags |= STONESHOE;
if (players[i].toxomisterCloud)
flags |= TOXOMISTERCLOUD;
if (players[i].flybot) if (players[i].flybot)
flags |= FLYBOT; flags |= FLYBOT;
@ -421,6 +425,9 @@ static void P_NetArchivePlayers(savebuffer_t *save)
if (flags & STONESHOE) if (flags & STONESHOE)
WRITEUINT32(save->p, players[i].stoneShoe->mobjnum); WRITEUINT32(save->p, players[i].stoneShoe->mobjnum);
if (flags & TOXOMISTERCLOUD)
WRITEUINT32(save->p, players[i].toxomisterCloud->mobjnum);
if (flags & FLYBOT) if (flags & FLYBOT)
WRITEUINT32(save->p, players[i].flybot->mobjnum); WRITEUINT32(save->p, players[i].flybot->mobjnum);
@ -1082,6 +1089,9 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
if (flags & STONESHOE) if (flags & STONESHOE)
players[i].stoneShoe = (mobj_t *)(size_t)READUINT32(save->p); players[i].stoneShoe = (mobj_t *)(size_t)READUINT32(save->p);
if (flags & TOXOMISTERCLOUD)
players[i].toxomisterCloud = (mobj_t *)(size_t)READUINT32(save->p);
if (flags & FLYBOT) if (flags & FLYBOT)
players[i].flybot = (mobj_t *)(size_t)READUINT32(save->p); players[i].flybot = (mobj_t *)(size_t)READUINT32(save->p);
@ -6247,6 +6257,11 @@ static void P_RelinkPointers(void)
if (!RelinkMobj(&players[i].stoneShoe)) if (!RelinkMobj(&players[i].stoneShoe))
CONS_Debug(DBG_GAMELOGIC, "stoneShoe not found on player %d\n", i); CONS_Debug(DBG_GAMELOGIC, "stoneShoe not found on player %d\n", i);
} }
if (players[i].toxomisterCloud)
{
if (!RelinkMobj(&players[i].toxomisterCloud))
CONS_Debug(DBG_GAMELOGIC, "toxomisterCloud not found on player %d\n", i);
}
if (players[i].flybot) if (players[i].flybot)
{ {
if (!RelinkMobj(&players[i].flybot)) if (!RelinkMobj(&players[i].flybot))

View file

@ -4259,6 +4259,7 @@ void P_PlayerThink(player_t *player)
PlayerPointerErase(player->ballhogreticule); PlayerPointerErase(player->ballhogreticule);
PlayerPointerErase(player->flickyAttacker); PlayerPointerErase(player->flickyAttacker);
PlayerPointerErase(player->stoneShoe); PlayerPointerErase(player->stoneShoe);
PlayerPointerErase(player->toxomisterCloud);
PlayerPointerErase(player->powerup.flickyController); PlayerPointerErase(player->powerup.flickyController);
PlayerPointerErase(player->powerup.barrier); PlayerPointerErase(player->powerup.barrier);