The following entries describe the new functionalities that have been implemented into Blade of Agony. It's mostly for us and other developers as an easy to find and use documentation.

This is just an introductory tour of the new features. For more accurate information on the implementation details, please refer to the Blade of Agony source code repository at

Actor Spawners

ActorSpawner (DoomEdNum 11519) spawns enemies when activated, and maintains a set number of those enemies alive (1 by default). Actors will not be spawned if a player can see the spawn point, or if a player is within a defined minimum spawn distance from the spawner (default 512 units).

By default, the spawner will spawn a single guard. Once that guard is killed, the spawner will spawn another guard, and so on. This can replace situations where an ACS script might mindlessly spam a huge flood of enemies, regardless of how many are being killed or not, by metering how many enemies are active at once. Spawned actors can be given a TID and can be set to navigate to a specific PatrolPoint TID after spawn as well.

AggressiveActorSpawner (DoomEdNum 11528) only counts actors it spawned itself, so the more of these are present in the area, the larger enemy amounts are being spawned.

WaveSpawner (DoomEdNum 11522) spawns enemies in waves; it means that additional enemies won't appear until all enemies from the current wave are killed.


An addon for Blade of Agony is a collection of GZDoom game data archives, loaded after the main game files in a specific order.

The only intended and supported way to load an addon with Blade of Agony is to drag-and-drop the .boa addon definition file over the GZDoom executable. Only a single addon could be loaded with Blade of Agony at once. At first this may seem a severe limitation, but in fact it is not: multiple mods in separate files can (and should) be easily combined into one addon in the following manner.

An addon definition consists of a "header" zip file that links the mod files together and makes sure they are loaded in proper order. It uses the extension .boa instead of .zip or .pk3. Inside the file should merely be a standard ZDoom GAMEINFO.TXT file, with contents similar to this:

IWAD="boa.ipk3" //main game archive, do not change this line
LOAD="autoautoautosave-v1.0.0.pk3", "Call_of_DOOty_IV_maps.wad", "hitler_gore_xbrz_x32.pk3", "ChexWeaponsForBoA.pk3" //additional archives which constitute the addon, listed and loaded in proper order after the main archive
STARTUPTITLE="My mod collection" //name of this addon which will be shown on startup
STARTUPCOLORS="FF FF FF", "00 00 00" //colors of the starting menu banner
STARTUPTYPE="Hexen" //BoA default, change this only if you know what you are doing

The .boa addon definition file should not contain anything else. Its purpose is similar to that of a batch script. All the game resources (maps, actor code, sprites, music, etc.) that need to be loaded with the addon belong to the external files listed in the LOAD=... line of GAMEINFO. Do not add .boa files to the LOAD=... line, only the actual resource archives are to be listed there!

If you want to combine two or more addons, just merge their GAMEINFO definitions into one (do not forget to test and check the proper load order in game).

Alarm configuration

There are three main actors that should be configured together to make realistic alarm scenarios: Alarm, AlarmSpawner, and AlarmPanel (DoomEdNum 11516, 11517, 11518 respectively).

Alarms and AlarmSpawners must be given the same TID as an AlarmPanel that will control them, otherwise they will not function unless activated via ACS. You can have multiple AlarmPanels with the same TID (e.g., so that the player can turn off alarms from a side office after the alarms are turned on by a guard in a main area). The three classes are defined and further discussed in scripts/actors/alarm.txt.

Alert Lights

AlertLight (DoomEdNum 32012) and derived classes of point lights affect the player's visibility calculated for stealth purposes. In particular, you can set up a SpotlightMount (DoomEdNum 32013) searchlight to trigger a specific script when a player ends up in its light (you can see this in action outside the prison in C2M1, where the script activates invisible machine gun turrets).


You can add a +Base.STOMP flag to any squishable (+Base.CANSQUISH) actor, for example a rat or a spider class, and the player will automatically look down and kick/stomp after running into it, Duke 3D-style.

Base enemy classes

There are two ZScript classes in Blade of Agony that define additional enemy behaviour. The Base class is used directly as a base for non-human enemies, such as sharks, rats, and the non-human bosses. The Nazi class inherits all of the functionality of the Base class, but adds additional capabilities that are used by human and human-like actors.

The two base classes have several dozen custom UDMF attributes, properties and functions. Their meaning is explained in the beginning of the file they are defined in, scripts/actors/enemies/base.txt. Please note that subclasses can also define new custom behaviour, so if you can find its definition neither in Base nor in itself, you should check the whole inheritance chain between that class and Base.

