New Ballhog design

Taking the old charge idea, but instead of missiles, it's bananas that explode on the floor and can only be lobbed forward. Charging creates more projectiles in a circle pattern around it.
This commit is contained in:
Sally Coolatta 2024-08-27 18:51:54 -04:00
parent c0bc1cc245
commit 03874c3374
17 changed files with 642 additions and 161 deletions

View file

@ -70,6 +70,7 @@
#include "sanitize.h" #include "sanitize.h"
#include "r_fps.h" #include "r_fps.h"
#include "filesrch.h" // refreshdirmenu #include "filesrch.h" // refreshdirmenu
#include "k_objects.h"
// cl loading screen // cl loading screen
#include "v_video.h" #include "v_video.h"
@ -2510,6 +2511,7 @@ void CL_ClearPlayer(INT32 playernum)
P_SetTarget(&players[playernum].whip, NULL); P_SetTarget(&players[playernum].whip, NULL);
P_SetTarget(&players[playernum].hand, NULL); P_SetTarget(&players[playernum].hand, NULL);
P_SetTarget(&players[playernum].hoverhyudoro, NULL); P_SetTarget(&players[playernum].hoverhyudoro, NULL);
P_SetTarget(&players[playernum].ballhogreticule, NULL);
P_SetTarget(&players[playernum].ringShooter, NULL); P_SetTarget(&players[playernum].ringShooter, NULL);
// TODO: Any better handling in store? // TODO: Any better handling in store?

View file

@ -787,6 +787,7 @@ struct player_t
UINT16 ballhogcharge; // Ballhog charge up -- the higher this value, the more projectiles UINT16 ballhogcharge; // Ballhog charge up -- the higher this value, the more projectiles
boolean ballhogtap; // Ballhog released during charge: used to allow semirapid tapfire 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 UINT16 hyudorotimer; // Duration of the Hyudoro offroad effect itself
SINT8 stealingtimer; // if >0 you are stealing, if <0 you are being stolen from SINT8 stealingtimer; // if >0 you are stealing, if <0 you are being stolen from

View file

@ -1922,6 +1922,7 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi
"S_BALLHOGBOOM14", "S_BALLHOGBOOM14",
"S_BALLHOGBOOM15", "S_BALLHOGBOOM15",
"S_BALLHOGBOOM16", "S_BALLHOGBOOM16",
"S_BALLHOG_RETICULE",
// Self-Propelled Bomb - just an explosion for now... // Self-Propelled Bomb - just an explosion for now...
"S_SPB1", "S_SPB1",
@ -3585,6 +3586,8 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t
"MT_BALLHOG", // Ballhog "MT_BALLHOG", // Ballhog
"MT_BALLHOGBOOM", "MT_BALLHOGBOOM",
"MT_BALLHOG_RETICULE",
"MT_BALLHOG_RETICULE_TEST",
"MT_SPB", // Self-Propelled Bomb "MT_SPB", // Self-Propelled Bomb
"MT_SPBEXPLOSION", "MT_SPBEXPLOSION",

View file

