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


Structure of BDLite weapons

Brutal Doom's weapons are... large. They all extend the base class Weapon or their equivalents from Doom, and it's typical for a definition to be about 500 lines. A lot of that bulk is made up of copy and pasted code that handles the special keys and the animations for them. For example, take the definition of the rifle from RIFLE.TXT:

BrutalDoomV20 Rifle class
ACTOR Rifle : Weapon Replaces Pistol { Weapon.AmmoUse1 0 Weapon.AmmoGive1 10 Weapon.AmmoUse2 0 Weapon.AmmoGive2 0 YScale 0.6 XScale 0.8 Weapon.SelectionOrder 4500 Weapon.AmmoType1 "Clip2" Weapon.AmmoType2 "RifleAmmo" Obituary "%o was shot down by %k's assault rifle." AttackSound "None" Inventory.PickupSound "CLIPIN" Inventory.Pickupmessage "You got the Assault Rifle!" +WEAPON.WIMPY_WEAPON +WEAPON.NOAUTOAIM +WEAPON.NOALERT +WEAPON.NOAUTOFIRE +FORCEXYBILLBOARD +WEAPON.NO_AUTO_SWITCH Scale 0.8 States { PickUp: TNT1 A 0 A_Playsound("PICKUPONELINER") TNT1 A 0 Stop Steady: TNT1 A 1 Goto Ready Ready: TNT1 A 2 A_JumpIfInventory("GoFatality", 1, "Steady") TNT1 A 0 A_PlaySound("CLIPIN") RIFS ABC 1 TNT1 AAAAAAAA 0 TNT1 A 0 //A_JumpIfInventory("RifleAmmo",1,2) //Goto Reload TNT1 AAAA 0 TNT1 A 0 A_JumpIfInventory("Kicking",1,"DoKick") TNT1 A 0 A_JumpIfInventory("Taunting",1,"Taunt") TNT1 A 0 A_JumpIfInventory("Salute1", 1, "Salute") TNT1 A 0 A_JumpIfInventory("Salute2", 1, "Salute") TNT1 A 0 A_JumpIfInventory("Reloading",1,"Reload") TNT1 A 0 A_JumpIfInventory("Unloading",1,"Unload") TNT1 A 0 A_JumpIfInventory("TossGrenade",1,"TossGrenade") TNT1 A 0 A_TakeInventory("RifleSpread", 5) RIFG A 1 A_WeaponReady TNT1 A 0 A_TakeInventory("UsedStamina", 1) Goto Ready+9 Ready2: TNT1 A 0 A_JumpIfInventory("Kicking",1,"DoKick") TNT1 A 0 A_JumpIfInventory("Taunting",1,"Taunt") TNT1 A 0 A_JumpIfInventory("Reloading",1,"Reload") TNT1 A 0 A_JumpIfInventory("Unloading",1,"Unload") TNT1 A 0 A_JumpIfInventory("Salute1", 1, "Salute") TNT1 A 0 A_JumpIfInventory("Salute2", 1, "Salute") RI2G A 2 A_WeaponReady TNT1 A 0 A_JumpIfInventory("TossGrenade",1,"TossGrenade") TNT1 A 0 A_TakeInventory("UsedStamina", 2) Loop Deselect: TNT1 A 0 TNT1 A 0 A_Takeinventory("Zoomed",1) TNT1 A 0 A_Takeinventory("ADSmode",1) TNT1 A 0 A_TakeInventory("TossGrenade", 1) TNT1 A 0 A_ZoomFactor(1.0) RIFS CBA 1 TNT1 AAAAAAAAAAAAAAAAAA 0 A_Lower TNT1 A 1 Wait Select: TNT1 A 0 A_Takeinventory("FistsSelected",1) TNT1 A 0 A_Takeinventory("SawSelected",1) TNT1 A 0 A_Takeinventory("ShotgunSelected",1) TNT1 A 0 A_Takeinventory("SSGSelected",1) TNT1 A 0 A_Takeinventory("MinigunSelected",1) TNT1 A 0 A_Takeinventory("PlasmaGunSelected",1) TNT1 A 0 A_Takeinventory("RocketLauncherSelected",1) TNT1 A 0 A_Takeinventory("GrenadeLauncherSelected",1) TNT1 A 0 A_Takeinventory("BFGSelected",1) TNT1 A 0 A_Takeinventory("BFG10kSelected",1) TNT1 A 0 A_Takeinventory("RailGunSelected",1) TNT1 A 0 A_Takeinventory("SubMachineGunSelected",1) TNT1 A 0 A_Takeinventory("RevenantLauncherSelected",1) TNT1 A 0 A_Takeinventory("LostSoulSelected",1) TNT1 A 0 A_Takeinventory("FlameCannonSelected",1) TNT1 A 0 A_Takeinventory("HasBarrel",1) TNT1 A 0 A_TakeInventory("TossGrenade", 1) MARN A 0 ACS_ExecuteAlways(728, 0, 0, 0, 0)//Check if penetration is on. TNT1 AAAAAAAAAAAAAAAAAAAAAAAAAA 0 A_Raise Goto Ready Fire: TNT1 A 0 A_JumpIfInventory("RifleAmmo",1,2) Goto Reload TNT1 AAAA 0 TNT1 A 0 A_JumpIfInventory("RifleSpread", 5, "FireHighSpread") TNT1 A 0 A_GiveInventory("RifleSpread", 1) TNT1 A 0 A_Takeinventory("DoubleRifleAmmo",1) TNT1 A 0 A_JumpIfInventory("Zoomed",1,"Fire2") TNT1 A 0 A_PlaySound("weapons/pistol") //TNT1 A 0 A_FireCustomMissile("SmokeSpawner",0,0,0,5) RIFF A 1 BRIGHT A_AlertMonsters //TNT1 A 0 A_FireCustomMissile("YellowFlareSpawn",0,0,0,0) TNT1 A 0 A_SpawnItemEx("PlayerMuzzle1",30,5,30) RIFF B 1 BRIGHT A_FireBullets (2, 2, 1, 9, "HitPuff") RIFF A 0 A_FireCustomMissile("Tracer", random(-1,1), 0, -1, -12, 0, random(-1,1)) TNT1 A 0 A_JumpIfInventory("NoPenetration", 1, 2) RIFF A 0 A_FireCustomMissile("WallPenetrationHitscan", 0, 0, -1, 0, 0, 0) TNT1 A 0 TNT1 A 0 A_SetPitch(-1.3 + pitch) //TNT1 A 0 ACS_Execute(373, 0, 0, 0, 0) //TNT1 A 0 A_FireCustomMissile("ShakeYourAssMinor", 0, 0, 0, 0) //TNT1 A 0 A_FireCustomMissile("YellowFlareSpawn",0,0,0,0) TNT1 A 0 A_Takeinventory("RifleAmmo",1) TNT1 A 0 A_SetPitch(+0.6 + pitch) RIFF C 1 A_FireCustomMissile("RifleCaseSpawn",5,0,6,-6) RIFG A 1 A_SetPitch(+0.5 + pitch) RIFG A 0 A_Refire RIFG A 5 A_WeaponReady(1) TNT1 A 0 A_JumpIfInventory("Kicking",1,"DoKick") TNT1 A 0 A_JumpIfInventory("Taunting",1,"Taunt") TNT1 A 0 A_JumpIfInventory("Salute1", 1, "Salute") TNT1 A 0 A_JumpIfInventory("Salute2", 1, "Salute") TNT1 A 0 A_JumpIfInventory("Reloading",1,"Reload") TNT1 A 0 A_TakeInventory("RifleSpread", 5) RIFG A 5 A_WeaponReady(1) Goto Ready+6 FireHighSpread: TNT1 A 0 A_JumpIfInventory("RifleAmmo",1,2) Goto Reload TNT1 AAAA 0 TNT1 A 0 A_Takeinventory("DoubleRifleAmmo",1) TNT1 A 0 A_JumpIfInventory("Zoomed",1,"Fire2") TNT1 A 0 A_PlaySound("weapons/rifle") //TNT1 A 0 A_FireCustomMissile("SmokeSpawner",0,0,0,5) RIFF A 1 BRIGHT A_AlertMonsters //TNT1 A 0 A_FireCustomMissile("YellowFlareSpawn",0,0,0,0) TNT1 A 0 A_SpawnItemEx("PlayerMuzzle1",30,5,30) RIFF B 1 BRIGHT A_FireBullets (8, 8, 1, 9, "HitPuff") RIFF A 0 A_FireCustomMissile("Tracer", random(-4,4), 0, -1, -12, 0, random(-4,4)) TNT1 A 0 A_JumpIfInventory("NoPenetration", 1, 2) RIFF A 0 A_FireCustomMissile("WallPenetrationHitscan", random(-3,3), 0, -1, 0, 0, random(-3,3)) TNT1 A 0 TNT1 A 0 A_SetPitch(-0.7 + pitch) //TNT1 A 0 ACS_Execute(373, 0, 0, 0, 0) //TNT1 A 0 A_FireCustomMissile("ShakeYourAssMinor", 0, 0, 0, 0) //TNT1 A 0 A_FireCustomMissile("YellowFlareSpawn",0,0,0,0) TNT1 A 0 A_Takeinventory("RifleAmmo",1) TNT1 A 0 A_SetPitch(+0.4 + pitch) RIFF C 1 A_FireCustomMissile("RifleCaseSpawn",5,0,6,-6) RIFG A 1 A_SetPitch(+0.3 + pitch) RIFG A 1 A_Refire RIFG A 5 A_WeaponReady(1) TNT1 A 0 A_JumpIfInventory("Kicking",1,"DoKick") TNT1 A 0 A_JumpIfInventory("Taunting",1,"Taunt") TNT1 A 0 A_JumpIfInventory("Salute1", 1, "Salute") TNT1 A 0 A_JumpIfInventory("Salute2", 1, "Salute") TNT1 A 0 A_JumpIfInventory("Reloading",1,"Reload") TNT1 A 0 A_TakeInventory("RifleSpread", 5) RIFG A 5 A_WeaponReady(1) Goto Ready+6 Fire2: TNT1 A 0 A_PlaySound("weapons/rifle") TNT1 A 0 A_JumpIfInventory("IsFiringAltFireOnFullAuto", 1, "Fire2FullAuto") //TNT1 A 0 A_FireCustomMissile("SmokeSpawner",0,0,0,5) RI2G A 0 A_AlertMonsters //TNT1 A 0 A_FireCustomMissile("YellowFlareSpawn",0,0,0,5) TNT1 A 0 A_SpawnItemEx("PlayerMuzzle1",30,0,45) RI2F A 1 BRIGHT A_FireBullets (0.1, 0.1, -1, 9, "HitPuff") RI2F A 0 BRIGHT A_FireCustomMissile("Tracer", 0, 0, -1, 0) TNT1 A 0 A_JumpIfInventory("NoPenetration", 1, 2) RIFF A 0 A_FireCustomMissile("WallPenetrationHitscan", 0, 0, -1, -12, 0, 0) TNT1 A 0 //TNT1 A 0 ACS_Execute(373, 0, 0, 0, 0) //TNT1 A 0 A_FireCustomMissile("ShakeYourAssMinor", 0, 0, 0, 0) TNT1 A 0 A_SetPitch(-0.8 + pitch) TNT1 A 0 A_Takeinventory("RifleAmmo",1) RI2F B 1 A_WeaponReady(1) TNT1 A 0 A_SetPitch(+0.4 + pitch) TNT1 A 0 A_FireCustomMissile("RifleCaseSpawn",1,0,8,0) RI2F C 1 A_WeaponReady(1) TNT1 A 0 A_SetPitch(+0.4 + pitch) TNT1 A 0 A_GiveInventory("IsFiringAltFireOnFullAuto", 1) RI2G A 1 A_WeaponReady(1) RI2F B 0 A_ReFire TNT1 A 0 A_TakeInventory("IsFiringAltFireOnFullAuto", 1) RI2G A 10 A_WeaponReady(1) RI2G A 0 Goto Ready2 Fire2FullAuto: TNT1 A 0 A_PlaySound("weapons/rifle") //TNT1 A 0 A_FireCustomMissile("SmokeSpawner",0,0,0,5) RI2G A 0 A_AlertMonsters //TNT1 A 0 A_FireCustomMissile("YellowFlareSpawn",0,0,0,5) TNT1 A 0 A_SpawnItemEx("PlayerMuzzle1",30,0,45) RI2F A 1 BRIGHT A_FireBullets (0.1, 0.1, -1, 9, "HitPuff") RI2F A 0 BRIGHT A_FireCustomMissile("Tracer", 0, 0, -1, 0) //TNT1 A 0 ACS_Execute(373, 0, 0, 0, 0) //TNT1 A 0 A_FireCustomMissile("ShakeYourAssMinor", 0, 0, 0, 0) TNT1 A 0 A_SetPitch(-1.3 + pitch) TNT1 A 0 A_Takeinventory("RifleAmmo",1) TNT1 A 0 A_SetAngle(random(1, -1) + angle) RI2F B 1 TNT1 A 0 A_SetPitch(+0.4 + pitch) TNT1 A 0 A_FireCustomMissile("RifleCaseSpawn",1,0,8,0) RI2F C 1 TNT1 A 0 A_SetPitch(+0.4 + pitch) RI2G A 1 A_WeaponReady(1) TNT1 A 0 A_Refire RI2F B 0 TNT1 A 0 A_TakeInventory("IsFiringAltFireOnFullAuto", 1) RI2G A 10 A_WeaponReady(1) RI2G A 0 Goto Ready2 AltFire: TNT1 A 0 TNT1 A 0 A_JumpIfInventory("Zoomed",1,8) TNT1 A 0 A_Giveinventory("Zoomed",1) TNT1 A 0 A_Giveinventory("GoSpecial",1) TNT1 A 0 A_ZoomFactor(1.6) TNT1 A 0 A_Giveinventory("ADSmode",1) RIFZ A 1 Goto Ready2 TNT1 AAAAAA 0 RIFZ A 1 TNT1 A 0 A_Takeinventory("Zoomed",1) TNT1 A 0 A_ZoomFactor(1.0) TNT1 A 0 A_Takeinventory("ADSmode",1) Goto Ready+6 NoAmmo: RIFG A 1 A_PlaySound("weapons/empty") TNT1 A 0 A_Takeinventory("Zoomed",1) TNT1 A 0 A_ZoomFactor(1.0) TNT1 A 0 A_Takeinventory("ADSmode",1) Goto Ready+12 Reload: RIFG A 1 A_WeaponReady TNT1 A 0 A_ZoomFactor(1.0) TNT1 A 0 A_Takeinventory("Reloading",1) TNT1 A 0 A_Takeinventory("ADSmode",1) TNT1 A 0 A_Takeinventory("Zoomed",1) TNT1 A 0 A_JumpIfInventory("RifleAmmo",31,64) TNT1 A 0 A_JumpIfInventory("Clip2",1,3) Goto NoAmmo TNT1 AAA 0 TNT1 A 0 A_Takeinventory("Zoomed",1) TNT1 A 0 A_PlaySound("Reload") TNT1 A 0 A_GiveInventory ("Pumping", 1) TNT1 A 0 A_Takeinventory("Reloading",1) TNT1 A 0 RIFR ABCDE 1 A_JumpIfInventory("Kicking",1,"DoKick") TNT1 A 0 A_JumpIfInventory("HasUnloaded", 1, 2) RIFR F 1 A_FireCustomMissile("EmptyClipSpawn",-5,0,8,-4) RIFR GGGGGGGG 1 RIFR HIKL 1 RIFR MMM 1 RIFR NOPQRST 1 A_JumpIfInventory("Kicking",1,"DoKick") TNT1 A 0 A_Takeinventory("HasUnloaded",1) TNT1 A 0 A_JumpIfInventory("RifleAmmo",1,"InsertBullets2")//30+1 effect InsertBullets: TNT1 AAAA 0 TNT1 A 0 A_JumpIfInventory("RifleAmmo",30,15) TNT1 A 0 A_JumpIfInventory("Clip2",1,3) Goto Ready+6 TNT1 AAAAAA 0 TNT1 A 0 A_Giveinventory("RifleAmmo",1) TNT1 A 0 A_Giveinventory("DoubleRifleAmmo",1) TNT1 A 0 A_Takeinventory("Clip2",1) Goto InsertBullets TNT1 AAAAAAAAAA 0 TNT1 A 0 A_Takeinventory("Reloading",1) Goto Ready+6 TNT1 AAAA 0 TNT1 A 0 A_Takeinventory("Reloading",1) Goto Ready+6 InsertBullets2: TNT1 AAAA 0 TNT1 A 0 A_JumpIfInventory("RifleAmmo",31,15) TNT1 A 0 A_JumpIfInventory("Clip2",1,3) Goto Ready+6 TNT1 AAAAAA 0 TNT1 A 0 A_Giveinventory("RifleAmmo",1) TNT1 A 0 A_Giveinventory("DoubleRifleAmmo",1) TNT1 A 0 A_Takeinventory("Clip2",1) Goto InsertBullets2 TNT1 AAAAAAAAAA 0 TNT1 A 0 A_Takeinventory("Reloading",1) Goto Ready+6 TNT1 AAAA 0 TNT1 A 0 A_Takeinventory("Reloading",1) Goto Ready+6 Unload: RIFG A 1 A_WeaponReady TNT1 A 0 A_ZoomFactor(1.0) TNT1 A 0 A_Takeinventory("Unloading",1) TNT1 A 0 A_Takeinventory("ADSmode",1) TNT1 A 0 A_Takeinventory("Zoomed",1) TNT1 A 0 A_JumpIfInventory("RifleAmmo",1,3) Goto NoAmmo TNT1 AAA 0 TNT1 A 0 A_Takeinventory("Zoomed",1) RIFR TSRQPO 1 TNT1 A 0 A_PlaySound("Reload") TNT1 A 0 A_GiveInventory ("Pumping", 1) TNT1 A 0 A_Takeinventory("Unloading",1) RemoveBullets: TNT1 AAAA 0 TNT1 A 0 A_JumpIfInventory("RifleAmmo",1,3) Goto FinishUnload TNT1 AAAAAA 0 TNT1 A 0 A_Takeinventory("RifleAmmo",1) TNT1 A 0 A_Takeinventory("DoubleRifleAmmo",1) TNT1 A 0 A_Giveinventory("Clip2",1) Goto RemoveBullets FInishUnload: RIFR NMLKIGHGFEDCBA 1 TNT1 A 0 A_PlaySound("DryFire") TNT1 A 0 A_GiveInventory("HasUnloaded", 1) TNT1 A 0 A_Takeinventory("Unloading",1) Goto Ready+6 Spawn: RIFL A -1 Stop DoKick: TNT1 A 0 TNT1 A 0 A_Takeinventory("Zoomed",1) TNT1 A 0 A_ZoomFactor(1.0) TNT1 A 0 A_Takeinventory("ADSmode",1) NULL A 0 A_JumpIf (momZ > 0, "AirKick") NULL A 0 A_JumpIf (momZ < 0, "AirKick") NULL A 0 A_JumpIf (pitch > 32, "LowKickChecker1") InitializeNormalKick: TNT1 A 0 A_jumpifinventory("PowerStrength",1,"BerserkerKick") TNT1 A 0 A_PlaySound("KICK") TNT1 A 0 SetPlayerProperty(0,1,0) STMP ABCDEF 1 //TNT1 A 0 A_Custompunch(4,0,1,"KickPuff") RIFF A 0 A_FireCustomMissile("KickAttack", 0, 0, 0, -7) STMP G 4 KICK A 0 A_Takeinventory("Kicking",1) STMP HIJK 2 TNT1 A 0 A_JumpIfInventory("GoFatality", 1, "Steady") TNT1 A 0 SetPlayerProperty(0,0,0) NULL A 0 A_TakeInventory("KickHasHit",1) Goto Ready+6 LowKickChecker1: TNT1 A 1 NULL A 0 A_JumpIf (pitch > 90, "InitializeNormalKick") Goto LowKick LowKick: TNT1 A 0 A_PlaySound("KICK") TNT1 A 0 SetPlayerProperty(0,1,0) TNT1 A 0 A_Recoil(-2) TNT1 A 0 A_SpawnItemEx ("PLOFT5",15,0,0,0,0,0,0,SXF_NOCHECKPOSITION,0) KICK UVW 1 A_SetPitch(-8.0 + pitch) KICK X 1 RIFF A 0 A_FireCustomMissile("KickAttackLow", 0, 0, 0, 0) KICK Y 1 KICK Z 4 KICK YX 1 KICK A 0 A_Takeinventory("Kicking",1) KICK WVU 1 TNT1 A 0 A_JumpIfInventory("GoFatality", 1, "Steady") TNT1 A 0 SetPlayerProperty(0,0,0) NULL A 0 A_TakeInventory("KickHasHit",1) Goto Ready+6 BerserkerKick: TNT1 A 0 A_PlaySound("KICK") TNT1 A 0 SetPlayerProperty(0,1,0) KICK ABCDEFG 1 RIFF A 0 A_FireCustomMissile("SuperKickAttack", 0, 0, 0, -7) KICK H 3 KICK A 0 A_Takeinventory("Kicking",1) KICK IGFEDCBA 1 TNT1 A 0 A_JumpIfInventory("GoFatality", 1, "Steady") TNT1 A 0 SetPlayerProperty(0,0,0) NULL A 0 A_TakeInventory("KickHasHit",1) Goto Ready+6 AirKick: TNT1 A 0 A_jumpifinventory("PowerStrength",1,"SuperAirKick") TNT1 A 0 A_PlaySound("KICK") TNT1 A 0 A_Recoil (-2) KICK JKLMNO 1 RIFF A 0 A_FireCustomMissile("AirKickAttack", 0, 0, 0, -8) KICK P 3 KICK A 0 A_Takeinventory("Kicking",1) KICK QRST 2 TNT1 A 0 A_JumpIfInventory("GoFatality", 1, "Steady") NULL A 0 A_TakeInventory("KickHasHit",1) Goto Ready+6 SuperAirKick: TNT1 A 0 TNT1 A 0 A_PlaySound("KICK") TNT1 A 0 A_Recoil (-2) KICK JKLMNO 1 RIFF A 0 A_FireCustomMissile("SuperAirKickAttack", 0, 0, 0, -8) KICK P 3 KICK A 0 A_Takeinventory("Kicking",1) KICK QRST 2 TNT1 A 0 A_JumpIfInventory("GoFatality", 1, "Steady") NULL A 0 A_TakeInventory("KickHasHit",1) Goto Ready+6 Taunt: TNT1 A 0 A_Takeinventory("Zoomed",1) TNT1 A 0 A_ZoomFactor(1.0) TNT1 A 0 A_Takeinventory("Taunting",1) TNT1 A 0 A_JumpIfInventory("RealismMode", 1, "CheckGrab") TNT1 A 10 FUCK A 1 TNT1 A 0 BRIGHT A_FireCustomMissile("Taunter", 0, 0, -1, 0) TNT1 A 0 BRIGHT A_FireCustomMissile("Taunter", -9, 0, -1, 0) TNT1 A 0 BRIGHT A_FireCustomMissile("Taunter", 9, 0, -1, 0) FUCK B 1 A_PlaySound("FUCK", 2) FUCK CD 1 A_AlertMonsters FUCK E 15 A_Takeinventory("Taunting",1) FUCK DCBA 1 TNT1 A 10 Goto Ready Salute: TNT1 A 0 SetPlayerProperty(0,1,0) TNT1 A 0 A_ALertMonsters SALU ABCDEDCDEDCDEDCBA 4 TNT1 A 0 A_TakeInventory("Salute1",1) TNT1 A 0 A_TakeInventory("Salute2",1) TNT1 A 0 SetPlayerProperty(0,0,0) Goto Ready CheckGrab: TNT1 A 0 TNT1 A 0 A_JumpIfTargetInLOS("CheckDistanceGrab") Goto Ready+6 CheckDistanceGrab: TNT1 A 0 TNT1 A 0 A_JumpIfCloser(100, "Grab") Goto Ready+6 CheckIfCanGrab: TNT1 A 0 TNT1 A 0 A_JumpIfInTargetInventory("CanGrab", 1, "Grab") Goto Ready+6 Grab: PKUP ABC 2 TNT1 A 0 A_CustomMissile("PickupProjectile") PKUP DEF 2 Goto Ready TossGrenade: TNT1 A 0 TNT1 A 0 A_Takeinventory("Zoomed",1) TNT1 A 0 A_ZoomFactor(1.0) TNT1 A 0 A_TakeInventory("TossGrenade", 1) TNT1 A 0 A_JumpIfInventory("GrenadeAmmo", 1, 1) Goto NoGrenade GRTH ABCD 1 TNT1 A 0 A_GiveInventory("FiredGrenade", 1) TNT1 A 0 A_PLaySound ("GRNPIN") GRTH EEFG 1 TNT1 A 0 A_PLaySound ("GRNTOSS") GRTH HI 1 TNT1 A 0 A_TakeInventory("GrenadeAmmo", 1) TNT1 A 0 A_FireCustomMissile("HandGrenade", random(-2,2), 0, 0, 0, 0, 0) TNT1 A 0 A_TakeInventory("FiredGrenade", 1) GRTH JKLM 1 TNT1 A 1 TNT1 A 0 A_TakeInventory("TossGrenade", 1) Goto Ready NoGrenade: TNT1 A 0 TNT1 A 0 A_Print("No Grenades Left.") Goto REady+6 } }

Initial tidying

The most obvious way to tidy this up was to change the weapons so that they all inherited from a superclass - I've called mine BdLiteWeapon. This sets up the standard flags that are common to all weapons, as well as providing the labels to handle the special attacks (grenades and kicks - I've removed the other taunts, salutes and variations of the kick attack). In this class, it's important not to use Goto for any states, because at runtime this causes any inheriting subclass that reaches that line to jump to the label in the superclass. Instead, A_Jump(256, "Label") is used, which passes back to the subclass.

Another thing to address was the large string of A_GiveInventory and A_TakeInventory commands whenever a new weapon was selected - all the flags for selected weapons are Taken, apart from the one mentioned in this weapon class which is Given. I could have kept this in by putting all the Takes into the superclass and doing a single Give on each subclassed weapon when it was selected, but seeing as these things had so little use I opted to remove them entirely.

Finally, I could remove all the commented-out lines, and take out any ability to alt-fire or dual-wield, cutting down the required labels and states considerably.

Ammunition types

Even with the kick/grenade code safely up in BdLiteWeapon and a lot of the extraenous stuff removed, the Rifle class is still pretty confusing. What, for example, is a RifleAmmo and how does it differ from a Clip2? For the rocket launcher, are RocketRounds and RocketAmmo interchangeable? What's the logic behind how we give and take all this inventory? Well, the ammo situation is victim to a very inconsistent naming scheme - the idea is that in addition to the player's reserve of different types of ammunition in their backpack, each weapon has an ammo type that represents the ammunition currently loaded into it. For the rifle, the reserve is called Clip2 and the current loaded ammunition is RifleAmmo. But on the rocket launcher, the RocketAmmo is the reserve and the rockets that are loaded are called RocketRounds. Obviously this was in need of changing, so the standard is now (where ___ is the name of the weapon):

___Ammo = The ammunition that you pick up from the game environment, and that represents the player's reserve.

___AmmoLoaded = Ammunition that's in a weapon. When a weapon is reloaded, it will subtract as much as it can fit from the player's corresponding "___Ammo" stock and refill itself.

Some weapons don't require reloading, and just pull straight from the stock of ___Ammo instead.

Special keys

I haven't looked deeply into the code for many other mods so I'm not sure if this is a standard thing, but Brutal Doom handles its special key commands by giving the player an inventory item when they're pressed and leaving it up to the weapon to handle those. For example:

BrutalDoomV20 Rifle class - Key detection through inventory items
Ready: TNT1 A 2 A_JumpIfInventory("GoFatality", 1, "Steady") TNT1 A 0 A_PlaySound("CLIPIN") RIFS ABC 1 TNT1 AAAAAAAA 0 TNT1 A 0 //A_JumpIfInventory("RifleAmmo",1,2) //Goto Reload TNT1 AAAA 0 TNT1 A 0 A_JumpIfInventory("Kicking",1,"DoKick") TNT1 A 0 A_JumpIfInventory("Taunting",1,"Taunt") TNT1 A 0 A_JumpIfInventory("Salute1", 1, "Salute") TNT1 A 0 A_JumpIfInventory("Salute2", 1, "Salute") TNT1 A 0 A_JumpIfInventory("Reloading",1,"Reload") TNT1 A 0 A_JumpIfInventory("Unloading",1,"Unload") TNT1 A 0 A_JumpIfInventory("TossGrenade",1,"TossGrenade") TNT1 A 0 A_TakeInventory("RifleSpread", 5) RIFG A 1 A_WeaponReady TNT1 A 0 A_TakeInventory("UsedStamina", 1) Goto Ready+9

There are actually a few things to point out about this state of Rifle. The first is generally what the state is doing - there are a ton of empty states that are continuously checking for the presence of special inventory flags while the weapon is in its ready state. These are named a bit misleadingly - if the player has a "Reloading" item, it doesn't mean that they're currently reloading - just that the key has been pressed to start a reload. This state checks for the presence of that item, then jumps to Reload, which clears the item from the inventory - a better name for these might be "ReloadRequested", "KickRequested", etc.

Another is a common thing in the Brutal Doom codebase - Goto commands with large state-label offsets that make it difficult to follow the flow, paired with many lines like "TNT1 AAAAAAAAAAAAAA 0" that don't do anything. My best guess as to why these exist is that they were written before custom labels existed, and that having the large fields of blank states gave the author some leeway in having to only set his offsets just about right. With these present, the Goto would still work as expected even if some code was pulled around before and after the point to jump to, because the Goto would still land somewhere in the middle of that field of blank states even if it was now a couple of states off either way. It's quite an interesting solution to a problem that should never have existed in the first place.

Finally, RifleSpread is an interesting idea - as you hold down Fire, you accumulate more and more of these in your inventory, and if it's over the threshold of 5, the rifle becomes less accurate, encouraging short bursts of fire. UsedStamina, by contrast, does nothing. I removed them both.

Anyway, to handle the special keys, all of this has been taken out - most were unnecessary. I've changed reloading to use the standard GZDoom key (which was added after the initial release of Brutal Doom), meaning the Reloading class is no longer needed. I've also moved kick to alt-fire and grenade to Zoom, allowing the other special inventory flags to be removed. The tradeoff is having slightly confusing label names in the BDLiteWeapon superclass.

Weapon states

Weapon behaviour is now standardized so that they go through a set of expected labels. Because most of the weapons require reloading, the standard labels work slightly differently from the standard Doom ones - if an expected state isn't implemented on a weapon, it will jump to the BdLiteWeapon superclass's label of it instead, which prints an error message to the screen.

The expected labels are:

Spawn: Sprite sitting in the game world.

Select: Is not used! BdLiteWeapon provides this and basically skips it by making a call to A_Raise(60) - once on the screen, weapons provide their own raising animation instead. (But if the standard raise behaviour is desired in a subclass it can be implemented here.)

Deselect: As above - the animation for putting the weapon away is played, then A_Lower is called enough to make the built-in lower behaviour instantaneous.

Ready: This is where the animation for unholstering the weapon is performed. It then falls through to ReadyLoop.

ReadyLoop: Continuously check for the inventory that's Given by using the special attack inputs, and show the weapon in its ready state.

Fire: Actually does a bit more than firing.

Reload: This is the second thing a weapon attempts if it fails to fire because of having no ammunition. It can also be called directly by the player pressing Reload. It checks some conditions and starts a reload if it can.

ReloadAnimate: Where the animation for the reload takes place. The weapon animates with the player inserting a new magazine, or changing the batteries, or filling his Super Soaker or whatever, and then flow continues to ReloadAmmo. You can allow the player to interrupt it (for example, with a check for Kicking) if they suddenly find themselves in a bad place.

ReloadAmmo: Where the actual transfer of ammo happens, through a series of zero-length states that make the transfer happen instantly. This is a loop that continuously checks to see if the weapon can fit more ammunition into it and the player has one in their stock available, and subtracts from the stock and adds to the loaded ammo count. This continues until either the stock is depleted or the weapon is full - then flow returns to ReadyLoop.

Not all weapons fit this pattern - for example, on the shotgun the duties for ReloadAnimate and ReloadAmmo are intertwined, as the player character gradually reloads shells one by one. But in general, following this template makes the weapons a lot more understandable.

As a result of all this, the Rifle class in BDLite is now 81 lines down from 511. Other weapons experience similarly satisfying results!

BDLite Rifle class
ACTOR Rifle : BDLiteWeapon Replaces Pistol { Weapon.AmmoGive1 10 Weapon.SlotNumber 2 Weapon.SelectionOrder 4500 Weapon.AmmoType1 "RifleAmmo" Weapon.AmmoType2 "RifleAmmoLoaded" Obituary "%o was shot down by %k's assault rifle." AttackSound "None" Inventory.PickupSound "CLIPIN" Inventory.Pickupmessage "You got the Assault Rifle!" States { Spawn: RIFL A -1 Stop PickUp: Stop Ready: TNT1 A 0 A_PlaySound("weapons/rifle/clip") RIF4 D 1 Offset(-10, 46) RIF4 D 1 Offset(-5, 38) RIF4 D 1 Offset(-2, 34) ReadyLoop: RIFG A D 1 A_WeaponReady(WRF_ALLOWRELOAD | WRF_ALLOWZOOM) Goto ReadyLoop Deselect: RIFS CBA 1 TNT1 AAAAAAAAAAAAAAAAAA 0 A_Lower TNT1 A 1 Wait Fire: RIFG A 0 A_CheckReload TNT1 A 0 A_JumpIfInventory("RifleAmmoLoaded", 1, 1) Goto Reload TNT1 A 0 A_PlaySound("weapons/pistol") RIFF A 1 BRIGHT A_AlertMonsters TNT1 A 0 A_SpawnItemEx("PlayerMuzzle1",30,5,30) RIFF B 1 BRIGHT A_FireBullets (0, 0, 1, 9, "HitPuff") RIFF A 0 A_FireCustomMissile("Tracer", 0, 0, -1, -12, 0, random(-1,1)) TNT1 A 0 A_SetPitch(-1.3 + pitch) TNT1 A 0 A_Takeinventory("RifleAmmoLoaded",1) TNT1 A 0 A_SetPitch(+0.6 + pitch) RIFF C 1 A_FireCustomMissile("RifleCaseSpawn", 5, 0, 6, -6) RIFG A 1 A_SetPitch(+0.5 + pitch) RIFG A 0 A_Refire Reload: RIFG A 1 A_WeaponReady TNT1 A 0 A_TakeInventory("RifleBurstCount", 3) TNT1 A 0 A_JumpIfInventory("RifleAmmoLoaded",ACS_NamedExecuteWithResult("checkCapacity", 1), "ReadyLoop") TNT1 A 0 A_JumpIfInventory("RifleAmmo",1,"ReloadAnimate") RIFG A 8 A_PlaySound("weapons/empty") Goto ReadyLoop ReloadAnimate: TNT1 A 0 A_PlaySound("Reload") RIFR ABCDE 1 A_JumpIfInventory("Kicking",1,"DoKick") RIFR F 1 A_FireCustomMissile("EmptyClipSpawn",-5,0,8,-4) RIFR G 8 RIFR HIKL 1 RIFR M 3 RIFR NOPQRST 1 A_JumpIfInventory("Kicking",1,"DoKick") ReloadAmmo: TNT1 AAAA 0 TNT1 A 0 A_JumpIfInventory("RifleAmmoLoaded",ACS_NamedExecuteWithResult("checkCapacity", 1), "ReadyLoop") TNT1 A 0 A_JumpIfInventory("RifleAmmo", 1, 1) Goto ReadyLoop TNT1 A 0 A_Giveinventory("RifleAmmoLoaded",1) TNT1 A 0 A_Takeinventory("RifleAmmo",1) Goto ReloadAmmo } }