Dapple up

Add all the bubble shield VFX
This commit is contained in:
Anonimus 2025-09-29 21:59:23 -04:00
parent 641f6ae865
commit a2d83f4b5f
9 changed files with 255 additions and 10 deletions

View file

@ -103,7 +103,7 @@ extern "C" {
// Special Hashing.
//#define NOFILEHASH
//#define NOVERIFYIWADS
#define NOVERIFYIWADS
// Uncheck this to compile debugging code
//#define RANGECHECK

View file

@ -612,6 +612,7 @@ _(THUNDERSHIELD) // Shields
_(BUBBLESHIELD)
_(FLAMESHIELD)
_(BUBBLESHIELDTRAP)
_(BUBBLESHLD_DEBRIS)
_(SINK) // Kitchen Sink Stuff
_(SINK_SHIELD)

View file

@ -445,6 +445,8 @@ _(BHBM) // Ballhog BOOM
_(SPBM) // Self-Propelled Bomb
_(THNS) // Thunder Shield
_(BUBS) // Bubble Shield (not Bubs)
_(BUBC) // Bubble Shield damage
_(BSDB) // Bubble Shield shatter debris
_(BWVE) // Bubble Shield waves
_(FLMS) // Flame Shield
_(FLMD) // Flame Shield dash

View file

@ -2888,7 +2888,7 @@ _(THUNDERSHIELD22)
_(THUNDERSHIELD23)
_(THUNDERSHIELD24)
// Bubble Shield
// Bubble Shield: Standard Sprites
_(BUBBLESHIELD1)
_(BUBBLESHIELD2)
_(BUBBLESHIELD3)
@ -2917,6 +2917,17 @@ _(BUBBLESHIELDTRAP6)
_(BUBBLESHIELDTRAP7)
_(BUBBLESHIELDTRAP8)
// Bubble Shield: Damage Visuals
_(BUBLSHLD_DMG_1)
_(BUBLSHLD_DMG_2)
_(BUBLSHLD_DMG_3)
_(BUBLSHLD_DMG_4)
_(BUBBLEDEBRIS_1)
_(BUBBLEDEBRIS_2)
_(BUBBLEDEBRIS_3)
_(BUBBLEDEBRIS_4)
_(BUBBLEDEBRIS_5)
// Flame Shield
_(FLAMESHIELD1)
_(FLAMESHIELD2)

View file

@ -545,6 +545,12 @@ static void K_BubbleShieldCollideDrain(player_t *player, mobj_t *bubble)
}
}
static boolean K_BubbleReflectingTrapItem(mobjtype_t t)
{
return ((t == MT_BANANA) || (t == MT_JAWZ_DUD) || (t == MT_SSMINE) || (t == MT_LANDMINE) ||
(t == MT_EGGMANITEM) || (t == MT_SSMINE_SHIELD));
}
boolean K_BubbleShieldReflect(mobj_t *t1, mobj_t *t2, boolean isplayer)
{
mobj_t *owner = t1->player ? t1 : t1->target;
@ -571,6 +577,12 @@ boolean K_BubbleShieldReflect(mobj_t *t1, mobj_t *t2, boolean isplayer)
{
t2->angle += ANGLE_180;
}
if (K_BubbleReflectingTrapItem(t2->type))
{
// Stupid hack: Toss trap/dud items into the air
t2->momz += (24*t2->scale) * P_MobjFlip(t2);
}
}
if (!isplayer)

View file