@ -2429,6 +2429,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
hoverhyudoro = players[player].hoverhyudoro; hoverhyudoro = players[player].hoverhyudoro;
skyboxviewpoint = players[player].skybox.viewpoint; skyboxviewpoint = players[player].skybox.viewpoint;
skyboxcenterpoint = players[player].skybox.centerpoint; skyboxcenterpoint = players[player].skybox.centerpoint;
K_UpdateBallhogReticules(&players[player], 0, false);
} }
else else
{ {

View file

@ -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|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|14, 1, {NULL}, 0, 0, S_BALLHOGBOOM16}, // S_BALLHOGBOOM15
{SPR_BHBM, FF_FULLBRIGHT|15, 1, {NULL}, 0, 0, S_NULL}, // S_BALLHOGBOOM16 {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, 0, 1, {NULL}, 0, 0, S_SPB2}, // S_SPB1
{SPR_SPBM, 1, 1, {NULL}, 0, 0, S_SPB3}, // S_SPB2 {SPR_SPBM, 1, 1, {NULL}, 0, 0, S_SPB3}, // S_SPB2
@ -15129,9 +15130,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
{ // MT_BALLHOG { // MT_BALLHOG
-1, // doomednum -1, // doomednum
S_BALLHOG1, // spawnstate S_BALLHOG1, // spawnstate
140, // spawnhealth 1, // spawnhealth
S_NULL, // seestate S_NULL, // seestate
sfx_None, // seesound sfx_tossed, // seesound
8, // reactiontime 8, // reactiontime
sfx_None, // attacksound sfx_None, // attacksound
S_NULL, // painstate S_NULL, // painstate
@ -15142,7 +15143,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
S_BALLHOG_DEAD, // deathstate S_BALLHOG_DEAD, // deathstate
S_NULL, // xdeathstate S_NULL, // xdeathstate
sfx_hogbom, // deathsound sfx_hogbom, // deathsound
40*FRACUNIT, // speed 0, // speed
26*FRACUNIT, // radius 26*FRACUNIT, // radius
64*FRACUNIT, // height 64*FRACUNIT, // height
0, // display offset 0, // display offset
@ -15176,7 +15177,61 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
100, // mass 100, // mass
1, // damage 1, // damage
sfx_None, // activesound 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 S_NULL // raisestate
}, },

View file

@ -2949,6 +2949,7 @@ typedef enum state
S_BALLHOGBOOM14, S_BALLHOGBOOM14,
S_BALLHOGBOOM15, S_BALLHOGBOOM15,
S_BALLHOGBOOM16, S_BALLHOGBOOM16,
S_BALLHOG_RETICULE,
// Self-Propelled Bomb // Self-Propelled Bomb
S_SPB1, S_SPB1,
@ -4639,6 +4640,8 @@ typedef enum mobj_type
MT_BALLHOG, // Ballhog MT_BALLHOG, // Ballhog
MT_BALLHOGBOOM, MT_BALLHOGBOOM,
MT_BALLHOG_RETICULE,
MT_BALLHOG_RETICULE_TEST,
MT_SPB, // SPB stuff MT_SPB, // SPB stuff
MT_SPBEXPLOSION, MT_SPBEXPLOSION,

View file

