Souls games are well known for their boss fights. Acting as the gatekeepers for many areas in the games, boss fights challenge players with a myriad of attacks, effects, and set pieces. It's not surprising then that boss fights are rather complex, with elements that touch almost every element in the game engine. In this article, we will examine the tutorial boss fight of DS3: Iudex Gundyr, and examine all the systems used to implement this fight including map placement, event scripting, animations, effects, and AI. This in turn will also serve as an example driven introduction to the many systems used to implement Dark Souls and give some intuition on how to mod them. With that said lets get started by looking at the files that implement the character.
Iudex Gundyr Game Assets
Every character in the game-players, enemies, bosses, npcs, and even the bonfire-have a unique character id in the form of cXXXX. The assets for all these characters reside in the chr/ directory when you unpack the game's assets. Iudex Gundyr's character ID is c5110, and as you can see there are four "bnd" files associated with this character:"bnd.dcx" files are simply a proprietary archive format that groups together a set of related assets and then compresses them. They can be thought of as analogous to a "zip" file (or more specifically a "tar.gz" file). These archives can be unpacked and repacked using a tool called yabber. The purpose of each of the archives is the following:
- c5110.chrbnd: The main character file. This contains the FLVER (Dark souls 3D model format) for the character, the Havok physics configuration file (the physics shapes or "hitboxes" of the character your weapon actually collides with along with ragdoll data), and the Havok cloth physics configuration data if the character uses cloth physics (most of them do).
- c5110.anibnd: The animation data for the character. All the Havok animation data for the character including walking, idles, and attacks are included. Each animation is identified by a three digit moveset id and a six digit animation id. In addition to animations, the archive also contains a Havok skeleton, which is linked to the skeleton in the 3D model data, and a TAE file, which defines a variety of gameplay events tied to animations (more on this later).
- c5110.behbnd: Havok behavior setup. This defines the character animation control setup using the Havok behavior system. This system is fairly complicated and not well understood, but the important thing to note is this largely implements low level sequencing of animations and combos and blends animations together. This low level behavior setup is pretty much copy pasted between enemies and thus isn't that important in the context of understanding how an enemy works, as it's basically used to implement functionality used by the higher level AI and TAE systems. If interested in digging more into these systems, a tool called hkxpack-ds3 in the modding discord can convert the contained havok files to XML and back, but even in XML form they are unwieldy to understand and edit.
- c5110.texbnd: Additional texture data. Characters with a large amount of texture data often have this separate archive to store model textures in. Nothing too exciting. Yapped can convert the embedded tpf file into dds files for editing with a program such as Paint.net.
Enemy Map Placement and Setup
Assets for the boss are great and all, but one might notice that there's still a lot missing in regards to what's needed to make a full boss fight. There's nothing that says how the enemy will behave (its AI), when it is active, where it is on the map, how much HP it has, and many other things. Our next step in understanding the inner workings of the boss take us to the map files, which control enemy placement among other things. Map files have a four part ID (mAA_BB_CC_DD) and are stored in a complex proprietary format known as MSB (presumably "Map Studio Binary" in reference to what From calls their internal map editor). Maps can be opened, viewed, and edited to some degree in a program called Dark Souls Map Studio (yes I ripped off their name so sue me). Opening the Cemetery of Ash map (m40_00_00_00) in Map Studio and going to the Iudex Gundyr arena you will see roughly the following:
You can see Iudex Gundyr in his full glory placed on the map. You'll notice that the form displayed in Map Studio is his transformed phase 2 form. This is because the model file contains the data for all his forms and the game actually hides the mesh for his phase 2 form in phase 1. More on this later.
More importantly, one will note that there are actually two Iudex Gundyrs. In the actual map, they are both placed in the exact same spot, but I moved one of them so that both are visible in the screenshot. Why are there two of them? One for each phase? Close, and some boss fights are setup that way, but you might also remember that Iudex Gundyr isn't the only Gundyr you fight. But how can this be? Isn't Champion Gundyr a different boss with different animations, AI, a different location, and doesn't have that eldritch abomination for a phase 2? Well you'll see that the same character model can allow for a very different and much more difficult fight based on how its configured. This is also how you can recycle existing bosses to make unique new fights when modding. As for Champion Gundyr being in the same location as Iudex Gundyr, well it may or may not be a surprise that Cemetery of Ash and Untended Graves are both the exact same map with different lighting and enemy placement depending on what state the map is. Dark Souls 3 has a largely cut mechanic known as "ceremonies" which allow maps to exist in different states and different enemies, objects, and lighting can appear depending on the state. It was intended to take a much larger role in DS3, but was eventually cut and this is one of the only remaining uses of the system in the game (the other being the storming state in Archdragon Peak).
Anyways, on the right you will see many properties for configuring the enemy. Most of it is uninteresting and configures some settings such as the position, the character model used, some configuration that determines when and how its rendered by the engine, and other things. There's a few important fields though: one is the EntityID, which is a unique identifier for referring to the entity in event scripts (more on this later). There's also some fields that reference some so called "params":
In case you don't know, params are a large amount of gameplay variables organized into various spreadsheets that are used by the game for various functions. Each param "row" has an identifier and each row contains a large amount of fields of various data that often configure how a particular game system works or is balanced. In this case, "think param" refers to a set of AI related variables such as configuring the range and field of view an enemy can spot you and aggro with, how it will cooperate with other enemies, when it will give up and stop chasing you, what parts of the level its capable of navigating, and most importantly what AI script it is using.
NPCParam param configures some non-behavior related data for an enemy, such as its base health, how fast it can turn, what loot it drops, defenses and resistances, what meshes in the model are visible by default (how phase 2 mesh is hidden), and much more.
If you look at the two different Gundyrs, you'll notice that the row ids for their thing and NPC params are different, and the data for these two params are also quite different. Champion Gundyr for example has a lot more HP and a different attached AI script. The param differences alone account for most (but not all) of the differences between the fights. To learn more about the other differences though, we will need to deep dive into the other half of Souls maps: event scripts.
Event Scripting Boss Setup
When playing the game and fighting bosses, you'll probably notice many scripted events: walking up to an empty but ominous arena, the intro cutscenes playing, a fogwall before the boss spawning when you die, phase two transitions, music playing and music changes, the deacons trying to curse you, and the victory banner when finally killing a boss. All of these and more are all controlled by event scripting-one of the most challenging but rewarding parts of souls modding.
While Demon's Souls used Lua for event scripting, the subsequent Souls games (with the notable exception of DS2) use variations of a proprietary bytecode scripting system popularly known as "emevd" based on the file extension given to the event scripts. In the event/ directory, each map has its own emevd file which defines all the events that can happen in each map. Each "event" has a unique identifier for it, can be started by other events, and executes "line by line" until its finished executing in which it can either finish permanently unless started again by another event, restart from the beginning right away, or reset when resting at a bonfire. Another core concept of event scripting is that of "event flags" which are used to hold state about the world. Each flag is binary-on or off-but groups of flags can be used to represent more complex state. Depending on the ID of the flag, it may also be automatically saved and stored in the save file, allowing persistence between save and quits. For instance, many flags associated with actually fighting the boss such as phase transitions and other boss events aren't saved in the save file, but when you kill a boss the associated flags set when it dies are saved.
Other than manipulating flags, the event bytecode has many game specific commands to do stuff needed to implement the game: it can show banners, create boss health bars, hide and show enemies, make enemies invincible or not, signal the AI scripts to take certain actions, force enemies or objects to play certain animations, spawn particles, warp the player or enemies, play cutscenes, enable or disable enemy AI, and much more. Events also have the concept of conditionals: an event may block and not execute further unless certain conditions are achieved such as the player walking into a specific region defined on the map, an enemy (or boss) has a specific amount of HP, a certain player action was triggered, and more. These conditions can also be immediately evaluated in the script to determine whether certain event commands should be made depending on certain circumstances (an example is the script will buff a boss' HP when there is a summon, but will otherwise skip the command that does the buff).