Merge branch 'ballhog-buffs' into 'master'

Ballhog Buffs & Feature

See merge request kart-krew-dev/ring-racers-internal!2725
This commit is contained in:
Oni VelocitOni 2025-08-09 04:12:18 +00:00
commit 3d1a7c1684
19 changed files with 165 additions and 69 deletions

View file

@ -147,6 +147,7 @@ typedef enum
PF2_ALWAYSDAMAGED = 1<<6, // Ignore invulnerability or clash conditions when evaulating damage (P_DamageMobj). Unset after use!
PF2_BUBBLECONTACT = 1<<7, // ACHTUNG VERY BAD HACK - Don't allow Bubble Shield to contact certain objects unless this is a fresh blowup.
PF2_SUPERTRANSFERVFX = 1<<8, // Don't respawn the "super transfer available" VFX.
PF2_FASTTUMBLEBOUNCE = 1<<9, // Don't lose speed when tumblebouncing.
} pflags2_t;
typedef enum
@ -850,6 +851,7 @@ struct player_t
UINT16 counterdash; // Flame Shield boost without the flame, largely. Used in places where awarding thrust would affect player control.
UINT16 ballhogcharge; // Ballhog charge up -- the higher this value, the more projectiles
UINT8 ballhogburst;
boolean ballhogtap; // Ballhog released during charge: used to allow semirapid tapfire
mobj_t *ballhogreticule; // First ballhog reticule estimation object

View file

@ -1938,22 +1938,7 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi
"S_BALLHOG7",
"S_BALLHOG8",
"S_BALLHOG_DEAD",
"S_BALLHOGBOOM1",
"S_BALLHOGBOOM2",
"S_BALLHOGBOOM3",
"S_BALLHOGBOOM4",
"S_BALLHOGBOOM5",
"S_BALLHOGBOOM6",
"S_BALLHOGBOOM7",
"S_BALLHOGBOOM8",
"S_BALLHOGBOOM9",
"S_BALLHOGBOOM10",
"S_BALLHOGBOOM11",
"S_BALLHOGBOOM12",
"S_BALLHOGBOOM13",
"S_BALLHOGBOOM14",
"S_BALLHOGBOOM15",
"S_BALLHOGBOOM16",
"S_BALLHOGBOOM",
"S_BALLHOG_RETICULE",
// Self-Propelled Bomb - just an explosion for now...

View file