@ -556,7 +556,7 @@ UINT8 K_ItemResultToAmount(SINT8 getitem, const itemroulette_t *roulette)
return 4; return 4;
case KITEM_BALLHOG: // Not a special result, but has a special amount case KITEM_BALLHOG: // Not a special result, but has a special amount
return 5; return 7;
case KITEM_SUPERRING: case KITEM_SUPERRING:
if (roulette && roulette->popcorn) if (roulette && roulette->popcorn)
@ -3157,7 +3157,6 @@ boolean K_WaterSkip(mobj_t *mobj)
case MT_ORBINAUT: case MT_ORBINAUT:
case MT_JAWZ: case MT_JAWZ:
case MT_BALLHOG:
{ {
// Allow // Allow
break; break;
@ -5501,7 +5500,7 @@ fixed_t K_DefaultPlayerRadius(player_t *player)
player->mo->info->radius); 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; mobj_t *th;
fixed_t x, y, z; 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; finalscale = source->scale;
} }
if (dir == -1) if (dir < 0)
{ {
fixed_t nerf = FRACUNIT; 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; nerf = FRACUNIT/4;
break; break;
case MT_BALLHOG:
nerf = FRACUNIT/8;
break;
default: default:
break; 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_s3kbfl);
S_StartSound(th, sfx_cdfm35); S_StartSound(th, sfx_cdfm35);
break; 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: case MT_GARDENTOP:
th->movefactor = finalspeed; th->movefactor = finalspeed;
break; break;
@ -6488,10 +6478,10 @@ static mobj_t *K_FindLastTrailMobj(player_t *player)
return trail; 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; mobj_t *mo;
INT32 dir; fixed_t dir = FRACUNIT;
fixed_t PROJSPEED; fixed_t PROJSPEED;
angle_t newangle; angle_t newangle;
fixed_t newx, newy, newz; 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 (altthrow == 2) // Kitchen sink throwing
{ {
#if 0
if (player->throwdir == 1) if (player->throwdir == 1)
dir = 3; dir = 2 * FRACUNIT;
else if (player->throwdir == -1) else if (player->throwdir == -1)
dir = 1; dir = FRACUNIT / 2;
else else
dir = 2; dir = FRACUNIT;
#else
if (player->throwdir == 1)
dir = 2;
else
dir = 1;
#endif
} }
else else
{ {
if (player->throwdir == 1) if (player->throwdir == 1)
dir = 2; dir = 2 * FRACUNIT;
else if (player->throwdir == -1) else if (player->throwdir == -1)
dir = -1; dir = -FRACUNIT;
else else
dir = 1; dir = FRACUNIT;
} }
} }
else else
{ {
if (player->throwdir != 0) if (player->throwdir != 0)
dir = player->throwdir; dir = player->throwdir * FRACUNIT;
else else
dir = defaultDir; dir = defaultDir * FRACUNIT;
} }
if (mapthing == MT_GACHABOM && dir > 0) 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); 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; 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) if (mo->eflags & MFE_UNDERWATER)
mo->momz = (117 * mo->momz) / 200; mo->momz = (117 * mo->momz) / 200;
P_SetScale(mo, finalscale); P_SetScale(mo, finalscale);
mo->destscale = finalscale; mo->destscale = finalscale;
if (mapthing == MT_BANANA) switch (mapthing)
{ {
mo->angle = FixedAngle(P_RandomRange(PR_DECORATION, -180, 180) << FRACBITS); case MT_BANANA:
mo->rollangle = FixedAngle(P_RandomRange(PR_DECORATION, -180, 180) << FRACBITS); mo->angle = FixedAngle(P_RandomRange(PR_DECORATION, -180, 180) << FRACBITS);
} mo->rollangle = FixedAngle(P_RandomRange(PR_DECORATION, -180, 180) << FRACBITS);
break;
if (mapthing == MT_GACHABOM) case MT_GACHABOM:
{ Obj_GachaBomThrown(mo, mo->radius, dir);
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: // 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 // 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; mo->cvmem = 1;
} }
@ -6736,6 +6744,11 @@ mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t mapthing,
return mo; 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) void K_PuntMine(mobj_t *origMine, mobj_t *punter)
{ {
angle_t fa = K_MomentumAngle(punter); angle_t fa = K_MomentumAngle(punter);
@ -13424,7 +13437,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
case KITEM_BALLHOG: case KITEM_BALLHOG:
if (!HOLDING_ITEM && NO_HYUDORO) 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 // This construct looks a little goofy, but we're basically just
// trying to prevent rapid taps from restarting a charge, while // 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) if (player->ballhogcharge == 0)
player->ballhogtap = false; 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)) if ((realcharge && !player->ballhogtap) || (player->ballhogtap && player->ballhogcharge < BALLHOGINCREMENT))
{ {
player->ballhogcharge++; if (player->ballhogcharge < ballhogmax)
if (player->ballhogcharge % BALLHOGINCREMENT == 0)
{ {
sfxenum_t hogsound[] = player->ballhogcharge++;
if (player->ballhogcharge % BALLHOGINCREMENT == 0)
{ {
sfx_bhog00, sfxenum_t hogsound[] =
sfx_bhog01, {
sfx_bhog02, sfx_bhog00,
sfx_bhog03, sfx_bhog01,
sfx_bhog04, sfx_bhog02,
sfx_bhog05 sfx_bhog03,
}; sfx_bhog04,
UINT8 chargesound = max(1, min(player->ballhogcharge / BALLHOGINCREMENT, 6)); sfx_bhog05
S_StartSound(player->mo, hogsound[chargesound-1]); };
UINT8 chargesound = max(1, min(player->ballhogcharge / BALLHOGINCREMENT, 6));
S_StartSound(player->mo, hogsound[chargesound-1]);
}
} }
} }
else else
{ {
if (cmd->buttons & BT_ATTACK)
{
player->itemflags &= ~IF_HOLDREADY;
}
else
{
player->itemflags |= IF_HOLDREADY;
}
if (player->ballhogcharge > 0) if (player->ballhogcharge > 0)
{ {
INT32 numhogs = min((player->ballhogcharge / BALLHOGINCREMENT), player->itemamount); INT32 numhogs = K_HogChargeToHogCount(player->ballhogcharge, player->itemamount);
if (numhogs > 0) // no tapfire scams
K_SetItemOut(player); // need this to set itemscale
if (numhogs <= 0)
{ {
// no tapfire scams K_SetItemOut(player); // need this to set itemscale
}
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;
player->itemamount -= numhogs; 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_PlayAttackTaunt(player->mo);
K_DoBallhogAttack(player, numhogs);
K_UnsetItemOut(player);
} }
K_UnsetItemOut(player);
player->ballhogcharge = 0; player->ballhogcharge = 0;
player->itemflags &= ~IF_HOLDREADY; player->itemflags &= ~IF_HOLDREADY;
player->botvars.itemconfirm = 0; player->botvars.itemconfirm = 0;
} }
else
{
if (cmd->buttons & BT_ATTACK)
{
player->itemflags &= ~IF_HOLDREADY;
}
else
{
player->itemflags |= IF_HOLDREADY;
}
}
} }
} }
break; break;
@ -14379,6 +14377,12 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
Obj_PlayerBulbThink(player); 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) void K_CheckSpectateStatus(boolean considermapreset)