Here are some properties which may be useful for mappers (ZScript/Decorate coders should consult the code for details):

Flag effects

Certain editor flags have been given additional meaning when applied to a Nazi-class actor:

    Actors with the AMBUSH flag set will default to their 'aiming' frame - there's no behavioral change, just the actor's appearance.
  • BOSS
    Actors with the BOSS flag set will show a health bar at the top of the screen when the player is aiming at them.

Custom UDMF attributes

Available for setting on Base and Nazi:

  • user_drawhealthbar
    The underlying variable for the Base.AlwaysDrawHealthBar property. Base.AlwaysDrawHealthBar is a boolean value that is used to tell the game if the enemy's health bar should always be drawn. Does not require the BOSS flag to be set, and can alternatively be controlled via ACS or in-editor by setting the user_DrawHealthBar variable. Developer note: This sets the statnum to STAT_DEFAULT - 3 (used to limit ThinkerIterator performance hit).
  • user_targetcrosshair
    Boolean value that causes the player's cursor to show a larger yellow targeting reticle when the player's crosshair is over the actor. Assists with sniping and firing from a distance.
  • user_nocountkill
    Boolean value that sets an actor to not add to or subtract from the enemy kill counter on the automap and stats.

Available for setting on Nazi:

  • user_sneakable
    The underlying variable for the Nazi.Sneakable property. Nazi.Sneakable is a boolean that controls if the enemy is a sneakable enemy or not. Can also be set in-editor on a per-actor basis by setting the user_sneakable variable. Can be set in-game (e.g., after a cutscene using normal actors) by setting the actor's state to MakeSneakable via ACS.
  • user_static
    Actor will remain at its spawn location until the player is within 256 units. The actor will still aim and fire at the player, but won't walk toward him until the player is within the 256-unit range.
  • user_ForceWeaponDrop
    Forces the enemy's weapon to be dropped when they are killed. The enemy must call the A_UnblockAndDrop function in their Death state for this property to have any effect.   
  • user_spymsgindex
    Message displayed when player presses use to interact with a FRIENDLY Nazi. Message names in in LANGUAGE are formatted as: SPYMESSAGE[MAPNAME][user_spymsgindex value], e.g., Spy message 1 in C3M1 would be the SPYMESSAGEC3M11 lookup.
  • user_spyitem
    Item given to player when you press use to interact with a FRIENDLY Nazi.
  • user_activatewhenshot
    If an enemy is +DORMANT and has this variable set to non-zero, they will become non-dormant if fired on by a player.
  • user_cansurrender
    Whether this Nazi type can surrender to the player or not (uses Death.Surrender state).

Color Grading

Color Grading in Scripts

For transitioning to a color grading directly in a script, the following two scripts can be used:

   ScriptCall("ColorGradeThinker", "TransitionTo", 0, 1, 140);
ScriptCall("ColorGradeThinker", "TransitionTo", 0, 2, 700);

The first one makes a seamless transition to the color grade with the index number 1 in 140 tics, the second one a very slow transition to index number 15 (max) in 700 tics (which is about 10 seconds)

   ScriptCall("ColorGradeThinker", "Set", 0, 1);

Instead of transitioning to a color grading in a certain amount of time, you can also directly set it in a script. The above line sets the color grading instantly to the look-up table index 1.

Color Grading on Lines

ColorGradeSet immediately sets the supplied LUT index on the player who called the script.

   ACS_NamedAlwaysExecute("ColorGradeSet", 0, 9); // Instantly sets to index 9

ColorGradeTo transitions to the supplied LUT index over the course of the specified number of tics. If a transition is already taking place, the LUT is queued to transition to after the current transition is done.

   ACS_NamedAlwaysExecute("ColorGradeTo", 0, 4, 70); // Index 4, in 70 tics (what is standard if set to 0)

ColorGradeBetween is meant to be used as a linedef action. It will transition to a LUT depending on which side the line is triggered from. This is useful to mark a LUT "boundary" between two areas without having to use two linedefs with a ColorGradeTo call. 

   ACS_NamedAlwaysExecute("ColorGradeBetween", 0, 0, 4, 70); // Repeatable and player walkover

Color Grading Types

The textures/pplut.png look-up table has a wide variety of different color gradings for different situations.

