BOOM Headshots. In DECORATE. Nothing (even remotely) fancy.

Handy guides on how to do things, written by users for users.

Moderators: GZDoom Developers, Raze Developers

Forum rules
Please don't start threads here asking for help. This forum is not for requesting guides, only for posting them. If you need help, the Editing forum is for you.
Double(Super)Shotgun
Posts: 75
Joined: Fri Jul 25, 2014 2:16 pm

BOOM Headshots. In DECORATE. Nothing (even remotely) fancy.

Post by Double(Super)Shotgun »

For better or worse, one of the main things I've wanted to do with my project from the beginning was incorporate location-based damage, at least headshots. So recently having some time, I decided to finally figure something out, or at least do some educated begging :)

Eventually I came across this thread, in which OP was asking about a method for achieving headshots which centered around spawning invisible child actors which would act as hitboxes, mainly using A_SpawnItemEx. With a little tweaking, this worked! After I first posted this tutorial, board member Mikk- showed me some things that could be used to greatly improve the system. So far I've successfully applied it to two monsters, and I'd like to demonstrate how it's done.

The OP of the aforementioned thread, CaptainToenail, called his sample monster TestZombie, and I think that's a pretty good name. So start by declaring a new actor that inherits the attributes of a Former Human, ZombieMan. You can import his attributes pretty much wholesale; the only thing we'll need to modify in the monster definition itself are States.

Code: Select all

ACTOR TestZombie : ZombieMan replaces ZombieMan
{
    States
    {
    Spawn:
        TNT1 AA 0 A_SpawnItemEx("HeadshotHitbox", 0,0,0, 0,0,0, 0, SXF_SETMASTER) 	
        Goto Super::Spawn
    Death.HeadShot:
        POSS HHHHH 0 A_SpawnItemEx("Blood",0,0,45,frandom(-2.0,2.0),frandom(-2.0,2.0),frandom(1.5,5.0))
        POSS H 2 
        POSS I 2 A_Scream 
        POSS J 2 
        POSS K 2 
        POSS L -1 A_NoBlocking
        Stop
    }
}
TestZombie inherits the Zombieman class and replaces Zombieman in the editor. If you want to make it usable in Doom Builder, give it an editor number instead of "replaces ZombieMan".

Then we declared a zero-duration blank frame using A_SpawnItemEx to spawn our headshot hitbox. We're setting the item to be spawned as "HeadshotHitbox", giving zeroes for all offsets (we'll set the correct offsets in the hitbox definition, which will override any set here), zeroes for all x, y, and z-velocities, zero angle, and a flag of SXF_SETMASTER, which ties the headshot hitbox to our monster in a child/master relationship.

"Goto Super::Spawn" allows us to only spawn one hitbox actor, one time, per monster. This custom spawn state will execute, then this command will cause execution control to move to this actor's parent class (vanilla ZombieMan), specifically its Spawn state.

This final bit here, the Death.HeadShot state, is a custom DamageType state. This isn't necessary for the headshot to function, but you can use it to add a custom headshot death animation. Without it the headshot kill will still work, it will just result in an instant death with a normal animation. Thanks to Mikk for the extra blood splattering effect achieved with A_SpawnItemEx (and if you know a simple way to cause extra blood DECALS to spawn, please let me know!)

...

Now let's define our hitbox actor, which will look like this:

Code: Select all

ACTOR HeadshotHitbox
{
    Radius 21
    Health 1
    Height 8
    Mass 0x7FFFFFFF
    +SHOOTABLE
    +NOGRAVITY
    +NODAMAGETHRUST

    States
    {
    Spawn:
        TNT1 A 1 A_Warp(AAPTR_MASTER, 0,0,45, 0, WARPF_INTERPOLATE|WARPF_NOCHECKPOSITION)
        TNT1 A 0 A_CheckFlag("SOLID", "Spawn", AAPTR_MASTER)
    RemoveActor:
        TNT1 A 0 A_Remove(AAPTR_DEFAULT)
        Stop
    Death:
        TNT1 A 0 A_KillMaster("HeadShot")
        Stop
    }
}
Make sure Radius is at least as large as your monster's Radius (ZombieMan is 20 by default). This will create a pretty wide hitbox, but you can tweak this by lowering the monster's height and/or playing with the hitbox spawning offsets. Just be aware that if you alter the monster's Height, it could potentially cause compatibility issues concerning ceiling heights and such.

