A_FireBullets

From ZDoom Wiki
Jump to navigation Jump to search
Note: This function is for the Weapon class. For monster hitscan attacks see A_CustomBulletAttack.


StateProvider

action void A_FireBullets(double spread_xy, double spread_z, int numbullets, int damageperbullet, class<Actor> pufftype = "BulletPuff", int flags = 1, double range = 0, class<Actor> missile = null, double Spawnheight = 32, double Spawnofs_xy = 0)

Usage

Defines a custom hitscan attack for weapons. You have to specify the horizontal and vertical spread, the amount of bullets and the damage per bullet.

When successfully called, the function plays the weapon's AttackSound sound, if present, on the weapon channel (CHAN_WEAPON) with normal attenuation.

Notes:

  • This function utilizes Player.AttackZOffset for positioning the origin of the bullet. Use this to adjust the height where bullets come from on the player for accuracy.
  • If the number of bullets is 1, by default it'll ignore the spread values if this is the first bullet fired. See below for more details.
  • Damage types cannot be defined in this function directly; they must be defined through a custom puff class.

Parameters

  • double spread_xy
The random spread going right and left.
  • double spread_z
The random spread going up and down.
  • int numbullets
The number of "bullets" (hitscan rays) this function fires. Note, there are several rules applied here depending on the value of this field:
  • 0: fires one bullet, always with perfect accuracy, ignoring the spread values.
  • 1: If this is the first bullet fired from the weapon (A_ReFire hasn't been called yet; the refire field in the PlayerInfo struct will be 0), the bullet is fired with perfect accuracy. After A_ReFire has been called once, the bullet will be subjected to regular spread until the player lets go of the attack button and player.refire resets back to 0. (This is why the first shot from Doom's Pistol and Chaingun is perfectly accurate.)
  • More than 1: when multiple bullets are fired, spread is always applied.
  • -1: fires one bullet, but the spread is always applied, even if it's the first bullet. Using smaller values, like -2, -3 and so on, is also possible, but the effect will be the same as using positive values, since multiple bullets are always fired with a spread.
  • int damageperbullet
The amount of damage to deal per bullet. Damage is multiplied by random(1, 3) (unless the FBF_NORANDOM flag is used).
  • class<Actor> pufftype
The puff to spawn at the point of impact. Default is 'BulletPuff'.
  • EFireBulletsFlags flags
Flags that modify the behavior of the function. (Multiple flags can be combined with |.) Flags used by this function are defined in the EFireBulletsFlags enum. Possible flags:
  • FBF_USEAMMO — If set, the attack uses ammo. This flag is set by default if the flags parameter. Passing 0 as the flags argument (or passing other flags without adding this one) will disable it.
  • FBF_EXPLICITANGLE — If set, the horizontal and vertical spread are used as explicitly stated, instead of being used as a range for random spread.
  • FBF_NOFLASH — If set, the attack does not cause a weapon flash.
  • FBF_NOPITCH — If set, the vertical angle is not adjusted to aim at the target.
  • FBF_NORANDOM — If set, the damage is not multiplied by 1d3.
  • FBF_NORANDOMPUFFZ — If set, the random z offset given to the puff when spawned is disabled.
  • FBF_PUFFTARGET — Only works when missile is used. Sets the puff as the missile's target.
  • FBF_PUFFMASTER — Only works when missile is used. Sets the puff as the missile's master.
  • FBF_PUFFTRACER — Only works when missile is used. Sets the puff as the missile's tracer.
NOTE: The pointer flags will not work if the puff does not exist, for example if the attack hits a bleeding actor, and Blood is spawned instead. Puffs can be forced to always spawn with ALWAYSPUFF and PUFFONACTORS flags (which requires a custom puff actor).
  • double range
The maximum distance the bullets can hit something. The default value of 0 is interpreted as the PLAYERMISSILERANGE constant, which is equal to 8192.
  • class<Actor> missile
The projectile actor class to spawn. This actor faces the point of hitscan impact and travels directly towards it. Spawning a missile does not consume extra ammo. Default is null (no projectile is spawned).
  • double spawnheight
Offsets how high up from the base of the player the missile spawns. Default is 32.
  • double spawnofs_xy
Offsets how far to the calling actor's right to spawn the missile from (assuming one is viewing the actor from behind). Negative values spawn it to the left. Default is 0.

Examples

ZScript:

Fire:
     TRIF A 5 Bright A_FireBullets(0, 0, 1, 45, "RiflePuff", FBF_USEAMMO|FBF_NORANDOM);
     TRIF B 5 Bright;
     TRIG A 10;
     TRIG B 0 A_ReFire;
     Goto Ready;

