diff --git a/src/d_clisrv.c b/src/d_clisrv.c index c4adc7476..b03224472 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -70,6 +70,7 @@ #include "sanitize.h" #include "r_fps.h" #include "filesrch.h" // refreshdirmenu +#include "k_objects.h" // cl loading screen #include "v_video.h" @@ -2510,6 +2511,7 @@ void CL_ClearPlayer(INT32 playernum) P_SetTarget(&players[playernum].whip, NULL); P_SetTarget(&players[playernum].hand, NULL); P_SetTarget(&players[playernum].hoverhyudoro, NULL); + P_SetTarget(&players[playernum].ballhogreticule, NULL); P_SetTarget(&players[playernum].ringShooter, NULL); // TODO: Any better handling in store? diff --git a/src/d_player.h b/src/d_player.h index ff370d8b7..7f3a55587 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -787,6 +787,7 @@ struct player_t UINT16 ballhogcharge; // Ballhog charge up -- the higher this value, the more projectiles boolean ballhogtap; // Ballhog released during charge: used to allow semirapid tapfire + mobj_t *ballhogreticule; // First ballhog reticule estimation object UINT16 hyudorotimer; // Duration of the Hyudoro offroad effect itself SINT8 stealingtimer; // if >0 you are stealing, if <0 you are being stolen from diff --git a/src/deh_tables.c b/src/deh_tables.c index 688abad70..2a55f5cbe 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -1922,6 +1922,7 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_BALLHOGBOOM14", "S_BALLHOGBOOM15", "S_BALLHOGBOOM16", + "S_BALLHOG_RETICULE", // Self-Propelled Bomb - just an explosion for now... "S_SPB1", @@ -3585,6 +3586,8 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_BALLHOG", // Ballhog "MT_BALLHOGBOOM", + "MT_BALLHOG_RETICULE", + "MT_BALLHOG_RETICULE_TEST", "MT_SPB", // Self-Propelled Bomb "MT_SPBEXPLOSION", diff --git a/src/g_game.c b/src/g_game.c index e905ca3b7..0158f6e6b 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2429,6 +2429,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) hoverhyudoro = players[player].hoverhyudoro; skyboxviewpoint = players[player].skybox.viewpoint; skyboxcenterpoint = players[player].skybox.centerpoint; + + K_UpdateBallhogReticules(&players[player], 0, false); } else { diff --git a/src/info.c b/src/info.c index 9538da5d2..7cd1a0dec 100644 --- a/src/info.c +++ b/src/info.c @@ -2458,6 +2458,7 @@ state_t states[NUMSTATES] = {SPR_BHBM, FF_FULLBRIGHT|13, 1, {NULL}, 0, 0, S_BALLHOGBOOM15}, // S_BALLHOGBOOM14 {SPR_BHBM, FF_FULLBRIGHT|14, 1, {NULL}, 0, 0, S_BALLHOGBOOM16}, // S_BALLHOGBOOM15 {SPR_BHBM, FF_FULLBRIGHT|15, 1, {NULL}, 0, 0, S_NULL}, // S_BALLHOGBOOM16 + {SPR_SPBM, FF_FULLBRIGHT|0, -1, {NULL}, 0, 0, S_NULL}, // S_BALLHOG_RETICULE {SPR_SPBM, 0, 1, {NULL}, 0, 0, S_SPB2}, // S_SPB1 {SPR_SPBM, 1, 1, {NULL}, 0, 0, S_SPB3}, // S_SPB2 @@ -15129,9 +15130,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { // MT_BALLHOG -1, // doomednum S_BALLHOG1, // spawnstate - 140, // spawnhealth + 1, // spawnhealth S_NULL, // seestate - sfx_None, // seesound + sfx_tossed, // seesound 8, // reactiontime sfx_None, // attacksound S_NULL, // painstate @@ -15142,7 +15143,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_BALLHOG_DEAD, // deathstate S_NULL, // xdeathstate sfx_hogbom, // deathsound - 40*FRACUNIT, // speed + 0, // speed 26*FRACUNIT, // radius 64*FRACUNIT, // height 0, // display offset @@ -15176,7 +15177,61 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 1, // damage sfx_None, // activesound - MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_DONTENCOREMAP, // flags + MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_DONTENCOREMAP, // flags + S_NULL // raisestate + }, + + { // MT_BALLHOG_RETICULE + -1, // doomednum + S_BALLHOG_RETICULE, // 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 + 26*FRACUNIT, // radius + 64*FRACUNIT, // height + 1, // display offset + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SCENERY|MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_DONTENCOREMAP, // flags + S_NULL // raisestate + }, + + { // MT_BALLHOG_RETICULE_TEST + -1, // doomednum + S_INVISIBLE, // 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 + 26*FRACUNIT, // radius + 64*FRACUNIT, // height + 0, // display offset + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_DONTENCOREMAP|MF_DONTPUNT, // flags S_NULL // raisestate }, diff --git a/src/info.h b/src/info.h index 9dc6fdb1a..6eff5131e 100644 --- a/src/info.h +++ b/src/info.h @@ -2949,6 +2949,7 @@ typedef enum state S_BALLHOGBOOM14, S_BALLHOGBOOM15, S_BALLHOGBOOM16, + S_BALLHOG_RETICULE, // Self-Propelled Bomb S_SPB1, @@ -4639,6 +4640,8 @@ typedef enum mobj_type MT_BALLHOG, // Ballhog MT_BALLHOGBOOM, + MT_BALLHOG_RETICULE, + MT_BALLHOG_RETICULE_TEST, MT_SPB, // SPB stuff MT_SPBEXPLOSION, diff --git a/src/k_kart.c b/src/k_kart.c index edb3f85d0..712d2db4a 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -556,7 +556,7 @@ UINT8 K_ItemResultToAmount(SINT8 getitem, const itemroulette_t *roulette) return 4; case KITEM_BALLHOG: // Not a special result, but has a special amount - return 5; + return 7; case KITEM_SUPERRING: if (roulette && roulette->popcorn) @@ -3157,7 +3157,6 @@ boolean K_WaterSkip(mobj_t *mobj) case MT_ORBINAUT: case MT_JAWZ: - case MT_BALLHOG: { // Allow break; @@ -5501,7 +5500,7 @@ fixed_t K_DefaultPlayerRadius(player_t *player) player->mo->info->radius); } -static mobj_t *K_SpawnKartMissile(mobj_t *source, mobjtype_t type, angle_t an, INT32 flags2, fixed_t speed, SINT8 dir) +static mobj_t *K_SpawnKartMissile(mobj_t *source, mobjtype_t type, angle_t an, INT32 flags2, fixed_t speed, fixed_t dir) { mobj_t *th; fixed_t x, y, z; @@ -5555,7 +5554,7 @@ static mobj_t *K_SpawnKartMissile(mobj_t *source, mobjtype_t type, angle_t an, I finalscale = source->scale; } - if (dir == -1) + if (dir < 0) { fixed_t nerf = FRACUNIT; @@ -5569,10 +5568,6 @@ static mobj_t *K_SpawnKartMissile(mobj_t *source, mobjtype_t type, angle_t an, I nerf = FRACUNIT/4; break; - case MT_BALLHOG: - nerf = FRACUNIT/8; - break; - default: break; } @@ -5631,11 +5626,6 @@ static mobj_t *K_SpawnKartMissile(mobj_t *source, mobjtype_t type, angle_t an, I S_StartSound(th, sfx_s3kbfl); S_StartSound(th, sfx_cdfm35); break; - case MT_BALLHOG: - // Contra spread shot scale up - th->destscale = th->destscale << 1; - th->scalespeed = abs(th->destscale - th->scale) / (2*TICRATE); - break; case MT_GARDENTOP: th->movefactor = finalspeed; break; @@ -6488,10 +6478,10 @@ static mobj_t *K_FindLastTrailMobj(player_t *player) return trail; } -mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t mapthing, INT32 defaultDir, INT32 altthrow, angle_t angleOffset) +mobj_t *K_ThrowKartItemEx(player_t *player, boolean missile, mobjtype_t mapthing, INT32 defaultDir, INT32 altthrow, angle_t angleOffset, fixed_t tossX, fixed_t tossY) { mobj_t *mo; - INT32 dir; + fixed_t dir = FRACUNIT; fixed_t PROJSPEED; angle_t newangle; fixed_t newx, newy, newz; @@ -6504,36 +6494,29 @@ mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t mapthing, { if (altthrow == 2) // Kitchen sink throwing { -#if 0 if (player->throwdir == 1) - dir = 3; + dir = 2 * FRACUNIT; else if (player->throwdir == -1) - dir = 1; + dir = FRACUNIT / 2; else - dir = 2; -#else - if (player->throwdir == 1) - dir = 2; - else - dir = 1; -#endif + dir = FRACUNIT; } else { if (player->throwdir == 1) - dir = 2; + dir = 2 * FRACUNIT; else if (player->throwdir == -1) - dir = -1; + dir = -FRACUNIT; else - dir = 1; + dir = FRACUNIT; } } else { if (player->throwdir != 0) - dir = player->throwdir; + dir = player->throwdir * FRACUNIT; else - dir = defaultDir; + dir = defaultDir * FRACUNIT; } if (mapthing == MT_GACHABOM && dir > 0) @@ -6604,32 +6587,57 @@ mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t mapthing, S_StartSound(player->mo, mo->info->seesound); - { - angle_t fa = player->mo->angle>>ANGLETOFINESHIFT; - fixed_t HEIGHT = ((20 + (dir*10)) * FRACUNIT) + (FixedDiv(player->mo->momz, mapobjectscale)*P_MobjFlip(player->mo)); // Also intentionally not player scale - - P_SetObjectMomZ(mo, HEIGHT, false); - mo->momx = player->mo->momx + FixedMul(FINECOSINE(fa), PROJSPEED*dir); - mo->momy = player->mo->momy + FixedMul(FINESINE(fa), PROJSPEED*dir); - } - mo->extravalue2 = dir; + fixed_t HEIGHT = ((20 * FRACUNIT) + (dir * 10)) + (FixedDiv(player->mo->momz, mapobjectscale) * P_MobjFlip(player->mo)); // Also intentionally not player scale + P_SetObjectMomZ(mo, HEIGHT, false); + + angle_t fa = (player->mo->angle >> ANGLETOFINESHIFT); + mo->momx = player->mo->momx + FixedMul(FINECOSINE(fa), FixedMul(PROJSPEED, dir)); + mo->momy = player->mo->momy + FixedMul( FINESINE(fa), FixedMul(PROJSPEED, dir)); + + if (tossX != 0 || tossY != 0) + { + fixed_t g = 5 * DEFAULT_GRAVITY / 2; // P_GetMobjGravity does not work here?? + if (dir > FRACUNIT) + { + g = FixedMul(g, dir); + } + + if (g > 0) + { + const INT32 air_time = (FixedDiv(mo->momz * P_MobjFlip(mo), g) * 2) / FRACUNIT; + + if (air_time > 0) + { + mo->momx += FixedMul(tossX, finalscale) / air_time; + mo->momy += FixedMul(tossY, finalscale) / air_time; + } + } + } + if (mo->eflags & MFE_UNDERWATER) mo->momz = (117 * mo->momz) / 200; P_SetScale(mo, finalscale); mo->destscale = finalscale; - if (mapthing == MT_BANANA) + switch (mapthing) { - mo->angle = FixedAngle(P_RandomRange(PR_DECORATION, -180, 180) << FRACBITS); - mo->rollangle = FixedAngle(P_RandomRange(PR_DECORATION, -180, 180) << FRACBITS); - } - - if (mapthing == MT_GACHABOM) - { - Obj_GachaBomThrown(mo, mo->radius, dir); + case MT_BANANA: + mo->angle = FixedAngle(P_RandomRange(PR_DECORATION, -180, 180) << FRACBITS); + mo->rollangle = FixedAngle(P_RandomRange(PR_DECORATION, -180, 180) << FRACBITS); + break; + case MT_GACHABOM: + Obj_GachaBomThrown(mo, mo->radius, dir); + break; + case MT_BALLHOG: + // Contra spread shot scale up + mo->destscale = mo->destscale << 1; + mo->scalespeed = abs(mo->destscale - mo->scale) / (2*TICRATE); + break; + default: + break; } // this is the small graphic effect that plops in you when you throw an item: @@ -6728,7 +6736,7 @@ mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t mapthing, } // Missiles set as traps inflict a nocollide stumble - if (dir < 0 && (mapthing == MT_ORBINAUT || mapthing == MT_ORBINAUT_SHIELD || mapthing == MT_JAWZ || mapthing == MT_JAWZ_SHIELD || mapthing == MT_BALLHOG || mapthing == MT_GACHABOM)) + if (dir < 0 && (mapthing == MT_ORBINAUT || mapthing == MT_ORBINAUT_SHIELD || mapthing == MT_JAWZ || mapthing == MT_JAWZ_SHIELD || mapthing == MT_GACHABOM)) { mo->cvmem = 1; } @@ -6736,6 +6744,11 @@ mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t mapthing, return mo; } +mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t mapthing, INT32 defaultDir, INT32 altthrow, angle_t angleOffset) +{ + return K_ThrowKartItemEx(player, missile, mapthing, defaultDir, altthrow, angleOffset, 0, 0); +} + void K_PuntMine(mobj_t *origMine, mobj_t *punter) { angle_t fa = K_MomentumAngle(punter); @@ -13424,7 +13437,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) case KITEM_BALLHOG: if (!HOLDING_ITEM && NO_HYUDORO) { - INT32 ballhogmax = (player->itemamount) * BALLHOGINCREMENT; + INT32 ballhogmax = player->itemamount * BALLHOGINCREMENT; // This construct looks a little goofy, but we're basically just // trying to prevent rapid taps from restarting a charge, while @@ -13437,75 +13450,60 @@ void K_MoveKartPlayer(player_t *player, boolean onground) if (player->ballhogcharge == 0) player->ballhogtap = false; - boolean realcharge = (cmd->buttons & BT_ATTACK) && (player->itemflags & IF_HOLDREADY) && (player->ballhogcharge < ballhogmax); + boolean realcharge = (cmd->buttons & BT_ATTACK) && (player->itemflags & IF_HOLDREADY) /*&& (player->ballhogcharge < ballhogmax)*/; if ((realcharge && !player->ballhogtap) || (player->ballhogtap && player->ballhogcharge < BALLHOGINCREMENT)) { - player->ballhogcharge++; - if (player->ballhogcharge % BALLHOGINCREMENT == 0) + if (player->ballhogcharge < ballhogmax) { - sfxenum_t hogsound[] = + player->ballhogcharge++; + + if (player->ballhogcharge % BALLHOGINCREMENT == 0) { - sfx_bhog00, - sfx_bhog01, - sfx_bhog02, - sfx_bhog03, - sfx_bhog04, - sfx_bhog05 - }; - UINT8 chargesound = max(1, min(player->ballhogcharge / BALLHOGINCREMENT, 6)); - S_StartSound(player->mo, hogsound[chargesound-1]); + sfxenum_t hogsound[] = + { + sfx_bhog00, + sfx_bhog01, + sfx_bhog02, + sfx_bhog03, + sfx_bhog04, + sfx_bhog05 + }; + UINT8 chargesound = max(1, min(player->ballhogcharge / BALLHOGINCREMENT, 6)); + S_StartSound(player->mo, hogsound[chargesound-1]); + } } } else { - if (cmd->buttons & BT_ATTACK) - { - player->itemflags &= ~IF_HOLDREADY; - } - else - { - player->itemflags |= IF_HOLDREADY; - } - if (player->ballhogcharge > 0) { - INT32 numhogs = min((player->ballhogcharge / BALLHOGINCREMENT), player->itemamount); - - K_SetItemOut(player); // need this to set itemscale - - if (numhogs <= 0) + INT32 numhogs = K_HogChargeToHogCount(player->ballhogcharge, player->itemamount); + if (numhogs > 0) // no tapfire scams { - // no tapfire scams - } - else if (numhogs == 1) - { - player->itemamount--; - K_ThrowKartItem(player, true, MT_BALLHOG, 1, 0, 0); - K_PlayAttackTaunt(player->mo); - } - else - { - angle_t cone = 0x01800000 * (numhogs-1); - angle_t offsetAmt = (cone * 2) / (numhogs-1); - angle_t angleOffset = cone; - INT32 i; + K_SetItemOut(player); // need this to set itemscale player->itemamount -= numhogs; - - for (i = 0; i < numhogs; i++) - { - K_ThrowKartItem(player, true, MT_BALLHOG, 1, 0, angleOffset); - angleOffset -= offsetAmt; - } - K_PlayAttackTaunt(player->mo); + K_DoBallhogAttack(player, numhogs); + + K_UnsetItemOut(player); } - K_UnsetItemOut(player); player->ballhogcharge = 0; player->itemflags &= ~IF_HOLDREADY; player->botvars.itemconfirm = 0; } + else + { + if (cmd->buttons & BT_ATTACK) + { + player->itemflags &= ~IF_HOLDREADY; + } + else + { + player->itemflags |= IF_HOLDREADY; + } + } } } break; @@ -14379,6 +14377,12 @@ void K_MoveKartPlayer(player_t *player, boolean onground) Obj_PlayerBulbThink(player); + UINT8 hog_count = 0; + if (player->itemtype == KITEM_BALLHOG) + { + hog_count = K_HogChargeToHogCount(player->ballhogcharge, player->itemamount); + } + K_UpdateBallhogReticules(player, hog_count, false); } void K_CheckSpectateStatus(boolean considermapreset) diff --git a/src/k_kart.h b/src/k_kart.h index a272eb7b2..e61767b2a 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -177,6 +177,7 @@ void K_SpawnDraftDust(mobj_t *mo); void K_SpawnMagicianParticles(mobj_t *mo, int spread); void K_DriftDustHandling(mobj_t *spawner); void K_Squish(mobj_t *mo); +mobj_t *K_ThrowKartItemEx(player_t *player, boolean missile, mobjtype_t mapthing, INT32 defaultDir, INT32 altthrow, angle_t angleOffset, fixed_t tossX, fixed_t tossY); mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t mapthing, INT32 defaultDir, INT32 altthrow, angle_t angleOffset); void K_PuntMine(mobj_t *mine, mobj_t *punter); void K_DoSneaker(player_t *player, INT32 type); diff --git a/src/k_objects.h b/src/k_objects.h index dbc6db7e0..cbc2f320e 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -64,8 +64,8 @@ mobj_t *Obj_MantaRingCreate(mobj_t *spb, mobj_t *owner, mobj_t *chase); /* Orbinaut */ void Obj_OrbinautThink(mobj_t *th); boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2); -void Obj_OrbinautThrown(mobj_t *th, fixed_t finalSpeed, SINT8 dir); -void Obj_GachaBomThrown(mobj_t *th, fixed_t finalSpeed, SINT8 dir); +void Obj_OrbinautThrown(mobj_t *th, fixed_t finalSpeed, fixed_t dir); +void Obj_GachaBomThrown(mobj_t *th, fixed_t finalSpeed, fixed_t dir); void Obj_OrbinautJawzMoveHeld(player_t *player); boolean Obj_GachaBomWasTossed(mobj_t *th); void Obj_OrbinautDrop(mobj_t *th); @@ -73,7 +73,7 @@ boolean Obj_OrbinautCanRunOnWater(mobj_t *th); /* Jawz */ void Obj_JawzThink(mobj_t *th); -void Obj_JawzThrown(mobj_t *th, fixed_t finalSpeed, SINT8 dir); +void Obj_JawzThrown(mobj_t *th, fixed_t finalSpeed, fixed_t dir); /* Duel Bomb */ void Obj_DuelBombThink(mobj_t *bomb); @@ -439,6 +439,11 @@ void Obj_DestroyedKartParticleLanding(mobj_t *part); void Obj_PulleyThink(mobj_t *root); void Obj_PulleyHookTouch(mobj_t *special, mobj_t *toucher); +/* Ballhog */ +UINT8 K_HogChargeToHogCount(INT32 charge, UINT8 cap); +void K_UpdateBallhogReticules(player_t *player, UINT8 num_hogs, boolean on_release); +void K_DoBallhogAttack(player_t *player, UINT8 num_hogs); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/objects/CMakeLists.txt b/src/objects/CMakeLists.txt index 487ce232f..593d5b8f6 100644 --- a/src/objects/CMakeLists.txt +++ b/src/objects/CMakeLists.txt @@ -59,6 +59,7 @@ target_sources(SRB2SDL2 PRIVATE destroyed-kart.cpp pulley.cpp amps.c + ballhog.cpp ) add_subdirectory(versus) diff --git a/src/objects/ballhog.cpp b/src/objects/ballhog.cpp new file mode 100644 index 000000000..e57d19273 --- /dev/null +++ b/src/objects/ballhog.cpp @@ -0,0 +1,383 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2024 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. +//----------------------------------------------------------------------------- +/// \file ballhog.cpp +/// \brief Ballhog item code. + +#include "../doomdef.h" +#include "../doomstat.h" +#include "../info.h" +#include "../k_kart.h" +#include "../k_objects.h" +#include "../m_random.h" +#include "../p_local.h" +#include "../r_main.h" +#include "../s_sound.h" +#include "../g_game.h" +#include "../z_zone.h" +#include "../k_collide.h" + +namespace +{ + +struct hog_angles +{ + fixed_t x, y; + hog_angles(fixed_t nx, fixed_t ny) : x(nx), y(ny) {} +}; + +std::vector g_hogangles; + +}; + +static void CalculateHogAngles(UINT8 n) +{ + const UINT8 total_hogs = n; + + // This algorithm should probably be replaced to + // maximize more space covered, but the desired effect + // is achieved for 1 to 5, which is the vast majority of uses. + g_hogangles.clear(); + + if (total_hogs == 0) + { + return; + } + + if (total_hogs == 1 || total_hogs > 4) + { + // Add a point to the exact middle. + g_hogangles.emplace_back(0, 0); + n--; + } + + if (total_hogs > 1) + { + const fixed_t base_radius = mobjinfo[MT_BALLHOG].radius * 3 / 2; + fixed_t radius = base_radius; + UINT8 max_points = 6; + angle_t circle_offset = 0; + + if (total_hogs < 5) + { + // Reduce size to get more space covered. + radius /= 2; + max_points = 4; + } + + while (n > 0) + { + const UINT8 add_points = std::min(n, max_points); + + angle_t angle = circle_offset; + const angle_t angle_offset = ANGLE_MAX / add_points; + + for (UINT8 c = 0; c < add_points; c++) + { + g_hogangles.emplace_back( + FixedMul(FINECOSINE( angle >> ANGLETOFINESHIFT ), radius), + FixedMul( FINESINE( angle >> ANGLETOFINESHIFT ), radius) + ); + angle += angle_offset; + n--; + } + + radius += base_radius; + circle_offset += ANGLE_MAX / max_points; + max_points += (max_points / 2); + } + } +} + +UINT8 K_HogChargeToHogCount(INT32 charge, UINT8 cap) +{ + return std::clamp((charge / BALLHOGINCREMENT), 0, cap); +} + +static boolean HogReticuleEmulate(mobj_t *mobj) +{ + fixed_t x, y, z; + + //I_Assert(mobj != NULL); + //I_Assert(P_MobjWasRemoved(mobj) == false); + + x = mobj->x, y = mobj->y, z = mobj->z; + + //if (mobj->momx || mobj->momy) + { + if (P_XYMovement(mobj) == false) + { + return true; + } + + if (P_MobjWasRemoved(mobj) == true) + { + return true; + } + } + + //if (mobj->momz) + { + if (P_ZMovement(mobj) == false) + { + return true; // mobj was removed + } + + //P_CheckPosition(mobj, mobj->x, mobj->y, NULL); + } + + return (P_MobjWasRemoved(mobj) == true || (x == mobj->x && y == mobj->y && z == mobj->z)); +} + +static void HogReticuleTest(player_t *player, vector3_t *ret) +{ + // Emulate the movement of a tossed ballhog + // until it hits something. + + fixed_t dir = FRACUNIT; + if (player->throwdir == 1) + { + dir = 2*FRACUNIT; + } + else if (player->throwdir == -1) + { + dir = FRACUNIT/2; + } + + // Use pre-determined speed for tossing + fixed_t proj_speed = FixedMul(82 * FRACUNIT, K_GetKartGameSpeedScalar(gamespeed)); + + // Scale to map scale + // Intentionally NOT player scale, that doesn't work. + proj_speed = FixedMul(proj_speed, mapobjectscale); + + fixed_t finalscale = ITEMSCALE_NORMAL; + if (player->mo->scale >= FixedMul(GROW_PHYSICS_SCALE, mapobjectscale)) + { + finalscale = ITEMSCALE_GROW; + } + else if (player->mo->scale <= FixedMul(SHRINK_PHYSICS_SCALE, mapobjectscale)) + { + finalscale = ITEMSCALE_SHRINK; + } + + // Shoot forward + //P_MoveOrigin(mo, player->mo->x, player->mo->y, player->mo->z + (player->mo->height / 2)); + // FINE! YOU WIN! I'll make an object every time I need to test this... + mobj_t *mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + (player->mo->height / 2), MT_BALLHOG_RETICULE_TEST); + mo->fuse = 2; // if something goes wrong, destroy this + + mo->angle = player->mo->angle; + + // These are really weird so let's make it a very specific case to make SURE it works... + if (player->mo->eflags & MFE_VERTICALFLIP) + { + mo->z -= player->mo->height; + mo->eflags |= MFE_VERTICALFLIP; + mo->flags2 |= (player->mo->flags2 & MF2_OBJECTFLIP); + } + + //P_SetTarget(&mo->target, player->mo); + + mo->extravalue2 = dir; + + fixed_t proj_speed_z = ((20 * FRACUNIT) + (10 * dir)) + (FixedDiv(player->mo->momz, mapobjectscale) * P_MobjFlip(player->mo)); // Also intentionally not player scale + P_SetObjectMomZ(mo, proj_speed_z, false); + + angle_t fa = (player->mo->angle >> ANGLETOFINESHIFT); + mo->momx = player->mo->momx + FixedMul(FINECOSINE(fa), FixedMul(proj_speed, dir)); + mo->momy = player->mo->momy + FixedMul( FINESINE(fa), FixedMul(proj_speed, dir)); + + if (mo->eflags & MFE_UNDERWATER) + { + mo->momz = (117 * mo->momz) / 200; + } + + P_SetScale(mo, finalscale); + mo->destscale = finalscale; + + // Contra spread shot scale up + //mo->destscale = mo->destscale << 1; + //mo->scalespeed = abs(mo->destscale - mo->scale) / (2*TICRATE); + + ret->x = mo->x; + ret->y = mo->y; + ret->z = mo->z; + + constexpr INT32 max_iteration = 256; + for (INT32 i = 0; i < max_iteration; i++) + { + if (HogReticuleEmulate(mo) == true) + { + break; + } + + ret->x = mo->x; + ret->y = mo->y; + ret->z = mo->z; + } + + P_RemoveMobj(mo); +} + +void K_UpdateBallhogReticules(player_t *player, UINT8 num_hogs, boolean on_release) +{ + if (player == nullptr) + { + return; + } + + if (player->mo == nullptr || P_MobjWasRemoved(player->mo) == true) + { + return; + } + + constexpr tic_t kBallhogReticuleTime = TICRATE / 2; + + const UINT8 start_hogs = num_hogs; + CalculateHogAngles(num_hogs); + + // Calculate center positon + vector3_t center = {0, 0, 0}; + HogReticuleTest(player, ¢er); // Originally this was called for everything, but it's more optimized to only run 1 prediction. + + // Update existing reticules. + mobj_t *reticule = player->ballhogreticule; + while (reticule != nullptr && P_MobjWasRemoved(reticule) == false) + { + mobj_t *next = reticule->hnext; + boolean removed = false; + + if (num_hogs > 0) + { + const UINT8 old_hogs = reticule->extravalue1; + + UINT8 angle_index = num_hogs - 1; + fixed_t x_offset = g_hogangles[angle_index].x; + fixed_t y_offset = g_hogangles[angle_index].y; + + if (on_release == true) + { + reticule->extravalue1 = start_hogs; + P_MoveOrigin( + reticule, + center.x + x_offset, + center.y + y_offset, + center.z + ); + } + else + { + if (start_hogs != old_hogs) + { + // Reset to the middle + P_SetOrigin(reticule, center.x, center.y, center.z); + reticule->extravalue1 = start_hogs; + } + + // Move to new position + P_MoveOrigin( + reticule, + reticule->x + (((center.x + x_offset) - reticule->x) / (BALLHOGINCREMENT / 2)), + reticule->y + (((center.y + y_offset) - reticule->y) / (BALLHOGINCREMENT / 2)), + reticule->z + ((center.z - reticule->z) / (BALLHOGINCREMENT / 2)) + ); + } + + reticule->tics = kBallhogReticuleTime; + num_hogs--; + } +#if 0 + else + { + // Too many reticules exist, so remove the remainder. + P_RemoveMobj(reticule); + removed = true; + } +#endif + + if (next == nullptr || P_MobjWasRemoved(next) == true) + { + break; + } + + if (removed == true) + { + P_SetTarget(&next->hprev, nullptr); + } + reticule = next; + } + + // Not enough reticules exist, so make new ones. + while (num_hogs > 0) + { + mobj_t *new_reticule = P_SpawnMobjFromMobj((reticule != nullptr) ? reticule : player->mo, 0, 0, 0, MT_BALLHOG_RETICULE); + if (new_reticule == nullptr) + { + break; + } + + if (reticule != nullptr) + { + P_SetTarget(&reticule->hnext, new_reticule); + P_SetTarget(&new_reticule->hprev, reticule); + } + else + { + P_SetTarget(&player->ballhogreticule, new_reticule); + P_SetTarget(&new_reticule->hprev, player->mo); + } + + // Start in center + new_reticule->extravalue1 = start_hogs; + new_reticule->tics = kBallhogReticuleTime; + + if (on_release == true) + { + UINT8 angle_index = num_hogs - 1; + fixed_t x_offset = g_hogangles[angle_index].x; + fixed_t y_offset = g_hogangles[angle_index].y; + + P_SetOrigin(new_reticule, center.x + x_offset, center.y + y_offset, center.z); + } + else + { + P_SetOrigin(new_reticule, center.x, center.y, center.z); + } + + reticule = new_reticule; + num_hogs--; + } +} + +void K_DoBallhogAttack(player_t *player, UINT8 num_hogs) +{ + // Update reticules instantly, then untie them to us. + K_UpdateBallhogReticules(player, num_hogs, true); + P_SetTarget(&player->ballhogreticule, nullptr); + + CalculateHogAngles(num_hogs); + + while (num_hogs > 0) + { + UINT8 angle_index = num_hogs - 1; + + fixed_t x_offset = g_hogangles[angle_index].x; + fixed_t y_offset = g_hogangles[angle_index].y; + + K_ThrowKartItemEx( + player, + false, MT_BALLHOG, 1, 2, + 0, + x_offset, y_offset + ); + + num_hogs--; + } +} diff --git a/src/objects/jawz.c b/src/objects/jawz.c index f34da68f2..ee91462fa 100644 --- a/src/objects/jawz.c +++ b/src/objects/jawz.c @@ -253,7 +253,7 @@ void Obj_JawzThink(mobj_t *th) } } -void Obj_JawzThrown(mobj_t *th, fixed_t finalSpeed, SINT8 dir) +void Obj_JawzThrown(mobj_t *th, fixed_t finalSpeed, fixed_t dir) { INT32 lastTarg = -1; player_t *owner = NULL; @@ -270,7 +270,7 @@ void Obj_JawzThrown(mobj_t *th, fixed_t finalSpeed, SINT8 dir) jawz_retcolor(th) = SKINCOLOR_KETCHUP; } - if (dir == -1) + if (dir < 0) { // Thrown backwards, init self-chase P_SetTarget(&jawz_chase(th), jawz_owner(th)); diff --git a/src/objects/orbinaut.c b/src/objects/orbinaut.c index 90c028325..7c47cb30b 100644 --- a/src/objects/orbinaut.c +++ b/src/objects/orbinaut.c @@ -323,7 +323,7 @@ boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2) return true; } -void Obj_OrbinautThrown(mobj_t *th, fixed_t finalSpeed, SINT8 dir) +void Obj_OrbinautThrown(mobj_t *th, fixed_t finalSpeed, fixed_t dir) { orbinaut_flags(th) = 0; @@ -335,7 +335,7 @@ void Obj_OrbinautThrown(mobj_t *th, fixed_t finalSpeed, SINT8 dir) const mobj_t *owner = orbinaut_owner(th); const ffloor_t *rover = P_IsObjectFlipped(owner) ? owner->ceilingrover : owner->floorrover; - if (dir != -1 && rover && (rover->fofflags & FOF_SWIMMABLE)) + if (dir >= 0 && rover && (rover->fofflags & FOF_SWIMMABLE)) { // The owner can run on water, so we should too! orbinaut_flags(th) |= ORBI_WATERSKI; @@ -351,7 +351,7 @@ void Obj_OrbinautThrown(mobj_t *th, fixed_t finalSpeed, SINT8 dir) orbinaut_flags(th) |= ORBI_TRAIL; - if (dir == -1) + if (dir < 0) { // Thrown backwards, init orbiting in place orbinaut_turn(th) = ORBINAUT_MAXTURN / ORBINAUT_TURNLERP; @@ -362,21 +362,19 @@ void Obj_OrbinautThrown(mobj_t *th, fixed_t finalSpeed, SINT8 dir) } } -void Obj_GachaBomThrown(mobj_t *th, fixed_t finalSpeed, SINT8 dir) +void Obj_GachaBomThrown(mobj_t *th, fixed_t finalSpeed, fixed_t dir) { Obj_OrbinautThrown(th, finalSpeed, dir); orbinaut_flags(th) &= ~(ORBI_TRAIL); - switch (dir) + if (dir < 0) { - case -1: - orbinaut_flags(th) |= ORBI_SPIN; - break; - - case 1: - orbinaut_flags(th) |= ORBI_TOSSED; - break; + orbinaut_flags(th) |= ORBI_SPIN; + } + else if (dir > 0) + { + orbinaut_flags(th) |= ORBI_TOSSED; } } diff --git a/src/p_mobj.c b/src/p_mobj.c index a5020b944..3c0a2d81f 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -594,18 +594,19 @@ void P_ExplodeMissile(mobj_t *mo) mo->momx = mo->momy = mo->momz = 0; - if (mo->flags & MF_NOCLIPTHING) + if (mo->flags2 & MF2_BOSSDEAD) return; mo->flags &= ~MF_MISSILE; mo->flags |= MF_NOGRAVITY; // Dead missiles don't need to sink anymore. - mo->flags |= MF_NOCLIPTHING; // Dummy flag to indicate that this was already called. + mo->flags2 |= MF2_BOSSDEAD; // Dummy flag to indicate that this was already called. if (mo->info->deathsound && !(mo->flags2 & MF2_DEBRIS)) S_StartSound(mo, mo->info->deathsound); P_SetMobjState(mo, mo->info->deathstate); + mo->health = 0; } // P_InsideANonSolidFFloor @@ -1059,6 +1060,8 @@ static boolean P_UseUnderwaterGravity(mobj_t *mo) switch (mo->type) { case MT_BANANA: + case MT_BALLHOG: + case MT_BALLHOG_RETICULE_TEST: return false; case MT_GACHABOM: @@ -1247,6 +1250,8 @@ fixed_t P_GetMobjGravity(mobj_t *mo) gravityadd = (5*gravityadd)/2; break; case MT_BANANA: + case MT_BALLHOG: + case MT_BALLHOG_RETICULE_TEST: case MT_EGGMANITEM: case MT_SSMINE: case MT_LANDMINE: @@ -1255,9 +1260,9 @@ fixed_t P_GetMobjGravity(mobj_t *mo) case MT_EMERALD: if (mo->health > 0) { - if (mo->extravalue2 > 0) + if (mo->extravalue2 > FRACUNIT) { - gravityadd *= mo->extravalue2; + gravityadd = FixedMul(gravityadd, mo->extravalue2); } gravityadd = (5*gravityadd)/2; @@ -1600,7 +1605,7 @@ static boolean P_CheckSkyHit(mobj_t *mo) // // P_XYMovement // -void P_XYMovement(mobj_t *mo) +boolean P_XYMovement(mobj_t *mo) { player_t *player; fixed_t xmove, ymove; @@ -1626,7 +1631,7 @@ void P_XYMovement(mobj_t *mo) // set in 'search new direction' state? P_SetMobjState(mo, mo->info->spawnstate); - return; + return false; } } @@ -1676,21 +1681,6 @@ void P_XYMovement(mobj_t *mo) if (CheckForBustableBlocks && ((mo->flags & MF_PUSHABLE) || ((mo->info->flags & MF_PUSHABLE) && mo->fuse))) P_PushableCheckBustables(mo); - //{ SRB2kart - Ballhogs - if (mo->type == MT_BALLHOG) - { - if (mo->health) - { - mo->health--; - if (mo->health == 0) - { - mo->scalespeed = mo->scale/12; - mo->destscale = 0; - } - } - } - //} - if (!P_TryMove(mo, mo->x + xmove, mo->y + ymove, true, &result) && !(P_MobjWasRemoved(mo) || mo->eflags & MFE_SPRUNG)) { @@ -1700,13 +1690,18 @@ void P_XYMovement(mobj_t *mo) if (LUA_HookMobjMoveBlocked(mo, g_tm.hitthing, result.line)) { if (P_MobjWasRemoved(mo)) - return; + return false; } else if (P_MobjWasRemoved(mo)) - return; + return false; P_PushSpecialLine(result.line, mo); + if (mo->type == MT_BALLHOG_RETICULE_TEST) + { + return false; + } + if (mo->flags & MF_MISSILE) { // explode a missile @@ -1718,7 +1713,7 @@ void P_XYMovement(mobj_t *mo) // Check frontsector as well. P_RemoveMobj(mo); - return; + return false; } // draw damage on wall @@ -1745,7 +1740,7 @@ void P_XYMovement(mobj_t *mo) // --------------------------------------------------------- SPLAT TEST P_ExplodeMissile(mo); - return; + return false; } else { @@ -1807,14 +1802,14 @@ void P_XYMovement(mobj_t *mo) { P_SlideMove(mo, &result); if (P_MobjWasRemoved(mo)) - return; + return false; xmove = ymove = 0; } else { P_BounceMove(mo, &result); if (P_MobjWasRemoved(mo)) - return; + return false; xmove = ymove = 0; S_StartSound(mo, mo->info->activesound); @@ -1822,9 +1817,7 @@ void P_XYMovement(mobj_t *mo) // Ballhog dies on contact with walls if (mo->type == MT_BALLHOG) { - S_StartSound(mo, mo->info->deathsound); - P_KillMobj(mo, NULL, NULL, DMG_NORMAL); - return; + P_ExplodeMissile(mo); } // Bump sparks else if (mo->type == MT_ORBINAUT || mo->type == MT_GACHABOM) @@ -1886,7 +1879,7 @@ void P_XYMovement(mobj_t *mo) moved = true; if (P_MobjWasRemoved(mo)) // MF_SPECIAL touched a player! O_o;; - return; + return false; if (moved == true) { @@ -1963,29 +1956,30 @@ void P_XYMovement(mobj_t *mo) P_CheckGravity(mo, false); if (mo->flags & MF_NOCLIPHEIGHT) - return; // no frictions for objects that can pass through floors + return moved; // no frictions for objects that can pass through floors if (mo->flags & MF_MISSILE || mo->flags2 & MF2_SKULLFLY) - return; // no friction for missiles ever + return moved; // no friction for missiles ever if ((mo->type == MT_BIGTUMBLEWEED || mo->type == MT_LITTLETUMBLEWEED) && (mo->standingslope && abs(mo->standingslope->zdelta) > FRACUNIT>>8)) // Special exception for tumbleweeds on slopes - return; + return moved; //{ SRB2kart stuff - if (mo->type == MT_FLINGRING || mo->type == MT_BALLHOG || mo->type == MT_BUBBLESHIELDTRAP) - return; + if (mo->type == MT_FLINGRING || mo->type == MT_BALLHOG || mo->type == MT_BALLHOG_RETICULE_TEST || mo->type == MT_BUBBLESHIELDTRAP) + return moved; if (player && (player->spinouttimer && !player->wipeoutslow) && player->speed <= FixedDiv(20*mapobjectscale, player->offroad + FRACUNIT)) - return; + return moved; //} if (((!(mo->eflags & MFE_VERTICALFLIP) && (mo->momz > 0 || mo->z > mo->floorz)) || (mo->eflags & MFE_VERTICALFLIP && (mo->momz < 0 || mo->z+mo->height < mo->ceilingz))) && !(player && player->carry == CR_SLIDING)) - return; // no friction when airborne + return moved; // no friction when airborne P_XYFriction(mo, oldx, oldy); + return moved; } void P_RingXYMovement(mobj_t *mo) @@ -2328,6 +2322,19 @@ boolean P_ZMovement(mobj_t *mo) return false; } break; + case MT_BALLHOG: + if (mo->z <= mo->floorz || mo->z + mo->height >= mo->ceilingz) + { + P_ExplodeMissile(mo); + return false; + } + break; + case MT_BALLHOG_RETICULE_TEST: + if (mo->z <= mo->floorz || mo->z + mo->height >= mo->ceilingz) + { + return false; + } + break; default: // SRB2kart stuff that should die in pits // Shouldn't stop moving along the Z if there's no speed though! @@ -2615,7 +2622,7 @@ boolean P_ZMovement(mobj_t *mo) if (((mo->z + mo->height > mo->ceilingz && !(mo->eflags & MFE_VERTICALFLIP)) || (mo->z < mo->floorz && mo->eflags & MFE_VERTICALFLIP)) - && !(mo->flags & MF_NOCLIPHEIGHT)) + && !(mo->flags & MF_NOCLIPHEIGHT)) { if (mo->eflags & MFE_VERTICALFLIP) mo->z = mo->floorz; @@ -10364,8 +10371,8 @@ void P_MobjThinker(mobj_t *mobj) if (mobj->health > 0 && P_MobjTouchingSectorSpecialFlag(mobj, SSF_DELETEITEMS)) { if (mobj->type == MT_SSMINE - || mobj->type == MT_BUBBLESHIELDTRAP - || mobj->type == MT_BALLHOG) + || mobj->type == MT_BUBBLESHIELDTRAP + || mobj->type == MT_BALLHOG) { S_StartSound(mobj, mobj->info->deathsound); P_KillMobj(mobj, NULL, NULL, DMG_NORMAL); diff --git a/src/p_mobj.h b/src/p_mobj.h index f0f5a19ad..aa8efc687 100644 --- a/src/p_mobj.h +++ b/src/p_mobj.h @@ -578,7 +578,7 @@ void P_NullPrecipThinker(precipmobj_t *mobj); void P_FreePrecipMobj(precipmobj_t *mobj); void P_SetScale(mobj_t *mobj, fixed_t newscale); void P_InstaScale(mobj_t *mobj, fixed_t newscale); -void P_XYMovement(mobj_t *mo); +boolean P_XYMovement(mobj_t *mo); void P_RingXYMovement(mobj_t *mo); void P_SceneryXYMovement(mobj_t *mo); boolean P_ZMovement(mobj_t *mo); diff --git a/src/p_saveg.c b/src/p_saveg.c index ac45276b5..9a642cc25 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -90,6 +90,7 @@ typedef enum FLICKYCONTROLLER = 0x1000, TRICKINDICATOR = 0x2000, BARRIER = 0x4000, + BALLHOGRETICULE = 0x8000, // uh oh, we're full now... } player_saveflags; static inline void P_ArchivePlayer(savebuffer_t *save) @@ -325,6 +326,9 @@ static void P_NetArchivePlayers(savebuffer_t *save) if (players[i].hoverhyudoro) flags |= HOVERHYUDORO; + if (players[i].ballhogreticule) + flags |= BALLHOGRETICULE; + if (players[i].stumbleIndicator) flags |= STUMBLE; @@ -369,6 +373,9 @@ static void P_NetArchivePlayers(savebuffer_t *save) if (flags & HOVERHYUDORO) WRITEUINT32(save->p, players[i].hoverhyudoro->mobjnum); + if (flags & BALLHOGRETICULE) + WRITEUINT32(save->p, players[i].ballhogreticule->mobjnum); + if (flags & STUMBLE) WRITEUINT32(save->p, players[i].stumbleIndicator->mobjnum); @@ -989,6 +996,9 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) if (flags & HOVERHYUDORO) players[i].hoverhyudoro = (mobj_t *)(size_t)READUINT32(save->p); + if (flags & BALLHOGRETICULE) + players[i].ballhogreticule = (mobj_t *)(size_t)READUINT32(save->p); + if (flags & STUMBLE) players[i].stumbleIndicator = (mobj_t *)(size_t)READUINT32(save->p); @@ -5971,6 +5981,11 @@ static void P_RelinkPointers(void) if (!RelinkMobj(&players[i].hoverhyudoro)) CONS_Debug(DBG_GAMELOGIC, "hoverhyudoro not found on player %d\n", i); } + if (players[i].ballhogreticule) + { + if (!RelinkMobj(&players[i].ballhogreticule)) + CONS_Debug(DBG_GAMELOGIC, "ballhogreticule not found on player %d\n", i); + } if (players[i].stumbleIndicator) { if (!RelinkMobj(&players[i].stumbleIndicator)) diff --git a/src/p_user.c b/src/p_user.c index 1f25f9ff4..c39b105bd 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -4244,6 +4244,7 @@ void P_PlayerThink(player_t *player) PlayerPointerErase(player->hand); PlayerPointerErase(player->ringShooter); PlayerPointerErase(player->hoverhyudoro); + PlayerPointerErase(player->ballhogreticule); PlayerPointerErase(player->flickyAttacker); PlayerPointerErase(player->powerup.flickyController); PlayerPointerErase(player->powerup.barrier);