@ -2517,22 +2517,7 @@ state_t states[NUMSTATES] =
{SPR_BHOG, 6, 1, {NULL}, 0, 0, S_BALLHOG8}, // S_BALLHOG7
{SPR_BHOG, 7, 1, {NULL}, 0, 0, S_BALLHOG1}, // S_BALLHOG8
{SPR_NULL, 0, 1, {A_BallhogExplode}, 0, 0, S_NULL}, // S_BALLHOG_DEAD
{SPR_BHBM, FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_BALLHOGBOOM2}, // S_BALLHOGBOOM1
{SPR_BHBM, FF_FULLBRIGHT|1, 1, {NULL}, 0, 0, S_BALLHOGBOOM3}, // S_BALLHOGBOOM2
{SPR_BHBM, FF_FULLBRIGHT|2, 1, {NULL}, 0, 0, S_BALLHOGBOOM4}, // S_BALLHOGBOOM3
{SPR_BHBM, FF_FULLBRIGHT|3, 1, {NULL}, 0, 0, S_BALLHOGBOOM5}, // S_BALLHOGBOOM4
{SPR_BHBM, FF_FULLBRIGHT|4, 1, {NULL}, 0, 0, S_BALLHOGBOOM6}, // S_BALLHOGBOOM5
{SPR_BHBM, FF_FULLBRIGHT|5, 1, {NULL}, 0, 0, S_BALLHOGBOOM7}, // S_BALLHOGBOOM6
{SPR_BHBM, FF_FULLBRIGHT|6, 1, {NULL}, 0, 0, S_BALLHOGBOOM8}, // S_BALLHOGBOOM7
{SPR_BHBM, FF_FULLBRIGHT|7, 1, {NULL}, 0, 0, S_BALLHOGBOOM9}, // S_BALLHOGBOOM8
{SPR_BHBM, FF_FULLBRIGHT|8, 1, {NULL}, 0, 0, S_BALLHOGBOOM10}, // S_BALLHOGBOOM9
{SPR_BHBM, FF_FULLBRIGHT|9, 1, {NULL}, 0, 0, S_BALLHOGBOOM11}, // S_BALLHOGBOOM10
{SPR_BHBM, FF_FULLBRIGHT|10, 1, {NULL}, 0, 0, S_BALLHOGBOOM12}, // S_BALLHOGBOOM11
{SPR_BHBM, FF_FULLBRIGHT|11, 1, {NULL}, 0, 0, S_BALLHOGBOOM13}, // S_BALLHOGBOOM12
{SPR_BHBM, FF_FULLBRIGHT|12, 1, {NULL}, 0, 0, S_BALLHOGBOOM14}, // S_BALLHOGBOOM13
{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_BHBM, FF_ANIMATE|FF_FULLBRIGHT, 27, {NULL}, 26, 1, S_NULL}, // S_BALLHOGBOOM
{SPR_BHGR, FF_ANIMATE|FF_FULLBRIGHT|0, 2*TICRATE, {NULL}, 5, 3, S_NULL}, // S_BALLHOG_RETICULE
{SPR_SPBM, 0, 1, {NULL}, 0, 0, S_SPB2}, // S_SPB1
@ -15203,7 +15188,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
S_NULL, // missilestate
S_SSMINE_EXPLODE, // deathstate
S_NULL, // xdeathstate
sfx_None, // deathsound
sfx_gshc5, // deathsound
0, // speed
16*FRACUNIT, // radius
56*FRACUNIT, // height
@ -15446,7 +15431,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
S_NULL, // missilestate
S_BALLHOG_DEAD, // deathstate
S_NULL, // xdeathstate
sfx_hogbom, // deathsound
sfx_gshdd, // deathsound
0, // speed
26*FRACUNIT, // radius
64*FRACUNIT, // height
@ -15460,7 +15445,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
{ // MT_BALLHOGBOOM
-1, // doomednum
S_BALLHOGBOOM1, // spawnstate
S_BALLHOGBOOM, // spawnstate
1000, // spawnhealth
S_NULL, // seestate
sfx_None, // seesound
@ -15475,8 +15460,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
S_NULL, // xdeathstate
sfx_None, // deathsound
0, // speed
32*FRACUNIT, // radius
32*FRACUNIT, // height
50*FRACUNIT, // radius
50*FRACUNIT, // height
0, // display offset
100, // mass
1, // damage

View file

@ -3007,22 +3007,7 @@ typedef enum state
S_BALLHOG7,
S_BALLHOG8,
S_BALLHOG_DEAD,
S_BALLHOGBOOM1,
S_BALLHOGBOOM2,
S_BALLHOGBOOM3,
S_BALLHOGBOOM4,
S_BALLHOGBOOM5,
S_BALLHOGBOOM6,
S_BALLHOGBOOM7,
S_BALLHOGBOOM8,
S_BALLHOGBOOM9,
S_BALLHOGBOOM10,
S_BALLHOGBOOM11,
S_BALLHOGBOOM12,
S_BALLHOGBOOM13,
S_BALLHOGBOOM14,
S_BALLHOGBOOM15,
S_BALLHOGBOOM16,
S_BALLHOGBOOM,
S_BALLHOG_RETICULE,
// Self-Propelled Bomb

View file

@ -75,6 +75,9 @@ boolean K_BananaBallhogCollide(mobj_t *t1, mobj_t *t2)
if (t1->type == MT_BALLHOGBOOM && t2->type == MT_BALLHOGBOOM)
return true; // Ballhogs don't collide with eachother
if (t1->type == MT_BALLHOGBOOM && t2->type == MT_PLAYER && t1->target == t2 )
return true; // Allied hog explosion, not snatchable but shouldn't damage
if (K_TryPickMeUp(t1, t2, false))
return true;
@ -87,7 +90,10 @@ boolean K_BananaBallhogCollide(mobj_t *t1, mobj_t *t2)
if (t1->type == MT_BANANA && t1->health > 1)
S_StartSound(t2, sfx_bsnipe);
damageitem = true;
if (t1->type != MT_BALLHOGBOOM) // ballhog booms linger and expire after their anim is done
{
damageitem = true;
}
if (t2->player->flamedash && t2->player->itemtype == KITEM_FLAMESHIELD)
{

View file

@ -1875,7 +1875,7 @@ static void K_drawKartItem(void)
V_ClearClipRect();
// A little goofy, but helps with ballhog charge conveyance—you're "loading" them.
UINT8 fakeitemamount = stplyr->itemamount - (stplyr->ballhogcharge / BALLHOGINCREMENT);
UINT8 fakeitemamount = stplyr->itemamount - std::min(stplyr->itemamount - 1, (stplyr->ballhogcharge / BALLHOGINCREMENT));
boolean transflag = V_HUDTRANS;

View file

@ -468,7 +468,20 @@ std::optional<TargetTracking::Tooltip> object_tooltip(const mobj_t* mobj)
TextElement().parse("<c_animated>").font(splitfont))
)
.offset3d(0, 0, 64 * mobj->scale * P_MobjFlip(mobj));
if (mobj->player == stplyr && stplyr->ballhogburst >= (BALLHOG_BURST_FUSE/3))
{
UINT32 flag = (stplyr->ballhogburst >= (2*BALLHOG_BURST_FUSE/3)) ? V_REDMAP : V_YELLOWMAP;
if (stplyr->ballhogburst % 2 && !cv_reducevfx.value)
flag = 0;
return Tooltip(
TextElement(
TextElement().parse("DANGER!").flags(V_20TRANS|flag).font(splitfont))
)
.offset3d(0, 0, 32 * mobj->scale * P_MobjFlip(mobj));
}
return conditional(
mobj->player == stplyr && stplyr->icecube.frozen,
[&] { return Tooltip(TextElement(

View file

@ -5550,8 +5550,13 @@ static void K_HandleTumbleBounce(player_t *player)
S_StartSound(player->mo, (player->tumbleHeight < 40) ? sfx_s3k5d : sfx_s3k5f); // s3k5d is bounce < 50, s3k5f otherwise!
player->mo->momx = player->mo->momx / 2;
player->mo->momy = player->mo->momy / 2;
if (!(player->pflags2 & PF2_FASTTUMBLEBOUNCE))
{
player->mo->momx = player->mo->momx / 2;
player->mo->momy = player->mo->momy / 2;
}
player->pflags2 &= ~PF2_FASTTUMBLEBOUNCE;
// and then modulate momz like that...
player->mo->momz = K_TumbleZ(player->mo, player->tumbleHeight * FRACUNIT);
@ -11141,6 +11146,25 @@ void K_KartResetPlayerColor(player_t *player)
}
}
if (player->ballhogcharge && player->ballhogburst >= (2*BALLHOG_BURST_FUSE/3))
{
player->mo->colorized = true;
player->mo->color = (player->ballhogburst % 2) ? SKINCOLOR_CRIMSON : SKINCOLOR_BLACK;
fullbright = true;
goto finalise;
}
if (player->ballhogcharge && player->ballhogburst >= (BALLHOG_BURST_FUSE/3))
{
if (player->ballhogburst % 2 == 0)
{
player->mo->colorized = true;
player->mo->color = SKINCOLOR_CRIMSON;
fullbright = true;
goto finalise;
}
}
if (player->invincibilitytimer) // You're gonna kiiiiill
{
const tic_t defaultTime = itemtime+(2*TICRATE);
@ -14879,6 +14903,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
{
if (player->ballhogcharge < ballhogmax)
{
player->ballhogburst = 0;
player->ballhogcharge++;
if (player->ballhogcharge % BALLHOGINCREMENT == 0)
@ -14896,6 +14921,63 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
S_StartSound(player->mo, hogsound[chargesound-1]);
}
}
else
{
player->ballhogburst++;
if (player->ballhogburst == 2*BALLHOG_BURST_FUSE/3)
S_StartSound(player->mo, sfx_gshb8);
if (player->ballhogburst == BALLHOG_BURST_FUSE/3)
S_StartSound(player->mo, sfx_gshda);
else if (player->ballhogburst == BALLHOG_BURST_FUSE)
{
K_PlayBoostTaunt(player->mo);
for (UINT8 j = 0; j < player->itemamount; j++)
{
K_DoSneaker(player, 0);
}
S_StopSoundByID(player->mo, sfx_gshda);
mobj_t *boom = P_SpawnMobjFromMobj(player->mo, 0, 0, 0, MT_THOK);
P_SetMobjState(boom, S_BALLHOGBOOM);
boom->scale = player->mo->scale + (player->mo->scale / 4 * player->itemamount);
// boom->momx = player->mo->momx/2;
// boom->momy = player->mo->momy/2;
// boom->momz = player->mo->momz/2;
boom->color = player->skincolor;
boom->colorized = true;
S_StartSound(player->mo, mobjinfo[MT_BALLHOG].deathsound);
K_StumblePlayer(player);
K_AddHitLag(player->mo, TICRATE/4, false);
player->tumbleBounces = TUMBLEBOUNCES;
P_Thrust(player->mo, player->mo->angle, (40 + 10 * player->itemamount) * player->mo->scale);
player->pflags2 |= PF2_FASTTUMBLEBOUNCE;
/*
if (onground)
{
P_SetObjectMomZ(player->mo, 10*FRACUNIT, true);
player->mo->eflags |= MFE_DONTSLOPELAUNCH;
}
else
{
P_SetObjectMomZ(player->mo, -50*FRACUNIT, true);
}
*/
player->itemamount = 0;
player->botvars.itemconfirm = 0;
player->ballhogcharge = 0;
player->ballhogburst = 0;
}
}
}
else
{
@ -14914,6 +14996,8 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
}
player->ballhogcharge = 0;
player->ballhogburst = 0;
S_StopSoundByID(player->mo, sfx_gshda);
player->itemflags &= ~IF_HOLDREADY;
player->botvars.itemconfirm = 0;
}
@ -16718,7 +16802,6 @@ static boolean K_PickUp(player_t *player, mobj_t *picked)
type = KITEM_JAWZ;
break;
case MT_BALLHOG:
case MT_BALLHOGBOOM:
type = KITEM_BALLHOG;
break;
case MT_LANDMINE:

View file

@ -68,6 +68,8 @@ Make sure this matches the actual number of states
#define FLAMESHIELD_MAX (120)
#define BALLHOG_BURST_FUSE (TICRATE*2)
#define RR_PROJECTILE_FUSE (8*TICRATE)
#define SCAMDIST (2000)

View file

@ -709,7 +709,7 @@ static int mobj_set(lua_State *L)
mo->flags2 = (UINT32)luaL_checkinteger(L, 3);
break;
case mobj_eflags:
mo->eflags = (UINT16)luaL_checkinteger(L, 3);
mo->eflags = (UINT32)luaL_checkinteger(L, 3);
break;
case mobj_renderflags:
mo->renderflags = (UINT32)luaL_checkinteger(L, 3);

View file

@ -498,6 +498,8 @@ static int player_get(lua_State *L)
lua_pushinteger(L, plr->lightningcharge);
else if (fastcmp(field,"ballhogcharge"))
lua_pushinteger(L, plr->ballhogcharge);
else if (fastcmp(field,"ballhogburst"))
lua_pushinteger(L, plr->ballhogburst);
else if (fastcmp(field,"ballhogtap"))
lua_pushinteger(L, plr->ballhogtap);
else if (fastcmp(field,"hyudorotimer"))
@ -1139,6 +1141,8 @@ static int player_set(lua_State *L)
plr->lightningcharge = luaL_checkinteger(L, 3);
else if (fastcmp(field,"ballhogcharge"))
plr->ballhogcharge = luaL_checkinteger(L, 3);
else if (fastcmp(field,"ballhogburst"))
plr->ballhogburst = luaL_checkinteger(L, 3);
else if (fastcmp(field,"ballhogtap"))
plr->ballhogtap = luaL_checkinteger(L, 3);
else if (fastcmp(field,"hyudorotimer"))

View file

@ -61,7 +61,7 @@ static void CalculateHogAngles(UINT8 n)
if (total_hogs > 1)
{
const fixed_t base_radius = mobjinfo[MT_BALLHOG].radius * 6;
const fixed_t base_radius = mobjinfo[MT_BALLHOG].radius * 12;
fixed_t radius = base_radius;
UINT8 max_points = 6;
angle_t circle_offset = 0;

View file

@ -12206,7 +12206,15 @@ void A_BallhogExplode(mobj_t *actor)
mo2->destscale = mo2->scale;
P_SetTarget(&mo2->target, actor->target);
S_StartSound(mo2, actor->info->deathsound);
if (actor->target && !P_MobjWasRemoved(actor->target) && actor->target->player)
{
mo2->color = actor->target->color;
mo2->colorized = true;
}
P_StartQuakeFromMobj(7, 50 * actor->scale, 1024 * actor->scale, actor);
actor->fuse = 1;
return;
}
@ -12217,7 +12225,7 @@ void A_SpecialStageBombExplode(mobj_t *actor)
return;
K_SpawnLandMineExplosion(actor, SKINCOLOR_KETCHUP, actor->hitlag);
P_StartQuakeFromMobj(TICRATE/6, 24 * actor->scale, 512 * mapobjectscale, actor);
//P_StartQuakeFromMobj(7, 80 * actor->scale, 4096 * mapobjectscale, actor);
}
// A_LightningFollowPlayer:

