In addition to these, take a look at the ZDoom forums project thread for some more development news.


Adding a new BDLite monster - Zombie Plasma Scientist

One of the biggest inspirations behind doing the BDLite overhaul was a comment on the first release of my "Vulkan" episode - that it was too easy to notice the difference between the complex hitboxes and death animations of the standard enemies and the relative plainness of the enhanced ones. When rewriting the monster and blood code, I wanted to achieve a compromise where monsters seemed reactive and varied like Brutal Doom, but making sure that adding a new one wasn't a completely insurmountable task like it used to be. Take a look at the Structure of BDLite Monsters tutorial for an overview of what a new BDLite monster should include in its sprites and code.

For this example, I'll complete a circle by enhancing the Zombie Plasma Scientist that was in the original Vulkan and which was subsequently improved by Enjay with more rotations, brightmaps and so on. See the tutorial on making the Revolver for how to lay out and combine the new monster resources with the base BDLite PK3 - you can see the final enhanced enemy in Vulkan 1.5.

After unpacking the enemy from the WAD into the folder that's to become our PK3, we already have a ton of sprites. I'm very grateful to Enjay for building on my mediocre spriting skills and doing the rotations in particular - I'd chosen to give him a missing left arm primarily because that meant I wouldn't have to draw it, but that made it very obvious when the sprite was flipped to fill out the rotations. Let's go in and make some more.

I could never pass as a genuine spriter, but through perseverance, trial and error, I can at least edit things around a bit. Most of these came from splicing together existing frames from the scientist with gore frames from Brutal Doom and pulling the results around until they looked somewhat convincing. Really, very few sprites are required to allow for a reasonable number of death types - we've got a set of sprites for being bowled over backwards (as well as forwards as in the standard death animation), some body parts and dismemberments of two levels of severity. I'm also fortunate that so many of the scientist enemies in Vulkan share exactly the same body, just with the heads and weapons being different - it means I can use these same sprites for many of them.

This is what the scientist's DECORATE looks like straight out of the WAD on Realm667. It's a fairly simple vanilla-style enemy that defines its own projectiles - the main thing that we'll be doing to it is expanding those Death and XDeath states to make them more spectacular.