Grading Index Type
0 Standard
1 Old photography (brown with low contrast, red to yellow stay, other colors desaturated)
2 Ending Sequence (light, blue to cyan, green to lime, low contrast)
3 C1M4 (blue teint, high contrast, dark look)
4 C2M2 (blue saturated, green to olive and weak, red to purple)
5 C3M1 (green tone, higher contrast)
6 C3M0_A (colder white, higher contrast, darker middle tones)

More Grading Types will be added soon.

Compass markers

These actor types will show up on the player's compass automatically when they are placed in the map. A good reference map for how these should be implemented is C3M2.

As a general rule, red Primary Objective Markers (ObjectiveIcon, DoomEdNum 21238) should be used to mark mission-critical areas that must be visited, and orange Secondary Markers (Exclamation and ExclamationTouchable, DoomEdNum 21237, 21238) should be used to indicate necessary specific actions/conversations/pickups.

Additionally, any other actors that are tagged with a specific TID can be made to show up as a grey dot on the compass via ACS by calling the BoA_CompassQueue script:

   ACS_NamedExecuteAlways("BoA_CompassQueue", 0, thingTID);

Or, you can specify a specific image ("ICON", here) to be used as the icon.

   ACS_NamedExecuteAlways("BoA_CompassAddIcon", 0, "ICON", thingTID);

Note that calling this script will add all actors with the matching TID to the compass.

You can also make an actor show itself on compass directly from ZScript by calling static BoACompass.Add and BoACompass.Remove methods.

Primary Objective Markers

These actors are used to indicate the general area of a primary objective or significant waypoint in the map. They should be used sparingly, so that seeing a red exclamation pointon the compass doesn't lose importance; only major mission-related locations should have a red marker.

These actors can be spawned as dormant, then activated via Thing_Activate in ACS, or they can be spawned normally and hidden or removed later using Thing_Deactivate or Thing_Remove.

Take special care to properly activated/deactivated/removed markers via ACS as the mission progresses in order to keep the path of progression clearly indicated on the compass... In other words, as the path to a specific objective becomes available to the player, the marker for that objective should become active, then, once the objective is reached, the marker should be deactivated or removed.

These don't have to correspond perfectly to the listed objectives. For example, most maps essentially have a final objective of "Find Douglas/Ryan/Ascher in order to return to HQ" upon completion of all of the primary objectives; even though "Finish the map" is not a listed objective, there should probably still be a red marker placed once that becomes the player's next goal. As another example, if a single objective requires multiple similar actions across the map to complete (like placing explosive charges), each of those locations should be marked with a red marker.

Secondary Objective Markers

These actors are typically used to mark NPCs that can be talked to, switches that should be activated, etc. These markers will not be added to the player's compass until the player is within 2048 units and has a line of sight to the marker.

Mission/Quest Item Markers

All items which inherit from the CompassItem class, as well as any disguise/uniform pickups, will automatically be added to the player's compass. These actors will use their inventory icon as their compass icon; if one is not set, they will use their spawn state sprite as their icon.

Base- and Nazi-class Enemies

By setting the user_oncompass UDMF attribute of an enemy actor to 1, that actor will be added to the player's compass automatically. These actors will use a grey dot as their compass icon. The icon for actors that are also tagged with the user_drawhealthbar UDMF property will be tinted with color to indicate the health level of that actor.

ActorSpawner-spawned Enemies

By setting the user_oncompass UDMF attribute of an ActorSpawner actor to 1, all enemies spawned by that actor will be added to the player's compass automatically. These actors will use a grey dot as their compass icon. See the initial machine-gun turret fight in C3M2 for a working example.

Controllable actors

Actors which need to be controllable by NPCs inherit from ControllableBase. It is implemented in a simple manner: when an actor (e. g. a WGuard), which can control the actor in question (CTurret, an machine gun turret), the controller actor is removed, and the controlled one is replaced with another actor (a manned turret, MGTurretSoldierW). It is also used for mechas.

Culling Decorations

The EffectsManager system automatically tracks most special effects on the level (this includes weather effects, grass, even trees and vehicles) which could become resource-intensive, fades them out and removes from the map when the player is too far away, and makes them reappear when the player again comes close.

To use the system for a specific type of a 'dynamic' actor like an explosion or a snow particle, inherit it from a subclass of EffectBase. Use CullActorBase instead for large and mostly static decorations.

Custom Crosshairs & Hold-to-activate Lines

The player's crosshair can be set via ACS or by setting the user_crosshair UDMF property on a linedef that the player is directly looking at.

Setting via ACS

