Puff
Puffs are actors spawned by hitscan attacks when the attack hits something: usually map geometry (wall/floor/plane), but in certain conditions they can also spawn when hitting actors.
Hitscan attacks encompass bullet attacks (fired by functions like A_FireBullets), railgun attacks (such as A_RailAttack) and melee attacks (such as A_CustomPunch).
Puffs have a few purposes:
- Modifying hitscan behavior. While when using projectiles things like damage types or flags like EXTREMEDEATH would be defined in the projectile, in case of hitscan attacks all of these definitions must be attached to the puff. Puffs can also use the DoSpecialDamage virtual function, like projectiles.
- Visuals. Puffs make hitscan attacks more interesting by creating a visual effect when hitting something. For example, in Doom bullet weapons will spawn a puff of smoke when hitting geometry. This effect can be expanded to make a puff spawn some extra visuals, like dynamic smoke effects, particles, sparks, etc.
- Sounds. Puffs can play different sounds depending on what they hit, which can provide nice audio feedback, especially for melee attacks.
There's a misconception that creating new puffs requires inheriting from BulletPuff. In reality, there's no special "puff" class. A puff is just an actor; it starts acting as a puff automatically when it's used by the appropriate attack functions. There are no special flags or properties that have to be given to the Actor in order for it to be useable as puff. In fact, even classes that aren't designed to be puffs, like items or monsters, can still be used as puffs and will spawn at the points where the attack hits.
Most of the time puffs don't need any collision and will use NOBLOCKMAP and NOGRAVITY. NOINTERACTION is usually possible as well, although it may sometimes cause weird positioning of the puff on top of floor/ceiling planes.
Puff usage and behavior
To become a puff, an actor must be spawned with the SpawnPuff function, or any of the hitscan-firing functions such as A_FireBullets. When spawned this way, these actors get the following properties:
- They get the ISPUFF flag, and the the actor's
DamageSource
field will contain a pointer to the actor responsible for spawning the puff. - They will pass their properties such as DamageType and damage-related flags (such as EXTREMEDEATH, FORCEPAIN and others) to the attack.
- They can utilize VSpeed property to automatically float upwards when they spawn.
- Puffs may be replaced by particles when spawned if the ALLOWPARTICLES flag is set (and the player has the appropriate option enabled in their configuration settings).
- Most importantly, puffs will automatically utilize special sounds and special states in their behavior.
Puff states and flags
Puffs have deceptively complex behavior when it comes to picking states. This behavior is tied to several aspects:
- Whether the puff has ALWAYSPUFF and/or PUFFONACTORS flags;
- What type of attack created it. There are 3 types:
- 1. Bullets - fired by LineAttack, A_FireBullets, A_CustomBulletAttack or built-in functions based on those (like A_FirePistol)
- 2. Rails - fired by RailAttack, A_RailAttack or A_CustomRailgun
- 3. Melee - performed by A_CustomPunch, A_CustomMeleeAttack or similar
Puff state entered | |||||||||
---|---|---|---|---|---|---|---|---|---|
Bullet attack | Rail attack | Melee attack | |||||||
No flags | ALWAYSPUFF | PUFFONACTORS | No flags | ALWAYSPUFF | PUFFONACTORS | No flags | ALWAYSPUFF | PUFFONACTORS | |
Hit a shootable non-bleeding actor (Non-bleeding requires one of the flollowing flags: NOBLOOD, INVULNERABLE, DORMANT.) |
Spawn | Spawn | Spawn | Spawn | Spawn | Spawn | Melee | Melee | Melee |
Hit a shootable bleeding actor | none | none | XDeath | none | XDeath | none | Melee (animation isn't played, pointers aren't obtained) |
Melee (animation isn't played, pointers aren't obtained) |
XDeath |
Hit geometry | Crash (if present) | Crash (if present) | Crash (if present) | none | Crash (if present) | none | Crash (if present) | Crash (if present) | Crash (if present) |
Hit nothing (If the attack has limited range and ends in the air. Normally this happens with melee attacks, but hitscan/railgun also can have a limited range specified.) |
none | Crash (if present) | none | none | none | none | none | Crash (if present) | none |
Note:
- If Crash of XDeath states are supposed to be entered but aren't defined in the puff, Spawn will be used instead.
- ALWAYSPUFF and PUFFONACTORS can be combined. For bullets and nelee attacks it means that the puff will be able to both enter XDeath when hitting a bleeding actor, and Crash will be used when hitting nothing.
- For rail attacks ALWAYSPUFF functions the same way as PUFFONACTORS for other attacks, while PUFFONACTORS has no effect on them. It's also not possible to force rail puffs to trigger when hitting nothing.
- Even when the puff gets replaced by blood (which is the default behavior when an attack hits a bleeding actor), it'll still call its DoSpecialDamage virtual.
Puff sounds
Puff can also play SeeSound, AttackSound or ActiveSound under certain conditions:
Puff sound played | |||||||||
---|---|---|---|---|---|---|---|---|---|
Bullet attack | Rail attack | Melee attack | |||||||
No flags | ALWAYSPUFF | PUFFONACTORS | No flags | ALWAYSPUFF | PUFFONACTORS | No flags | ALWAYSPUFF | PUFFONACTORS | |
Hit a shootable non-bleeding actor (Non-bleeding requires one of the flollowing flags: NOBLOOD, INVULNERABLE, DORMANT.) |
SeeSound | SeeSound | SeeSound | SeeSound | SeeSound | SeeSound | SeeSound | SeeSound | SeeSound |
Hit a shootable bleeding actor | none | none | SeeSound | none | SeeSound | none | none | none | SeeSound |
Hit geometry | AttackSound | AttackSound | AttackSound | none | AttackSound | none | AttackSound | AttackSound | AttackSound |
Hit nothing (If the attack has limited range and ends in the air. Normally this happens with melee attacks, but hitscan/railgun also can have a limited range specified.) |
none | AttackSound | none | none | none | none | ActiveSound | AttackSound AND ActiveSound | ActiveSound |
Note that sounds don't follow any fallback rules, in contrast to states: if a specific sound isn't defined, it simply won't play; no different sound will be used instead.
Puff pointers
Puffs can utilize actor pointers under the following conditions:
- By default, a puff will have a readonly
DamageSource
pointer to the actor responsible for the attack that spawned the puff. Prior to 4.13.0, puffs did not get any pointers by default at all. - With PUFFGETSOWNER flag, the puff will have a
target
pointer to the actor responsible for the attacked (similar to projectiles). - Puffs, just like projectiles, utilize the DoSpecialDamage virtual function, which can be overridden to add special behavior. Within the context of that function, the puff will have a local
victim
pointer to the actor it hit. - HITTRACER/HITMASTER/HITTARGET flags will write a pointer to the actor hit by the attack to the puff's
tracer
/master
/target
field respectively.
Further customization
There are several extra features that can be utilized on puffs:
- Using the PUFFONACTORS will make the puff respect the three HIT* flags (HITTRACER, HITTARGET and HITMASTER): this allows performing thigs with the actor that was hit using the obtained pointer (with HITTRACER it'll be tracer) and one of the states entered when the attack hits an actor (such as Melee).
- Using PUFFGETSOWNER flag will set whoever used the attack as the target of the puff, which allows performing conditional checks and such based on the shooter's class and properties.
- The hitscan attack can be made to pass through certain actors when using flags like MTHRUSPECIES, THRUGHOST and such.
Note that puffs are spawned so as to face the originator of the attack.