mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-11-27 22:33:17 +00:00
99479d84b1
Without the test for ceiling hit fast projectile could enter its Death state every tick infinitely https://forum.zdoom.org/viewtopic.php?t=63023
201 lines
4.2 KiB
Text
201 lines
4.2 KiB
Text
// Fast projectiles --------------------------------------------------------
|
|
|
|
class FastProjectile : Actor
|
|
{
|
|
Default
|
|
{
|
|
Projectile;
|
|
MissileHeight 0;
|
|
}
|
|
|
|
|
|
virtual void Effect()
|
|
{
|
|
class<Actor> trail = MissileName;
|
|
if (trail != null)
|
|
{
|
|
double hitz = pos.z - 8;
|
|
|
|
if (hitz < floorz)
|
|
{
|
|
hitz = floorz;
|
|
}
|
|
// Do not clip this offset to the floor.
|
|
hitz += MissileHeight;
|
|
|
|
Actor act = Spawn (trail, (pos.xy, hitz), ALLOW_REPLACE);
|
|
if (act != null)
|
|
{
|
|
if (bGetOwner && target != null)
|
|
act.target = target;
|
|
else
|
|
act.target = self;
|
|
|
|
act.angle = angle;
|
|
act.pitch = pitch;
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// AFastProjectile :: Tick
|
|
//
|
|
// Thinker for the ultra-fast projectiles used by Heretic and Hexen
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
override void Tick ()
|
|
{
|
|
ClearInterpolation();
|
|
double oldz = pos.Z;
|
|
|
|
if (!bNoTimeFreeze)
|
|
{
|
|
//Added by MC: Freeze mode.
|
|
if (globalfreeze || level.Frozen)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// [RH] Ripping is a little different than it was in Hexen
|
|
FCheckPosition tm;
|
|
tm.DoRipping = bRipper;
|
|
|
|
int count = 8;
|
|
if (radius > 0)
|
|
{
|
|
while (abs(Vel.X) >= radius * count || abs(Vel.Y) >= radius * count)
|
|
{
|
|
// we need to take smaller steps.
|
|
count += count;
|
|
}
|
|
}
|
|
|
|
if (height > 0)
|
|
{
|
|
while (abs(Vel.Z) >= height * count)
|
|
{
|
|
count += count;
|
|
}
|
|
}
|
|
|
|
// Handle movement
|
|
bool ismoved = Vel != (0, 0, 0)
|
|
// Check Z position set during previous tick.
|
|
// It should be strictly equal to the argument of SetZ() function.
|
|
|| ( (pos.Z != floorz ) /* Did it hit the floor? */
|
|
&& (pos.Z != ceilingz - Height) /* Did it hit the ceiling? */ );
|
|
|
|
if (ismoved)
|
|
{
|
|
// force some lateral movement so that collision detection works as intended.
|
|
if (bMissile && Vel.X == 0 && Vel.Y == 0 && !IsZeroDamage())
|
|
{
|
|
Vel.X = MinVel;
|
|
}
|
|
|
|
Vector3 frac = Vel / count;
|
|
int changexy = frac.X != 0 || frac.Y != 0;
|
|
int ripcount = count / 8;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
if (changexy)
|
|
{
|
|
if (--ripcount <= 0)
|
|
{
|
|
tm.ClearLastRipped(); // [RH] Do rip damage each step, like Hexen
|
|
}
|
|
|
|
if (!TryMove (Pos.XY + frac.XY, true, NULL, tm))
|
|
{ // Blocked move
|
|
if (!bSkyExplode)
|
|
{
|
|
let l = tm.ceilingline;
|
|
if (l &&
|
|
l.backsector &&
|
|
l.backsector.GetTexture(sector.ceiling) == skyflatnum)
|
|
{
|
|
let posr = PosRelative(l.backsector);
|
|
if (pos.Z >= l.backsector.ceilingplane.ZatPoint(posr.XY))
|
|
{
|
|
// Hack to prevent missiles exploding against the sky.
|
|
// Does not handle sky floors.
|
|
Destroy ();
|
|
return;
|
|
}
|
|
}
|
|
// [RH] Don't explode on horizon lines.
|
|
if (BlockingLine != NULL && BlockingLine.special == Line_Horizon)
|
|
{
|
|
Destroy ();
|
|
return;
|
|
}
|
|
}
|
|
|
|
ExplodeMissile (BlockingLine, BlockingMobj);
|
|
return;
|
|
}
|
|
}
|
|
AddZ(frac.Z);
|
|
UpdateWaterLevel ();
|
|
oldz = pos.Z;
|
|
if (oldz <= floorz)
|
|
{ // Hit the floor
|
|
|
|
if (floorpic == skyflatnum && !bSkyExplode)
|
|
{
|
|
// [RH] Just remove the missile without exploding it
|
|
// if this is a sky floor.
|
|
Destroy ();
|
|
return;
|
|
}
|
|
|
|
SetZ(floorz);
|
|
HitFloor ();
|
|
Destructible.ProjectileHitPlane(self, SECPART_Floor);
|
|
ExplodeMissile (NULL, NULL);
|
|
return;
|
|
}
|
|
if (pos.Z + height > ceilingz)
|
|
{ // Hit the ceiling
|
|
|
|
if (ceilingpic == skyflatnum && !bSkyExplode)
|
|
{
|
|
Destroy ();
|
|
return;
|
|
}
|
|
|
|
SetZ(ceilingz - Height);
|
|
Destructible.ProjectileHitPlane(self, SECPART_Ceiling);
|
|
ExplodeMissile (NULL, NULL);
|
|
return;
|
|
}
|
|
CheckPortalTransition();
|
|
if (changexy && ripcount <= 0)
|
|
{
|
|
ripcount = count >> 3;
|
|
|
|
// call the 'Effect' method.
|
|
Effect();
|
|
}
|
|
}
|
|
}
|
|
if (!CheckNoDelay())
|
|
return; // freed itself
|
|
// Advance the state
|
|
if (tics != -1)
|
|
{
|
|
if (tics > 0) tics--;
|
|
while (!tics)
|
|
{
|
|
if (!SetState (CurState.NextState))
|
|
{ // mobj was removed
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|