The player's current crosshair can also be changed via ACS by setting the player's crosshair property to a valid crosshair number, as discussed above. This change takes precedence over any line-set crosshair. This can only be reverted by setting the crosshair variable to zero.

  • This command will set the crosshair to the XHAIR90 graphic:
    SetUserVariable(0, "crosshair", 90);
  • This command will restore the crosshair to the player's set crosshair:
    SetUserVariable(0, "crosshair", 0);

Setting when looking at a line

Any blocking line (in essence, any line that could be hit by a hitscan) can be set up to show the player a custom crosshair/icon by setting the user_crosshair UDMF property on the line. The value of this property must correspond to a thing class or a text string - e.g., a "RepairKit" string will display the corresponding actor graphic. The crosshair will be restored to normal once the player is no longer looking at the line.

This effect can be used on an activation line to give a hint as to a required inventory item, key, etc. (as with the generator repair points in C3M0_A).

Custom UDMF Attributes
  • Sets the crosshair of the player when the player's crosshair is over a portion of the line (can be thing class or text)

"Hold-to-activate" lines

Within Blade of Agony, player control handling has been altered so that holding down the 'use' button while facing a 'repeatable' activation line will cause that line's special to be run once per tick for as long as the 'use' button is held (and not just once per 'use' press, as is normal). In most cases, this change doesn't alter gameplay at all, however, scripts can be written to take advantage of this implementation.

The line to be activated must be set up to be activated When player presses use and as Repeatable Action, and must be assigned a unique id/tag.
If the portion of the line that you want to be activated is the midtexture rather than an upper or lower section, you must set the line to Block Everything or Block Hitscans.

The line's special must be set to 80 - Script Execute, and pointed to a custom script that takes, at a minimum, the line's id as a parameter.

  • A generic "hold-to-activate" script will look something like this:
    Script "ActivateGenerator" (int lineid)
       if (ACS_NamedExecuteWithResult("Activate", lineid, 5)) // The 'Activate' script takes line id and activation time in seconds as parameters.
          /* Activation complete.  Do post-activation stuff here. */
       else { PlaySound(0, "effects/repair", CHAN_AUTO, 0.7); } // Play repair sounds

Day-night cycle

C3M0_A originally had a constant day-night cycle that would allow the player to have a daylight break from zombie spawning, with all of the zombies burning up in the sunlight.

The status bar code to override the actual map time and display the "in-game" time is still in place. Global ACS integer 60, named time in boalib.acs, controls the time value set here: 0 or 1.0 is midnight, 0.25 is early morning, 0.5 is mid-day, etc.

The code which rotated the skybox and changed the brightness during the cycle was reused for the map ending.


DebugEventHandler is a tool which uses network events to provide information about the current game state for testing: a detailed list of remaining enemies or treasures on the level, the player's current speed, and current skill of the game. Enter netevent cmdname in the console to use one of its commands.

For testing fonts, use testfonts or testuifont network events (please note that executing the former can take a few minutes, as it checks every symbol present in the full several-megabyte CSV file).

Dialogues and radio messages

For talking with NPCs the game makes use of Strife-style dialogues. They are already quite flexible, but Blade of Agony defines its own conversation menu (see ZScript classes ExtendedConversationMenuBase and BoAConversationMenu) to change the default ZDoom design of the dialogue box and provide further options for customization.

If the NPC wants to contact the player from afar, a radio transmission is used. The message is printed character-wise in a green box near the top of the screen. These are implemented in the BoADialogue ACS script, and are easy to incorporate into maps. Keep in mind that, in contrast to the dialogue messages, which can be scrolled and have an essentially unlimited maximum length, a radio transmission can overflow the box if it is too long.

Dragging objects

Objects being draggable by the player is an implemented but switched off feature. To take the object into their hands, the player should hold use while manually holding the crouch button, and they will be able to maneuver and put the object into some other place. To let go of it, just stop holding either the crouch or the use key. The mechanic works on PUSHABLE objects and corpses of regular enemies, so it could be useful for puzzle and stealth maps. You can enable and disable dragging for a player by changing the dodragging variable defined in the BoAPlayer class.

Editor thing numbers

Editor numbers (DoomEdNums) for actors which can be placed in maps cannot be defined in a ZScript file. Blade of Agony uses MAPINFO for that purpose. If you need to know a DoomEdNum of an actor, you should search for it in mapinfo.txt. If there is none, the actor cannot be placed on the map directly from the editor, only spawned via scripts.

Falling missiles