View file

@ -2800,11 +2800,17 @@ fixed_t P_BaseStepUp(void)
fixed_t P_GetThingStepUp(mobj_t *thing, fixed_t destX, fixed_t destY)
{
// I have no idea why this check caused Ballhog to step up on EVERYTHING,
// but it sure did step up on everything.
/*
if (thing->type == MT_BALLHOG || thing->type == MT_BALLHOG_RETICULE_TEST)
{
// these should explode, not go up stairs
return 0;
}
*/
const fixed_t maxstepmove = P_BaseStepUp();
fixed_t maxstep = maxstepmove;

View file

@ -1719,11 +1719,13 @@ boolean P_XYMovement(mobj_t *mo)
P_PushSpecialLine(result.line, mo);
/*
if (mo->type == MT_BALLHOG || mo->type == MT_BALLHOG_RETICULE_TEST)
{
P_ExplodeMissile(mo);
return false;
}
*/
if (mo->flags & MF_MISSILE)
{
@ -2011,7 +2013,7 @@ boolean P_XYMovement(mobj_t *mo)
return moved;
//{ SRB2kart stuff
if (mo->type == MT_FLINGRING || mo->type == MT_BALLHOG || mo->type == MT_BALLHOG_RETICULE_TEST || mo->type == MT_BUBBLESHIELDTRAP)
if (mo->type == MT_FLINGRING || mo->type == MT_BUBBLESHIELDTRAP)
return moved;
if (player && (player->spinouttimer && !player->wipeoutslow)
@ -2390,7 +2392,7 @@ boolean P_ZMovement(mobj_t *mo)
break;
case MT_BALLHOG:
case MT_BALLHOG_RETICULE_TEST:
if (mo->z <= mo->floorz || mo->z + mo->height >= mo->ceilingz)
if (mo->z <= mo->floorz)
{
P_ExplodeMissile(mo);
return false;
@ -4136,6 +4138,8 @@ static void P_PlayerMobjThinker(mobj_t *mobj)
mobj->eflags &= ~MFE_JUSTHITFLOOR;
}
mobj->eflags &= ~MFE_DONTSLOPELAUNCH;
P_SquishThink(mobj);
K_UpdateTerrainOverlay(mobj);
@ -10919,6 +10923,7 @@ void P_MobjThinker(mobj_t *mobj)
P_ButteredSlope(mobj);
}
mobj->eflags &= ~MFE_DONTSLOPELAUNCH;
P_SquishThink(mobj);
K_UpdateTerrainOverlay(mobj);

View file

@ -262,6 +262,8 @@ typedef enum
MFE_SLOPELAUNCHED = 1<<14,
// Thinker is paused due to hitlag
MFE_PAUSED = 1<<15,
// Don't launch off of slopes
MFE_DONTSLOPELAUNCH = 1<<16,
} mobjeflag_t;
//
@ -339,7 +341,7 @@ struct mobj_t
state_t *state;
UINT32 flags; // flags from mobjinfo tables
UINT32 flags2; // MF2_ flags
UINT16 eflags; // extra flags
UINT32 eflags; // extra flags
mtag_t tid;
mobj_t *tid_next;

View file

@ -565,6 +565,7 @@ static void P_NetArchivePlayers(savebuffer_t *save)
WRITEUINT8(save->p, players[i].lightningcharge);
WRITEUINT16(save->p, players[i].ballhogcharge);
WRITEUINT8(save->p, players[i].ballhogburst);
WRITEUINT8(save->p, players[i].ballhogtap);
WRITEUINT16(save->p, players[i].hyudorotimer);
@ -1235,6 +1236,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
players[i].lightningcharge = READUINT8(save->p);
players[i].ballhogcharge = READUINT16(save->p);
players[i].ballhogburst = READUINT8(save->p);
players[i].ballhogtap = READUINT8(save->p);
players[i].hyudorotimer = READUINT16(save->p);
@ -3476,7 +3478,7 @@ static void SaveMobjThinker(savebuffer_t *save, const thinker_t *th, const UINT8
WRITEUINT16(save->p, mobj->anim_duration);
}
if (diff & MD_EFLAGS)
WRITEUINT16(save->p, mobj->eflags);
WRITEUINT32(save->p, mobj->eflags);
if (diff & MD_PLAYER)
WRITEUINT8(save->p, mobj->player-players);
if (diff & MD_MOVEDIR)
@ -4751,7 +4753,7 @@ static thinker_t* LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker)
mobj->anim_duration = (UINT16)mobj->state->var2;
}
if (diff & MD_EFLAGS)
mobj->eflags = READUINT16(save->p);
mobj->eflags = READUINT32(save->p);
if (diff & MD_PLAYER)
{
i = READUINT8(save->p);

View file

@ -989,6 +989,14 @@ boolean P_CanApplySlopeLaunch(mobj_t *mo, pslope_t *slope)
return false;
}
if (mo->eflags & MFE_DONTSLOPELAUNCH)
{
CONS_Printf("MFE_DONTSLOPELAUNCH\n");
mo->eflags &= ~MFE_DONTSLOPELAUNCH; // You get one cancelled launch
// Don't launch off of slopes.
return false;
}
// We can do slope launching.
return true;
}

View file

@ -1457,7 +1457,7 @@ sfxinfo_t S_sfx[NUMSFX] =
{"gshc2", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gshc3", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gshc4", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gshc5", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gshc5", false, 64, 16, -1, NULL, 0, -1, -1, LUMPERROR, ""}, //x8away, ProxMineBOOM!
{"gshc6", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gshc7", false, 64, 16, -1, NULL, 0, -1, -1, LUMPERROR, ""}, //x8away
{"gshc8", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
@ -1481,7 +1481,7 @@ sfxinfo_t S_sfx[NUMSFX] =
{"gshda", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gshdb", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gshdc", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gshdd", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gshdd", false, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR, ""}, //New ballhog explosion
{"gshde", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gshdf", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gshe0", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},