Invincible player punts hazardous/solid things, unless MF_ELEMENTAL

If player is in one of these states:

- Invincibility
- Grow (K_IsBigger)
- Flame Shield dash
- Over 200% speed

And the other object:

- Does not have MF_DONTPUNT

Then, touching a solid object:

- Punts the object, unless the object has MF_ELEMENTAL
- Fizzles the object, if the object has MF_ELEMENTAL

Or, when an object damages the player:

- That object is punted, unless it has MF_ELEMENTAL
- The object fizzles, if it has MF_ELEMENTAL

Punting means:

- A copy of the object is made
- Both the player and copy receive 5 tics of hitlag
- The copy is thrust away from the player at a minimum of
  60 FU, or 2x either the player's or object's momentum,
  whichever is ultimately greater
- The copy despawns after 2 seconds
- The copy flickers constantly, while thrust away

Fizzling means:

- The object disappears completely
- A puff of smoke is spawned in place of the object
- No hitlag is applied to the player

Both punting and fizzling:

- Hide the original object (intangible and invisible)
- The original object reppears after 30 seconds
- For 2 seconds before reappearing, the object flickers
  back in, but is still intangible
This commit is contained in:
James R 2023-11-13 01:04:55 -08:00
parent a8a1c14580
commit 61cad641bb
12 changed files with 192 additions and 2 deletions

View file

@ -18,6 +18,7 @@
#include "k_podium.h" #include "k_podium.h"
#include "k_powerup.h" #include "k_powerup.h"
#include "k_hitlag.h" #include "k_hitlag.h"
#include "m_random.h"
angle_t K_GetCollideAngle(mobj_t *t1, mobj_t *t2) angle_t K_GetCollideAngle(mobj_t *t1, mobj_t *t2)
{ {
@ -1170,3 +1171,114 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2)
return false; return false;
} }
void K_PuntHazard(mobj_t *t1, mobj_t *t2)
{
// TODO: spawn a unique mobjtype other than MT_GHOST
mobj_t *img = P_SpawnGhostMobj(t1);
K_MakeObjectReappear(t1);
img->flags &= ~MF_NOGRAVITY;
img->renderflags = t1->renderflags & ~RF_DONTDRAW;
img->extravalue1 = 1;
img->extravalue2 = 2;
img->fuse = 2*TICRATE;
struct Vector
{
fixed_t x_, y_, z_;
fixed_t h_ = FixedHypot(x_, y_);
fixed_t speed_ = std::max(60 * mapobjectscale, FixedHypot(h_, z_) * 2);
explicit Vector(fixed_t x, fixed_t y, fixed_t z) : x_(x), y_(y), z_(z) {}
explicit Vector(const mobj_t* mo) :
Vector(std::max(
Vector(mo->x - mo->old_x, mo->y - mo->old_y, mo->z - mo->old_z),
Vector(mo->momx, mo->momy, mo->momz)
))
{
}
explicit Vector(const Vector&) = default;
bool operator<(const Vector& b) const { return speed_ < b.speed_; }
void invert()
{
x_ = -x_;
y_ = -y_;
z_ = -z_;
}
void thrust(mobj_t* mo) const
{
angle_t yaw = R_PointToAngle2(0, 0, h_, z_);
yaw = std::max(AbsAngle(yaw), static_cast<angle_t>(ANGLE_11hh)) + (yaw & ANGLE_180);
P_InstaThrust(mo, R_PointToAngle2(0, 0, x_, y_), FixedMul(speed_, FCOS(yaw)));
mo->momz = FixedMul(speed_, FSIN(yaw));
}
};
Vector h_vector(t1);
Vector p_vector(t2);
h_vector.invert();
std::max(h_vector, p_vector).thrust(img);
K_DoPowerClash(img, t2); // applies hitlag
P_SpawnMobj(t2->x/2 + t1->x/2, t2->y/2 + t1->y/2, t2->z/2 + t1->z/2, MT_ITEMCLASH);
}
boolean K_PuntCollide(mobj_t *t1, mobj_t *t2)
{
if (t1->flags & MF_DONTPUNT)
{
return false;
}
if (!t2->player)
{
return false;
}
if (!K_PlayerCanPunt(t2->player))
{
return false;
}
if (t1->flags & MF_ELEMENTAL)
{
K_MakeObjectReappear(t1);
// copied from MT_ITEMCAPSULE
UINT8 i;
INT16 spacing = (t1->radius >> 1) / t1->scale;
// dust effects
for (i = 0; i < 10; i++)
{
mobj_t *puff = P_SpawnMobjFromMobj(
t1,
P_RandomRange(PR_ITEM_DEBRIS, -spacing, spacing) * FRACUNIT,
P_RandomRange(PR_ITEM_DEBRIS, -spacing, spacing) * FRACUNIT,
P_RandomRange(PR_ITEM_DEBRIS, 0, 4*spacing) * FRACUNIT,
MT_SPINDASHDUST
);
puff->momz = puff->scale * P_MobjFlip(puff);
P_Thrust(puff, R_PointToAngle2(t2->x, t2->y, puff->x, puff->y), 3*puff->scale);
puff->momx += t2->momx / 2;
puff->momy += t2->momy / 2;
puff->momz += t2->momz / 2;
}
}
else
{
K_PuntHazard(t1, t2);
}
return true;
}