The A_ArcProjectile method fires a projectile subject to gravity and automatically calculates and applies proper vertical speed to it, so it would hit the target rather than under-/overshoot it. The codepointer is based on A_SpawnProjectile and takes two additional arguments: maxdistance for limiting the distance the projectile would fly across the map, and additionalHeight for offsetting the projectile's Z coordinate with respect to the target.

Frightening hazards

An actor which inherits the Base subclass (for example, a regular enemy, or a PlayerFollower) will fear GrenadeBase actors: if it ends up too close to one, the Base actor will try to run away from the grenade.


LaserShooter spawns a 3D model beam actor once and then only moves and rescales it afterwards, which is less performance-demanding than destroying and respawning it every tic. You can change the color and style of the ray by inheriting from LaserBeam and adding a different texture to the new actor.

Lump parsing

GZDoom's Wads namespace allows to process arbitrary lump data from ZScript code. Blade of Agony's FileReader class contains a text data parser which is able to easily process lumps which contain <key> = <value>; statements in structured C-style nested brackets. Using this format helps to organize complex information for usage in code more carefully. You can find examples of it in the data folder.

Managing translations

It is a very difficult task for a developer team to manage the consistency of translations for a game still being in development, as the baseline strings tend to get changed, added, or removed. The large scale of the project has an additional negative impact on it. However, the situation could be improved by 1) choosing a standard of distinction between up-to-date text lines and outdated ones, 2) adding a system which controls the state of the translation.

The Blade of Agony team used a Google Docs spreadsheet for storing and editing the translations, so 1) was accomplished simply by requiring that a person who changes an English (the default language) cell in the spreadsheet marks all other cells in that row with red borders (note that a CSV cannot hold any additional information besides the cells, so the original table was stored in another format and was exported to CSV every time we wanted to update the translation file). After that a translator should add their corrections to the previously translated text and erase the border on the cell manually.

As for 2), two types of counters were added for every language, and we shall now explain their purpose and the way they were done.

Important note 1: even though the counters work for English texts too, its column (or one of another primary language you are developing the addon for) should be always complete. If there is no fully complete exemplary column, the translation process quickly becomes a mess.

Percentage counter

In the release version, its instances could be found in cells D7:M7. This counter displays the fraction of the messages that are currently translated in this language. Every line with a nonempty Identifier is considered to be a text string which is to be translated. Languages correspond to columns, so the counter for a language just finds the number M of cells in the column which are not blank and correspond to a nonempty Identifier, and displays M/N*100%, where N is the total number of lines having a nonempty Identifier in the spreadsheet.

The formula used for E7 is =COUNTA(E9:E2884)/$C7. N is to be used in multiple counters, so it is counted in a separate cell C7 by a formula =COUNTA(B9:B2884). (In our spreadsheet, the B column contains the identifiers, and E contains German texts.) Same works for all other languages.

The cells could be coloured via conditional formatting (choose Format->Conditional Formatting in the drop-down menu and add a red format rule for Value is less than 100% and a green one for Value is equal to 100%).

Important note 2: some languages may require additional cells for displaying specific phrases with proper wording. This is often the case for strings that contain some quantity and have to be 'glued' from multiple parts. For example, 3 Flakvierling guns left sounds well in English, but languages from the Slavic family would rather use an additional prefix string to change the word order: Осталось 3 зенитки, or Zbývají 3 flak kanony. The prefix is not needed for English, but if it is not added, it would count towards incomplete translation strings, distracting the translator from lines which need their work for real! The simple solution we used was to add one space character into such strings and distinguish them from blank cells by a special turquoise colour filling.

List of untranslated cells

These counters rest in cells D5:M5. Such a cell lists all Identifiers which do not have a translation in a given language. A line would count towards this counter if and only if both of these conditions hold: 1) the cell has an empty entry in the corresponding language, and 2) the Identifier of the line is not empty. That means, e. g., that you can have a line with comments related to the surrounding cells, or a subsection delimiter, and it will not affect the counter, as long as you do not write something into its Identifier.

The formula used for E5 is =IFERROR(JOIN(", ";FILTER($B9:$B2884;ISBLANK(E9:E2884);$B9:$B2884<>""));"*NO EMPTY CELL*"). Let us take it apart now:

		", ";

The FILTER function call takes all cells from the Identifier column ($B9:$B2884) and returns those which adhere to the previously stated two conditions simultaneously: 1) this cell must have a blank German translation (ISBLANK(E9:E2884)), and 2) the Identifier of the row must be nonempty itself ($B9:$B2884<>"").