@ -5227,12 +5227,76 @@ void K_DropHnextList(player_t *player)
}
}
// Bubble shield break SFX and VFX (maybe, hopefully)
static void K_BreakBubbleShield(player_t *player)
#define MAXSHARDCOUNT 40
#define SHARDROT (360 * FRACUNIT / 40)
static void K_BreakBubbleShield(player_t* player)
{
if (!player->mo->shieldtracer)
{
// No shield tracer, assume there's no bubble shield.
return;
}
// TODO: Some more dramatic SFX and VFX (DSS3K59, bubble shield shards fly all over)
S_StartSound(player->mo, sfx_s3k4b);
mobj_t* shard;
fixed_t move_magnitude, shieldrad, shieldscale, scalediff;
vector2_t mul_vec;
vector3_t mom;
fixed_t randang, randzang;
INT32 flip = P_MobjFlip(player->mo);
shieldscale = player->mo->shieldtracer->scale;
scalediff = shieldscale - mapobjectscale;
shieldrad = FixedMul(player->mo->shieldtracer->radius, shieldscale);
mul_vec.x = FRACUNIT;
mul_vec.y = 0;
INT32 i;
for (i = 0; i < MAXSHARDCOUNT; i++)
{
mul_vec.x = FRACUNIT;
mul_vec.y = 0;
randang = (SHARDROT * (i + 1));
randzang = (P_RandomRange(23, 157) * FRACUNIT);
move_magnitude =
FixedMul((P_RandomRange(4, 16) * FRACUNIT), mapobjectscale + (scalediff / 4));
FV2_Rotate(&mul_vec, randzang);
// Do shitty initial 3D rotations around the shield's radius.
shard = P_SpawnMobj(
player->mo->shieldtracer->x +
FixedMul(FixedMul(mul_vec.x, shieldrad), FCOS(FixedAngle(randang))),
player->mo->shieldtracer->y +
FixedMul(FixedMul(mul_vec.x, shieldrad), FSIN(FixedAngle(randang))),
player->mo->shieldtracer->z + FixedMul(mul_vec.y * flip, shieldrad),
MT_BUBBLESHLD_DEBRIS);
if (shard)
{
//CONS_Printf(M_GetText("randzang: %d, randang: %d\n"), randzang / FRACUNIT, randang / FRACUNIT);
mul_vec.x = FixedMul(move_magnitude, mul_vec.x);
mul_vec.y = FixedMul(move_magnitude, mul_vec.y * flip);
mom.x = FixedMul(mul_vec.x, FCOS(FixedAngle(randang)));
mom.y = FixedMul(mul_vec.x, FSIN(FixedAngle(randang)));
mom.z = mul_vec.y;
shard->momx = mom.x + player->mo->momx;
shard->momy = mom.y + player->mo->momy;
shard->momz = mom.z + player->mo->momz;
}
}
S_StartSound(player->mo, sfx_kc41);
}
#undef MAXSHARDCOUNT
#undef SHARDROT
// For getting hit!
void K_PopPlayerShield(player_t *player)
@ -10583,6 +10647,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
mobj_t *shield = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BUBBLESHIELD);
P_SetScale(shield, (shield->destscale = (5*shield->destscale)>>2));
P_SetTarget(&shield->target, player->mo);
P_SetTarget(&player->mo->shieldtracer, shield);
S_StartSound(player->mo, sfx_s3k3f);
player->curshield = KSHIELD_BUBBLE;

View file