View file

@ -177,6 +177,7 @@ void K_SpawnDraftDust(mobj_t *mo);
void K_SpawnMagicianParticles(mobj_t *mo, int spread); void K_SpawnMagicianParticles(mobj_t *mo, int spread);
void K_DriftDustHandling(mobj_t *spawner); void K_DriftDustHandling(mobj_t *spawner);
void K_Squish(mobj_t *mo); 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); 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_PuntMine(mobj_t *mine, mobj_t *punter);
void K_DoSneaker(player_t *player, INT32 type); void K_DoSneaker(player_t *player, INT32 type);

View file

@ -64,8 +64,8 @@ mobj_t *Obj_MantaRingCreate(mobj_t *spb, mobj_t *owner, mobj_t *chase);
/* Orbinaut */ /* Orbinaut */
void Obj_OrbinautThink(mobj_t *th); void Obj_OrbinautThink(mobj_t *th);
boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2); boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2);
void Obj_OrbinautThrown(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, SINT8 dir); void Obj_GachaBomThrown(mobj_t *th, fixed_t finalSpeed, fixed_t dir);
void Obj_OrbinautJawzMoveHeld(player_t *player); void Obj_OrbinautJawzMoveHeld(player_t *player);
boolean Obj_GachaBomWasTossed(mobj_t *th); boolean Obj_GachaBomWasTossed(mobj_t *th);
void Obj_OrbinautDrop(mobj_t *th); void Obj_OrbinautDrop(mobj_t *th);
@ -73,7 +73,7 @@ boolean Obj_OrbinautCanRunOnWater(mobj_t *th);
/* Jawz */ /* Jawz */
void Obj_JawzThink(mobj_t *th); 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 */ /* Duel Bomb */
void Obj_DuelBombThink(mobj_t *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_PulleyThink(mobj_t *root);
void Obj_PulleyHookTouch(mobj_t *special, mobj_t *toucher); 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 #ifdef __cplusplus
} // extern "C" } // extern "C"
#endif #endif

View file