DECORATE (deprecated):

Fire:
     TRIF A 5 Bright A_FireBullets(0, 0, 1, 45, "RiflePuff", FBF_USEAMMO|FBF_NORANDOM)
     TRIF B 5 Bright
     TRIG A 10
     TRIG B 0 A_ReFire
     Goto Ready

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.
	action void A_FireBullets(double spread_xy, double spread_z, int numbullets, int damageperbullet, class<Actor> pufftype = "BulletPuff", int flags = 1, double range = 0, class<Actor> missile = null, double Spawnheight = 32, double Spawnofs_xy = 0)
	{
		let player = player;
		if (!player) return;

		let pawn = PlayerPawn(self);
		let weapon = player.ReadyWeapon;

		int i;
		double bangle;
		double bslope = 0.;
		int laflags = (flags & FBF_NORANDOMPUFFZ)? LAF_NORANDOMPUFFZ : 0;
		FTranslatedLineTarget t;

		if ((flags & FBF_USEAMMO) && weapon &&  stateinfo != null && stateinfo.mStateType == STATE_Psprite)
		{
			if (!weapon.DepleteAmmo(weapon.bAltFire, true))
				return;	// out of ammo
		}
		
		if (range == 0)	range = PLAYERMISSILERANGE;

		if (!(flags & FBF_NOFLASH)) pawn.PlayAttacking2 ();

		if (!(flags & FBF_NOPITCH)) bslope = BulletSlope();
		bangle = Angle;

		if (pufftype == NULL) pufftype = 'BulletPuff';

		if (weapon != NULL)
		{
			A_StartSound(weapon.AttackSound, CHAN_WEAPON);
		}

		if ((numbullets == 1 && !player.refire) || numbullets == 0)
		{
			int damage = damageperbullet;

			if (!(flags & FBF_NORANDOM))
				damage *= random[cabullet](1, 3);

			let puff = LineAttack(bangle, range, bslope, damage, 'Hitscan', pufftype, laflags, t);

			if (missile != null)
			{
				bool temp = false;
				double ang = Angle - 90;
				Vector2 ofs = AngleToVector(ang, Spawnofs_xy);
				Actor proj = SpawnPlayerMissile(missile, bangle, ofs.X, ofs.Y, Spawnheight);
				if (proj)
				{
					if (!puff)
					{
						temp = true;
						puff = LineAttack(bangle, range, bslope, 0, 'Hitscan', pufftype, laflags | LAF_NOINTERACT, t);
					}
					AimBulletMissile(proj, puff, flags, temp, false);
					if (t.unlinked)
					{
						// Arbitary portals will make angle and pitch calculations unreliable.
						// So use the angle and pitch we passed instead.
						proj.Angle = bangle;
						proj.Pitch = bslope;
						proj.Vel3DFromAngle(proj.Speed, proj.Angle, proj.Pitch);
					}
				}
			}
		}
		else 
		{
			if (numbullets < 0)
				numbullets = 1;
			for (i = 0; i < numbullets; i++)
			{
				double pangle = bangle;
				double slope = bslope;

				if (flags & FBF_EXPLICITANGLE)
				{
					pangle += spread_xy;
					slope += spread_z;
				}
				else
				{
					pangle += spread_xy * Random2[cabullet]() / 255.;
					slope += spread_z * Random2[cabullet]() / 255.;
				}

				int damage = damageperbullet;

				if (!(flags & FBF_NORANDOM))
					damage *= random[cabullet](1, 3);

				let puff = LineAttack(pangle, range, slope, damage, 'Hitscan', pufftype, laflags, t);

				if (missile != null)
				{
					bool temp = false;
					double ang = Angle - 90;
					Vector2 ofs = AngleToVector(ang, Spawnofs_xy);
					Actor proj = SpawnPlayerMissile(missile, bangle, ofs.X, ofs.Y, Spawnheight);
					if (proj)
					{
						if (!puff)
						{
							temp = true;
							puff = LineAttack(bangle, range, bslope, 0, 'Hitscan', pufftype, laflags | LAF_NOINTERACT, t);
						}
						AimBulletMissile(proj, puff, flags, temp, false);
						if (t.unlinked)
						{
							// Arbitary portals will make angle and pitch calculations unreliable.
							// So use the angle and pitch we passed instead.
							proj.Angle = bangle;
							proj.Pitch = bslope;
							proj.Vel3DFromAngle(proj.Speed, proj.Angle, proj.Pitch);
						}
					}
				}
			}
		}
	}