Health is set to 1 for instant kills; height is the hitbox's vertical size; a Mass of 0x7FFFFFFF will render the hitbox immune to game physics; and keep the NODAMAGETHRUST flag on, or your zombie will fly across the room upon death.

For our states, Spawn first needs to call A_Warp. AAPTR_MASTER codepoints the hitbox actor to the monster actor as its Master, set 45 for the z-offset, and the above flags for keeping the hitbox securely attached to the monster in the correct position.

What's happening next, with A_CheckFlag, is another very cool thing Mikk suggested. A_CheckFlag takes three arguments. The first is the flag to be checked. If this flag is TRUE, or on (+), execution will jump to the state (of the calling actor, in this case HeadshotHitbox) indicated in the SECOND argument. The third, optional (but not for us), argument accepts a codepointer indicating which actor to check the flag indicated in the first argument.

So, here A_CheckFlag looks at the SOLID flag in TestZombie (as pointed to by argument #3, AAPTR_DEFAULT), and if it's True, it returns to the top of its own Spawn state, refreshing A_Warp. Now that's just sexy. But it gets better! If SOLID returns as False -- which will happen when TestZombie enters its death state and calls A_NoBlocking, control will not return to the top of the Spawn state, but instead will drop through to our custom State, RemoveActor. This calls A_Remove which we've told to remove the calling actor, itself, HeadshotHitbox, and ensures its deletion with the death of the monster.

If you want the custom death animation for headshots we talked about before, call A_KillMaster in your hitbox's Death state and give it the name of your custom damage type in quotations.

...

That's really about it. For testing, if you need to do so, I found it helpful to set a few custom properties. For one, have the monster's health very high and his speed very low. 500 and 1, respectively, were good for me. That way you can check for the headshot hitbox and body hitbox's boundaries with precision, and set the speed so you're not giving yourself a headache trying to aim at zigzagging Zombie Men.

In the headshot hitbox definition, if you set the BloodColor property to green, or another vividly non-red color, you'll be able to clearly discern between hitting the monster's body and head. Also if you set the Spawn state to a visible frame (we found BAL2A0 useful) and set the RenderStyle property to something like 'Add' (additive) or 'Shadow', you'll have a convenient visual approximation of the hitbox's center.

If you're anything like me, and you just want to get the thing done, and not have to spend a whole bunch of time digging through years of scraps of information (even if that can be kind of fun in itself =P) then I hope this eases your suffering a bit. If you have any specific questions or know of any way to improve this tutorial, or just feel like saying hi! .. feel free to post here or PM me. G'day.
Last edited by Double(Super)Shotgun on Tue Jun 30, 2015 4:53 pm, edited 1 time in total.
User avatar
Mikk-
Posts: 2274
Joined: Tue Jun 30, 2009 1:31 pm
Location: Somewhere off Kanagawa

Re: BOOM Headshots. In DECORATE. Nothing (even remotely) fan

Post by Mikk- »

Hi! I looked at your example and thought of a few ways it could be improved on. Most notably, my example removes the need to call A_SpawnItemEx every couple of tics, and instead uses A_Warp, a function that lets the hitbox "stick" to the head of the Zombieman.

Another issue is that the Zombieman is 12 units shorter and therefore can walk under lower ceilings, which in some cases, could break maps and/or Vanilla compat.

Here's my Example, I've documented most changes and you might notice my code is much shorter due to not having to redefine all the states.
Spoiler:
Double(Super)Shotgun
Posts: 75
Joined: Fri Jul 25, 2014 2:16 pm

Re: BOOM Headshots. In DECORATE. Nothing (even remotely) fan

Post by Double(Super)Shotgun »

Aha, so that's how you use A_Warp. Actor pointers are still really confusing to me. :oops: Ok, if you don't mind, a couple questions before I revise the tutorial with this much nicer code. Answer or address whatever you see fit:

In the HeadshotZombie Spawn state, what does calling two frames of TNT1A ("TNT1 AA 0") do exactly? I see that it's necessary -- without it the hitbox does not spawn -- but why is that? I seem to have read somewhere recently that the first line of a Spawn state isn't read or isn't read correctly, something like that. Maybe AA ameliorates that. Wow, ameliorates is actually a word. Thanks, brain.

And I think I see why the Super::Spawn command: Looks like if you try to include the hitbox spawn in the loop, now that it uses A_Warp (so awesome..) it will just keep spawning more hitboxes as long as he's in the Spawn state. Which doesn't sound healthy.

M'k, oh, nice added blood effects in the death state. What I'd really like to see is more/larger blood decals being spawned with a headshot kill, any idea how to pull that off? And right, smart about keeping ZombieMan's height at default and just increasing the radius of the hitbox. Durp.

Now A_Warp. I've been curious for a long time: what exactly IS interpolation, and what does it do in regards to zdoom? I see for our purposes it relates to the minutiae of how spawned hitboxes follow their masters. So I experimented a bit with the flags, switching them out, turning them off and on and such, and I see why at least the first flag is there. But why that specific one? And what does WARPF_NOCHECKPOSITION do? Sorry, I know the wiki entry on A_Warp talks about it some, but sometimes the language there is still too arcane for me. I don't understand what "blindly accept any resultant position" means. I also don't yet understand what the success state stuff is all about, success relative to what, I wonder.

So ok, as you documented, A_CheckFlag ensures the master is still alive by continuously checking its SOLID flag, returning to the top of its own Spawn state if true, and finally falling through to RemoveActor when false. Is RemoveActor strictly necessary, though? I noticed the hitbox still goes away without it, I'd assume because it's being killed thus entering its death state and removing itself naturally.

Nice touch with making the visualization sprite semi-transparent, btw. Ok now to edit!
User avatar
Mikk-
Posts: 2274
Joined: Tue Jun 30, 2009 1:31 pm
Location: Somewhere off Kanagawa

Re: BOOM Headshots. In DECORATE. Nothing (even remotely) fan

Post by Mikk- »

Double(Super)Shotgun wrote: In the HeadshotZombie Spawn state, what does calling two frames of TNT1A ("TNT1 AA 0") do exactly? I see that it's necessary -- without it the hitbox does not spawn -- but why is that? I seem to have read somewhere recently that the first line of a Spawn state isn't read or isn't read correctly, something like that. Maybe AA ameliorates that. Wow, ameliorates is actually a word. Thanks, brain.
Yes, exactly that - because Actions on the first frame of the Spawn State is ignored using TNT1 AA is necessary, I can't exactly remember why the first function is never called (maybe someone else can clear that up for me?)
Double(Super)Shotgun wrote: And I think I see why the Super::Spawn command: Looks like if you try to include the hitbox spawn in the loop, now that it uses A_Warp (so awesome..) it will just keep spawning more hitboxes as long as he's in the Spawn state. Which doesn't sound healthy.
Using Super::Spawn after spawning the hitbox actor makes it so that the HeadshotZombie actor never returns to the spawn State of its own, and instead, the Spawn state of its parent actor, the Zombieman - the code only ever spawns one Hitbox Actor per Zombie. However, I did notice a flaw with my own code and that would be when a Zombie is killed and then resurrected by an Archvile or any other monster, the Hitbox isn't recreated. To fix that problem I'd simply redefine the Raise state to spawn another Hitbox actor.
Double(Super)Shotgun wrote: M'k, oh, nice added blood effects in the death state. What I'd really like to see is more/larger blood decals being spawned with a headshot kill, any idea how to pull that off? And right, smart about keeping ZombieMan's height at default and just increasing the radius of the hitbox. Durp.
Well an idea would be to fire several invisible short lived projectiles which, if they hit a wall they produce a large decal of blood. And to create more blood effects when the Zombie dies, it's just as simple as creating a new blood actor and messing with the scale and other properties.
Double(Super)Shotgun wrote: Now A_Warp. I've been curious for a long time: what exactly IS interpolation, and what does it do in regards to zdoom? I see for our purposes it relates to the minutiae of how spawned hitboxes follow their masters. So I experimented a bit with the flags, switching them out, turning them off and on and such, and I see why at least the first flag is there. But why that specific one? And what does WARPF_NOCHECKPOSITION do? Sorry, I know the wiki entry on A_Warp talks about it some, but sometimes the language there is still too arcane for me. I don't understand what "blindly accept any resultant position" means. I also don't yet understand what the success state stuff is all about, success relative to what, I wonder.
Quite frankly, I can't remember why I used WARPF_INTERPOLATE, mostly due to habit when using A_Warp, it might not even be truly necessary. WARPF_NOCHECKPOSITION is used so when the Hitbox warps to the point of the Zombie's head, it doesn't fail, in normal circumstances, the actor would not warp to the location because it would then collide with the Zombie's hitbox. NOCHECKPOSITION removes collision checks so the hitbox will always be in the same position. Success state stuff is a mystery to me, too - as I haven't found a use for it yet.
Double(Super)Shotgun wrote: So ok, as you documented, A_CheckFlag ensures the master is still alive by continuously checking its SOLID flag, returning to the top of its own Spawn state if true, and finally falling through to RemoveActor when false. Is RemoveActor strictly necessary, though? I noticed the hitbox still goes away without it, I'd assume because it's being killed thus entering its death state and removing itself naturally.

If I recall correctly, "removing" actors via forcing them to their death state can cause irregular/buggy behaviour, so I drop it into a different state.

Hope this helps!
User avatar
phantombeta
Posts: 2084
Joined: Thu May 02, 2013 1:27 am
Operating System Version (Optional): Windows 10
Graphics Processor: nVidia with Vulkan support
Location: Brazil

Re: BOOM Headshots. In DECORATE. Nothing (even remotely) fan

Post by phantombeta »

Mikk- wrote:
Double(Super)Shotgun wrote: In the HeadshotZombie Spawn state, what does calling two frames of TNT1A ("TNT1 AA 0") do exactly? I see that it's necessary -- without it the hitbox does not spawn -- but why is that? I seem to have read somewhere recently that the first line of a Spawn state isn't read or isn't read correctly, something like that. Maybe AA ameliorates that. Wow, ameliorates is actually a word. Thanks, brain.
Yes, exactly that - because Actions on the first frame of the Spawn State is ignored using TNT1 AA is necessary, I can't exactly remember why the first function is never called (maybe someone else can clear that up for me?)
You should actually use the NoDelay state keyword.

Code: Select all

actor Exploder {
    states {
    Spawn:
        TNT1 A 0 noDelay a_explode (666, 666)
        stop
    }
}
I believe the reason for it not executing the first action of the Spawn state when created is because it's set to the frame. It doesn't do anything else. It's literally just set to the first frame of the Spawn state and pretty much doesn't execute any behaviour.
Double(Super)Shotgun
Posts: 75
Joined: Fri Jul 25, 2014 2:16 pm

Re: BOOM Headshots. In DECORATE. Nothing (even remotely) fan

Post by Double(Super)Shotgun »

Mikk- wrote:
Double(Super)Shotgun wrote:M'k, oh, nice added blood effects in the death state. What I'd really like to see is more/larger blood decals being spawned with a headshot kill, any idea how to pull that off? And right, smart about keeping ZombieMan's height at default and just increasing the radius of the hitbox. Durp.
Well an idea would be to fire several invisible short lived projectiles which, if they hit a wall they produce a large decal of blood.
Multiple projectiles, interesting. That's another area I haven't explored yet, custom projectiles. Will need to do that eventually for tracers.

The DECALDEF wiki page talks about the intensity/size of blood decals drawn relating to the amount of damage inflicted in an instance, so I'm messing with that now. Having trouble find a way to make normal hitscan attacks increase the damage inflicted (A_Damage seems to bypass the blood decal spawning function). One more reason to finally learn how to make projectiles I guess.

Also I found another good reason to keep your self-deleting hitbox function, Mikk-: the hitbox remains if the monster dies in any way other than by headshot.
User avatar
phantombeta
Posts: 2084
Joined: Thu May 02, 2013 1:27 am
Operating System Version (Optional): Windows 10
Graphics Processor: nVidia with Vulkan support
Location: Brazil

Re: BOOM Headshots. In DECORATE. Nothing (even remotely) fan

Post by phantombeta »

Pretty sure there's an A_KillChildren (Or something) DECORATE action, so you could just redefine the Death states and add that to the first tic of the state, which should kill the hitbox.
User avatar
Amuscaria
Posts: 6628
Joined: Mon Jul 26, 2004 12:59 pm
Location: Growing from mycelium near you.

Re: BOOM Headshots. In DECORATE. Nothing (even remotely) fan

Post by Amuscaria »

Oh sweet. I needed something very similar to this. Thanks :D
Double(Super)Shotgun
Posts: 75
Joined: Fri Jul 25, 2014 2:16 pm

Re: BOOM Headshots. In DECORATE. Nothing (even remotely) fan

Post by Double(Super)Shotgun »

Eriance wrote:Oh sweet. I needed something very similar to this. Thanks :D
You're very welcome! Funny, I think I just downloaded a file with a bunch of your weapons sprites called "OldErianceStuff", scouring the forums trying to find a chaingun base for something I have in mind.
blood
Posts: 134
Joined: Thu Aug 25, 2011 3:17 pm

Re: BOOM Headshots. In DECORATE. Nothing (even remotely) fan

Post by blood »

Thanks a lot for this demonstration, "A_Warp"/"A_CheckFlag"/"A_Remove" that a lot of useful functions we got there.
User avatar
jdredalert
Posts: 1668
Joined: Sat Jul 13, 2013 10:13 pm
Contact:

Re: BOOM Headshots. In DECORATE. Nothing (even remotely) fan

Post by jdredalert »

Nice and clean way to do headshots, well done!
User avatar
TheRailgunner
Posts: 1555
Joined: Mon Jul 08, 2013 10:08 pm

Re: BOOM Headshots. In DECORATE. Nothing (even remotely) fan

Post by TheRailgunner »

So, does this headshot code kill the monster instantly (akin to Nash's ACS headshots) or can it be configured to simply increase damage taken from headshots relative to bodyshots (like *shudder* Brutal Doom's headshots)?
User avatar
Mikk-
Posts: 2274
Joined: Tue Jun 30, 2009 1:31 pm
Location: Somewhere off Kanagawa

Re: BOOM Headshots. In DECORATE. Nothing (even remotely) fan

Post by Mikk- »

TheRailgunner wrote:So, does this headshot code kill the monster instantly (akin to Nash's ACS headshots) or can it be configured to simply increase damage taken from headshots relative to bodyshots (like *shudder* Brutal Doom's headshots)?
It could be modified to use A_DamageMaster as opposed to using A_KillMaster, so in its current state the code causes instant death, but tweaking it slightly can make it more akin to Brutal Doom's.
User avatar
phantombeta
Posts: 2084
Joined: Thu May 02, 2013 1:27 am
Operating System Version (Optional): Windows 10
Graphics Processor: nVidia with Vulkan support
Location: Brazil

Re: BOOM Headshots. In DECORATE. Nothing (even remotely) fan

Post by phantombeta »

This should work. (Didn't test.) This works.
Should deal quad damage on headshots.

Code: Select all

actor HeadshotZombie : Zombieman replaces Zombieman {
    states {
    Spawn:
        TNT1 A 0 NoDelay A_SpawnItemEx ("HeadshotHitbox", 0, 0, 45, 0, 0, 0, 0, SXF_SetMaster)
        goto Super::Spawn
    Death.Headshot:
        POSS HHHHH 0 A_SpawnItemEx ("Blood", 0, 0, 45, frandom (-2.0, 2.0), frandom (-2.0, 2.0), frandom (1.5, 5.0))
        POSS H 2
        POSS I 2 A_Scream
        POSS J 2
        POSS K 2
        POSS L -1 A_NoBlocking
        stop
    }
}

actor HeadshotHitbox {
    radius 21
    health 0x7FFFFFFF
    height 8
    mass 0x7FFFFFFF
    renderStyle Add
    //alpha 0.0
    damageFactor Explode, 0.0
    damageFactor Drowning, 0.0
    damageFactor BFGSplash, 0.0
    damageFactor Telefrag, 0.0
    damageFactor Massacre, 0.0
    damageFactor InstantDeath, 0.0
    +shootable +noGravity +noDamageThrust
    
    var int user_oldHealth;
    states {
    Spawn:
        TNT1 A 0
    Idle:
        BAL2 A 0 A_SetUserVar ("user_oldHealth", health)
        BAL2 A 1 A_Warp (AAPTR_Master, 0, 0, 45, 0, WARPF_Interpolate | WARPF_NoCheckPosition)
        BAL2 A 0 A_JumpIf ((user_oldHealth - health) < 1, "CheckMaster")
        BAL2 A 0 A_DamageMaster ((user_oldHealth - health) * 4, "Headshot")
    CheckMaster:
        BAL2 A 0 A_SetHealth (0x7FFFFFFF)
        BAL2 A 0 A_CheckFlag ("solid", "Spawn", AAPTR_Master)
    RemoveActor:
        BAL2 A 0 A_Remove (AAPTR_Default)
        stop
    }
}
[edit]Fixed. I can't into maths[/edit]

[edit]
Zandronum 3.x compatible version:

Code: Select all

actor HeadshotZombie : Zombieman replaces Zombieman {
    states {
    Spawn:
        TNT1 A 0 NoDelay A_SpawnItemEx ("HeadshotHitbox", 0, 0, 45, 0, 0, 0, 0, SXF_SetMaster)
        goto Super::Spawn
    Death.Headshot:
        POSS HHHHH 0 A_SpawnItemEx ("Blood", 0, 0, 45, frandom (-2.0, 2.0), frandom (-2.0, 2.0), frandom (1.5, 5.0))
        POSS H 2
        POSS I 2 A_Scream
        POSS J 2
        POSS K 2
        POSS L -1 A_NoBlocking
        stop
    }
}

actor HeadshotHitbox {
    radius 21
    health 0x7FFFFFFF
    height 8
    mass 0x7FFFFFFF
    renderStyle Add
    //alpha 0.0
    damageFactor Explode, 0.0
    damageFactor Drowning, 0.0
    damageFactor BFGSplash, 0.0
    damageFactor Telefrag, 0.0
    damageFactor Massacre, 0.0
    damageFactor InstantDeath, 0.0
    +shootable +noGravity +noDamageThrust +isMonster
    
    var int user_oldHealth;
    states {
    Spawn:
        BAL2 AA 0 A_ChangeFlag ("isMonster", 0)
    Idle:
        BAL2 A 0
        BAL2 A 0 A_SetUserVar ("user_oldHealth", health)
        BAL2 A 1 A_Warp (AAPTR_Master, 0, 0, 45, 0, WARPF_Interpolate | WARPF_NoCheckPosition)
        BAL2 A 0 A_JumpIf ((user_oldHealth - health) < 1, "CheckMaster")
        BAL2 A 0 A_DamageMaster ((user_oldHealth - health) * 4, "Headshot")
    CheckMaster:
        BAL2 A 0 A_CheckFlag ("solid", "Idle", AAPTR_Master)
    RemoveActor:
        BAL2 A 0
        stop
    }
}
[/edit]
User avatar
Zanieon
Posts: 2059
Joined: Tue Jan 13, 2009 4:13 pm
Graphics Processor: ATI/AMD with Vulkan/Metal Support
Location: Somewhere in the future
Contact:

Re: BOOM Headshots. In DECORATE. Nothing (even remotely) fan

Post by Zanieon »

Hmm i might implement this into Hunter's Moon, not as a headshot system but can be usefull to make the lazy-aim players actually try lookup to shoot on the taller monsters. (e.g me)
Post Reply

Return to “Tutorials”