@ -59,6 +59,7 @@ target_sources(SRB2SDL2 PRIVATE
destroyed-kart.cpp destroyed-kart.cpp
pulley.cpp pulley.cpp
amps.c amps.c
ballhog.cpp
) )
add_subdirectory(versus) add_subdirectory(versus)

383
src/objects/ballhog.cpp Normal file
View file

@ -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<struct hog_angles> 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<UINT8>(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<UINT8>((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, &center); // 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--;
}
}

View file

@ -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; INT32 lastTarg = -1;
player_t *owner = NULL; player_t *owner = NULL;
@ -270,7 +270,7 @@ void Obj_JawzThrown(mobj_t *th, fixed_t finalSpeed, SINT8 dir)
jawz_retcolor(th) = SKINCOLOR_KETCHUP; jawz_retcolor(th) = SKINCOLOR_KETCHUP;
} }
if (dir == -1) if (dir < 0)
{ {
// Thrown backwards, init self-chase // Thrown backwards, init self-chase
P_SetTarget(&jawz_chase(th), jawz_owner(th)); P_SetTarget(&jawz_chase(th), jawz_owner(th));

View file

@ -323,7 +323,7 @@ boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2)
return true; 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; 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 mobj_t *owner = orbinaut_owner(th);
const ffloor_t *rover = P_IsObjectFlipped(owner) ? owner->ceilingrover : owner->floorrover; 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! // The owner can run on water, so we should too!
orbinaut_flags(th) |= ORBI_WATERSKI; 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; orbinaut_flags(th) |= ORBI_TRAIL;
if (dir == -1) if (dir < 0)
{ {
// Thrown backwards, init orbiting in place // Thrown backwards, init orbiting in place
orbinaut_turn(th) = ORBINAUT_MAXTURN / ORBINAUT_TURNLERP; 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); Obj_OrbinautThrown(th, finalSpeed, dir);
orbinaut_flags(th) &= ~(ORBI_TRAIL); orbinaut_flags(th) &= ~(ORBI_TRAIL);
switch (dir) if (dir < 0)
{ {
case -1: orbinaut_flags(th) |= ORBI_SPIN;
orbinaut_flags(th) |= ORBI_SPIN; }
break; else if (dir > 0)
{
case 1: orbinaut_flags(th) |= ORBI_TOSSED;
orbinaut_flags(th) |= ORBI_TOSSED;
break;
} }
} }

View file