View file

@ -33,6 +33,9 @@ boolean K_SMKIceBlockCollide(mobj_t *t1, mobj_t *t2);
boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2); boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2);
void K_PuntHazard(mobj_t *t1, mobj_t *t2);
boolean K_PuntCollide(mobj_t *t1, mobj_t *t2);
#ifdef __cplusplus #ifdef __cplusplus
} // extern "C" } // extern "C"
#endif #endif

View file

@ -12636,4 +12636,34 @@ boolean K_IsPlayingDisplayPlayer(player_t *player)
return P_IsDisplayPlayer(player) && (!player->exiting); return P_IsDisplayPlayer(player) && (!player->exiting);
} }
boolean K_PlayerCanPunt(player_t *player)
{
if (player->invincibilitytimer > 0)
{
return true;
}
if (player->flamedash > 0 && player->itemtype == KITEM_FLAMESHIELD)
{
return true;
}
if (player->growshrinktimer > 0)
{
return true;
}
if (player->tripwirePass >= TRIPWIRE_BLASTER && player->speed >= 2 * K_GetKartSpeed(player, false, false))
{
return true;
}
return false;
}
void K_MakeObjectReappear(mobj_t *mo)
{
(!P_MobjWasRemoved(mo->punt_ref) ? mo->punt_ref : mo)->reappear = leveltime + (30*TICRATE);
}
//} //}

View file

@ -245,6 +245,9 @@ void K_SetTireGrease(player_t *player, tic_t tics);
boolean K_IsPlayingDisplayPlayer(player_t *player); boolean K_IsPlayingDisplayPlayer(player_t *player);
boolean K_PlayerCanPunt(player_t *player);
void K_MakeObjectReappear(mobj_t *mo);
#ifdef __cplusplus #ifdef __cplusplus
} // extern "C" } // extern "C"
#endif #endif

View file