The JOIN function attempts to make a list of the Identifiers that were returned by FILTER. If there are no such identifiers, the function returns an error, which is then caught by the outermost IFERROR; in that case, the "*NO EMPTY CELL*" string is displayed in the counter cell. If an error does not occur, the JOIN function would have assembled the list correctly, and the counter cell will show the list of untranslated Identifiers.

Miscellaneous Script Tools

ZScriptTools and ACSTools classes contain several static methods which need to be used from different contexts. As well as some mathematical ones, there are also helper functions which allow to control subtle ZScript functionality via ACS. Examples include trigonometry and projectile intercept code, Z velocity calculations for falling projectiles, font testing, checking if an actor has alternative death states, and even getting proper declensions for languages that need them or writing a positive integer as a Roman numeral.


A recently-added feature of ZScript, mixins are class definition snippets which can be automatically inserted into a class when GZDoom processes the modification's code. Mixins become useful when the same behaviour is wanted for several unrelated classes, e. g. CKPickup mixin is used for multiple Commander Keen pickups, which include health, gem keys, weapons, and other inventory items; it is simply not feasible to make all of them inherit the same base class to write the common properties in.

Using mixins is more convenient than plain copy-pasting, because a developer would only have to change the desired property in the mixin itself, not search for its instances across the whole modification (likely introducing bugs). Other instances of mixin usage include SpawnsGroundSplash for missiles and GiveBuoyancy for 3D model objects.

Paper texts and in game pop up menus

Showing text on papers is achieved by calling a ViewItem menu. The texts can be localized, and the formatting is done automatically, which means you won't have to think too much about spacing and word wrap.

Most of the papers presented in-game are instances of the TextPaper class, they are not placed into the inventory. Paper classes which inherit from TextPaperCollectible can be taken by the player and later activated again from the inventory (they are lost upon exiting the level); for instance, they are used for the secret hints. The texts are obtained dynamically each time the player starts reading, and you can add any necessary special behaviour into an overridden GetDisplayString method in your text paper class.

The ViewItem menu supports various paper and font styles, and in your own modifications you can get even more by inheriting a new menu class from it and overriding its methods, and afterwards creating new paper classes based on InteractiveItem which will use your new menu class. Here are some more contexts of using ZScript menu classes in Blade of Agony:

  • IWADNotice is the message you get if you try to load the game alongside a Doom IWAD;
  • TextScroll shows the letter sent by Douglas to Blazkowicz before the first episode;
  • BoAInfo shows F1 help messages;
  • CombinationSafe shows the safe unlocking minigame;
  • Finale is the menu which is shown after completing an episode (or a special level).

Parallel items

ParallelInventory class is used for special inventory tokens, the amount of which needs to be aligned with another inventory class. Blade of Agony uses it for green Astrostein grenades: you can collect several of them in Astrostein and then use them in subsequent levels. Each one gives a regular GrenadePickup and an AstroGrenadeToken which means that the player picked up an Astrostein grenade, not a regular one (and will throw it first).

Player Followers

An actor that will stand dormant until it sees a player, then will follow that player. The actor can be set to carry a specific weapon which can be changed via ACS, and commanded by the player to hold a specific position or to assist them in combat. See scripts/actors/playerfollowers.txt for details.

DoomEdNum Type
12710 PrisonerAgent - Agent Ryan as a prisoner (No weapon)
12740 AgentArmed - Agent Ryan after returning to duty (Luger)
12741 AgentArmedMP40 - Agent Ryan after returning to duty (MP40 - but with no new sprites)
12742 DouglasArmed - Douglas (Pistol)
12743 SoldierArmed - Generic Soldier (Pistol)
12744 SoldierArmedRifle - Generic Soldier (Rifle)
12745 AscherArmed - Lt Ascher (Rifle)
- Dog Follower - A German Shepherd (Melee, with handling to patrol the general area instead of standing still)


Player statistics

As an alternative to Doom's per-level intermission screens, MapStatsHandler gathers information about the player's current advancements in the current episode and is able to display all of it at once. The statistics include treasure, secret and kill counters, level completion time, obtained special items, and so on. The tally screen can be brought up by running the ShowStats ACS script.

Please note that MapStatsHandler only counts levels which are named as CxMx, and adds up statistics from levels with first four characters identical, such as C2M6_A, C2M6_B and C2M6_C, because such maps are considered parts of a single mission, rather than separate ones.

Powered items