Vanilla-style Zombie Plasma Scientist DECORATE
ACTOR ZombieScientistPlasma 21010 { //$Category Monsters obituary "%o was melted by a zombie scientist." health 50 mass 100 speed 16 Radius 20 Height 52 painchance 150 seesound "scientist/sight" painsound "scientist/pain" deathsound "scientist/death" activesound "scientist/active" MONSTER +FLOORCLIP +AVOIDMELEE +MISSILEMORE DropItem Cell States { Spawn: SCZP AB 10 A_Look loop See: SCZP AABBCCDD 3 A_Chase loop Missile: SCZP E 6 A_FaceTarget SCZP E 1 A_PlaySound ("PlasmaHi/Fire") SCZP F 5 A_CustomMissile ("ZombiePlasmaBall", 45, 3, (1)*Random(-3, 3), CMF_OFFSETPITCH, (0.1)*Random(-3, 3)) SCZP E 12 goto See Pain: SCZP G 3 SCZP G 3 A_Pain goto See Death: SCZP H 5 A_SpawnItemEx ("ZombiePlasmaDeathExplosion", 0.0, 0.0, 32.0, 0.0, 0.0, 0.0, 0.0, SXF_NOCHECKPOSITION, 0) SCZP I 5 A_Scream SCZP J 5 A_NoBlocking SCZP K 5 SCZP L 5 SCZP M 5 SCZP N -1 stop XDeath: SCZP O 5 A_SpawnItemEx ("ZombiePlasmaDeathExplosion", 0.0, 0.0, 32.0, 0.0, 0.0, 0.0, 0.0, SXF_NOCHECKPOSITION, 0) SCZP P 5 A_XScream SCZP Q 5 A_NoBlocking SCZP RSTUV 5 SCZP W -1 stop Raise: SCZP MLKJIH 5 goto See } } Actor ZombiePlasmaBall { Height 8 Radius 6 Speed 15 FastSpeed 25 Projectile RenderStyle Add Damage (10 + random(1,5)) Scale 0.4 DeathSound "weapons/plasmax" Decal PlasmaScorch States { Spawn: ZPLS AB 2 BRIGHT loop Death: PLSE ABCDE 4 Bright Stop } } //Why can't we get his plasma gun? //Because it went up in a flash of plasma when he died, that's why. :P ACTOR ZombiePlasmaDeathExplosion { Radius 1 Height 2 +NOGRAVITY RenderStyle Add Alpha 0.75 Scale 0.5 States { Spawn: ZPLX A 0 ZPLX A 4 A_PlaySound("weapons/plasmax") ZPLX BCDE 4 Bright Stop } }

Let's define some flying body parts first, which will be spawned by several of our death states - these extend from BdGib and follow a simple pattern, with most of the work being done by the base class. Check the Blood and Gibs tutorial for how these work - all that we need here is to define a flying sprite, tell it to roll, splash when it hits the ground and define its lying-around sprite. Again, these are shared by most of the scientist enemies in Vulkan.

Vulkan gibs DECORATE
ACTOR VGibScientistHeadDark : BdGib { States { Fly: SCGR I 4 A_SpawnProjectile("BdFlyingBloodTrailStrong", 0, 0, random (0, 360), 2, random (0, 360)) TNT1 A 0 A_SetRoll(roll + user_rotationspeed) Loop Death: TNT1 A 0 A_SpawnItem("BdBloodSpot",0,0,0,1) TNT1 AAA 0 A_CustomMissile ("BdFlyingBloodSmall", 0, 0, random (0, 360), CMF_AIMDIRECTION, random (0, 160)) TNT1 A 0 A_SetRoll(0) SCGR I 1 Goto Lying } } ACTOR VGibScientistArm : BdGib { States { Fly: SCGR B 4 A_SpawnProjectile("BdFlyingBloodTrailStrong", 0, 0, random (0, 360), 2, random (0, 360)) TNT1 A 0 A_SetRoll(roll + user_rotationspeed) Loop Death: TNT1 A 0 A_SpawnItem("BdBloodSpot",0,0,0,1) TNT1 A 0 A_SetRoll(0) TNT1 AAA 0 A_SpawnItem("Blood", 0, 5) SCGR B 1 Goto Lying } } ACTOR VGibScientistLeg : BdGib { States { Fly: SCGR A 4 A_SpawnProjectile("BdFlyingBloodTrailStrong", 0, 0, random (0, 360), 2, random (0, 360)) TNT1 A 0 A_SetRoll(roll + user_rotationspeed) Loop Death: TNT1 A 0 A_SpawnItem("BdBloodSpot",0,0,0,1) TNT1 A 0 A_SetRoll(0) TNT1 AAA 0 A_SpawnItem("Blood", 0, 5) SCGR A 1 Goto Lying } }

As mentioned in the Structure of BDLite Monsters, a BDLite monster's death states come in two parts - the Death.* states that the monster jumps to when its health reaches zero or below, and the Die states which perform the death animations. The Die states are the easiest to explain first, so here are a collection of them for this monster.

Zombie Plasma Scientist die states - defining death animations
DieNormal: TNT1 A 0 A_CustomMissile ("BdBloodSpawnerSmall", 35, 0, random (0, 360), 2, random (0, 160)) SCZP HIJKLM 5 A_CustomMissile ("BdBloodDrop", 15, 0, random (0, 360), 2, random (0, 40)) SCZP N -1 stop DieNormal2: TNT1 A 0 A_CustomMissile ("BdBloodSpawnerSmall", 35, 0, random (0, 360), 2, random (0, 160)) SCGR DEFG 5 A_CustomMissile ("BdBloodDrop", 15, 0, random (0, 360), 2, random (0, 40)) SCGR H -1 stop DieRollBack: TNT1 A 0 A_CustomMissile ("BdBloodSpawnerSmall", 35, 0, random (0, 360), 2, random (0, 160)) TNT1 A 0 A_FaceTarget TNT1 A 0 ThrustThingZ(0,10,0,1) TNT1 A 0 A_Recoil(4) SCGR DEFG 5 A_CustomMissile ("BdBloodDrop", 15, 0, random (0, 360), 2, random (0, 40)) SCGR H -1 stop DiePlasma: PBR1 A 2 TNT1 AAAAAAAAAAAAAAAAAAAAAAA 0 { A_CustomMissile ("BdAshes", 32, 0, random (0, 360), 2, random (0, 180)); A_CustomMissile ("BdAshesHeavy", 32, 0, random (0, 360), 2, random (0, 180)); } TNT1 A 0 A_CustomMissile ("BdBloodSpawnerMelting", 35, 0, random (0, 360), 2, random (0, 160)) PBR1 BBBBBB 2 A_SpawnItemEx("BdPlasmaEvaporate", random(0 - radius/2, radius/2), random(0 - radius/2, radius/2), 48) PBR1 CDEFGH 6 A_CustomMissile ("Blood", 10, 0, random (0, 360), 2, random (0, 160)) TNT1 A 0 A_SpawnItem ("BdBloodPool") PBR1 HHHHHHHHHHHHHHHHHHHHHH 8 { A_CustomMissile ("MiniSmoke", 10, 0, random (0, 360), 2, random (0, 160)); A_SpawnItemEx("BdPlasmaEvaporate", random(0 - radius/2, radius/2), random(0 - radius/2, radius/2), 8); } PBR1 H -1 Stop DieJustLegs: TNT1 AA 0 A_CustomMissile ("VGibScientistArm", 48, 0, random (0, 360), 2, random (0, 160)) TNT1 A 0 A_CustomMissile ("VGibScientistHeadDark", 48, 0, random (0, 360), 2, random (0, 160)) TNT1 A 0 A_CustomMissile ("BdBloodSpawnerSmall", 35, 0, random (0, 360), 2, random (0, 160)) TNT1 AAA 0 A_CustomMissile ("BdBloodLump", 42, 0, random (0, 360), 2, random (10, 160)) TNT1 AA 0 A_CustomMissile ("BdBloodLumpBone", 42, 0, random (0, 360), 2, random (10, 160)) TNT1 A 0 A_CustomMissile ("BdGibZombieRibcage", 42, 0, random (0, 360), 2, random (10, 160)) TNT1 AA 0 A_CustomMissile ("BdBloodLumpBone", 35, 0, random (0, 360), 2, random(25, 80)) TNT1 AAA 0 A_CustomMissile ("BdGibGut", 35, 0, random (0, 360), 2, random(25, 45)) SCGR JKLM 6 SCGR M -1 DieSplit: TNT1 A 0 A_CustomMissile ("BdBloodSpawnerSmall", 35, 0, random (0, 360), 2, random (0, 160)) TNT1 AA 0 A_CustomMissile ("BdBloodLump", 42, 0, random (0, 360), 2, random (10, 160)) TNT1 AA 0 A_CustomMissile ("BdBloodLumpBone", 42, 0, random (0, 360), 2, random (10, 160)) TNT1 A 0 A_CustomMissile ("BdGibZombieRibcage", 42, 0, random (0, 360), 2, random (10, 160)) TNT1 AA 0 A_CustomMissile ("BdBloodLumpBone", 35, 0, random (0, 360), 2, random(25, 80)) TNT1 AAA 0 A_CustomMissile ("BdGibGut", 35, 0, random (0, 360), 2, random(25, 45)) TNT1 A 0 A_CustomMissile ("VGibScientistHeadDark", 48, 0, random (0, 360), 2, random (0, 160)) SCGR OPQRS 5 SCGR S -1 DieGibs: TNT1 AA 0 A_CustomMissile ("VGibScientistArm", 48, 0, random (0, 360), 2, random (0, 160)) TNT1 AA 0 A_CustomMissile ("VGibScientistLeg", 48, 0, random (0, 360), 2, random (0, 160)) TNT1 A 0 A_CustomMissile ("VGibScientistHeadDark", 48, 0, random (0, 360), 2, random (0, 160)) TNT1 A 0 A_CustomMissile ("BdBloodSpawnerSmall", 35, 0, random (0, 360), 2, random (0, 160)) TNT1 AA 0 A_CustomMissile ("BdBloodLumpBone", 35, 0, random (0, 360), 2, random(25, 80)) TNT1 AAA 0 A_CustomMissile ("BdGibGut", 35, 0, random (0, 360), 2, random(25, 45)) SCZP OPQRSTUV 5 SCZP W -1 XDeath: TNT1 A 0 A_XScream TNT1 A 0 A_NoBlocking TNT1 A 0 A_FaceTarget TNT1 AA 0 A_CustomMissile ("BdBloodSpawnerSmall", 35, 0, random (0, 360), 2, random (0, 160)) TNT1 AA 0 A_CustomMissile ("BdBloodLump", 42, 0, random (0, 360), 2, random (10, 160)) TNT1 AAA 0 A_CustomMissile ("BdBloodLumpBone", 42, 0, random (0, 360), 2, random (10, 160)) TNT1 A 0 A_CustomMissile ("BdGibZombieRibcage", 42, 0, random (0, 360), 2, random (10, 160)) TNT1 AA 0 A_CustomMissile ("VGibScientistArm", 42, 0, random (0, 360), 2, random (10, 160)) TNT1 AA 0 A_CustomMissile ("VGibScientistLeg", 42, 0, random (0, 360), 2, random (10, 160)) TNT1 A 0 A_CustomMissile ("VGibScientistHeadDark", 48, 0, random (0, 360), 2, random (0, 160)) TNT1 AAAA 0 A_CustomMissile ("BdGibGut", 35, 0, random (0, 360), 2, random(25, 45)) TNT1 A 0 A_SpawnItem ("BdBloodSpot") TNT1 A 1 //1-tic delay required after XDeath Stop

Even with a relatively small number of gore frames (and nowhere near the hundreds that were present in the original Brutal Doom), we've got eight different death animations for the scientist. In the actual DECORATE file, the DieNormal2 and DiePlasma states are inherited from an AbstractZombieScientist, making the code a bit shorter still. The labels for these animations are:

The other major change we have to make to the DECORATE file is to get the monster to play these animations when it dies. In ZDoom, the monster will jump to the label called "Death" when its health is reduced to zero - or if available, the state called "Death.[damage type]" according to the projectile or attack that killed it. We can use these to define an array of possible death states to jump to from each damage type that the player can cause in BDLite.

Zombie Plasma Scientist death states - mapping damage types to deaths
Death: Death.Bullet: TNT1 A 0 A_ChangeFLag("NODROPOFF", 0) TNT1 A 0 A_Scream TNT1 A 0 A_NoBlocking TNT1 A 0 A_Jump(256, "DieNormal", "DieNormal2") Death.Kick: TNT1 A 0 A_ChangeFLag("NODROPOFF", 0) TNT1 A 0 A_Scream TNT1 A 0 A_NoBlocking TNT1 A 0 A_Jump(256, "DieRollBack") Death.SuperKick: TNT1 A 0 A_ChangeFLag("NODROPOFF", 0) TNT1 A 0 A_Scream TNT1 A 0 A_NoBlocking TNT1 A 0 A_Jump(256, "DieGibs", "XDeath") Death.Shotgun: TNT1 A 0 A_ChangeFLag("NODROPOFF", 0) TNT1 A 0 A_Scream TNT1 A 0 A_NoBlocking TNT1 A 0 A_Jump(256, "DieRollBack", "DieJustLegs") Death.RapidFire: TNT1 A 0 A_ChangeFLag("NODROPOFF", 0) TNT1 A 0 A_Scream TNT1 A 0 A_NoBlocking TNT1 A 0 A_Jump(256, "DieJustLegs", "DieSplit") Death.Explosive: TNT1 A 0 A_ChangeFLag("NODROPOFF", 0) TNT1 A 0 A_Scream TNT1 A 0 A_NoBlocking TNT1 A 0 A_JumpIf(health > -10, "DieNormal") TNT1 A 0 A_Jump(256, "DieGibs", "XDeath") Death.Plasma: TNT1 A 0 A_ChangeFLag("NODROPOFF", 0) TNT1 A 0 A_Scream TNT1 A 0 A_NoBlocking TNT1 A 0 A_Jump(256, "DiePlasma") Death.Flak: TNT1 A 0 A_ChangeFLag("NODROPOFF", 0) TNT1 A 0 A_Scream TNT1 A 0 A_NoBlocking TNT1 A 0 A_Jump(256, "DieGibs", "XDeath", "DieJustLegs", "DieSplit")

Each major damage type performs the usual stuff to indicate a monster death, then uses A_Jump to jump to a "die" state appropriate to that weapon's damage. Lighter weapons like the rifle will cause relatively unspectacular death animations, whereas big ones like the explosives will cause the gib-spawning ones. This pattern utilizes a handy feature of the A_Jump function - we use 256 as the first parameter to make it always jump to somewhere, but as we provide a list of labels, ZDoom will jump to a randomly chosen one. Without this, we'd have to do much more with A_JumpIf and it would be much less neat.

And so here's the final BDLite-ized version of the scientist. Again, the Death states are shared between many scientist enemies so they're in the AbstractZombieScientist class, shortening the length of this one. I've also removed its DoomEd number (which is defined in MAPINFO instead) and changed its drop item to a SingleCell because they're used so often in Vulkan that dropping a Cell every time would be complete overkill.

ZombiePlasmaScientist class in Vulkan
ACTOR ZombieScientistPlasma : AbstractZombieScientist { //$Category Monsters obituary "%o was melted by a zombie scientist." health 50 gibhealth 10 mass 100 speed 16 Radius 20 Height 52 painchance 150 DropItem "SingleCell" 100 seesound "scientist/sight" painsound "scientist/pain" deathsound "scientist/death" activesound "scientist/active" +AVOIDMELEE +MISSILEMORE States { Spawn: SCZP AB 10 A_Look loop See: SCZP AABBCCDD 3 A_Chase loop Missile: SCZP E 6 A_FaceTarget SCZP E 1 A_PlaySound ("PlasmaHi/Fire") SCZP F 5 A_CustomMissile ("ZombiePlasmaBall", 45, 3, (1)*Random(-3, 3), CMF_OFFSETPITCH, (0.1)*Random(-3, 3)) SCZP E 12 goto See Pain: SCZP G 3 SCZP G 3 A_Pain goto See DieNormal: TNT1 A 0 A_CustomMissile ("BdBloodSpawnerSmall", 35, 0, random (0, 360), 2, random (0, 160)) SCZP HIJKLM 5 A_CustomMissile ("BdBloodDrop", 15, 0, random (0, 360), 2, random (0, 40)) SCZP N -1 stop //DieNormal2 is inherited from abstract DieRollBack: TNT1 A 0 A_CustomMissile ("BdBloodSpawnerSmall", 35, 0, random (0, 360), 2, random (0, 160)) TNT1 A 0 A_FaceTarget TNT1 A 0 ThrustThingZ(0,10,0,1) TNT1 A 0 A_Recoil(4) SCGR DEFG 5 A_CustomMissile ("BdBloodDrop", 15, 0, random (0, 360), 2, random (0, 40)) SCGR H -1 stop //DiePlasma is inherited from abstract DieJustLegs: TNT1 AA 0 A_CustomMissile ("VGibScientistArm", 48, 0, random (0, 360), 2, random (0, 160)) TNT1 A 0 A_CustomMissile ("VGibScientistHeadDark", 48, 0, random (0, 360), 2, random (0, 160)) TNT1 A 0 A_CustomMissile ("BdBloodSpawnerSmall", 35, 0, random (0, 360), 2, random (0, 160)) TNT1 AAA 0 A_CustomMissile ("BdBloodLump", 42, 0, random (0, 360), 2, random (10, 160)) TNT1 AA 0 A_CustomMissile ("BdBloodLumpBone", 42, 0, random (0, 360), 2, random (10, 160)) TNT1 A 0 A_CustomMissile ("BdGibZombieRibcage", 42, 0, random (0, 360), 2, random (10, 160)) TNT1 AA 0 A_CustomMissile ("BdBloodLumpBone", 35, 0, random (0, 360), 2, random(25, 80)) TNT1 AAA 0 A_CustomMissile ("BdGibGut", 35, 0, random (0, 360), 2, random(25, 45)) SCGR JKLM 6 SCGR M -1 DieSplit: TNT1 A 0 A_CustomMissile ("BdBloodSpawnerSmall", 35, 0, random (0, 360), 2, random (0, 160)) TNT1 AA 0 A_CustomMissile ("BdBloodLump", 42, 0, random (0, 360), 2, random (10, 160)) TNT1 AA 0 A_CustomMissile ("BdBloodLumpBone", 42, 0, random (0, 360), 2, random (10, 160)) TNT1 A 0 A_CustomMissile ("BdGibZombieRibcage", 42, 0, random (0, 360), 2, random (10, 160)) TNT1 AA 0 A_CustomMissile ("BdBloodLumpBone", 35, 0, random (0, 360), 2, random(25, 80)) TNT1 AAA 0 A_CustomMissile ("BdGibGut", 35, 0, random (0, 360), 2, random(25, 45)) TNT1 A 0 A_CustomMissile ("VGibScientistHeadDark", 48, 0, random (0, 360), 2, random (0, 160)) SCGR OPQRS 5 SCGR S -1 DieGibs: TNT1 AA 0 A_CustomMissile ("VGibScientistArm", 48, 0, random (0, 360), 2, random (0, 160)) TNT1 AA 0 A_CustomMissile ("VGibScientistLeg", 48, 0, random (0, 360), 2, random (0, 160)) TNT1 A 0 A_CustomMissile ("VGibScientistHeadDark", 48, 0, random (0, 360), 2, random (0, 160)) TNT1 A 0 A_CustomMissile ("BdBloodSpawnerSmall", 35, 0, random (0, 360), 2, random (0, 160)) TNT1 AA 0 A_CustomMissile ("BdBloodLumpBone", 35, 0, random (0, 360), 2, random(25, 80)) TNT1 AAA 0 A_CustomMissile ("BdGibGut", 35, 0, random (0, 360), 2, random(25, 45)) SCZP OPQRSTUV 5 SCZP W -1 XDeath: TNT1 A 0 A_XScream TNT1 A 0 A_NoBlocking TNT1 A 0 A_FaceTarget TNT1 AA 0 A_CustomMissile ("BdBloodSpawnerSmall", 35, 0, random (0, 360), 2, random (0, 160)) TNT1 AA 0 A_CustomMissile ("BdBloodLump", 42, 0, random (0, 360), 2, random (10, 160)) TNT1 AAA 0 A_CustomMissile ("BdBloodLumpBone", 42, 0, random (0, 360), 2, random (10, 160)) TNT1 A 0 A_CustomMissile ("BdGibZombieRibcage", 42, 0, random (0, 360), 2, random (10, 160)) TNT1 AA 0 A_CustomMissile ("VGibScientistArm", 42, 0, random (0, 360), 2, random (10, 160)) TNT1 AA 0 A_CustomMissile ("VGibScientistLeg", 42, 0, random (0, 360), 2, random (10, 160)) TNT1 A 0 A_CustomMissile ("VGibScientistHeadDark", 48, 0, random (0, 360), 2, random (0, 160)) TNT1 AAAA 0 A_CustomMissile ("BdGibGut", 35, 0, random (0, 360), 2, random(25, 45)) TNT1 A 0 A_SpawnItem ("BdBloodSpot") TNT1 A 1 //1-tic delay required after XDeath Stop Raise: SCZP MLKJIH 5 goto See } } Actor ZombiePlasmaBall { Height 8 Radius 6 Speed 15 FastSpeed 25 Projectile RenderStyle Add Damage (7 + random(1,5)) Scale 0.4 DeathSound "weapons/plasmax" Decal PlasmaScorch States { Spawn: ZPLS AB 2 BRIGHT loop Death: PLSE ABCDE 4 Bright Stop } } //Why can't we get his plasma gun? //Because it went up in a flash of plasma when he died, that's why. :P ACTOR ZombiePlasmaDeathExplosion { Radius 1 Height 2 +NOGRAVITY RenderStyle Add Alpha 0.75 Scale 0.5 States { Spawn: ZPLX A 0 ZPLX A 4 A_PlaySound("weapons/plasmax") ZPLX BCDE 4 Bright Stop } }