@ -594,18 +594,19 @@ void P_ExplodeMissile(mobj_t *mo)
mo->momx = mo->momy = mo->momz = 0; mo->momx = mo->momy = mo->momz = 0;
if (mo->flags & MF_NOCLIPTHING) if (mo->flags2 & MF2_BOSSDEAD)
return; return;
mo->flags &= ~MF_MISSILE; mo->flags &= ~MF_MISSILE;
mo->flags |= MF_NOGRAVITY; // Dead missiles don't need to sink anymore. 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)) if (mo->info->deathsound && !(mo->flags2 & MF2_DEBRIS))
S_StartSound(mo, mo->info->deathsound); S_StartSound(mo, mo->info->deathsound);
P_SetMobjState(mo, mo->info->deathstate); P_SetMobjState(mo, mo->info->deathstate);
mo->health = 0;
} }
// P_InsideANonSolidFFloor // P_InsideANonSolidFFloor
@ -1059,6 +1060,8 @@ static boolean P_UseUnderwaterGravity(mobj_t *mo)
switch (mo->type) switch (mo->type)
{ {
case MT_BANANA: case MT_BANANA:
case MT_BALLHOG:
case MT_BALLHOG_RETICULE_TEST:
return false; return false;
case MT_GACHABOM: case MT_GACHABOM:
@ -1247,6 +1250,8 @@ fixed_t P_GetMobjGravity(mobj_t *mo)
gravityadd = (5*gravityadd)/2; gravityadd = (5*gravityadd)/2;
break; break;
case MT_BANANA: case MT_BANANA:
case MT_BALLHOG:
case MT_BALLHOG_RETICULE_TEST:
case MT_EGGMANITEM: case MT_EGGMANITEM:
case MT_SSMINE: case MT_SSMINE:
case MT_LANDMINE: case MT_LANDMINE:
@ -1255,9 +1260,9 @@ fixed_t P_GetMobjGravity(mobj_t *mo)
case MT_EMERALD: case MT_EMERALD:
if (mo->health > 0) if (mo->health > 0)
{ {
if (mo->extravalue2 > 0) if (mo->extravalue2 > FRACUNIT)
{ {
gravityadd *= mo->extravalue2; gravityadd = FixedMul(gravityadd, mo->extravalue2);
} }
gravityadd = (5*gravityadd)/2; gravityadd = (5*gravityadd)/2;
@ -1600,7 +1605,7 @@ static boolean P_CheckSkyHit(mobj_t *mo)
// //
// P_XYMovement // P_XYMovement
// //
void P_XYMovement(mobj_t *mo) boolean P_XYMovement(mobj_t *mo)
{ {
player_t *player; player_t *player;
fixed_t xmove, ymove; fixed_t xmove, ymove;
@ -1626,7 +1631,7 @@ void P_XYMovement(mobj_t *mo)
// set in 'search new direction' state? // set in 'search new direction' state?
P_SetMobjState(mo, mo->info->spawnstate); 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))) if (CheckForBustableBlocks && ((mo->flags & MF_PUSHABLE) || ((mo->info->flags & MF_PUSHABLE) && mo->fuse)))
P_PushableCheckBustables(mo); 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) if (!P_TryMove(mo, mo->x + xmove, mo->y + ymove, true, &result)
&& !(P_MobjWasRemoved(mo) || mo->eflags & MFE_SPRUNG)) && !(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 (LUA_HookMobjMoveBlocked(mo, g_tm.hitthing, result.line))
{ {
if (P_MobjWasRemoved(mo)) if (P_MobjWasRemoved(mo))
return; return false;
} }
else if (P_MobjWasRemoved(mo)) else if (P_MobjWasRemoved(mo))
return; return false;
P_PushSpecialLine(result.line, mo); P_PushSpecialLine(result.line, mo);
if (mo->type == MT_BALLHOG_RETICULE_TEST)
{
return false;
}
if (mo->flags & MF_MISSILE) if (mo->flags & MF_MISSILE)
{ {
// explode a missile // explode a missile
@ -1718,7 +1713,7 @@ void P_XYMovement(mobj_t *mo)
// Check frontsector as well. // Check frontsector as well.
P_RemoveMobj(mo); P_RemoveMobj(mo);
return; return false;
} }
// draw damage on wall // draw damage on wall
@ -1745,7 +1740,7 @@ void P_XYMovement(mobj_t *mo)
// --------------------------------------------------------- SPLAT TEST // --------------------------------------------------------- SPLAT TEST
P_ExplodeMissile(mo); P_ExplodeMissile(mo);
return; return false;
} }
else else
{ {
@ -1807,14 +1802,14 @@ void P_XYMovement(mobj_t *mo)
{ {
P_SlideMove(mo, &result); P_SlideMove(mo, &result);
if (P_MobjWasRemoved(mo)) if (P_MobjWasRemoved(mo))
return; return false;
xmove = ymove = 0; xmove = ymove = 0;
} }
else else
{ {
P_BounceMove(mo, &result); P_BounceMove(mo, &result);
if (P_MobjWasRemoved(mo)) if (P_MobjWasRemoved(mo))
return; return false;
xmove = ymove = 0; xmove = ymove = 0;
S_StartSound(mo, mo->info->activesound); S_StartSound(mo, mo->info->activesound);
@ -1822,9 +1817,7 @@ void P_XYMovement(mobj_t *mo)
// Ballhog dies on contact with walls // Ballhog dies on contact with walls
if (mo->type == MT_BALLHOG) if (mo->type == MT_BALLHOG)
{ {
S_StartSound(mo, mo->info->deathsound); P_ExplodeMissile(mo);
P_KillMobj(mo, NULL, NULL, DMG_NORMAL);
return;
} }
// Bump sparks // Bump sparks
else if (mo->type == MT_ORBINAUT || mo->type == MT_GACHABOM) else if (mo->type == MT_ORBINAUT || mo->type == MT_GACHABOM)
@ -1886,7 +1879,7 @@ void P_XYMovement(mobj_t *mo)
moved = true; moved = true;
if (P_MobjWasRemoved(mo)) // MF_SPECIAL touched a player! O_o;; if (P_MobjWasRemoved(mo)) // MF_SPECIAL touched a player! O_o;;
return; return false;
if (moved == true) if (moved == true)
{ {
@ -1963,29 +1956,30 @@ void P_XYMovement(mobj_t *mo)
P_CheckGravity(mo, false); P_CheckGravity(mo, false);
if (mo->flags & MF_NOCLIPHEIGHT) 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) 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) if ((mo->type == MT_BIGTUMBLEWEED || mo->type == MT_LITTLETUMBLEWEED)
&& (mo->standingslope && abs(mo->standingslope->zdelta) > FRACUNIT>>8)) // Special exception for tumbleweeds on slopes && (mo->standingslope && abs(mo->standingslope->zdelta) > FRACUNIT>>8)) // Special exception for tumbleweeds on slopes
return; return moved;
//{ SRB2kart stuff //{ SRB2kart stuff
if (mo->type == MT_FLINGRING || mo->type == MT_BALLHOG || mo->type == MT_BUBBLESHIELDTRAP) if (mo->type == MT_FLINGRING || mo->type == MT_BALLHOG || mo->type == MT_BALLHOG_RETICULE_TEST || mo->type == MT_BUBBLESHIELDTRAP)
return; return moved;
if (player && (player->spinouttimer && !player->wipeoutslow) if (player && (player->spinouttimer && !player->wipeoutslow)
&& player->speed <= FixedDiv(20*mapobjectscale, player->offroad + FRACUNIT)) && 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))) 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)) && !(player && player->carry == CR_SLIDING))
return; // no friction when airborne return moved; // no friction when airborne
P_XYFriction(mo, oldx, oldy); P_XYFriction(mo, oldx, oldy);
return moved;
} }
void P_RingXYMovement(mobj_t *mo) void P_RingXYMovement(mobj_t *mo)
@ -2328,6 +2322,19 @@ boolean P_ZMovement(mobj_t *mo)
return false; return false;
} }
break; 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: default:
// SRB2kart stuff that should die in pits // SRB2kart stuff that should die in pits
// Shouldn't stop moving along the Z if there's no speed though! // 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)) if (((mo->z + mo->height > mo->ceilingz && !(mo->eflags & MFE_VERTICALFLIP))
|| (mo->z < mo->floorz && 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) if (mo->eflags & MFE_VERTICALFLIP)
mo->z = mo->floorz; 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->health > 0 && P_MobjTouchingSectorSpecialFlag(mobj, SSF_DELETEITEMS))
{ {
if (mobj->type == MT_SSMINE if (mobj->type == MT_SSMINE
|| mobj->type == MT_BUBBLESHIELDTRAP || mobj->type == MT_BUBBLESHIELDTRAP
|| mobj->type == MT_BALLHOG) || mobj->type == MT_BALLHOG)
{ {
S_StartSound(mobj, mobj->info->deathsound); S_StartSound(mobj, mobj->info->deathsound);
P_KillMobj(mobj, NULL, NULL, DMG_NORMAL); P_KillMobj(mobj, NULL, NULL, DMG_NORMAL);

View file

@ -578,7 +578,7 @@ void P_NullPrecipThinker(precipmobj_t *mobj);
void P_FreePrecipMobj(precipmobj_t *mobj); void P_FreePrecipMobj(precipmobj_t *mobj);
void P_SetScale(mobj_t *mobj, fixed_t newscale); void P_SetScale(mobj_t *mobj, fixed_t newscale);
void P_InstaScale(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_RingXYMovement(mobj_t *mo);
void P_SceneryXYMovement(mobj_t *mo); void P_SceneryXYMovement(mobj_t *mo);
boolean P_ZMovement(mobj_t *mo); boolean P_ZMovement(mobj_t *mo);

View file

@ -90,6 +90,7 @@ typedef enum
FLICKYCONTROLLER = 0x1000, FLICKYCONTROLLER = 0x1000,
TRICKINDICATOR = 0x2000, TRICKINDICATOR = 0x2000,
BARRIER = 0x4000, BARRIER = 0x4000,
BALLHOGRETICULE = 0x8000, // uh oh, we're full now...
} player_saveflags; } player_saveflags;
static inline void P_ArchivePlayer(savebuffer_t *save) static inline void P_ArchivePlayer(savebuffer_t *save)
@ -325,6 +326,9 @@ static void P_NetArchivePlayers(savebuffer_t *save)
if (players[i].hoverhyudoro) if (players[i].hoverhyudoro)
flags |= HOVERHYUDORO; flags |= HOVERHYUDORO;
if (players[i].ballhogreticule)
flags |= BALLHOGRETICULE;
if (players[i].stumbleIndicator) if (players[i].stumbleIndicator)
flags |= STUMBLE; flags |= STUMBLE;
@ -369,6 +373,9 @@ static void P_NetArchivePlayers(savebuffer_t *save)
if (flags & HOVERHYUDORO) if (flags & HOVERHYUDORO)
WRITEUINT32(save->p, players[i].hoverhyudoro->mobjnum); WRITEUINT32(save->p, players[i].hoverhyudoro->mobjnum);
if (flags & BALLHOGRETICULE)
WRITEUINT32(save->p, players[i].ballhogreticule->mobjnum);
if (flags & STUMBLE) if (flags & STUMBLE)
WRITEUINT32(save->p, players[i].stumbleIndicator->mobjnum); WRITEUINT32(save->p, players[i].stumbleIndicator->mobjnum);
@ -989,6 +996,9 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
if (flags & HOVERHYUDORO) if (flags & HOVERHYUDORO)
players[i].hoverhyudoro = (mobj_t *)(size_t)READUINT32(save->p); 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) if (flags & STUMBLE)
players[i].stumbleIndicator = (mobj_t *)(size_t)READUINT32(save->p); players[i].stumbleIndicator = (mobj_t *)(size_t)READUINT32(save->p);
@ -5971,6 +5981,11 @@ static void P_RelinkPointers(void)
if (!RelinkMobj(&players[i].hoverhyudoro)) if (!RelinkMobj(&players[i].hoverhyudoro))
CONS_Debug(DBG_GAMELOGIC, "hoverhyudoro not found on player %d\n", i); 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 (players[i].stumbleIndicator)
{ {
if (!RelinkMobj(&players[i].stumbleIndicator)) if (!RelinkMobj(&players[i].stumbleIndicator))

View file

@ -4244,6 +4244,7 @@ void P_PlayerThink(player_t *player)
PlayerPointerErase(player->hand); PlayerPointerErase(player->hand);
PlayerPointerErase(player->ringShooter); PlayerPointerErase(player->ringShooter);
PlayerPointerErase(player->hoverhyudoro); PlayerPointerErase(player->hoverhyudoro);
PlayerPointerErase(player->ballhogreticule);
PlayerPointerErase(player->flickyAttacker); PlayerPointerErase(player->flickyAttacker);
PlayerPointerErase(player->powerup.flickyController); PlayerPointerErase(player->powerup.flickyController);
PlayerPointerErase(player->powerup.barrier); PlayerPointerErase(player->powerup.barrier);