@ -43,6 +43,7 @@
#include "k_hitlag.h" #include "k_hitlag.h"
#include "acs/interface.h" #include "acs/interface.h"
#include "k_powerup.h" #include "k_powerup.h"
#include "k_collide.h"
// CTF player names // CTF player names
#define CTFTEAMCODE(pl) pl->ctfteam ? (pl->ctfteam == 1 ? "\x85" : "\x84") : "" #define CTFTEAMCODE(pl) pl->ctfteam ? (pl->ctfteam == 1 ? "\x85" : "\x84") : ""
@ -2929,6 +2930,14 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
invincible = false; invincible = false;
} }
// TODO: doing this from P_DamageMobj limits punting to objects that damage the player.
// And it may be kind of yucky.
// But this is easier than accounting for every condition in PIT_CheckThing!
if (inflictor && K_PuntCollide(inflictor, target))
{
return false;
}
if (invincible && type != DMG_STUMBLE && type != DMG_WHUMBLE) if (invincible && type != DMG_STUMBLE && type != DMG_WHUMBLE)
{ {
const INT32 oldHitlag = target->hitlag; const INT32 oldHitlag = target->hitlag;

View file

@ -595,6 +595,8 @@ mobj_t *P_FindMobjFromTID(mtag_t tid, mobj_t *i, mobj_t *activator);
void P_DeleteMobjStringArgs(mobj_t *mobj); void P_DeleteMobjStringArgs(mobj_t *mobj);
tic_t P_MobjIsReappearing(const mobj_t *mobj);
#ifdef __cplusplus #ifdef __cplusplus
} // extern "C" } // extern "C"
#endif #endif

View file

@ -563,6 +563,10 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
if ((thing->flags & MF_NOCLIPTHING) || !(thing->flags & (MF_SOLID|MF_SPECIAL|MF_PAIN|MF_SHOOTABLE|MF_SPRING))) if ((thing->flags & MF_NOCLIPTHING) || !(thing->flags & (MF_SOLID|MF_SPECIAL|MF_PAIN|MF_SHOOTABLE|MF_SPRING)))
return BMIT_CONTINUE; return BMIT_CONTINUE;
// Thing is respawning
if (P_MobjIsReappearing(thing))
return BMIT_CONTINUE;
blockdist = thing->radius + tm.thing->radius; blockdist = thing->radius + tm.thing->radius;
if (abs(thing->x - tm.x) >= blockdist || abs(thing->y - tm.y) >= blockdist) if (abs(thing->x - tm.x) >= blockdist || abs(thing->y - tm.y) >= blockdist)
@ -1631,7 +1635,10 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
if (tm.thing->z + tm.thing->height < thing->z) if (tm.thing->z + tm.thing->height < thing->z)
return BMIT_CONTINUE; // underneath return BMIT_CONTINUE; // underneath
if (!K_PuntCollide(thing, tm.thing))
{
K_KartSolidBounce(tm.thing, thing); K_KartSolidBounce(tm.thing, thing);
}
return BMIT_CONTINUE; return BMIT_CONTINUE;
} }
} }
@ -2346,7 +2353,8 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y, TryMoveResult_t *re
// Check things first, possibly picking things up. // Check things first, possibly picking things up.
// MF_NOCLIPTHING: used by camera to not be blocked by things // MF_NOCLIPTHING: used by camera to not be blocked by things
if (!(thing->flags & MF_NOCLIPTHING)) // Respawning things should also be intangible to other things
if (!(thing->flags & MF_NOCLIPTHING) && !P_MobjIsReappearing(thing))
{ {
for (bx = xl; bx <= xh; bx++) for (bx = xl; bx <= xh; bx++)
{ {

View file

@ -15381,3 +15381,9 @@ void P_DeleteMobjStringArgs(mobj_t *mobj)
mobj->script_stringargs[i] = NULL; mobj->script_stringargs[i] = NULL;
} }
} }
tic_t P_MobjIsReappearing(const mobj_t *mobj)
{
tic_t t = (!P_MobjWasRemoved(mobj->punt_ref) ? mobj->punt_ref : mobj)->reappear;
return t - min(leveltime, t);
}

View file

@ -437,7 +437,12 @@ struct mobj_t
boolean frozen; boolean frozen;
// Object was punted and is temporarily invisible and
// intangible. This is the leveltime that it will
// reappear.
tic_t reappear; tic_t reappear;
// If punt_ref, set punt_ref->reappear, treat as if this->reappear
mobj_t *punt_ref; mobj_t *punt_ref;
// WARNING: New fields must be added separately to savegame and Lua. // WARNING: New fields must be added separately to savegame and Lua.

View file

@ -1166,6 +1166,9 @@ mobj_t *P_SpawnGhostMobj(mobj_t *mobj)
K_ReduceVFX(ghost, mobj->player); K_ReduceVFX(ghost, mobj->player);
ghost->reappear = mobj->reappear;
P_SetTarget(&ghost->punt_ref, mobj->punt_ref);
return ghost; return ghost;
} }

View file

@ -3746,6 +3746,12 @@ boolean R_ThingVisible (mobj_t *thing)
if (r_viewmobj && (thing == r_viewmobj || (r_viewmobj->player && r_viewmobj->player->followmobj == thing))) if (r_viewmobj && (thing == r_viewmobj || (r_viewmobj->player && r_viewmobj->player->followmobj == thing)))
return false; return false;
if (tic_t t = P_MobjIsReappearing(thing))
{
// Flicker back in
return t <= 2*TICRATE && (leveltime & 1);
}
if ((viewssnum == 0 && (thing->renderflags & RF_DONTDRAWP1)) if ((viewssnum == 0 && (thing->renderflags & RF_DONTDRAWP1))
|| (viewssnum == 1 && (thing->renderflags & RF_DONTDRAWP2)) || (viewssnum == 1 && (thing->renderflags & RF_DONTDRAWP2))
|| (viewssnum == 2 && (thing->renderflags & RF_DONTDRAWP3)) || (viewssnum == 2 && (thing->renderflags & RF_DONTDRAWP3))

View file

@ -948,6 +948,9 @@ boolean S_AdjustSoundParams(const mobj_t *listener, const mobj_t *source, INT32
if (!listener) if (!listener)
return false; return false;
if (source->thinker.function.acp1 == (actionf_p1)P_MobjThinker && P_MobjIsReappearing(source))
return false;
// Init listensource with default listener // Init listensource with default listener
listensource.x = listener->x; listensource.x = listener->x;
listensource.y = listener->y; listensource.y = listener->y;