PoweredInventory is the base class for inventory items that can be activated and use up a resource (of the PoweredInventory.Fuel class) from the player's inventory over time. Examples include LanternPickup and Minesweeper. The current remaining fuel amount is shown as a percentage over the item's inventory icon, Blood-style.

Remembering the player's inventory

In some situations you have to restore the player's inventory to its original state, e. g. after a training course or a secret mission. This is done by means of an InventoryHolder. It is a non-actor class, so it needs to be attached to an inventory token which will determine when to restore the items. See RyanToken and HQTrainingCourse for examples of its usage.

Screen Shaders

There are various post-processing shaders for the player screen that can be used at any time as effects to support certain situations. Just give or take the corresponding tokens to the player inventory to activate or deactivate time ingame.

Shaders marked with an asterisk* are used in a following way: to enable it, give 2 of the corresponding ShaderControl subclass item to the player; to disable the shader, take 1 of the shader control item away, so that the player only has 1 of said item.


Distorts the sight after an explosion. The amount of the item given is equal to the desired duration of the effect in tics, e. g. if you give 35, the effect will last one second. The effect will stack if you give more when there's already a blur in action. There's also a ~0.5 second fade in to the blur effect, then a slow fade back to normal over time once the blur is in place.


Adds an old video effect to the screen. (???)


Adds an shake effect to the screen, works well for situations when being on a truck or a minecart.

ColorGradeShaderControl (?)

Adds a color grading effect to the screen. (???)

HeatShaderControl (?)

Adds a heat effect to the screen. (???)

Settings and CVARs

In addition to standard GZDoom CVARs, our team has added many more to change Blade of Agony's behaviour. These are defined in CVARINFO and have self-descriptive names, mostly starting with a boa_ prefix. If you are interested in the functionality of a specific CVAR, there are comments in CVARINFO which can give a more detailed hint on its purpose, and you can also search the WolfenDoom repository for its name.

Static Skyboxes

SkyViewpointStatic (DoomEdNum 19990) is a derivative of the skybox viewpoint actor that moves in relation to the player's movement, giving the illusion that the skybox is a part of normal level geometry.

The map author can specify the amount that the skybox's movement is scaled relative to the player's motion, allowing for a very small skybox to appear much larger and closer in relation to the player.

By default, the "anchor", or the neutral origin point of the skybox view, will be the player's spawn location. Alternatively, the viewpoint can also be "anchored" to a SkyViewpointAnchor (DoomEdNum 19991) actor by specifying the TID of the actor to use as the anchor.

Steam jets

A SteamSpawner will spawn lots of small actors (in this case steam particles) in a cone shape. The particles can be damaging or not, and can fade out or live until a collision, just keep in mind that a large number of actors may slow down the game significantly.


Blade of Agony tanks are constructed from three 3D model parts: the hull, the turret and the main gun. The turret can be rotated around the vertical axis, and the gun can be moved up or down. The main tank actor is inherited from TankTreadsBase.

Tanks can be NPC-controlled or player-controlled. TankBase is the parent class for NPC-controlled ones, it should be linked to the proper TankTreadsBase descendant. Players can control actors which inherit from TankTreadsBase and have a TankMorphPowerup defined which will transform the player into a TankPlayer subclass. To have different firing behaviour (e. g. change the machinegun sounds or the shell reloading time) you should add your own MorphWeapon to the new morphed player class.

Tiltable decorations

These inherit from Tiltable and are used in situations where the playing space is tilted, such as the end of C3M5_C, typically for tiny decorations like teapots and plants. The actor will randomly slide from the surface it is resting on, and break if it fell from a considerable height (such as from a table or a cupboard).


Blade of Agony uses a CSV spreadsheet, a powerful means of localization which originates from GZDoom. It is superior to the old ZDoom format which used a separate file for each language the game was meant to be translated into, because it is much easier to control the status of translations compared to the base text and update them in one file. To work with contents of such a spreadsheet, you can use any office package, a local installation or an online application like Google Spreadsheets (which we used for the ease of working with multiple translators).

The string names which are used by the game code are placed into the Identifier column, and the texts are put under the corresponding languages' columns. Their names are two- or three-letter codes of the language or its dialect. If you want, you can translate Blade of Agony or your own addons into other languages; you should refer to the GZDoom repository for the proper language code.

To use the contents of the translated string in some ACS functions like HUDMessage, you can refer to them with an l: prefix. Prepend a dollar sign to use the string in DECORATE and ZScript properties. In ZScript functions you should use the StringTable.Localize method.

Useful tools

