Classes:FastProjectile

From ZDoom Wiki
Jump to navigation Jump to search
Note: Wait! Stop! You do not need to copy this actor's code into your project! Here's why:
  1. This actor is already defined in GZDoom, there's no reason to define it again.
  2. In fact, trying to define an actor with the same name will cause an error (because it already exists).
  3. If you want to make your own version of this actor, use inheritance.
  4. Definitions for existing actors are put on the wiki for reference purpose only.
Fast projectile
Actor type Internal Game MiniZDoomLogoIcon.png (ZDoom)
DoomEd Number None Class Name FastProjectile


Classes: FastProjectile
 →BlasterFX1
 →CFlameMissile
 →MageWandMissile

A base class for all fast projectiles, with special movement code. Standard missile code doesn't manage speeds greater than about 60 very well. To create a fast-moving projectile, inherit from this class or one of its children classes.

The MissileType property has a special use for fast projectiles. When one is specified, several (at least eight) are spawned every tic in the projectile's wake, as a trail. The greater the speed, and the smaller (yet non-null) the radius, the more numerous the steps. See MageWandMissile for an example of use, and MageWandSmoke for how a trail actor might be defined.

The MissileHeight property also has a special use for fast projectiles. Normally, trails are spawned 8 map units lower than the projectile itself. Using this property, the trail's height can be adjusted to different heights. To spawn the trail at the same height as the projectile, use a MissileHeight of 8.

Ripper projectiles will also cause a lot of bloodshed, which can slow down a game's FPS quite considerably, so use with caution.

Trails spawned by FastProjectile inherit their pitch and set the projectile as their target. To get the owner (for explosions, ownership, etc), use the GETOWNER flag.

Specific features

FastProjectile is not entirely identical to regular projectiles; aside from special handling of collision for high velocities and the ability to spawn trails, they also have a few quirks authors should be aware of:

  • Fast projectiles can't be subjected to gravity; the gravity property and the NOGRAVITY flag values are ignored.
  • Due to the movement code, fast projectiles are destroyed if they are incapable of moving or hit any plane. Hence, bounce properties that imply losing momentum (such as Bouncetype "Doom") or other plane-related flags don't work.
  • Fast projectiles ignore the Death.Sky state sequence and always disappear when hitting a sky.

ZScript definition

Note: The ZScript definition below is for reference and may be different in the current version of GZDoom.The most up-to-date version of this code can be found on GZDoom GitHub.


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 (isFrozen())
			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())
			{
				VelFromAngle(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, false, 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;
				}
			}
		}
	}
}

DECORATE definition

Note: This is legacy code, kept for archival purposes only. DECORATE is deprecated in GZDoom and is completely superseded by ZScript. GZDoom internally uses the ZScript definition above.
Actor FastProjectile native
{
  Projectile
  MissileHeight 0
}