@ -68,6 +68,18 @@ mobj_t *boss3cap = NULL;
mobj_t *mobjcache = NULL;
// Bubble Shield overlay spritescales.
static fixed_t bubbleoverlayscales[4][2] = {
// All scales are raw 16.16 fixed-point numbers based on FRACUNIT.
{65536, 65536}, // 1.0, 1.0
{65536, 59667}, // 1.0, 0.9104477
{90593, 61623}, // 1.382352, 0.940298
{100231, 53798} // 1.529411, 0.820895
};
static statenum_t bubbledamagestates[5] = { S_INVISIBLE, S_BUBLSHLD_DMG_1, S_BUBLSHLD_DMG_2, S_BUBLSHLD_DMG_3, S_BUBLSHLD_DMG_4 };
void P_InitCachedActions(void)
{
actioncachehead.prev = actioncachehead.next = &actioncachehead;
@ -9159,11 +9171,27 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
case MT_BUBBLESHIELD:
{
fixed_t scale;
statenum_t curstate;
statenum_t curstate, overlaystate;
if (!mobj->target || !mobj->target->health || !mobj->target->player
|| mobj->target->player->curshield != KSHIELD_BUBBLE)
{
if ((mobj->target->shieldtracer) && (mobj->target->shieldtracer->mobjnum == mobj->mobjnum))
{
// We're being traced by this object; unset its shieldtracer.
P_SetTarget(&mobj->target->shieldtracer, NULL);
}
if (mobj->tracer && (!mobj->tracer->player))
{
// Remove the tracer (overlay)
if (!P_MobjWasRemoved(mobj->tracer))
{
P_RemoveMobj(mobj->tracer);
}
P_SetTarget(&mobj->tracer, NULL);
}
P_RemoveMobj(mobj);
return false;
}
@ -9171,6 +9199,11 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
scale = (5*mobj->target->scale)>>2;
curstate = ((mobj->tics == 1) ? (mobj->state->nextstate) : ((statenum_t)(mobj->state-states)));
// Lookup for rescaling the "bubble damage" overlay
UINT8 frame, scale_idx;
frame = scale_idx = 0;
if (mobj->target->player->bubbleblowup)
{
INT32 blow = mobj->target->player->bubbleblowup;
@ -9185,6 +9218,10 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
scale += (blow * ((3*scale)>>1)) / bubbletime;
mobj->frame = CLAMP(states[S_BUBBLESHIELDBLOWUP].frame + mobj->extravalue1, states[S_BUBBLESHIELDBLOWUP].frame, states[S_BUBBLESHIELDBLOWUP].frame + 3);
frame = mobj->frame & FF_FRAMEMASK;
scale_idx = CLAMP(frame - 9, 0, 3);
if ((mobj->target->player->bubbleblowup > bubbletime) && (leveltime & 1))
{
mobj->color = SKINCOLOR_WHITE;
@ -9247,6 +9284,9 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
{
mobj->frame = (states[S_BUBBLESHIELDBLOWUP].frame + mobj->extravalue1);
frame = mobj->frame & FF_FRAMEMASK;
scale_idx = CLAMP(frame - 9, 0, 3);
if (mobj->extravalue1 < 0 && (leveltime & 1))
mobj->extravalue1++;
else if (mobj->extravalue1 > 0)
@ -9270,15 +9310,92 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
mobj->extravalue2 = mobj->target->player->bubbleblowup;
P_SetScale(mobj, (mobj->destscale = scale));
mobj->pitch = mobj->target->pitch;
mobj->roll = mobj->target->roll;
if (mobj->tracer && (!P_MobjWasRemoved(mobj->tracer)))
{
if (mobj->tracer->type == MT_OVERLAY)
{
fixed_t health_scalar;
P_SetScale(mobj->tracer, mobj->destscale);
mobj->slopepitch = mobj->target->slopepitch;
mobj->sloperoll = mobj->target->sloperoll;
mobj->tracer->threshold |= OV_DONTXYSCALE;
mobj->tracer->spritexscale = FixedMul(mobj->spritexscale, bubbleoverlayscales[scale_idx][0]);
mobj->tracer->spriteyscale = FixedMul(mobj->spriteyscale, bubbleoverlayscales[scale_idx][1]);
health_scalar = mobj->target->player->bubblehealth * FRACUNIT / MAXBUBBLEHEALTH;
health_scalar = 5 - CLAMP(health_scalar / (FRACUNIT / 5), 1, 5);
overlaystate = ((mobj->tracer->tics == 1) ? (mobj->tracer->state->nextstate) : ((statenum_t)(mobj->tracer->state-states)));
// Depending on the level of damage done to the shield, show some cracks.
if (overlaystate != bubbledamagestates[health_scalar])
{
P_SetMobjState(mobj->tracer, bubbledamagestates[health_scalar]);
}
}
else
{
// Very silently detach ourselves from our tracer and try a
// damage-control fix.
P_SetTarget(&mobj->tracer, NULL);
mobj_t *spawn = P_SpawnMobj(mobj->x, mobj->z, mobj->z, MT_OVERLAY);
spawn->destscale = mobj->scale;
P_SetScale(spawn, mobj->scale);
P_SetTarget(&mobj->tracer, spawn);
P_SetTarget(&spawn->target, mobj);
}
}
// Don't sloperoll Bubble Shields since it messes with the shatter VFX
mobj->pitch = 0;
mobj->roll = 0;
mobj->slopepitch = 0;
mobj->sloperoll = 0;
P_MoveOrigin(mobj, mobj->target->x, mobj->target->y, mobj->target->z);
break;
}
case MT_BUBBLESHLD_DEBRIS:
{
statenum_t curstate;
curstate = ((mobj->tics == 1) ? (mobj->state->nextstate) : ((statenum_t)(mobj->state-states)));
if (curstate != S_INVISIBLE)
{
mobj->rollangle += ANG1 * 4;
}
else
{
mobj->rollangle = 0;
}
if ((P_IsObjectOnGround(mobj)) && (!mobj->threshold))
{
mobj->threshold = (TICRATE / 2);
if (curstate != S_INVISIBLE)
{
// "Despawn" and play the glass landing sound.
S_StartSound(mobj, mobj->info->activesound);
P_SetMobjState(mobj, S_INVISIBLE);
}
}
if (mobj->threshold)
{
mobj->threshold--;
if (!mobj->threshold)
{
P_RemoveMobj(mobj);
}
}
break;
}
case MT_FLAMESHIELD:
{
statenum_t curstate;
@ -10389,6 +10506,8 @@ void P_MobjThinker(mobj_t *mobj)
P_SetTarget(&mobj->hprev, NULL);
if (mobj->itnext && P_MobjWasRemoved(mobj->itnext))
P_SetTarget(&mobj->itnext, NULL);
if (mobj->shieldtracer && P_MobjWasRemoved(mobj->shieldtracer))
P_SetTarget(&mobj->shieldtracer, NULL);
if (mobj->flags & MF_NOTHINK)
return;
@ -11282,6 +11401,23 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
nummaprings++;
break;
// SRB2Kart
case MT_BUBBLESHIELD:
{
// Spawn in the damage overlay
mobj_t *spawn = P_SpawnMobj(mobj->x, mobj->z, mobj->z, MT_OVERLAY);
spawn->destscale = mobj->scale;
P_SetScale(spawn, mobj->scale);
P_SetTarget(&mobj->tracer, spawn);
P_SetTarget(&spawn->target, mobj);
break;
}
case MT_BUBBLESHLD_DEBRIS:
{
statenum_t shardset[] = {S_BUBBLEDEBRIS_1, S_BUBBLEDEBRIS_2, S_BUBBLEDEBRIS_3, S_BUBBLEDEBRIS_4, S_BUBBLEDEBRIS_5};
P_SetMobjState(mobj, shardset[P_RandomRange(0, 4)]);
break;
}
case MT_ITEMCAPSULE:
{
// set default item & count

View file

@ -387,6 +387,8 @@ struct mobj_t
mobj_t *tracer; // Thing being chased/attacked for tracers.
mobj_t *shieldtracer; // Blankart: Shield mobj, currently only for certain VFX
fixed_t friction;
fixed_t movefactor;
fixed_t gravity;

View file

@ -2118,6 +2118,7 @@ typedef enum
MD3_MISCCAP = 1<<1,
MD3_BAKEDOFFSET = 1<<2,
MD3_EXTVAL3 = 1<<3,
MD3_SHIELDTRACER = 1<<4,
} mobj_diff3_t;
typedef enum
@ -2435,6 +2436,8 @@ static void SaveMobjThinker(savebuffer_t *save, const thinker_t *th, const UINT8
diff3 |= MD3_BAKEDOFFSET;
if (mobj->extravalue3)
diff3 |= MD3_EXTVAL3;
if (mobj->shieldtracer)
diff3 |= MD3_SHIELDTRACER;
if (diff3 != 0)
diff2 |= MD2_MORE;
@ -2725,6 +2728,10 @@ static void SaveMobjThinker(savebuffer_t *save, const thinker_t *th, const UINT8
{
WRITEINT32(save->p, mobj->extravalue3);
}
if (diff3 & MD3_SHIELDTRACER)
{
WRITEUINT32(save->p, mobj->shieldtracer->mobjnum);
}
WRITEUINT32(save->p, mobj->mobjnum);
}
@ -4028,6 +4035,10 @@ static thinker_t* LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker, UINT8
{
mobj->extravalue3 = READINT32(save->p);
}
if (diff3 & MD3_SHIELDTRACER)
{
mobj->shieldtracer = (mobj_t *)(size_t)READUINT32(save->p);
}
// Reset some non-synch values
mobj->sloperoll = 0;
@ -5097,6 +5108,11 @@ static void P_RelinkPointers(void)
if (!RelinkMobj(&mobj->terrainOverlay))
CONS_Debug(DBG_GAMELOGIC, "terrainOverlay not found on %d\n", mobj->type);
}
if (mobj->shieldtracer)
{
if (!RelinkMobj(&mobj->shieldtracer))
CONS_Debug(DBG_GAMELOGIC, "shieldtracer not found on %d\n", mobj->type);
}
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i])