Here are some of the most notable editing tools that were useful for creating Blade of Agony, with brief explanations for their use cases.


  • Paint XP

For general pixel art.

  • RotSprite

Used for scaling of lowres 64x64 enemies and other sprites into double resolution, as a base for further editing. Also used for sprite rotation, like enemy rolling animation base (45 degree steps rotation). Its advantage is that it doesn't add new colors and keeps the palette, so it is good for further recolorings.

  • ScalerTest

xBRZ scaler that replaced RotSprite at later stages of the project as a base for hires art. Gives much better result, though less sharper and with outline artefact, also adds much more colors which can affect retro look.

  • RecolorBMP

Small and fast 8-bit recoloring tool used in Wolf3D modding that predates similar SLADE 3 recoloring capabilities. Ability to fully control recoloring and save recoloring palettes.

  • SpriteMaker

Tool used in Wolf3D modding mainly for 8-bit (or to 8-bit) palette conversion. For example - many BoA enemies used Hexen palette (before being converted to true color), but to match Soviet uniform greens we had to use Operation Bodycount palette.

  • PixelFormer

Nice pixel art tool that supports layers and transparency, was used to make missing font letters.

  • XNView

Simple but powerful free image editor used for reducing color count in sprites (to match retro look better) and to do batch file conversions.

  • Aseprite

One of the best pixel art tools for retro modding/pixel art, the only downside - it's not free.

  • GIMP

Very powerful all-purpose image editing tool. Useful for 'professionally' editing sprites and textures, but it tends to have performance issues with lots of opened images, even when they are small.

  • Paint.NET

Quite a simple program, but it supports translucency, layers and loading multiple files at once, and does not suffer from so much overhead as GIMP when doing so. It is quite convenient to draw missing font characters.

  • ZFontConverter

Talon1024's program that converts BMF/FON2 fonts to GZDoom PNG format. Storing fonts in PNG makes it way easier to edit, add, or replace individual characters.

  • Photoshop

For editing various textures and UI graphics.

  • SpriteIlluminator

Allows to create normal maps for sprites and textures and 'bake' them inside for creating believable lighting on them.


  • UDB

A Doom map editor from the Doom Builder family. Very useful for UDMF maps. It has lots of features, and gets quite frequent updates.


  • Audacity

Powerful editing tool to work with various sound and music formats.

  • LameXP

An audio converter which supports lots of input formats.

3D models

  • Misfit Model 3D, Maverick Model 3D

Lightweight Qt-based OpenGL 3D model editors. The support of Misfit Model 3D is discontinued, and the Maverick version gets bugfix updates.

  • Milkshape 3D

A low-poly model editor which supports 70 different formats.

  • Blender

A well-known professional multi-purpose tool for working with various 3D data.

Code and binaries

  • ACC

A command-line interface ZDoom ACS compiler. Often included in map editor distributions, but a separate installation would be useful for compiling library scripts that are to be executed on multiple maps.

  • Visual Studio

The new custom launcher was done as a C/C++ Visual Studio project.

  • HxD

A hex editor that is able to open and properly edit the raw contents of binary files.

Game resources

  • NIFSkope

For opening, editing and repacking Gamebryo NIF 3D model files.

  • XModel Exporter

Exports xmodel files used in the Call of Duty game series.

  • Dragon UnPACKer

An unpacker that works with resource packs of over 80 different games.

  • Alferd Spritesheet Unpacker

Splits a large sprite sheet into individual image files.


  • OBS

Flexible program for streaming and recording gameplay videos.

  • Avidemux

Simple video editing software, useful for e. g. cutting unnecessary gameplay video segments.

General-purpose tools


Archive editor for Doom and other 90's games. Was not too useful for Blade of Agony in its late development stages, but quite convenient for at the very least adding offsets to images in a directory.

  • Format Factory

A multimedia file converter which supports various image, sound and video formats.

Walkable 3D objects

ClimbableZone is a helper actor usually placed near a ladder which needs to be climbable by players. They can be scaled or stacked vertically to allow longer ladders.
A pair of a RopeSpawner and a RopeSpawnerTarget with the same tag will spawn a rope between them which can be walked on. You can also use a CableSpawner or a BridgeSpawner to get a walkable cable (it is thinner than a rope) or a bridge (which is substantially wider).

We use cookies on our website. Some of them are essential for the operation of the site, while others help us to improve this site and the user experience (tracking cookies). You can decide for yourself whether you want to allow cookies or not. Please note that if you reject them, you may not be able to use all the functionalities of the site.